diff --git a/.changelog/1c88b192e747436c998a32096006308c.json b/.changelog/1c88b192e747436c998a32096006308c.json new file mode 100644 index 000000000000..6e03dd07889e --- /dev/null +++ b/.changelog/1c88b192e747436c998a32096006308c.json @@ -0,0 +1,8 @@ +{ + "id": "1c88b192-e747-436c-998a-32096006308c", + "type": "bugfix", + "description": "Loose download directory check to allow downloading objects with double dot in filename", + "modules": [ + "feature/s3/transfermanager" + ] +} \ No newline at end of file diff --git a/feature/s3/transfermanager/api_op_DownloadDirectory.go b/feature/s3/transfermanager/api_op_DownloadDirectory.go index c151f49b8aa0..c244ba93fc6d 100644 --- a/feature/s3/transfermanager/api_op_DownloadDirectory.go +++ b/feature/s3/transfermanager/api_op_DownloadDirectory.go @@ -236,7 +236,7 @@ func (d *directoryDownloader) getLocalPath(key string) (string, error) { if err != nil { return "", err } - if relPath == "." || strings.Contains(relPath, "..") { + if relPath == "." || !filepath.IsLocal(relPath) { return "", fmt.Errorf("resolved local path %s is outside of destination %s", path, destination) } diff --git a/feature/s3/transfermanager/download_directory_test.go b/feature/s3/transfermanager/download_directory_test.go index 585bc467c9d4..3983c864c7f9 100644 --- a/feature/s3/transfermanager/download_directory_test.go +++ b/feature/s3/transfermanager/download_directory_test.go @@ -280,6 +280,46 @@ func TestDownloadDirectory(t *testing.T) { l.expectFailed(t, in, err) }, }, + "object key with double dots in name and path": { + destination: "double-dots-in-name-and-path", + objectsLists: [][]s3types.Object{ + { + { + Key: aws.String("foo/bar/../baz..zoo"), + }, + { + Key: aws.String("a..b"), + }, + }, + }, + expectTokens: []string{""}, + expectKeys: []string{"foo/bar/../baz..zoo", "a..b"}, + expectFiles: []string{"foo/baz..zoo", "a..b"}, + expectObjectsDownloaded: 2, + listenerValidationFn: func(t *testing.T, l *mockDirectoryListener, in, out any, err error) { + l.expectStart(t, in) + l.expectComplete(t, in, out, 2) + }, + }, + "object key with double dots in name and path escaping": { + destination: "double-dots-in-name-and-path-escaping", + objectsLists: [][]s3types.Object{ + { + { + Key: aws.String("foo/../../baz..zoo"), + }, + { + Key: aws.String("a..b"), + }, + }, + }, + expectErr: "outside of destination", + listenerValidationFn: func(t *testing.T, l *mockDirectoryListener, in, out any, err error) { + // only validate failure listener since start listener + // might never be triggerred if the error response is returned first + l.expectFailed(t, in, err) + }, + }, "multiple objects with filter applied": { destination: "multiple-objects-with-filter-applied", objectsLists: [][]s3types.Object{