package storage // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. import ( "encoding/xml" "fmt" "net/http" "net/url" "strconv" "strings" ) // BlobStorageClient contains operations for Microsoft Azure Blob Storage // Service. type BlobStorageClient struct { client Client auth authentication } // GetServiceProperties gets the properties of your storage account's blob service. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-blob-service-properties func (b *BlobStorageClient) GetServiceProperties() (*ServiceProperties, error) { return b.client.getServiceProperties(blobServiceName, b.auth) } // SetServiceProperties sets the properties of your storage account's blob service. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-blob-service-properties func (b *BlobStorageClient) SetServiceProperties(props ServiceProperties) error { return b.client.setServiceProperties(props, blobServiceName, b.auth) } // ListContainersParameters defines the set of customizable parameters to make a // List Containers call. // // See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx type ListContainersParameters struct { Prefix string Marker string Include string MaxResults uint Timeout uint } // GetContainerReference returns a Container object for the specified container name. func (b *BlobStorageClient) GetContainerReference(name string) *Container { return &Container{ bsc: b, Name: name, } } // GetContainerReferenceFromSASURI returns a Container object for the specified // container SASURI func GetContainerReferenceFromSASURI(sasuri url.URL) (*Container, error) { path := strings.Split(sasuri.Path, "/") if len(path) <= 1 { return nil, fmt.Errorf("could not find a container in URI: %s", sasuri.String()) } c, err := newSASClientFromURL(&sasuri) if err != nil { return nil, err } cli := c.GetBlobService() return &Container{ bsc: &cli, Name: path[1], sasuri: sasuri, }, nil } // ListContainers returns the list of containers in a storage account along with // pagination token and other response details. // // See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx func (b BlobStorageClient) ListContainers(params ListContainersParameters) (*ContainerListResponse, error) { q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}}) uri := b.client.getEndpoint(blobServiceName, "", q) headers := b.client.getStandardHeaders() type ContainerAlias struct { bsc *BlobStorageClient Name string `xml:"Name"` Properties ContainerProperties `xml:"Properties"` Metadata BlobMetadata sasuri url.URL } type ContainerListResponseAlias struct { XMLName xml.Name `xml:"EnumerationResults"` Xmlns string `xml:"xmlns,attr"` Prefix string `xml:"Prefix"` Marker string `xml:"Marker"` NextMarker string `xml:"NextMarker"` MaxResults int64 `xml:"MaxResults"` Containers []ContainerAlias `xml:"Containers>Container"` } var outAlias ContainerListResponseAlias resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth) if err != nil { return nil, err } defer resp.Body.Close() err = xmlUnmarshal(resp.Body, &outAlias) if err != nil { return nil, err } out := ContainerListResponse{ XMLName: outAlias.XMLName, Xmlns: outAlias.Xmlns, Prefix: outAlias.Prefix, Marker: outAlias.Marker, NextMarker: outAlias.NextMarker, MaxResults: outAlias.MaxResults, Containers: make([]Container, len(outAlias.Containers)), } for i, cnt := range outAlias.Containers { out.Containers[i] = Container{ bsc: &b, Name: cnt.Name, Properties: cnt.Properties, Metadata: map[string]string(cnt.Metadata), sasuri: cnt.sasuri, } } return &out, err } func (p ListContainersParameters) getParameters() url.Values { out := url.Values{} if p.Prefix != "" { out.Set("prefix", p.Prefix) } if p.Marker != "" { out.Set("marker", p.Marker) } if p.Include != "" { out.Set("include", p.Include) } if p.MaxResults != 0 { out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10)) } if p.Timeout != 0 { out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10)) } return out } func writeMetadata(h http.Header) map[string]string { metadata := make(map[string]string) for k, v := range h { // 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-Lol" or "x-ms-meta-lol_rofl". k = strings.ToLower(k) if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) { continue } // metadata["lol"] = content of the last X-Ms-Meta-Lol header k = k[len(userDefinedMetadataHeaderPrefix):] metadata[k] = v[len(v)-1] } return metadata }