aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/Azure/azure-storage-go/blob.go
blob: 636efc662d693da28f9242de80d5b5ec7a54e488 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                      






































































                                                                                    
                                                         











                                                                    





























                                                              
                                           


























                                                                                

                                                    
                                            




































                                                                           
                                                          





















                                                                                 


                                                                             

                                                                                                
                                                                              
                        
                                                 







                                                                                               



                                                                                  



                                                                      
                                                                                                    


































                                                                                                                                       

                                                                                                
                                                              








                                                                      
                                                                             





                               
                                                                                   



                                                                                                                                              
                                                                             


                               
                                         







                                                                                     






































                                                                                                                                                               

                                                                                                              

                                                                                             


                                                                                                                                                            








                                                                                 

                                                                 



                                                          




                                                                                          
                                                                                 




                                           












































                                                                                                                                                     
                                               






                                                                                     
                                                                            










                                                                                                                 
                                               












                                                                                                             
                                               











                                                                                               


                                                                                                
                                                                              


                               
                                         
 
                                                                                   






































                                                                                        
                                                                            





















                                                                                                     
                                                                             


                          
                                         














                                                                                                                                      

                                                              








                                                              
                                                                             


                          
                                         














                                                                                               
                                                                             


                               
                                         





































                                                                                                           
                                                                      






                                                                                                                                                 
                                                              







                                                           
                                                                              


                          
                                         





                                                                        
                                                                   










                                                                                                              
                                                                     




                                                                                                                                                   
                                                              







                                                           
                                                                              



                          
                                         












                                                                                                                     
                                                                                                         


                          
                                         











                                                                                                                     
                                                                             
















                                                                                                                  
                                                              







                                                                     
                                                                             


                          
                                         











                                                                                                                                                                   
                                                              

















                                                                              
                                                                              


                          
                                         












                                                                                                 
                                                                             




                               
                                                                                   












                                                                                                        
                                                              






                                                          
                                                                             


                          
                                         









                                                                                                                    
                                                              







                                                                 
                                                                                                


                          
                                         










                                                                                
                                                                   



                          
                                                         

 





                                                                                              




                                                                                                
                                                                             


                              
                                         











                                                                                                              
































                                                                                                             
































                                                                                                                                       
                                         








                                                                                                                     




                                                                                                     
         



                                                                                                                         
                                                                                                
                                                              




                                                
                                                                          







                                                                             











                                                                                                                      

                                                                   
                                                                                                                                                                                     



                                                                 
                                                                                          










                                                                                                                                                  





                                                                             
 










                                                                                                                                                       












                                                    






                                                           







                                            









                                                                                                                                                           





                                                                              




                                                                                                                                                                                                                                                      



                                                                                                                                                                                                                         
 

                                                                                                  
package storage

import (
    "bytes"
    "encoding/xml"
    "errors"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strconv"
    "strings"
    "time"
)

// A Blob is an entry in BlobListResponse.
type Blob struct {
    Name       string         `xml:"Name"`
    Properties BlobProperties `xml:"Properties"`
    Metadata   BlobMetadata   `xml:"Metadata"`
}

// BlobMetadata is a set of custom name/value pairs.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179404.aspx
type BlobMetadata map[string]string

type blobMetadataEntries struct {
    Entries []blobMetadataEntry `xml:",any"`
}
type blobMetadataEntry struct {
    XMLName xml.Name
    Value   string `xml:",chardata"`
}

// UnmarshalXML converts the xml:Metadata into Metadata map
func (bm *BlobMetadata) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var entries blobMetadataEntries
    if err := d.DecodeElement(&entries, &start); err != nil {
        return err
    }
    for _, entry := range entries.Entries {
        if *bm == nil {
            *bm = make(BlobMetadata)
        }
        (*bm)[strings.ToLower(entry.XMLName.Local)] = entry.Value
    }
    return nil
}

// MarshalXML implements the xml.Marshaler interface. It encodes
// metadata name/value pairs as they would appear in an Azure
// ListBlobs response.
func (bm BlobMetadata) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
    entries := make([]blobMetadataEntry, 0, len(bm))
    for k, v := range bm {
        entries = append(entries, blobMetadataEntry{
            XMLName: xml.Name{Local: http.CanonicalHeaderKey(k)},
            Value:   v,
        })
    }
    return enc.EncodeElement(blobMetadataEntries{
        Entries: entries,
    }, start)
}

