copy.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package tos
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "time"
  9. )
  10. // CopyObject copy an object
  11. // srcObjectKey: the source object name
  12. // dstObjectKey: the destination object name. srcObjectKey and dstObjectKey belongs to the same bucket.
  13. // options: WithVersionID the version id of source object,
  14. // WithMetadataDirective copy source object metadata or replace with new object metadata,
  15. // WithACL WithACLGrantFullControl WithACLGrantRead WithACLGrantReadAcp WithACLGrantWrite WithACLGrantWriteAcp set object acl,
  16. // WithCopySourceIfMatch WithCopySourceIfNoneMatch WithCopySourceIfModifiedSince WithCopySourceIfUnmodifiedSince set copy conditions
  17. // if CopyObject called with WithMetadataDirective(tos.MetadataDirectiveReplace), these options can be used:
  18. // WithContentType set Content-Type,
  19. // WithContentDisposition set Content-Disposition,
  20. // WithContentLanguage set Content-Language,
  21. // WithContentEncoding set Content-Encoding,
  22. // WithCacheControl set Cache-Control,
  23. // WithExpires set Expires,
  24. // WithMeta set meta header(s),
  25. //
  26. // Deprecated: use CopyObject of ClientV2 instead
  27. func (bkt *Bucket) CopyObject(ctx context.Context, srcObjectKey, dstObjectKey string, options ...Option) (*CopyObjectOutput, error) {
  28. if err := isValidKey(dstObjectKey, srcObjectKey); err != nil {
  29. return nil, err
  30. }
  31. return bkt.client.copyObject(ctx, bkt.name, dstObjectKey, bkt.name, srcObjectKey, options...)
  32. }
  33. // CopyObjectTo copy an object to target bucket
  34. // dstBucket: the destination bucket
  35. // dstObjectKey: the destination object name
  36. // srcObjectKey: the source object name
  37. // options: WithVersionID the version id of source object,
  38. // WithMetadataDirective copy source object metadata or replace with new object metadata.
  39. // WithACL WithACLGrantFullControl WithACLGrantRead WithACLGrantReadAcp WithACLGrantWrite WithACLGrantWriteAcp set object acl,
  40. // WithCopySourceIfMatch WithCopySourceIfNoneMatch WithCopySourceIfModifiedSince WithCopySourceIfUnmodifiedSince set copy conditions
  41. // if CopyObjectTo called with WithMetadataDirective(tos.MetadataDirectiveReplace), these options can be used:
  42. // WithContentType set Content-Type,
  43. // WithContentDisposition set Content-Disposition,
  44. // WithContentLanguage set Content-Language,
  45. // WithContentEncoding set Content-Encoding,
  46. // WithCacheControl set Cache-Control,
  47. // WithExpires set Expires,
  48. // WithMeta set meta header(s),
  49. //
  50. // Deprecated: use CopyObject of ClientV2 instead
  51. func (bkt *Bucket) CopyObjectTo(ctx context.Context, dstBucket, dstObjectKey, srcObjectKey string, options ...Option) (*CopyObjectOutput, error) {
  52. if err := isValidNames(dstBucket, dstObjectKey, false, srcObjectKey); err != nil {
  53. return nil, err
  54. }
  55. return bkt.client.copyObject(ctx, dstBucket, dstObjectKey, bkt.name, srcObjectKey, options...)
  56. }
  57. // CopyObjectFrom copy an object from target bucket
  58. // srcBucket: the srcBucket bucket
  59. // srcObjectKey: the source object name
  60. // dstObjectKey: the destination object name
  61. // options: WithVersionID the version id of source object,
  62. // WithMetadataDirective copy source object metadata or replace with new object metadata
  63. // WithACL WithACLGrantFullControl WithACLGrantRead WithACLGrantReadAcp WithACLGrantWrite WithACLGrantWriteAcp set object acl,
  64. // WithCopySourceIfMatch WithCopySourceIfNoneMatch WithCopySourceIfModifiedSince WithCopySourceIfUnmodifiedSince set copy conditions
  65. // if CopyObjectFrom called with WithMetadataDirective(tos.MetadataDirectiveReplace), these options can be used:
  66. // WithContentType set Content-Type,
  67. // WithContentDisposition set Content-Disposition,
  68. // WithContentLanguage set Content-Language,
  69. // WithContentEncoding set Content-Encoding,
  70. // WithCacheControl set Cache-Control,
  71. // WithExpires set Expires,
  72. // WithMeta set meta header(s),
  73. //
  74. // Deprecated: use CopyObject of ClientV2 instead
  75. func (bkt *Bucket) CopyObjectFrom(ctx context.Context, srcBucket, srcObjectKey, dstObjectKey string, options ...Option) (*CopyObjectOutput, error) {
  76. if err := isValidNames(srcBucket, srcObjectKey, false, dstObjectKey); err != nil {
  77. return nil, err
  78. }
  79. return bkt.client.copyObject(ctx, bkt.name, dstObjectKey, srcBucket, srcObjectKey, options...)
  80. }
  81. func (cli *Client) copyObject(ctx context.Context, dstBucket, dstObject string, srcBucket, srcObject string, options ...Option) (*CopyObjectOutput, error) {
  82. res, err := cli.newBuilder(dstBucket, dstObject, options...).
  83. WithCopySource(srcBucket, srcObject).
  84. WithRetry(nil, ServerErrorClassifier{}).
  85. Request(ctx, http.MethodPut, nil, cli.roundTripper(http.StatusOK))
  86. if err != nil {
  87. return nil, err
  88. }
  89. defer res.Close()
  90. marshalOut := copyObjectOutput{}
  91. if err = marshalOutput(res.RequestInfo().RequestID, res.Body, &marshalOut); err != nil {
  92. return nil, err
  93. }
  94. if marshalOut.ETag == "" {
  95. return nil, &TosServerError{
  96. TosError: TosError{marshalOut.Message},
  97. RequestInfo: res.RequestInfo(),
  98. Code: marshalOut.Code,
  99. HostID: marshalOut.HostID,
  100. Resource: marshalOut.Resource,
  101. }
  102. }
  103. out := CopyObjectOutput{RequestInfo: res.RequestInfo(), ETag: marshalOut.ETag, LastModified: marshalOut.LastModified}
  104. out.VersionID = res.Header.Get(HeaderVersionID)
  105. out.SourceVersionID = res.Header.Get(HeaderCopySourceVersionID)
  106. out.SSECAlgorithm = res.Header.Get(HeaderSSECustomerAlgorithm)
  107. out.SSECKeyMD5 = res.Header.Get(HeaderSSECustomerKeyMD5)
  108. out.ServerSideEncryption = res.Header.Get(HeaderServerSideEncryption)
  109. out.ServerSideEncryptionKeyID = res.Header.Get(HeaderServerSideEncryptionKmsKeyID)
  110. return &out, nil
  111. }
  112. // CopyObject copy an object
  113. func (cli *ClientV2) CopyObject(ctx context.Context, input *CopyObjectInput) (*CopyObjectOutput, error) {
  114. if err := isValidBucketName(input.SrcBucket, false); err != nil {
  115. return nil, err
  116. }
  117. if err := isValidBucketName(input.Bucket, cli.isCustomDomain); err != nil {
  118. return nil, err
  119. }
  120. if err := isValidKey(input.Key, input.SrcKey); err != nil {
  121. return nil, err
  122. }
  123. if err := isValidMetadataDirective(input.MetadataDirective); len(input.MetadataDirective) != 0 && err != nil {
  124. return nil, err
  125. }
  126. res, err := cli.newBuilder(input.Bucket, input.Key).
  127. WithParams(*input).
  128. WithCopySource(input.SrcBucket, input.SrcKey).
  129. WithRetry(nil, ServerErrorClassifier{}).
  130. Request(ctx, http.MethodPut, nil, cli.roundTripper(http.StatusOK))
  131. if err != nil {
  132. return nil, err
  133. }
  134. defer res.Close()
  135. marshalOut := copyObjectOutput{}
  136. if err = marshalOutput(res.RequestInfo().RequestID, res.Body, &marshalOut); err != nil {
  137. return nil, err
  138. }
  139. // Body 的 Etag 存在复制成功
  140. if marshalOut.ETag == "" {
  141. return nil, &TosServerError{
  142. TosError: TosError{marshalOut.Message},
  143. RequestInfo: res.RequestInfo(),
  144. Code: marshalOut.Code,
  145. HostID: marshalOut.HostID,
  146. Resource: marshalOut.Resource,
  147. }
  148. }
  149. out := CopyObjectOutput{RequestInfo: res.RequestInfo(), ETag: marshalOut.ETag, LastModified: marshalOut.LastModified}
  150. out.VersionID = res.Header.Get(HeaderVersionID)
  151. out.SourceVersionID = res.Header.Get(HeaderCopySourceVersionID)
  152. return &out, nil
  153. }
  154. type uploadPartCopyOutput struct {
  155. ETag string `json:"ETag,omitempty"`
  156. LastModified string `json:"LastModified,omitempty"`
  157. Error
  158. }
  159. func copyRange(startOffset, partSize *int64) string {
  160. cr := ""
  161. if startOffset != nil {
  162. if partSize != nil {
  163. cr = fmt.Sprintf("bytes=%d-%d", *startOffset, *startOffset+*partSize-1)
  164. } else {
  165. cr = fmt.Sprintf("bytes=%d-", *startOffset)
  166. }
  167. } else if partSize != nil {
  168. cr = fmt.Sprintf("bytes=0-%d", *partSize-1)
  169. }
  170. return cr
  171. }
  172. func copySource(bucket, object, versionID string) string {
  173. if len(versionID) == 0 {
  174. return "/" + bucket + "/" + url.QueryEscape(object)
  175. }
  176. return "/" + bucket + "/" + url.QueryEscape(object) + "?versionId=" + versionID
  177. }
  178. func (up *UploadPartCopyOutput) uploadedPart() uploadedPart {
  179. return uploadedPart{PartNumber: up.PartNumber, ETag: up.ETag}
  180. }
  181. // UploadPartCopy copy a part of object as a part of a multipart upload operation
  182. // input: uploadID, DestinationKey, SourceBucket, SourceKey and other parameters,
  183. // options: WithCopySourceIfMatch WithCopySourceIfNoneMatch WithCopySourceIfModifiedSince WithCopySourceIfUnmodifiedSince set copy conditions
  184. //
  185. // Deprecated: use UploadPartCopy of ClientV2 instead
  186. func (bkt *Bucket) UploadPartCopy(ctx context.Context, input *UploadPartCopyInput, options ...Option) (*UploadPartCopyOutput, error) {
  187. if err := isValidNames(input.SourceBucket, input.DestinationKey, false); err != nil {
  188. return nil, err
  189. }
  190. res, err := bkt.client.newBuilder(bkt.name, input.DestinationKey, options...).
  191. WithQuery("partNumber", strconv.Itoa(input.PartNumber)).
  192. WithQuery("uploadId", input.UploadID).
  193. WithQuery("versionId", input.SourceVersionID).
  194. WithHeader(HeaderCopySourceRange, copyRange(input.StartOffset, input.PartSize)).
  195. WithCopySource(input.SourceBucket, input.SourceKey).
  196. WithRetry(nil, ServerErrorClassifier{}).
  197. Request(ctx, http.MethodPut, nil, bkt.client.roundTripper(http.StatusOK))
  198. if err != nil {
  199. return nil, err
  200. }
  201. defer res.Close()
  202. var out uploadPartCopyOutput
  203. if err = marshalOutput(res.RequestInfo().RequestID, res.Body, &out); err != nil {
  204. return nil, err
  205. }
  206. if out.ETag == "" {
  207. return nil, &TosServerError{
  208. TosError: TosError{out.Message},
  209. RequestInfo: res.RequestInfo(),
  210. Code: out.Code,
  211. HostID: out.HostID,
  212. Resource: out.Resource,
  213. }
  214. }
  215. return &UploadPartCopyOutput{
  216. RequestInfo: res.RequestInfo(),
  217. VersionID: res.Header.Get(HeaderVersionID),
  218. SourceVersionID: res.Header.Get(HeaderCopySourceVersionID),
  219. PartNumber: input.PartNumber,
  220. ETag: out.ETag,
  221. LastModified: out.LastModified,
  222. }, nil
  223. }
  224. func copyRangeV2(start, end int64) string {
  225. cr := ""
  226. if start == 0 && end == 0 {
  227. return cr
  228. }
  229. if start > end {
  230. return cr
  231. }
  232. cr = fmt.Sprintf("bytes=%d-%d", start, end)
  233. return cr
  234. }
  235. // UploadPartCopyV2 copy a part of object as a part of a multipart upload operation
  236. func (cli *ClientV2) UploadPartCopyV2(
  237. ctx context.Context,
  238. input *UploadPartCopyV2Input) (*UploadPartCopyV2Output, error) {
  239. if err := isValidBucketName(input.Bucket, cli.isCustomDomain); err != nil {
  240. return nil, err
  241. }
  242. if err := isValidBucketName(input.SrcBucket, false); err != nil {
  243. return nil, err
  244. }
  245. if err := isValidKey(input.SrcKey, input.Key); err != nil {
  246. return nil, err
  247. }
  248. req := cli.newBuilder(input.Bucket, input.Key).
  249. WithParams(*input)
  250. if input.CopySourceRange != "" {
  251. req = req.WithHeader(HeaderCopySourceRange, input.CopySourceRange)
  252. } else if input.CopySourceRangeEnd != 0 {
  253. req = req.WithHeader(HeaderCopySourceRange, copyRangeV2(input.CopySourceRangeStart, input.CopySourceRangeEnd))
  254. }
  255. res, err := req.WithCopySource(input.SrcBucket, input.SrcKey).
  256. WithRetry(nil, ServerErrorClassifier{}).
  257. Request(ctx, http.MethodPut, nil, cli.roundTripper(http.StatusOK))
  258. if err != nil {
  259. return nil, err
  260. }
  261. defer res.Close()
  262. var out uploadPartCopyOutput
  263. if err = marshalOutput(res.RequestInfo().RequestID, res.Body, &out); err != nil {
  264. return nil, err
  265. }
  266. lastModified, _ := time.ParseInLocation(http.TimeFormat, res.Header.Get(HeaderLastModified), time.UTC)
  267. if out.ETag == "" {
  268. return nil, &TosServerError{
  269. TosError: TosError{out.Message},
  270. RequestInfo: res.RequestInfo(),
  271. Code: out.Code,
  272. HostID: out.HostID,
  273. Resource: out.Resource,
  274. }
  275. }
  276. return &UploadPartCopyV2Output{
  277. RequestInfo: res.RequestInfo(),
  278. PartNumber: input.PartNumber,
  279. ETag: out.ETag,
  280. LastModified: lastModified,
  281. CopySourceVersionID: res.Header.Get(HeaderCopySourceVersionID),
  282. ServerSideEncryption: res.Header.Get(HeaderServerSideEncryption),
  283. ServerSideEncryptionKeyID: res.Header.Get(HeaderServerSideEncryptionKmsKeyID),
  284. SSECAlgorithm: res.Header.Get(HeaderSSECustomerAlgorithm),
  285. SSECKeyMD5: res.Header.Get(HeaderSSECustomerKeyMD5),
  286. }, nil
  287. }