...

Source file src/github.com/Azure/azure-sdk-for-go/storage/client.go

Documentation: github.com/Azure/azure-sdk-for-go/storage

     1  // Package storage provides clients for Microsoft Azure Storage Services.
     2  package storage
     3  
     4  // Copyright (c) Microsoft Corporation. All rights reserved.
     5  // Licensed under the MIT License. See License.txt in the project root for license information.
     6  
     7  import (
     8  	"bufio"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"encoding/xml"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"mime"
    17  	"mime/multipart"
    18  	"net/http"
    19  	"net/url"
    20  	"regexp"
    21  	"runtime"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/Azure/azure-sdk-for-go/version"
    27  	"github.com/Azure/go-autorest/autorest"
    28  	"github.com/Azure/go-autorest/autorest/azure"
    29  )
    30  
    31  const (
    32  	// DefaultBaseURL is the domain name used for storage requests in the
    33  	// public cloud when a default client is created.
    34  	DefaultBaseURL = "core.windows.net"
    35  
    36  	// DefaultAPIVersion is the Azure Storage API version string used when a
    37  	// basic client is created.
    38  	DefaultAPIVersion = "2018-03-28"
    39  
    40  	defaultUseHTTPS      = true
    41  	defaultRetryAttempts = 5
    42  	defaultRetryDuration = time.Second * 5
    43  
    44  	// StorageEmulatorAccountName is the fixed storage account used by Azure Storage Emulator
    45  	StorageEmulatorAccountName = "devstoreaccount1"
    46  
    47  	// StorageEmulatorAccountKey is the the fixed storage account used by Azure Storage Emulator
    48  	StorageEmulatorAccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
    49  
    50  	blobServiceName  = "blob"
    51  	tableServiceName = "table"
    52  	queueServiceName = "queue"
    53  	fileServiceName  = "file"
    54  
    55  	storageEmulatorBlob  = "127.0.0.1:10000"
    56  	storageEmulatorTable = "127.0.0.1:10002"
    57  	storageEmulatorQueue = "127.0.0.1:10001"
    58  
    59  	userAgentHeader = "User-Agent"
    60  
    61  	userDefinedMetadataHeaderPrefix = "x-ms-meta-"
    62  
    63  	connectionStringAccountName      = "accountname"
    64  	connectionStringAccountKey       = "accountkey"
    65  	connectionStringEndpointSuffix   = "endpointsuffix"
    66  	connectionStringEndpointProtocol = "defaultendpointsprotocol"
    67  
    68  	connectionStringBlobEndpoint  = "blobendpoint"
    69  	connectionStringFileEndpoint  = "fileendpoint"
    70  	connectionStringQueueEndpoint = "queueendpoint"
    71  	connectionStringTableEndpoint = "tableendpoint"
    72  	connectionStringSAS           = "sharedaccesssignature"
    73  )
    74  
    75  var (
    76  	validStorageAccount     = regexp.MustCompile("^[0-9a-z]{3,24}$")
    77  	validCosmosAccount      = regexp.MustCompile("^[0-9a-z-]{3,44}$")
    78  	defaultValidStatusCodes = []int{
    79  		http.StatusRequestTimeout,      // 408
    80  		http.StatusInternalServerError, // 500
    81  		http.StatusBadGateway,          // 502
    82  		http.StatusServiceUnavailable,  // 503
    83  		http.StatusGatewayTimeout,      // 504
    84  	}
    85  )
    86  
    87  // Sender sends a request
    88  type Sender interface {
    89  	Send(*Client, *http.Request) (*http.Response, error)
    90  }
    91  
    92  // DefaultSender is the default sender for the client. It implements
    93  // an automatic retry strategy.
    94  type DefaultSender struct {
    95  	RetryAttempts    int
    96  	RetryDuration    time.Duration
    97  	ValidStatusCodes []int
    98  	attempts         int // used for testing
    99  }
   100  
   101  // Send is the default retry strategy in the client
   102  func (ds *DefaultSender) Send(c *Client, req *http.Request) (resp *http.Response, err error) {
   103  	rr := autorest.NewRetriableRequest(req)
   104  	for attempts := 0; attempts < ds.RetryAttempts; attempts++ {
   105  		err = rr.Prepare()
   106  		if err != nil {
   107  			return resp, err
   108  		}
   109  		resp, err = c.HTTPClient.Do(rr.Request())
   110  		if err == nil && !autorest.ResponseHasStatusCode(resp, ds.ValidStatusCodes...) {
   111  			return resp, err
   112  		}
   113  		drainRespBody(resp)
   114  		autorest.DelayForBackoff(ds.RetryDuration, attempts, req.Cancel)
   115  		ds.attempts = attempts
   116  	}
   117  	ds.attempts++
   118  	return resp, err
   119  }
   120  
   121  // Client is the object that needs to be constructed to perform
   122  // operations on the storage account.
   123  type Client struct {
   124  	// HTTPClient is the http.Client used to initiate API
   125  	// requests. http.DefaultClient is used when creating a
   126  	// client.
   127  	HTTPClient *http.Client
   128  
   129  	// Sender is an interface that sends the request. Clients are
   130  	// created with a DefaultSender. The DefaultSender has an
   131  	// automatic retry strategy built in. The Sender can be customized.
   132  	Sender Sender
   133  
   134  	accountName       string
   135  	accountKey        []byte
   136  	useHTTPS          bool
   137  	UseSharedKeyLite  bool
   138  	baseURL           string
   139  	apiVersion        string
   140  	userAgent         string
   141  	sasClient         bool
   142  	accountSASToken   url.Values
   143  	additionalHeaders map[string]string
   144  }
   145  
   146  type odataResponse struct {
   147  	resp  *http.Response
   148  	odata odataErrorWrapper
   149  }
   150  
   151  // AzureStorageServiceError contains fields of the error response from
   152  // Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
   153  // Some fields might be specific to certain calls.
   154  type AzureStorageServiceError struct {
   155  	Code                      string `xml:"Code"`
   156  	Message                   string `xml:"Message"`
   157  	AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
   158  	QueryParameterName        string `xml:"QueryParameterName"`
   159  	QueryParameterValue       string `xml:"QueryParameterValue"`
   160  	Reason                    string `xml:"Reason"`
   161  	Lang                      string
   162  	StatusCode                int
   163  	RequestID                 string
   164  	Date                      string
   165  	APIVersion                string
   166  }
   167  
   168  // AzureTablesServiceError contains fields of the error response from
   169  // Azure Table Storage Service REST API in Atom format.
   170  // See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
   171  type AzureTablesServiceError struct {
   172  	Code       string `xml:"code"`
   173  	Message    string `xml:"message"`
   174  	StatusCode int
   175  	RequestID  string
   176  	Date       string
   177  	APIVersion string
   178  }
   179  
   180  func (e AzureTablesServiceError) Error() string {
   181  	return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestInitiated=%s, RequestId=%s, API Version=%s",
   182  		e.StatusCode, e.Code, e.Message, e.Date, e.RequestID, e.APIVersion)
   183  }
   184  
   185  type odataErrorMessage struct {
   186  	Lang  string `json:"lang"`
   187  	Value string `json:"value"`
   188  }
   189  
   190  type odataError struct {
   191  	Code    string            `json:"code"`
   192  	Message odataErrorMessage `json:"message"`
   193  }
   194  
   195  type odataErrorWrapper struct {
   196  	Err odataError `json:"odata.error"`
   197  }
   198  
   199  // UnexpectedStatusCodeError is returned when a storage service responds with neither an error
   200  // nor with an HTTP status code indicating success.
   201  type UnexpectedStatusCodeError struct {
   202  	allowed []int
   203  	got     int
   204  	inner   error
   205  }
   206  
   207  func (e UnexpectedStatusCodeError) Error() string {
   208  	s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
   209  
   210  	got := s(e.got)
   211  	expected := []string{}
   212  	for _, v := range e.allowed {
   213  		expected = append(expected, s(v))
   214  	}
   215  	return fmt.Sprintf("storage: status code from service response is %s; was expecting %s.  Inner error: %+v", got, strings.Join(expected, " or "), e.inner)
   216  }
   217  
   218  // Got is the actual status code returned by Azure.
   219  func (e UnexpectedStatusCodeError) Got() int {
   220  	return e.got
   221  }
   222  
   223  // Inner returns any inner error info.
   224  func (e UnexpectedStatusCodeError) Inner() error {
   225  	return e.inner
   226  }
   227  
   228  // NewClientFromConnectionString creates a Client from the connection string.
   229  func NewClientFromConnectionString(input string) (Client, error) {
   230  	// build a map of connection string key/value pairs
   231  	parts := map[string]string{}
   232  	for _, pair := range strings.Split(input, ";") {
   233  		if pair == "" {
   234  			continue
   235  		}
   236  
   237  		equalDex := strings.IndexByte(pair, '=')
   238  		if equalDex <= 0 {
   239  			return Client{}, fmt.Errorf("Invalid connection segment %q", pair)
   240  		}
   241  
   242  		value := strings.TrimSpace(pair[equalDex+1:])
   243  		key := strings.TrimSpace(strings.ToLower(pair[:equalDex]))
   244  		parts[key] = value
   245  	}
   246  
   247  	// TODO: validate parameter sets?
   248  
   249  	if parts[connectionStringAccountName] == StorageEmulatorAccountName {
   250  		return NewEmulatorClient()
   251  	}
   252  
   253  	if parts[connectionStringSAS] != "" {
   254  		endpoint := ""
   255  		if parts[connectionStringBlobEndpoint] != "" {
   256  			endpoint = parts[connectionStringBlobEndpoint]
   257  		} else if parts[connectionStringFileEndpoint] != "" {
   258  			endpoint = parts[connectionStringFileEndpoint]
   259  		} else if parts[connectionStringQueueEndpoint] != "" {
   260  			endpoint = parts[connectionStringQueueEndpoint]
   261  		} else {
   262  			endpoint = parts[connectionStringTableEndpoint]
   263  		}
   264  
   265  		return NewAccountSASClientFromEndpointToken(endpoint, parts[connectionStringSAS])
   266  	}
   267  
   268  	useHTTPS := defaultUseHTTPS
   269  	if parts[connectionStringEndpointProtocol] != "" {
   270  		useHTTPS = parts[connectionStringEndpointProtocol] == "https"
   271  	}
   272  
   273  	return NewClient(parts[connectionStringAccountName], parts[connectionStringAccountKey],
   274  		parts[connectionStringEndpointSuffix], DefaultAPIVersion, useHTTPS)
   275  }
   276  
   277  // NewBasicClient constructs a Client with given storage service name and
   278  // key.
   279  func NewBasicClient(accountName, accountKey string) (Client, error) {
   280  	if accountName == StorageEmulatorAccountName {
   281  		return NewEmulatorClient()
   282  	}
   283  	return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
   284  }
   285  
   286  // NewBasicClientOnSovereignCloud constructs a Client with given storage service name and
   287  // key in the referenced cloud.
   288  func NewBasicClientOnSovereignCloud(accountName, accountKey string, env azure.Environment) (Client, error) {
   289  	if accountName == StorageEmulatorAccountName {
   290  		return NewEmulatorClient()
   291  	}
   292  	return NewClient(accountName, accountKey, env.StorageEndpointSuffix, DefaultAPIVersion, defaultUseHTTPS)
   293  }
   294  
   295  //NewEmulatorClient contructs a Client intended to only work with Azure
   296  //Storage Emulator
   297  func NewEmulatorClient() (Client, error) {
   298  	return NewClient(StorageEmulatorAccountName, StorageEmulatorAccountKey, DefaultBaseURL, DefaultAPIVersion, false)
   299  }
   300  
   301  // NewClient constructs a Client. This should be used if the caller wants
   302  // to specify whether to use HTTPS, a specific REST API version or a custom
   303  // storage endpoint than Azure Public Cloud.
   304  func NewClient(accountName, accountKey, serviceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
   305  	var c Client
   306  	if !IsValidStorageAccount(accountName) {
   307  		return c, fmt.Errorf("azure: account name is not valid: it must be between 3 and 24 characters, and only may contain numbers and lowercase letters: %v", accountName)
   308  	} else if accountKey == "" {
   309  		return c, fmt.Errorf("azure: account key required")
   310  	} else if serviceBaseURL == "" {
   311  		return c, fmt.Errorf("azure: base storage service url required")
   312  	}
   313  
   314  	key, err := base64.StdEncoding.DecodeString(accountKey)
   315  	if err != nil {
   316  		return c, fmt.Errorf("azure: malformed storage account key: %v", err)
   317  	}
   318  
   319  	return newClient(accountName, key, serviceBaseURL, apiVersion, useHTTPS)
   320  }
   321  
   322  // NewCosmosClient constructs a Client for Azure CosmosDB. This should be used if the caller wants
   323  // to specify whether to use HTTPS, a specific REST API version or a custom
   324  // cosmos endpoint than Azure Public Cloud.
   325  func NewCosmosClient(accountName, accountKey, serviceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
   326  	var c Client
   327  	if !IsValidCosmosAccount(accountName) {
   328  		return c, fmt.Errorf("azure: account name is not valid: The name can contain only lowercase letters, numbers and the '-' character, and must be between 3 and 44 characters: %v", accountName)
   329  	} else if accountKey == "" {
   330  		return c, fmt.Errorf("azure: account key required")
   331  	} else if serviceBaseURL == "" {
   332  		return c, fmt.Errorf("azure: base storage service url required")
   333  	}
   334  
   335  	key, err := base64.StdEncoding.DecodeString(accountKey)
   336  	if err != nil {
   337  		return c, fmt.Errorf("azure: malformed cosmos account key: %v", err)
   338  	}
   339  
   340  	return newClient(accountName, key, serviceBaseURL, apiVersion, useHTTPS)
   341  }
   342  
   343  // newClient constructs a Client with given parameters.
   344  func newClient(accountName string, accountKey []byte, serviceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
   345  	c := Client{
   346  		HTTPClient:       http.DefaultClient,
   347  		accountName:      accountName,
   348  		accountKey:       accountKey,
   349  		useHTTPS:         useHTTPS,
   350  		baseURL:          serviceBaseURL,
   351  		apiVersion:       apiVersion,
   352  		sasClient:        false,
   353  		UseSharedKeyLite: false,
   354  		Sender: &DefaultSender{
   355  			RetryAttempts:    defaultRetryAttempts,
   356  			ValidStatusCodes: defaultValidStatusCodes,
   357  			RetryDuration:    defaultRetryDuration,
   358  		},
   359  	}
   360  	c.userAgent = c.getDefaultUserAgent()
   361  	return c, nil
   362  }
   363  
   364  // IsValidStorageAccount checks if the storage account name is valid.
   365  // See https://docs.microsoft.com/en-us/azure/storage/storage-create-storage-account
   366  func IsValidStorageAccount(account string) bool {
   367  	return validStorageAccount.MatchString(account)
   368  }
   369  
   370  // IsValidCosmosAccount checks if the Cosmos account name is valid.
   371  // See https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-manage-database-account
   372  func IsValidCosmosAccount(account string) bool {
   373  	return validCosmosAccount.MatchString(account)
   374  }
   375  
   376  // NewAccountSASClient contructs a client that uses accountSAS authorization
   377  // for its operations.
   378  func NewAccountSASClient(account string, token url.Values, env azure.Environment) Client {
   379  	return newSASClient(account, env.StorageEndpointSuffix, token)
   380  }
   381  
   382  // NewAccountSASClientFromEndpointToken constructs a client that uses accountSAS authorization
   383  // for its operations using the specified endpoint and SAS token.
   384  func NewAccountSASClientFromEndpointToken(endpoint string, sasToken string) (Client, error) {
   385  	u, err := url.Parse(endpoint)
   386  	if err != nil {
   387  		return Client{}, err
   388  	}
   389  	_, err = url.ParseQuery(sasToken)
   390  	if err != nil {
   391  		return Client{}, err
   392  	}
   393  	u.RawQuery = sasToken
   394  	return newSASClientFromURL(u)
   395  }
   396  
   397  func newSASClient(accountName, baseURL string, sasToken url.Values) Client {
   398  	c := Client{
   399  		HTTPClient: http.DefaultClient,
   400  		apiVersion: DefaultAPIVersion,
   401  		sasClient:  true,
   402  		Sender: &DefaultSender{
   403  			RetryAttempts:    defaultRetryAttempts,
   404  			ValidStatusCodes: defaultValidStatusCodes,
   405  			RetryDuration:    defaultRetryDuration,
   406  		},
   407  		accountName:     accountName,
   408  		baseURL:         baseURL,
   409  		accountSASToken: sasToken,
   410  		useHTTPS:        defaultUseHTTPS,
   411  	}
   412  	c.userAgent = c.getDefaultUserAgent()
   413  	// Get API version and protocol from token
   414  	c.apiVersion = sasToken.Get("sv")
   415  	if spr := sasToken.Get("spr"); spr != "" {
   416  		c.useHTTPS = spr == "https"
   417  	}
   418  	return c
   419  }
   420  
   421  func newSASClientFromURL(u *url.URL) (Client, error) {
   422  	// the host name will look something like this
   423  	// - foo.blob.core.windows.net
   424  	// "foo" is the account name
   425  	// "core.windows.net" is the baseURL
   426  
   427  	// find the first dot to get account name
   428  	i1 := strings.IndexByte(u.Host, '.')
   429  	if i1 < 0 {
   430  		return Client{}, fmt.Errorf("failed to find '.' in %s", u.Host)
   431  	}
   432  
   433  	// now find the second dot to get the base URL
   434  	i2 := strings.IndexByte(u.Host[i1+1:], '.')
   435  	if i2 < 0 {
   436  		return Client{}, fmt.Errorf("failed to find '.' in %s", u.Host[i1+1:])
   437  	}
   438  
   439  	sasToken := u.Query()
   440  	c := newSASClient(u.Host[:i1], u.Host[i1+i2+2:], sasToken)
   441  	if spr := sasToken.Get("spr"); spr == "" {
   442  		// infer from URL if not in the query params set
   443  		c.useHTTPS = u.Scheme == "https"
   444  	}
   445  	return c, nil
   446  }
   447  
   448  func (c Client) isServiceSASClient() bool {
   449  	return c.sasClient && c.accountSASToken == nil
   450  }
   451  
   452  func (c Client) isAccountSASClient() bool {
   453  	return c.sasClient && c.accountSASToken != nil
   454  }
   455  
   456  func (c Client) getDefaultUserAgent() string {
   457  	return fmt.Sprintf("Go/%s (%s-%s) azure-storage-go/%s api-version/%s",
   458  		runtime.Version(),
   459  		runtime.GOARCH,
   460  		runtime.GOOS,
   461  		version.Number,
   462  		c.apiVersion,
   463  	)
   464  }
   465  
   466  // AddToUserAgent adds an extension to the current user agent
   467  func (c *Client) AddToUserAgent(extension string) error {
   468  	if extension != "" {
   469  		c.userAgent = fmt.Sprintf("%s %s", c.userAgent, extension)
   470  		return nil
   471  	}
   472  	return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.userAgent)
   473  }
   474  
   475  // AddAdditionalHeaders adds additional standard headers
   476  func (c *Client) AddAdditionalHeaders(headers map[string]string) {
   477  	if headers != nil {
   478  		c.additionalHeaders = map[string]string{}
   479  		for k, v := range headers {
   480  			c.additionalHeaders[k] = v
   481  		}
   482  	}
   483  }
   484  
   485  // protectUserAgent is used in funcs that include extraheaders as a parameter.
   486  // It prevents the User-Agent header to be overwritten, instead if it happens to
   487  // be present, it gets added to the current User-Agent. Use it before getStandardHeaders
   488  func (c *Client) protectUserAgent(extraheaders map[string]string) map[string]string {
   489  	if v, ok := extraheaders[userAgentHeader]; ok {
   490  		c.AddToUserAgent(v)
   491  		delete(extraheaders, userAgentHeader)
   492  	}
   493  	return extraheaders
   494  }
   495  
   496  func (c Client) getBaseURL(service string) *url.URL {
   497  	scheme := "http"
   498  	if c.useHTTPS {
   499  		scheme = "https"
   500  	}
   501  	host := ""
   502  	if c.accountName == StorageEmulatorAccountName {
   503  		switch service {
   504  		case blobServiceName:
   505  			host = storageEmulatorBlob
   506  		case tableServiceName:
   507  			host = storageEmulatorTable
   508  		case queueServiceName:
   509  			host = storageEmulatorQueue
   510  		}
   511  	} else {
   512  		host = fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)
   513  	}
   514  
   515  	return &url.URL{
   516  		Scheme: scheme,
   517  		Host:   host,
   518  	}
   519  }
   520  
   521  func (c Client) getEndpoint(service, path string, params url.Values) string {
   522  	u := c.getBaseURL(service)
   523  
   524  	// API doesn't accept path segments not starting with '/'
   525  	if !strings.HasPrefix(path, "/") {
   526  		path = fmt.Sprintf("/%v", path)
   527  	}
   528  
   529  	if c.accountName == StorageEmulatorAccountName {
   530  		path = fmt.Sprintf("/%v%v", StorageEmulatorAccountName, path)
   531  	}
   532  
   533  	u.Path = path
   534  	u.RawQuery = params.Encode()
   535  	return u.String()
   536  }
   537  
   538  // AccountSASTokenOptions includes options for constructing
   539  // an account SAS token.
   540  // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas
   541  type AccountSASTokenOptions struct {
   542  	APIVersion    string
   543  	Services      Services
   544  	ResourceTypes ResourceTypes
   545  	Permissions   Permissions
   546  	Start         time.Time
   547  	Expiry        time.Time
   548  	IP            string
   549  	UseHTTPS      bool
   550  }
   551  
   552  // Services specify services accessible with an account SAS.
   553  type Services struct {
   554  	Blob  bool
   555  	Queue bool
   556  	Table bool
   557  	File  bool
   558  }
   559  
   560  // ResourceTypes specify the resources accesible with an
   561  // account SAS.
   562  type ResourceTypes struct {
   563  	Service   bool
   564  	Container bool
   565  	Object    bool
   566  }
   567  
   568  // Permissions specifies permissions for an accountSAS.
   569  type Permissions struct {
   570  	Read    bool
   571  	Write   bool
   572  	Delete  bool
   573  	List    bool
   574  	Add     bool
   575  	Create  bool
   576  	Update  bool
   577  	Process bool
   578  }
   579  
   580  // GetAccountSASToken creates an account SAS token
   581  // See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas
   582  func (c Client) GetAccountSASToken(options AccountSASTokenOptions) (url.Values, error) {
   583  	if options.APIVersion == "" {
   584  		options.APIVersion = c.apiVersion
   585  	}
   586  
   587  	if options.APIVersion < "2015-04-05" {
   588  		return url.Values{}, fmt.Errorf("account SAS does not support API versions prior to 2015-04-05. API version : %s", options.APIVersion)
   589  	}
   590  
   591  	// build services string
   592  	services := ""
   593  	if options.Services.Blob {
   594  		services += "b"
   595  	}
   596  	if options.Services.Queue {
   597  		services += "q"
   598  	}
   599  	if options.Services.Table {
   600  		services += "t"
   601  	}
   602  	if options.Services.File {
   603  		services += "f"
   604  	}
   605  
   606  	// build resources string
   607  	resources := ""
   608  	if options.ResourceTypes.Service {
   609  		resources += "s"
   610  	}
   611  	if options.ResourceTypes.Container {
   612  		resources += "c"
   613  	}
   614  	if options.ResourceTypes.Object {
   615  		resources += "o"
   616  	}
   617  
   618  	// build permissions string
   619  	permissions := ""
   620  	if options.Permissions.Read {
   621  		permissions += "r"
   622  	}
   623  	if options.Permissions.Write {
   624  		permissions += "w"
   625  	}
   626  	if options.Permissions.Delete {
   627  		permissions += "d"
   628  	}
   629  	if options.Permissions.List {
   630  		permissions += "l"
   631  	}
   632  	if options.Permissions.Add {
   633  		permissions += "a"
   634  	}
   635  	if options.Permissions.Create {
   636  		permissions += "c"
   637  	}
   638  	if options.Permissions.Update {
   639  		permissions += "u"
   640  	}
   641  	if options.Permissions.Process {
   642  		permissions += "p"
   643  	}
   644  
   645  	// build start time, if exists
   646  	start := ""
   647  	if options.Start != (time.Time{}) {
   648  		start = options.Start.UTC().Format(time.RFC3339)
   649  	}
   650  
   651  	// build expiry time
   652  	expiry := options.Expiry.UTC().Format(time.RFC3339)
   653  
   654  	protocol := "https,http"
   655  	if options.UseHTTPS {
   656  		protocol = "https"
   657  	}
   658  
   659  	stringToSign := strings.Join([]string{
   660  		c.accountName,
   661  		permissions,
   662  		services,
   663  		resources,
   664  		start,
   665  		expiry,
   666  		options.IP,
   667  		protocol,
   668  		options.APIVersion,
   669  		"",
   670  	}, "\n")
   671  	signature := c.computeHmac256(stringToSign)
   672  
   673  	sasParams := url.Values{
   674  		"sv":  {options.APIVersion},
   675  		"ss":  {services},
   676  		"srt": {resources},
   677  		"sp":  {permissions},
   678  		"se":  {expiry},
   679  		"spr": {protocol},
   680  		"sig": {signature},
   681  	}
   682  	if start != "" {
   683  		sasParams.Add("st", start)
   684  	}
   685  	if options.IP != "" {
   686  		sasParams.Add("sip", options.IP)
   687  	}
   688  
   689  	return sasParams, nil
   690  }
   691  
   692  // GetBlobService returns a BlobStorageClient which can operate on the blob
   693  // service of the storage account.
   694  func (c Client) GetBlobService() BlobStorageClient {
   695  	b := BlobStorageClient{
   696  		client: c,
   697  	}
   698  	b.client.AddToUserAgent(blobServiceName)
   699  	b.auth = sharedKey
   700  	if c.UseSharedKeyLite {
   701  		b.auth = sharedKeyLite
   702  	}
   703  	return b
   704  }
   705  
   706  // GetQueueService returns a QueueServiceClient which can operate on the queue
   707  // service of the storage account.
   708  func (c Client) GetQueueService() QueueServiceClient {
   709  	q := QueueServiceClient{
   710  		client: c,
   711  	}
   712  	q.client.AddToUserAgent(queueServiceName)
   713  	q.auth = sharedKey
   714  	if c.UseSharedKeyLite {
   715  		q.auth = sharedKeyLite
   716  	}
   717  	return q
   718  }
   719  
   720  // GetTableService returns a TableServiceClient which can operate on the table
   721  // service of the storage account.
   722  func (c Client) GetTableService() TableServiceClient {
   723  	t := TableServiceClient{
   724  		client: c,
   725  	}
   726  	t.client.AddToUserAgent(tableServiceName)
   727  	t.auth = sharedKeyForTable
   728  	if c.UseSharedKeyLite {
   729  		t.auth = sharedKeyLiteForTable
   730  	}
   731  	return t
   732  }
   733  
   734  // GetFileService returns a FileServiceClient which can operate on the file
   735  // service of the storage account.
   736  func (c Client) GetFileService() FileServiceClient {
   737  	f := FileServiceClient{
   738  		client: c,
   739  	}
   740  	f.client.AddToUserAgent(fileServiceName)
   741  	f.auth = sharedKey
   742  	if c.UseSharedKeyLite {
   743  		f.auth = sharedKeyLite
   744  	}
   745  	return f
   746  }
   747  
   748  func (c Client) getStandardHeaders() map[string]string {
   749  	headers := map[string]string{}
   750  	for k, v := range c.additionalHeaders {
   751  		headers[k] = v
   752  	}
   753  
   754  	headers[userAgentHeader] = c.userAgent
   755  	headers["x-ms-version"] = c.apiVersion
   756  	headers["x-ms-date"] = currentTimeRfc1123Formatted()
   757  
   758  	return headers
   759  }
   760  
   761  func (c Client) exec(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*http.Response, error) {
   762  	headers, err := c.addAuthorizationHeader(verb, url, headers, auth)
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  
   767  	req, err := http.NewRequest(verb, url, body)
   768  	if err != nil {
   769  		return nil, errors.New("azure/storage: error creating request: " + err.Error())
   770  	}
   771  
   772  	// http.NewRequest() will automatically set req.ContentLength for a handful of types
   773  	// otherwise we will handle here.
   774  	if req.ContentLength < 1 {
   775  		if clstr, ok := headers["Content-Length"]; ok {
   776  			if cl, err := strconv.ParseInt(clstr, 10, 64); err == nil {
   777  				req.ContentLength = cl
   778  			}
   779  		}
   780  	}
   781  
   782  	for k, v := range headers {
   783  		req.Header[k] = append(req.Header[k], v) // Must bypass case munging present in `Add` by using map functions directly. See https://github.com/Azure/azure-sdk-for-go/issues/645
   784  	}
   785  
   786  	if c.isAccountSASClient() {
   787  		// append the SAS token to the query params
   788  		v := req.URL.Query()
   789  		v = mergeParams(v, c.accountSASToken)
   790  		req.URL.RawQuery = v.Encode()
   791  	}
   792  
   793  	resp, err := c.Sender.Send(&c, req)
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  
   798  	if resp.StatusCode >= 400 && resp.StatusCode <= 505 {
   799  		return resp, getErrorFromResponse(resp)
   800  	}
   801  
   802  	return resp, nil
   803  }
   804  
   805  func (c Client) execInternalJSONCommon(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, *http.Request, *http.Response, error) {
   806  	headers, err := c.addAuthorizationHeader(verb, url, headers, auth)
   807  	if err != nil {
   808  		return nil, nil, nil, err
   809  	}
   810  
   811  	req, err := http.NewRequest(verb, url, body)
   812  	for k, v := range headers {
   813  		req.Header.Add(k, v)
   814  	}
   815  
   816  	resp, err := c.Sender.Send(&c, req)
   817  	if err != nil {
   818  		return nil, nil, nil, err
   819  	}
   820  
   821  	respToRet := &odataResponse{resp: resp}
   822  
   823  	statusCode := resp.StatusCode
   824  	if statusCode >= 400 && statusCode <= 505 {
   825  		var respBody []byte
   826  		respBody, err = readAndCloseBody(resp.Body)
   827  		if err != nil {
   828  			return nil, nil, nil, err
   829  		}
   830  
   831  		requestID, date, version := getDebugHeaders(resp.Header)
   832  		if len(respBody) == 0 {
   833  			// no error in response body, might happen in HEAD requests
   834  			err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID, date, version)
   835  			return respToRet, req, resp, err
   836  		}
   837  		// response contains storage service error object, unmarshal
   838  		if resp.Header.Get("Content-Type") == "application/xml" {
   839  			storageErr := AzureTablesServiceError{
   840  				StatusCode: resp.StatusCode,
   841  				RequestID:  requestID,
   842  				Date:       date,
   843  				APIVersion: version,
   844  			}
   845  			if err := xml.Unmarshal(respBody, &storageErr); err != nil {
   846  				storageErr.Message = fmt.Sprintf("Response body could no be unmarshaled: %v. Body: %v.", err, string(respBody))
   847  			}
   848  			err = storageErr
   849  		} else {
   850  			err = json.Unmarshal(respBody, &respToRet.odata)
   851  		}
   852  	}
   853  
   854  	return respToRet, req, resp, err
   855  }
   856  
   857  func (c Client) execInternalJSON(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, error) {
   858  	respToRet, _, _, err := c.execInternalJSONCommon(verb, url, headers, body, auth)
   859  	return respToRet, err
   860  }
   861  
   862  func (c Client) execBatchOperationJSON(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, error) {
   863  	// execute common query, get back generated request, response etc... for more processing.
   864  	respToRet, req, resp, err := c.execInternalJSONCommon(verb, url, headers, body, auth)
   865  	if err != nil {
   866  		return nil, err
   867  	}
   868  
   869  	// return the OData in the case of executing batch commands.
   870  	// In this case we need to read the outer batch boundary and contents.
   871  	// Then we read the changeset information within the batch
   872  	var respBody []byte
   873  	respBody, err = readAndCloseBody(resp.Body)
   874  	if err != nil {
   875  		return nil, err
   876  	}
   877  
   878  	// outer multipart body
   879  	_, batchHeader, err := mime.ParseMediaType(resp.Header["Content-Type"][0])
   880  	if err != nil {
   881  		return nil, err
   882  	}
   883  
   884  	// batch details.
   885  	batchBoundary := batchHeader["boundary"]
   886  	batchPartBuf, changesetBoundary, err := genBatchReader(batchBoundary, respBody)
   887  	if err != nil {
   888  		return nil, err
   889  	}
   890  
   891  	// changeset details.
   892  	err = genChangesetReader(req, respToRet, batchPartBuf, changesetBoundary)
   893  	if err != nil {
   894  		return nil, err
   895  	}
   896  
   897  	return respToRet, nil
   898  }
   899  
   900  func genChangesetReader(req *http.Request, respToRet *odataResponse, batchPartBuf io.Reader, changesetBoundary string) error {
   901  	changesetMultiReader := multipart.NewReader(batchPartBuf, changesetBoundary)
   902  	changesetPart, err := changesetMultiReader.NextPart()
   903  	if err != nil {
   904  		return err
   905  	}
   906  
   907  	changesetPartBufioReader := bufio.NewReader(changesetPart)
   908  	changesetResp, err := http.ReadResponse(changesetPartBufioReader, req)
   909  	if err != nil {
   910  		return err
   911  	}
   912  
   913  	if changesetResp.StatusCode != http.StatusNoContent {
   914  		changesetBody, err := readAndCloseBody(changesetResp.Body)
   915  		err = json.Unmarshal(changesetBody, &respToRet.odata)
   916  		if err != nil {
   917  			return err
   918  		}
   919  		respToRet.resp = changesetResp
   920  	}
   921  
   922  	return nil
   923  }
   924  
   925  func genBatchReader(batchBoundary string, respBody []byte) (io.Reader, string, error) {
   926  	respBodyString := string(respBody)
   927  	respBodyReader := strings.NewReader(respBodyString)
   928  
   929  	// reading batchresponse
   930  	batchMultiReader := multipart.NewReader(respBodyReader, batchBoundary)
   931  	batchPart, err := batchMultiReader.NextPart()
   932  	if err != nil {
   933  		return nil, "", err
   934  	}
   935  	batchPartBufioReader := bufio.NewReader(batchPart)
   936  
   937  	_, changesetHeader, err := mime.ParseMediaType(batchPart.Header.Get("Content-Type"))
   938  	if err != nil {
   939  		return nil, "", err
   940  	}
   941  	changesetBoundary := changesetHeader["boundary"]
   942  	return batchPartBufioReader, changesetBoundary, nil
   943  }
   944  
   945  func readAndCloseBody(body io.ReadCloser) ([]byte, error) {
   946  	defer body.Close()
   947  	out, err := ioutil.ReadAll(body)
   948  	if err == io.EOF {
   949  		err = nil
   950  	}
   951  	return out, err
   952  }
   953  
   954  // reads the response body then closes it
   955  func drainRespBody(resp *http.Response) {
   956  	if resp != nil {
   957  		io.Copy(ioutil.Discard, resp.Body)
   958  		resp.Body.Close()
   959  	}
   960  }
   961  
   962  func serviceErrFromXML(body []byte, storageErr *AzureStorageServiceError) error {
   963  	if err := xml.Unmarshal(body, storageErr); err != nil {
   964  		storageErr.Message = fmt.Sprintf("Response body could no be unmarshaled: %v. Body: %v.", err, string(body))
   965  		return err
   966  	}
   967  	return nil
   968  }
   969  
   970  func serviceErrFromJSON(body []byte, storageErr *AzureStorageServiceError) error {
   971  	odataError := odataErrorWrapper{}
   972  	if err := json.Unmarshal(body, &odataError); err != nil {
   973  		storageErr.Message = fmt.Sprintf("Response body could no be unmarshaled: %v. Body: %v.", err, string(body))
   974  		return err
   975  	}
   976  	storageErr.Code = odataError.Err.Code
   977  	storageErr.Message = odataError.Err.Message.Value
   978  	storageErr.Lang = odataError.Err.Message.Lang
   979  	return nil
   980  }
   981  
   982  func serviceErrFromStatusCode(code int, status string, requestID, date, version string) AzureStorageServiceError {
   983  	return AzureStorageServiceError{
   984  		StatusCode: code,
   985  		Code:       status,
   986  		RequestID:  requestID,
   987  		Date:       date,
   988  		APIVersion: version,
   989  		Message:    "no response body was available for error status code",
   990  	}
   991  }
   992  
   993  func (e AzureStorageServiceError) Error() string {
   994  	return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestInitiated=%s, RequestId=%s, API Version=%s, QueryParameterName=%s, QueryParameterValue=%s",
   995  		e.StatusCode, e.Code, e.Message, e.Date, e.RequestID, e.APIVersion, e.QueryParameterName, e.QueryParameterValue)
   996  }
   997  
   998  // checkRespCode returns UnexpectedStatusError if the given response code is not
   999  // one of the allowed status codes; otherwise nil.
  1000  func checkRespCode(resp *http.Response, allowed []int) error {
  1001  	for _, v := range allowed {
  1002  		if resp.StatusCode == v {
  1003  			return nil
  1004  		}
  1005  	}
  1006  	err := getErrorFromResponse(resp)
  1007  	return UnexpectedStatusCodeError{
  1008  		allowed: allowed,
  1009  		got:     resp.StatusCode,
  1010  		inner:   err,
  1011  	}
  1012  }
  1013  
  1014  func (c Client) addMetadataToHeaders(h map[string]string, metadata map[string]string) map[string]string {
  1015  	metadata = c.protectUserAgent(metadata)
  1016  	for k, v := range metadata {
  1017  		h[userDefinedMetadataHeaderPrefix+k] = v
  1018  	}
  1019  	return h
  1020  }
  1021  
  1022  func getDebugHeaders(h http.Header) (requestID, date, version string) {
  1023  	requestID = h.Get("x-ms-request-id")
  1024  	version = h.Get("x-ms-version")
  1025  	date = h.Get("Date")
  1026  	return
  1027  }
  1028  
  1029  func getErrorFromResponse(resp *http.Response) error {
  1030  	respBody, err := readAndCloseBody(resp.Body)
  1031  	if err != nil {
  1032  		return err
  1033  	}
  1034  
  1035  	requestID, date, version := getDebugHeaders(resp.Header)
  1036  	if len(respBody) == 0 {
  1037  		// no error in response body, might happen in HEAD requests
  1038  		err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID, date, version)
  1039  	} else {
  1040  		storageErr := AzureStorageServiceError{
  1041  			StatusCode: resp.StatusCode,
  1042  			RequestID:  requestID,
  1043  			Date:       date,
  1044  			APIVersion: version,
  1045  		}
  1046  		// response contains storage service error object, unmarshal
  1047  		if resp.Header.Get("Content-Type") == "application/xml" {
  1048  			errIn := serviceErrFromXML(respBody, &storageErr)
  1049  			if err != nil { // error unmarshaling the error response
  1050  				err = errIn
  1051  			}
  1052  		} else {
  1053  			errIn := serviceErrFromJSON(respBody, &storageErr)
  1054  			if err != nil { // error unmarshaling the error response
  1055  				err = errIn
  1056  			}
  1057  		}
  1058  		err = storageErr
  1059  	}
  1060  	return err
  1061  }
  1062  

View as plain text