diff --git a/internal/pkg/provider/provider.go b/internal/pkg/provider/provider.go index 386b1e0..80f7b98 100644 --- a/internal/pkg/provider/provider.go +++ b/internal/pkg/provider/provider.go @@ -11,8 +11,18 @@ var urlEncode = url.QueryEscape var corsAllowHeaders = []string{"content-type", "content-disposition", "x-amz-acl"} +// Object is the basic operation unit +type Object struct { + Key string // remote file path + ETag string // file md5 + FilePath string // local file path + Type string // local file type, added or changed +} + type Provider interface { SetupCORS() error + List(prefix string) ([]Object, error) + Move(object, newObject string) error SignedPutURL(key, filetype string, public bool) (url string, headers http.Header, err error) SignedGetURL(key, filename string) (url string, err error) PublicURL(key string) (url string) diff --git a/internal/pkg/provider/provider_mock.go b/internal/pkg/provider/provider_mock.go index 0a3076c..ac9e0bd 100644 --- a/internal/pkg/provider/provider_mock.go +++ b/internal/pkg/provider/provider_mock.go @@ -12,6 +12,14 @@ func (m *MockProvider) SetupCORS() error { return nil } +func (m *MockProvider) List(prefix string) ([]Object, error) { + return []Object{}, nil +} + +func (m *MockProvider) Move(object, newObject string) error { + return nil +} + func (m *MockProvider) SignedPutURL(key, filetype string, public bool) (url string, headers http.Header, err error) { headers = make(http.Header) headers.Add("", "") diff --git a/internal/pkg/provider/provider_s3.go b/internal/pkg/provider/provider_s3.go index 7c5f1bb..59fee45 100644 --- a/internal/pkg/provider/provider_s3.go +++ b/internal/pkg/provider/provider_s3.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -78,6 +79,53 @@ func (p *S3Provider) SetupCORS() error { return err } +// List returns the remote objects +func (p *S3Provider) List(prefix string) ([]Object, error) { + marker := "" + objects := make([]Object, 0) + for { + input := &s3.ListObjectsInput{ + Bucket: aws.String(p.bucket), + Prefix: aws.String(prefix), + Marker: aws.String(marker), + } + objectsResult, err := p.client.ListObjects(input) + if err != nil { + return nil, err + } + + for _, obj := range objectsResult.Contents { + fObj := Object{ + Key: aws.StringValue(obj.Key), + ETag: strings.Trim(aws.StringValue(obj.ETag), `"`), + } + + objects = append(objects, fObj) + } + + if aws.BoolValue(objectsResult.IsTruncated) { + marker = aws.StringValue(objectsResult.NextMarker) + } else { + break + } + } + + return objects, nil +} + +func (p *S3Provider) Move(object, newObject string) error { + input := &s3.CopyObjectInput{ + Bucket: aws.String(p.bucket), + CopySource: aws.String(object), + Key: aws.String(newObject), + } + if _, err := p.client.CopyObject(input); err != nil { + return err + } + + return p.ObjectDelete(object) +} + func (p *S3Provider) SignedPutURL(key, filetype string, public bool) (string, http.Header, error) { acl := s3.ObjectCannedACLAuthenticatedRead if public { diff --git a/internal/pkg/provider/provider_uss.go b/internal/pkg/provider/provider_uss.go index e347bcc..bc32886 100644 --- a/internal/pkg/provider/provider_uss.go +++ b/internal/pkg/provider/provider_uss.go @@ -1,8 +1,13 @@ package provider import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" "fmt" "net/http" + "path/filepath" + "strings" "time" "github.com/upyun/go-sdk/v3/upyun" @@ -28,15 +33,22 @@ func (p *USSProvider) SetupCORS() error { return nil } -func (p *USSProvider) SignedPutURL(key, filetype string, public bool) (url string, headers http.Header, err error) { - // todo 完成token计算,参考https://help.upyun.com/knowledge-base/object_storage_authorization/ +func (p *USSProvider) List(prefix string) ([]Object, error) { + panic("implement me") +} + +func (p *USSProvider) Move(object, newObject string) error { + panic("implement me") +} +func (p *USSProvider) SignedPutURL(key, filetype string, public bool) (url string, headers http.Header, err error) { headers = make(http.Header) - expireAt := time.Now().Add(time.Minute).Unix() + expireAt := time.Now().Add(time.Minute * 15).Unix() + uriPrefix := fmt.Sprintf("/%s/%s", p.client.Bucket, strings.TrimSuffix(key, filepath.Ext(key))) headers.Set("X-Upyun-Expire", fmt.Sprint(expireAt)) - headers.Set("X-Upyun-Uri-Prefix", fmt.Sprintf("%s/%s", p.client.Bucket, key)) + headers.Set("X-Upyun-Uri-Prefix", uriPrefix) headers.Set("Content-Type", filetype) - //headers.Set("Authorization", token) + headers.Set("Authorization", p.buildSign("PUT", uriPrefix, fmt.Sprint(expireAt))) return fmt.Sprintf("http://v0.api.upyun.com/%s/%s", p.client.Bucket, key), headers, err } @@ -68,3 +80,10 @@ func (p *USSProvider) ObjectsDelete(keys []string) error { return nil } + +func (p *USSProvider) buildSign(items ...string) string { + mac := hmac.New(sha1.New, []byte(p.client.Password)) + mac.Write([]byte(strings.Join(items, "&"))) + signStr := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + return fmt.Sprintf("UPYUN %s:%s", p.client.Operator, signStr) +}