1//go:build go1.7
2// +build go1.7
3
4package s3
5
6import (
7	"bytes"
8	"fmt"
9	"io/ioutil"
10	"net/http"
11	"net/http/httptest"
12	"strings"
13	"testing"
14
15	"github.com/aws/aws-sdk-go/aws"
16	"github.com/aws/aws-sdk-go/aws/endpoints"
17	"github.com/aws/aws-sdk-go/aws/request"
18	"github.com/aws/aws-sdk-go/awstesting/unit"
19)
20
21func TestEndpoint(t *testing.T) {
22	cases := map[string]struct {
23		bucket                string
24		config                *aws.Config
25		req                   func(svc *S3) *request.Request
26		expectedEndpoint      string
27		expectedSigningName   string
28		expectedSigningRegion string
29		expectedErr           string
30	}{
31		"standard custom endpoint url": {
32			bucket: "bucketname",
33			config: &aws.Config{
34				Region:   aws.String("us-west-2"),
35				Endpoint: aws.String("beta.example.com"),
36			},
37			expectedEndpoint:      "https://bucketname.beta.example.com",
38			expectedSigningName:   "s3",
39			expectedSigningRegion: "us-west-2",
40		},
41		"Object Lambda with no UseARNRegion flag set": {
42			bucket: "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myap",
43			config: &aws.Config{
44				Region: aws.String("us-west-2"),
45			},
46			expectedEndpoint:      "https://myap-123456789012.s3-object-lambda.us-west-2.amazonaws.com",
47			expectedSigningName:   "s3-object-lambda",
48			expectedSigningRegion: "us-west-2",
49		},
50		"Object Lambda with UseARNRegion flag set": {
51			bucket: "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myap",
52			config: &aws.Config{
53				Region:         aws.String("us-west-2"),
54				S3UseARNRegion: aws.Bool(true),
55			},
56			expectedEndpoint:      "https://myap-123456789012.s3-object-lambda.us-east-1.amazonaws.com",
57			expectedSigningName:   "s3-object-lambda",
58			expectedSigningRegion: "us-east-1",
59		},
60		"Object Lambda with Cross-Region error": {
61			bucket: "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myap",
62			config: &aws.Config{
63				Region: aws.String("us-west-2"),
64			},
65			expectedErr: "client region does not match provided ARN region",
66		},
67		"Object Lambda Pseudo-Region with UseARNRegion flag set": {
68			bucket: "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myap",
69			config: &aws.Config{
70				Region:         aws.String("aws-global"),
71				S3UseARNRegion: aws.Bool(true),
72			},
73			expectedEndpoint:      "https://myap-123456789012.s3-object-lambda.us-east-1.amazonaws.com",
74			expectedSigningRegion: "us-east-1",
75			expectedSigningName:   "s3-object-lambda",
76		},
77		"Object Lambda Cross-Region DualStack error": {
78			bucket: "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myap",
79			config: &aws.Config{
80				Region:         aws.String("us-west-2"),
81				UseDualStack:   aws.Bool(true),
82				S3UseARNRegion: aws.Bool(true),
83			},
84			expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN",
85		},
86		"Object Lambda Cross-Partition error": {
87			bucket: "arn:aws-cn:s3-object-lambda:cn-north-1:123456789012:accesspoint/myap",
88			config: &aws.Config{
89				Region:         aws.String("us-west-2"),
90				S3UseARNRegion: aws.Bool(true),
91			},
92			expectedErr: "client partition does not match provided ARN partition",
93		},
94		"Object Lambda FIPS Pseudo-Region": {
95			bucket: "arn:aws-us-gov:s3-object-lambda:us-gov-west-1:123456789012:accesspoint/myap",
96			config: &aws.Config{
97				Region: aws.String("fips-us-gov-west-1"),
98			},
99			expectedEndpoint:      "https://myap-123456789012.s3-object-lambda-fips.us-gov-west-1.amazonaws.com",
100			expectedSigningRegion: "us-gov-west-1",
101			expectedSigningName:   "s3-object-lambda",
102		},
103		"Object Lambda FIPS Pseudo-Region with UseARNRegion flag set": {
104			bucket: "arn:aws-us-gov:s3-object-lambda:us-gov-west-1:123456789012:accesspoint/myap",
105			config: &aws.Config{
106				Region:         aws.String("fips-us-gov-west-1"),
107				S3UseARNRegion: aws.Bool(true),
108			},
109			expectedEndpoint:      "https://myap-123456789012.s3-object-lambda-fips.us-gov-west-1.amazonaws.com",
110			expectedSigningRegion: "us-gov-west-1",
111			expectedSigningName:   "s3-object-lambda",
112		},
113		"Object Lambda with Accelerate": {
114			bucket: "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:myendpoint",
115			config: &aws.Config{
116				Region:          aws.String("us-west-2"),
117				S3UseAccelerate: aws.Bool(true),
118			},
119			expectedErr: "client configured for S3 Accelerate but is not supported with resource ARN",
120		},
121		"Object Lambda with Custom Endpoint": {
122			bucket: "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:myendpoint",
123			config: &aws.Config{
124				Region:   aws.String("us-west-2"),
125				Endpoint: aws.String("my-domain.com"),
126			},
127			expectedEndpoint:      "https://myendpoint-123456789012.my-domain.com",
128			expectedSigningName:   "s3-object-lambda",
129			expectedSigningRegion: "us-west-2",
130		},
131		"AccessPoint with custom endpoint url": {
132			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
133			config: &aws.Config{
134				Region:   aws.String("us-west-2"),
135				Endpoint: aws.String("beta.example.com"),
136			},
137			expectedEndpoint:      "https://myendpoint-123456789012.beta.example.com",
138			expectedSigningName:   "s3",
139			expectedSigningRegion: "us-west-2",
140		},
141		"Outpost AccessPoint with custom endpoint url": {
142			bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
143			config: &aws.Config{
144				Region:   aws.String("us-west-2"),
145				Endpoint: aws.String("beta.example.com"),
146			},
147			expectedEndpoint:      "https://myaccesspoint-123456789012.op-01234567890123456.beta.example.com",
148			expectedSigningName:   "s3-outposts",
149			expectedSigningRegion: "us-west-2",
150		},
151		"ListBucket with custom endpoint url": {
152			config: &aws.Config{
153				Region:   aws.String("us-west-2"),
154				Endpoint: aws.String("bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com"),
155			},
156			req: func(svc *S3) *request.Request {
157				req, _ := svc.ListBucketsRequest(&ListBucketsInput{})
158				return req
159			},
160			expectedEndpoint:      "https://bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com",
161			expectedSigningName:   "s3",
162			expectedSigningRegion: "us-west-2",
163		},
164		"Path-style addressing with custom endpoint url": {
165			bucket: "bucketname",
166			config: &aws.Config{
167				Region:           aws.String("us-west-2"),
168				Endpoint:         aws.String("bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com"),
169				S3ForcePathStyle: aws.Bool(true),
170			},
171			expectedEndpoint:      "https://bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com",
172			expectedSigningName:   "s3",
173			expectedSigningRegion: "us-west-2",
174		},
175		"Virtual host addressing with custom endpoint url": {
176			bucket: "bucketname",
177			config: &aws.Config{
178				Region:   aws.String("us-west-2"),
179				Endpoint: aws.String("bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com"),
180			},
181			expectedEndpoint:      "https://bucketname.bucket.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com",
182			expectedSigningName:   "s3",
183			expectedSigningRegion: "us-west-2",
184		},
185		"Access-point with custom endpoint url and use_arn_region set": {
186			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
187			config: &aws.Config{
188				Region:         aws.String("eu-west-1"),
189				Endpoint:       aws.String("accesspoint.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com"),
190				S3UseARNRegion: aws.Bool(true),
191			},
192			expectedEndpoint:      "https://myendpoint-123456789012.accesspoint.vpce-123-abc.s3.us-west-2.vpce.amazonaws.com",
193			expectedSigningName:   "s3",
194			expectedSigningRegion: "us-west-2",
195		},
196		"Custom endpoint url with Dualstack": {
197			bucket: "bucketname",
198			config: &aws.Config{
199				Region:       aws.String("us-west-2"),
200				Endpoint:     aws.String("beta.example.com"),
201				UseDualStack: aws.Bool(true),
202			},
203			expectedEndpoint:      "https://bucketname.beta.example.com",
204			expectedSigningName:   "s3",
205			expectedSigningRegion: "us-west-2",
206		},
207		"Outpost with custom endpoint url and Dualstack": {
208			bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
209			config: &aws.Config{
210				Region:       aws.String("us-west-2"),
211				Endpoint:     aws.String("beta.example.com"),
212				UseDualStack: aws.Bool(true),
213			},
214			expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN",
215		},
216		"Outpost AccessPoint with no S3UseARNRegion flag set": {
217			bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
218			config: &aws.Config{
219				Region: aws.String("us-west-2"),
220			},
221			expectedEndpoint:      "https://myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-west-2.amazonaws.com",
222			expectedSigningName:   "s3-outposts",
223			expectedSigningRegion: "us-west-2",
224		},
225		"Outpost AccessPoint Cross-Region Enabled": {
226			bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
227			config: &aws.Config{
228				Region:         aws.String("us-west-2"),
229				S3UseARNRegion: aws.Bool(true),
230			},
231			expectedEndpoint:      "https://myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-east-1.amazonaws.com",
232			expectedSigningName:   "s3-outposts",
233			expectedSigningRegion: "us-east-1",
234		},
235		"Outpost AccessPoint Cross-Region Disabled": {
236			bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
237			config: &aws.Config{
238				Region: aws.String("us-west-2"),
239			},
240			expectedErr: "client region does not match provided ARN region",
241		},
242		"Outpost AccessPoint other partition": {
243			bucket: "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
244			config: &aws.Config{
245				Region:         aws.String("us-west-2"),
246				S3UseARNRegion: aws.Bool(true),
247			},
248			expectedErr: "ConfigurationError: client partition does not match provided ARN partition",
249		},
250		"Outpost AccessPoint cn partition": {
251			bucket: "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
252			config: &aws.Config{
253				Region: aws.String("cn-north-1"),
254			},
255			expectedEndpoint:      "https://myaccesspoint-123456789012.op-01234567890123456.s3-outposts.cn-north-1.amazonaws.com.cn",
256			expectedSigningName:   "s3-outposts",
257			expectedSigningRegion: "cn-north-1",
258		},
259		"Outpost AccessPoint us-gov region": {
260			bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
261			config: &aws.Config{
262				Region:         aws.String("us-gov-east-1"),
263				S3UseARNRegion: aws.Bool(true),
264			},
265			expectedEndpoint:      "https://myaccesspoint-123456789012.op-01234567890123456.s3-outposts.us-gov-east-1.amazonaws.com",
266			expectedSigningName:   "s3-outposts",
267			expectedSigningRegion: "us-gov-east-1",
268		},
269		"Outpost AccessPoint FIPS client region, resolved signing region does not match ARN region": {
270			bucket: "arn:aws-us-gov:s3-outposts:us-gov-unknown-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
271			config: &aws.Config{
272				EndpointResolver: endpoints.AwsUsGovPartition(),
273				Region:           aws.String("fips-us-gov-unknown-1"),
274			},
275			expectedErr: "ConfigurationError: client region does not match provided ARN region",
276		},
277		"Outpost AccessPoint FIPS client region, resolved signing region does match ARN region": {
278			bucket: "arn:aws-us-gov:s3-outposts:us-gov-west-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
279			config: &aws.Config{
280				EndpointResolver: endpoints.AwsUsGovPartition(),
281				Region:           aws.String("fips-us-gov-west-1"),
282			},
283			expectedErr: "use of ARN is not supported when client or request is configured for FIPS",
284		},
285		"Outpost AccessPoint FIPS client region with matching ARN region": {
286			bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
287			config: &aws.Config{
288				EndpointResolver: endpoints.AwsUsGovPartition(),
289				Region:           aws.String("fips-us-gov-east-1"),
290				S3UseARNRegion:   aws.Bool(true),
291			},
292			expectedErr: "use of ARN is not supported when client or request is configured for FIPS",
293		},
294		"Outpost AccessPoint FIPS client region with cross-region ARN": {
295			bucket: "arn:aws-us-gov:s3-outposts:us-gov-west-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
296			config: &aws.Config{
297				EndpointResolver: endpoints.AwsUsGovPartition(),
298				Region:           aws.String("fips-us-gov-east-1"),
299				S3UseARNRegion:   aws.Bool(true),
300			},
301			expectedErr: "use of ARN is not supported when client or request is configured for FIPS",
302		},
303		"Outpost AccessPoint with DualStack": {
304			bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
305			config: &aws.Config{
306				Region:       aws.String("us-west-2"),
307				UseDualStack: aws.Bool(true),
308			},
309			expectedErr: "ConfigurationError: client configured for S3 Dual-stack but is not supported with resource ARN",
310		},
311		"Outpost AccessPoint with Accelerate": {
312			bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
313			config: &aws.Config{
314				Region:          aws.String("us-west-2"),
315				S3UseAccelerate: aws.Bool(true),
316			},
317			expectedErr: "ConfigurationError: client configured for S3 Accelerate but is not supported with resource ARN",
318		},
319		"AccessPoint": {
320			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
321			config: &aws.Config{
322				Region: aws.String("us-west-2"),
323			},
324			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com",
325			expectedSigningName:   "s3",
326			expectedSigningRegion: "us-west-2",
327		},
328		"AccessPoint slash delimiter": {
329			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint/myendpoint",
330			config: &aws.Config{
331				Region: aws.String("us-west-2"),
332			},
333			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com",
334			expectedSigningName:   "s3",
335			expectedSigningRegion: "us-west-2",
336		},
337		"AccessPoint other partition": {
338			bucket: "arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint",
339			config: &aws.Config{
340				Region: aws.String("cn-north-1"),
341			},
342			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.cn-north-1.amazonaws.com.cn",
343			expectedSigningName:   "s3",
344			expectedSigningRegion: "cn-north-1",
345		},
346		"AccessPoint Cross-Region Disabled": {
347			bucket: "arn:aws:s3:ap-south-1:123456789012:accesspoint:myendpoint",
348			config: &aws.Config{
349				Region: aws.String("us-west-2"),
350			},
351			expectedErr: "client region does not match provided ARN region",
352		},
353		"AccessPoint Cross-Region Enabled": {
354			bucket: "arn:aws:s3:ap-south-1:123456789012:accesspoint:myendpoint",
355			config: &aws.Config{
356				Region:         aws.String("us-west-2"),
357				S3UseARNRegion: aws.Bool(true),
358			},
359			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.ap-south-1.amazonaws.com",
360			expectedSigningName:   "s3",
361			expectedSigningRegion: "ap-south-1",
362		},
363		"AccessPoint us-east-1": {
364			bucket: "arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint",
365			config: &aws.Config{
366				Region:         aws.String("us-east-1"),
367				S3UseARNRegion: aws.Bool(true),
368			},
369			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com",
370			expectedSigningName:   "s3",
371			expectedSigningRegion: "us-east-1",
372		},
373		"AccessPoint us-east-1 cross region": {
374			bucket: "arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint",
375			config: &aws.Config{
376				Region:         aws.String("us-west-2"),
377				S3UseARNRegion: aws.Bool(true),
378			},
379			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-east-1.amazonaws.com",
380			expectedSigningName:   "s3",
381			expectedSigningRegion: "us-east-1",
382		},
383		"AccessPoint Cross-Partition not supported": {
384			bucket: "arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint",
385			config: &aws.Config{
386				Region:         aws.String("us-west-2"),
387				UseDualStack:   aws.Bool(true),
388				S3UseARNRegion: aws.Bool(true),
389			},
390			expectedErr: "client partition does not match provided ARN partition",
391		},
392		"AccessPoint DualStack": {
393			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
394			config: &aws.Config{
395				Region:       aws.String("us-west-2"),
396				UseDualStack: aws.Bool(true),
397			},
398			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.dualstack.us-west-2.amazonaws.com",
399			expectedSigningName:   "s3",
400			expectedSigningRegion: "us-west-2",
401		},
402		"AccessPoint FIPS same region with cross region disabled": {
403			bucket: "arn:aws-us-gov:s3:us-gov-west-1:123456789012:accesspoint:myendpoint",
404			config: &aws.Config{
405				Region: aws.String("fips-us-gov-west-1"),
406				EndpointResolver: endpoints.ResolverFunc(
407					func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
408						switch region {
409						case "fips-us-gov-west-1":
410							return endpoints.ResolvedEndpoint{
411								URL:           "s3-fips.us-gov-west-1.amazonaws.com",
412								PartitionID:   "aws-us-gov",
413								SigningRegion: "us-gov-west-1",
414								SigningName:   service,
415								SigningMethod: "s3v4",
416							}, nil
417						}
418						return endpoints.ResolvedEndpoint{}, nil
419					}),
420			},
421			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint-fips.us-gov-west-1.amazonaws.com",
422			expectedSigningName:   "s3",
423			expectedSigningRegion: "us-gov-west-1",
424		},
425		"AccessPoint FIPS same region with cross region enabled": {
426			bucket: "arn:aws-us-gov:s3:us-gov-west-1:123456789012:accesspoint:myendpoint",
427			config: &aws.Config{
428				Region: aws.String("fips-us-gov-west-1"),
429				EndpointResolver: endpoints.ResolverFunc(
430					func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
431						switch region {
432						case "fips-us-gov-west-1":
433							return endpoints.ResolvedEndpoint{
434								URL:           "s3-fips.us-gov-west-1.amazonaws.com",
435								PartitionID:   "aws-us-gov",
436								SigningRegion: "us-gov-west-1",
437								SigningName:   service,
438								SigningMethod: "s3v4",
439							}, nil
440						}
441						return endpoints.ResolvedEndpoint{}, nil
442					}),
443				S3UseARNRegion: aws.Bool(true),
444			},
445			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint-fips.us-gov-west-1.amazonaws.com",
446			expectedSigningName:   "s3",
447			expectedSigningRegion: "us-gov-west-1",
448		},
449		"AccessPoint FIPS cross region not supported": {
450			bucket: "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint",
451			config: &aws.Config{
452				Region:         aws.String("fips-us-gov-west-1"),
453				S3UseARNRegion: aws.Bool(true),
454			},
455			expectedErr: "client configured for FIPS",
456		},
457		"AccessPoint Accelerate not supported": {
458			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
459			config: &aws.Config{
460				Region:          aws.String("us-west-2"),
461				S3UseAccelerate: aws.Bool(true),
462			},
463			expectedErr: "client configured for S3 Accelerate",
464		},
465		"Custom Resolver Without PartitionID in ClientInfo": {
466			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
467			config: &aws.Config{
468				Region: aws.String("us-west-2"),
469				EndpointResolver: endpoints.ResolverFunc(
470					func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
471						switch region {
472						case "us-west-2":
473							return endpoints.ResolvedEndpoint{
474								URL:           "s3.us-west-2.amazonaws.com",
475								SigningRegion: "us-west-2",
476								SigningName:   service,
477								SigningMethod: "s3v4",
478							}, nil
479						}
480						return endpoints.ResolvedEndpoint{}, nil
481					}),
482			},
483			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com",
484			expectedSigningRegion: "us-west-2",
485			expectedSigningName:   "s3",
486		},
487		"Custom Resolver Without PartitionID in Cross-Region Target": {
488			bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
489			config: &aws.Config{
490				Region:         aws.String("us-east-1"),
491				S3UseARNRegion: aws.Bool(true),
492				EndpointResolver: endpoints.ResolverFunc(
493					func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
494						switch region {
495						case "us-west-2":
496							return endpoints.ResolvedEndpoint{
497								URL:           "s3.us-west-2.amazonaws.com",
498								PartitionID:   "aws",
499								SigningRegion: "us-west-2",
500								SigningName:   service,
501								SigningMethod: "s3v4",
502							}, nil
503						case "us-east-1":
504							return endpoints.ResolvedEndpoint{
505								URL:           "s3.us-east-1.amazonaws.com",
506								SigningRegion: "us-east-1",
507								SigningName:   service,
508								SigningMethod: "s3v4",
509							}, nil
510						}
511
512						return endpoints.ResolvedEndpoint{}, nil
513					}),
514			},
515			expectedEndpoint:      "https://myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com",
516			expectedSigningRegion: "us-west-2",
517			expectedSigningName:   "s3",
518		},
519		"bucket host-style": {
520			bucket:                "mock-bucket",
521			config:                &aws.Config{Region: aws.String("us-west-2")},
522			expectedEndpoint:      "https://mock-bucket.s3.us-west-2.amazonaws.com",
523			expectedSigningName:   "s3",
524			expectedSigningRegion: "us-west-2",
525		},
526		"bucket path-style": {
527			bucket: "mock-bucket",
528			config: &aws.Config{
529				Region:           aws.String("us-west-2"),
530				S3ForcePathStyle: aws.Bool(true),
531			},
532			expectedEndpoint:      "https://s3.us-west-2.amazonaws.com",
533			expectedSigningName:   "s3",
534			expectedSigningRegion: "us-west-2",
535		},
536		"bucket host-style endpoint with default port": {
537			bucket: "mock-bucket",
538			config: &aws.Config{
539				Region:   aws.String("us-west-2"),
540				Endpoint: aws.String("https://s3.us-west-2.amazonaws.com:443"),
541			},
542			expectedEndpoint:      "https://mock-bucket.s3.us-west-2.amazonaws.com:443",
543			expectedSigningName:   "s3",
544			expectedSigningRegion: "us-west-2",
545		},
546		"bucket host-style endpoint with non-default port": {
547			bucket: "mock-bucket",
548			config: &aws.Config{
549				Region:   aws.String("us-west-2"),
550				Endpoint: aws.String("https://s3.us-west-2.amazonaws.com:8443"),
551			},
552			expectedEndpoint:      "https://mock-bucket.s3.us-west-2.amazonaws.com:8443",
553			expectedSigningName:   "s3",
554			expectedSigningRegion: "us-west-2",
555		},
556		"bucket path-style endpoint with default port": {
557			bucket: "mock-bucket",
558			config: &aws.Config{
559				Region:           aws.String("us-west-2"),
560				Endpoint:         aws.String("https://s3.us-west-2.amazonaws.com:443"),
561				S3ForcePathStyle: aws.Bool(true),
562			},
563			expectedEndpoint:      "https://s3.us-west-2.amazonaws.com:443",
564			expectedSigningName:   "s3",
565			expectedSigningRegion: "us-west-2",
566		},
567		"bucket path-style endpoint with non-default port": {
568			bucket: "mock-bucket",
569			config: &aws.Config{
570				Region:           aws.String("us-west-2"),
571				Endpoint:         aws.String("https://s3.us-west-2.amazonaws.com:8443"),
572				S3ForcePathStyle: aws.Bool(true),
573			},
574			expectedEndpoint:      "https://s3.us-west-2.amazonaws.com:8443",
575			expectedSigningName:   "s3",
576			expectedSigningRegion: "us-west-2",
577		},
578		"Invalid AccessPoint ARN with FIPS pseudo-region (prefix)": {
579			bucket: "arn:aws:s3:fips-us-east-1:123456789012:accesspoint:myendpoint",
580			config: &aws.Config{
581				Region:         aws.String("us-west-2"),
582				S3UseARNRegion: aws.Bool(true),
583			},
584			expectedErr: "FIPS region not allowed in ARN",
585		},
586		"Invalid AccessPoint ARN with FIPS pseudo-region (suffix)": {
587			bucket: "arn:aws:s3:us-east-1-fips:123456789012:accesspoint:myendpoint",
588			config: &aws.Config{
589				Region:         aws.String("us-west-2"),
590				S3UseARNRegion: aws.Bool(true),
591			},
592			expectedErr: "FIPS region not allowed in ARN",
593		},
594		"Invalid Outpost AccessPoint ARN with FIPS pseudo-region (prefix)": {
595			bucket: "arn:aws:s3-outposts:fips-us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
596			config: &aws.Config{
597				Region:         aws.String("us-west-2"),
598				S3UseARNRegion: aws.Bool(true),
599			},
600			expectedErr: "FIPS region not allowed in ARN",
601		},
602		"Invalid Outpost AccessPoint ARN with FIPS pseudo-region (suffix)": {
603			bucket: "arn:aws:s3-outposts:us-east-1-fips:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint",
604			config: &aws.Config{
605				Region:         aws.String("us-west-2"),
606				S3UseARNRegion: aws.Bool(true),
607			},
608			expectedErr: "FIPS region not allowed in ARN",
609		},
610		"Invalid Object Lambda ARN with FIPS pseudo-region (prefix)": {
611			bucket: "arn:aws:s3-object-lambda:fips-us-east-1:123456789012:accesspoint/myap",
612			config: &aws.Config{
613				Region:         aws.String("us-west-2"),
614				S3UseARNRegion: aws.Bool(true),
615			},
616			expectedErr: "FIPS region not allowed in ARN",
617		},
618		"Invalid Object Lambda ARN with FIPS pseudo-region (suffix)": {
619			bucket: "arn:aws:s3-object-lambda:us-east-1-fips:123456789012:accesspoint/myap",
620			config: &aws.Config{
621				Region:         aws.String("us-west-2"),
622				S3UseARNRegion: aws.Bool(true),
623			},
624			expectedErr: "FIPS region not allowed in ARN",
625		},
626	}
627
628	for name, c := range cases {
629		t.Run(name, func(t *testing.T) {
630			if strings.EqualFold("az", name) {
631				fmt.Print()
632			}
633
634			sess := unit.Session.Copy(c.config)
635
636			svc := New(sess)
637
638			var req *request.Request
639			if c.req == nil {
640				req, _ = svc.ListObjectsRequest(&ListObjectsInput{
641					Bucket: &c.bucket,
642				})
643			} else {
644				req = c.req(svc)
645			}
646
647			req.Handlers.Send.Clear()
648			req.Handlers.Send.PushBack(func(r *request.Request) {
649				defer func() {
650					r.HTTPResponse = &http.Response{
651						StatusCode:    200,
652						ContentLength: 0,
653						Body:          ioutil.NopCloser(bytes.NewReader(nil)),
654					}
655				}()
656				if len(c.expectedErr) != 0 {
657					return
658				}
659
660				endpoint := fmt.Sprintf("%s://%s", r.HTTPRequest.URL.Scheme, r.HTTPRequest.URL.Host)
661				if e, a := c.expectedEndpoint, endpoint; e != a {
662					t.Errorf("expected %v, got %v", e, a)
663				}
664
665				if e, a := c.expectedSigningName, r.ClientInfo.SigningName; e != a {
666					t.Errorf("expected %v, got %v", e, a)
667				}
668				if e, a := c.expectedSigningRegion, r.ClientInfo.SigningRegion; e != a {
669					t.Errorf("expected %v, got %v", e, a)
670				}
671			})
672			err := req.Send()
673			if len(c.expectedErr) == 0 && err != nil {
674				t.Errorf("expected no error but got: %v", err)
675			} else if len(c.expectedErr) != 0 && err == nil {
676				t.Errorf("expected err %q, but got nil", c.expectedErr)
677			} else if len(c.expectedErr) != 0 && err != nil && !strings.Contains(err.Error(), c.expectedErr) {
678				t.Errorf("expected %v, got %v", c.expectedErr, err.Error())
679			}
680		})
681	}
682}
683
684func TestWriteGetObjectResponse_UpdateEndpoint(t *testing.T) {
685	cases := map[string]struct {
686		config                *aws.Config
687		expectedEndpoint      string
688		expectedSigningRegion string
689		expectedSigningName   string
690		expectedErr           string
691	}{
692		"standard endpoint": {
693			config: &aws.Config{
694				Region: aws.String("us-west-2"),
695			},
696			expectedEndpoint:      "https://test-route.s3-object-lambda.us-west-2.amazonaws.com",
697			expectedSigningRegion: "us-west-2",
698			expectedSigningName:   "s3-object-lambda",
699		},
700		"fips endpoint": {
701			config: &aws.Config{
702				Region: aws.String("fips-us-gov-west-1"),
703			},
704			expectedEndpoint:      "https://test-route.s3-object-lambda-fips.us-gov-west-1.amazonaws.com",
705			expectedSigningRegion: "us-gov-west-1",
706			expectedSigningName:   "s3-object-lambda",
707		},
708		"duakstack endpoint": {
709			config: &aws.Config{
710				Region:       aws.String("us-west-2"),
711				UseDualStack: aws.Bool(true),
712			},
713			expectedErr: "client configured for dualstack but not supported for operation",
714		},
715		"accelerate endpoint": {
716			config: &aws.Config{
717				Region:          aws.String("us-west-2"),
718				S3UseAccelerate: aws.Bool(true),
719			},
720			expectedErr: "client configured for accelerate but not supported for operation",
721		},
722		"custom endpoint": {
723			config: &aws.Config{
724				Region:   aws.String("us-west-2"),
725				Endpoint: aws.String("https://my-domain.com"),
726			},
727			expectedEndpoint:      "https://test-route.my-domain.com",
728			expectedSigningRegion: "us-west-2",
729			expectedSigningName:   "s3-object-lambda",
730		},
731	}
732
733	for name, c := range cases {
734		t.Run(name, func(t *testing.T) {
735			sess := unit.Session.Copy(c.config)
736
737			svc := New(sess)
738
739			var req *request.Request
740			req, _ = svc.WriteGetObjectResponseRequest(&WriteGetObjectResponseInput{
741				RequestRoute: aws.String("test-route"),
742				RequestToken: aws.String("test-token"),
743			})
744
745			req.Handlers.Send.Clear()
746			req.Handlers.Send.PushBack(func(r *request.Request) {
747				defer func() {
748					r.HTTPResponse = &http.Response{
749						StatusCode:    200,
750						ContentLength: 0,
751						Body:          ioutil.NopCloser(bytes.NewReader(nil)),
752					}
753				}()
754				if len(c.expectedErr) != 0 {
755					return
756				}
757
758				endpoint := fmt.Sprintf("%s://%s", r.HTTPRequest.URL.Scheme, r.HTTPRequest.URL.Host)
759				if e, a := c.expectedEndpoint, endpoint; e != a {
760					t.Errorf("expected %v, got %v", e, a)
761				}
762
763				if e, a := c.expectedSigningName, r.ClientInfo.SigningName; e != a {
764					t.Errorf("expected %v, got %v", e, a)
765				}
766				if e, a := c.expectedSigningRegion, r.ClientInfo.SigningRegion; e != a {
767					t.Errorf("expected %v, got %v", e, a)
768				}
769			})
770			err := req.Send()
771			if len(c.expectedErr) == 0 && err != nil {
772				t.Errorf("expected no error but got: %v", err)
773			} else if len(c.expectedErr) != 0 && err == nil {
774				t.Errorf("expected err %q, but got nil", c.expectedErr)
775			} else if len(c.expectedErr) != 0 && err != nil && !strings.Contains(err.Error(), c.expectedErr) {
776				t.Errorf("expected %v, got %v", c.expectedErr, err.Error())
777			}
778		})
779	}
780}
781
782type readSeeker struct {
783	br *bytes.Reader
784}
785
786func (r *readSeeker) Read(p []byte) (int, error) {
787	return r.br.Read(p)
788}
789
790func (r *readSeeker) Seek(offset int64, whence int) (int64, error) {
791	return r.br.Seek(offset, whence)
792}
793
794type readOnlyReader struct {
795	br *bytes.Reader
796}
797
798func (r *readOnlyReader) Read(p []byte) (int, error) {
799	return r.br.Read(p)
800}
801
802type lenReader struct {
803	br *bytes.Reader
804}
805
806func (r *lenReader) Read(p []byte) (int, error) {
807	return r.br.Read(p)
808}
809
810func (r *lenReader) Len() int {
811	return r.br.Len()
812}
813
814func TestWriteGetObjectResponse(t *testing.T) {
815	cases := map[string]struct {
816		Handler func(*testing.T) http.Handler
817		Input   WriteGetObjectResponseInput
818	}{
819		"Content-Length seekable": {
820			Handler: func(t *testing.T) http.Handler {
821				return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
822					expectedInput := []byte("test input")
823
824					if len(request.TransferEncoding) != 0 {
825						t.Errorf("expect no transfer-encoding")
826					}
827
828					if e, a := fmt.Sprintf("%d", len(expectedInput)), request.Header.Get("Content-Length"); e != a {
829						t.Errorf("expect %v, got %v", e, a)
830					}
831
832					if e, a := "UNSIGNED-PAYLOAD", request.Header.Get("X-Amz-Content-Sha256"); e != a {
833						t.Errorf("expect %v, got %v", e, a)
834					}
835
836					all, err := ioutil.ReadAll(request.Body)
837					if err != nil {
838						t.Errorf("expect no error, got %v", err)
839					}
840					if !bytes.Equal(all, expectedInput) {
841						t.Error("input did not match expected")
842					}
843					writer.WriteHeader(200)
844				})
845			},
846			Input: WriteGetObjectResponseInput{
847				RequestRoute: aws.String("route"),
848				RequestToken: aws.String("token"),
849				Body:         &readSeeker{br: bytes.NewReader([]byte("test input"))},
850			},
851		},
852		"Content-Length Len Interface": {
853			Handler: func(t *testing.T) http.Handler {
854				return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
855					expectedInput := []byte("test input")
856
857					if len(request.TransferEncoding) != 0 {
858						t.Errorf("expect no transfer-encoding")
859					}
860
861					if e, a := fmt.Sprintf("%d", len(expectedInput)), request.Header.Get("Content-Length"); e != a {
862						t.Errorf("expect %v, got %v", e, a)
863					}
864
865					if e, a := "UNSIGNED-PAYLOAD", request.Header.Get("X-Amz-Content-Sha256"); e != a {
866						t.Errorf("expect %v, got %v", e, a)
867					}
868
869					all, err := ioutil.ReadAll(request.Body)
870					if err != nil {
871						t.Errorf("expect no error, got %v", err)
872					}
873					if !bytes.Equal(all, expectedInput) {
874						t.Error("input did not match expected")
875					}
876					writer.WriteHeader(200)
877				})
878			},
879			Input: WriteGetObjectResponseInput{
880				RequestRoute: aws.String("route"),
881				RequestToken: aws.String("token"),
882				Body:         aws.ReadSeekCloser(&lenReader{bytes.NewReader([]byte("test input"))}),
883			},
884		},
885		"Content-Length Input Parameter": {
886			Handler: func(t *testing.T) http.Handler {
887				return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
888					expectedInput := []byte("test input")
889
890					if len(request.TransferEncoding) != 0 {
891						t.Errorf("expect no transfer-encoding")
892					}
893
894					if e, a := fmt.Sprintf("%d", len(expectedInput)), request.Header.Get("Content-Length"); e != a {
895						t.Errorf("expect %v, got %v", e, a)
896					}
897
898					if e, a := "UNSIGNED-PAYLOAD", request.Header.Get("X-Amz-Content-Sha256"); e != a {
899						t.Errorf("expect %v, got %v", e, a)
900					}
901
902					all, err := ioutil.ReadAll(request.Body)
903					if err != nil {
904						t.Errorf("expect no error, got %v", err)
905					}
906					if !bytes.Equal(all, expectedInput) {
907						t.Error("input did not match expected")
908					}
909					writer.WriteHeader(200)
910				})
911			},
912			Input: WriteGetObjectResponseInput{
913				RequestRoute:  aws.String("route"),
914				RequestToken:  aws.String("token"),
915				Body:          aws.ReadSeekCloser(&readOnlyReader{bytes.NewReader([]byte("test input"))}),
916				ContentLength: aws.Int64(10),
917			},
918		},
919		"Content-Length Not Provided": {
920			Handler: func(t *testing.T) http.Handler {
921				return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
922					expectedInput := []byte("test input")
923
924					encoding := ""
925					if len(request.TransferEncoding) == 1 {
926						encoding = request.TransferEncoding[0]
927					}
928					if encoding != "chunked" {
929						t.Errorf("expect transfer-encoding chunked, got %v", encoding)
930					}
931
932					if e, a := "", request.Header.Get("Content-Length"); e != a {
933						t.Errorf("expect %v, got %v", e, a)
934					}
935
936					if e, a := "UNSIGNED-PAYLOAD", request.Header.Get("X-Amz-Content-Sha256"); e != a {
937						t.Errorf("expect %v, got %v", e, a)
938					}
939
940					all, err := ioutil.ReadAll(request.Body)
941					if err != nil {
942						t.Errorf("expect no error, got %v", err)
943					}
944					if !bytes.Equal(all, expectedInput) {
945						t.Error("input did not match expected")
946					}
947					writer.WriteHeader(200)
948				})
949			},
950			Input: WriteGetObjectResponseInput{
951				RequestRoute: aws.String("route"),
952				RequestToken: aws.String("token"),
953				Body:         aws.ReadSeekCloser(&readOnlyReader{bytes.NewReader([]byte("test input"))}),
954			},
955		},
956	}
957
958	for name, tt := range cases {
959		t.Run(name, func(t *testing.T) {
960			server := httptest.NewServer(tt.Handler(t))
961			defer server.Close()
962
963			sess := unit.Session.Copy(&aws.Config{
964				Region:                    aws.String("us-west-2"),
965				Endpoint:                  &server.URL,
966				DisableEndpointHostPrefix: aws.Bool(true),
967			})
968
969			client := New(sess)
970
971			_, err := client.WriteGetObjectResponse(&tt.Input)
972			if err != nil {
973				t.Fatalf("expect no error, got %v", err)
974			}
975		})
976	}
977}
978