mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 10:06:14 +00:00
444 lines
11 KiB
Go
444 lines
11 KiB
Go
// Copyright 2019 The CubeFS Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package objectnode
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
var (
|
|
regexTagKeyValue = regexp.MustCompile(`^[0-9a-zA-Z+=._ :/@-]+$`)
|
|
invalidTagKeyPrefix = []string{"cfs", "cubefs"}
|
|
)
|
|
|
|
func MarshalXMLEntity(entity interface{}) ([]byte, error) {
|
|
var err error
|
|
var result []byte
|
|
if result, err = xml.Marshal(entity); err != nil {
|
|
return nil, err
|
|
}
|
|
return append([]byte(xml.Header), result...), nil
|
|
}
|
|
|
|
func UnmarshalXMLEntity(bytes []byte, data interface{}) error {
|
|
err := xml.Unmarshal(bytes, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ParseJSONEntity(raw interface{}, entity interface{}) error {
|
|
data, err := json.Marshal(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.DisallowUnknownFields()
|
|
return decoder.Decode(entity)
|
|
}
|
|
|
|
type LocationResponse struct {
|
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"`
|
|
Location string `xml:",chardata"`
|
|
}
|
|
|
|
type CopyObjectResult struct {
|
|
XMLName xml.Name `xml:"CopyObjectResult"`
|
|
ETag string `xml:"ETag"`
|
|
LastModified string `xml:"LastModified"`
|
|
}
|
|
|
|
type Deleted struct {
|
|
XMLName xml.Name `xml:"Deleted"`
|
|
Key string `xml:"Key"`
|
|
VersionId string `xml:"VersionId,omitempty"`
|
|
DeleteMarker string `xml:"DeleteMarker,omitempty"`
|
|
DeleteMarkerVersionId string `xml:"DeleteMarkerVersionId,omitempty"`
|
|
}
|
|
|
|
type Error struct {
|
|
XMLName xml.Name `xml:"Error"`
|
|
Key string `xml:"Key"`
|
|
VersionId string `xml:"VersionId,omitempty"`
|
|
Code string `xml:"Code,omitempty"`
|
|
Message string `xml:"Message,omitempty"`
|
|
}
|
|
|
|
type DeleteResult struct {
|
|
XMLName xml.Name `json:"DeleteResult"`
|
|
Deleted []Deleted `xml:"Deleted,omitempty"`
|
|
Error []Error `xml:"Error,omitempty"`
|
|
}
|
|
|
|
type InitMultipartResult struct {
|
|
XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
|
|
Bucket string `xml:"Bucket"`
|
|
Key string `xml:"Key"`
|
|
UploadId string `xml:"UploadId"`
|
|
}
|
|
|
|
type CompleteMultipartResult struct {
|
|
XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
|
|
Location string `xml:"Location"`
|
|
Bucket string `xml:"Bucket"`
|
|
Key string `xml:"Key"`
|
|
ETag string `xml:"ETag"`
|
|
}
|
|
|
|
type Initiator struct {
|
|
XMLName xml.Name `xml:"Initiator"`
|
|
ID string `xml:"ID"`
|
|
DisplayName string `xml:"DisplayName"`
|
|
}
|
|
|
|
type BucketOwner struct {
|
|
XMLName xml.Name `xml:"Owner"`
|
|
ID string `xml:"ID"`
|
|
DisplayName string `xml:"DisplayName"`
|
|
}
|
|
|
|
type Upload struct {
|
|
XMLName xml.Name `xml:"Upload"`
|
|
Key string `xml:"Key"`
|
|
UploadId string `xml:"UploadId"`
|
|
StorageClass string `xml:"StorageClass"`
|
|
Initiated string `xml:"Initiated"`
|
|
Owner *BucketOwner `xml:"Owner"`
|
|
}
|
|
|
|
type Part struct {
|
|
XMLName xml.Name `xml:"Part"`
|
|
PartNumber int `xml:"PartNumber"`
|
|
LastModified string `xml:"LastModified"`
|
|
ETag string `xml:"ETag"`
|
|
Size int `xml:"Size"`
|
|
}
|
|
|
|
type ListPartsResult struct {
|
|
XMLName xml.Name `xml:"ListPartsResult"`
|
|
Bucket string `xml:"Bucket"`
|
|
Key string `xml:"Key"`
|
|
UploadId string `xml:"UploadId"`
|
|
Initiator *Initiator `xml:"Initiator"`
|
|
Owner *BucketOwner `xml:"Owner"`
|
|
StorageClass string `xml:"StorageClass"`
|
|
PartNumberMarker int `xml:"PartNumberMarker"`
|
|
NextMarker uint64 `xml:"NextPartNumberMarker"`
|
|
MaxParts uint64 `xml:"MaxParts"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
Parts []*Part `xml:"Parts"`
|
|
}
|
|
|
|
type CommonPrefix struct {
|
|
XMLName xml.Name `xml:"CommonPrefixes"`
|
|
Prefix string
|
|
}
|
|
|
|
type ListUploadsResult struct {
|
|
XMLName xml.Name `xml:"ListMultipartUploadsResult"`
|
|
Bucket string `xml:"Bucket"`
|
|
KeyMarker string `xml:"KeyMarker"`
|
|
UploadIdMarker string `xml:"UploadIdMarker"`
|
|
NextKeyMarker string `xml:"NextKeyMarker"`
|
|
NextUploadIdMarker string `xml:"NextUploadIdMarker"`
|
|
Delimiter string `xml:"Delimiter"`
|
|
Prefix string `xml:"Prefix"`
|
|
MaxUploads uint64 `xml:"MaxUploads"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
Uploads []*Upload `xml:"Uploads"`
|
|
CommonPrefixes []*CommonPrefix `xml:"CommonPrefixes"`
|
|
}
|
|
|
|
type Content struct {
|
|
Key string `xml:"Key"`
|
|
LastModified string `xml:"LastModified"`
|
|
ETag string `xml:"ETag"`
|
|
Size int `xml:"Size"`
|
|
StorageClass string `xml:"StorageClass"`
|
|
Owner *BucketOwner `xml:"Owner,omitempty"`
|
|
}
|
|
|
|
type ListBucketResult struct {
|
|
XMLName xml.Name `xml:"ListBucketResult"`
|
|
Bucket string `xml:"Name"`
|
|
Prefix string `xml:"Prefix"`
|
|
Marker string `xml:"Marker"`
|
|
MaxKeys int `xml:"MaxKeys"`
|
|
Delimiter string `xml:"Delimiter"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
NextMarker string `xml:"NextMarker,omitempty"`
|
|
Contents []*Content `xml:"Contents"`
|
|
CommonPrefixes []*CommonPrefix `xml:"CommonPrefixes"`
|
|
}
|
|
|
|
func NewParts(fsParts []*FSPart) []*Part {
|
|
parts := make([]*Part, 0)
|
|
for _, fsPart := range fsParts {
|
|
part := &Part{
|
|
PartNumber: fsPart.PartNumber,
|
|
LastModified: fsPart.LastModified,
|
|
ETag: "\"" + fsPart.ETag + "\"",
|
|
Size: fsPart.Size,
|
|
}
|
|
parts = append(parts, part)
|
|
}
|
|
return parts
|
|
}
|
|
|
|
func NewUploads(fsUploads []*FSUpload, accessKey string) []*Upload {
|
|
owner := &BucketOwner{
|
|
ID: accessKey,
|
|
DisplayName: accessKey,
|
|
}
|
|
uploads := make([]*Upload, 0)
|
|
for _, fsUpload := range fsUploads {
|
|
upload := &Upload{
|
|
Key: fsUpload.Key,
|
|
UploadId: fsUpload.UploadId,
|
|
StorageClass: StorageClassStandard,
|
|
Initiated: fsUpload.Initiated,
|
|
Owner: owner,
|
|
}
|
|
uploads = append(uploads, upload)
|
|
}
|
|
return uploads
|
|
}
|
|
|
|
func NewBucketOwner(volume *Volume) *BucketOwner {
|
|
return &BucketOwner{
|
|
ID: volume.GetOwner(),
|
|
DisplayName: volume.GetOwner(),
|
|
}
|
|
}
|
|
|
|
func NewInitiator(volume *Volume) *Initiator {
|
|
return &Initiator{
|
|
ID: volume.GetOwner(),
|
|
DisplayName: volume.GetOwner(),
|
|
}
|
|
}
|
|
|
|
type Object struct {
|
|
Key string `xml:"Key"`
|
|
VersionId string `xml:"VersionId,omitempty"`
|
|
}
|
|
|
|
type DeleteRequest struct {
|
|
XMLName xml.Name `xml:"Delete"`
|
|
Objects []Object `xml:"Object"`
|
|
}
|
|
|
|
type CopyResult struct {
|
|
XMLName xml.Name `xml:"CopyObjectResult"`
|
|
LastModified string `xml:"LastModified,omitempty"`
|
|
ETag string `xml:"ETag,omitempty"`
|
|
}
|
|
|
|
type ListBucketResultV2 struct {
|
|
XMLName xml.Name `xml:"ListBucketResult"`
|
|
Name string `xml:"Name"`
|
|
Prefix string `xml:"Prefix,omitempty"`
|
|
Token string `xml:"ContinuationToken,omitempty"`
|
|
NextToken string `xml:"NextContinuationToken,omitempty"`
|
|
KeyCount uint64 `xml:"KeyCount"`
|
|
MaxKeys uint64 `xml:"MaxKeys"`
|
|
Delimiter string `xml:"Delimiter,omitempty"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
Contents []*Content `xml:"Contents"`
|
|
CommonPrefixes []*CommonPrefix `xml:"CommonPrefixes"`
|
|
}
|
|
|
|
type Tag struct {
|
|
Key string `xml:"Key" json:"k"`
|
|
Value string `xml:"Value" json:"v"`
|
|
}
|
|
|
|
func (t Tag) isValid() bool {
|
|
if len(t.Key) < 1 || len(t.Key) > TaggingKeyMaxLength {
|
|
return false
|
|
}
|
|
if len(t.Value) < 1 || len(t.Value) > TaggingValueMaxLength {
|
|
return false
|
|
}
|
|
|
|
if !regexTagKeyValue.MatchString(t.Key) || !regexTagKeyValue.MatchString(t.Value) {
|
|
return false
|
|
}
|
|
|
|
if !utf8.ValidString(t.Key) || !utf8.ValidString(t.Value) {
|
|
return false
|
|
}
|
|
|
|
for _, pre := range invalidTagKeyPrefix {
|
|
if strings.HasPrefix(t.Key, pre) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
type Tagging struct {
|
|
XMLName xml.Name `json:"-"`
|
|
TagSet []Tag `xml:"TagSet>Tag,omitempty" json:"ts"`
|
|
}
|
|
|
|
func (t Tagging) Encode() string {
|
|
values := url.Values{}
|
|
for _, tag := range t.TagSet {
|
|
values[tag.Key] = []string{tag.Value}
|
|
}
|
|
return values.Encode()
|
|
}
|
|
|
|
func (t Tagging) hasDuplicateKey() bool {
|
|
m := make(map[string]struct{})
|
|
for _, ts := range t.TagSet {
|
|
if _, ok := m[ts.Key]; ok {
|
|
return true
|
|
}
|
|
m[ts.Key] = struct{}{}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (t Tagging) Validate() error {
|
|
if len(t.TagSet) == 0 {
|
|
return MissingTagInBody
|
|
}
|
|
|
|
if len(t.TagSet) > TaggingCounts {
|
|
return TooManyTags
|
|
}
|
|
|
|
if t.hasDuplicateKey() {
|
|
return DuplicateTagKey
|
|
}
|
|
|
|
for _, tag := range t.TagSet {
|
|
if !tag.isValid() {
|
|
return InvalidTag
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewTagging() *Tagging {
|
|
return &Tagging{
|
|
XMLName: xml.Name{Local: "Tagging"},
|
|
}
|
|
}
|
|
|
|
func ParseTagging(src string) (*Tagging, error) {
|
|
values, err := url.ParseQuery(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tagSet := make([]Tag, 0, len(values))
|
|
for key, value := range values {
|
|
tagSet = append(tagSet, Tag{Key: key, Value: value[0]})
|
|
}
|
|
if len(tagSet) == 0 {
|
|
return NewTagging(), nil
|
|
}
|
|
return &Tagging{
|
|
XMLName: xml.Name{Local: "Tagging"},
|
|
TagSet: tagSet,
|
|
}, nil
|
|
}
|
|
|
|
type XAttr struct {
|
|
Key string `xml:"Key"`
|
|
Value string `xml:"Value"`
|
|
}
|
|
|
|
type PutXAttrRequest struct {
|
|
XMLName xml.Name `xml:"PutXAttrRequest"`
|
|
XAttr *XAttr `xml:"XAttr"`
|
|
}
|
|
|
|
type GetXAttrOutput struct {
|
|
XMLName xml.Name `xml:"GetXAttrOutput"`
|
|
XAttr *XAttr `xml:"XAttr"`
|
|
}
|
|
|
|
type ListXAttrsOutput struct {
|
|
XMLName xml.Name `xml:"ListXAttrsResult"`
|
|
Keys []string `xml:"Keys"`
|
|
}
|
|
|
|
type PartRequest struct {
|
|
XMLName xml.Name `xml:"Part"`
|
|
PartNumber int `xml:"PartNumber"`
|
|
ETag string `xml:"ETag"`
|
|
}
|
|
|
|
type CompleteMultipartUploadRequest struct {
|
|
XMLName xml.Name `xml:"CompleteMultipartUpload"`
|
|
Parts []*PartRequest `xml:"Part"`
|
|
}
|
|
|
|
type CreateBucketRequest struct {
|
|
XMLName xml.Name `xml:"CreateBucketConfiguration"`
|
|
LocationConstraint string `xml:"LocationConstraint"`
|
|
}
|
|
|
|
type S3UploadObject struct {
|
|
XMLName xml.Name
|
|
Bucket string `xml:"Bucket"` // bucket name
|
|
Key string `xml:"Key"` // object id or name
|
|
ETag string `xml:"ETag"` // object content MD5 hash
|
|
Location string `xml:"Location"`
|
|
}
|
|
|
|
func NewS3UploadObject() *S3UploadObject {
|
|
return &S3UploadObject{
|
|
XMLName: xml.Name{
|
|
Space: S3Namespace,
|
|
Local: "PostResponse",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s3 *S3UploadObject) SetBucket(bucket string) {
|
|
s3.Bucket = bucket
|
|
}
|
|
|
|
func (s3 *S3UploadObject) SetKey(key string) {
|
|
s3.Key = key
|
|
}
|
|
|
|
func (s3 *S3UploadObject) SetETag(etag string) {
|
|
s3.ETag = etag
|
|
}
|
|
|
|
func (s3 *S3UploadObject) SetLocation(location string) {
|
|
s3.Location = location
|
|
}
|
|
|
|
func (s3 *S3UploadObject) String() string {
|
|
b, _ := xml.Marshal(s3)
|
|
return string(b)
|
|
}
|