// BlobProperties contains various properties of a blob
// returned in various endpoints like ListBlobs or GetBlobProperties.
type BlobProperties struct {
    LastModified          string   `xml:"Last-Modified"`
    Etag                  string   `xml:"Etag"`
    ContentMD5            string   `xml:"Content-MD5"`
    ContentLength         int64    `xml:"Content-Length"`
    ContentType           string   `xml:"Content-Type"`
    ContentEncoding       string   `xml:"Content-Encoding"`
    CacheControl          string   `xml:"Cache-Control"`
    ContentLanguage       string   `xml:"Cache-Language"`
    BlobType              BlobType `xml:"x-ms-blob-blob-type"`
    SequenceNumber        int64    `xml:"x-ms-blob-sequence-number"`
    CopyID                string   `xml:"CopyId"`
    CopyStatus            string   `xml:"CopyStatus"`
    CopySource            string   `xml:"CopySource"`
    CopyProgress          string   `xml:"CopyProgress"`
    CopyCompletionTime    string   `xml:"CopyCompletionTime"`
    CopyStatusDescription string   `xml:"CopyStatusDescription"`
    LeaseStatus           string   `xml:"LeaseStatus"`
    LeaseState            string   `xml:"LeaseState"`
}

// BlobHeaders contains various properties of a blob and is an entry
// in SetBlobProperties
type BlobHeaders struct {
    ContentMD5      string `header:"x-ms-blob-content-md5"`
    ContentLanguage string `header:"x-ms-blob-content-language"`
    ContentEncoding string `header:"x-ms-blob-content-encoding"`
    ContentType     string `header:"x-ms-blob-content-type"`
    CacheControl    string `header:"x-ms-blob-cache-control"`
}

// BlobType defines the type of the Azure Blob.
type BlobType string

// Types of page blobs
const (
    BlobTypeBlock  BlobType = "BlockBlob"
    BlobTypePage   BlobType = "PageBlob"
    BlobTypeAppend BlobType = "AppendBlob"
)

// PageWriteType defines the type updates that are going to be
// done on the page blob.
type PageWriteType string

// Types of operations on page blobs
const (
    PageWriteTypeUpdate PageWriteType = "update"
    PageWriteTypeClear  PageWriteType = "clear"
)

const (
    blobCopyStatusPending = "pending"
    blobCopyStatusSuccess = "success"
    blobCopyStatusAborted = "aborted"
    blobCopyStatusFailed  = "failed"
)

// lease constants.
const (
    leaseHeaderPrefix = "x-ms-lease-"
    headerLeaseID     = "x-ms-lease-id"
    leaseAction       = "x-ms-lease-action"
    leaseBreakPeriod  = "x-ms-lease-break-period"
    leaseDuration     = "x-ms-lease-duration"
    leaseProposedID   = "x-ms-proposed-lease-id"
    leaseTime         = "x-ms-lease-time"

    acquireLease = "acquire"
    renewLease   = "renew"
    changeLease  = "change"
    releaseLease = "release"
    breakLease   = "break"
)

// BlockListType is used to filter out types of blocks in a Get Blocks List call
// for a block blob.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx for all
// block types.
type BlockListType string

// Filters for listing blocks in block blobs
const (
    BlockListTypeAll         BlockListType = "all"
    BlockListTypeCommitted   BlockListType = "committed"
    BlockListTypeUncommitted BlockListType = "uncommitted"
)

// Maximum sizes (per REST API) for various concepts
const (
    MaxBlobBlockSize = 100 * 1024 * 1024
    MaxBlobPageSize  = 4 * 1024 * 1024
)

// BlockStatus defines states a block for a block blob can
// be in.
type BlockStatus string

// List of statuses that can be used to refer to a block in a block list
const (
    BlockStatusUncommitted BlockStatus = "Uncommitted"
    BlockStatusCommitted   BlockStatus = "Committed"
    BlockStatusLatest      BlockStatus = "Latest"
)

// Block is used to create Block entities for Put Block List
// call.
type Block struct {
    ID     string
    Status BlockStatus
}

// BlockListResponse contains the response fields from Get Block List call.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
type BlockListResponse struct {
    XMLName           xml.Name        `xml:"BlockList"`
    CommittedBlocks   []BlockResponse `xml:"CommittedBlocks>Block"`
    UncommittedBlocks []BlockResponse `xml:"UncommittedBlocks>Block"`
}

// BlockResponse contains the block information returned
// in the GetBlockListCall.
type BlockResponse struct {
    Name string `xml:"Name"`
    Size int64  `xml:"Size"`
}

