You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
3.7 KiB

4 years ago
4 years ago
5 years ago
  1. package s3api
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/chrislusf/seaweedfs/weed/util"
  10. )
  11. func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
  12. dstBucket, dstObject := getBucketAndObject(r)
  13. // Copy source path.
  14. cpSrcPath, err := url.QueryUnescape(r.Header.Get("X-Amz-Copy-Source"))
  15. if err != nil {
  16. // Save unescaped string as is.
  17. cpSrcPath = r.Header.Get("X-Amz-Copy-Source")
  18. }
  19. srcBucket, srcObject := pathToBucketAndObject(cpSrcPath)
  20. // If source object is empty or bucket is empty, reply back invalid copy source.
  21. if srcObject == "" || srcBucket == "" {
  22. writeErrorResponse(w, ErrInvalidCopySource, r.URL)
  23. return
  24. }
  25. if srcBucket == dstBucket && srcObject == dstObject {
  26. writeErrorResponse(w, ErrInvalidCopySource, r.URL)
  27. return
  28. }
  29. dstUrl := fmt.Sprintf("http://%s%s/%s%s?collection=%s",
  30. s3a.option.Filer, s3a.option.BucketsPath, dstBucket, dstObject, dstBucket)
  31. srcUrl := fmt.Sprintf("http://%s%s/%s%s",
  32. s3a.option.Filer, s3a.option.BucketsPath, srcBucket, srcObject)
  33. _, _, dataReader, err := util.DownloadFile(srcUrl)
  34. if err != nil {
  35. writeErrorResponse(w, ErrInvalidCopySource, r.URL)
  36. return
  37. }
  38. defer dataReader.Close()
  39. etag, errCode := s3a.putToFiler(r, dstUrl, dataReader)
  40. if errCode != ErrNone {
  41. writeErrorResponse(w, errCode, r.URL)
  42. return
  43. }
  44. setEtag(w, etag)
  45. response := CopyObjectResult{
  46. ETag: etag,
  47. LastModified: time.Now().UTC(),
  48. }
  49. writeSuccessResponseXML(w, encodeResponse(response))
  50. }
  51. func pathToBucketAndObject(path string) (bucket, object string) {
  52. path = strings.TrimPrefix(path, "/")
  53. parts := strings.SplitN(path, "/", 2)
  54. if len(parts) == 2 {
  55. return parts[0], "/" + parts[1]
  56. }
  57. return parts[0], "/"
  58. }
  59. type CopyPartResult struct {
  60. LastModified time.Time `xml:"LastModified"`
  61. ETag string `xml:"ETag"`
  62. }
  63. func (s3a *S3ApiServer) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) {
  64. // https://docs.aws.amazon.com/AmazonS3/latest/dev/CopyingObjctsUsingRESTMPUapi.html
  65. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html
  66. dstBucket, _ := getBucketAndObject(r)
  67. // Copy source path.
  68. cpSrcPath, err := url.QueryUnescape(r.Header.Get("X-Amz-Copy-Source"))
  69. if err != nil {
  70. // Save unescaped string as is.
  71. cpSrcPath = r.Header.Get("X-Amz-Copy-Source")
  72. }
  73. srcBucket, srcObject := pathToBucketAndObject(cpSrcPath)
  74. // If source object is empty or bucket is empty, reply back invalid copy source.
  75. if srcObject == "" || srcBucket == "" {
  76. writeErrorResponse(w, ErrInvalidCopySource, r.URL)
  77. return
  78. }
  79. uploadID := r.URL.Query().Get("uploadId")
  80. partIDString := r.URL.Query().Get("partNumber")
  81. partID, err := strconv.Atoi(partIDString)
  82. if err != nil {
  83. writeErrorResponse(w, ErrInvalidPart, r.URL)
  84. return
  85. }
  86. // check partID with maximum part ID for multipart objects
  87. if partID > globalMaxPartID {
  88. writeErrorResponse(w, ErrInvalidMaxParts, r.URL)
  89. return
  90. }
  91. rangeHeader := r.Header.Get("x-amz-copy-source-range")
  92. dstUrl := fmt.Sprintf("http://%s%s/%s/%04d.part?collection=%s",
  93. s3a.option.Filer, s3a.genUploadsFolder(dstBucket), uploadID, partID-1, dstBucket)
  94. srcUrl := fmt.Sprintf("http://%s%s/%s%s",
  95. s3a.option.Filer, s3a.option.BucketsPath, srcBucket, srcObject)
  96. dataReader, err := util.ReadUrlAsReaderCloser(srcUrl, rangeHeader)
  97. if err != nil {
  98. writeErrorResponse(w, ErrInvalidCopySource, r.URL)
  99. return
  100. }
  101. defer dataReader.Close()
  102. etag, errCode := s3a.putToFiler(r, dstUrl, dataReader)
  103. if errCode != ErrNone {
  104. writeErrorResponse(w, errCode, r.URL)
  105. return
  106. }
  107. setEtag(w, etag)
  108. response := CopyPartResult{
  109. ETag: etag,
  110. LastModified: time.Now().UTC(),
  111. }
  112. writeSuccessResponseXML(w, encodeResponse(response))
  113. }