diff --git a/drivers/s3/driver.go b/drivers/s3/driver.go index a037912f6a..46d7f414a3 100644 --- a/drivers/s3/driver.go +++ b/drivers/s3/driver.go @@ -193,7 +193,7 @@ func (d *S3) Remove(ctx context.Context, obj model.Obj) error { if obj.IsDir() { return d.removeDir(ctx, obj.GetPath()) } - return d.removeFile(obj.GetPath()) + return d.removeFile(ctx, obj.GetPath()) } func (d *S3) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error { @@ -249,7 +249,15 @@ func (d *S3) GetDirectUploadInfo(ctx context.Context, _ string, dstDir model.Obj // implements driver.Getter interface func (d *S3) Get(ctx context.Context, path string) (model.Obj, error) { // try to get object as a file using HeadObject - path = stdpath.Join(d.GetRootPath(), path) + rootPath := d.GetRootPath() + // Avoid double-prepending root path when path already contains it. + // This happens when obj.GetPath() from a previous Get call is passed back + // to op.List → op.Get → d.Get. + if rootPath != "/" && utils.IsSubPath(rootPath, path) { + // path already includes the root prefix + } else { + path = stdpath.Join(rootPath, path) + } key := getKey(path, false) headInput := &s3.HeadObjectInput{ Bucket: &d.Bucket, diff --git a/drivers/s3/meta.go b/drivers/s3/meta.go index b2439c683a..4243f12d56 100644 --- a/drivers/s3/meta.go +++ b/drivers/s3/meta.go @@ -23,6 +23,7 @@ type Addition struct { AddFilenameToDisposition bool `json:"add_filename_to_disposition" help:"Add filename to Content-Disposition header."` EnableDirectUpload bool `json:"enable_direct_upload" default:"false"` DirectUploadHost string `json:"direct_upload_host" required:"false"` + UserAgent string `json:"user_agent" required:"false" default:"" help:"Custom User-Agent for S3 requests."` } func init() { diff --git a/drivers/s3/util.go b/drivers/s3/util.go index c84380786a..8500ed2645 100644 --- a/drivers/s3/util.go +++ b/drivers/s3/util.go @@ -49,6 +49,11 @@ const ( func (d *S3) getClient(clientType int) *s3.S3 { client := s3.New(d.Session) + if d.UserAgent != "" { + client.Handlers.Build.PushBack(func(r *request.Request) { + r.HTTPRequest.Header.Set("User-Agent", d.UserAgent) + }) + } if clientType == ClientTypeLink && d.CustomHost != "" { client.Handlers.Build.PushBack(func(r *request.Request) { if r.HTTPRequest.Method != http.MethodGet { @@ -223,7 +228,7 @@ func (d *S3) copyFile(ctx context.Context, src string, dst string) error { CopySource: aws.String(encodedKey), Key: &dstKey, } - _, err := d.client.CopyObject(input) + _, err := d.client.CopyObjectWithContext(ctx, input) return err } @@ -257,23 +262,23 @@ func (d *S3) removeDir(ctx context.Context, src string) error { if obj.IsDir() { err = d.removeDir(ctx, cSrc) } else { - err = d.removeFile(cSrc) + err = d.removeFile(ctx, cSrc) } if err != nil { return err } } - _ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder))) - _ = d.removeFile(path.Join(src, d.Placeholder)) + _ = d.removeFile(ctx, path.Join(src, getPlaceholderName(d.Placeholder))) + _ = d.removeFile(ctx, path.Join(src, d.Placeholder)) return nil } -func (d *S3) removeFile(src string) error { +func (d *S3) removeFile(ctx context.Context, src string) error { key := getKey(src, false) input := &s3.DeleteObjectInput{ Bucket: &d.Bucket, Key: &key, } - _, err := d.client.DeleteObject(input) + _, err := d.client.DeleteObjectWithContext(ctx, input) return err }