// GetPageRangesResponse contains the response fields from
// Get Page Ranges call.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
type GetPageRangesResponse struct {
    XMLName  xml.Name    `xml:"PageList"`
    PageList []PageRange `xml:"PageRange"`
}

// PageRange contains information about a page of a page blob from
// Get Pages Range call.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
type PageRange struct {
    Start int64 `xml:"Start"`
    End   int64 `xml:"End"`
}

var (
    errBlobCopyAborted    = errors.New("storage: blob copy is aborted")
    errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch")
)

// BlobExists returns true if a blob with given name exists on the specified
// container of the storage account.
func (b BlobStorageClient) BlobExists(container, name string) (bool, error) {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
    headers := b.client.getStandardHeaders()
    resp, err := b.client.exec(http.MethodHead, uri, headers, nil, b.auth)
    if resp != nil {
        defer readAndCloseBody(resp.body)
        if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound {
            return resp.statusCode == http.StatusOK, nil
        }
    }
    return false, err
}

// GetBlobURL gets the canonical URL to the blob with the specified name in the
// specified container. If name is not specified, the canonical URL for the entire
// container is obtained.
// This method does not create a publicly accessible URL if the blob or container
// is private and this method does not check if the blob exists.
func (b BlobStorageClient) GetBlobURL(container, name string) string {
    if container == "" {
        container = "$root"
    }
    return b.client.getEndpoint(blobServiceName, pathForResource(container, name), url.Values{})
}

// GetBlob returns a stream to read the blob. Caller must call Close() the
// reader to close on the underlying connection.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
func (b BlobStorageClient) GetBlob(container, name string) (io.ReadCloser, error) {
    resp, err := b.getBlobRange(container, name, "", nil)
    if err != nil {
        return nil, err
    }

    if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        return nil, err
    }
    return resp.body, nil
}

// GetBlobRange reads the specified range of a blob to a stream. The bytesRange
// string must be in a format like "0-", "10-100" as defined in HTTP 1.1 spec.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
func (b BlobStorageClient) GetBlobRange(container, name, bytesRange string, extraHeaders map[string]string) (io.ReadCloser, error) {
    resp, err := b.getBlobRange(container, name, bytesRange, extraHeaders)
    if err != nil {
        return nil, err
    }

    if err := checkRespCode(resp.statusCode, []int{http.StatusPartialContent}); err != nil {
        return nil, err
    }
    return resp.body, nil
}

func (b BlobStorageClient) getBlobRange(container, name, bytesRange string, extraHeaders map[string]string) (*storageResponse, error) {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})

    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    if bytesRange != "" {
        headers["Range"] = fmt.Sprintf("bytes=%s", bytesRange)
    }

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth)
    if err != nil {
        return nil, err
    }
    return resp, err
}

// leasePut is common PUT code for the various acquire/release/break etc functions.
func (b BlobStorageClient) leaseCommonPut(container string, name string, headers map[string]string, expectedStatus int) (http.Header, error) {
    params := url.Values{"comp": {"lease"}}
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)

    if err := checkRespCode(resp.statusCode, []int{expectedStatus}); err != nil {
        return nil, err
    }

    return resp.headers, nil
}

// SnapshotBlob creates a snapshot for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691971.aspx
func (b BlobStorageClient) SnapshotBlob(container string, name string, timeout int, extraHeaders map[string]string) (snapshotTimestamp *time.Time, err error) {
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    params := url.Values{"comp": {"snapshot"}}

    if timeout > 0 {
        params.Add("timeout", strconv.Itoa(timeout))
    }

    for k, v := range extraHeaders {
        headers[k] = v
    }

    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil || resp == nil {
        return nil, err
    }

    defer readAndCloseBody(resp.body)

    if err := checkRespCode(resp.statusCode, []int{http.StatusCreated}); err != nil {
        return nil, err
    }

    snapshotResponse := resp.headers.Get(http.CanonicalHeaderKey("x-ms-snapshot"))
    if snapshotResponse != "" {
        snapshotTimestamp, err := time.Parse(time.RFC3339, snapshotResponse)
        if err != nil {
            return nil, err
        }

        return &snapshotTimestamp, nil
    }

    return nil, errors.New("Snapshot not created")
}

