post-policy.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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 minio
  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. // SetCondition - Sets condition for credentials, date and algorithm
  123. func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
  124. if strings.TrimSpace(value) == "" || value == "" {
  125. return errInvalidArgument("No value specified for condition")
  126. }
  127. policyCond := policyCondition{
  128. matchType: matchType,
  129. condition: "$" + condition,
  130. value: value,
  131. }
  132. if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
  133. if err := p.addNewPolicy(policyCond); err != nil {
  134. return err
  135. }
  136. p.formData[condition] = value
  137. return nil
  138. }
  139. return errInvalidArgument("Invalid condition in policy")
  140. }
  141. // SetContentType - Sets content-type of the object for this policy
  142. // based upload.
  143. func (p *PostPolicy) SetContentType(contentType string) error {
  144. if strings.TrimSpace(contentType) == "" || contentType == "" {
  145. return errInvalidArgument("No content type specified.")
  146. }
  147. policyCond := policyCondition{
  148. matchType: "eq",
  149. condition: "$Content-Type",
  150. value: contentType,
  151. }
  152. if err := p.addNewPolicy(policyCond); err != nil {
  153. return err
  154. }
  155. p.formData["Content-Type"] = contentType
  156. return nil
  157. }
  158. // SetContentTypeStartsWith - Sets what content-type of the object for this policy
  159. // based upload can start with.
  160. func (p *PostPolicy) SetContentTypeStartsWith(contentTypeStartsWith string) error {
  161. if strings.TrimSpace(contentTypeStartsWith) == "" || contentTypeStartsWith == "" {
  162. return errInvalidArgument("No content type specified.")
  163. }
  164. policyCond := policyCondition{
  165. matchType: "starts-with",
  166. condition: "$Content-Type",
  167. value: contentTypeStartsWith,
  168. }
  169. if err := p.addNewPolicy(policyCond); err != nil {
  170. return err
  171. }
  172. p.formData["Content-Type"] = contentTypeStartsWith
  173. return nil
  174. }
  175. // SetContentLengthRange - Set new min and max content length
  176. // condition for all incoming uploads.
  177. func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
  178. if min > max {
  179. return errInvalidArgument("Minimum limit is larger than maximum limit.")
  180. }
  181. if min < 0 {
  182. return errInvalidArgument("Minimum limit cannot be negative.")
  183. }
  184. if max < 0 {
  185. return errInvalidArgument("Maximum limit cannot be negative.")
  186. }
  187. p.contentLengthRange.min = min
  188. p.contentLengthRange.max = max
  189. return nil
  190. }
  191. // SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
  192. // based upload.
  193. func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
  194. if strings.TrimSpace(redirect) == "" || redirect == "" {
  195. return errInvalidArgument("Redirect is empty")
  196. }
  197. policyCond := policyCondition{
  198. matchType: "eq",
  199. condition: "$success_action_redirect",
  200. value: redirect,
  201. }
  202. if err := p.addNewPolicy(policyCond); err != nil {
  203. return err
  204. }
  205. p.formData["success_action_redirect"] = redirect
  206. return nil
  207. }
  208. // SetSuccessStatusAction - Sets the status success code of the object for this policy
  209. // based upload.
  210. func (p *PostPolicy) SetSuccessStatusAction(status string) error {
  211. if strings.TrimSpace(status) == "" || status == "" {
  212. return errInvalidArgument("Status is empty")
  213. }
  214. policyCond := policyCondition{
  215. matchType: "eq",
  216. condition: "$success_action_status",
  217. value: status,
  218. }
  219. if err := p.addNewPolicy(policyCond); err != nil {
  220. return err
  221. }
  222. p.formData["success_action_status"] = status
  223. return nil
  224. }
  225. // SetUserMetadata - Set user metadata as a key/value couple.
  226. // Can be retrieved through a HEAD request or an event.
  227. func (p *PostPolicy) SetUserMetadata(key string, value string) error {
  228. if strings.TrimSpace(key) == "" || key == "" {
  229. return errInvalidArgument("Key is empty")
  230. }
  231. if strings.TrimSpace(value) == "" || value == "" {
  232. return errInvalidArgument("Value is empty")
  233. }
  234. headerName := fmt.Sprintf("x-amz-meta-%s", key)
  235. policyCond := policyCondition{
  236. matchType: "eq",
  237. condition: fmt.Sprintf("$%s", headerName),
  238. value: value,
  239. }
  240. if err := p.addNewPolicy(policyCond); err != nil {
  241. return err
  242. }
  243. p.formData[headerName] = value
  244. return nil
  245. }
  246. // SetUserData - Set user data as a key/value couple.
  247. // Can be retrieved through a HEAD request or an event.
  248. func (p *PostPolicy) SetUserData(key string, value string) error {
  249. if key == "" {
  250. return errInvalidArgument("Key is empty")
  251. }
  252. if value == "" {
  253. return errInvalidArgument("Value is empty")
  254. }
  255. headerName := fmt.Sprintf("x-amz-%s", key)
  256. policyCond := policyCondition{
  257. matchType: "eq",
  258. condition: fmt.Sprintf("$%s", headerName),
  259. value: value,
  260. }
  261. if err := p.addNewPolicy(policyCond); err != nil {
  262. return err
  263. }
  264. p.formData[headerName] = value
  265. return nil
  266. }
  267. // addNewPolicy - internal helper to validate adding new policies.
  268. func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
  269. if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
  270. return errInvalidArgument("Policy fields are empty.")
  271. }
  272. p.conditions = append(p.conditions, policyCond)
  273. return nil
  274. }
  275. // String function for printing policy in json formatted string.
  276. func (p PostPolicy) String() string {
  277. return string(p.marshalJSON())
  278. }
  279. // marshalJSON - Provides Marshaled JSON in bytes.
  280. func (p PostPolicy) marshalJSON() []byte {
  281. expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
  282. var conditionsStr string
  283. conditions := []string{}
  284. for _, po := range p.conditions {
  285. conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
  286. }
  287. if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
  288. conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
  289. p.contentLengthRange.min, p.contentLengthRange.max))
  290. }
  291. if len(conditions) > 0 {
  292. conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
  293. }
  294. retStr := "{"
  295. retStr = retStr + expirationStr + ","
  296. retStr = retStr + conditionsStr
  297. retStr = retStr + "}"
  298. return []byte(retStr)
  299. }
  300. // base64 - Produces base64 of PostPolicy's Marshaled json.
  301. func (p PostPolicy) base64() string {
  302. return base64.StdEncoding.EncodeToString(p.marshalJSON())
  303. }