diff --git a/pkg/api/minioapi/buckets.go b/pkg/api/minioapi/buckets.go new file mode 100644 index 000000000..e1680c59b --- /dev/null +++ b/pkg/api/minioapi/buckets.go @@ -0,0 +1,235 @@ +package minioapi + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + mstorage "github.com/minio-io/minio/pkg/storage" + "github.com/minio-io/minio/pkg/utils/policy" +) + +func (server *minioApi) putBucketPolicyHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + acceptsContentType := getContentType(req) + + policy, ok := policy.Parsepolicy(req.Body) + if ok == false { + error := errorCodeError(InvalidPolicyDocument) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + return + } + + err := server.storage.StoreBucketPolicy(bucket, policy) + switch err := err.(type) { + case nil: + { + w.WriteHeader(http.StatusNoContent) + writeCommonHeaders(w, getContentString(acceptsContentType)) + w.Header().Set("Connection", "keep-alive") + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNotFound: + { + error := errorCodeError(NoSuchBucket) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BackendCorrupted: + case mstorage.ImplementationError: + { + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) getBucketPolicyHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + acceptsContentType := getContentType(req) + + p, err := server.storage.GetBucketPolicy(bucket) + switch err := err.(type) { + case nil: + { + responsePolicy, ret := json.Marshal(p) + if ret != nil { + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + writeCommonHeaders(w, getContentString(acceptsContentType)) + w.Header().Set("Connection", "keep-alive") + w.Write(responsePolicy) + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNotFound: + { + error := errorCodeError(NoSuchBucket) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketPolicyNotFound: + { + error := errorCodeError(NoSuchBucketPolicy) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BackendCorrupted: + case mstorage.ImplementationError: + { + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + + resources := getBucketResources(req.URL.Query()) + if resources.policy == true { + server.getBucketPolicyHandler(w, req) + return + } + + acceptsContentType := getContentType(req) + objects, isTruncated, err := server.storage.ListObjects(bucket, resources.prefix, 1000) + switch err := err.(type) { + case nil: // success + { + response := generateObjectsListResult(bucket, objects, isTruncated) + w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType)) + } + case mstorage.BucketNotFound: + { + error := errorCodeError(NoSuchBucket) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ImplementationError: + { + // Embed error log on server side + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ObjectNameInvalid: + { + error := errorCodeError(NoSuchKey) + errorResponse := getErrorResponse(error, resources.prefix) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) { + acceptsContentType := getContentType(req) + buckets, err := server.storage.ListBuckets() + switch err := err.(type) { + case nil: + { + response := generateBucketsListResult(buckets) + w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType)) + } + case mstorage.ImplementationError: + { + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, "") + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BackendCorrupted: + { + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, "") + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + err := server.storage.StoreBucket(bucket) + + resources := getBucketResources(req.URL.Query()) + if resources.policy == true { + server.putBucketPolicyHandler(w, req) + return + } + + acceptsContentType := getContentType(req) + switch err := err.(type) { + case nil: + { + w.Header().Set("Server", "Minio") + w.Header().Set("Connection", "close") + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketExists: + { + error := errorCodeError(BucketAlreadyExists) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ImplementationError: + { + // Embed errors log on serve side + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, bucket) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} diff --git a/pkg/api/minioapi/definitions.go b/pkg/api/minioapi/definitions.go index ec8bd05c2..acc82cc8a 100644 --- a/pkg/api/minioapi/definitions.go +++ b/pkg/api/minioapi/definitions.go @@ -62,11 +62,12 @@ type Owner struct { var unimplementedBucketResourceNames = map[string]bool{ "acl": true, + "cors": true, "lifecycle": true, - "policy": true, "location": true, "logging": true, "notification": true, + "tagging": true, "versions": true, "requestPayment": true, "versioning": true, diff --git a/pkg/api/minioapi/errors.go b/pkg/api/minioapi/errors.go index 28173441b..b189b4c7c 100644 --- a/pkg/api/minioapi/errors.go +++ b/pkg/api/minioapi/errors.go @@ -59,6 +59,8 @@ const ( RequestTimeTooSkewed SignatureDoesNotMatch TooManyBuckets + InvalidPolicyDocument + NoSuchBucketPolicy ) var errorCodeResponse = map[int]Error{ @@ -167,6 +169,16 @@ var errorCodeResponse = map[int]Error{ Description: "You have attempted to create more buckets than allowed.", HttpStatusCode: http.StatusBadRequest, }, + InvalidPolicyDocument: { + Code: "InvalidPolicyDocument", + Description: "The content of the form does not meet the conditions specified in the policy document.", + HttpStatusCode: http.StatusBadRequest, + }, + NoSuchBucketPolicy: { + Code: "NoSuchBucketPolicy", + Description: "The specified bucket does not have a bucket policy.", + HttpStatusCode: http.StatusNotFound, + }, } // errorCodeError provides errorCode to Error. It returns empty if diff --git a/pkg/api/minioapi/minioapi.go b/pkg/api/minioapi/minioapi.go deleted file mode 100644 index b26e3281c..000000000 --- a/pkg/api/minioapi/minioapi.go +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Mini Object Storage, (C) 2014 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 minioapi - -import ( - "log" - "net/http" - - "github.com/gorilla/mux" - mstorage "github.com/minio-io/minio/pkg/storage" - "github.com/minio-io/minio/pkg/utils/config" -) - -const ( - dateFormat = "2006-01-02T15:04:05.000Z" -) - -type minioApi struct { - storage mstorage.Storage -} - -// No encoder interface exists, so we create one. -type encoder interface { - Encode(v interface{}) error -} - -func HttpHandler(storage mstorage.Storage) http.Handler { - mux := mux.NewRouter() - var api = minioApi{} - api.storage = storage - - var conf = config.Config{} - if err := conf.SetupConfig(); err != nil { - log.Fatal(err) - } - - mux.HandleFunc("/", api.listBucketsHandler).Methods("GET") - mux.HandleFunc("/{bucket}", api.listObjectsHandler).Methods("GET") - mux.HandleFunc("/{bucket}", api.putBucketHandler).Methods("PUT") - mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET") - mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD") - mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT") - - return validateHandler(conf, ignoreUnimplementedResources(mux)) -} - -func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - prefix, ok := vars["prefix"] - if !ok { - prefix = "" - } - - acceptsContentType := getContentType(req) - buckets, err := server.storage.ListBuckets(prefix) - switch err := err.(type) { - case nil: - { - response := generateBucketsListResult(buckets) - w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType)) - } - case mstorage.BucketNameInvalid: - { - error := errorCodeError(InvalidBucketName) - errorResponse := getErrorResponse(error, prefix) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ImplementationError: - { - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, prefix) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BackendCorrupted: - { - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, prefix) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } -} - -func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - bucket := vars["bucket"] - prefix, ok := vars["prefix"] - if !ok { - prefix = "" - } - - acceptsContentType := getContentType(req) - - objects, isTruncated, err := server.storage.ListObjects(bucket, prefix, 1000) - switch err := err.(type) { - case nil: // success - { - response := generateObjectsListResult(bucket, objects, isTruncated) - w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType)) - } - case mstorage.BucketNotFound: - { - error := errorCodeError(NoSuchBucket) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ImplementationError: - { - // Embed error log on server side - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BucketNameInvalid: - { - error := errorCodeError(InvalidBucketName) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ObjectNameInvalid: - { - error := errorCodeError(NoSuchKey) - errorResponse := getErrorResponse(error, prefix) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } -} - -func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - bucket := vars["bucket"] - err := server.storage.StoreBucket(bucket) - - acceptsContentType := getContentType(req) - - switch err := err.(type) { - case nil: - { - w.Header().Set("Server", "Minio") - w.Header().Set("Connection", "close") - } - case mstorage.BucketNameInvalid: - { - error := errorCodeError(InvalidBucketName) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BucketExists: - { - error := errorCodeError(BucketAlreadyExists) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ImplementationError: - { - // Embed errors log on serve side - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, bucket) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } -} - -func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - bucket := vars["bucket"] - object := vars["object"] - - acceptsContentType := getContentType(req) - - metadata, err := server.storage.GetObjectMetadata(bucket, object) - switch err := err.(type) { - case nil: // success - { - log.Println("Found: " + bucket + "#" + object) - writeObjectHeaders(w, metadata) - if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil { - log.Println(err) - } - } - case mstorage.ObjectNotFound: - { - error := errorCodeError(NoSuchKey) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ObjectNameInvalid: - { - error := errorCodeError(NoSuchKey) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BucketNameInvalid: - { - error := errorCodeError(InvalidBucketName) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ImplementationError: - { - // Embed errors log on serve side - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } -} - -func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - bucket := vars["bucket"] - object := vars["object"] - - acceptsContentType := getContentType(req) - - metadata, err := server.storage.GetObjectMetadata(bucket, object) - switch err := err.(type) { - case nil: - writeObjectHeaders(w, metadata) - case mstorage.ObjectNotFound: - { - error := errorCodeError(NoSuchKey) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ObjectNameInvalid: - { - error := errorCodeError(NoSuchKey) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ImplementationError: - { - // Embed error log on server side - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } -} - -func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - bucket := vars["bucket"] - object := vars["object"] - - acceptsContentType := getContentType(req) - - err := server.storage.StoreObject(bucket, object, "", req.Body) - switch err := err.(type) { - case nil: - w.Header().Set("Server", "Minio") - w.Header().Set("Connection", "close") - case mstorage.ImplementationError: - { - // Embed error log on server side - log.Println(err) - error := errorCodeError(InternalError) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BucketNotFound: - { - error := errorCodeError(NoSuchBucket) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.BucketNameInvalid: - { - error := errorCodeError(InvalidBucketName) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - case mstorage.ObjectExists: - { - error := errorCodeError(NotImplemented) - errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) - w.WriteHeader(error.HttpStatusCode) - w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) - } - } - -} - -func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse { - var listbuckets []*Bucket - var data = BucketListResponse{} - var owner = Owner{} - - owner.ID = "minio" - owner.DisplayName = "minio" - - for _, bucket := range buckets { - var listbucket = &Bucket{} - listbucket.Name = bucket.Name - listbucket.CreationDate = bucket.Created.Format(dateFormat) - listbuckets = append(listbuckets, listbucket) - } - - data.Owner = owner - data.Buckets.Bucket = listbuckets - - return data -} - -// takes a set of objects and prepares the objects for serialization -// input: -// bucket name -// array of object metadata -// results truncated flag -// -// output: -// populated struct that can be serialized to match xml and json api spec output -func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse { - var contents []*Item - var owner = Owner{} - var data = ObjectListResponse{} - - owner.ID = "minio" - owner.DisplayName = "minio" - - for _, object := range objects { - var content = &Item{} - content.Key = object.Key - content.LastModified = object.Created.Format(dateFormat) - content.ETag = object.ETag - content.Size = object.Size - content.StorageClass = "STANDARD" - content.Owner = owner - contents = append(contents, content) - } - data.Name = bucket - data.Contents = contents - data.MaxKeys = MAX_OBJECT_LIST - data.IsTruncated = isTruncated - return data -} diff --git a/pkg/api/minioapi/minioapi_test.go b/pkg/api/minioapi/minioapi_test.go index de0c37bee..cfa53a388 100644 --- a/pkg/api/minioapi/minioapi_test.go +++ b/pkg/api/minioapi/minioapi_test.go @@ -218,7 +218,7 @@ func (s *MySuite) TestPutBucket(c *C) { testServer := httptest.NewServer(httpHandler) defer testServer.Close() - buckets, err := storage.ListBuckets("bucket") + buckets, err := storage.ListBuckets() c.Assert(len(buckets), Equals, 0) c.Assert(err, IsNil) @@ -231,7 +231,7 @@ func (s *MySuite) TestPutBucket(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // check bucket exists - buckets, err = storage.ListBuckets("bucket") + buckets, err = storage.ListBuckets() c.Assert(len(buckets), Equals, 1) c.Assert(err, IsNil) c.Assert(buckets[0].Name, Equals, "bucket") diff --git a/pkg/api/minioapi/objects.go b/pkg/api/minioapi/objects.go new file mode 100644 index 000000000..db0fbd559 --- /dev/null +++ b/pkg/api/minioapi/objects.go @@ -0,0 +1,143 @@ +package minioapi + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" + mstorage "github.com/minio-io/minio/pkg/storage" +) + +func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) { + var object, bucket string + + acceptsContentType := getContentType(req) + vars := mux.Vars(req) + bucket = vars["bucket"] + object = vars["object"] + + metadata, err := server.storage.GetObjectMetadata(bucket, object) + switch err := err.(type) { + case nil: // success + { + log.Println("Found: " + bucket + "#" + object) + writeObjectHeaders(w, metadata) + if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil { + log.Println(err) + } + } + case mstorage.ObjectNotFound: + { + error := errorCodeError(NoSuchKey) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ObjectNameInvalid: + { + error := errorCodeError(NoSuchKey) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ImplementationError: + { + // Embed errors log on serve side + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) { + var object, bucket string + acceptsContentType := getContentType(req) + vars := mux.Vars(req) + bucket = vars["bucket"] + object = vars["object"] + + metadata, err := server.storage.GetObjectMetadata(bucket, object) + switch err := err.(type) { + case nil: + writeObjectHeaders(w, metadata) + case mstorage.ObjectNotFound: + { + error := errorCodeError(NoSuchKey) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ObjectNameInvalid: + { + error := errorCodeError(NoSuchKey) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ImplementationError: + { + // Embed error log on server side + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } +} + +func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) { + var object, bucket string + vars := mux.Vars(req) + acceptsContentType := getContentType(req) + bucket = vars["bucket"] + object = vars["object"] + + err := server.storage.StoreObject(bucket, object, "", req.Body) + switch err := err.(type) { + case nil: + w.Header().Set("Server", "Minio") + w.Header().Set("Connection", "close") + case mstorage.ImplementationError: + { + // Embed error log on server side + log.Println(err) + error := errorCodeError(InternalError) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNotFound: + { + error := errorCodeError(NoSuchBucket) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.BucketNameInvalid: + { + error := errorCodeError(InvalidBucketName) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + case mstorage.ObjectExists: + { + error := errorCodeError(NotImplemented) + errorResponse := getErrorResponse(error, "/"+bucket+"/"+object) + w.WriteHeader(error.HttpStatusCode) + w.Write(writeErrorResponse(w, errorResponse, acceptsContentType)) + } + } + +} diff --git a/pkg/api/minioapi/policy.go b/pkg/api/minioapi/policy.go new file mode 100644 index 000000000..c7423574f --- /dev/null +++ b/pkg/api/minioapi/policy.go @@ -0,0 +1,32 @@ +/* + * Mini Object Storage, (C) 2015 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 minioapi + +import ( + "net/http" +) + +type pHandler struct { + handler http.Handler +} + +func policyHandler(h http.Handler) http.Handler { + return pHandler{h} +} + +func (p pHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +} diff --git a/pkg/api/minioapi/resources.go b/pkg/api/minioapi/resources.go new file mode 100644 index 000000000..44e9fbbe6 --- /dev/null +++ b/pkg/api/minioapi/resources.go @@ -0,0 +1,33 @@ +package minioapi + +import ( + "net/url" + "strconv" +) + +type bucketResources struct { + prefix string + marker string + maxkeys int + policy bool + delimiter string + // uploads bool - TODO implemented with multipart support +} + +func getBucketResources(values url.Values) (v bucketResources) { + for key, value := range values { + switch true { + case key == "prefix": + v.prefix = value[0] + case key == "marker": + v.marker = value[0] + case key == "maxkeys": + v.maxkeys, _ = strconv.Atoi(value[0]) + case key == "policy": + v.policy = true + case key == "delimiter": + v.delimiter = value[0] + } + } + return +} diff --git a/pkg/api/minioapi/response.go b/pkg/api/minioapi/response.go new file mode 100644 index 000000000..6789b4d7f --- /dev/null +++ b/pkg/api/minioapi/response.go @@ -0,0 +1,59 @@ +package minioapi + +import ( + mstorage "github.com/minio-io/minio/pkg/storage" +) + +func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse { + var listbuckets []*Bucket + var data = BucketListResponse{} + var owner = Owner{} + + owner.ID = "minio" + owner.DisplayName = "minio" + + for _, bucket := range buckets { + var listbucket = &Bucket{} + listbucket.Name = bucket.Name + listbucket.CreationDate = bucket.Created.Format(dateFormat) + listbuckets = append(listbuckets, listbucket) + } + + data.Owner = owner + data.Buckets.Bucket = listbuckets + + return data +} + +// takes a set of objects and prepares the objects for serialization +// input: +// bucket name +// array of object metadata +// results truncated flag +// +// output: +// populated struct that can be serialized to match xml and json api spec output +func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse { + var contents []*Item + var owner = Owner{} + var data = ObjectListResponse{} + + owner.ID = "minio" + owner.DisplayName = "minio" + + for _, object := range objects { + var content = &Item{} + content.Key = object.Key + content.LastModified = object.Created.Format(dateFormat) + content.ETag = object.ETag + content.Size = object.Size + content.StorageClass = "STANDARD" + content.Owner = owner + contents = append(contents, content) + } + data.Name = bucket + data.Contents = contents + data.MaxKeys = MAX_OBJECT_LIST + data.IsTruncated = isTruncated + return data +} diff --git a/pkg/api/minioapi/router.go b/pkg/api/minioapi/router.go new file mode 100644 index 000000000..ac282dc49 --- /dev/null +++ b/pkg/api/minioapi/router.go @@ -0,0 +1,59 @@ +/* + * Mini Object Storage, (C) 2014 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 minioapi + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" + mstorage "github.com/minio-io/minio/pkg/storage" + "github.com/minio-io/minio/pkg/utils/config" +) + +const ( + dateFormat = "2006-01-02T15:04:05.000Z" +) + +type minioApi struct { + storage mstorage.Storage +} + +// No encoder interface exists, so we create one. +type encoder interface { + Encode(v interface{}) error +} + +func HttpHandler(storage mstorage.Storage) http.Handler { + mux := mux.NewRouter() + var api = minioApi{} + api.storage = storage + + var conf = config.Config{} + if err := conf.SetupConfig(); err != nil { + log.Fatal(err) + } + + mux.HandleFunc("/", api.listBucketsHandler).Methods("GET") + mux.HandleFunc("/{bucket}", api.listObjectsHandler).Methods("GET") + mux.HandleFunc("/{bucket}", api.putBucketHandler).Methods("PUT") + mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET") + mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD") + mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT") + + return validateHandler(conf, ignoreUnimplementedResources(mux)) +} diff --git a/pkg/api/minioapi/handler.go b/pkg/api/minioapi/validate.go similarity index 100% rename from pkg/api/minioapi/handler.go rename to pkg/api/minioapi/validate.go diff --git a/pkg/storage/fs/fs.go b/pkg/storage/fs/fs.go index a07b696da..ea9f0a809 100644 --- a/pkg/storage/fs/fs.go +++ b/pkg/storage/fs/fs.go @@ -27,6 +27,7 @@ import ( "sync" mstorage "github.com/minio-io/minio/pkg/storage" + "github.com/minio-io/minio/pkg/utils/policy" ) type storage struct { @@ -57,13 +58,7 @@ func start(ctrlChannel <-chan string, errorChannel chan<- error) { // Bucket Operations -func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, error) { - if prefix != "" { - if mstorage.IsValidBucket(prefix) == false { - return []mstorage.BucketMetadata{}, mstorage.BucketNameInvalid{Bucket: prefix} - } - } - +func (storage *storage) ListBuckets() ([]mstorage.BucketMetadata, error) { files, err := ioutil.ReadDir(storage.root) if err != nil { return []mstorage.BucketMetadata{}, mstorage.EmbedError("bucket", "", err) @@ -74,13 +69,11 @@ func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, e if !file.IsDir() { return []mstorage.BucketMetadata{}, mstorage.BackendCorrupted{Path: storage.root} } - if strings.HasPrefix(file.Name(), prefix) { - metadata := mstorage.BucketMetadata{ - Name: file.Name(), - Created: file.ModTime(), // TODO - provide real created time - } - metadataList = append(metadataList, metadata) + metadata := mstorage.BucketMetadata{ + Name: file.Name(), + Created: file.ModTime(), // TODO - provide real created time } + metadataList = append(metadataList, metadata) } return metadataList, nil } @@ -112,6 +105,86 @@ func (storage *storage) StoreBucket(bucket string) error { return nil } +func (storage *storage) GetBucketPolicy(bucket string) (interface{}, error) { + storage.writeLock.Lock() + defer storage.writeLock.Unlock() + + var p policy.BucketPolicy + // verify bucket path legal + if mstorage.IsValidBucket(bucket) == false { + return policy.BucketPolicy{}, mstorage.BucketNameInvalid{Bucket: bucket} + } + + // get bucket path + bucketDir := path.Join(storage.root, bucket) + // check if bucket exists + if _, err := os.Stat(bucketDir); err != nil { + return policy.BucketPolicy{}, mstorage.BucketNotFound{Bucket: bucket} + } + + // get policy path + bucketPolicy := path.Join(storage.root, bucket+"_policy.json") + filestat, err := os.Stat(bucketPolicy) + if filestat.IsDir() { + return policy.BucketPolicy{}, mstorage.BackendCorrupted{Path: bucketPolicy} + } + + if os.IsNotExist(err) { + return policy.BucketPolicy{}, mstorage.BucketPolicyNotFound{Bucket: bucket} + } + + file, err := os.OpenFile(bucketPolicy, os.O_RDONLY, 0666) + defer file.Close() + if err != nil { + return policy.BucketPolicy{}, mstorage.EmbedError(bucket, "", err) + } + encoder := json.NewDecoder(file) + err = encoder.Decode(&p) + if err != nil { + return policy.BucketPolicy{}, mstorage.EmbedError(bucket, "", err) + } + + return p, nil + +} + +func (storage *storage) StoreBucketPolicy(bucket string, policy interface{}) error { + storage.writeLock.Lock() + defer storage.writeLock.Unlock() + + // verify bucket path legal + if mstorage.IsValidBucket(bucket) == false { + return mstorage.BucketNameInvalid{Bucket: bucket} + } + + // get bucket path + bucketDir := path.Join(storage.root, bucket) + // check if bucket exists + if _, err := os.Stat(bucketDir); err != nil { + return mstorage.BucketNotFound{ + Bucket: bucket, + } + } + + // get policy path + bucketPolicy := path.Join(storage.root, bucket+"_policy.json") + filestat, _ := os.Stat(bucketPolicy) + if filestat.IsDir() { + return mstorage.BackendCorrupted{Path: bucketPolicy} + } + file, err := os.OpenFile(bucketPolicy, os.O_WRONLY|os.O_CREATE, 0600) + defer file.Close() + if err != nil { + return mstorage.EmbedError(bucket, "", err) + } + encoder := json.NewEncoder(file) + err = encoder.Encode(policy) + if err != nil { + return mstorage.EmbedError(bucket, "", err) + } + return nil +} + // Object Operations func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error) { @@ -145,6 +218,10 @@ func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object st } } file, err := os.Open(objectPath) + defer file.Close() + if err != nil { + return 0, mstorage.EmbedError(bucket, object, err) + } count, err := io.Copy(w, file) if err != nil { return count, mstorage.EmbedError(bucket, object, err) diff --git a/pkg/storage/inmemory/inmemory.go b/pkg/storage/inmemory/inmemory.go index b9b03e826..1bea65b4f 100644 --- a/pkg/storage/inmemory/inmemory.go +++ b/pkg/storage/inmemory/inmemory.go @@ -26,6 +26,7 @@ import ( "time" mstorage "github.com/minio-io/minio/pkg/storage" + "github.com/minio-io/minio/pkg/utils/policy" ) type storage struct { @@ -57,6 +58,14 @@ func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object st } } +func (storage *storage) StoreBucketPolicy(bucket string, policy interface{}) error { + return mstorage.ApiNotImplemented{Api: "PutBucketPolicy"} +} + +func (storage *storage) GetBucketPolicy(bucket string) (interface{}, error) { + return policy.BucketPolicy{}, mstorage.ApiNotImplemented{Api: "GetBucketPolicy"} +} + func (storage *storage) StoreObject(bucket, key, contentType string, data io.Reader) error { objectKey := bucket + ":" + key @@ -145,12 +154,10 @@ func (b ByBucketName) Len() int { return len(b) } func (b ByBucketName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b ByBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name } -func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, error) { +func (storage *storage) ListBuckets() ([]mstorage.BucketMetadata, error) { var results []mstorage.BucketMetadata - for key, bucket := range storage.bucketdata { - if strings.HasPrefix(key, prefix) { - results = append(results, bucket.metadata) - } + for _, bucket := range storage.bucketdata { + results = append(results, bucket.metadata) } sort.Sort(ByBucketName(results)) return results, nil diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 7a8092612..3ffaf32ef 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -24,8 +24,10 @@ import ( type Storage interface { // Bucket Operations - ListBuckets(prefix string) ([]BucketMetadata, error) + ListBuckets() ([]BucketMetadata, error) StoreBucket(bucket string) error + StoreBucketPolicy(bucket string, policy interface{}) error + GetBucketPolicy(bucket string) (interface{}, error) // Object Operations CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error) diff --git a/pkg/storage/storage_api_suite.go b/pkg/storage/storage_api_suite.go index badfd2b20..09ddc5537 100644 --- a/pkg/storage/storage_api_suite.go +++ b/pkg/storage/storage_api_suite.go @@ -183,7 +183,7 @@ func testListBuckets(c *C, create func() Storage) { storage := create() // test empty list - buckets, err := storage.ListBuckets("") + buckets, err := storage.ListBuckets() c.Assert(len(buckets), Equals, 0) c.Assert(err, IsNil) @@ -191,7 +191,7 @@ func testListBuckets(c *C, create func() Storage) { err = storage.StoreBucket("bucket1") c.Assert(err, IsNil) - buckets, err = storage.ListBuckets("") + buckets, err = storage.ListBuckets() c.Assert(len(buckets), Equals, 1) c.Assert(err, IsNil) @@ -199,20 +199,16 @@ func testListBuckets(c *C, create func() Storage) { err = storage.StoreBucket("bucket2") c.Assert(err, IsNil) - buckets, err = storage.ListBuckets("") + buckets, err = storage.ListBuckets() c.Assert(len(buckets), Equals, 2) c.Assert(err, IsNil) // add three and test exists + prefix err = storage.StoreBucket("bucket22") - buckets, err = storage.ListBuckets("") + buckets, err = storage.ListBuckets() c.Assert(len(buckets), Equals, 3) c.Assert(err, IsNil) - - buckets, err = storage.ListBuckets("bucket2") - c.Assert(len(buckets), Equals, 2) - c.Assert(err, IsNil) } func testListBucketsOrder(c *C, create func() Storage) { @@ -224,7 +220,7 @@ func testListBucketsOrder(c *C, create func() Storage) { storage.StoreBucket("bucket1") storage.StoreBucket("bucket2") - buckets, err := storage.ListBuckets("bucket") + buckets, err := storage.ListBuckets() c.Assert(len(buckets), Equals, 2) c.Assert(err, IsNil) c.Assert(buckets[0].Name, Equals, "bucket1") diff --git a/pkg/storage/storage_errors.go b/pkg/storage/storage_errors.go index 152e55e62..37c41dddf 100644 --- a/pkg/storage/storage_errors.go +++ b/pkg/storage/storage_errors.go @@ -30,6 +30,10 @@ type ObjectExists struct { Key string } +type ApiNotImplemented struct { + Api string +} + type ObjectNotFound GenericObjectError type GenericBucketError struct { @@ -68,15 +72,24 @@ func EmbedError(bucket, object string, err error) ImplementationError { } type BackendCorrupted BackendError +type BucketPolicyNotFound GenericBucketError type BucketNameInvalid GenericBucketError type BucketExists GenericBucketError type BucketNotFound GenericBucketError type ObjectNameInvalid GenericObjectError +func (self BucketPolicyNotFound) Error() string { + return "Bucket policy not found for: " + self.Bucket +} + func (self ObjectNotFound) Error() string { return "Object not Found: " + self.Bucket + "#" + self.Object } +func (self ApiNotImplemented) Error() string { + return "Api not implemented: " + self.Api +} + func (self ObjectExists) Error() string { return "Object exists: " + self.Bucket + "#" + self.Key } diff --git a/pkg/utils/policy/date.go b/pkg/utils/policy/date.go new file mode 100644 index 000000000..2c1b83151 --- /dev/null +++ b/pkg/utils/policy/date.go @@ -0,0 +1,60 @@ +package policy + +import ( + "fmt" + "strconv" + "strings" +) + +// For 0000-00-00 Date type +type Date struct { + Year int16 + Month byte + Day byte +} + +func (d Date) String() string { + return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) +} + +// True if date is 0000-00-00 +func (d Date) IsZero() bool { + return d.Day == 0 && d.Month == 0 && d.Year == 0 +} + +// Convert string date in format YYYY-MM-DD to Date. +// Leading and trailing spaces are ignored. If format is invalid returns zero. +func ParseDate(str string) (d Date, err error) { + str = strings.TrimSpace(str) + if str == "0000-00-00" { + return + } + var ( + y, m, n int + ) + if len(str) != 10 || str[4] != '-' || str[7] != '-' { + err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str) + return + } + if y, err = strconv.Atoi(str[0:4]); err != nil { + return + } + if m, err = strconv.Atoi(str[5:7]); err != nil { + return + } + if m < 1 || m > 12 { + err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str) + return + } + if n, err = strconv.Atoi(str[8:10]); err != nil { + return + } + if n < 1 || n > 31 { + err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str) + return + } + d.Year = int16(y) + d.Month = byte(m) + d.Day = byte(n) + return +} diff --git a/pkg/utils/policy/policy.go b/pkg/utils/policy/policy.go new file mode 100644 index 000000000..ee3d10a15 --- /dev/null +++ b/pkg/utils/policy/policy.go @@ -0,0 +1,67 @@ +package policy + +import ( + "encoding/json" + "io" +) + +type UserCred struct { + AWS string +} + +type Stmt struct { + Sid string + Effect string + Principal UserCred + Action []string + Resource []string +} + +type BucketPolicy struct { + Version string // date in 0000-00-00 format + Statement []Stmt +} + +// TODO: Add more checks + +// validate request body is proper JSON +func Parsepolicy(data io.Reader) (BucketPolicy, bool) { + var policy BucketPolicy + decoder := json.NewDecoder(data) + err := decoder.Decode(&policy) + if err != nil { + goto error + } + if len(policy.Version) == 0 { + goto error + } + _, err = ParseDate(policy.Version) + if err != nil { + goto error + } + if len(policy.Statement) == 0 { + goto error + } + + for _, statement := range policy.Statement { + if len(statement.Sid) == 0 { + goto error + } + if len(statement.Effect) == 0 { + goto error + } + if len(statement.Principal.AWS) == 0 { + goto error + } + if len(statement.Action) == 0 { + goto error + } + if len(statement.Resource) == 0 { + goto error + } + } + return policy, true + +error: + return BucketPolicy{}, false +}