// AcquireLease creates a lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
// returns leaseID acquired
// In API Versions starting on 2012-02-12, the minimum leaseTimeInSeconds is 15, the maximum
// non-infinite leaseTimeInSeconds is 60. To specify an infinite lease, provide the value -1.
func (b BlobStorageClient) AcquireLease(container string, name string, leaseTimeInSeconds int, proposedLeaseID string) (returnedLeaseID string, err error) {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = acquireLease

    if leaseTimeInSeconds == -1 {
        // Do nothing, but don't trigger the following clauses.
    } else if leaseTimeInSeconds > 60 || b.client.apiVersion < "2012-02-12" {
        leaseTimeInSeconds = 60
    } else if leaseTimeInSeconds < 15 {
        leaseTimeInSeconds = 15
    }

    headers[leaseDuration] = strconv.Itoa(leaseTimeInSeconds)

    if proposedLeaseID != "" {
        headers[leaseProposedID] = proposedLeaseID
    }

    respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusCreated)
    if err != nil {
        return "", err
    }

    returnedLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID))

    if returnedLeaseID != "" {
        return returnedLeaseID, nil
    }

    return "", errors.New("LeaseID not returned")
}

// BreakLease breaks the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
// Returns the timeout remaining in the lease in seconds
func (b BlobStorageClient) BreakLease(container string, name string) (breakTimeout int, err error) {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = breakLease
    return b.breakLeaseCommon(container, name, headers)
}

// BreakLeaseWithBreakPeriod breaks the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
// breakPeriodInSeconds is used to determine how long until new lease can be created.
// Returns the timeout remaining in the lease in seconds
func (b BlobStorageClient) BreakLeaseWithBreakPeriod(container string, name string, breakPeriodInSeconds int) (breakTimeout int, err error) {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = breakLease
    headers[leaseBreakPeriod] = strconv.Itoa(breakPeriodInSeconds)
    return b.breakLeaseCommon(container, name, headers)
}

// breakLeaseCommon is common code for both version of BreakLease (with and without break period)
func (b BlobStorageClient) breakLeaseCommon(container string, name string, headers map[string]string) (breakTimeout int, err error) {

    respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusAccepted)
    if err != nil {
        return 0, err
    }

    breakTimeoutStr := respHeaders.Get(http.CanonicalHeaderKey(leaseTime))
    if breakTimeoutStr != "" {
        breakTimeout, err = strconv.Atoi(breakTimeoutStr)
        if err != nil {
            return 0, err
        }
    }

    return breakTimeout, nil
}

// ChangeLease changes a lease ID for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
// Returns the new LeaseID acquired
func (b BlobStorageClient) ChangeLease(container string, name string, currentLeaseID string, proposedLeaseID string) (newLeaseID string, err error) {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = changeLease
    headers[headerLeaseID] = currentLeaseID
    headers[leaseProposedID] = proposedLeaseID

    respHeaders, err := b.leaseCommonPut(container, name, headers, http.StatusOK)
    if err != nil {
        return "", err
    }

    newLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID))
    if newLeaseID != "" {
        return newLeaseID, nil
    }

    return "", errors.New("LeaseID not returned")
}

// ReleaseLease releases the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
func (b BlobStorageClient) ReleaseLease(container string, name string, currentLeaseID string) error {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = releaseLease
    headers[headerLeaseID] = currentLeaseID

    _, err := b.leaseCommonPut(container, name, headers, http.StatusOK)
    if err != nil {
        return err
    }

    return nil
}

// RenewLease renews the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
func (b BlobStorageClient) RenewLease(container string, name string, currentLeaseID string) error {
    headers := b.client.getStandardHeaders()
    headers[leaseAction] = renewLease
    headers[headerLeaseID] = currentLeaseID

    _, err := b.leaseCommonPut(container, name, headers, http.StatusOK)
    if err != nil {
        return err
    }

    return nil
}

