gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

post-policy.go (10051B)


      1 /*
      2  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
      3  * Copyright 2015-2023 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 
     18 package minio
     19 
     20 import (
     21 	"encoding/base64"
     22 	"fmt"
     23 	"net/http"
     24 	"strings"
     25 	"time"
     26 
     27 	"github.com/minio/minio-go/v7/pkg/encrypt"
     28 )
     29 
     30 // expirationDateFormat date format for expiration key in json policy.
     31 const expirationDateFormat = "2006-01-02T15:04:05.000Z"
     32 
     33 // policyCondition explanation:
     34 // http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
     35 //
     36 // Example:
     37 //
     38 //	policyCondition {
     39 //	    matchType: "$eq",
     40 //	    key: "$Content-Type",
     41 //	    value: "image/png",
     42 //	}
     43 type policyCondition struct {
     44 	matchType string
     45 	condition string
     46 	value     string
     47 }
     48 
     49 // PostPolicy - Provides strict static type conversion and validation
     50 // for Amazon S3's POST policy JSON string.
     51 type PostPolicy struct {
     52 	// Expiration date and time of the POST policy.
     53 	expiration time.Time
     54 	// Collection of different policy conditions.
     55 	conditions []policyCondition
     56 	// ContentLengthRange minimum and maximum allowable size for the
     57 	// uploaded content.
     58 	contentLengthRange struct {
     59 		min int64
     60 		max int64
     61 	}
     62 
     63 	// Post form data.
     64 	formData map[string]string
     65 }
     66 
     67 // NewPostPolicy - Instantiate new post policy.
     68 func NewPostPolicy() *PostPolicy {
     69 	p := &PostPolicy{}
     70 	p.conditions = make([]policyCondition, 0)
     71 	p.formData = make(map[string]string)
     72 	return p
     73 }
     74 
     75 // SetExpires - Sets expiration time for the new policy.
     76 func (p *PostPolicy) SetExpires(t time.Time) error {
     77 	if t.IsZero() {
     78 		return errInvalidArgument("No expiry time set.")
     79 	}
     80 	p.expiration = t
     81 	return nil
     82 }
     83 
     84 // SetKey - Sets an object name for the policy based upload.
     85 func (p *PostPolicy) SetKey(key string) error {
     86 	if strings.TrimSpace(key) == "" || key == "" {
     87 		return errInvalidArgument("Object name is empty.")
     88 	}
     89 	policyCond := policyCondition{
     90 		matchType: "eq",
     91 		condition: "$key",
     92 		value:     key,
     93 	}
     94 	if err := p.addNewPolicy(policyCond); err != nil {
     95 		return err
     96 	}
     97 	p.formData["key"] = key
     98 	return nil
     99 }
    100 
    101 // SetKeyStartsWith - Sets an object name that an policy based upload
    102 // can start with.
    103 // Can use an empty value ("") to allow any key.
    104 func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
    105 	policyCond := policyCondition{
    106 		matchType: "starts-with",
    107 		condition: "$key",
    108 		value:     keyStartsWith,
    109 	}
    110 	if err := p.addNewPolicy(policyCond); err != nil {
    111 		return err
    112 	}
    113 	p.formData["key"] = keyStartsWith
    114 	return nil
    115 }
    116 
    117 // SetBucket - Sets bucket at which objects will be uploaded to.
    118 func (p *PostPolicy) SetBucket(bucketName string) error {
    119 	if strings.TrimSpace(bucketName) == "" || bucketName == "" {
    120 		return errInvalidArgument("Bucket name is empty.")
    121 	}
    122 	policyCond := policyCondition{
    123 		matchType: "eq",
    124 		condition: "$bucket",
    125 		value:     bucketName,
    126 	}
    127 	if err := p.addNewPolicy(policyCond); err != nil {
    128 		return err
    129 	}
    130 	p.formData["bucket"] = bucketName
    131 	return nil
    132 }
    133 
    134 // SetCondition - Sets condition for credentials, date and algorithm
    135 func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
    136 	if strings.TrimSpace(value) == "" || value == "" {
    137 		return errInvalidArgument("No value specified for condition")
    138 	}
    139 
    140 	policyCond := policyCondition{
    141 		matchType: matchType,
    142 		condition: "$" + condition,
    143 		value:     value,
    144 	}
    145 	if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
    146 		if err := p.addNewPolicy(policyCond); err != nil {
    147 			return err
    148 		}
    149 		p.formData[condition] = value
    150 		return nil
    151 	}
    152 	return errInvalidArgument("Invalid condition in policy")
    153 }
    154 
    155 // SetContentType - Sets content-type of the object for this policy
    156 // based upload.
    157 func (p *PostPolicy) SetContentType(contentType string) error {
    158 	if strings.TrimSpace(contentType) == "" || contentType == "" {
    159 		return errInvalidArgument("No content type specified.")
    160 	}
    161 	policyCond := policyCondition{
    162 		matchType: "eq",
    163 		condition: "$Content-Type",
    164 		value:     contentType,
    165 	}
    166 	if err := p.addNewPolicy(policyCond); err != nil {
    167 		return err
    168 	}
    169 	p.formData["Content-Type"] = contentType
    170 	return nil
    171 }
    172 
    173 // SetContentTypeStartsWith - Sets what content-type of the object for this policy
    174 // based upload can start with.
    175 // Can use an empty value ("") to allow any content-type.
    176 func (p *PostPolicy) SetContentTypeStartsWith(contentTypeStartsWith string) error {
    177 	policyCond := policyCondition{
    178 		matchType: "starts-with",
    179 		condition: "$Content-Type",
    180 		value:     contentTypeStartsWith,
    181 	}
    182 	if err := p.addNewPolicy(policyCond); err != nil {
    183 		return err
    184 	}
    185 	p.formData["Content-Type"] = contentTypeStartsWith
    186 	return nil
    187 }
    188 
    189 // SetContentLengthRange - Set new min and max content length
    190 // condition for all incoming uploads.
    191 func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
    192 	if min > max {
    193 		return errInvalidArgument("Minimum limit is larger than maximum limit.")
    194 	}
    195 	if min < 0 {
    196 		return errInvalidArgument("Minimum limit cannot be negative.")
    197 	}
    198 	if max <= 0 {
    199 		return errInvalidArgument("Maximum limit cannot be non-positive.")
    200 	}
    201 	p.contentLengthRange.min = min
    202 	p.contentLengthRange.max = max
    203 	return nil
    204 }
    205 
    206 // SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
    207 // based upload.
    208 func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
    209 	if strings.TrimSpace(redirect) == "" || redirect == "" {
    210 		return errInvalidArgument("Redirect is empty")
    211 	}
    212 	policyCond := policyCondition{
    213 		matchType: "eq",
    214 		condition: "$success_action_redirect",
    215 		value:     redirect,
    216 	}
    217 	if err := p.addNewPolicy(policyCond); err != nil {
    218 		return err
    219 	}
    220 	p.formData["success_action_redirect"] = redirect
    221 	return nil
    222 }
    223 
    224 // SetSuccessStatusAction - Sets the status success code of the object for this policy
    225 // based upload.
    226 func (p *PostPolicy) SetSuccessStatusAction(status string) error {
    227 	if strings.TrimSpace(status) == "" || status == "" {
    228 		return errInvalidArgument("Status is empty")
    229 	}
    230 	policyCond := policyCondition{
    231 		matchType: "eq",
    232 		condition: "$success_action_status",
    233 		value:     status,
    234 	}
    235 	if err := p.addNewPolicy(policyCond); err != nil {
    236 		return err
    237 	}
    238 	p.formData["success_action_status"] = status
    239 	return nil
    240 }
    241 
    242 // SetUserMetadata - Set user metadata as a key/value couple.
    243 // Can be retrieved through a HEAD request or an event.
    244 func (p *PostPolicy) SetUserMetadata(key string, value string) error {
    245 	if strings.TrimSpace(key) == "" || key == "" {
    246 		return errInvalidArgument("Key is empty")
    247 	}
    248 	if strings.TrimSpace(value) == "" || value == "" {
    249 		return errInvalidArgument("Value is empty")
    250 	}
    251 	headerName := fmt.Sprintf("x-amz-meta-%s", key)
    252 	policyCond := policyCondition{
    253 		matchType: "eq",
    254 		condition: fmt.Sprintf("$%s", headerName),
    255 		value:     value,
    256 	}
    257 	if err := p.addNewPolicy(policyCond); err != nil {
    258 		return err
    259 	}
    260 	p.formData[headerName] = value
    261 	return nil
    262 }
    263 
    264 // SetChecksum sets the checksum of the request.
    265 func (p *PostPolicy) SetChecksum(c Checksum) {
    266 	if c.IsSet() {
    267 		p.formData[amzChecksumAlgo] = c.Type.String()
    268 		p.formData[c.Type.Key()] = c.Encoded()
    269 	}
    270 }
    271 
    272 // SetEncryption - sets encryption headers for POST API
    273 func (p *PostPolicy) SetEncryption(sse encrypt.ServerSide) {
    274 	if sse == nil {
    275 		return
    276 	}
    277 	h := http.Header{}
    278 	sse.Marshal(h)
    279 	for k, v := range h {
    280 		p.formData[k] = v[0]
    281 	}
    282 }
    283 
    284 // SetUserData - Set user data as a key/value couple.
    285 // Can be retrieved through a HEAD request or an event.
    286 func (p *PostPolicy) SetUserData(key string, value string) error {
    287 	if key == "" {
    288 		return errInvalidArgument("Key is empty")
    289 	}
    290 	if value == "" {
    291 		return errInvalidArgument("Value is empty")
    292 	}
    293 	headerName := fmt.Sprintf("x-amz-%s", key)
    294 	policyCond := policyCondition{
    295 		matchType: "eq",
    296 		condition: fmt.Sprintf("$%s", headerName),
    297 		value:     value,
    298 	}
    299 	if err := p.addNewPolicy(policyCond); err != nil {
    300 		return err
    301 	}
    302 	p.formData[headerName] = value
    303 	return nil
    304 }
    305 
    306 // addNewPolicy - internal helper to validate adding new policies.
    307 // Can use starts-with with an empty value ("") to allow any content within a form field.
    308 func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
    309 	if policyCond.matchType == "" || policyCond.condition == "" {
    310 		return errInvalidArgument("Policy fields are empty.")
    311 	}
    312 	if policyCond.matchType != "starts-with" && policyCond.value == "" {
    313 		return errInvalidArgument("Policy value is empty.")
    314 	}
    315 	p.conditions = append(p.conditions, policyCond)
    316 	return nil
    317 }
    318 
    319 // String function for printing policy in json formatted string.
    320 func (p PostPolicy) String() string {
    321 	return string(p.marshalJSON())
    322 }
    323 
    324 // marshalJSON - Provides Marshaled JSON in bytes.
    325 func (p PostPolicy) marshalJSON() []byte {
    326 	expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
    327 	var conditionsStr string
    328 	conditions := []string{}
    329 	for _, po := range p.conditions {
    330 		conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
    331 	}
    332 	if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
    333 		conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
    334 			p.contentLengthRange.min, p.contentLengthRange.max))
    335 	}
    336 	if len(conditions) > 0 {
    337 		conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
    338 	}
    339 	retStr := "{"
    340 	retStr = retStr + expirationStr + ","
    341 	retStr += conditionsStr
    342 	retStr += "}"
    343 	return []byte(retStr)
    344 }
    345 
    346 // base64 - Produces base64 of PostPolicy's Marshaled json.
    347 func (p PostPolicy) base64() string {
    348 	return base64.StdEncoding.EncodeToString(p.marshalJSON())
    349 }