@@ -20,18 +20,66 @@ export function getSignatureKey(
2020 return kSigning
2121}
2222
23- export function parseS3Uri ( s3Uri : string ) : {
23+ export function parseS3Uri (
24+ s3Uri : string ,
25+ fallbackRegion ?: string
26+ ) : {
2427 bucketName : string
2528 region : string
2629 objectKey : string
2730} {
2831 try {
2932 const url = new URL ( s3Uri )
3033 const hostname = url . hostname
31- const bucketName = hostname . split ( '.' ) [ 0 ]
32- const regionMatch = hostname . match ( / s 3 [ . - ] ( [ ^ . ] + ) \. a m a z o n a w s \. c o m / )
33- const region = regionMatch ? regionMatch [ 1 ] : 'us-east-1'
34- const objectKey = url . pathname . startsWith ( '/' ) ? url . pathname . substring ( 1 ) : url . pathname
34+ const normalizedPath = url . pathname . startsWith ( '/' ) ? url . pathname . slice ( 1 ) : url . pathname
35+
36+ const virtualHostedDualstackMatch = hostname . match (
37+ / ^ ( .+ ) \. s 3 \. d u a l s t a c k \. ( [ ^ . ] + ) \. a m a z o n a w s \. c o m (?: \. c n ) ? $ /
38+ )
39+ const virtualHostedRegionalMatch = hostname . match (
40+ / ^ ( .+ ) \. s 3 [ . - ] ( [ ^ . ] + ) \. a m a z o n a w s \. c o m (?: \. c n ) ? $ /
41+ )
42+ const virtualHostedGlobalMatch = hostname . match ( / ^ ( .+ ) \. s 3 \. a m a z o n a w s \. c o m (?: \. c n ) ? $ / )
43+
44+ const pathStyleDualstackMatch = hostname . match (
45+ / ^ s 3 \. d u a l s t a c k \. ( [ ^ . ] + ) \. a m a z o n a w s \. c o m (?: \. c n ) ? $ /
46+ )
47+ const pathStyleRegionalMatch = hostname . match ( / ^ s 3 [ . - ] ( [ ^ . ] + ) \. a m a z o n a w s \. c o m (?: \. c n ) ? $ / )
48+ const pathStyleGlobalMatch = hostname . match ( / ^ s 3 \. a m a z o n a w s \. c o m (?: \. c n ) ? $ / )
49+
50+ const isPathStyleHost = Boolean (
51+ pathStyleDualstackMatch || pathStyleRegionalMatch || pathStyleGlobalMatch
52+ )
53+
54+ const firstSlashIndex = normalizedPath . indexOf ( '/' )
55+ const pathStyleBucketName =
56+ firstSlashIndex === - 1 ? normalizedPath : normalizedPath . slice ( 0 , firstSlashIndex )
57+ const pathStyleObjectKey =
58+ firstSlashIndex === - 1 ? '' : normalizedPath . slice ( firstSlashIndex + 1 )
59+
60+ const bucketName = isPathStyleHost
61+ ? pathStyleBucketName
62+ : ( virtualHostedDualstackMatch ?. [ 1 ] ??
63+ virtualHostedRegionalMatch ?. [ 1 ] ??
64+ virtualHostedGlobalMatch ?. [ 1 ] ??
65+ '' )
66+
67+ const rawObjectKey = isPathStyleHost ? pathStyleObjectKey : normalizedPath
68+ const objectKey = ( ( ) => {
69+ try {
70+ return decodeURIComponent ( rawObjectKey )
71+ } catch {
72+ return rawObjectKey
73+ }
74+ } ) ( )
75+
76+ const normalizedFallbackRegion = fallbackRegion ?. trim ( )
77+ const regionFromHost =
78+ virtualHostedDualstackMatch ?. [ 2 ] ??
79+ virtualHostedRegionalMatch ?. [ 2 ] ??
80+ pathStyleDualstackMatch ?. [ 1 ] ??
81+ pathStyleRegionalMatch ?. [ 1 ]
82+ const region = regionFromHost || normalizedFallbackRegion || 'us-east-1'
3583
3684 if ( ! bucketName || ! objectKey ) {
3785 throw new Error ( 'Invalid S3 URI format' )
@@ -40,7 +88,7 @@ export function parseS3Uri(s3Uri: string): {
4088 return { bucketName, region, objectKey }
4189 } catch ( _error ) {
4290 throw new Error (
43- 'Invalid S3 Object URL format. Expected format: https://bucket-name.s3.region.amazonaws.com/ path/to/file '
91+ 'Invalid S3 Object URL format. Expected S3 virtual-hosted or path-style URL with object key. '
4492 )
4593 }
4694}
0 commit comments