// GetBlobProperties provides various information about the specified
// blob. See https://msdn.microsoft.com/en-us/library/azure/dd179394.aspx
func (b BlobStorageClient) GetBlobProperties(container, name string) (*BlobProperties, error) {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})

    headers := b.client.getStandardHeaders()
    resp, err := b.client.exec(http.MethodHead, uri, headers, nil, b.auth)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)

    if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        return nil, err
    }

    var contentLength int64
    contentLengthStr := resp.headers.Get("Content-Length")
    if contentLengthStr != "" {
        contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
        if err != nil {
            return nil, err
        }
    }

    var sequenceNum int64
    sequenceNumStr := resp.headers.Get("x-ms-blob-sequence-number")
    if sequenceNumStr != "" {
        sequenceNum, err = strconv.ParseInt(sequenceNumStr, 0, 64)
        if err != nil {
            return nil, err
        }
    }

    return &BlobProperties{
        LastModified:          resp.headers.Get("Last-Modified"),
        Etag:                  resp.headers.Get("Etag"),
        ContentMD5:            resp.headers.Get("Content-MD5"),
        ContentLength:         contentLength,
        ContentEncoding:       resp.headers.Get("Content-Encoding"),
        ContentType:           resp.headers.Get("Content-Type"),
        CacheControl:          resp.headers.Get("Cache-Control"),
        ContentLanguage:       resp.headers.Get("Content-Language"),
        SequenceNumber:        sequenceNum,
        CopyCompletionTime:    resp.headers.Get("x-ms-copy-completion-time"),
        CopyStatusDescription: resp.headers.Get("x-ms-copy-status-description"),
        CopyID:                resp.headers.Get("x-ms-copy-id"),
        CopyProgress:          resp.headers.Get("x-ms-copy-progress"),
        CopySource:            resp.headers.Get("x-ms-copy-source"),
        CopyStatus:            resp.headers.Get("x-ms-copy-status"),
        BlobType:              BlobType(resp.headers.Get("x-ms-blob-type")),
        LeaseStatus:           resp.headers.Get("x-ms-lease-status"),
        LeaseState:            resp.headers.Get("x-ms-lease-state"),
    }, nil
}

// SetBlobProperties replaces the BlobHeaders for the specified blob.
//
// Some keys may be converted to Camel-Case before sending. All keys
// are returned in lower case by GetBlobProperties. HTTP header names
// are case-insensitive so case munging should not matter to other
// applications either.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee691966.aspx
func (b BlobStorageClient) SetBlobProperties(container, name string, blobHeaders BlobHeaders) error {
    params := url.Values{"comp": {"properties"}}
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    headers := b.client.getStandardHeaders()

    extraHeaders := headersFromStruct(blobHeaders)

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusOK})
}

// SetBlobMetadata replaces the metadata for the specified blob.
//
// Some keys may be converted to Camel-Case before sending. All keys
// are returned in lower case by GetBlobMetadata. HTTP header names
// are case-insensitive so case munging should not matter to other
// applications either.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
func (b BlobStorageClient) SetBlobMetadata(container, name string, metadata map[string]string, extraHeaders map[string]string) error {
    params := url.Values{"comp": {"metadata"}}
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    metadata = b.client.protectUserAgent(metadata)
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    for k, v := range metadata {
        headers[userDefinedMetadataHeaderPrefix+k] = v
    }

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusOK})
}

// GetBlobMetadata returns all user-defined metadata for the specified blob.
//
// All metadata keys will be returned in lower case. (HTTP header
// names are case-insensitive.)
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
func (b BlobStorageClient) GetBlobMetadata(container, name string) (map[string]string, error) {
    params := url.Values{"comp": {"metadata"}}
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    headers := b.client.getStandardHeaders()

    resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)

    if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        return nil, err
    }

    metadata := make(map[string]string)
    for k, v := range resp.headers {
        // Can't trust CanonicalHeaderKey() to munge case
        // reliably. "_" is allowed in identifiers:
        // https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
        // https://msdn.microsoft.com/library/aa664670(VS.71).aspx
        // http://tools.ietf.org/html/rfc7230#section-3.2
        // ...but "_" is considered invalid by
        // CanonicalMIMEHeaderKey in
        // https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
        // so k can be "X-Ms-Meta-Foo" or "x-ms-meta-foo_bar".
        k = strings.ToLower(k)
        if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
            continue
        }
        // metadata["foo"] = content of the last X-Ms-Meta-Foo header
        k = k[len(userDefinedMetadataHeaderPrefix):]
        metadata[k] = v[len(v)-1]
    }
    return metadata, nil
}

// CreateBlockBlob initializes an empty block blob with no blocks.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) CreateBlockBlob(container, name string) error {
    return b.CreateBlockBlobFromReader(container, name, 0, nil, nil)
}

// CreateBlockBlobFromReader initializes a block blob using data from
// reader. Size must be the number of bytes read from reader. To
// create an empty blob, use size==0 and reader==nil.
//
// The API rejects requests with size > 256 MiB (but this limit is not
// checked by the SDK). To write a larger blob, use CreateBlockBlob,
// PutBlock, and PutBlockList.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) CreateBlockBlobFromReader(container, name string, size uint64, blob io.Reader, extraHeaders map[string]string) error {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypeBlock)
    headers["Content-Length"] = fmt.Sprintf("%d", size)

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, blob, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)
    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// PutBlock saves the given data chunk to the specified block blob with
