object.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package handlers
  15. import (
  16. "context"
  17. "io"
  18. "net/http"
  19. "net/url"
  20. "strconv"
  21. "strings"
  22. "yunion.io/x/cloudmux/pkg/cloudprovider"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/timeutils"
  26. "yunion.io/x/s3cli"
  27. "yunion.io/x/onecloud/pkg/appsrv"
  28. "yunion.io/x/onecloud/pkg/httperrors"
  29. "yunion.io/x/onecloud/pkg/mcclient"
  30. "yunion.io/x/onecloud/pkg/s3gateway/models"
  31. )
  32. func headObject(ctx context.Context, userCred mcclient.TokenCredential, bucketName string, key string) (http.Header, error) {
  33. bucket, err := models.BucketManager.GetByName(ctx, userCred, bucketName)
  34. if err != nil {
  35. return nil, errors.Wrap(err, "models.BucketManager.GetByName")
  36. }
  37. iBucket, err := bucket.GetIBucket(ctx, userCred)
  38. if err != nil {
  39. return nil, errors.Wrap(err, "bucket.GetIBucket")
  40. }
  41. obj, err := cloudprovider.GetIObject(iBucket, key)
  42. if err != nil {
  43. return nil, errors.Wrap(err, "cloudprovider.GetIObject")
  44. }
  45. hdr := cloudprovider.MetaToHttpHeader(cloudprovider.META_HEADER_PREFIX, obj.GetMeta())
  46. hdr.Set(http.CanonicalHeaderKey("x-amz-acl"), string(obj.GetAcl()))
  47. hdr.Set(http.CanonicalHeaderKey("x-amz-storage-class"), obj.GetStorageClass())
  48. hdr.Set(http.CanonicalHeaderKey("content-length"), strconv.FormatInt(obj.GetSizeBytes(), 10))
  49. hdr.Set(http.CanonicalHeaderKey("etag"), obj.GetETag())
  50. hdr.Set(http.CanonicalHeaderKey("last-modified"), obj.GetLastModified().Format(timeutils.RFC2882Format))
  51. return hdr, nil
  52. }
  53. func uploadObject(ctx context.Context, userCred mcclient.TokenCredential, bucketName string, key string, header http.Header, body io.Reader, uploadId string, partNumber int) (http.Header, error) {
  54. bucket, err := models.BucketManager.GetByName(ctx, userCred, bucketName)
  55. if err != nil {
  56. return nil, errors.Wrap(err, "models.BucketManager.GetByName")
  57. }
  58. err = bucket.IsOutOfLimit()
  59. if err != nil {
  60. return nil, errors.Wrap(err, "IsOutOfLimit")
  61. }
  62. iBucket, err := bucket.GetIBucket(ctx, userCred)
  63. if err != nil {
  64. return nil, errors.Wrap(err, "bucket.GetIBucket")
  65. }
  66. contLenStr := header.Get(http.CanonicalHeaderKey("Content-Length"))
  67. contLen, err := strconv.ParseInt(contLenStr, 10, 64)
  68. if err != nil {
  69. return nil, errors.Wrap(httperrors.ErrBadRequest, "missing content length")
  70. }
  71. respHdr := http.Header{}
  72. if len(uploadId) > 0 {
  73. etag, err := iBucket.UploadPart(ctx, key, uploadId, partNumber, body, contLen, 0, 0)
  74. if err != nil {
  75. return nil, errors.Wrap(err, "iBucket.UploadPart")
  76. }
  77. respHdr.Set("ETag", etag)
  78. } else {
  79. meta := cloudprovider.FetchMetaFromHttpHeader(cloudprovider.META_HEADER_PREFIX, header)
  80. aclStr := header.Get(http.CanonicalHeaderKey("x-amz-acl"))
  81. storageClassStr := header.Get(http.CanonicalHeaderKey("x-amz-storage-class"))
  82. err = iBucket.PutObject(ctx, key, body, contLen, cloudprovider.TBucketACLType(aclStr), storageClassStr, meta)
  83. if err != nil {
  84. return nil, errors.Wrap(err, "iBucket.PutObject")
  85. }
  86. obj, err := cloudprovider.GetIObject(iBucket, key)
  87. if err != nil {
  88. return nil, errors.Wrap(err, "cloudprovider.GetIObject")
  89. }
  90. respHdr.Set("ETag", obj.GetETag())
  91. }
  92. bucket.Invalidate()
  93. return respHdr, nil
  94. }
  95. const (
  96. MIN_PART_BYTES = 1000 * 1000 * 10 // 100 MB
  97. MAX_PART_COUNT = 10000
  98. )
  99. func copyObject(ctx context.Context, userCred mcclient.TokenCredential, bucketName string, key string, copySource string, hdr http.Header, uploadId string, partNumber int) (interface{}, http.Header, error) {
  100. log.Debugf("CopyObject %s => %s/%s %s %d %s", copySource, bucketName, key, uploadId, partNumber, hdr)
  101. srcSegs := appsrv.SplitPath(copySource)
  102. srcBucketName := srcSegs[0]
  103. srcKey := strings.Join(srcSegs[1:], "/")
  104. if strings.HasSuffix(copySource, "/") {
  105. srcKey += "/"
  106. }
  107. var err error
  108. srcKey, err = url.PathUnescape(srcKey)
  109. if err != nil {
  110. return nil, nil, errors.Wrap(err, "url.PathUnescape")
  111. }
  112. srcBucket, err := models.BucketManager.GetByName(ctx, userCred, srcBucketName)
  113. if err != nil {
  114. return nil, nil, errors.Wrap(err, "source bucket GetByName")
  115. }
  116. iSrcBucket, err := srcBucket.GetIBucket(ctx, userCred)
  117. if err != nil {
  118. return nil, nil, errors.Wrap(err, "srcBucket.GetIBucket")
  119. }
  120. srcObj, err := cloudprovider.GetIObject(iSrcBucket, srcKey)
  121. if err != nil {
  122. return nil, nil, errors.Wrap(err, "src cloudprovider.GetIObject")
  123. }
  124. dstBucket, err := models.BucketManager.GetByName(ctx, userCred, bucketName)
  125. if err != nil {
  126. return nil, nil, errors.Wrap(err, "dest bucket GetByName")
  127. }
  128. err = dstBucket.IsOutOfLimit()
  129. if err != nil {
  130. return nil, nil, errors.Wrap(err, "IsOutOfLimit")
  131. }
  132. iDstBucket, err := dstBucket.GetIBucket(ctx, userCred)
  133. if err != nil {
  134. return nil, nil, errors.Wrap(err, "dstBucket.GetIBucket")
  135. }
  136. sizeBytes := srcObj.GetSizeBytes()
  137. rangeStr := hdr.Get(http.CanonicalHeaderKey("x-amz-copy-source-range"))
  138. rangeOpt, err := getRangeOpt(rangeStr, sizeBytes)
  139. if err != nil {
  140. return nil, nil, errors.Wrap(err, rangeStr)
  141. }
  142. if rangeOpt != nil {
  143. if len(uploadId) == 0 {
  144. return nil, nil, errors.Wrap(httperrors.ErrBadRequest, "range copy must be a multipart upload")
  145. }
  146. // upload directory
  147. var etag string
  148. if dstBucket.ManagerId == srcBucket.ManagerId && dstBucket.RegionExternalId == srcBucket.RegionExternalId {
  149. etag, err = iDstBucket.CopyPart(ctx, key, uploadId, partNumber, iSrcBucket.GetName(), srcKey, rangeOpt.Start, rangeOpt.SizeBytes())
  150. } else {
  151. etag, err = cloudprovider.CopyPart(ctx, iDstBucket, key, uploadId, partNumber, iSrcBucket, srcKey, rangeOpt)
  152. }
  153. if err != nil {
  154. return nil, nil, errors.Wrap(err, "copyPart fail")
  155. }
  156. result := s3cli.CopyPartResult{
  157. ETag: etag,
  158. LastModified: srcObj.GetLastModified(),
  159. }
  160. return &result, nil, nil
  161. } else {
  162. meta := cloudprovider.FetchMetaFromHttpHeader(cloudprovider.META_HEADER_PREFIX, hdr)
  163. if dstBucket.ManagerId == srcBucket.ManagerId && dstBucket.RegionExternalId == srcBucket.RegionExternalId {
  164. err = iDstBucket.CopyObject(ctx, key, iSrcBucket.GetName(), srcKey, srcObj.GetAcl(), srcObj.GetStorageClass(), meta)
  165. if err != nil {
  166. return nil, nil, errors.Wrap(err, "iDstBucket.CopyObject")
  167. }
  168. } else {
  169. err = cloudprovider.CopyObject(ctx, 0, iDstBucket, key, iSrcBucket, srcKey, meta, false)
  170. if err != nil {
  171. return nil, nil, errors.Wrap(err, "cloudprovider.CopyObject")
  172. }
  173. }
  174. dstBucket.Invalidate()
  175. dstObj, err := cloudprovider.GetIObject(iDstBucket, key)
  176. if err != nil {
  177. return nil, nil, errors.Wrap(err, "cloudprovider.GetIObject")
  178. }
  179. result := s3cli.CopyObjectResult{
  180. ETag: dstObj.GetETag(),
  181. LastModified: dstObj.GetLastModified(),
  182. }
  183. return &result, nil, nil
  184. }
  185. }
  186. func deleteObjectTags(ctx context.Context, userCred mcclient.TokenCredential, bucket string, key string) (*s3cli.Tagging, error) {
  187. return nil, nil
  188. }
  189. func removeObject(ctx context.Context, userCred mcclient.TokenCredential, bucketName string, key string) error {
  190. bucket, err := models.BucketManager.GetByName(ctx, userCred, bucketName)
  191. if err != nil {
  192. return errors.Wrap(err, "models.BucketManager.GetByName")
  193. }
  194. iBucket, err := bucket.GetIBucket(ctx, userCred)
  195. if err != nil {
  196. return errors.Wrap(err, "bucket.GetIBucket")
  197. }
  198. err = iBucket.DeleteObject(ctx, key)
  199. if err != nil {
  200. return errors.Wrap(err, "DeleteObject")
  201. }
  202. bucket.Invalidate()
  203. return nil
  204. }
  205. func objectAcl(ctx context.Context, userCred mcclient.TokenCredential, bucketName string, objKey string) (*s3cli.AccessControlPolicy, error) {
  206. bucket, err := models.BucketManager.GetByName(ctx, userCred, bucketName)
  207. if err != nil {
  208. return nil, errors.Wrap(err, "models.BucketManager.GetByName")
  209. }
  210. iBucket, err := bucket.GetIBucket(ctx, userCred)
  211. if err != nil {
  212. return nil, errors.Wrap(err, "bucket.GetIBucket")
  213. }
  214. obj, err := cloudprovider.GetIObject(iBucket, objKey)
  215. if err != nil {
  216. return nil, errors.Wrap(err, "cloudprovider.GetIObject")
  217. }
  218. result := str2Acl(userCred, obj.GetAcl())
  219. return result, nil
  220. }
  221. func str2Acl(userCred mcclient.TokenCredential, aclStr cloudprovider.TBucketACLType) *s3cli.AccessControlPolicy {
  222. result := s3cli.AccessControlPolicy{}
  223. result.Owner.DisplayName = userCred.GetProjectName()
  224. result.Owner.ID = userCred.GetProjectId()
  225. fullControl := s3cli.Grant{}
  226. fullControl.Permission = s3cli.PERMISSION_FULL_CONTROL
  227. fullControl.Grantee.Type = s3cli.GRANTEE_TYPE_USER
  228. fullControl.Grantee.ID = userCred.GetProjectId()
  229. fullControl.Grantee.DisplayName = userCred.GetProjectName()
  230. publicRead := s3cli.Grant{}
  231. publicRead.Permission = s3cli.PERMISSION_READ
  232. publicRead.Grantee.Type = s3cli.GRANTEE_TYPE_GROUP
  233. publicRead.Grantee.URI = s3cli.GRANTEE_GROUP_URI_ALL_USERS
  234. publicWrite := s3cli.Grant{}
  235. publicWrite.Permission = s3cli.PERMISSION_WRITE
  236. publicWrite.Grantee.Type = s3cli.GRANTEE_TYPE_GROUP
  237. publicWrite.Grantee.URI = s3cli.GRANTEE_GROUP_URI_ALL_USERS
  238. authRead := s3cli.Grant{}
  239. authRead.Permission = s3cli.PERMISSION_READ
  240. authRead.Grantee.Type = s3cli.GRANTEE_TYPE_GROUP
  241. authRead.Grantee.URI = s3cli.GRANTEE_GROUP_URI_AUTH_USERS
  242. switch aclStr {
  243. case cloudprovider.ACLPrivate:
  244. result.AccessControlList.Grant = []s3cli.Grant{
  245. fullControl,
  246. }
  247. case cloudprovider.ACLAuthRead:
  248. result.AccessControlList.Grant = []s3cli.Grant{
  249. fullControl,
  250. authRead,
  251. }
  252. case cloudprovider.ACLPublicRead:
  253. result.AccessControlList.Grant = []s3cli.Grant{
  254. fullControl,
  255. publicRead,
  256. }
  257. case cloudprovider.ACLPublicReadWrite:
  258. result.AccessControlList.Grant = []s3cli.Grant{
  259. fullControl,
  260. publicRead,
  261. publicWrite,
  262. }
  263. }
  264. return &result
  265. }