post-policy.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2017 MinIO, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package s3cli
  18. import (
  19. "encoding/base64"
  20. "fmt"
  21. "strings"
  22. "time"
  23. )
  24. // expirationDateFormat date format for expiration key in json policy.
  25. const expirationDateFormat = "2006-01-02T15:04:05.999Z"
  26. // policyCondition explanation:
  27. // http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  28. //
  29. // Example:
  30. //
  31. // policyCondition {
  32. // matchType: "$eq",
  33. // key: "$Content-Type",
  34. // value: "image/png",
  35. // }
  36. //
  37. type policyCondition struct {
  38. matchType string
  39. condition string
  40. value string
  41. }
  42. // PostPolicy - Provides strict static type conversion and validation
  43. // for Amazon S3's POST policy JSON string.
  44. type PostPolicy struct {
  45. // Expiration date and time of the POST policy.
  46. expiration time.Time
  47. // Collection of different policy conditions.
  48. conditions []policyCondition
  49. // ContentLengthRange minimum and maximum allowable size for the
  50. // uploaded content.
  51. contentLengthRange struct {
  52. min int64
  53. max int64
  54. }
  55. // Post form data.
  56. formData map[string]string
  57. }
  58. // NewPostPolicy - Instantiate new post policy.
  59. func NewPostPolicy() *PostPolicy {
  60. p := &PostPolicy{}
  61. p.conditions = make([]policyCondition, 0)
  62. p.formData = make(map[string]string)
  63. return p
  64. }
  65. // SetExpires - Sets expiration time for the new policy.
  66. func (p *PostPolicy) SetExpires(t time.Time) error {
  67. if t.IsZero() {
  68. return ErrInvalidArgument("No expiry time set.")
  69. }
  70. p.expiration = t
  71. return nil
  72. }
  73. // SetKey - Sets an object name for the policy based upload.
  74. func (p *PostPolicy) SetKey(key string) error {
  75. if strings.TrimSpace(key) == "" || key == "" {
  76. return ErrInvalidArgument("Object name is empty.")
  77. }
  78. policyCond := policyCondition{
  79. matchType: "eq",
  80. condition: "$key",
  81. value: key,
  82. }
  83. if err := p.addNewPolicy(policyCond); err != nil {
  84. return err
  85. }
  86. p.formData["key"] = key
  87. return nil
  88. }
  89. // SetKeyStartsWith - Sets an object name that an policy based upload
  90. // can start with.
  91. func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
  92. if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
  93. return ErrInvalidArgument("Object prefix is empty.")
  94. }
  95. policyCond := policyCondition{
  96. matchType: "starts-with",
  97. condition: "$key",
  98. value: keyStartsWith,
  99. }
  100. if err := p.addNewPolicy(policyCond); err != nil {
  101. return err
  102. }
  103. p.formData["key"] = keyStartsWith
  104. return nil
  105. }
  106. // SetBucket - Sets bucket at which objects will be uploaded to.
  107. func (p *PostPolicy) SetBucket(bucketName string) error {
  108. if strings.TrimSpace(bucketName) == "" || bucketName == "" {
  109. return ErrInvalidArgument("Bucket name is empty.")
  110. }
  111. policyCond := policyCondition{
  112. matchType: "eq",
  113. condition: "$bucket",
  114. value: bucketName,
  115. }
  116. if err := p.addNewPolicy(policyCond); err != nil {
  117. return err
  118. }
  119. p.formData["bucket"] = bucketName
  120. return nil
  121. }
  122. // SetContentType - Sets content-type of the object for this policy
  123. // based upload.
  124. func (p *PostPolicy) SetContentType(contentType string) error {
  125. if strings.TrimSpace(contentType) == "" || contentType == "" {
  126. return ErrInvalidArgument("No content type specified.")
  127. }
  128. policyCond := policyCondition{
  129. matchType: "eq",
  130. condition: "$Content-Type",
  131. value: contentType,
  132. }
  133. if err := p.addNewPolicy(policyCond); err != nil {
  134. return err
  135. }
  136. p.formData["Content-Type"] = contentType
  137. return nil
  138. }
  139. // SetContentLengthRange - Set new min and max content length
  140. // condition for all incoming uploads.
  141. func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
  142. if min > max {
  143. return ErrInvalidArgument("Minimum limit is larger than maximum limit.")
  144. }
  145. if min < 0 {
  146. return ErrInvalidArgument("Minimum limit cannot be negative.")
  147. }
  148. if max < 0 {
  149. return ErrInvalidArgument("Maximum limit cannot be negative.")
  150. }
  151. p.contentLengthRange.min = min
  152. p.contentLengthRange.max = max
  153. return nil
  154. }
  155. // SetSuccessStatusAction - Sets the status success code of the object for this policy
  156. // based upload.
  157. func (p *PostPolicy) SetSuccessStatusAction(status string) error {
  158. if strings.TrimSpace(status) == "" || status == "" {
  159. return ErrInvalidArgument("Status is empty")
  160. }
  161. policyCond := policyCondition{
  162. matchType: "eq",
  163. condition: "$success_action_status",
  164. value: status,
  165. }
  166. if err := p.addNewPolicy(policyCond); err != nil {
  167. return err
  168. }
  169. p.formData["success_action_status"] = status
  170. return nil
  171. }
  172. // SetUserMetadata - Set user metadata as a key/value couple.
  173. // Can be retrieved through a HEAD request or an event.
  174. func (p *PostPolicy) SetUserMetadata(key string, value string) error {
  175. if strings.TrimSpace(key) == "" || key == "" {
  176. return ErrInvalidArgument("Key is empty")
  177. }
  178. if strings.TrimSpace(value) == "" || value == "" {
  179. return ErrInvalidArgument("Value is empty")
  180. }
  181. headerName := fmt.Sprintf("x-amz-meta-%s", key)
  182. policyCond := policyCondition{
  183. matchType: "eq",
  184. condition: fmt.Sprintf("$%s", headerName),
  185. value: value,
  186. }
  187. if err := p.addNewPolicy(policyCond); err != nil {
  188. return err
  189. }
  190. p.formData[headerName] = value
  191. return nil
  192. }
  193. // SetUserData - Set user data as a key/value couple.
  194. // Can be retrieved through a HEAD request or an event.
  195. func (p *PostPolicy) SetUserData(key string, value string) error {
  196. if key == "" {
  197. return ErrInvalidArgument("Key is empty")
  198. }
  199. if value == "" {
  200. return ErrInvalidArgument("Value is empty")
  201. }
  202. headerName := fmt.Sprintf("x-amz-%s", key)
  203. policyCond := policyCondition{
  204. matchType: "eq",
  205. condition: fmt.Sprintf("$%s", headerName),
  206. value: value,
  207. }
  208. if err := p.addNewPolicy(policyCond); err != nil {
  209. return err
  210. }
  211. p.formData[headerName] = value
  212. return nil
  213. }
  214. // addNewPolicy - internal helper to validate adding new policies.
  215. func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
  216. if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
  217. return ErrInvalidArgument("Policy fields are empty.")
  218. }
  219. p.conditions = append(p.conditions, policyCond)
  220. return nil
  221. }
  222. // Stringer interface for printing policy in json formatted string.
  223. func (p PostPolicy) String() string {
  224. return string(p.marshalJSON())
  225. }
  226. // marshalJSON - Provides Marshalled JSON in bytes.
  227. func (p PostPolicy) marshalJSON() []byte {
  228. expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
  229. var conditionsStr string
  230. conditions := []string{}
  231. for _, po := range p.conditions {
  232. conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
  233. }
  234. if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
  235. conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
  236. p.contentLengthRange.min, p.contentLengthRange.max))
  237. }
  238. if len(conditions) > 0 {
  239. conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
  240. }
  241. retStr := "{"
  242. retStr = retStr + expirationStr + ","
  243. retStr = retStr + conditionsStr
  244. retStr = retStr + "}"
  245. return []byte(retStr)
  246. }
  247. // base64 - Produces base64 of PostPolicy's Marshalled json.
  248. func (p PostPolicy) base64() string {
  249. return base64.StdEncoding.EncodeToString(p.marshalJSON())
  250. }