// given ID.
//
// The API rejects chunks larger than 100 MB (but this limit is not
// checked by the SDK).
//
// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
func (b BlobStorageClient) PutBlock(container, name, blockID string, chunk []byte) error {
    return b.PutBlockWithLength(container, name, blockID, uint64(len(chunk)), bytes.NewReader(chunk), nil)
}

// PutBlockWithLength saves the given data stream of exactly specified size to
// the block blob with given ID. It is an alternative to PutBlocks where data
// comes as stream but the length is known in advance.
//
// The API rejects requests with size > 100 MB (but this limit is not
// checked by the SDK).
//
// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
func (b BlobStorageClient) PutBlockWithLength(container, name, blockID string, size uint64, blob io.Reader, extraHeaders map[string]string) error {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"block"}, "blockid": {blockID}})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypeBlock)
    headers["Content-Length"] = fmt.Sprintf("%v", size)

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, blob, b.auth)
    if err != nil {
        return err
    }

    defer readAndCloseBody(resp.body)
    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// PutBlockList saves list of blocks to the specified block blob.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179467.aspx
func (b BlobStorageClient) PutBlockList(container, name string, blocks []Block) error {
    blockListXML := prepareBlockListRequest(blocks)

    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"blocklist"}})
    headers := b.client.getStandardHeaders()
    headers["Content-Length"] = fmt.Sprintf("%v", len(blockListXML))

    resp, err := b.client.exec(http.MethodPut, uri, headers, strings.NewReader(blockListXML), b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)
    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// GetBlockList retrieves list of blocks in the specified block blob.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
func (b BlobStorageClient) GetBlockList(container, name string, blockType BlockListType) (BlockListResponse, error) {
    params := url.Values{"comp": {"blocklist"}, "blocklisttype": {string(blockType)}}
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    headers := b.client.getStandardHeaders()

    var out BlockListResponse
    resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth)
    if err != nil {
        return out, err
    }
    defer resp.body.Close()

    err = xmlUnmarshal(resp.body, &out)
    return out, err
}

// PutPageBlob initializes an empty page blob with specified name and maximum
// size in bytes (size must be aligned to a 512-byte boundary). A page blob must
// be created using this method before writing pages.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) PutPageBlob(container, name string, size int64, extraHeaders map[string]string) error {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypePage)
    headers["x-ms-blob-content-length"] = fmt.Sprintf("%v", size)

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// PutPage writes a range of pages to a page blob or clears the given range.
// In case of 'clear' writes, given chunk is discarded. Ranges must be aligned
// with 512-byte boundaries and chunk must be of size multiplies by 512.
//
// See https://msdn.microsoft.com/en-us/library/ee691975.aspx
func (b BlobStorageClient) PutPage(container, name string, startByte, endByte int64, writeType PageWriteType, chunk []byte, extraHeaders map[string]string) error {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"page"}})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypePage)
    headers["x-ms-page-write"] = string(writeType)
    headers["x-ms-range"] = fmt.Sprintf("bytes=%v-%v", startByte, endByte)
    for k, v := range extraHeaders {
        headers[k] = v
    }
    var contentLength int64
    var data io.Reader
    if writeType == PageWriteTypeClear {
        contentLength = 0
        data = bytes.NewReader([]byte{})
    } else {
        contentLength = int64(len(chunk))
        data = bytes.NewReader(chunk)
    }
    headers["Content-Length"] = fmt.Sprintf("%v", contentLength)

    resp, err := b.client.exec(http.MethodPut, uri, headers, data, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// GetPageRanges returns the list of valid page ranges for a page blob.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
func (b BlobStorageClient) GetPageRanges(container, name string) (GetPageRangesResponse, error) {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"pagelist"}})
    headers := b.client.getStandardHeaders()

    var out GetPageRangesResponse
    resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth)
    if err != nil {
        return out, err
    }
    defer resp.body.Close()

    if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        return out, err
    }
    err = xmlUnmarshal(resp.body, &out)
    return out, err
}

