1
2 package storage
3
4
5
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
33
34 DefaultBaseURL = "core.windows.net"
35
36
37
38 DefaultAPIVersion = "2018-03-28"
39
40 defaultUseHTTPS = true
41 defaultRetryAttempts = 5
42 defaultRetryDuration = time.Second * 5
43
44
45 StorageEmulatorAccountName = "devstoreaccount1"
46
47
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,
80 http.StatusInternalServerError,
81 http.StatusBadGateway,
82 http.StatusServiceUnavailable,
83 http.StatusGatewayTimeout,
84 }
85 )
86
87
88 type Sender interface {
89 Send(*Client, *http.Request) (*http.Response, error)
90 }
91
92
93
94 type DefaultSender struct {
95 RetryAttempts int
96 RetryDuration time.Duration
97 ValidStatusCodes []int
98 attempts int
99 }
100
101
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
122
123 type Client struct {
124
125
126
127 HTTPClient *http.Client
128
129
130
131
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
152
153
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
169
170
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
200
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
219 func (e UnexpectedStatusCodeError) Got() int {
220 return e.got
221 }
222
223
224 func (e UnexpectedStatusCodeError) Inner() error {
225 return e.inner
226 }
227
228
229 func NewClientFromConnectionString(input string) (Client, error) {
230
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
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
278
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
287
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
296
297 func NewEmulatorClient() (Client, error) {
298 return NewClient(StorageEmulatorAccountName, StorageEmulatorAccountKey, DefaultBaseURL, DefaultAPIVersion, false)
299 }
300
301
302
303
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
323
324
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
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
365
366 func IsValidStorageAccount(account string) bool {
367 return validStorageAccount.MatchString(account)
368 }
369
370
371
372 func IsValidCosmosAccount(account string) bool {
373 return validCosmosAccount.MatchString(account)
374 }
375
376
377
378 func NewAccountSASClient(account string, token url.Values, env azure.Environment) Client {
379 return newSASClient(account, env.StorageEndpointSuffix, token)
380 }
381
382
383
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
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
423
424
425
426
427
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
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
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
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
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
486
487
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
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
539
540
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
553 type Services struct {
554 Blob bool
555 Queue bool
556 Table bool
557 File bool
558 }
559
560
561
562 type ResourceTypes struct {
563 Service bool
564 Container bool
565 Object bool
566 }
567
568
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
581
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
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
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
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
646 start := ""
647 if options.Start != (time.Time{}) {
648 start = options.Start.UTC().Format(time.RFC3339)
649 }
650
651
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
693
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
707
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
721
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
735
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
773
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)
784 }
785
786 if c.isAccountSASClient() {
787
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
834 err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID, date, version)
835 return respToRet, req, resp, err
836 }
837
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
864 respToRet, req, resp, err := c.execInternalJSONCommon(verb, url, headers, body, auth)
865 if err != nil {
866 return nil, err
867 }
868
869
870
871
872 var respBody []byte
873 respBody, err = readAndCloseBody(resp.Body)
874 if err != nil {
875 return nil, err
876 }
877
878
879 _, batchHeader, err := mime.ParseMediaType(resp.Header["Content-Type"][0])
880 if err != nil {
881 return nil, err
882 }
883
884
885 batchBoundary := batchHeader["boundary"]
886 batchPartBuf, changesetBoundary, err := genBatchReader(batchBoundary, respBody)
887 if err != nil {
888 return nil, err
889 }
890
891
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
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
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
999
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
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
1047 if resp.Header.Get("Content-Type") == "application/xml" {
1048 errIn := serviceErrFromXML(respBody, &storageErr)
1049 if err != nil {
1050 err = errIn
1051 }
1052 } else {
1053 errIn := serviceErrFromJSON(respBody, &storageErr)
1054 if err != nil {
1055 err = errIn
1056 }
1057 }
1058 err = storageErr
1059 }
1060 return err
1061 }
1062
View as plain text