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