// PutAppendBlob initializes an empty append blob with specified name. An
// append blob must be created using this method before appending blocks.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) PutAppendBlob(container, name string, extraHeaders map[string]string) error {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypeAppend)

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// AppendBlock appends a block to an append blob.
//
// See https://msdn.microsoft.com/en-us/library/azure/mt427365.aspx
func (b BlobStorageClient) AppendBlock(container, name string, chunk []byte, extraHeaders map[string]string) error {
    path := fmt.Sprintf("%s/%s", container, name)
    uri := b.client.getEndpoint(blobServiceName, path, url.Values{"comp": {"appendblock"}})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    headers["x-ms-blob-type"] = string(BlobTypeAppend)
    headers["Content-Length"] = fmt.Sprintf("%v", len(chunk))

    for k, v := range extraHeaders {
        headers[k] = v
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, bytes.NewReader(chunk), b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}

// CopyBlob starts a blob copy operation and waits for the operation to
// complete. sourceBlob parameter must be a canonical URL to the blob (can be
// obtained using GetBlobURL method.) There is no SLA on blob copy and therefore
// this helper method works faster on smaller files.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx
func (b BlobStorageClient) CopyBlob(container, name, sourceBlob string) error {
    copyID, err := b.StartBlobCopy(container, name, sourceBlob)
    if err != nil {
        return err
    }

    return b.WaitForBlobCopy(container, name, copyID)
}

// StartBlobCopy starts a blob copy operation.
// sourceBlob parameter must be a canonical URL to the blob (can be
// obtained using GetBlobURL method.)
//
// See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx
func (b BlobStorageClient) StartBlobCopy(container, name, sourceBlob string) (string, error) {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})

    headers := b.client.getStandardHeaders()
    headers["x-ms-copy-source"] = sourceBlob

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return "", err
    }
    defer readAndCloseBody(resp.body)

    if err := checkRespCode(resp.statusCode, []int{http.StatusAccepted, http.StatusCreated}); err != nil {
        return "", err
    }

    copyID := resp.headers.Get("x-ms-copy-id")
    if copyID == "" {
        return "", errors.New("Got empty copy id header")
    }
    return copyID, nil
}

// AbortBlobCopy aborts a BlobCopy which has already been triggered by the StartBlobCopy function.
// copyID is generated from StartBlobCopy function.
// currentLeaseID is required IF the destination blob has an active lease on it.
// As defined in https://msdn.microsoft.com/en-us/library/azure/jj159098.aspx
func (b BlobStorageClient) AbortBlobCopy(container, name, copyID, currentLeaseID string, timeout int) error {
    params := url.Values{"comp": {"copy"}, "copyid": {copyID}}
    if timeout > 0 {
        params.Add("timeout", strconv.Itoa(timeout))
    }

    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
    headers := b.client.getStandardHeaders()
    headers["x-ms-copy-action"] = "abort"

    if currentLeaseID != "" {
        headers[headerLeaseID] = currentLeaseID
    }

    resp, err := b.client.exec(http.MethodPut, uri, headers, nil, b.auth)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)

    if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil {
        return err
    }

    return nil
}

// WaitForBlobCopy loops until a BlobCopy operation is completed (or fails with error)
func (b BlobStorageClient) WaitForBlobCopy(container, name, copyID string) error {
    for {
        props, err := b.GetBlobProperties(container, name)
        if err != nil {
            return err
        }

        if props.CopyID != copyID {
            return errBlobCopyIDMismatch
        }

        switch props.CopyStatus {
        case blobCopyStatusSuccess:
            return nil
        case blobCopyStatusPending:
            continue
        case blobCopyStatusAborted:
            return errBlobCopyAborted
        case blobCopyStatusFailed:
            return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", props.CopyID, props.CopyStatusDescription)
        default:
            return fmt.Errorf("storage: unhandled blob copy status: '%s'", props.CopyStatus)
        }
    }
}

// DeleteBlob deletes the given blob from the specified container.
// If the blob does not exists at the time of the Delete Blob operation, it
// returns error. See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx
func (b BlobStorageClient) DeleteBlob(container, name string, extraHeaders map[string]string) error {
    resp, err := b.deleteBlob(container, name, extraHeaders)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)
    return checkRespCode(resp.statusCode, []int{http.StatusAccepted})
}

// DeleteBlobIfExists deletes the given blob from the specified container If the
// blob is deleted with this call, returns true. Otherwise returns false.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx
func (b BlobStorageClient) DeleteBlobIfExists(container, name string, extraHeaders map[string]string) (bool, error) {
    resp, err := b.deleteBlob(container, name, extraHeaders)
    if resp != nil {
        defer readAndCloseBody(resp.body)
        if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound {
            return resp.statusCode == http.StatusAccepted, nil
        }
    }
    return false, err
}

