client.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. package tos
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/sha256"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "encoding/json"
  9. "fmt"
  10. "net"
  11. "net/http"
  12. "net/url"
  13. "runtime"
  14. "strconv"
  15. "strings"
  16. "time"
  17. )
  18. const (
  19. signPolicyDate = "x-tos-date"
  20. signPolicyCredential = "x-tos-credential"
  21. signPolicyAlgorithm = "x-tos-algorithm"
  22. signPolicySecurityToken = "x-tos-security-token"
  23. signPolicyExpiration = "expiration"
  24. signConditionContentLengthRange = "content-length-range"
  25. signConditionBucket = "bucket"
  26. signConditionKey = "key"
  27. signConditions = "conditions"
  28. maxPreSignExpires = 604800 // 7 day
  29. defaultSignExpires = 3600 // 1 hour
  30. )
  31. // Client TOS Client
  32. // use NewClient to create a new Client
  33. //
  34. // example:
  35. // client, err := NewClient(endpoint, WithCredentials(credentials), WithRegion(region))
  36. // if err != nil {
  37. // // ...
  38. // }
  39. // // do something
  40. //
  41. // if you only access the public bucket:
  42. // client, err := NewClient(endpoint)
  43. // // do something
  44. //
  45. // Deprecated: use ClientV2 instead
  46. type Client struct {
  47. scheme string
  48. host string
  49. urlMode urlMode
  50. userAgent string
  51. credentials Credentials // nullable
  52. signer Signer // nullable
  53. transport Transport
  54. recognizer ContentTypeRecognizer
  55. config Config
  56. retry *retryer
  57. enableCRC bool
  58. logger Logger
  59. isCustomDomain bool
  60. }
  61. // ClientV2 TOS ClientV2
  62. // use NewClientV2 to create a new ClientV2
  63. //
  64. // example:
  65. // client, err := NewClientV2(endpoint, WithCredentials(credentials), WithRegion(region))
  66. // if err != nil {
  67. // // ...
  68. // }
  69. // // do something
  70. //
  71. // if you only access the public bucket:
  72. // client, err := NewClientV2(endpoint)
  73. // // do something
  74. //
  75. type ClientV2 struct {
  76. Client
  77. baseClient *baseClient
  78. }
  79. func (cli *ClientV2) Close() {
  80. if t, ok := cli.transport.(*DefaultTransport); ok {
  81. if h, ok := t.client.Transport.(*http.Transport); ok {
  82. h.CloseIdleConnections()
  83. }
  84. }
  85. }
  86. func (cli *ClientV2) SetHTTPTransport(transport http.RoundTripper) {
  87. cli.transport = newDefaultTranposrtWithHTTPTransport(transport)
  88. }
  89. type ClientOption func(*Client)
  90. // WithCredentials set Credentials
  91. //
  92. // see StaticCredentials, WithoutSecretKeyCredentials and FederationCredentials
  93. func WithCredentials(credentials Credentials) ClientOption {
  94. return func(client *Client) {
  95. client.credentials = credentials
  96. }
  97. }
  98. // WithEnableVerifySSL set whether a client verifies the server's certificate chain and host name.
  99. func WithEnableVerifySSL(enable bool) ClientOption {
  100. skip := !enable
  101. return func(client *Client) {
  102. client.config.TransportConfig.InsecureSkipVerify = skip
  103. }
  104. }
  105. // WithRequestTimeout set timeout for single http request
  106. func WithRequestTimeout(timeout time.Duration) ClientOption {
  107. return func(client *Client) {
  108. client.config.TransportConfig.ResponseHeaderTimeout = timeout
  109. }
  110. }
  111. //
  112. // WithLogger sets the tos sdk logger
  113. //
  114. func WithLogger(logger Logger) ClientOption {
  115. return func(client *Client) {
  116. client.logger = logger
  117. }
  118. }
  119. // WithConnectionTimeout set timeout for constructing connection
  120. func WithConnectionTimeout(timeout time.Duration) ClientOption {
  121. return func(client *Client) {
  122. client.config.TransportConfig.DialTimeout = timeout
  123. }
  124. }
  125. // WithProxy set http Proxy for tos client
  126. func WithProxy(proxy *Proxy) ClientOption {
  127. return func(client *Client) {
  128. client.config.TransportConfig.Proxy = proxy
  129. }
  130. }
  131. // WithMaxConnections set maximum number of http connections
  132. func WithMaxConnections(max int) ClientOption {
  133. return func(client *Client) {
  134. client.config.TransportConfig.MaxIdleConns = max
  135. client.config.TransportConfig.MaxIdleConnsPerHost = max
  136. client.config.TransportConfig.MaxConnsPerHost = max
  137. }
  138. }
  139. // WithIdleConnTimeout set max idle time of a http connection
  140. func WithIdleConnTimeout(timeout time.Duration) ClientOption {
  141. return func(client *Client) {
  142. client.config.TransportConfig.IdleConnTimeout = timeout
  143. }
  144. }
  145. // WithUserAgentSuffix set suffix of user-agent
  146. func WithUserAgentSuffix(suffix string) ClientOption {
  147. return func(client *Client) {
  148. client.userAgent = strings.Join([]string{client.userAgent, suffix}, " ")
  149. }
  150. }
  151. // WithDNSCacheTime set dnsCacheTime in Minute
  152. func WithDNSCacheTime(dnsCacheTime int) ClientOption {
  153. return func(client *Client) {
  154. client.config.TransportConfig.DNSCacheTime = time.Minute * time.Duration(dnsCacheTime)
  155. }
  156. }
  157. // WithEnableCRC set if check crc after uploading object.
  158. // Checking crc is enabled by default.
  159. func WithEnableCRC(enableCRC bool) ClientOption {
  160. return func(client *Client) {
  161. client.enableCRC = enableCRC
  162. }
  163. }
  164. // // WithMaxRetryCount set MaxRetryCount
  165. func WithMaxRetryCount(retryCount int) ClientOption {
  166. return func(client *Client) {
  167. if client.retry != nil {
  168. client.retry.SetBackoff(exponentialBackoff(retryCount, DefaultRetryBackoffBase))
  169. }
  170. }
  171. }
  172. func WithCustomDomain(isCustomDomain bool) ClientOption {
  173. return func(client *Client) {
  174. client.isCustomDomain = isCustomDomain
  175. }
  176. }
  177. // WithTransport set Transport
  178. //
  179. // Deprecated: this function is Deprecated.
  180. // If you want to set http.Transport use WithHTTPTransport instead
  181. func WithTransport(transport Transport) ClientOption {
  182. return func(client *Client) {
  183. client.transport = transport
  184. }
  185. }
  186. // WithHTTPTransport set Transport of http.Client
  187. func WithHTTPTransport(transport http.RoundTripper) ClientOption {
  188. return func(client *Client) {
  189. client.transport = newDefaultTranposrtWithHTTPTransport(transport)
  190. }
  191. }
  192. // WithTransportConfig set TransportConfig
  193. func WithTransportConfig(config *TransportConfig) ClientOption {
  194. return func(client *Client) {
  195. // client.config never be nil
  196. client.config.TransportConfig = *config
  197. }
  198. }
  199. // WithSocketTimeout set read-write timeout
  200. func WithSocketTimeout(readTimeout, writeTimeout time.Duration) ClientOption {
  201. return func(client *Client) {
  202. client.config.TransportConfig.ReadTimeout = readTimeout
  203. client.config.TransportConfig.WriteTimeout = writeTimeout
  204. }
  205. }
  206. // WithRegion set region
  207. func WithRegion(region string) ClientOption {
  208. return func(client *Client) {
  209. // client.config never be nil
  210. client.config.Region = region
  211. if endpoint, ok := SupportedRegion()[region]; ok {
  212. if len(client.config.Endpoint) == 0 {
  213. client.config.Endpoint = endpoint
  214. }
  215. }
  216. }
  217. }
  218. // WithSigner for self-defined Signer
  219. func WithSigner(signer Signer) ClientOption {
  220. return func(client *Client) {
  221. client.signer = signer
  222. }
  223. }
  224. // WithPathAccessMode url mode is path model or default mode
  225. //
  226. // Deprecated: This option is deprecated. Setting PathAccessMode will be ignored silently.
  227. func WithPathAccessMode(pathAccessMode bool) ClientOption {
  228. return func(client *Client) {
  229. }
  230. }
  231. // WithAutoRecognizeContentType set to recognize Content-Type or not, the default is enabled.
  232. func WithAutoRecognizeContentType(enable bool) ClientOption {
  233. return func(client *Client) {
  234. if enable {
  235. client.recognizer = ExtensionBasedContentTypeRecognizer{}
  236. } else {
  237. client.recognizer = EmptyContentTypeRecognizer{}
  238. }
  239. }
  240. }
  241. // WithContentTypeRecognizer set ContentTypeRecognizer to recognize Content-Type,
  242. // the default is ExtensionBasedContentTypeRecognizer
  243. func WithContentTypeRecognizer(recognizer ContentTypeRecognizer) ClientOption {
  244. return func(client *Client) {
  245. client.recognizer = recognizer
  246. }
  247. }
  248. func schemeHost(endpoint string) (scheme string, host string, urlMode urlMode) {
  249. if strings.HasPrefix(endpoint, "https://") {
  250. scheme = "https"
  251. host = endpoint[len("https://"):]
  252. } else if strings.HasPrefix(endpoint, "http://") {
  253. scheme = "http"
  254. host = endpoint[len("http://"):]
  255. } else {
  256. scheme = "https"
  257. host = endpoint
  258. }
  259. urlMode = urlModeDefault
  260. hostWithoutPort, _, _ := net.SplitHostPort(host)
  261. if net.ParseIP(host) != nil || net.ParseIP(hostWithoutPort) != nil {
  262. urlMode = urlModePath
  263. }
  264. return scheme, host, urlMode
  265. }
  266. func initClient(client *Client, endpoint string, options ...ClientOption) error {
  267. client.config.Endpoint = endpoint
  268. for _, option := range options {
  269. option(client)
  270. }
  271. client.scheme, client.host, client.urlMode = schemeHost(client.config.Endpoint)
  272. if client.transport == nil {
  273. transport := NewDefaultTransport(&client.config.TransportConfig)
  274. transport.WithDefaultTransportLogger(client.logger)
  275. client.transport = transport
  276. }
  277. if cred := client.credentials; cred != nil && client.signer == nil {
  278. if len(client.config.Region) == 0 {
  279. if region, ok := SupportedEndpoint()[client.host]; ok {
  280. client.config.Region = region
  281. } else {
  282. return newTosClientError("tos: missing Region option", nil)
  283. }
  284. }
  285. signer := NewSignV4(cred, client.config.Region)
  286. signer.WithSignLogger(client.logger)
  287. client.signer = signer
  288. }
  289. return nil
  290. }
  291. // NewClient create a new Tos Client
  292. // endpoint: access endpoint
  293. // options: WithCredentials set Credentials
  294. // WithRegion set region, this is required if WithCredentials is used
  295. // WithSocketTimeout set read-write timeout
  296. // WithTransportConfig set TransportConfig
  297. // WithTransport set self-defined Transport
  298. func NewClient(endpoint string, options ...ClientOption) (*Client, error) {
  299. client := Client{
  300. recognizer: ExtensionBasedContentTypeRecognizer{},
  301. config: defaultConfig(),
  302. userAgent: fmt.Sprintf("tos-go-sdk/%s (%s/%s;%s)", Version, runtime.GOOS, runtime.GOARCH, runtime.Version()),
  303. retry: newRetryer([]time.Duration{}),
  304. }
  305. client.retry.SetJitter(0.25)
  306. err := initClient(&client, endpoint, options...)
  307. if err != nil {
  308. return nil, err
  309. }
  310. return &client, nil
  311. }
  312. // NewClientV2 create a new Tos ClientV2
  313. // endpoint: access endpoint
  314. // options: WithCredentials set Credentials
  315. // WithRegion set region, this is required if WithCredentials is used.
  316. // If Region is supported and the Endpoint parameter is not set, the Endpoint will be resolved automatically
  317. // WithSocketTimeout set read-write timeout
  318. // WithTransportConfig set TransportConfig
  319. // WithTransport set self-defined Transport
  320. // WithLogger set self-defined Logger
  321. // WithEnableCRC set CRC switch.
  322. // WithMaxRetryCount set Max Retry Count
  323. func NewClientV2(endpoint string, options ...ClientOption) (*ClientV2, error) {
  324. if strings.Contains(endpoint, "s3") {
  325. return nil, InvalidS3Endpoint
  326. }
  327. client := ClientV2{
  328. Client: Client{
  329. recognizer: ExtensionBasedContentTypeRecognizer{},
  330. config: defaultConfig(),
  331. retry: newRetryer(exponentialBackoff(DefaultRetryTime, DefaultRetryBackoffBase)),
  332. userAgent: fmt.Sprintf("tos-go-sdk/%s (%s/%s;%s)", Version, runtime.GOOS, runtime.GOARCH, runtime.Version()),
  333. enableCRC: true,
  334. },
  335. }
  336. client.retry.SetJitter(0.25)
  337. err := initClient(&client.Client, endpoint, options...)
  338. if err != nil {
  339. return nil, err
  340. }
  341. client.baseClient = newBaseClient(&client.Client)
  342. return &client, nil
  343. }
  344. func (cli *Client) newBuilder(bucket, object string, options ...Option) *requestBuilder {
  345. rb := &requestBuilder{
  346. Signer: cli.signer,
  347. Scheme: cli.scheme,
  348. Host: cli.host,
  349. Bucket: bucket,
  350. Object: object,
  351. URLMode: cli.urlMode,
  352. Query: make(url.Values),
  353. Header: make(http.Header),
  354. OnRetry: func(req *Request) error { return nil },
  355. Classifier: StatusCodeClassifier{},
  356. IsCustomDomain: cli.isCustomDomain,
  357. }
  358. rb.Header.Set(HeaderUserAgent, cli.userAgent)
  359. if typ := cli.recognizer.ContentType(object); len(typ) > 0 {
  360. rb.Header.Set(HeaderContentType, typ)
  361. }
  362. for _, option := range options {
  363. option(rb)
  364. }
  365. rb.Retry = cli.retry
  366. return rb
  367. }
  368. func (cli *Client) roundTrip(ctx context.Context, req *Request, expectedCode int, expectedCodes ...int) (*Response, error) {
  369. res, err := cli.transport.RoundTrip(ctx, req)
  370. if err != nil {
  371. return nil, err
  372. }
  373. readBody := req.Method != http.MethodHead
  374. if err = checkError(res, readBody, expectedCode, expectedCodes...); err != nil {
  375. return nil, err
  376. }
  377. return res, nil
  378. }
  379. func (cli *Client) roundTripper(expectedCode int, expectedCodes ...int) roundTripper {
  380. return func(ctx context.Context, req *Request) (*Response, error) {
  381. start := time.Now()
  382. resp, err := cli.roundTrip(ctx, req, expectedCode, expectedCodes...)
  383. if cli.logger != nil {
  384. if err != nil {
  385. cli.logger.Info(fmt.Sprintf("[tos] http error:%s.", err.Error()))
  386. } else {
  387. cli.logger.Info(fmt.Sprintf("[tos] Response StatusCode:%d, RequestId:%s, Cost:%d ms", resp.StatusCode, resp.RequestInfo().RequestID, time.Since(start).Milliseconds()))
  388. }
  389. }
  390. return resp, err
  391. }
  392. }
  393. // PreSignedURL return pre-signed url
  394. // httpMethod: HTTP method, {
  395. // PutObject: http.MethodPut
  396. // GetObject: http.MethodGet
  397. // HeadObject: http.MethodHead
  398. // DeleteObject: http.MethodDelete
  399. // },
  400. // bucket: the bucket name
  401. // objectKey: the object name
  402. // ttl: the time-to-live of signed URL
  403. // options: WithVersionID the version id of the object
  404. // Deprecated: use PreSignedURL of ClientV2 instead
  405. func (cli *Client) PreSignedURL(httpMethod string, bucket, objectKey string, ttl time.Duration, options ...Option) (string, error) {
  406. return cli.newBuilder(bucket, objectKey, options...).
  407. PreSignedURL(httpMethod, ttl)
  408. }
  409. // PreSignedURL return pre-signed url
  410. func (cli *ClientV2) PreSignedURL(input *PreSignedURLInput) (*PreSignedURLOutput, error) {
  411. rb := cli.newBuilder(input.Bucket, input.Key)
  412. if input.IsCustomDomain != nil {
  413. rb.IsCustomDomain = *input.IsCustomDomain
  414. }
  415. if input.AlternativeEndpoint != "" {
  416. schema, host, _ := schemeHost(input.AlternativeEndpoint)
  417. rb.Host = host
  418. rb.Scheme = schema
  419. }
  420. for k, v := range input.Header {
  421. rb.WithHeader(k, v)
  422. }
  423. for k, v := range input.Query {
  424. rb.WithQuery(k, v)
  425. }
  426. if input.Expires == 0 {
  427. input.Expires = defaultPreSignedURLExpires
  428. }
  429. signedURL, err := rb.PreSignedURL(string(input.HTTPMethod), time.Second*time.Duration(input.Expires))
  430. if err != nil {
  431. return nil, err
  432. }
  433. signed := make(map[string]string)
  434. for k := range rb.Header {
  435. signed[k] = rb.Header.Get(k)
  436. }
  437. output := &PreSignedURLOutput{
  438. SignedUrl: signedURL,
  439. SignedHeader: signed,
  440. }
  441. return output, nil
  442. }
  443. func (cli *ClientV2) PreSignedPostSignature(ctx context.Context, input *PreSingedPostSignatureInput) (*PreSingedPostSignatureOutput, error) {
  444. algorithm := signPrefix
  445. postPolicy := make(map[string]interface{})
  446. cred := cli.credentials.Credential()
  447. region := cli.config.Region
  448. date := UTCNow()
  449. if input.Expires == 0 {
  450. input.Expires = defaultSignExpires
  451. }
  452. postPolicy[signPolicyExpiration] = date.Add(time.Second * time.Duration(input.Expires)).Format(serverTimeFormat)
  453. cond := make([]interface{}, 0)
  454. credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, date.Format(yyMMdd), region)
  455. cond = append(cond, map[string]string{signPolicyAlgorithm: algorithm})
  456. cond = append(cond, map[string]string{signPolicyCredential: credential})
  457. cond = append(cond, map[string]string{signPolicyDate: date.Format(iso8601Layout)})
  458. if cred.SecurityToken != "" {
  459. cond = append(cond, map[string]string{signPolicySecurityToken: cred.SecurityToken})
  460. }
  461. if input.Bucket != "" {
  462. cond = append(cond, map[string]string{signConditionBucket: input.Bucket})
  463. }
  464. if input.Key != "" {
  465. cond = append(cond, map[string]string{signConditionKey: input.Key})
  466. }
  467. for _, condition := range input.Conditions {
  468. if condition.Operator != nil {
  469. cond = append(cond, []string{*condition.Operator, "$" + condition.Key, condition.Value})
  470. } else {
  471. cond = append(cond, map[string]string{condition.Key: condition.Value})
  472. }
  473. }
  474. if input.ContentLengthRange != nil {
  475. cond = append(cond, []interface{}{signConditionContentLengthRange, input.ContentLengthRange.RangeStart, input.ContentLengthRange.RangeEnd})
  476. }
  477. postPolicy[signConditions] = cond
  478. originPolicy, err := json.Marshal(postPolicy)
  479. if err != nil {
  480. return nil, InvalidMarshal
  481. }
  482. signK := SigningKey(&SigningKeyInfo{
  483. Date: date.Format(yyMMdd),
  484. Region: region,
  485. Credential: &cred,
  486. })
  487. policy := base64.StdEncoding.EncodeToString(originPolicy)
  488. return &PreSingedPostSignatureOutput{
  489. OriginPolicy: string(originPolicy),
  490. Policy: policy,
  491. Algorithm: signPrefix,
  492. Credential: credential,
  493. Date: date.Format(iso8601Layout),
  494. Signature: hex.EncodeToString(hmacSHA256(signK, []byte(policy))),
  495. }, nil
  496. }
  497. func (cli *ClientV2) FetchObjectV2(ctx context.Context, input *FetchObjectInputV2) (*FetchObjectOutputV2, error) {
  498. if err := isValidKey(input.Key); err != nil {
  499. return nil, err
  500. }
  501. if err := isValidStorageClass(input.StorageClass); len(input.StorageClass) > 0 && err != nil {
  502. return nil, InvalidStorageClass
  503. }
  504. if err := isValidACL(input.ACL); len(input.ACL) > 0 && err != nil {
  505. return nil, InvalidACL
  506. }
  507. data, contentMD5, err := marshalInput("FetchObjectInputV2", &fetchObjectInput{
  508. URL: input.URL,
  509. IgnoreSameKey: input.IgnoreSameKey,
  510. ContentMD5: input.HexMD5,
  511. })
  512. if err != nil {
  513. return nil, err
  514. }
  515. res, err := cli.newBuilder(input.Bucket, input.Key).
  516. WithQuery("fetch", "").
  517. WithHeader(HeaderContentMD5, contentMD5).
  518. WithParams(*input).
  519. WithRetry(OnRetryFromStart, ServerErrorClassifier{}).
  520. Request(ctx, http.MethodPost, bytes.NewReader(data), cli.roundTripper(http.StatusOK))
  521. if err != nil {
  522. return nil, err
  523. }
  524. defer res.Close()
  525. output := FetchObjectOutputV2{RequestInfo: res.RequestInfo()}
  526. if err = marshalOutput(output.RequestID, res.Body, &output); err != nil {
  527. return nil, err
  528. }
  529. output.VersionID = res.Header.Get(HeaderVersionID)
  530. output.SSECAlgorithm = res.Header.Get(HeaderSSECustomerAlgorithm)
  531. output.SSECKeyMD5 = res.Header.Get(HeaderCopySourceSSECKeyMD5)
  532. return &output, nil
  533. }
  534. func (cli *ClientV2) PutFetchTaskV2(ctx context.Context, input *PutFetchTaskInputV2) (*PutFetchTaskOutputV2, error) {
  535. if err := isValidKey(input.Key); err != nil {
  536. return nil, err
  537. }
  538. if err := isValidStorageClass(input.StorageClass); len(input.StorageClass) > 0 && err != nil {
  539. return nil, err
  540. }
  541. if err := isValidACL(input.ACL); len(input.ACL) > 0 && err != nil {
  542. return nil, err
  543. }
  544. data, contentMD5, err := marshalInput("PutFetchTaskInputV2", putFetchTaskV2Input{
  545. URL: input.URL,
  546. IgnoreSameKey: input.IgnoreSameKey,
  547. HexMD5: input.HexMD5,
  548. Object: input.Key,
  549. })
  550. if err != nil {
  551. return nil, err
  552. }
  553. res, err := cli.newBuilder(input.Bucket, "").
  554. WithQuery("fetchTask", "").
  555. WithHeader(HeaderContentMD5, contentMD5).
  556. WithParams(*input).
  557. WithRetry(OnRetryFromStart, ServerErrorClassifier{}).
  558. Request(ctx, http.MethodPost, bytes.NewReader(data), cli.roundTripper(http.StatusOK))
  559. if err != nil {
  560. return nil, err
  561. }
  562. defer res.Close()
  563. output := PutFetchTaskOutputV2{RequestInfo: res.RequestInfo()}
  564. if err = marshalOutput(output.RequestID, res.Body, &output); err != nil {
  565. return nil, err
  566. }
  567. return &output, nil
  568. }
  569. func (cli *ClientV2) PreSignedPolicyURL(ctx context.Context, input *PreSingedPolicyURLInput) (*PreSingedPolicyURLOutput, error) {
  570. if err := isValidBucketName(input.Bucket, input.IsCustomDomain); err != nil {
  571. return nil, err
  572. }
  573. if input.Expires == 0 {
  574. input.Expires = defaultSignExpires
  575. }
  576. policyConditions := make(map[string]interface{})
  577. cred := cli.credentials.Credential()
  578. region := cli.config.Region
  579. query := make(url.Values)
  580. date := UTCNow()
  581. query.Add(v4Expires, strconv.FormatInt(input.Expires, 10))
  582. // algorithm
  583. algorithm := signPrefix
  584. query.Add(v4Algorithm, algorithm)
  585. // data
  586. dateQuery := date.Format(iso8601Layout)
  587. query.Add(v4Date, dateQuery)
  588. // credential
  589. credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, date.Format(yyMMdd), region)
  590. query.Add(v4Credential, credential)
  591. if cred.SecurityToken != "" {
  592. query.Add(v4SecurityToken, cred.SecurityToken)
  593. }
  594. // input conditions
  595. cond := make([]interface{}, 0)
  596. for _, condition := range input.Conditions {
  597. key := condition.Key
  598. operator := condition.Operator
  599. if key != "key" {
  600. return nil, InvalidPreSignedConditions
  601. }
  602. if operator != nil && !(*operator == "eq" || *operator == "starts-with") {
  603. return nil, InvalidPreSignedConditions
  604. }
  605. if operator != nil {
  606. cond = append(cond, []string{*operator, "$" + key, condition.Value})
  607. } else {
  608. cond = append(cond, map[string]string{key: condition.Value})
  609. }
  610. }
  611. cond = append(cond, map[string]string{"bucket": input.Bucket})
  612. policyConditions[signConditions] = cond
  613. originPolicy, err := json.Marshal(policyConditions)
  614. if err != nil {
  615. return nil, InvalidMarshal
  616. }
  617. policy := base64.StdEncoding.EncodeToString(originPolicy)
  618. query.Add("X-Tos-Policy", policy)
  619. // CanonicalRequest
  620. const split = byte('\n')
  621. var buf bytes.Buffer
  622. var req bytes.Buffer
  623. req.Grow(512)
  624. var queryReq = make(KVs, 0, len(query))
  625. for key, values := range query {
  626. queryReq = append(queryReq, KV{Key: key, Values: values})
  627. }
  628. req.Write(encodeQuery(queryReq))
  629. req.WriteByte(split)
  630. req.WriteString(unsignedPayload)
  631. canonicalStr := req.String()
  632. // StringToSign
  633. buf.Grow(len(signPrefix) + 128)
  634. buf.WriteString(signPrefix)
  635. buf.WriteByte(split)
  636. buf.WriteString(date.Format(iso8601Layout))
  637. buf.WriteByte(split)
  638. buf.WriteString(date.Format(yyMMdd)) // yyMMdd + '/' + region + '/' + service + '/' + request
  639. buf.WriteByte('/')
  640. buf.WriteString(region)
  641. buf.WriteString("/tos/request")
  642. buf.WriteByte(split)
  643. sum := sha256.Sum256([]byte(canonicalStr))
  644. buf.WriteString(hex.EncodeToString(sum[:]))
  645. // SigningKey
  646. signK := SigningKey(&SigningKeyInfo{Date: date.Format(yyMMdd), Region: region, Credential: &cred})
  647. // Signature
  648. sign := hmacSHA256(signK, buf.Bytes())
  649. query.Add(v4Signature, hex.EncodeToString(sign))
  650. rawQuery := query.Encode()
  651. // set scheme and host
  652. resScheme := cli.scheme
  653. resHost := cli.host
  654. if input.AlternativeEndpoint != "" {
  655. resScheme, resHost, _ = schemeHost(input.AlternativeEndpoint)
  656. }
  657. return &PreSingedPolicyURLOutput{
  658. bucket: input.Bucket,
  659. SignatureQuery: rawQuery,
  660. scheme: resScheme,
  661. host: resHost,
  662. isCustomDomain: input.IsCustomDomain,
  663. }, nil
  664. }