@@ -68,60 +68,51 @@ public struct S3Middleware: AWSMiddlewareProtocol {
68
68
if request. url. path. hasPrefix ( " /arn: " ) {
69
69
return try await handleARNBucket ( request, context: context, next: next)
70
70
}
71
- /// process URL into form ${bucket}.s3.amazon.com
71
+ /// split path into components. Drop first as it is an empty string
72
72
let paths = request. url. path. split ( separator: " / " , maxSplits: 2 , omittingEmptySubsequences: false ) . dropFirst ( )
73
73
guard let bucket = paths. first, var host = request. url. host else { return try await next ( request, context) }
74
+ let path = paths. dropFirst ( ) . first. flatMap { String ( $0) } ?? " "
74
75
75
76
if let port = request. url. port {
76
77
host = " \( host) : \( port) "
77
78
}
78
79
var urlPath : String
79
80
var urlHost : String
80
- let isAmazonUrl = host. hasSuffix ( " amazonaws.com " )
81
+ let isAmazonUrl = host. hasSuffix ( context . serviceConfig . region . partition . dnsSuffix )
81
82
82
- var hostComponents = host. split ( separator: " . " )
83
- if isAmazonUrl, context. serviceConfig. options. contains ( . s3UseTransferAcceleratedEndpoint) {
84
- if let s3Index = hostComponents. firstIndex ( where: { $0 == " s3 " } ) {
85
- var s3 = " s3 "
86
- s3 += " -accelerate "
87
- // assume next host component is region
88
- let regionIndex = s3Index + 1
89
- hostComponents. remove ( at: regionIndex)
90
- hostComponents [ s3Index] = Substring ( s3)
91
- host = hostComponents. joined ( separator: " . " )
92
- }
93
- }
83
+ // if bucket has suffix "-x-s3" then it must be an s3 express directory bucket and the endpoint needs set up
84
+ // to use s3express
85
+ if bucket. hasSuffix ( " --x-s3 " ) , let host = getS3ExpressBucketEndpoint ( bucket: bucket, path: path, context: context) {
86
+ urlHost = host
87
+ urlPath = path
88
+ } else {
94
89
95
- // Is bucket an ARN
96
- if bucket. hasPrefix ( " arn: " ) {
97
- guard let arn = ARN ( string: bucket) ,
98
- let resourceType = arn. resourceType,
99
- let region = arn. region,
100
- let accountId = arn. accountId
101
- else {
102
- throw AWSClient . ClientError. invalidARN
103
- }
104
- guard resourceType == " accesspoint " , arn. service == " s3-object-lambda " || arn. service == " s3-outposts " else {
105
- throw AWSClient . ClientError. invalidARN
90
+ // Should we use accelerated endpoint
91
+ var hostComponents = host. split ( separator: " . " )
92
+ if isAmazonUrl, context. serviceConfig. options. contains ( . s3UseTransferAcceleratedEndpoint) {
93
+ if let s3Index = hostComponents. firstIndex ( where: { $0 == " s3 " } ) {
94
+ var s3 = " s3 "
95
+ s3 += " -accelerate "
96
+ // assume next host component is region
97
+ let regionIndex = s3Index + 1
98
+ hostComponents. remove ( at: regionIndex)
99
+ hostComponents [ s3Index] = Substring ( s3)
100
+ host = hostComponents. joined ( separator: " . " )
101
+ }
106
102
}
107
- urlPath = " / "
108
- // https://tutorial-object-lambda-accesspoint-123456789012.s3-object-lambda.us-west-2.amazonaws.com:443
109
- urlHost = " \( arn. resourceId) - \( resourceType) - \( accountId) . \( arn. service) . \( region) .amazonaws.com "
110
103
111
- // if host name contains amazonaws.com and bucket name doesn't contain a period do virtual address look up
112
- } else if isAmazonUrl || context . serviceConfig . options . contains ( . s3ForceVirtualHost ) , !bucket . contains ( " . " ) {
113
- let pathsWithoutBucket = paths . dropFirst ( ) // bucket
114
- urlPath = pathsWithoutBucket . first . flatMap { String ( $0 ) } ?? " "
115
-
116
- if hostComponents . first == bucket {
117
- // Bucket name is part of host. No need to append bucket
118
- urlHost = host
104
+ if isAmazonUrl || context . serviceConfig . options . contains ( . s3ForceVirtualHost ) , ! bucket. contains ( " . " ) {
105
+ urlPath = path
106
+ if hostComponents . first == bucket {
107
+ // Bucket name is part of host. No need to append bucket
108
+ urlHost = host
109
+ } else {
110
+ urlHost = " \( bucket) . \( host ) "
111
+ }
119
112
} else {
120
- urlHost = " \( bucket) . \( host) "
113
+ urlPath = paths. joined ( separator: " / " )
114
+ urlHost = host
121
115
}
122
- } else {
123
- urlPath = paths. joined ( separator: " / " )
124
- urlHost = host
125
116
}
126
117
let request = Self . updateRequestURL ( request, host: urlHost, path: urlPath)
127
118
return try await next ( request, context)
@@ -155,7 +146,7 @@ public struct S3Middleware: AWSMiddlewareProtocol {
155
146
let path = String ( resourceIDSplit. dropFirst ( ) . first ?? " " )
156
147
let service = String ( arn. service)
157
148
let serviceIdentifier = service != " s3 " ? service : " s3-accesspoint "
158
- let urlHost = " \( bucket) - \( accountId) . \( serviceIdentifier) . \( region) .amazonaws.com "
149
+ let urlHost = " \( bucket) - \( accountId) . \( serviceIdentifier) . \( region) . \( context . serviceConfig . region . partition . dnsSuffix ) "
159
150
let request = Self . updateRequestURL ( request, host: urlHost, path: path)
160
151
161
152
var context = context
@@ -177,11 +168,29 @@ public struct S3Middleware: AWSMiddlewareProtocol {
177
168
return try await next ( request, context)
178
169
}
179
170
171
+ /// Handle bucket names in the form of an ARN
172
+ /// - Parameters:
173
+ /// - request: request
174
+ /// - context: request context
175
+ /// - next: next handler
176
+ /// - Returns: returns response from next handler
177
+ func getS3ExpressBucketEndpoint(
178
+ bucket: Substring ,
179
+ path: String ,
180
+ context: AWSMiddlewareContext
181
+ ) -> String ? {
182
+ // Note this uses my own version of split (as the Swift one requires macOS 13)
183
+ let split = bucket. split ( separator: " -- " )
184
+ guard split. count > 2 , split. last == " x-s3 " else { return nil }
185
+ let zone = split [ split. count - 2 ]
186
+ return " \( bucket) .s3express- \( zone) . \( context. serviceConfig. region) . \( context. serviceConfig. region. partition. dnsSuffix) "
187
+ }
188
+
180
189
/// Update request with new host and path
181
190
/// - Parameters:
182
191
/// - request: request
183
192
/// - host: new host name
184
- /// - path: new path
193
+ /// - path: new path (without forward slash prefix)
185
194
/// - Returns: new request
186
195
static func updateRequestURL( _ request: AWSHTTPRequest , host: some StringProtocol , path: String ) -> AWSHTTPRequest {
187
196
var path = path
@@ -210,16 +219,18 @@ public struct S3Middleware: AWSMiddlewareProtocol {
210
219
switch context. operation {
211
220
// fixup CreateBucket to include location
212
221
case " CreateBucket " :
213
- var xml = " "
214
- if context. serviceConfig. region != . useast1 {
215
- xml += " <CreateBucketConfiguration xmlns= \" http://s3.amazonaws.com/doc/2006-03-01/ \" > "
216
- xml += " <LocationConstraint> "
217
- xml += context. serviceConfig. region. rawValue
218
- xml += " </LocationConstraint> "
219
- xml += " </CreateBucketConfiguration> "
222
+ if request. body. isEmpty {
223
+ var xml = " "
224
+ if context. serviceConfig. region != . useast1 {
225
+ xml += " <CreateBucketConfiguration xmlns= \" http://s3.amazonaws.com/doc/2006-03-01/ \" > "
226
+ xml += " <LocationConstraint> "
227
+ xml += context. serviceConfig. region. rawValue
228
+ xml += " </LocationConstraint> "
229
+ xml += " </CreateBucketConfiguration> "
230
+ }
231
+ // TODO: pass service config down so we can use the ByteBufferAllocator
232
+ request. body = . init( string: xml)
220
233
}
221
- // TODO: pass service config down so we can use the ByteBufferAllocator
222
- request. body = . init( string: xml)
223
234
224
235
default :
225
236
break
@@ -263,3 +274,35 @@ public struct S3Middleware: AWSMiddlewareProtocol {
263
274
return error
264
275
}
265
276
}
277
+
278
+ extension StringProtocol {
279
+ func split( separator: some StringProtocol ) -> [ Self . SubSequence ] {
280
+ var split : [ Self . SubSequence ] = [ ]
281
+ var index : Self . Index = self . startIndex
282
+ var startSplit : Self . Index = self . startIndex
283
+ while index != self . endIndex {
284
+ if let end = self [ index... ] . prefixEnd ( separator) {
285
+ split. append ( self [ startSplit..< index] )
286
+ startSplit = end
287
+ index = end
288
+ } else {
289
+ index = self . index ( after: index)
290
+ }
291
+ }
292
+ split. append ( self [ startSplit..< index] )
293
+ return split
294
+ }
295
+
296
+ func prefixEnd( _ prefix: some StringProtocol ) -> Self . Index ? {
297
+ var prefixIndex = prefix. startIndex
298
+ var index = self . startIndex
299
+ while prefixIndex != prefix. endIndex {
300
+ if self [ index] != prefix [ prefixIndex] {
301
+ return nil
302
+ }
303
+ index = self . index ( after: index)
304
+ prefixIndex = prefix. index ( after: prefixIndex)
305
+ }
306
+ return index
307
+ }
308
+ }
0 commit comments