func (b BlobStorageClient) deleteBlob(container, name string, extraHeaders map[string]string) (*storageResponse, error) {
    uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
    extraHeaders = b.client.protectUserAgent(extraHeaders)
    headers := b.client.getStandardHeaders()
    for k, v := range extraHeaders {
        headers[k] = v
    }

    return b.client.exec(http.MethodDelete, uri, headers, nil, b.auth)
}

// helper method to construct the path to a blob given its container and blob
// name
func pathForBlob(container, name string) string {
    return fmt.Sprintf("/%s/%s", container, name)
}

// helper method to construct the path to either a blob or container
func pathForResource(container, name string) string {
    if len(name) > 0 {
        return fmt.Sprintf("/%s/%s", container, name)
    }
    return fmt.Sprintf("/%s", container)
}

// GetBlobSASURIWithSignedIPAndProtocol creates an URL to the specified blob which contains the Shared
// Access Signature with specified permissions and expiration time. Also includes signedIPRange and allowed protocols.
// If old API version is used but no signedIP is passed (ie empty string) then this should still work.
// We only populate the signedIP when it non-empty.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx
func (b BlobStorageClient) GetBlobSASURIWithSignedIPAndProtocol(container, name string, expiry time.Time, permissions string, signedIPRange string, HTTPSOnly bool) (string, error) {
    var (
        signedPermissions = permissions
        blobURL           = b.GetBlobURL(container, name)
    )
    canonicalizedResource, err := b.client.buildCanonicalizedResource(blobURL, b.auth)
    if err != nil {
        return "", err
    }

    // "The canonicalizedresouce portion of the string is a canonical path to the signed resource.
    // It must include the service name (blob, table, queue or file) for version 2015-02-21 or
    // later, the storage account name, and the resource name, and must be URL-decoded.
    // -- https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx

    // We need to replace + with %2b first to avoid being treated as a space (which is correct for query strings, but not the path component).
    canonicalizedResource = strings.Replace(canonicalizedResource, "+", "%2b", -1)
    canonicalizedResource, err = url.QueryUnescape(canonicalizedResource)
    if err != nil {
        return "", err
    }

    signedExpiry := expiry.UTC().Format(time.RFC3339)

    //If blob name is missing, resource is a container
    signedResource := "c"
    if len(name) > 0 {
        signedResource = "b"
    }

    protocols := "https,http"
    if HTTPSOnly {
        protocols = "https"
    }
    stringToSign, err := blobSASStringToSign(b.client.apiVersion, canonicalizedResource, signedExpiry, signedPermissions, signedIPRange, protocols)
    if err != nil {
        return "", err
    }

    sig := b.client.computeHmac256(stringToSign)
    sasParams := url.Values{
        "sv":  {b.client.apiVersion},
        "se":  {signedExpiry},
        "sr":  {signedResource},
        "sp":  {signedPermissions},
        "sig": {sig},
    }

    if b.client.apiVersion >= "2015-04-05" {
        sasParams.Add("spr", protocols)
        if signedIPRange != "" {
            sasParams.Add("sip", signedIPRange)
        }
    }

    sasURL, err := url.Parse(blobURL)
    if err != nil {
        return "", err
    }
    sasURL.RawQuery = sasParams.Encode()
    return sasURL.String(), nil
}

// GetBlobSASURI creates an URL to the specified blob which contains the Shared
// Access Signature with specified permissions and expiration time.
//
// See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx
func (b BlobStorageClient) GetBlobSASURI(container, name string, expiry time.Time, permissions string) (string, error) {
    url, err := b.GetBlobSASURIWithSignedIPAndProtocol(container, name, expiry, permissions, "", false)
    return url, err
}

func blobSASStringToSign(signedVersion, canonicalizedResource, signedExpiry, signedPermissions string, signedIP string, protocols string) (string, error) {
    var signedStart, signedIdentifier, rscc, rscd, rsce, rscl, rsct string

    if signedVersion >= "2015-02-21" {
        canonicalizedResource = "/blob" + canonicalizedResource
    }

    // https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx#Anchor_12
    if signedVersion >= "2015-04-05" {
        return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
    }

    // reference: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
    if signedVersion >= "2013-08-15" {
        return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
    }

    return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
}