|
package s3 |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"net/http" |
|
"path" |
|
"strings" |
|
|
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/internal/op" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
"github.com/aws/aws-sdk-go/aws" |
|
"github.com/aws/aws-sdk-go/aws/credentials" |
|
"github.com/aws/aws-sdk-go/aws/request" |
|
"github.com/aws/aws-sdk-go/aws/session" |
|
"github.com/aws/aws-sdk-go/service/s3" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
|
|
|
|
func (d *S3) initSession() error { |
|
var err error |
|
accessKeyID, secretAccessKey, sessionToken := d.AccessKeyID, d.SecretAccessKey, d.SessionToken |
|
if d.config.Name == "Doge" { |
|
credentialsTmp, err := getCredentials(d.AccessKeyID, d.SecretAccessKey) |
|
if err != nil { |
|
return err |
|
} |
|
accessKeyID, secretAccessKey, sessionToken = credentialsTmp.AccessKeyId, credentialsTmp.SecretAccessKey, credentialsTmp.SessionToken |
|
} |
|
cfg := &aws.Config{ |
|
Credentials: credentials.NewStaticCredentials(accessKeyID, secretAccessKey, sessionToken), |
|
Region: &d.Region, |
|
Endpoint: &d.Endpoint, |
|
S3ForcePathStyle: aws.Bool(d.ForcePathStyle), |
|
} |
|
d.Session, err = session.NewSession(cfg) |
|
return err |
|
} |
|
|
|
func (d *S3) getClient(link bool) *s3.S3 { |
|
client := s3.New(d.Session) |
|
if link && d.CustomHost != "" { |
|
client.Handlers.Build.PushBack(func(r *request.Request) { |
|
if r.HTTPRequest.Method != http.MethodGet { |
|
return |
|
} |
|
|
|
split := strings.SplitN(d.CustomHost, "://", 2) |
|
if utils.SliceContains([]string{"http", "https"}, split[0]) { |
|
r.HTTPRequest.URL.Scheme = split[0] |
|
r.HTTPRequest.URL.Host = split[1] |
|
} else { |
|
r.HTTPRequest.URL.Host = d.CustomHost |
|
} |
|
}) |
|
} |
|
return client |
|
} |
|
|
|
func getKey(path string, dir bool) string { |
|
path = strings.TrimPrefix(path, "/") |
|
if path != "" && dir { |
|
path += "/" |
|
} |
|
return path |
|
} |
|
|
|
var defaultPlaceholderName = ".alist" |
|
|
|
func getPlaceholderName(placeholder string) string { |
|
if placeholder == "" { |
|
return defaultPlaceholderName |
|
} |
|
return placeholder |
|
} |
|
|
|
func (d *S3) listV1(prefix string, args model.ListArgs) ([]model.Obj, error) { |
|
prefix = getKey(prefix, true) |
|
log.Debugf("list: %s", prefix) |
|
files := make([]model.Obj, 0) |
|
marker := "" |
|
for { |
|
input := &s3.ListObjectsInput{ |
|
Bucket: &d.Bucket, |
|
Marker: &marker, |
|
Prefix: &prefix, |
|
Delimiter: aws.String("/"), |
|
} |
|
listObjectsResult, err := d.client.ListObjects(input) |
|
if err != nil { |
|
return nil, err |
|
} |
|
for _, object := range listObjectsResult.CommonPrefixes { |
|
name := path.Base(strings.Trim(*object.Prefix, "/")) |
|
file := model.Object{ |
|
|
|
Name: name, |
|
Modified: d.Modified, |
|
IsFolder: true, |
|
} |
|
files = append(files, &file) |
|
} |
|
for _, object := range listObjectsResult.Contents { |
|
name := path.Base(*object.Key) |
|
if !args.S3ShowPlaceholder && (name == getPlaceholderName(d.Placeholder) || name == d.Placeholder) { |
|
continue |
|
} |
|
file := model.Object{ |
|
|
|
Name: name, |
|
Size: *object.Size, |
|
Modified: *object.LastModified, |
|
} |
|
files = append(files, &file) |
|
} |
|
if listObjectsResult.IsTruncated == nil { |
|
return nil, errors.New("IsTruncated nil") |
|
} |
|
if *listObjectsResult.IsTruncated { |
|
marker = *listObjectsResult.NextMarker |
|
} else { |
|
break |
|
} |
|
} |
|
return files, nil |
|
} |
|
|
|
func (d *S3) listV2(prefix string, args model.ListArgs) ([]model.Obj, error) { |
|
prefix = getKey(prefix, true) |
|
files := make([]model.Obj, 0) |
|
var continuationToken, startAfter *string |
|
for { |
|
input := &s3.ListObjectsV2Input{ |
|
Bucket: &d.Bucket, |
|
ContinuationToken: continuationToken, |
|
Prefix: &prefix, |
|
Delimiter: aws.String("/"), |
|
StartAfter: startAfter, |
|
} |
|
listObjectsResult, err := d.client.ListObjectsV2(input) |
|
if err != nil { |
|
return nil, err |
|
} |
|
log.Debugf("resp: %+v", listObjectsResult) |
|
for _, object := range listObjectsResult.CommonPrefixes { |
|
name := path.Base(strings.Trim(*object.Prefix, "/")) |
|
file := model.Object{ |
|
|
|
Name: name, |
|
Modified: d.Modified, |
|
IsFolder: true, |
|
} |
|
files = append(files, &file) |
|
} |
|
for _, object := range listObjectsResult.Contents { |
|
if strings.HasSuffix(*object.Key, "/") { |
|
continue |
|
} |
|
name := path.Base(*object.Key) |
|
if !args.S3ShowPlaceholder && (name == getPlaceholderName(d.Placeholder) || name == d.Placeholder) { |
|
continue |
|
} |
|
file := model.Object{ |
|
|
|
Name: name, |
|
Size: *object.Size, |
|
Modified: *object.LastModified, |
|
} |
|
files = append(files, &file) |
|
} |
|
if !aws.BoolValue(listObjectsResult.IsTruncated) { |
|
break |
|
} |
|
if listObjectsResult.NextContinuationToken != nil { |
|
continuationToken = listObjectsResult.NextContinuationToken |
|
continue |
|
} |
|
if len(listObjectsResult.Contents) == 0 { |
|
break |
|
} |
|
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key |
|
} |
|
return files, nil |
|
} |
|
|
|
func (d *S3) copy(ctx context.Context, src string, dst string, isDir bool) error { |
|
if isDir { |
|
return d.copyDir(ctx, src, dst) |
|
} |
|
return d.copyFile(ctx, src, dst) |
|
} |
|
|
|
func (d *S3) copyFile(ctx context.Context, src string, dst string) error { |
|
srcKey := getKey(src, false) |
|
dstKey := getKey(dst, false) |
|
input := &s3.CopyObjectInput{ |
|
Bucket: &d.Bucket, |
|
CopySource: aws.String("/" + d.Bucket + "/" + srcKey), |
|
Key: &dstKey, |
|
} |
|
_, err := d.client.CopyObject(input) |
|
return err |
|
} |
|
|
|
func (d *S3) copyDir(ctx context.Context, src string, dst string) error { |
|
objs, err := op.List(ctx, d, src, model.ListArgs{S3ShowPlaceholder: true}) |
|
if err != nil { |
|
return err |
|
} |
|
for _, obj := range objs { |
|
cSrc := path.Join(src, obj.GetName()) |
|
cDst := path.Join(dst, obj.GetName()) |
|
if obj.IsDir() { |
|
err = d.copyDir(ctx, cSrc, cDst) |
|
} else { |
|
err = d.copyFile(ctx, cSrc, cDst) |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (d *S3) removeDir(ctx context.Context, src string) error { |
|
objs, err := op.List(ctx, d, src, model.ListArgs{}) |
|
if err != nil { |
|
return err |
|
} |
|
for _, obj := range objs { |
|
cSrc := path.Join(src, obj.GetName()) |
|
if obj.IsDir() { |
|
err = d.removeDir(ctx, cSrc) |
|
} else { |
|
err = d.removeFile(cSrc) |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
_ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder))) |
|
_ = d.removeFile(path.Join(src, d.Placeholder)) |
|
return nil |
|
} |
|
|
|
func (d *S3) removeFile(src string) error { |
|
key := getKey(src, false) |
|
input := &s3.DeleteObjectInput{ |
|
Bucket: &d.Bucket, |
|
Key: &key, |
|
} |
|
_, err := d.client.DeleteObject(input) |
|
return err |
|
} |
|
|