mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-23 20:26:39 +00:00
53ee6aef08
* update go-storage dependency, for S3Storage manually call PutObject() so we can set content-type * update calls to PutFile() to include the contentType
409 lines
12 KiB
Go
409 lines
12 KiB
Go
/*
|
|
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
|
|
* Copyright 2015-2023 MinIO, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package minio
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
|
"github.com/minio/minio-go/v7/pkg/tags"
|
|
)
|
|
|
|
// expirationDateFormat date format for expiration key in json policy.
|
|
const expirationDateFormat = "2006-01-02T15:04:05.000Z"
|
|
|
|
// policyCondition explanation:
|
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
|
//
|
|
// Example:
|
|
//
|
|
// policyCondition {
|
|
// matchType: "$eq",
|
|
// key: "$Content-Type",
|
|
// value: "image/png",
|
|
// }
|
|
type policyCondition struct {
|
|
matchType string
|
|
condition string
|
|
value string
|
|
}
|
|
|
|
// PostPolicy - Provides strict static type conversion and validation
|
|
// for Amazon S3's POST policy JSON string.
|
|
type PostPolicy struct {
|
|
// Expiration date and time of the POST policy.
|
|
expiration time.Time
|
|
// Collection of different policy conditions.
|
|
conditions []policyCondition
|
|
// ContentLengthRange minimum and maximum allowable size for the
|
|
// uploaded content.
|
|
contentLengthRange struct {
|
|
min int64
|
|
max int64
|
|
}
|
|
|
|
// Post form data.
|
|
formData map[string]string
|
|
}
|
|
|
|
// NewPostPolicy - Instantiate new post policy.
|
|
func NewPostPolicy() *PostPolicy {
|
|
p := &PostPolicy{}
|
|
p.conditions = make([]policyCondition, 0)
|
|
p.formData = make(map[string]string)
|
|
return p
|
|
}
|
|
|
|
// SetExpires - Sets expiration time for the new policy.
|
|
func (p *PostPolicy) SetExpires(t time.Time) error {
|
|
if t.IsZero() {
|
|
return errInvalidArgument("No expiry time set.")
|
|
}
|
|
p.expiration = t
|
|
return nil
|
|
}
|
|
|
|
// SetKey - Sets an object name for the policy based upload.
|
|
func (p *PostPolicy) SetKey(key string) error {
|
|
if strings.TrimSpace(key) == "" || key == "" {
|
|
return errInvalidArgument("Object name is empty.")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$key",
|
|
value: key,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["key"] = key
|
|
return nil
|
|
}
|
|
|
|
// SetKeyStartsWith - Sets an object name that an policy based upload
|
|
// can start with.
|
|
// Can use an empty value ("") to allow any key.
|
|
func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
|
|
policyCond := policyCondition{
|
|
matchType: "starts-with",
|
|
condition: "$key",
|
|
value: keyStartsWith,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["key"] = keyStartsWith
|
|
return nil
|
|
}
|
|
|
|
// SetBucket - Sets bucket at which objects will be uploaded to.
|
|
func (p *PostPolicy) SetBucket(bucketName string) error {
|
|
if strings.TrimSpace(bucketName) == "" || bucketName == "" {
|
|
return errInvalidArgument("Bucket name is empty.")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$bucket",
|
|
value: bucketName,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["bucket"] = bucketName
|
|
return nil
|
|
}
|
|
|
|
// SetCondition - Sets condition for credentials, date and algorithm
|
|
func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
|
|
if strings.TrimSpace(value) == "" || value == "" {
|
|
return errInvalidArgument("No value specified for condition")
|
|
}
|
|
|
|
policyCond := policyCondition{
|
|
matchType: matchType,
|
|
condition: "$" + condition,
|
|
value: value,
|
|
}
|
|
if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData[condition] = value
|
|
return nil
|
|
}
|
|
return errInvalidArgument("Invalid condition in policy")
|
|
}
|
|
|
|
// SetTagging - Sets tagging for the object for this policy based upload.
|
|
func (p *PostPolicy) SetTagging(tagging string) error {
|
|
if strings.TrimSpace(tagging) == "" || tagging == "" {
|
|
return errInvalidArgument("No tagging specified.")
|
|
}
|
|
_, err := tags.ParseObjectXML(strings.NewReader(tagging))
|
|
if err != nil {
|
|
return errors.New("The XML you provided was not well-formed or did not validate against our published schema.") //nolint
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$tagging",
|
|
value: tagging,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["tagging"] = tagging
|
|
return nil
|
|
}
|
|
|
|
// SetContentType - Sets content-type of the object for this policy
|
|
// based upload.
|
|
func (p *PostPolicy) SetContentType(contentType string) error {
|
|
if strings.TrimSpace(contentType) == "" || contentType == "" {
|
|
return errInvalidArgument("No content type specified.")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$Content-Type",
|
|
value: contentType,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["Content-Type"] = contentType
|
|
return nil
|
|
}
|
|
|
|
// SetContentTypeStartsWith - Sets what content-type of the object for this policy
|
|
// based upload can start with.
|
|
// Can use an empty value ("") to allow any content-type.
|
|
func (p *PostPolicy) SetContentTypeStartsWith(contentTypeStartsWith string) error {
|
|
policyCond := policyCondition{
|
|
matchType: "starts-with",
|
|
condition: "$Content-Type",
|
|
value: contentTypeStartsWith,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["Content-Type"] = contentTypeStartsWith
|
|
return nil
|
|
}
|
|
|
|
// SetContentDisposition - Sets content-disposition of the object for this policy
|
|
func (p *PostPolicy) SetContentDisposition(contentDisposition string) error {
|
|
if strings.TrimSpace(contentDisposition) == "" || contentDisposition == "" {
|
|
return errInvalidArgument("No content disposition specified.")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$Content-Disposition",
|
|
value: contentDisposition,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["Content-Disposition"] = contentDisposition
|
|
return nil
|
|
}
|
|
|
|
// SetContentLengthRange - Set new min and max content length
|
|
// condition for all incoming uploads.
|
|
func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
|
|
if min > max {
|
|
return errInvalidArgument("Minimum limit is larger than maximum limit.")
|
|
}
|
|
if min < 0 {
|
|
return errInvalidArgument("Minimum limit cannot be negative.")
|
|
}
|
|
if max <= 0 {
|
|
return errInvalidArgument("Maximum limit cannot be non-positive.")
|
|
}
|
|
p.contentLengthRange.min = min
|
|
p.contentLengthRange.max = max
|
|
return nil
|
|
}
|
|
|
|
// SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
|
|
// based upload.
|
|
func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
|
|
if strings.TrimSpace(redirect) == "" || redirect == "" {
|
|
return errInvalidArgument("Redirect is empty")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$success_action_redirect",
|
|
value: redirect,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["success_action_redirect"] = redirect
|
|
return nil
|
|
}
|
|
|
|
// SetSuccessStatusAction - Sets the status success code of the object for this policy
|
|
// based upload.
|
|
func (p *PostPolicy) SetSuccessStatusAction(status string) error {
|
|
if strings.TrimSpace(status) == "" || status == "" {
|
|
return errInvalidArgument("Status is empty")
|
|
}
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: "$success_action_status",
|
|
value: status,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData["success_action_status"] = status
|
|
return nil
|
|
}
|
|
|
|
// SetUserMetadata - Set user metadata as a key/value couple.
|
|
// Can be retrieved through a HEAD request or an event.
|
|
func (p *PostPolicy) SetUserMetadata(key, value string) error {
|
|
if strings.TrimSpace(key) == "" || key == "" {
|
|
return errInvalidArgument("Key is empty")
|
|
}
|
|
if strings.TrimSpace(value) == "" || value == "" {
|
|
return errInvalidArgument("Value is empty")
|
|
}
|
|
headerName := fmt.Sprintf("x-amz-meta-%s", key)
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: fmt.Sprintf("$%s", headerName),
|
|
value: value,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData[headerName] = value
|
|
return nil
|
|
}
|
|
|
|
// SetUserMetadataStartsWith - Set how an user metadata should starts with.
|
|
// Can be retrieved through a HEAD request or an event.
|
|
func (p *PostPolicy) SetUserMetadataStartsWith(key, value string) error {
|
|
if strings.TrimSpace(key) == "" || key == "" {
|
|
return errInvalidArgument("Key is empty")
|
|
}
|
|
headerName := fmt.Sprintf("x-amz-meta-%s", key)
|
|
policyCond := policyCondition{
|
|
matchType: "starts-with",
|
|
condition: fmt.Sprintf("$%s", headerName),
|
|
value: value,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData[headerName] = value
|
|
return nil
|
|
}
|
|
|
|
// SetChecksum sets the checksum of the request.
|
|
func (p *PostPolicy) SetChecksum(c Checksum) {
|
|
if c.IsSet() {
|
|
p.formData[amzChecksumAlgo] = c.Type.String()
|
|
p.formData[c.Type.Key()] = c.Encoded()
|
|
}
|
|
}
|
|
|
|
// SetEncryption - sets encryption headers for POST API
|
|
func (p *PostPolicy) SetEncryption(sse encrypt.ServerSide) {
|
|
if sse == nil {
|
|
return
|
|
}
|
|
h := http.Header{}
|
|
sse.Marshal(h)
|
|
for k, v := range h {
|
|
p.formData[k] = v[0]
|
|
}
|
|
}
|
|
|
|
// SetUserData - Set user data as a key/value couple.
|
|
// Can be retrieved through a HEAD request or an event.
|
|
func (p *PostPolicy) SetUserData(key, value string) error {
|
|
if key == "" {
|
|
return errInvalidArgument("Key is empty")
|
|
}
|
|
if value == "" {
|
|
return errInvalidArgument("Value is empty")
|
|
}
|
|
headerName := fmt.Sprintf("x-amz-%s", key)
|
|
policyCond := policyCondition{
|
|
matchType: "eq",
|
|
condition: fmt.Sprintf("$%s", headerName),
|
|
value: value,
|
|
}
|
|
if err := p.addNewPolicy(policyCond); err != nil {
|
|
return err
|
|
}
|
|
p.formData[headerName] = value
|
|
return nil
|
|
}
|
|
|
|
// addNewPolicy - internal helper to validate adding new policies.
|
|
// Can use starts-with with an empty value ("") to allow any content within a form field.
|
|
func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
|
|
if policyCond.matchType == "" || policyCond.condition == "" {
|
|
return errInvalidArgument("Policy fields are empty.")
|
|
}
|
|
if policyCond.matchType != "starts-with" && policyCond.value == "" {
|
|
return errInvalidArgument("Policy value is empty.")
|
|
}
|
|
p.conditions = append(p.conditions, policyCond)
|
|
return nil
|
|
}
|
|
|
|
// String function for printing policy in json formatted string.
|
|
func (p PostPolicy) String() string {
|
|
return string(p.marshalJSON())
|
|
}
|
|
|
|
// marshalJSON - Provides Marshaled JSON in bytes.
|
|
func (p PostPolicy) marshalJSON() []byte {
|
|
expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
|
|
var conditionsStr string
|
|
conditions := []string{}
|
|
for _, po := range p.conditions {
|
|
conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
|
|
}
|
|
if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
|
|
conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
|
|
p.contentLengthRange.min, p.contentLengthRange.max))
|
|
}
|
|
if len(conditions) > 0 {
|
|
conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
|
|
}
|
|
retStr := "{"
|
|
retStr = retStr + expirationStr + ","
|
|
retStr += conditionsStr
|
|
retStr += "}"
|
|
return []byte(retStr)
|
|
}
|
|
|
|
// base64 - Produces base64 of PostPolicy's Marshaled json.
|
|
func (p PostPolicy) base64() string {
|
|
return base64.StdEncoding.EncodeToString(p.marshalJSON())
|
|
}
|