func (api Minio) isValidOp(w http.ResponseWriter, req *http.Request, acceptsContentType contentType) bool { vars := mux.Vars(req) bucket := vars["bucket"] bucketMetadata, err := api.Donut.GetBucketMetadata(bucket, nil) if err == nil { if _, err := StripAccessKeyID(req.Header.Get("Authorization")); err != nil { if bucketMetadata.ACL.IsPrivate() { return true //uncomment this when we have webcli //writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path) //return false } if bucketMetadata.ACL.IsPublicRead() && req.Method == "PUT" { return true //uncomment this when we have webcli //writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path) //return false } } return true } switch err.ToGoError().(type) { case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) return false case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) return false default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return false } }
// ListMultipartUploadsHandler - GET Bucket (List Multipart uploads) // ------------------------- // This operation lists in-progress multipart uploads. An in-progress // multipart upload is a multipart upload that has been initiated, // using the Initiate Multipart Upload request, but has not yet been completed or aborted. // This operation returns at most 1,000 multipart uploads in the response. // func (api Minio) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } resources := getBucketMultipartResources(req.URL.Query()) if resources.MaxUploads < 0 { writeErrorResponse(w, req, InvalidMaxUploads, acceptsContentType, req.URL.Path) return } if resources.MaxUploads == 0 { resources.MaxUploads = maxObjectList } vars := mux.Vars(req) bucket := vars["bucket"] var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } resources, err := api.Donut.ListMultipartUploads(bucket, resources, signature) if err == nil { // generate response response := generateListMultipartUploadsResponse(bucket, resources) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) // write headers setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) // write body w.Write(encodedSuccessResponse) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// NewMultipartUploadHandler - New multipart upload func (api Minio) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } if !isRequestUploads(req.URL.Query()) { writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) return } var object, bucket string vars := mux.Vars(req) bucket = vars["bucket"] object = vars["object"] var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } uploadID, err := api.Donut.NewMultipartUpload(bucket, object, req.Header.Get("Content-Type"), signature) if err == nil { response := generateInitiateMultipartUploadResponse(bucket, object, uploadID) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) // write headers setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) // write body w.Write(encodedSuccessResponse) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.ObjectExists: writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// HeadObjectHandler - HEAD Object // ----------- // The HEAD operation retrieves metadata from an object without returning the object itself. func (api Minio) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { // ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } var object, bucket string vars := mux.Vars(req) bucket = vars["bucket"] object = vars["object"] var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.GetObjectMetadata(bucket, object, signature) if err == nil { setObjectHeaders(w, metadata, nil) w.WriteHeader(http.StatusOK) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) case donut.ObjectNotFound: writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) case donut.ObjectNameInvalid: writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// PutBucketACLHandler - PUT Bucket ACL // ---------- // This implementation of the PUT operation modifies the bucketACL for authenticated request func (api Minio) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path) return } vars := mux.Vars(req) bucket := vars["bucket"] var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } err := api.Donut.SetBucketMetadata(bucket, map[string]string{"acl": getACLTypeString(aclType)}, signature) if err == nil { writeSuccessResponse(w, acceptsContentType) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// AbortMultipartUploadHandler - Abort multipart upload func (api Minio) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } vars := mux.Vars(req) bucket := vars["bucket"] object := vars["object"] objectResourcesMetadata := getObjectResources(req.URL.Query()) var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } err := api.Donut.AbortMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, signature) if err == nil { setCommonHeaders(w, getContentTypeString(acceptsContentType), 0) w.WriteHeader(http.StatusNoContent) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.InvalidUploadID: writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// PutBucketHandler - PUT Bucket // ---------- // This implementation of the PUT operation creates a new bucket for authenticated request func (api Minio) PutBucketHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) // uncomment this when we have webcli // without access key credentials one cannot create a bucket // if _, err := StripAccessKeyID(req); err != nil { // writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path) // return // } if isRequestBucketACL(req.URL.Query()) { api.PutBucketACLHandler(w, req) return } // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path) return } vars := mux.Vars(req) bucket := vars["bucket"] var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } // if body of request is non-nil then check for validity of Content-Length if req.Body != nil { /// if Content-Length missing, deny the request size := req.Header.Get("Content-Length") if size == "" { writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path) return } } err := api.Donut.MakeBucket(bucket, getACLTypeString(aclType), req.Body, signature) if err == nil { // Make sure to add Location information here only for bucket w.Header().Set("Location", "/"+bucket) writeSuccessResponse(w, acceptsContentType) return } switch err.ToGoError().(type) { case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.TooManyBuckets: writeErrorResponse(w, req, TooManyBuckets, acceptsContentType, req.URL.Path) case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) case donut.BucketExists: writeErrorResponse(w, req, BucketAlreadyExists, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// CompleteMultipartUploadHandler - Complete multipart upload func (api Minio) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } vars := mux.Vars(req) bucket := vars["bucket"] object := vars["object"] objectResourcesMetadata := getObjectResources(req.URL.Query()) var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, req.Body, signature) if err == nil { response := generateCompleteMultpartUploadResponse(bucket, object, "", metadata.MD5Sum) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) // write headers setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) // write body w.Write(encodedSuccessResponse) return } switch err.ToGoError().(type) { case donut.InvalidUploadID: writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) case donut.InvalidPart: writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path) case donut.InvalidPartOrder: writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path) case donut.MissingDateHeader: writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.IncompleteBody: writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) case donut.MalformedXML: writeErrorResponse(w, req, MalformedXML, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// PutObjectPartHandler - Upload part func (api Minio) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } // get Content-MD5 sent by client and verify if valid md5 := req.Header.Get("Content-MD5") if !isValidMD5(md5) { writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) return } /// if Content-Length missing, throw away size := req.Header.Get("Content-Length") if size == "" { writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path) return } /// maximum Upload size for multipart objects in a single operation if isMaxObjectSize(size) { writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) return } var sizeInt64 int64 { var err error sizeInt64, err = strconv.ParseInt(size, 10, 64) if err != nil { writeErrorResponse(w, req, InvalidRequest, acceptsContentType, req.URL.Path) return } } vars := mux.Vars(req) bucket := vars["bucket"] object := vars["object"] uploadID := req.URL.Query().Get("uploadId") partIDString := req.URL.Query().Get("partNumber") var partID int { var err error partID, err = strconv.Atoi(partIDString) if err != nil { writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path) } } var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } calculatedMD5, err := api.Donut.CreateObjectPart(bucket, object, uploadID, partID, "", md5, sizeInt64, req.Body, signature) if err == nil { w.Header().Set("ETag", calculatedMD5) writeSuccessResponse(w, acceptsContentType) return } switch err.ToGoError().(type) { case donut.InvalidUploadID: writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) case donut.ObjectExists: writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) case donut.BadDigest: writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.IncompleteBody: writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) case donut.EntityTooLarge: writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) case donut.InvalidDigest: writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }
// PutObjectHandler - PUT Object // ---------- // This implementation of the PUT operation adds an object to a bucket. func (api Minio) PutObjectHandler(w http.ResponseWriter, req *http.Request) { // Ticket master block { op := Operation{} op.ProceedCh = make(chan struct{}) api.OP <- op // block until Ticket master gives us a go <-op.ProceedCh } acceptsContentType := getContentType(req) if !api.isValidOp(w, req, acceptsContentType) { return } var object, bucket string vars := mux.Vars(req) bucket = vars["bucket"] object = vars["object"] // get Content-MD5 sent by client and verify if valid md5 := req.Header.Get("Content-MD5") if !isValidMD5(md5) { writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) return } /// if Content-Length missing, deny the request size := req.Header.Get("Content-Length") if size == "" { writeErrorResponse(w, req, MissingContentLength, acceptsContentType, req.URL.Path) return } /// maximum Upload size for objects in a single operation if isMaxObjectSize(size) { writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) return } /// minimum Upload size for objects in a single operation // // Surprisingly while Amazon in their document states that S3 objects have 1byte // as the minimum limit, they do not seem to enforce it one can successfully // create a 0byte file using a regular putObject() operation // // if isMinObjectSize(size) { // writeErrorResponse(w, req, EntityTooSmall, acceptsContentType, req.URL.Path) // return // } var sizeInt64 int64 { var err error sizeInt64, err = strconv.ParseInt(size, 10, 64) if err != nil { writeErrorResponse(w, req, InvalidRequest, acceptsContentType, req.URL.Path) return } } var signature *donut.Signature if _, ok := req.Header["Authorization"]; ok { // Init signature V4 verification var err *probe.Error signature, err = InitSignatureV4(req) if err != nil { writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.CreateObject(bucket, object, md5, sizeInt64, req.Body, nil, signature) if err == nil { w.Header().Set("ETag", metadata.MD5Sum) writeSuccessResponse(w, acceptsContentType) return } switch err.ToGoError().(type) { case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) case donut.ObjectExists: writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) case donut.BadDigest: writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) case donut.MissingDateHeader: writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) case donut.IncompleteBody: writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) case donut.EntityTooLarge: writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) case donut.InvalidDigest: writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) default: log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } }