245 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
| package storage
 | |
| 
 | |
| // Copyright 2017 Microsoft Corporation
 | |
| //
 | |
| //  Licensed under the Apache License, Version 2.0 (the "License");
 | |
| //  you may not use this file except in compliance with the License.
 | |
| //  You may obtain a copy of the License at
 | |
| //
 | |
| //      http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| //  Unless required by applicable law or agreed to in writing, software
 | |
| //  distributed under the License is distributed on an "AS IS" BASIS,
 | |
| //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| //  See the License for the specific language governing permissions and
 | |
| //  limitations under the License.
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto/hmac"
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/xml"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	fixedTime         = time.Date(2050, time.December, 20, 21, 55, 0, 0, time.FixedZone("GMT", -6))
 | |
| 	accountSASOptions = AccountSASTokenOptions{
 | |
| 		Services: Services{
 | |
| 			Blob: true,
 | |
| 		},
 | |
| 		ResourceTypes: ResourceTypes{
 | |
| 			Service:   true,
 | |
| 			Container: true,
 | |
| 			Object:    true,
 | |
| 		},
 | |
| 		Permissions: Permissions{
 | |
| 			Read:    true,
 | |
| 			Write:   true,
 | |
| 			Delete:  true,
 | |
| 			List:    true,
 | |
| 			Add:     true,
 | |
| 			Create:  true,
 | |
| 			Update:  true,
 | |
| 			Process: true,
 | |
| 		},
 | |
| 		Expiry:   fixedTime,
 | |
| 		UseHTTPS: true,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func (c Client) computeHmac256(message string) string {
 | |
| 	h := hmac.New(sha256.New, c.accountKey)
 | |
| 	h.Write([]byte(message))
 | |
| 	return base64.StdEncoding.EncodeToString(h.Sum(nil))
 | |
| }
 | |
| 
 | |
| func currentTimeRfc1123Formatted() string {
 | |
| 	return timeRfc1123Formatted(time.Now().UTC())
 | |
| }
 | |
| 
 | |
| func timeRfc1123Formatted(t time.Time) string {
 | |
| 	return t.Format(http.TimeFormat)
 | |
| }
 | |
| 
 | |
| func timeRFC3339Formatted(t time.Time) string {
 | |
| 	return t.Format("2006-01-02T15:04:05.0000000Z")
 | |
| }
 | |
| 
 | |
| func mergeParams(v1, v2 url.Values) url.Values {
 | |
| 	out := url.Values{}
 | |
| 	for k, v := range v1 {
 | |
| 		out[k] = v
 | |
| 	}
 | |
| 	for k, v := range v2 {
 | |
| 		vals, ok := out[k]
 | |
| 		if ok {
 | |
| 			vals = append(vals, v...)
 | |
| 			out[k] = vals
 | |
| 		} else {
 | |
| 			out[k] = v
 | |
| 		}
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func prepareBlockListRequest(blocks []Block) string {
 | |
| 	s := `<?xml version="1.0" encoding="utf-8"?><BlockList>`
 | |
| 	for _, v := range blocks {
 | |
| 		s += fmt.Sprintf("<%s>%s</%s>", v.Status, v.ID, v.Status)
 | |
| 	}
 | |
| 	s += `</BlockList>`
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| func xmlUnmarshal(body io.Reader, v interface{}) error {
 | |
| 	data, err := ioutil.ReadAll(body)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return xml.Unmarshal(data, v)
 | |
| }
 | |
| 
 | |
| func xmlMarshal(v interface{}) (io.Reader, int, error) {
 | |
| 	b, err := xml.Marshal(v)
 | |
| 	if err != nil {
 | |
| 		return nil, 0, err
 | |
| 	}
 | |
| 	return bytes.NewReader(b), len(b), nil
 | |
| }
 | |
| 
 | |
| func headersFromStruct(v interface{}) map[string]string {
 | |
| 	headers := make(map[string]string)
 | |
| 	value := reflect.ValueOf(v)
 | |
| 	for i := 0; i < value.NumField(); i++ {
 | |
| 		key := value.Type().Field(i).Tag.Get("header")
 | |
| 		if key != "" {
 | |
| 			reflectedValue := reflect.Indirect(value.Field(i))
 | |
| 			var val string
 | |
| 			if reflectedValue.IsValid() {
 | |
| 				switch reflectedValue.Type() {
 | |
| 				case reflect.TypeOf(fixedTime):
 | |
| 					val = timeRfc1123Formatted(reflectedValue.Interface().(time.Time))
 | |
| 				case reflect.TypeOf(uint64(0)), reflect.TypeOf(uint(0)):
 | |
| 					val = strconv.FormatUint(reflectedValue.Uint(), 10)
 | |
| 				case reflect.TypeOf(int(0)):
 | |
| 					val = strconv.FormatInt(reflectedValue.Int(), 10)
 | |
| 				default:
 | |
| 					val = reflectedValue.String()
 | |
| 				}
 | |
| 			}
 | |
| 			if val != "" {
 | |
| 				headers[key] = val
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return headers
 | |
| }
 | |
| 
 | |
| // merges extraHeaders into headers and returns headers
 | |
| func mergeHeaders(headers, extraHeaders map[string]string) map[string]string {
 | |
| 	for k, v := range extraHeaders {
 | |
| 		headers[k] = v
 | |
| 	}
 | |
| 	return headers
 | |
| }
 | |
| 
 | |
| func addToHeaders(h map[string]string, key, value string) map[string]string {
 | |
| 	if value != "" {
 | |
| 		h[key] = value
 | |
| 	}
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| func addTimeToHeaders(h map[string]string, key string, value *time.Time) map[string]string {
 | |
| 	if value != nil {
 | |
| 		h = addToHeaders(h, key, timeRfc1123Formatted(*value))
 | |
| 	}
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| func addTimeout(params url.Values, timeout uint) url.Values {
 | |
| 	if timeout > 0 {
 | |
| 		params.Add("timeout", fmt.Sprintf("%v", timeout))
 | |
| 	}
 | |
| 	return params
 | |
| }
 | |
| 
 | |
| func addSnapshot(params url.Values, snapshot *time.Time) url.Values {
 | |
| 	if snapshot != nil {
 | |
| 		params.Add("snapshot", timeRFC3339Formatted(*snapshot))
 | |
| 	}
 | |
| 	return params
 | |
| }
 | |
| 
 | |
| func getTimeFromHeaders(h http.Header, key string) (*time.Time, error) {
 | |
| 	var out time.Time
 | |
| 	var err error
 | |
| 	outStr := h.Get(key)
 | |
| 	if outStr != "" {
 | |
| 		out, err = time.Parse(time.RFC1123, outStr)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return &out, nil
 | |
| }
 | |
| 
 | |
| // TimeRFC1123 is an alias for time.Time needed for custom Unmarshalling
 | |
| type TimeRFC1123 time.Time
 | |
| 
 | |
| // UnmarshalXML is a custom unmarshaller that overrides the default time unmarshal which uses a different time layout.
 | |
| func (t *TimeRFC1123) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	var value string
 | |
| 	d.DecodeElement(&value, &start)
 | |
| 	parse, err := time.Parse(time.RFC1123, value)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	*t = TimeRFC1123(parse)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // MarshalXML marshals using time.RFC1123.
 | |
| func (t *TimeRFC1123) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | |
| 	return e.EncodeElement(time.Time(*t).Format(time.RFC1123), start)
 | |
| }
 | |
| 
 | |
| // returns a map of custom metadata values from the specified HTTP header
 | |
| func getMetadataFromHeaders(header http.Header) map[string]string {
 | |
| 	metadata := make(map[string]string)
 | |
| 	for k, v := range header {
 | |
| 		// Can't trust CanonicalHeaderKey() to munge case
 | |
| 		// reliably. "_" is allowed in identifiers:
 | |
| 		// https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
 | |
| 		// https://msdn.microsoft.com/library/aa664670(VS.71).aspx
 | |
| 		// http://tools.ietf.org/html/rfc7230#section-3.2
 | |
| 		// ...but "_" is considered invalid by
 | |
| 		// CanonicalMIMEHeaderKey in
 | |
| 		// https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
 | |
| 		// so k can be "X-Ms-Meta-Lol" or "x-ms-meta-lol_rofl".
 | |
| 		k = strings.ToLower(k)
 | |
| 		if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
 | |
| 			continue
 | |
| 		}
 | |
| 		// metadata["lol"] = content of the last X-Ms-Meta-Lol header
 | |
| 		k = k[len(userDefinedMetadataHeaderPrefix):]
 | |
| 		metadata[k] = v[len(v)-1]
 | |
| 	}
 | |
| 
 | |
| 	if len(metadata) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return metadata
 | |
| }
 |