mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			290 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2017 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 cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/xml"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"strconv"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/Azure/azure-sdk-for-go/storage"
 | 
						|
)
 | 
						|
 | 
						|
// Make anonymous HTTP request to azure endpoint.
 | 
						|
func azureAnonRequest(verb, urlStr string, header http.Header) (*http.Response, error) {
 | 
						|
	req, err := http.NewRequest(verb, urlStr, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if header != nil {
 | 
						|
		req.Header = header
 | 
						|
	}
 | 
						|
	resp, err := http.DefaultClient.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// 4XX and 5XX are error HTTP codes.
 | 
						|
	if resp.StatusCode >= 400 && resp.StatusCode <= 511 {
 | 
						|
		defer resp.Body.Close()
 | 
						|
		respBody, err := ioutil.ReadAll(resp.Body)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		if len(respBody) == 0 {
 | 
						|
			// no error in response body, might happen in HEAD requests
 | 
						|
			return nil, storage.AzureStorageServiceError{
 | 
						|
				StatusCode: resp.StatusCode,
 | 
						|
				Code:       resp.Status,
 | 
						|
				Message:    "no response body was available for error status code",
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Response contains Azure storage service error object.
 | 
						|
		var storageErr storage.AzureStorageServiceError
 | 
						|
		if err := xml.Unmarshal(respBody, &storageErr); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		storageErr.StatusCode = resp.StatusCode
 | 
						|
		return nil, storageErr
 | 
						|
	}
 | 
						|
 | 
						|
	return resp, nil
 | 
						|
}
 | 
						|
 | 
						|
// AnonGetBucketInfo - Get bucket metadata from azure anonymously.
 | 
						|
func (a *azureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) {
 | 
						|
	url, err := url.Parse(a.client.GetBlobURL(bucket, ""))
 | 
						|
	if err != nil {
 | 
						|
		return bucketInfo, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	url.RawQuery = "restype=container"
 | 
						|
	resp, err := azureAnonRequest(httpHEAD, url.String(), nil)
 | 
						|
	if err != nil {
 | 
						|
		return bucketInfo, azureToObjectError(traceError(err), bucket)
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusOK {
 | 
						|
		return bucketInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket)), bucket)
 | 
						|
	}
 | 
						|
 | 
						|
	t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
 | 
						|
	if err != nil {
 | 
						|
		return bucketInfo, traceError(err)
 | 
						|
	}
 | 
						|
	bucketInfo = BucketInfo{
 | 
						|
		Name:    bucket,
 | 
						|
		Created: t,
 | 
						|
	}
 | 
						|
	return bucketInfo, nil
 | 
						|
}
 | 
						|
 | 
						|
// AnonPutObject - SendPUT request without authentication.
 | 
						|
// This is needed when clients send PUT requests on objects that can be uploaded without auth.
 | 
						|
func (a *azureObjects) AnonPutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string, sha256sum string) (objInfo ObjectInfo, err error) {
 | 
						|
	// azure doesn't support anonymous put
 | 
						|
	return ObjectInfo{}, traceError(NotImplemented{})
 | 
						|
}
 | 
						|
 | 
						|
// AnonGetObject - SendGET request without authentication.
 | 
						|
// This is needed when clients send GET requests on objects that can be downloaded without auth.
 | 
						|
func (a *azureObjects) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) {
 | 
						|
	h := make(http.Header)
 | 
						|
	if length > 0 && startOffset > 0 {
 | 
						|
		h.Add("Range", fmt.Sprintf("bytes=%d-%d", startOffset, startOffset+length-1))
 | 
						|
	} else if startOffset > 0 {
 | 
						|
		h.Add("Range", fmt.Sprintf("bytes=%d-", startOffset))
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := azureAnonRequest(httpGET, a.client.GetBlobURL(bucket, object), h)
 | 
						|
	if err != nil {
 | 
						|
		return azureToObjectError(traceError(err), bucket, object)
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK {
 | 
						|
		return azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = io.Copy(writer, resp.Body)
 | 
						|
	return traceError(err)
 | 
						|
}
 | 
						|
 | 
						|
// AnonGetObjectInfo - Send HEAD request without authentication and convert the
 | 
						|
// result to ObjectInfo.
 | 
						|
func (a *azureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) {
 | 
						|
	resp, err := azureAnonRequest(httpHEAD, a.client.GetBlobURL(bucket, object), nil)
 | 
						|
	if err != nil {
 | 
						|
		return objInfo, azureToObjectError(traceError(err), bucket, object)
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusOK {
 | 
						|
		return objInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
 | 
						|
	}
 | 
						|
 | 
						|
	var contentLength int64
 | 
						|
	contentLengthStr := resp.Header.Get("Content-Length")
 | 
						|
	if contentLengthStr != "" {
 | 
						|
		contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
 | 
						|
		if err != nil {
 | 
						|
			return objInfo, azureToObjectError(traceError(errUnexpected), bucket, object)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
 | 
						|
	if err != nil {
 | 
						|
		return objInfo, traceError(err)
 | 
						|
	}
 | 
						|
 | 
						|
	objInfo.ModTime = t
 | 
						|
	objInfo.Bucket = bucket
 | 
						|
	objInfo.UserDefined = make(map[string]string)
 | 
						|
	if resp.Header.Get("Content-Encoding") != "" {
 | 
						|
		objInfo.UserDefined["Content-Encoding"] = resp.Header.Get("Content-Encoding")
 | 
						|
	}
 | 
						|
	objInfo.UserDefined["Content-Type"] = resp.Header.Get("Content-Type")
 | 
						|
	objInfo.ETag = resp.Header.Get("Etag")
 | 
						|
	objInfo.ModTime = t
 | 
						|
	objInfo.Name = object
 | 
						|
	objInfo.Size = contentLength
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// AnonListObjects - Use Azure equivalent ListBlobs.
 | 
						|
func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
 | 
						|
	params := storage.ListBlobsParameters{
 | 
						|
		Prefix:     prefix,
 | 
						|
		Marker:     marker,
 | 
						|
		Delimiter:  delimiter,
 | 
						|
		MaxResults: uint(maxKeys),
 | 
						|
	}
 | 
						|
 | 
						|
	q := azureListBlobsGetParameters(params)
 | 
						|
	q.Set("restype", "container")
 | 
						|
	q.Set("comp", "list")
 | 
						|
 | 
						|
	url, err := url.Parse(a.client.GetBlobURL(bucket, ""))
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	url.RawQuery = q.Encode()
 | 
						|
 | 
						|
	resp, err := azureAnonRequest(httpGET, url.String(), nil)
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	var listResp storage.BlobListResponse
 | 
						|
 | 
						|
	data, err := ioutil.ReadAll(resp.Body)
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	err = xml.Unmarshal(data, &listResp)
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
 | 
						|
	result.IsTruncated = listResp.NextMarker != ""
 | 
						|
	result.NextMarker = listResp.NextMarker
 | 
						|
	for _, object := range listResp.Blobs {
 | 
						|
		t, e := time.Parse(time.RFC1123, object.Properties.LastModified)
 | 
						|
		if e != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		result.Objects = append(result.Objects, ObjectInfo{
 | 
						|
			Bucket:          bucket,
 | 
						|
			Name:            object.Name,
 | 
						|
			ModTime:         t,
 | 
						|
			Size:            object.Properties.ContentLength,
 | 
						|
			ETag:            object.Properties.Etag,
 | 
						|
			ContentType:     object.Properties.ContentType,
 | 
						|
			ContentEncoding: object.Properties.ContentEncoding,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	result.Prefixes = listResp.BlobPrefixes
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
// AnonListObjectsV2 - List objects in V2 mode, anonymously
 | 
						|
func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken string, fetchOwner bool, delimiter string, maxKeys int) (result ListObjectsV2Info, err error) {
 | 
						|
	params := storage.ListBlobsParameters{
 | 
						|
		Prefix:     prefix,
 | 
						|
		Marker:     continuationToken,
 | 
						|
		Delimiter:  delimiter,
 | 
						|
		MaxResults: uint(maxKeys),
 | 
						|
	}
 | 
						|
 | 
						|
	q := azureListBlobsGetParameters(params)
 | 
						|
	q.Set("restype", "container")
 | 
						|
	q.Set("comp", "list")
 | 
						|
 | 
						|
	url, err := url.Parse(a.client.GetBlobURL(bucket, ""))
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	url.RawQuery = q.Encode()
 | 
						|
 | 
						|
	resp, err := http.Get(url.String())
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	var listResp storage.BlobListResponse
 | 
						|
 | 
						|
	data, err := ioutil.ReadAll(resp.Body)
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
	err = xml.Unmarshal(data, &listResp)
 | 
						|
	if err != nil {
 | 
						|
		return result, azureToObjectError(traceError(err))
 | 
						|
	}
 | 
						|
 | 
						|
	// If NextMarker is not empty, this means response is truncated and NextContinuationToken should be set
 | 
						|
	if listResp.NextMarker != "" {
 | 
						|
		result.IsTruncated = true
 | 
						|
		result.NextContinuationToken = listResp.NextMarker
 | 
						|
	}
 | 
						|
	for _, object := range listResp.Blobs {
 | 
						|
		t, e := time.Parse(time.RFC1123, object.Properties.LastModified)
 | 
						|
		if e != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		result.Objects = append(result.Objects, ObjectInfo{
 | 
						|
			Bucket:          bucket,
 | 
						|
			Name:            object.Name,
 | 
						|
			ModTime:         t,
 | 
						|
			Size:            object.Properties.ContentLength,
 | 
						|
			ETag:            canonicalizeETag(object.Properties.Etag),
 | 
						|
			ContentType:     object.Properties.ContentType,
 | 
						|
			ContentEncoding: object.Properties.ContentEncoding,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	result.Prefixes = listResp.BlobPrefixes
 | 
						|
	return result, nil
 | 
						|
}
 |