aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api
diff options
context:
space:
mode:
authorhilimd <68371223+hilimd@users.noreply.github.com>2021-10-23 19:52:48 +0800
committerGitHub <noreply@github.com>2021-10-23 19:52:48 +0800
commitee90edd0e3746ae0f6046dd9e7362aeec821456c (patch)
treeaf291d9afde8e3be8ba9061e14edad75a34d0c6b /weed/s3api
parent0548ed3a1b5aa15b035b452519568bc017c065a3 (diff)
parent07dd4873db996c4f4a94ee3b0975052d0ec7595b (diff)
downloadseaweedfs-ee90edd0e3746ae0f6046dd9e7362aeec821456c.tar.xz
seaweedfs-ee90edd0e3746ae0f6046dd9e7362aeec821456c.zip
Merge pull request #83 from chrislusf/master
sync
Diffstat (limited to 'weed/s3api')
-rw-r--r--weed/s3api/auth_credentials.go34
-rw-r--r--weed/s3api/auth_credentials_subscribe.go9
-rw-r--r--weed/s3api/auth_signature_v4.go13
-rw-r--r--weed/s3api/auto_signature_v4_test.go8
-rw-r--r--weed/s3api/filer_multipart.go14
-rw-r--r--weed/s3api/s3api_bucket_handlers.go97
-rw-r--r--weed/s3api/s3api_handlers.go1
-rw-r--r--weed/s3api/s3api_object_handlers.go25
-rw-r--r--weed/s3api/s3api_object_handlers_postpolicy.go16
-rw-r--r--weed/s3api/s3api_object_multipart_handlers.go9
-rw-r--r--weed/s3api/s3api_object_skip_handlers.go8
-rw-r--r--weed/s3api/s3api_object_tagging_handlers.go10
-rw-r--r--weed/s3api/s3api_policy.go147
-rw-r--r--weed/s3api/s3api_server.go22
-rw-r--r--weed/s3api/s3api_xsd_generated.go16
-rw-r--r--weed/s3api/s3err/s3api_errors.go8
-rw-r--r--weed/s3api/tags.go5
-rw-r--r--weed/s3api/tags_test.go1
18 files changed, 394 insertions, 49 deletions
diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go
index 44c3f7aa7..998a74625 100644
--- a/weed/s3api/auth_credentials.go
+++ b/weed/s3api/auth_credentials.go
@@ -2,6 +2,10 @@ package s3api
import (
"fmt"
+ "net/http"
+ "os"
+ "strings"
+
"github.com/chrislusf/seaweedfs/weed/filer"
"github.com/chrislusf/seaweedfs/weed/glog"
"github.com/chrislusf/seaweedfs/weed/pb"
@@ -10,9 +14,6 @@ import (
xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
- "io/ioutil"
- "net/http"
- "strings"
)
type Action string
@@ -37,6 +38,31 @@ type Credential struct {
SecretKey string
}
+func (action Action) isAdmin() bool {
+ return strings.HasPrefix(string(action), s3_constants.ACTION_ADMIN)
+}
+
+func (action Action) isOwner(bucket string) bool {
+ return string(action) == s3_constants.ACTION_ADMIN+":"+bucket
+}
+
+func (action Action) overBucket(bucket string) bool {
+ return strings.HasSuffix(string(action), ":"+bucket) || strings.HasSuffix(string(action), ":*")
+}
+
+func (action Action) getPermission() Permission {
+ switch act := strings.Split(string(action), ":")[0]; act {
+ case s3_constants.ACTION_ADMIN:
+ return Permission("FULL_CONTROL")
+ case s3_constants.ACTION_WRITE:
+ return Permission("WRITE")
+ case s3_constants.ACTION_READ:
+ return Permission("READ")
+ default:
+ return Permission("")
+ }
+}
+
func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement {
iam := &IdentityAccessManagement{
domain: option.DomainName,
@@ -66,7 +92,7 @@ func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3A
}
func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error {
- content, readErr := ioutil.ReadFile(fileName)
+ content, readErr := os.ReadFile(fileName)
if readErr != nil {
glog.Warningf("fail to read %s : %v", fileName, readErr)
return fmt.Errorf("fail to read %s : %v", fileName, readErr)
diff --git a/weed/s3api/auth_credentials_subscribe.go b/weed/s3api/auth_credentials_subscribe.go
index 05cce632a..a2a3e807a 100644
--- a/weed/s3api/auth_credentials_subscribe.go
+++ b/weed/s3api/auth_credentials_subscribe.go
@@ -8,7 +8,7 @@ import (
"github.com/chrislusf/seaweedfs/weed/util"
)
-func (s3a *S3ApiServer) subscribeMetaEvents(clientName string, prefix string, lastTsNs int64) error {
+func (s3a *S3ApiServer) subscribeMetaEvents(clientName string, prefix string, lastTsNs int64) {
processEventFn := func(resp *filer_pb.SubscribeMetadataResponse) error {
@@ -32,8 +32,11 @@ func (s3a *S3ApiServer) subscribeMetaEvents(clientName string, prefix string, la
return nil
}
- return util.Retry("followIamChanges", func() error {
- return pb.WithFilerClientFollowMetadata(s3a, clientName, prefix, lastTsNs, 0, processEventFn, true)
+ util.RetryForever("followIamChanges", func() error {
+ return pb.WithFilerClientFollowMetadata(s3a, clientName, prefix, &lastTsNs, 0, processEventFn, true)
+ }, func(err error) bool {
+ glog.V(0).Infof("iam follow metadata changes: %v", err)
+ return true
})
}
diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go
index 0df26e6fc..a49caad06 100644
--- a/weed/s3api/auth_signature_v4.go
+++ b/weed/s3api/auth_signature_v4.go
@@ -23,8 +23,7 @@ import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
- "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
- "io/ioutil"
+ "io"
"net/http"
"net/url"
"regexp"
@@ -33,6 +32,8 @@ import (
"strings"
"time"
"unicode/utf8"
+
+ "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
)
func (iam *IdentityAccessManagement) reqSignatureV4Verify(r *http.Request) (*Identity, s3err.ErrorCode) {
@@ -135,9 +136,9 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r
// Get hashed Payload
if signV4Values.Credential.scope.service != "s3" && hashedPayload == emptySHA256 && r.Body != nil {
- buf, _ := ioutil.ReadAll(r.Body)
- r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
- b, _ := ioutil.ReadAll(bytes.NewBuffer(buf))
+ buf, _ := io.ReadAll(r.Body)
+ r.Body = io.NopCloser(bytes.NewBuffer(buf))
+ b, _ := io.ReadAll(bytes.NewBuffer(buf))
if len(b) != 0 {
bodyHash := sha256.Sum256(b)
hashedPayload = hex.EncodeToString(bodyHash[:])
@@ -433,7 +434,7 @@ func (iam *IdentityAccessManagement) doesPresignedSignatureMatch(hashedPayload s
}
}
- /// Verify finally if signature is same.
+ // / Verify finally if signature is same.
// Get canonical request.
presignedCanonicalReq := getCanonicalRequest(extractedSignedHeaders, hashedPayload, encodedQuery, req.URL.Path, req.Method)
diff --git a/weed/s3api/auto_signature_v4_test.go b/weed/s3api/auto_signature_v4_test.go
index b47cd5f2d..a58551187 100644
--- a/weed/s3api/auto_signature_v4_test.go
+++ b/weed/s3api/auto_signature_v4_test.go
@@ -8,9 +8,7 @@ import (
"encoding/hex"
"errors"
"fmt"
- "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
"io"
- "io/ioutil"
"net/http"
"net/url"
"sort"
@@ -19,6 +17,8 @@ import (
"testing"
"time"
"unicode/utf8"
+
+ "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
)
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.
@@ -86,7 +86,7 @@ func TestIsReqAuthenticated(t *testing.T) {
// Validates all testcases.
for i, testCase := range testCases {
if _, s3Error := iam.reqSignatureV4Verify(testCase.req); s3Error != testCase.s3Error {
- ioutil.ReadAll(testCase.req.Body)
+ io.ReadAll(testCase.req.Body)
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d", i, testCase.s3Error, s3Error)
}
}
@@ -167,7 +167,7 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek
case body == nil:
hashedPayload = getSHA256Hash([]byte{})
default:
- payloadBytes, err := ioutil.ReadAll(body)
+ payloadBytes, err := io.ReadAll(body)
if err != nil {
return nil, err
}
diff --git a/weed/s3api/filer_multipart.go b/weed/s3api/filer_multipart.go
index 9a485ec66..d93ac63ea 100644
--- a/weed/s3api/filer_multipart.go
+++ b/weed/s3api/filer_multipart.go
@@ -38,6 +38,9 @@ func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInp
for k, v := range input.Metadata {
entry.Extended[k] = []byte(*v)
}
+ if input.ContentType != nil {
+ entry.Attributes.Mime = *input.ContentType
+ }
}); err != nil {
glog.Errorf("NewMultipartUpload error: %v", err)
return nil, s3err.ErrInternalError
@@ -65,7 +68,7 @@ func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploa
uploadDirectory := s3a.genUploadsFolder(*input.Bucket) + "/" + *input.UploadId
- entries, _, err := s3a.list(uploadDirectory, "", "", false, 0)
+ entries, _, err := s3a.list(uploadDirectory, "", "", false, maxPartsList)
if err != nil || len(entries) == 0 {
glog.Errorf("completeMultipartUpload %s %s error: %v, entries:%d", *input.Bucket, *input.UploadId, err, len(entries))
return nil, s3err.ErrNoSuchUpload
@@ -79,9 +82,13 @@ func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploa
var finalParts []*filer_pb.FileChunk
var offset int64
+ var mime string
for _, entry := range entries {
if strings.HasSuffix(entry.Name, ".part") && !entry.IsDirectory {
+ if entry.Name == "0001.part" && entry.Attributes.Mime != "" {
+ mime = entry.Attributes.Mime
+ }
for _, chunk := range entry.Chunks {
p := &filer_pb.FileChunk{
FileId: chunk.GetFileIdString(),
@@ -121,6 +128,11 @@ func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploa
entry.Extended[k] = v
}
}
+ if pentry.Attributes.Mime != "" {
+ entry.Attributes.Mime = pentry.Attributes.Mime
+ } else if mime != "" {
+ entry.Attributes.Mime = mime
+ }
})
if err != nil {
diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go
index 024c90c8a..b75012e9d 100644
--- a/weed/s3api/s3api_bucket_handlers.go
+++ b/weed/s3api/s3api_bucket_handlers.go
@@ -4,7 +4,9 @@ import (
"context"
"encoding/xml"
"fmt"
+ "github.com/chrislusf/seaweedfs/weed/filer"
"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
+ "github.com/chrislusf/seaweedfs/weed/storage/needle"
"math"
"net/http"
"time"
@@ -205,3 +207,98 @@ func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool {
}
return true
}
+
+// GetBucketAclHandler Get Bucket ACL
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html
+func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Request) {
+ // collect parameters
+ bucket, _ := getBucketAndObject(r)
+ glog.V(3).Infof("GetBucketAclHandler %s", bucket)
+
+ if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
+ s3err.WriteErrorResponse(w, err, r)
+ return
+ }
+
+ response := AccessControlPolicy{}
+ for _, ident := range s3a.iam.identities {
+ if len(ident.Credentials) == 0 {
+ continue
+ }
+ for _, action := range ident.Actions {
+ if !action.overBucket(bucket) || action.getPermission() == "" {
+ continue
+ }
+ id := ident.Credentials[0].AccessKey
+ if response.Owner.DisplayName == "" && action.isOwner(bucket) && len(ident.Credentials) > 0 {
+ response.Owner.DisplayName = ident.Name
+ response.Owner.ID = id
+ }
+ response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
+ Grantee: Grantee{
+ ID: id,
+ DisplayName: ident.Name,
+ Type: "CanonicalUser",
+ XMLXSI: "CanonicalUser",
+ XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
+ Permission: action.getPermission(),
+ })
+ }
+ }
+ writeSuccessResponseXML(w, response)
+}
+
+// GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
+func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
+ // collect parameters
+ bucket, _ := getBucketAndObject(r)
+ glog.V(3).Infof("GetBucketAclHandler %s", bucket)
+
+ if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
+ s3err.WriteErrorResponse(w, err, r)
+ return
+ }
+ fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
+ if err != nil {
+ glog.Errorf("GetBucketLifecycleConfigurationHandler: %s", err)
+ s3err.WriteErrorResponse(w, s3err.ErrInternalError, r)
+ return
+ }
+ ttls := fc.GetCollectionTtls(bucket)
+ if len(ttls) == 0 {
+ s3err.WriteErrorResponse(w, s3err.ErrNoSuchLifecycleConfiguration, r)
+ }
+ response := Lifecycle{}
+ for prefix, internalTtl := range ttls {
+ ttl, _ := needle.ReadTTL(internalTtl)
+ days := int(ttl.Minutes() / 60 / 24)
+ if days == 0 {
+ continue
+ }
+ response.Rules = append(response.Rules, Rule{
+ Status: Enabled, Filter: Filter{
+ Prefix: Prefix{string: prefix, set: true},
+ set: true,
+ },
+ Expiration: Expiration{Days: days, set: true},
+ })
+ }
+ writeSuccessResponseXML(w, response)
+}
+
+// PutBucketLifecycleConfigurationHandler Put Bucket Lifecycle configuration
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
+func (s3a *S3ApiServer) PutBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
+
+ s3err.WriteErrorResponse(w, s3err.ErrNotImplemented, r)
+
+}
+
+// DeleteBucketMetricsConfiguration Delete Bucket Lifecycle
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html
+func (s3a *S3ApiServer) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
+
+ s3err.WriteErrorResponse(w, s3err.ErrNotImplemented, r)
+
+}
diff --git a/weed/s3api/s3api_handlers.go b/weed/s3api/s3api_handlers.go
index 2149ad9cd..e99abb8e2 100644
--- a/weed/s3api/s3api_handlers.go
+++ b/weed/s3api/s3api_handlers.go
@@ -21,6 +21,7 @@ func (s3a *S3ApiServer) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) err
}, s3a.option.Filer.ToGrpcAddress(), s3a.option.GrpcDialOption)
}
+
func (s3a *S3ApiServer) AdjustedUrl(location *filer_pb.Location) string {
return location.Url
}
diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go
index 54b6da61c..e5513a703 100644
--- a/weed/s3api/s3api_object_handlers.go
+++ b/weed/s3api/s3api_object_handlers.go
@@ -1,20 +1,21 @@
package s3api
import (
+ "bytes"
"crypto/md5"
"encoding/json"
"encoding/xml"
"fmt"
- "github.com/chrislusf/seaweedfs/weed/filer"
- "github.com/pquerna/cachecontrol/cacheobject"
"io"
- "io/ioutil"
"net/http"
"net/url"
"sort"
"strings"
"time"
+ "github.com/chrislusf/seaweedfs/weed/filer"
+ "github.com/pquerna/cachecontrol/cacheobject"
+
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
"github.com/gorilla/mux"
@@ -36,6 +37,16 @@ func init() {
}}
}
+func mimeDetect(r *http.Request, dataReader io.Reader) io.ReadCloser {
+ mimeBuffer := make([]byte, 512)
+ size, _ := dataReader.Read(mimeBuffer)
+ if size > 0 {
+ r.Header.Set("Content-Type", http.DetectContentType(mimeBuffer[:size]))
+ return io.NopCloser(io.MultiReader(bytes.NewReader(mimeBuffer[:size]), dataReader))
+ }
+ return io.NopCloser(dataReader)
+}
+
func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
@@ -95,6 +106,10 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request)
} else {
uploadUrl := fmt.Sprintf("http://%s%s/%s%s", s3a.option.Filer.ToHttpAddress(), s3a.option.BucketsPath, bucket, urlPathEscape(object))
+ if r.Header.Get("Content-Type") == "" {
+ dataReader = mimeDetect(r, dataReader)
+ }
+
etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader)
if errCode != s3err.ErrNone {
@@ -198,7 +213,7 @@ func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *h
bucket, _ := getBucketAndObject(r)
glog.V(3).Infof("DeleteMultipleObjectsHandler %s", bucket)
- deleteXMLBytes, err := ioutil.ReadAll(r.Body)
+ deleteXMLBytes, err := io.ReadAll(r.Body)
if err != nil {
s3err.WriteErrorResponse(w, s3err.ErrInternalError, r)
return
@@ -394,7 +409,7 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, uploadUrl string, dataReader
etag = fmt.Sprintf("%x", hash.Sum(nil))
- resp_body, ra_err := ioutil.ReadAll(resp.Body)
+ resp_body, ra_err := io.ReadAll(resp.Body)
if ra_err != nil {
glog.Errorf("upload to filer response read %d: %v", resp.StatusCode, ra_err)
return etag, s3err.ErrInternalError
diff --git a/weed/s3api/s3api_object_handlers_postpolicy.go b/weed/s3api/s3api_object_handlers_postpolicy.go
index c0e2589ae..cccbd2442 100644
--- a/weed/s3api/s3api_object_handlers_postpolicy.go
+++ b/weed/s3api/s3api_object_handlers_postpolicy.go
@@ -5,17 +5,17 @@ import (
"encoding/base64"
"errors"
"fmt"
- "github.com/chrislusf/seaweedfs/weed/glog"
- "github.com/chrislusf/seaweedfs/weed/s3api/policy"
- "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
- "github.com/dustin/go-humanize"
- "github.com/gorilla/mux"
"io"
- "io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strings"
+
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ "github.com/chrislusf/seaweedfs/weed/s3api/policy"
+ "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
+ "github.com/dustin/go-humanize"
+ "github.com/gorilla/mux"
)
func (s3a *S3ApiServer) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
@@ -152,7 +152,7 @@ func (s3a *S3ApiServer) PostPolicyBucketHandler(w http.ResponseWriter, r *http.R
// Extract form fields and file data from a HTTP POST Policy
func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues http.Header, err error) {
- /// HTML Form values
+ // / HTML Form values
fileName = ""
// Canonicalize the form values into http.Header.
@@ -175,7 +175,7 @@ func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser,
b.WriteString(v)
}
fileSize = int64(b.Len())
- filePart = ioutil.NopCloser(b)
+ filePart = io.NopCloser(b)
return filePart, fileName, fileSize, formValues, nil
}
diff --git a/weed/s3api/s3api_object_multipart_handlers.go b/weed/s3api/s3api_object_multipart_handlers.go
index c9ad222b1..486161dfb 100644
--- a/weed/s3api/s3api_object_multipart_handlers.go
+++ b/weed/s3api/s3api_object_multipart_handlers.go
@@ -36,6 +36,10 @@ func (s3a *S3ApiServer) NewMultipartUploadHandler(w http.ResponseWriter, r *http
createMultipartUploadInput.Metadata[k] = aws.String(string(v))
}
+ contentType := r.Header.Get("Content-Type")
+ if contentType != "" {
+ createMultipartUploadInput.ContentType = &contentType
+ }
response, errCode := s3a.createMultipartUpload(createMultipartUploadInput)
glog.V(2).Info("NewMultipartUploadHandler", string(s3err.EncodeXMLResponse(response)), errCode)
@@ -213,8 +217,11 @@ func (s3a *S3ApiServer) PutObjectPartHandler(w http.ResponseWriter, r *http.Requ
uploadUrl := fmt.Sprintf("http://%s%s/%s/%04d.part?collection=%s",
s3a.option.Filer.ToHttpAddress(), s3a.genUploadsFolder(bucket), uploadID, partID, bucket)
- etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader)
+ if partID == 1 && r.Header.Get("Content-Type") == "" {
+ dataReader = mimeDetect(r, dataReader)
+ }
+ etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader)
if errCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, errCode, r)
return
diff --git a/weed/s3api/s3api_object_skip_handlers.go b/weed/s3api/s3api_object_skip_handlers.go
index 935787fbb..160d02475 100644
--- a/weed/s3api/s3api_object_skip_handlers.go
+++ b/weed/s3api/s3api_object_skip_handlers.go
@@ -4,6 +4,14 @@ import (
"net/http"
)
+// GetObjectAclHandler Put object ACL
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html
+func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Request) {
+
+ w.WriteHeader(http.StatusNoContent)
+
+}
+
// PutObjectAclHandler Put object ACL
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html
func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/weed/s3api/s3api_object_tagging_handlers.go b/weed/s3api/s3api_object_tagging_handlers.go
index 2ee339e29..1ba1fb52d 100644
--- a/weed/s3api/s3api_object_tagging_handlers.go
+++ b/weed/s3api/s3api_object_tagging_handlers.go
@@ -3,13 +3,13 @@ package s3api
import (
"encoding/xml"
"fmt"
+ "io"
+ "net/http"
+
"github.com/chrislusf/seaweedfs/weed/glog"
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
"github.com/chrislusf/seaweedfs/weed/util"
- "io"
- "io/ioutil"
- "net/http"
)
// GetObjectTaggingHandler - GET object tagging
@@ -49,7 +49,7 @@ func (s3a *S3ApiServer) PutObjectTaggingHandler(w http.ResponseWriter, r *http.R
dir, name := target.DirAndName()
tagging := &Tagging{}
- input, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
+ input, err := io.ReadAll(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
glog.Errorf("PutObjectTaggingHandler read input %s: %v", r.URL, err)
s3err.WriteErrorResponse(w, s3err.ErrInternalError, r)
@@ -90,7 +90,7 @@ func (s3a *S3ApiServer) PutObjectTaggingHandler(w http.ResponseWriter, r *http.R
return
}
- w.WriteHeader(http.StatusNoContent)
+ w.WriteHeader(http.StatusOK)
}
diff --git a/weed/s3api/s3api_policy.go b/weed/s3api/s3api_policy.go
new file mode 100644
index 000000000..4177d27f3
--- /dev/null
+++ b/weed/s3api/s3api_policy.go
@@ -0,0 +1,147 @@
+package s3api
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+// Status represents lifecycle configuration status
+type ruleStatus string
+
+// Supported status types
+const (
+ Enabled ruleStatus = "Enabled"
+ Disabled ruleStatus = "Disabled"
+)
+
+// Lifecycle - Configuration for bucket lifecycle.
+type Lifecycle struct {
+ XMLName xml.Name `xml:"LifecycleConfiguration"`
+ Rules []Rule `xml:"Rule"`
+}
+
+// Rule - a rule for lifecycle configuration.
+type Rule struct {
+ XMLName xml.Name `xml:"Rule"`
+ ID string `xml:"ID,omitempty"`
+ Status ruleStatus `xml:"Status"`
+ Filter Filter `xml:"Filter,omitempty"`
+ Prefix Prefix `xml:"Prefix,omitempty"`
+ Expiration Expiration `xml:"Expiration,omitempty"`
+ Transition Transition `xml:"Transition,omitempty"`
+}
+
+// Filter - a filter for a lifecycle configuration Rule.
+type Filter struct {
+ XMLName xml.Name `xml:"Filter"`
+ set bool
+
+ Prefix Prefix
+
+ And And
+ andSet bool
+
+ Tag Tag
+ tagSet bool
+}
+
+// Prefix holds the prefix xml tag in <Rule> and <Filter>
+type Prefix struct {
+ string
+ set bool
+}
+
+// MarshalXML - decodes XML data.
+func (p Prefix) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if !p.set {
+ return nil
+ }
+ return e.EncodeElement(p.string, startElement)
+}
+
+func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
+ return err
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
+type And struct {
+ XMLName xml.Name `xml:"And"`
+ Prefix Prefix `xml:"Prefix,omitempty"`
+ Tags []Tag `xml:"Tag,omitempty"`
+}
+
+// Expiration - expiration actions for a rule in lifecycle configuration.
+type Expiration struct {
+ XMLName xml.Name `xml:"Expiration"`
+ Days int `xml:"Days,omitempty"`
+ Date ExpirationDate `xml:"Date,omitempty"`
+ DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker"`
+
+ set bool
+}
+
+// MarshalXML encodes expiration field into an XML form.
+func (e Expiration) MarshalXML(enc *xml.Encoder, startElement xml.StartElement) error {
+ if !e.set {
+ return nil
+ }
+ type expirationWrapper Expiration
+ return enc.EncodeElement(expirationWrapper(e), startElement)
+}
+
+// ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element.
+type ExpireDeleteMarker struct {
+ val bool
+ set bool
+}
+
+// MarshalXML encodes delete marker boolean into an XML form.
+func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if !b.set {
+ return nil
+ }
+ return e.EncodeElement(b.val, startElement)
+}
+
+// ExpirationDate is a embedded type containing time.Time to unmarshal
+// Date in Expiration
+type ExpirationDate struct {
+ time.Time
+}
+
+// MarshalXML encodes expiration date if it is non-zero and encodes
+// empty string otherwise
+func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if eDate.Time.IsZero() {
+ return nil
+ }
+ return e.EncodeElement(eDate.Format(time.RFC3339), startElement)
+}
+
+// Transition - transition actions for a rule in lifecycle configuration.
+type Transition struct {
+ XMLName xml.Name `xml:"Transition"`
+ Days int `xml:"Days,omitempty"`
+ Date time.Time `xml:"Date,omitempty"`
+ StorageClass string `xml:"StorageClass,omitempty"`
+
+ set bool
+}
+
+// MarshalXML encodes transition field into an XML form.
+func (t Transition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
+ if !t.set {
+ return nil
+ }
+ type transitionWrapper Transition
+ return enc.EncodeElement(transitionWrapper(t), start)
+}
+
+// TransitionDays is a type alias to unmarshal Days in Transition
+type TransitionDays int
diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go
index 5e72fdcb3..ee961bf3b 100644
--- a/weed/s3api/s3api_server.go
+++ b/weed/s3api/s3api_server.go
@@ -115,14 +115,30 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV2Handler, ACTION_LIST), "LIST")).Queries("list-type", "2")
// GetObject, but directory listing is not supported
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.GetObjectHandler, ACTION_READ), "GET"))
- // ListObjectsV1 (Legacy)
- bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV1Handler, ACTION_LIST), "LIST"))
// PostPolicy
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(track(s3a.iam.Auth(s3a.PostPolicyBucketHandler, ACTION_WRITE), "POST"))
// DeleteMultipleObjects
bucket.Methods("POST").HandlerFunc(track(s3a.iam.Auth(s3a.DeleteMultipleObjectsHandler, ACTION_WRITE), "DELETE")).Queries("delete", "")
+
+ // GetBucketACL
+ bucket.Methods("GET").HandlerFunc(s3a.iam.Auth(s3a.GetBucketAclHandler, ACTION_READ)).Queries("acl", "")
+
+ // GetObjectACL
+ bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(s3a.iam.Auth(s3a.GetObjectAclHandler, ACTION_READ)).Queries("acl", "")
+
+ // GetBucketLifecycleConfiguration
+ bucket.Methods("GET").HandlerFunc(s3a.iam.Auth(s3a.GetBucketLifecycleConfigurationHandler, ACTION_READ)).Queries("lifecycle", "")
+
+ // PutBucketLifecycleConfiguration
+ bucket.Methods("PUT").HandlerFunc(s3a.iam.Auth(s3a.PutBucketLifecycleConfigurationHandler, ACTION_WRITE)).Queries("lifecycle", "")
+
+ // DeleteBucketLifecycleConfiguration
+ bucket.Methods("DELETE").HandlerFunc(s3a.iam.Auth(s3a.DeleteBucketLifecycleHandler, ACTION_WRITE)).Queries("lifecycle", "")
+
+ // ListObjectsV1 (Legacy)
+ bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV1Handler, ACTION_LIST), "LIST"))
/*
// not implemented
@@ -132,8 +148,6 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
bucket.Methods("GET").HandlerFunc(s3a.GetBucketPolicyHandler).Queries("policy", "")
// GetObjectACL
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(s3a.GetObjectACLHandler).Queries("acl", "")
- // GetBucketACL
- bucket.Methods("GET").HandlerFunc(s3a.GetBucketACLHandler).Queries("acl", "")
// PutBucketPolicy
bucket.Methods("PUT").HandlerFunc(s3a.PutBucketPolicyHandler).Queries("policy", "")
// DeleteBucketPolicy
diff --git a/weed/s3api/s3api_xsd_generated.go b/weed/s3api/s3api_xsd_generated.go
index 9d62afc4e..a8e4ef404 100644
--- a/weed/s3api/s3api_xsd_generated.go
+++ b/weed/s3api/s3api_xsd_generated.go
@@ -8,12 +8,12 @@ import (
)
type AccessControlList struct {
- Grant []Grant `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Grant,omitempty"`
+ Grant []Grant `xml:"Grant,omitempty"`
}
type AccessControlPolicy struct {
- Owner CanonicalUser `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Owner"`
- AccessControlList AccessControlList `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlList"`
+ Owner CanonicalUser `xml:"Owner"`
+ AccessControlList AccessControlList `xml:"AccessControlList"`
}
type AmazonCustomerByEmail struct {
@@ -467,11 +467,17 @@ func (t *GetObjectResult) UnmarshalXML(d *xml.Decoder, start xml.StartElement) e
}
type Grant struct {
- Grantee Grantee `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Grantee"`
- Permission Permission `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Permission"`
+ Grantee Grantee `xml:"Grantee"`
+ Permission Permission `xml:"Permission"`
}
type Grantee struct {
+ XMLNS string `xml:"xmlns:xsi,attr"`
+ XMLXSI string `xml:"xsi:type,attr"`
+ Type string `xml:"Type"`
+ ID string `xml:"ID,omitempty"`
+ DisplayName string `xml:"DisplayName,omitempty"`
+ URI string `xml:"URI,omitempty"`
}
type Group struct {
diff --git a/weed/s3api/s3err/s3api_errors.go b/weed/s3api/s3err/s3api_errors.go
index a46bd0f04..3063df844 100644
--- a/weed/s3api/s3err/s3api_errors.go
+++ b/weed/s3api/s3err/s3api_errors.go
@@ -51,6 +51,7 @@ const (
ErrBucketAlreadyExists
ErrBucketAlreadyOwnedByYou
ErrNoSuchBucket
+ ErrNoSuchLifecycleConfiguration
ErrNoSuchKey
ErrNoSuchUpload
ErrInvalidBucketName
@@ -163,6 +164,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "The specified bucket does not exist",
HTTPStatusCode: http.StatusNotFound,
},
+ ErrNoSuchLifecycleConfiguration: {
+ Code: "NoSuchLifecycleConfiguration",
+ Description: "The lifecycle configuration does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
ErrNoSuchKey: {
Code: "NoSuchKey",
Description: "The specified key does not exist.",
@@ -196,7 +202,7 @@ var errorCodeResponse = map[ErrorCode]APIError{
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidTag: {
- Code: "InvalidArgument",
+ Code: "InvalidTag",
Description: "The Tag value you have provided is invalid",
HTTPStatusCode: http.StatusBadRequest,
},
diff --git a/weed/s3api/tags.go b/weed/s3api/tags.go
index 9ff7d1fba..979e5a80c 100644
--- a/weed/s3api/tags.go
+++ b/weed/s3api/tags.go
@@ -14,8 +14,9 @@ type TagSet struct {
}
type Tagging struct {
- XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Tagging"`
+ XMLName xml.Name `xml:"Tagging"`
TagSet TagSet `xml:"TagSet"`
+ Xmlns string `xml:"xmlns,attr"`
}
func (t *Tagging) ToTags() map[string]string {
@@ -27,7 +28,7 @@ func (t *Tagging) ToTags() map[string]string {
}
func FromTags(tags map[string]string) (t *Tagging) {
- t = &Tagging{}
+ t = &Tagging{Xmlns: "http://s3.amazonaws.com/doc/2006-03-01/"}
for k, v := range tags {
t.TagSet.Tag = append(t.TagSet.Tag, Tag{
Key: k,
diff --git a/weed/s3api/tags_test.go b/weed/s3api/tags_test.go
index 52adb36c1..d8beb1922 100644
--- a/weed/s3api/tags_test.go
+++ b/weed/s3api/tags_test.go
@@ -32,6 +32,7 @@ func TestXMLUnmarshall(t *testing.T) {
func TestXMLMarshall(t *testing.T) {
tags := &Tagging{
+ Xmlns: "http://s3.amazonaws.com/doc/2006-03-01/",
TagSet: TagSet{
[]Tag{
{