| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2017-01-19 04:24:34 +08:00
										 |  |  |  * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. | 
					
						
							| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	"mime/multipart" | 
					
						
							| 
									
										
										
										
											2017-11-15 08:56:24 +08:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2016-07-19 12:20:17 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2017-10-25 10:04:51 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-10-25 10:04:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/errors" | 
					
						
							| 
									
										
										
										
											2017-10-25 10:04:51 +08:00
										 |  |  | 	httptracer "github.com/minio/minio/pkg/handlers" | 
					
						
							| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-04 05:50:09 +08:00
										 |  |  | // Parses location constraint from the incoming reader.
 | 
					
						
							|  |  |  | func parseLocationConstraint(r *http.Request) (location string, s3Error APIErrorCode) { | 
					
						
							| 
									
										
										
										
											2016-07-19 12:20:17 +08:00
										 |  |  | 	// If the request has no body with content-length set to 0,
 | 
					
						
							|  |  |  | 	// we do not have to validate location constraint. Bucket will
 | 
					
						
							|  |  |  | 	// be created at default region.
 | 
					
						
							|  |  |  | 	locationConstraint := createBucketLocationConfiguration{} | 
					
						
							| 
									
										
										
										
											2016-09-30 06:51:00 +08:00
										 |  |  | 	err := xmlDecoder(r.Body, &locationConstraint, r.ContentLength) | 
					
						
							| 
									
										
										
										
											2017-04-04 05:50:09 +08:00
										 |  |  | 	if err != nil && err != io.EOF { | 
					
						
							|  |  |  | 		errorIf(err, "Unable to xml decode location constraint") | 
					
						
							|  |  |  | 		// Treat all other failures as XML parsing errors.
 | 
					
						
							|  |  |  | 		return "", ErrMalformedXML | 
					
						
							|  |  |  | 	} // else for both err as nil or io.EOF
 | 
					
						
							|  |  |  | 	location = locationConstraint.Location | 
					
						
							|  |  |  | 	if location == "" { | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 		location = globalServerConfig.GetRegion() | 
					
						
							| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-04 05:50:09 +08:00
										 |  |  | 	return location, ErrNone | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Validates input location is same as configured region
 | 
					
						
							|  |  |  | // of Minio server.
 | 
					
						
							|  |  |  | func isValidLocation(location string) bool { | 
					
						
							| 
									
										
										
										
											2017-11-30 05:12:47 +08:00
										 |  |  | 	return globalServerConfig.GetRegion() == "" || globalServerConfig.GetRegion() == location | 
					
						
							| 
									
										
										
										
											2016-04-29 11:01:11 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Supported headers that needs to be extracted.
 | 
					
						
							|  |  |  | var supportedHeaders = []string{ | 
					
						
							|  |  |  | 	"content-type", | 
					
						
							|  |  |  | 	"cache-control", | 
					
						
							|  |  |  | 	"content-encoding", | 
					
						
							|  |  |  | 	"content-disposition", | 
					
						
							| 
									
										
										
										
											2017-12-22 19:28:13 +08:00
										 |  |  | 	amzStorageClass, | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 	// Add more supported headers here.
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  | // isMetadataDirectiveValid - check if metadata-directive is valid.
 | 
					
						
							|  |  |  | func isMetadataDirectiveValid(h http.Header) bool { | 
					
						
							|  |  |  | 	_, ok := h[http.CanonicalHeaderKey("X-Amz-Metadata-Directive")] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		// Check atleast set metadata-directive is valid.
 | 
					
						
							|  |  |  | 		return (isMetadataCopy(h) || isMetadataReplace(h)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// By default if x-amz-metadata-directive is not we
 | 
					
						
							|  |  |  | 	// treat it as 'COPY' this function returns true.
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Check if the metadata COPY is requested.
 | 
					
						
							|  |  |  | func isMetadataCopy(h http.Header) bool { | 
					
						
							|  |  |  | 	return h.Get("X-Amz-Metadata-Directive") == "COPY" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Check if the metadata REPLACE is requested.
 | 
					
						
							|  |  |  | func isMetadataReplace(h http.Header) bool { | 
					
						
							|  |  |  | 	return h.Get("X-Amz-Metadata-Directive") == "REPLACE" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Splits an incoming path into bucket and object components.
 | 
					
						
							|  |  |  | func path2BucketAndObject(path string) (bucket, object string) { | 
					
						
							|  |  |  | 	// Skip the first element if it is '/', split the rest.
 | 
					
						
							|  |  |  | 	path = strings.TrimPrefix(path, "/") | 
					
						
							|  |  |  | 	pathComponents := strings.SplitN(path, "/", 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Save the bucket and object extracted from path.
 | 
					
						
							|  |  |  | 	switch len(pathComponents) { | 
					
						
							|  |  |  | 	case 1: | 
					
						
							|  |  |  | 		bucket = pathComponents[0] | 
					
						
							|  |  |  | 	case 2: | 
					
						
							|  |  |  | 		bucket = pathComponents[0] | 
					
						
							|  |  |  | 		object = pathComponents[1] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return bucket, object | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 07:53:35 +08:00
										 |  |  | // userMetadataKeyPrefixes contains the prefixes of used-defined metadata keys.
 | 
					
						
							|  |  |  | // All values stored with a key starting with one of the following prefixes
 | 
					
						
							|  |  |  | // must be extracted from the header.
 | 
					
						
							|  |  |  | var userMetadataKeyPrefixes = []string{ | 
					
						
							|  |  |  | 	"X-Amz-Meta-", | 
					
						
							|  |  |  | 	"X-Minio-Meta-", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | // extractMetadataFromHeader extracts metadata from HTTP header.
 | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  | func extractMetadataFromHeader(header http.Header) (map[string]string, error) { | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 	if header == nil { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 		return nil, errors.Trace(errInvalidArgument) | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 	metadata := make(map[string]string) | 
					
						
							| 
									
										
										
										
											2017-12-22 19:28:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Save all supported headers.
 | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 	for _, supportedHeader := range supportedHeaders { | 
					
						
							|  |  |  | 		canonicalHeader := http.CanonicalHeaderKey(supportedHeader) | 
					
						
							|  |  |  | 		// HTTP headers are case insensitive, look for both canonical
 | 
					
						
							|  |  |  | 		// and non canonical entries.
 | 
					
						
							|  |  |  | 		if _, ok := header[canonicalHeader]; ok { | 
					
						
							|  |  |  | 			metadata[supportedHeader] = header.Get(canonicalHeader) | 
					
						
							|  |  |  | 		} else if _, ok := header[supportedHeader]; ok { | 
					
						
							|  |  |  | 			metadata[supportedHeader] = header.Get(supportedHeader) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-12-22 19:28:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 	// Go through all other headers for any additional headers that needs to be saved.
 | 
					
						
							|  |  |  | 	for key := range header { | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  | 		if key != http.CanonicalHeaderKey(key) { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 			return nil, errors.Trace(errInvalidArgument) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-23 07:53:35 +08:00
										 |  |  | 		for _, prefix := range userMetadataKeyPrefixes { | 
					
						
							|  |  |  | 			if strings.HasPrefix(key, prefix) { | 
					
						
							|  |  |  | 				metadata[key] = header.Get(key) | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  | 	return metadata, nil | 
					
						
							| 
									
										
										
										
											2016-12-20 08:14:04 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | // The Query string for the redirect URL the client is
 | 
					
						
							|  |  |  | // redirected on successful upload.
 | 
					
						
							|  |  |  | func getRedirectPostRawQuery(objInfo ObjectInfo) string { | 
					
						
							|  |  |  | 	redirectValues := make(url.Values) | 
					
						
							|  |  |  | 	redirectValues.Set("bucket", objInfo.Bucket) | 
					
						
							|  |  |  | 	redirectValues.Set("key", objInfo.Name) | 
					
						
							| 
									
										
										
										
											2017-05-15 03:05:51 +08:00
										 |  |  | 	redirectValues.Set("etag", "\""+objInfo.ETag+"\"") | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 	return redirectValues.Encode() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Extract request params to be sent with event notifiation.
 | 
					
						
							|  |  |  | func extractReqParams(r *http.Request) map[string]string { | 
					
						
							|  |  |  | 	if r == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2016-12-20 08:14:04 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Success.
 | 
					
						
							|  |  |  | 	return map[string]string{ | 
					
						
							|  |  |  | 		"sourceIPAddress": r.RemoteAddr, | 
					
						
							|  |  |  | 		// Add more fields here.
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-28 08:02:04 +08:00
										 |  |  | // Trims away `aws-chunked` from the content-encoding header if present.
 | 
					
						
							|  |  |  | // Streaming signature clients can have custom content-encoding such as
 | 
					
						
							|  |  |  | // `aws-chunked,gzip` here we need to only save `gzip`.
 | 
					
						
							|  |  |  | // For more refer http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
 | 
					
						
							|  |  |  | func trimAwsChunkedContentEncoding(contentEnc string) (trimmedContentEnc string) { | 
					
						
							|  |  |  | 	if contentEnc == "" { | 
					
						
							|  |  |  | 		return contentEnc | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var newEncs []string | 
					
						
							|  |  |  | 	for _, enc := range strings.Split(contentEnc, ",") { | 
					
						
							|  |  |  | 		if enc != streamingContentEncoding { | 
					
						
							|  |  |  | 			newEncs = append(newEncs, enc) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return strings.Join(newEncs, ",") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | // Validate form field size for s3 specification requirement.
 | 
					
						
							|  |  |  | func validateFormFieldSize(formValues http.Header) error { | 
					
						
							|  |  |  | 	// Iterate over form values
 | 
					
						
							|  |  |  | 	for k := range formValues { | 
					
						
							|  |  |  | 		// Check if value's field exceeds S3 limit
 | 
					
						
							|  |  |  | 		if int64(len(formValues.Get(k))) > maxFormFieldSize { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 			return errors.Trace(errSizeUnexpected) | 
					
						
							| 
									
										
										
										
											2016-12-20 08:14:04 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Success.
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-29 03:02:22 +08:00
										 |  |  | // Extract form fields and file data from a HTTP POST Policy
 | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues http.Header, err error) { | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 	/// HTML Form values
 | 
					
						
							| 
									
										
										
										
											2016-07-29 03:02:22 +08:00
										 |  |  | 	fileName = "" | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 	// Canonicalize the form values into http.Header.
 | 
					
						
							|  |  |  | 	formValues = make(http.Header) | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 	for k, v := range form.Value { | 
					
						
							| 
									
										
										
										
											2017-03-14 05:41:13 +08:00
										 |  |  | 		formValues[http.CanonicalHeaderKey(k)] = v | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Validate form values.
 | 
					
						
							|  |  |  | 	if err = validateFormFieldSize(formValues); err != nil { | 
					
						
							|  |  |  | 		return nil, "", 0, nil, err | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Iterator until we find a valid File field and break
 | 
					
						
							|  |  |  | 	for k, v := range form.File { | 
					
						
							|  |  |  | 		canonicalFormName := http.CanonicalHeaderKey(k) | 
					
						
							|  |  |  | 		if canonicalFormName == "File" { | 
					
						
							|  |  |  | 			if len(v) == 0 { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 				return nil, "", 0, nil, errors.Trace(errInvalidArgument) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 			// Fetch fileHeader which has the uploaded file information
 | 
					
						
							|  |  |  | 			fileHeader := v[0] | 
					
						
							|  |  |  | 			// Set filename
 | 
					
						
							|  |  |  | 			fileName = fileHeader.Filename | 
					
						
							|  |  |  | 			// Open the uploaded part
 | 
					
						
							|  |  |  | 			filePart, err = fileHeader.Open() | 
					
						
							| 
									
										
										
										
											2017-02-10 04:37:32 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 				return nil, "", 0, nil, errors.Trace(err) | 
					
						
							| 
									
										
										
										
											2017-02-10 04:37:32 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 			// Compute file size
 | 
					
						
							|  |  |  | 			fileSize, err = filePart.(io.Seeker).Seek(0, 2) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 				return nil, "", 0, nil, errors.Trace(err) | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			// Reset Seek to the beginning
 | 
					
						
							|  |  |  | 			_, err = filePart.(io.Seeker).Seek(0, 0) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2017-11-26 03:58:29 +08:00
										 |  |  | 				return nil, "", 0, nil, errors.Trace(err) | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			// File found and ready for reading
 | 
					
						
							|  |  |  | 			break | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-03 02:45:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return filePart, fileName, fileSize, formValues, nil | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-10-25 10:04:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Log headers and body.
 | 
					
						
							|  |  |  | func httpTraceAll(f http.HandlerFunc) http.HandlerFunc { | 
					
						
							|  |  |  | 	if !globalHTTPTrace { | 
					
						
							|  |  |  | 		return f | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return httptracer.TraceReqHandlerFunc(f, os.Stdout, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Log only the headers.
 | 
					
						
							|  |  |  | func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc { | 
					
						
							|  |  |  | 	if !globalHTTPTrace { | 
					
						
							|  |  |  | 		return f | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return httptracer.TraceReqHandlerFunc(f, os.Stdout, false) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-11-15 08:56:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
 | 
					
						
							|  |  |  | func getResource(path string, host string, domain string) (string, error) { | 
					
						
							|  |  |  | 	if domain == "" { | 
					
						
							|  |  |  | 		return path, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// If virtual-host-style is enabled construct the "resource" properly.
 | 
					
						
							|  |  |  | 	if strings.Contains(host, ":") { | 
					
						
							|  |  |  | 		// In bucket.mydomain.com:9000, strip out :9000
 | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		if host, _, err = net.SplitHostPort(host); err != nil { | 
					
						
							|  |  |  | 			errorIf(err, "Unable to split %s", host) | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if !strings.HasSuffix(host, "."+domain) { | 
					
						
							|  |  |  | 		return path, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bucket := strings.TrimSuffix(host, "."+domain) | 
					
						
							|  |  |  | 	return slashSeparator + pathJoin(bucket, path), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // If none of the http routes match respond with MethodNotAllowed
 | 
					
						
							|  |  |  | func notFoundHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	writeErrorResponse(w, ErrMethodNotAllowed, r.URL) | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } |