github.com/aliyun/aliyun-oss-go-sdk@v3.0.2+incompatible/oss/conn.go (about)

     1  package oss
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/md5"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"hash"
    12  	"io"
    13  	"io/ioutil"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"os"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  )
    23  
    24  // Conn defines OSS Conn
    25  type Conn struct {
    26  	config *Config
    27  	url    *urlMaker
    28  	client *http.Client
    29  }
    30  
    31  var signKeyList = []string{"acl", "uploads", "location", "cors",
    32  	"logging", "website", "referer", "lifecycle",
    33  	"delete", "append", "tagging", "objectMeta",
    34  	"uploadId", "partNumber", "security-token",
    35  	"position", "img", "style", "styleName",
    36  	"replication", "replicationProgress",
    37  	"replicationLocation", "cname", "bucketInfo",
    38  	"comp", "qos", "live", "status", "vod",
    39  	"startTime", "endTime", "symlink",
    40  	"x-oss-process", "response-content-type", "x-oss-traffic-limit",
    41  	"response-content-language", "response-expires",
    42  	"response-cache-control", "response-content-disposition",
    43  	"response-content-encoding", "udf", "udfName", "udfImage",
    44  	"udfId", "udfImageDesc", "udfApplication", "comp",
    45  	"udfApplicationLog", "restore", "callback", "callback-var", "qosInfo",
    46  	"policy", "stat", "encryption", "versions", "versioning", "versionId", "requestPayment",
    47  	"x-oss-request-payer", "sequential",
    48  	"inventory", "inventoryId", "continuation-token", "asyncFetch",
    49  	"worm", "wormId", "wormExtend", "withHashContext",
    50  	"x-oss-enable-md5", "x-oss-enable-sha1", "x-oss-enable-sha256",
    51  	"x-oss-hash-ctx", "x-oss-md5-ctx", "transferAcceleration",
    52  	"regionList", "cloudboxes", "x-oss-ac-source-ip", "x-oss-ac-subnet-mask", "x-oss-ac-vpc-id", "x-oss-ac-forward-allow",
    53  	"metaQuery", "resourceGroup", "rtc", "x-oss-async-process", "responseHeader",
    54  }
    55  
    56  const (
    57  	timeFormatV4       = "20060102T150405Z"
    58  	shortTimeFormatV4  = "20060102"
    59  	signingAlgorithmV4 = "OSS4-HMAC-SHA256"
    60  )
    61  
    62  // init initializes Conn
    63  func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error {
    64  	if client == nil {
    65  		// New transport
    66  		transport := newTransport(conn, config)
    67  
    68  		// Proxy
    69  		if conn.config.IsUseProxy {
    70  			proxyURL, err := url.Parse(config.ProxyHost)
    71  			if err != nil {
    72  				return err
    73  			}
    74  			if config.IsAuthProxy {
    75  				if config.ProxyPassword != "" {
    76  					proxyURL.User = url.UserPassword(config.ProxyUser, config.ProxyPassword)
    77  				} else {
    78  					proxyURL.User = url.User(config.ProxyUser)
    79  				}
    80  			}
    81  			transport.Proxy = http.ProxyURL(proxyURL)
    82  		}
    83  		client = &http.Client{Transport: transport}
    84  		if !config.RedirectEnabled {
    85  			disableHTTPRedirect(client)
    86  		}
    87  	}
    88  
    89  	conn.config = config
    90  	conn.url = urlMaker
    91  	conn.client = client
    92  
    93  	return nil
    94  }
    95  
    96  // Do sends request and returns the response
    97  func (conn Conn) Do(method, bucketName, objectName string, params map[string]interface{}, headers map[string]string,
    98  	data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
    99  	return conn.DoWithContext(nil, method, bucketName, objectName, params, headers, data, initCRC, listener)
   100  }
   101  
   102  // DoWithContext sends request and returns the response with context
   103  func (conn Conn) DoWithContext(ctx context.Context, method, bucketName, objectName string, params map[string]interface{}, headers map[string]string,
   104  	data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
   105  	urlParams := conn.getURLParams(params)
   106  	subResource := conn.getSubResource(params)
   107  	uri := conn.url.getURL(bucketName, objectName, urlParams)
   108  
   109  	resource := ""
   110  	if conn.config.AuthVersion != AuthV4 {
   111  		resource = conn.getResource(bucketName, objectName, subResource)
   112  	} else {
   113  		resource = conn.getResourceV4(bucketName, objectName, subResource)
   114  	}
   115  
   116  	return conn.doRequest(ctx, method, uri, resource, headers, data, initCRC, listener)
   117  }
   118  
   119  // DoURL sends the request with signed URL and returns the response result.
   120  func (conn Conn) DoURL(method HTTPMethod, signedURL string, headers map[string]string,
   121  	data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
   122  	return conn.DoURLWithContext(nil, method, signedURL, headers, data, initCRC, listener)
   123  }
   124  
   125  // DoURLWithContext sends the request with signed URL and context and returns the response result.
   126  func (conn Conn) DoURLWithContext(ctx context.Context, method HTTPMethod, signedURL string, headers map[string]string,
   127  	data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
   128  	// Get URI from signedURL
   129  	uri, err := url.ParseRequestURI(signedURL)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	m := strings.ToUpper(string(method))
   135  	req := &http.Request{
   136  		Method:     m,
   137  		URL:        uri,
   138  		Proto:      "HTTP/1.1",
   139  		ProtoMajor: 1,
   140  		ProtoMinor: 1,
   141  		Header:     make(http.Header),
   142  		Host:       uri.Host,
   143  	}
   144  
   145  	if ctx != nil {
   146  		req = req.WithContext(ctx)
   147  	}
   148  	tracker := &readerTracker{completedBytes: 0}
   149  	fd, crc := conn.handleBody(req, data, initCRC, listener, tracker)
   150  	if fd != nil {
   151  		defer func() {
   152  			fd.Close()
   153  			os.Remove(fd.Name())
   154  		}()
   155  	}
   156  
   157  	if conn.config.IsAuthProxy {
   158  		auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
   159  		basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
   160  		req.Header.Set("Proxy-Authorization", basic)
   161  	}
   162  
   163  	req.Header.Set(HTTPHeaderHost, req.Host)
   164  	req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
   165  
   166  	if headers != nil {
   167  		for k, v := range headers {
   168  			req.Header.Set(k, v)
   169  		}
   170  	}
   171  
   172  	// Transfer started
   173  	event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0)
   174  	publishProgress(listener, event)
   175  
   176  	if conn.config.LogLevel >= Debug {
   177  		conn.LoggerHTTPReq(req)
   178  	}
   179  
   180  	resp, err := conn.client.Do(req)
   181  	if err != nil {
   182  		// Transfer failed
   183  		conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error())
   184  		event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0)
   185  		publishProgress(listener, event)
   186  
   187  		return nil, err
   188  	}
   189  
   190  	if conn.config.LogLevel >= Debug {
   191  		//print out http resp
   192  		conn.LoggerHTTPResp(req, resp)
   193  	}
   194  
   195  	// Transfer completed
   196  	event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0)
   197  	publishProgress(listener, event)
   198  
   199  	return conn.handleResponse(resp, crc)
   200  }
   201  
   202  func (conn Conn) getURLParams(params map[string]interface{}) string {
   203  	// Sort
   204  	keys := make([]string, 0, len(params))
   205  	for k := range params {
   206  		keys = append(keys, k)
   207  	}
   208  	sort.Strings(keys)
   209  
   210  	// Serialize
   211  	var buf bytes.Buffer
   212  	for _, k := range keys {
   213  		if buf.Len() > 0 {
   214  			buf.WriteByte('&')
   215  		}
   216  		buf.WriteString(url.QueryEscape(k))
   217  		if params[k] != nil && params[k].(string) != "" {
   218  			buf.WriteString("=" + strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1))
   219  		}
   220  	}
   221  
   222  	return buf.String()
   223  }
   224  
   225  func (conn Conn) getSubResource(params map[string]interface{}) string {
   226  	// Sort
   227  	keys := make([]string, 0, len(params))
   228  	signParams := make(map[string]string)
   229  	for k := range params {
   230  		if conn.config.AuthVersion == AuthV2 || conn.config.AuthVersion == AuthV4 {
   231  			encodedKey := url.QueryEscape(k)
   232  			keys = append(keys, encodedKey)
   233  			if params[k] != nil && params[k] != "" {
   234  				signParams[encodedKey] = strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1)
   235  			}
   236  		} else if conn.isParamSign(k) {
   237  			keys = append(keys, k)
   238  			if params[k] != nil {
   239  				signParams[k] = params[k].(string)
   240  			}
   241  		}
   242  	}
   243  	sort.Strings(keys)
   244  
   245  	// Serialize
   246  	var buf bytes.Buffer
   247  	for _, k := range keys {
   248  		if buf.Len() > 0 {
   249  			buf.WriteByte('&')
   250  		}
   251  		buf.WriteString(k)
   252  		if _, ok := signParams[k]; ok {
   253  			if signParams[k] != "" {
   254  				buf.WriteString("=" + signParams[k])
   255  			}
   256  		}
   257  	}
   258  	return buf.String()
   259  }
   260  
   261  func (conn Conn) isParamSign(paramKey string) bool {
   262  	for _, k := range signKeyList {
   263  		if paramKey == k {
   264  			return true
   265  		}
   266  	}
   267  	return false
   268  }
   269  
   270  // getResource gets canonicalized resource
   271  func (conn Conn) getResource(bucketName, objectName, subResource string) string {
   272  	if subResource != "" {
   273  		subResource = "?" + subResource
   274  	}
   275  	if bucketName == "" {
   276  		if conn.config.AuthVersion == AuthV2 {
   277  			return url.QueryEscape("/") + subResource
   278  		}
   279  		return fmt.Sprintf("/%s%s", bucketName, subResource)
   280  	}
   281  	if conn.config.AuthVersion == AuthV2 {
   282  		return url.QueryEscape("/"+bucketName+"/") + strings.Replace(url.QueryEscape(objectName), "+", "%20", -1) + subResource
   283  	}
   284  	return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource)
   285  }
   286  
   287  // getResource gets canonicalized resource
   288  func (conn Conn) getResourceV4(bucketName, objectName, subResource string) string {
   289  	if subResource != "" {
   290  		subResource = "?" + subResource
   291  	}
   292  
   293  	if bucketName == "" {
   294  		return fmt.Sprintf("/%s", subResource)
   295  	}
   296  
   297  	if objectName != "" {
   298  		objectName = url.QueryEscape(objectName)
   299  		objectName = strings.Replace(objectName, "+", "%20", -1)
   300  		objectName = strings.Replace(objectName, "%2F", "/", -1)
   301  		return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource)
   302  	}
   303  	return fmt.Sprintf("/%s/%s", bucketName, subResource)
   304  }
   305  
   306  func (conn Conn) doRequest(ctx context.Context, method string, uri *url.URL, canonicalizedResource string, headers map[string]string,
   307  	data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
   308  	method = strings.ToUpper(method)
   309  	var req *http.Request
   310  	var err error
   311  	req = &http.Request{
   312  		Method:     method,
   313  		URL:        uri,
   314  		Proto:      "HTTP/1.1",
   315  		ProtoMajor: 1,
   316  		ProtoMinor: 1,
   317  		Header:     make(http.Header),
   318  		Host:       uri.Host,
   319  	}
   320  	if ctx != nil {
   321  		req = req.WithContext(ctx)
   322  	}
   323  	tracker := &readerTracker{completedBytes: 0}
   324  	fd, crc := conn.handleBody(req, data, initCRC, listener, tracker)
   325  	if fd != nil {
   326  		defer func() {
   327  			fd.Close()
   328  			os.Remove(fd.Name())
   329  		}()
   330  	}
   331  
   332  	if conn.config.IsAuthProxy {
   333  		auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
   334  		basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
   335  		req.Header.Set("Proxy-Authorization", basic)
   336  	}
   337  
   338  	stNow := time.Now().UTC()
   339  	req.Header.Set(HTTPHeaderDate, stNow.Format(http.TimeFormat))
   340  	req.Header.Set(HTTPHeaderHost, req.Host)
   341  	req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
   342  
   343  	if conn.config.AuthVersion == AuthV4 {
   344  		req.Header.Set(HttpHeaderOssContentSha256, DefaultContentSha256)
   345  	}
   346  
   347  	var akIf Credentials
   348  	if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok {
   349  		if akIf, err = providerE.GetCredentialsE(); err != nil {
   350  			return nil, err
   351  		}
   352  	} else {
   353  		akIf = conn.config.GetCredentials()
   354  	}
   355  
   356  	if akIf.GetSecurityToken() != "" {
   357  		req.Header.Set(HTTPHeaderOssSecurityToken, akIf.GetSecurityToken())
   358  	}
   359  
   360  	if headers != nil {
   361  		for k, v := range headers {
   362  			req.Header.Set(k, v)
   363  		}
   364  	}
   365  
   366  	conn.signHeader(req, canonicalizedResource, akIf)
   367  
   368  	// Transfer started
   369  	event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0)
   370  	publishProgress(listener, event)
   371  
   372  	if conn.config.LogLevel >= Debug {
   373  		conn.LoggerHTTPReq(req)
   374  	}
   375  
   376  	resp, err := conn.client.Do(req)
   377  
   378  	if err != nil {
   379  		conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error())
   380  		// Transfer failed
   381  		event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0)
   382  		publishProgress(listener, event)
   383  		return nil, err
   384  	}
   385  
   386  	if conn.config.LogLevel >= Debug {
   387  		//print out http resp
   388  		conn.LoggerHTTPResp(req, resp)
   389  	}
   390  
   391  	// Transfer completed
   392  	event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0)
   393  	publishProgress(listener, event)
   394  
   395  	return conn.handleResponse(resp, crc)
   396  }
   397  
   398  func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) (string, error) {
   399  	var akIf Credentials
   400  	var err error
   401  	if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok {
   402  		if akIf, err = providerE.GetCredentialsE(); err != nil {
   403  			return "", err
   404  		}
   405  	} else {
   406  		akIf = conn.config.GetCredentials()
   407  	}
   408  
   409  	m := strings.ToUpper(string(method))
   410  	req := &http.Request{
   411  		Method: m,
   412  		Header: make(http.Header),
   413  	}
   414  
   415  	if conn.config.IsAuthProxy {
   416  		auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
   417  		basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
   418  		req.Header.Set("Proxy-Authorization", basic)
   419  	}
   420  
   421  	if conn.config.AuthVersion == AuthV4 {
   422  		if akIf.GetSecurityToken() != "" {
   423  			params[HTTPParamOssSecurityToken] = akIf.GetSecurityToken()
   424  		}
   425  
   426  		if headers != nil {
   427  			for k, v := range headers {
   428  				req.Header.Set(k, v)
   429  			}
   430  		}
   431  
   432  		now := time.Now().UTC()
   433  		expires := expiration - now.Unix()
   434  		product := conn.config.GetSignProduct()
   435  		region := conn.config.GetSignRegion()
   436  		strDay := now.Format(shortTimeFormatV4)
   437  		additionalList, _ := conn.getAdditionalHeaderKeys(req)
   438  
   439  		params[HTTPParamSignatureVersion] = signingAlgorithmV4
   440  		params[HTTPParamCredential] = fmt.Sprintf("%s/%s/%s/%s/aliyun_v4_request", akIf.GetAccessKeyID(), strDay, region, product)
   441  		params[HTTPParamDate] = now.Format(timeFormatV4)
   442  		params[HTTPParamExpiresV2] = strconv.FormatInt(expires, 10)
   443  		if len(additionalList) > 0 {
   444  			params[HTTPParamAdditionalHeadersV2] = strings.Join(additionalList, ";")
   445  		}
   446  
   447  		subResource := conn.getSubResource(params)
   448  		canonicalizedResource := conn.getResourceV4(bucketName, objectName, subResource)
   449  		authorizationStr := conn.getSignedStrV4(req, canonicalizedResource, akIf.GetAccessKeySecret(), &now)
   450  		params[HTTPParamSignatureV2] = authorizationStr
   451  	} else {
   452  		if akIf.GetSecurityToken() != "" {
   453  			params[HTTPParamSecurityToken] = akIf.GetSecurityToken()
   454  		}
   455  
   456  		req.Header.Set(HTTPHeaderDate, strconv.FormatInt(expiration, 10))
   457  
   458  		if headers != nil {
   459  			for k, v := range headers {
   460  				req.Header.Set(k, v)
   461  			}
   462  		}
   463  
   464  		if conn.config.AuthVersion == AuthV2 {
   465  			params[HTTPParamSignatureVersion] = "OSS2"
   466  			params[HTTPParamExpiresV2] = strconv.FormatInt(expiration, 10)
   467  			params[HTTPParamAccessKeyIDV2] = conn.config.AccessKeyID
   468  			additionalList, _ := conn.getAdditionalHeaderKeys(req)
   469  			if len(additionalList) > 0 {
   470  				params[HTTPParamAdditionalHeadersV2] = strings.Join(additionalList, ";")
   471  			}
   472  		}
   473  
   474  		subResource := conn.getSubResource(params)
   475  		canonicalizedResource := conn.getResource(bucketName, objectName, subResource)
   476  		signedStr := conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret())
   477  
   478  		if conn.config.AuthVersion == AuthV1 {
   479  			params[HTTPParamExpires] = strconv.FormatInt(expiration, 10)
   480  			params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID()
   481  			params[HTTPParamSignature] = signedStr
   482  		} else if conn.config.AuthVersion == AuthV2 {
   483  			params[HTTPParamSignatureV2] = signedStr
   484  		}
   485  	}
   486  
   487  	urlParams := conn.getURLParams(params)
   488  	return conn.url.getSignURL(bucketName, objectName, urlParams), nil
   489  }
   490  
   491  func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string {
   492  	params := map[string]interface{}{}
   493  	if playlistName != "" {
   494  		params[HTTPParamPlaylistName] = playlistName
   495  	}
   496  	expireStr := strconv.FormatInt(expiration, 10)
   497  	params[HTTPParamExpires] = expireStr
   498  
   499  	akIf := conn.config.GetCredentials()
   500  	if akIf.GetAccessKeyID() != "" {
   501  		params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID()
   502  		if akIf.GetSecurityToken() != "" {
   503  			params[HTTPParamSecurityToken] = akIf.GetSecurityToken()
   504  		}
   505  		signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, akIf.GetAccessKeySecret(), params)
   506  		params[HTTPParamSignature] = signedStr
   507  	}
   508  
   509  	urlParams := conn.getURLParams(params)
   510  	return conn.url.getSignRtmpURL(bucketName, channelName, urlParams)
   511  }
   512  
   513  // handleBody handles request body
   514  func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
   515  	listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) {
   516  	var file *os.File
   517  	var crc hash.Hash64
   518  	reader := body
   519  	readerLen, err := GetReaderLen(reader)
   520  	if err == nil {
   521  		req.ContentLength = readerLen
   522  	}
   523  	req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10))
   524  
   525  	// MD5
   526  	if body != nil && conn.config.IsEnableMD5 && req.Header.Get(HTTPHeaderContentMD5) == "" {
   527  		md5 := ""
   528  		reader, md5, file, _ = calcMD5(body, req.ContentLength, conn.config.MD5Threshold)
   529  		req.Header.Set(HTTPHeaderContentMD5, md5)
   530  	}
   531  
   532  	// CRC
   533  	if reader != nil && conn.config.IsEnableCRC {
   534  		crc = NewCRC(CrcTable(), initCRC)
   535  		reader = TeeReader(reader, crc, req.ContentLength, listener, tracker)
   536  	}
   537  
   538  	// HTTP body
   539  	rc, ok := reader.(io.ReadCloser)
   540  	if !ok && reader != nil {
   541  		rc = ioutil.NopCloser(reader)
   542  	}
   543  
   544  	if conn.isUploadLimitReq(req) {
   545  		limitReader := &LimitSpeedReader{
   546  			reader:     rc,
   547  			ossLimiter: conn.config.UploadLimiter,
   548  		}
   549  		req.Body = limitReader
   550  	} else {
   551  		req.Body = rc
   552  	}
   553  	return file, crc
   554  }
   555  
   556  // isUploadLimitReq: judge limit upload speed or not
   557  func (conn Conn) isUploadLimitReq(req *http.Request) bool {
   558  	if conn.config.UploadLimitSpeed == 0 || conn.config.UploadLimiter == nil {
   559  		return false
   560  	}
   561  
   562  	if req.Method != "GET" && req.Method != "DELETE" && req.Method != "HEAD" {
   563  		if req.ContentLength > 0 {
   564  			return true
   565  		}
   566  	}
   567  	return false
   568  }
   569  
   570  func tryGetFileSize(f *os.File) int64 {
   571  	fInfo, _ := f.Stat()
   572  	return fInfo.Size()
   573  }
   574  
   575  // handleResponse handles response
   576  func (conn Conn) handleResponse(resp *http.Response, crc hash.Hash64) (*Response, error) {
   577  	var cliCRC uint64
   578  	var srvCRC uint64
   579  
   580  	statusCode := resp.StatusCode
   581  	if statusCode/100 != 2 {
   582  		if statusCode >= 400 && statusCode <= 505 {
   583  			// 4xx and 5xx indicate that the operation has error occurred
   584  			var respBody []byte
   585  			var errorXml []byte
   586  			respBody, err := readResponseBody(resp)
   587  			if err != nil {
   588  				return nil, err
   589  			}
   590  			errorXml = respBody
   591  			if len(respBody) == 0 && len(resp.Header.Get(HTTPHeaderOssErr)) > 0 {
   592  				errorXml, err = base64.StdEncoding.DecodeString(resp.Header.Get(HTTPHeaderOssErr))
   593  				if err != nil {
   594  					errorXml = respBody
   595  				}
   596  			}
   597  			if len(errorXml) == 0 {
   598  				err = ServiceError{
   599  					StatusCode: statusCode,
   600  					RequestID:  resp.Header.Get(HTTPHeaderOssRequestID),
   601  					Ec:         resp.Header.Get(HTTPHeaderOssEc),
   602  				}
   603  			} else {
   604  				srvErr, errIn := serviceErrFromXML(errorXml, resp.StatusCode,
   605  					resp.Header.Get(HTTPHeaderOssRequestID))
   606  				if errIn != nil { // error unmarshal the error response
   607  					if len(resp.Header.Get(HTTPHeaderOssEc)) > 0 {
   608  						err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s, ec = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID), resp.Header.Get(HTTPHeaderOssEc))
   609  					} else {
   610  						err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID))
   611  					}
   612  				} else {
   613  					err = srvErr
   614  				}
   615  			}
   616  			return &Response{
   617  				StatusCode: resp.StatusCode,
   618  				Headers:    resp.Header,
   619  				Body:       ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body
   620  			}, err
   621  		} else if statusCode >= 300 && statusCode <= 307 {
   622  			// OSS use 3xx, but response has no body
   623  			err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status)
   624  			return &Response{
   625  				StatusCode: resp.StatusCode,
   626  				Headers:    resp.Header,
   627  				Body:       resp.Body,
   628  			}, err
   629  		} else {
   630  			// (0,300) [308,400) [506,)
   631  			// Other extended http StatusCode
   632  			var respBody []byte
   633  			var errorXml []byte
   634  			respBody, err := readResponseBody(resp)
   635  			if err != nil {
   636  				return &Response{StatusCode: resp.StatusCode, Headers: resp.Header, Body: ioutil.NopCloser(bytes.NewReader(respBody))}, err
   637  			}
   638  			errorXml = respBody
   639  			if len(respBody) == 0 && len(resp.Header.Get(HTTPHeaderOssErr)) > 0 {
   640  				errorXml, err = base64.StdEncoding.DecodeString(resp.Header.Get(HTTPHeaderOssErr))
   641  				if err != nil {
   642  					errorXml = respBody
   643  				}
   644  			}
   645  			if len(errorXml) == 0 {
   646  				err = ServiceError{
   647  					StatusCode: statusCode,
   648  					RequestID:  resp.Header.Get(HTTPHeaderOssRequestID),
   649  					Ec:         resp.Header.Get(HTTPHeaderOssEc),
   650  				}
   651  			} else {
   652  				srvErr, errIn := serviceErrFromXML(errorXml, resp.StatusCode,
   653  					resp.Header.Get(HTTPHeaderOssRequestID))
   654  				if errIn != nil { // error unmarshal the error response
   655  					if len(resp.Header.Get(HTTPHeaderOssEc)) > 0 {
   656  						err = fmt.Errorf("unknown response body, status = %s, RequestId = %s, ec = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID), resp.Header.Get(HTTPHeaderOssEc))
   657  					} else {
   658  						err = fmt.Errorf("unknown response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID))
   659  					}
   660  				} else {
   661  					err = srvErr
   662  				}
   663  			}
   664  			return &Response{
   665  				StatusCode: resp.StatusCode,
   666  				Headers:    resp.Header,
   667  				Body:       ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body
   668  			}, err
   669  		}
   670  	} else {
   671  		if conn.config.IsEnableCRC && crc != nil {
   672  			cliCRC = crc.Sum64()
   673  		}
   674  		srvCRC, _ = strconv.ParseUint(resp.Header.Get(HTTPHeaderOssCRC64), 10, 64)
   675  
   676  		realBody := resp.Body
   677  		if conn.isDownloadLimitResponse(resp) {
   678  			limitReader := &LimitSpeedReader{
   679  				reader:     realBody,
   680  				ossLimiter: conn.config.DownloadLimiter,
   681  			}
   682  			realBody = limitReader
   683  		}
   684  
   685  		// 2xx, successful
   686  		return &Response{
   687  			StatusCode: resp.StatusCode,
   688  			Headers:    resp.Header,
   689  			Body:       realBody,
   690  			ClientCRC:  cliCRC,
   691  			ServerCRC:  srvCRC,
   692  		}, nil
   693  	}
   694  }
   695  
   696  // isUploadLimitReq: judge limit upload speed or not
   697  func (conn Conn) isDownloadLimitResponse(resp *http.Response) bool {
   698  	if resp == nil || conn.config.DownloadLimitSpeed == 0 || conn.config.DownloadLimiter == nil {
   699  		return false
   700  	}
   701  
   702  	if strings.EqualFold(resp.Request.Method, "GET") {
   703  		return true
   704  	}
   705  	return false
   706  }
   707  
   708  // LoggerHTTPReq Print the header information of the http request
   709  func (conn Conn) LoggerHTTPReq(req *http.Request) {
   710  	var logBuffer bytes.Buffer
   711  	logBuffer.WriteString(fmt.Sprintf("[Req:%p]Method:%s\t", req, req.Method))
   712  	logBuffer.WriteString(fmt.Sprintf("Host:%s\t", req.URL.Host))
   713  	logBuffer.WriteString(fmt.Sprintf("Path:%s\t", req.URL.Path))
   714  	logBuffer.WriteString(fmt.Sprintf("Query:%s\t", req.URL.RawQuery))
   715  	logBuffer.WriteString(fmt.Sprintf("Header info:"))
   716  
   717  	for k, v := range req.Header {
   718  		var valueBuffer bytes.Buffer
   719  		for j := 0; j < len(v); j++ {
   720  			if j > 0 {
   721  				valueBuffer.WriteString(" ")
   722  			}
   723  			valueBuffer.WriteString(v[j])
   724  		}
   725  		logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String()))
   726  	}
   727  	conn.config.WriteLog(Debug, "%s\n", logBuffer.String())
   728  }
   729  
   730  // LoggerHTTPResp Print Response to http request
   731  func (conn Conn) LoggerHTTPResp(req *http.Request, resp *http.Response) {
   732  	var logBuffer bytes.Buffer
   733  	logBuffer.WriteString(fmt.Sprintf("[Resp:%p]StatusCode:%d\t", req, resp.StatusCode))
   734  	logBuffer.WriteString(fmt.Sprintf("Header info:"))
   735  	for k, v := range resp.Header {
   736  		var valueBuffer bytes.Buffer
   737  		for j := 0; j < len(v); j++ {
   738  			if j > 0 {
   739  				valueBuffer.WriteString(" ")
   740  			}
   741  			valueBuffer.WriteString(v[j])
   742  		}
   743  		logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String()))
   744  	}
   745  	conn.config.WriteLog(Debug, "%s\n", logBuffer.String())
   746  }
   747  
   748  func calcMD5(body io.Reader, contentLen, md5Threshold int64) (reader io.Reader, b64 string, tempFile *os.File, err error) {
   749  	if contentLen == 0 || contentLen > md5Threshold {
   750  		// Huge body, use temporary file
   751  		tempFile, err = ioutil.TempFile(os.TempDir(), TempFilePrefix)
   752  		if tempFile != nil {
   753  			io.Copy(tempFile, body)
   754  			tempFile.Seek(0, os.SEEK_SET)
   755  			md5 := md5.New()
   756  			io.Copy(md5, tempFile)
   757  			sum := md5.Sum(nil)
   758  			b64 = base64.StdEncoding.EncodeToString(sum[:])
   759  			tempFile.Seek(0, os.SEEK_SET)
   760  			reader = tempFile
   761  		}
   762  	} else {
   763  		// Small body, use memory
   764  		buf, _ := ioutil.ReadAll(body)
   765  		sum := md5.Sum(buf)
   766  		b64 = base64.StdEncoding.EncodeToString(sum[:])
   767  		reader = bytes.NewReader(buf)
   768  	}
   769  	return
   770  }
   771  
   772  func readResponseBody(resp *http.Response) ([]byte, error) {
   773  	defer resp.Body.Close()
   774  	out, err := ioutil.ReadAll(resp.Body)
   775  	if err == io.EOF {
   776  		err = nil
   777  	}
   778  	return out, err
   779  }
   780  
   781  func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) {
   782  	var storageErr ServiceError
   783  
   784  	if err := xml.Unmarshal(body, &storageErr); err != nil {
   785  		return storageErr, err
   786  	}
   787  
   788  	storageErr.StatusCode = statusCode
   789  	storageErr.RequestID = requestID
   790  	storageErr.RawMessage = string(body)
   791  	return storageErr, nil
   792  }
   793  
   794  func xmlUnmarshal(body io.Reader, v interface{}) error {
   795  	data, err := ioutil.ReadAll(body)
   796  	if err != nil {
   797  		return err
   798  	}
   799  	return xml.Unmarshal(data, v)
   800  }
   801  
   802  func jsonUnmarshal(body io.Reader, v interface{}) error {
   803  	data, err := ioutil.ReadAll(body)
   804  	if err != nil {
   805  		return err
   806  	}
   807  	return json.Unmarshal(data, v)
   808  }
   809  
   810  // timeoutConn handles HTTP timeout
   811  type timeoutConn struct {
   812  	conn        net.Conn
   813  	timeout     time.Duration
   814  	longTimeout time.Duration
   815  }
   816  
   817  func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn {
   818  	conn.SetReadDeadline(time.Now().Add(longTimeout))
   819  	return &timeoutConn{
   820  		conn:        conn,
   821  		timeout:     timeout,
   822  		longTimeout: longTimeout,
   823  	}
   824  }
   825  
   826  func (c *timeoutConn) Read(b []byte) (n int, err error) {
   827  	c.SetReadDeadline(time.Now().Add(c.timeout))
   828  	n, err = c.conn.Read(b)
   829  	c.SetReadDeadline(time.Now().Add(c.longTimeout))
   830  	return n, err
   831  }
   832  
   833  func (c *timeoutConn) Write(b []byte) (n int, err error) {
   834  	c.SetWriteDeadline(time.Now().Add(c.timeout))
   835  	n, err = c.conn.Write(b)
   836  	c.SetReadDeadline(time.Now().Add(c.longTimeout))
   837  	return n, err
   838  }
   839  
   840  func (c *timeoutConn) Close() error {
   841  	return c.conn.Close()
   842  }
   843  
   844  func (c *timeoutConn) LocalAddr() net.Addr {
   845  	return c.conn.LocalAddr()
   846  }
   847  
   848  func (c *timeoutConn) RemoteAddr() net.Addr {
   849  	return c.conn.RemoteAddr()
   850  }
   851  
   852  func (c *timeoutConn) SetDeadline(t time.Time) error {
   853  	return c.conn.SetDeadline(t)
   854  }
   855  
   856  func (c *timeoutConn) SetReadDeadline(t time.Time) error {
   857  	return c.conn.SetReadDeadline(t)
   858  }
   859  
   860  func (c *timeoutConn) SetWriteDeadline(t time.Time) error {
   861  	return c.conn.SetWriteDeadline(t)
   862  }
   863  
   864  // UrlMaker builds URL and resource
   865  const (
   866  	urlTypeCname     = 1
   867  	urlTypeIP        = 2
   868  	urlTypeAliyun    = 3
   869  	urlTypePathStyle = 4
   870  )
   871  
   872  type urlMaker struct {
   873  	Scheme  string // HTTP or HTTPS
   874  	NetLoc  string // Host or IP
   875  	Type    int    // 1 CNAME, 2 IP, 3 ALIYUN
   876  	IsProxy bool   // Proxy
   877  }
   878  
   879  // Init parses endpoint
   880  func (um *urlMaker) Init(endpoint string, isCname bool, isProxy bool) error {
   881  	return um.InitExt(endpoint, isCname, isProxy, false)
   882  }
   883  
   884  // InitExt parses endpoint
   885  func (um *urlMaker) InitExt(endpoint string, isCname bool, isProxy bool, isPathStyle bool) error {
   886  	if strings.HasPrefix(endpoint, "http://") {
   887  		um.Scheme = "http"
   888  		um.NetLoc = endpoint[len("http://"):]
   889  	} else if strings.HasPrefix(endpoint, "https://") {
   890  		um.Scheme = "https"
   891  		um.NetLoc = endpoint[len("https://"):]
   892  	} else {
   893  		um.Scheme = "http"
   894  		um.NetLoc = endpoint
   895  	}
   896  
   897  	//use url.Parse() to get real host
   898  	strUrl := um.Scheme + "://" + um.NetLoc
   899  	url, err := url.Parse(strUrl)
   900  	if err != nil {
   901  		return err
   902  	}
   903  
   904  	um.NetLoc = url.Host
   905  	host, _, err := net.SplitHostPort(um.NetLoc)
   906  	if err != nil {
   907  		host = um.NetLoc
   908  		if len(host) > 0 && host[0] == '[' && host[len(host)-1] == ']' {
   909  			host = host[1 : len(host)-1]
   910  		}
   911  	}
   912  
   913  	ip := net.ParseIP(host)
   914  	if ip != nil {
   915  		um.Type = urlTypeIP
   916  	} else if isCname {
   917  		um.Type = urlTypeCname
   918  	} else if isPathStyle {
   919  		um.Type = urlTypePathStyle
   920  	} else {
   921  		um.Type = urlTypeAliyun
   922  	}
   923  	um.IsProxy = isProxy
   924  
   925  	return nil
   926  }
   927  
   928  // getURL gets URL
   929  func (um urlMaker) getURL(bucket, object, params string) *url.URL {
   930  	host, path := um.buildURL(bucket, object)
   931  	addr := ""
   932  	if params == "" {
   933  		addr = fmt.Sprintf("%s://%s%s", um.Scheme, host, path)
   934  	} else {
   935  		addr = fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
   936  	}
   937  	uri, _ := url.ParseRequestURI(addr)
   938  	return uri
   939  }
   940  
   941  // getSignURL gets sign URL
   942  func (um urlMaker) getSignURL(bucket, object, params string) string {
   943  	host, path := um.buildURL(bucket, object)
   944  	return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params)
   945  }
   946  
   947  // getSignRtmpURL Build Sign Rtmp URL
   948  func (um urlMaker) getSignRtmpURL(bucket, channelName, params string) string {
   949  	host, path := um.buildURL(bucket, "live")
   950  
   951  	channelName = url.QueryEscape(channelName)
   952  	channelName = strings.Replace(channelName, "+", "%20", -1)
   953  
   954  	return fmt.Sprintf("rtmp://%s%s/%s?%s", host, path, channelName, params)
   955  }
   956  
   957  // buildURL builds URL
   958  func (um urlMaker) buildURL(bucket, object string) (string, string) {
   959  	var host = ""
   960  	var path = ""
   961  
   962  	object = url.QueryEscape(object)
   963  	object = strings.Replace(object, "+", "%20", -1)
   964  
   965  	if um.Type == urlTypeCname {
   966  		host = um.NetLoc
   967  		path = "/" + object
   968  	} else if um.Type == urlTypeIP || um.Type == urlTypePathStyle {
   969  		if bucket == "" {
   970  			host = um.NetLoc
   971  			path = "/"
   972  		} else {
   973  			host = um.NetLoc
   974  			path = fmt.Sprintf("/%s/%s", bucket, object)
   975  		}
   976  	} else {
   977  		if bucket == "" {
   978  			host = um.NetLoc
   979  			path = "/"
   980  		} else {
   981  			host = bucket + "." + um.NetLoc
   982  			path = "/" + object
   983  		}
   984  	}
   985  
   986  	return host, path
   987  }
   988  
   989  // buildURL builds URL
   990  func (um urlMaker) buildURLV4(bucket, object string) (string, string) {
   991  	var host = ""
   992  	var path = ""
   993  
   994  	object = url.QueryEscape(object)
   995  	object = strings.Replace(object, "+", "%20", -1)
   996  
   997  	// no escape /
   998  	object = strings.Replace(object, "%2F", "/", -1)
   999  
  1000  	if um.Type == urlTypeCname {
  1001  		host = um.NetLoc
  1002  		path = "/" + object
  1003  	} else if um.Type == urlTypeIP || um.Type == urlTypePathStyle {
  1004  		if bucket == "" {
  1005  			host = um.NetLoc
  1006  			path = "/"
  1007  		} else {
  1008  			host = um.NetLoc
  1009  			path = fmt.Sprintf("/%s/%s", bucket, object)
  1010  		}
  1011  	} else {
  1012  		if bucket == "" {
  1013  			host = um.NetLoc
  1014  			path = "/"
  1015  		} else {
  1016  			host = bucket + "." + um.NetLoc
  1017  			path = fmt.Sprintf("/%s/%s", bucket, object)
  1018  		}
  1019  	}
  1020  	return host, path
  1021  }