1package v4
2
3import (
4	"bytes"
5	"io"
6	"io/ioutil"
7	"net/http"
8	"net/http/httptest"
9	"reflect"
10	"strconv"
11	"strings"
12	"testing"
13	"time"
14
15	"github.com/aws/aws-sdk-go/aws"
16	"github.com/aws/aws-sdk-go/aws/credentials"
17	"github.com/aws/aws-sdk-go/aws/request"
18	"github.com/aws/aws-sdk-go/awstesting"
19)
20
21func epochTime() time.Time { return time.Unix(0, 0) }
22
23func TestStripExcessHeaders(t *testing.T) {
24	vals := []string{
25		"",
26		"123",
27		"1 2 3",
28		"1 2 3 ",
29		"  1 2 3",
30		"1  2 3",
31		"1  23",
32		"1  2  3",
33		"1  2  ",
34		" 1  2  ",
35		"12   3",
36		"12   3   1",
37		"12           3     1",
38		"12     3       1abc123",
39	}
40
41	expected := []string{
42		"",
43		"123",
44		"1 2 3",
45		"1 2 3",
46		"1 2 3",
47		"1 2 3",
48		"1 23",
49		"1 2 3",
50		"1 2",
51		"1 2",
52		"12 3",
53		"12 3 1",
54		"12 3 1",
55		"12 3 1abc123",
56	}
57
58	stripExcessSpaces(vals)
59	for i := 0; i < len(vals); i++ {
60		if e, a := expected[i], vals[i]; e != a {
61			t.Errorf("%d, expect %v, got %v", i, e, a)
62		}
63	}
64}
65
66func buildRequest(serviceName, region, body string) (*http.Request, io.ReadSeeker) {
67	reader := strings.NewReader(body)
68	return buildRequestWithBodyReader(serviceName, region, reader)
69}
70
71func buildRequestReaderSeeker(serviceName, region, body string) (*http.Request, io.ReadSeeker) {
72	reader := &readerSeekerWrapper{strings.NewReader(body)}
73	return buildRequestWithBodyReader(serviceName, region, reader)
74}
75
76func buildRequestWithBodyReader(serviceName, region string, body io.Reader) (*http.Request, io.ReadSeeker) {
77	var bodyLen int
78
79	type lenner interface {
80		Len() int
81	}
82	if lr, ok := body.(lenner); ok {
83		bodyLen = lr.Len()
84	}
85
86	endpoint := "https://" + serviceName + "." + region + ".amazonaws.com"
87	req, _ := http.NewRequest("POST", endpoint, body)
88	req.URL.Opaque = "//example.org/bucket/key-._~,!@#$%^&*()"
89	req.Header.Set("X-Amz-Target", "prefix.Operation")
90	req.Header.Set("Content-Type", "application/x-amz-json-1.0")
91
92	if bodyLen > 0 {
93		req.Header.Set("Content-Length", strconv.Itoa(bodyLen))
94	}
95
96	req.Header.Set("X-Amz-Meta-Other-Header", "some-value=!@#$%^&* (+)")
97	req.Header.Add("X-Amz-Meta-Other-Header_With_Underscore", "some-value=!@#$%^&* (+)")
98	req.Header.Add("X-amz-Meta-Other-Header_With_Underscore", "some-value=!@#$%^&* (+)")
99
100	var seeker io.ReadSeeker
101	if sr, ok := body.(io.ReadSeeker); ok {
102		seeker = sr
103	} else {
104		seeker = aws.ReadSeekCloser(body)
105	}
106
107	return req, seeker
108}
109
110func buildSigner() Signer {
111	return Signer{
112		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
113	}
114}
115
116func removeWS(text string) string {
117	text = strings.Replace(text, " ", "", -1)
118	text = strings.Replace(text, "\n", "", -1)
119	text = strings.Replace(text, "\t", "", -1)
120	return text
121}
122
123func assertEqual(t *testing.T, expected, given string) {
124	if removeWS(expected) != removeWS(given) {
125		t.Errorf("\nExpected: %s\nGiven:    %s", expected, given)
126	}
127}
128
129func TestPresignRequest(t *testing.T) {
130	req, body := buildRequest("dynamodb", "us-east-1", "{}")
131
132	signer := buildSigner()
133	signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, epochTime())
134
135	expectedDate := "19700101T000000Z"
136	expectedHeaders := "content-length;content-type;host;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore"
137	expectedSig := "122f0b9e091e4ba84286097e2b3404a1f1f4c4aad479adda95b7dff0ccbe5581"
138	expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
139	expectedTarget := "prefix.Operation"
140
141	q := req.URL.Query()
142	if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
143		t.Errorf("expect %v, got %v", e, a)
144	}
145	if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
146		t.Errorf("expect %v, got %v", e, a)
147	}
148	if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
149		t.Errorf("expect %v, got %v", e, a)
150	}
151	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
152		t.Errorf("expect %v, got %v", e, a)
153	}
154	if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
155		t.Errorf("expect %v to be empty", a)
156	}
157	if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
158		t.Errorf("expect %v, got %v", e, a)
159	}
160}
161
162func TestPresignBodyWithArrayRequest(t *testing.T) {
163	req, body := buildRequest("dynamodb", "us-east-1", "{}")
164	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
165
166	signer := buildSigner()
167	signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, epochTime())
168
169	expectedDate := "19700101T000000Z"
170	expectedHeaders := "content-length;content-type;host;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore"
171	expectedSig := "e3ac55addee8711b76c6d608d762cff285fe8b627a057f8b5ec9268cf82c08b1"
172	expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
173	expectedTarget := "prefix.Operation"
174
175	q := req.URL.Query()
176	if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
177		t.Errorf("expect %v, got %v", e, a)
178	}
179	if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
180		t.Errorf("expect %v, got %v", e, a)
181	}
182	if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
183		t.Errorf("expect %v, got %v", e, a)
184	}
185	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
186		t.Errorf("expect %v, got %v", e, a)
187	}
188	if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
189		t.Errorf("expect %v to be empty, was not", a)
190	}
191	if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
192		t.Errorf("expect %v, got %v", e, a)
193	}
194}
195
196func TestSignRequest(t *testing.T) {
197	req, body := buildRequest("dynamodb", "us-east-1", "{}")
198	signer := buildSigner()
199	signer.Sign(req, body, "dynamodb", "us-east-1", epochTime())
200
201	expectedDate := "19700101T000000Z"
202	expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore;x-amz-security-token;x-amz-target, Signature=a518299330494908a70222cec6899f6f32f297f8595f6df1776d998936652ad9"
203
204	q := req.Header
205	if e, a := expectedSig, q.Get("Authorization"); e != a {
206		t.Errorf("expect\n%v\nactual\n%v\n", e, a)
207	}
208	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
209		t.Errorf("expect\n%v\nactual\n%v\n", e, a)
210	}
211}
212
213func TestSignBodyS3(t *testing.T) {
214	req, body := buildRequest("s3", "us-east-1", "hello")
215	signer := buildSigner()
216	signer.Sign(req, body, "s3", "us-east-1", time.Now())
217	hash := req.Header.Get("X-Amz-Content-Sha256")
218	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
219		t.Errorf("expect %v, got %v", e, a)
220	}
221}
222
223func TestSignBodyGlacier(t *testing.T) {
224	req, body := buildRequest("glacier", "us-east-1", "hello")
225	signer := buildSigner()
226	signer.Sign(req, body, "glacier", "us-east-1", time.Now())
227	hash := req.Header.Get("X-Amz-Content-Sha256")
228	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
229		t.Errorf("expect %v, got %v", e, a)
230	}
231}
232
233func TestPresign_SignedPayload(t *testing.T) {
234	req, body := buildRequest("glacier", "us-east-1", "hello")
235	signer := buildSigner()
236	signer.Presign(req, body, "glacier", "us-east-1", 5*time.Minute, time.Now())
237	hash := req.Header.Get("X-Amz-Content-Sha256")
238	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
239		t.Errorf("expect %v, got %v", e, a)
240	}
241}
242
243func TestPresign_UnsignedPayload(t *testing.T) {
244	req, body := buildRequest("service-name", "us-east-1", "hello")
245	signer := buildSigner()
246	signer.UnsignedPayload = true
247	signer.Presign(req, body, "service-name", "us-east-1", 5*time.Minute, time.Now())
248	hash := req.Header.Get("X-Amz-Content-Sha256")
249	if e, a := "UNSIGNED-PAYLOAD", hash; e != a {
250		t.Errorf("expect %v, got %v", e, a)
251	}
252}
253
254func TestPresign_UnsignedPayload_S3(t *testing.T) {
255	req, body := buildRequest("s3", "us-east-1", "hello")
256	signer := buildSigner()
257	signer.Presign(req, body, "s3", "us-east-1", 5*time.Minute, time.Now())
258	if a := req.Header.Get("X-Amz-Content-Sha256"); len(a) != 0 {
259		t.Errorf("expect no content sha256 got %v", a)
260	}
261}
262
263func TestSignUnseekableBody(t *testing.T) {
264	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
265	signer := buildSigner()
266	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
267	if err == nil {
268		t.Fatalf("expect error signing request")
269	}
270
271	if e, a := "unseekable request body", err.Error(); !strings.Contains(a, e) {
272		t.Errorf("expect %q to be in %q", e, a)
273	}
274}
275
276func TestSignUnsignedPayloadUnseekableBody(t *testing.T) {
277	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
278
279	signer := buildSigner()
280	signer.UnsignedPayload = true
281
282	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
283	if err != nil {
284		t.Fatalf("expect no error, got %v", err)
285	}
286
287	hash := req.Header.Get("X-Amz-Content-Sha256")
288	if e, a := "UNSIGNED-PAYLOAD", hash; e != a {
289		t.Errorf("expect %v, got %v", e, a)
290	}
291}
292
293func TestSignPreComputedHashUnseekableBody(t *testing.T) {
294	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
295
296	signer := buildSigner()
297
298	req.Header.Set("X-Amz-Content-Sha256", "some-content-sha256")
299	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
300	if err != nil {
301		t.Fatalf("expect no error, got %v", err)
302	}
303
304	hash := req.Header.Get("X-Amz-Content-Sha256")
305	if e, a := "some-content-sha256", hash; e != a {
306		t.Errorf("expect %v, got %v", e, a)
307	}
308}
309
310func TestSignPrecomputedBodyChecksum(t *testing.T) {
311	req, body := buildRequest("dynamodb", "us-east-1", "hello")
312	req.Header.Set("X-Amz-Content-Sha256", "PRECOMPUTED")
313	signer := buildSigner()
314	signer.Sign(req, body, "dynamodb", "us-east-1", time.Now())
315	hash := req.Header.Get("X-Amz-Content-Sha256")
316	if e, a := "PRECOMPUTED", hash; e != a {
317		t.Errorf("expect %v, got %v", e, a)
318	}
319}
320
321func TestAnonymousCredentials(t *testing.T) {
322	svc := awstesting.NewClient(&aws.Config{Credentials: credentials.AnonymousCredentials})
323	r := svc.NewRequest(
324		&request.Operation{
325			Name:       "BatchGetItem",
326			HTTPMethod: "POST",
327			HTTPPath:   "/",
328		},
329		nil,
330		nil,
331	)
332	SignSDKRequest(r)
333
334	urlQ := r.HTTPRequest.URL.Query()
335	if a := urlQ.Get("X-Amz-Signature"); len(a) != 0 {
336		t.Errorf("expect %v to be empty, was not", a)
337	}
338	if a := urlQ.Get("X-Amz-Credential"); len(a) != 0 {
339		t.Errorf("expect %v to be empty, was not", a)
340	}
341	if a := urlQ.Get("X-Amz-SignedHeaders"); len(a) != 0 {
342		t.Errorf("expect %v to be empty, was not", a)
343	}
344	if a := urlQ.Get("X-Amz-Date"); len(a) != 0 {
345		t.Errorf("expect %v to be empty, was not", a)
346	}
347
348	hQ := r.HTTPRequest.Header
349	if a := hQ.Get("Authorization"); len(a) != 0 {
350		t.Errorf("expect %v to be empty, was not", a)
351	}
352	if a := hQ.Get("X-Amz-Date"); len(a) != 0 {
353		t.Errorf("expect %v to be empty, was not", a)
354	}
355}
356
357func TestIgnoreResignRequestWithValidCreds(t *testing.T) {
358	svc := awstesting.NewClient(&aws.Config{
359		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
360		Region:      aws.String("us-west-2"),
361	})
362	r := svc.NewRequest(
363		&request.Operation{
364			Name:       "BatchGetItem",
365			HTTPMethod: "POST",
366			HTTPPath:   "/",
367		},
368		nil,
369		nil,
370	)
371
372	SignSDKRequest(r)
373	sig := r.HTTPRequest.Header.Get("Authorization")
374
375	SignSDKRequestWithCurrentTime(r, func() time.Time {
376		// Simulate one second has passed so that signature's date changes
377		// when it is resigned.
378		return time.Now().Add(1 * time.Second)
379	})
380	if e, a := sig, r.HTTPRequest.Header.Get("Authorization"); e == a {
381		t.Errorf("expect %v to be %v, but was not", e, a)
382	}
383}
384
385func TestIgnorePreResignRequestWithValidCreds(t *testing.T) {
386	svc := awstesting.NewClient(&aws.Config{
387		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
388		Region:      aws.String("us-west-2"),
389	})
390	r := svc.NewRequest(
391		&request.Operation{
392			Name:       "BatchGetItem",
393			HTTPMethod: "POST",
394			HTTPPath:   "/",
395		},
396		nil,
397		nil,
398	)
399	r.ExpireTime = time.Minute * 10
400
401	SignSDKRequest(r)
402	sig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
403
404	SignSDKRequestWithCurrentTime(r, func() time.Time {
405		// Simulate one second has passed so that signature's date changes
406		// when it is resigned.
407		return time.Now().Add(1 * time.Second)
408	})
409	if e, a := sig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
410		t.Errorf("expect %v to be %v, but was not", e, a)
411	}
412}
413
414func TestResignRequestExpiredCreds(t *testing.T) {
415	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
416	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
417	r := svc.NewRequest(
418		&request.Operation{
419			Name:       "BatchGetItem",
420			HTTPMethod: "POST",
421			HTTPPath:   "/",
422		},
423		nil,
424		nil,
425	)
426	SignSDKRequest(r)
427	querySig := r.HTTPRequest.Header.Get("Authorization")
428	var origSignedHeaders string
429	for _, p := range strings.Split(querySig, ", ") {
430		if strings.HasPrefix(p, "SignedHeaders=") {
431			origSignedHeaders = p[len("SignedHeaders="):]
432			break
433		}
434	}
435	if a := origSignedHeaders; len(a) == 0 {
436		t.Errorf("expect not to be empty, but was")
437	}
438	if e, a := origSignedHeaders, "authorization"; strings.Contains(a, e) {
439		t.Errorf("expect %v to not be in %v, but was", e, a)
440	}
441	origSignedAt := r.LastSignedAt
442
443	creds.Expire()
444
445	SignSDKRequestWithCurrentTime(r, func() time.Time {
446		// Simulate one second has passed so that signature's date changes
447		// when it is resigned.
448		return time.Now().Add(1 * time.Second)
449	})
450	updatedQuerySig := r.HTTPRequest.Header.Get("Authorization")
451	if e, a := querySig, updatedQuerySig; e == a {
452		t.Errorf("expect %v to be %v, was not", e, a)
453	}
454
455	var updatedSignedHeaders string
456	for _, p := range strings.Split(updatedQuerySig, ", ") {
457		if strings.HasPrefix(p, "SignedHeaders=") {
458			updatedSignedHeaders = p[len("SignedHeaders="):]
459			break
460		}
461	}
462	if a := updatedSignedHeaders; len(a) == 0 {
463		t.Errorf("expect not to be empty, but was")
464	}
465	if e, a := updatedQuerySig, "authorization"; strings.Contains(a, e) {
466		t.Errorf("expect %v to not be in %v, but was", e, a)
467	}
468	if e, a := origSignedAt, r.LastSignedAt; e == a {
469		t.Errorf("expect %v to be %v, was not", e, a)
470	}
471}
472
473func TestPreResignRequestExpiredCreds(t *testing.T) {
474	provider := &credentials.StaticProvider{Value: credentials.Value{
475		AccessKeyID:     "AKID",
476		SecretAccessKey: "SECRET",
477		SessionToken:    "SESSION",
478	}}
479	creds := credentials.NewCredentials(provider)
480	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
481	r := svc.NewRequest(
482		&request.Operation{
483			Name:       "BatchGetItem",
484			HTTPMethod: "POST",
485			HTTPPath:   "/",
486		},
487		nil,
488		nil,
489	)
490	r.ExpireTime = time.Minute * 10
491
492	SignSDKRequest(r)
493	querySig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
494	signedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
495	if a := signedHeaders; len(a) == 0 {
496		t.Errorf("expect not to be empty, but was")
497	}
498	origSignedAt := r.LastSignedAt
499
500	creds.Expire()
501
502	SignSDKRequestWithCurrentTime(r, func() time.Time {
503		// Simulate the request occurred 15 minutes in the past
504		return time.Now().Add(-48 * time.Hour)
505	})
506	if e, a := querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
507		t.Errorf("expect %v to be %v, was not", e, a)
508	}
509	resignedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
510	if e, a := signedHeaders, resignedHeaders; e != a {
511		t.Errorf("expect %v, got %v", e, a)
512	}
513	if e, a := signedHeaders, "x-amz-signedHeaders"; strings.Contains(a, e) {
514		t.Errorf("expect %v to not be in %v, but was", e, a)
515	}
516	if e, a := origSignedAt, r.LastSignedAt; e == a {
517		t.Errorf("expect %v to be %v, was not", e, a)
518	}
519}
520
521func TestResignRequestExpiredRequest(t *testing.T) {
522	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
523	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
524	r := svc.NewRequest(
525		&request.Operation{
526			Name:       "BatchGetItem",
527			HTTPMethod: "POST",
528			HTTPPath:   "/",
529		},
530		nil,
531		nil,
532	)
533
534	SignSDKRequest(r)
535	querySig := r.HTTPRequest.Header.Get("Authorization")
536	origSignedAt := r.LastSignedAt
537
538	SignSDKRequestWithCurrentTime(r, func() time.Time {
539		// Simulate the request occurred 15 minutes in the past
540		return time.Now().Add(15 * time.Minute)
541	})
542	if e, a := querySig, r.HTTPRequest.Header.Get("Authorization"); e == a {
543		t.Errorf("expect %v to be %v, was not", e, a)
544	}
545	if e, a := origSignedAt, r.LastSignedAt; e == a {
546		t.Errorf("expect %v to be %v, was not", e, a)
547	}
548}
549
550func TestSignWithRequestBody(t *testing.T) {
551	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
552	signer := NewSigner(creds)
553
554	expectBody := []byte("abc123")
555
556	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
557		b, err := ioutil.ReadAll(r.Body)
558		r.Body.Close()
559		if err != nil {
560			t.Errorf("expect no error, got %v", err)
561		}
562		if e, a := expectBody, b; !reflect.DeepEqual(e, a) {
563			t.Errorf("expect %v, got %v", e, a)
564		}
565		w.WriteHeader(http.StatusOK)
566	}))
567	defer server.Close()
568
569	req, err := http.NewRequest("POST", server.URL, nil)
570	if err != nil {
571		t.Errorf("expect not no error, got %v", err)
572	}
573
574	_, err = signer.Sign(req, bytes.NewReader(expectBody), "service", "region", time.Now())
575	if err != nil {
576		t.Errorf("expect not no error, got %v", err)
577	}
578
579	resp, err := http.DefaultClient.Do(req)
580	if err != nil {
581		t.Errorf("expect not no error, got %v", err)
582	}
583	if e, a := http.StatusOK, resp.StatusCode; e != a {
584		t.Errorf("expect %v, got %v", e, a)
585	}
586}
587
588func TestSignWithRequestBody_Overwrite(t *testing.T) {
589	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
590	signer := NewSigner(creds)
591
592	var expectBody []byte
593
594	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
595		b, err := ioutil.ReadAll(r.Body)
596		r.Body.Close()
597		if err != nil {
598			t.Errorf("expect not no error, got %v", err)
599		}
600		if e, a := len(expectBody), len(b); e != a {
601			t.Errorf("expect %v, got %v", e, a)
602		}
603		w.WriteHeader(http.StatusOK)
604	}))
605	defer server.Close()
606
607	req, err := http.NewRequest("GET", server.URL, strings.NewReader("invalid body"))
608	if err != nil {
609		t.Errorf("expect not no error, got %v", err)
610	}
611
612	_, err = signer.Sign(req, nil, "service", "region", time.Now())
613	req.ContentLength = 0
614
615	if err != nil {
616		t.Errorf("expect not no error, got %v", err)
617	}
618
619	resp, err := http.DefaultClient.Do(req)
620	if err != nil {
621		t.Errorf("expect not no error, got %v", err)
622	}
623	if e, a := http.StatusOK, resp.StatusCode; e != a {
624		t.Errorf("expect %v, got %v", e, a)
625	}
626}
627
628func TestBuildCanonicalRequest(t *testing.T) {
629	req, body := buildRequest("dynamodb", "us-east-1", "{}")
630	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
631	ctx := &signingCtx{
632		ServiceName: "dynamodb",
633		Region:      "us-east-1",
634		Request:     req,
635		Body:        body,
636		Query:       req.URL.Query(),
637		Time:        time.Now(),
638		ExpireTime:  5 * time.Second,
639	}
640
641	ctx.buildCanonicalString()
642	expected := "https://example.org/bucket/key-._~,!@#$%^&*()?Foo=z&Foo=o&Foo=m&Foo=a"
643	if e, a := expected, ctx.Request.URL.String(); e != a {
644		t.Errorf("expect %v, got %v", e, a)
645	}
646}
647
648func TestSignWithBody_ReplaceRequestBody(t *testing.T) {
649	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
650	req, seekerBody := buildRequest("dynamodb", "us-east-1", "{}")
651	req.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
652
653	s := NewSigner(creds)
654	origBody := req.Body
655
656	_, err := s.Sign(req, seekerBody, "dynamodb", "us-east-1", time.Now())
657	if err != nil {
658		t.Fatalf("expect no error, got %v", err)
659	}
660
661	if req.Body == origBody {
662		t.Errorf("expeect request body to not be origBody")
663	}
664
665	if req.Body == nil {
666		t.Errorf("expect request body to be changed but was nil")
667	}
668}
669
670func TestSignWithBody_NoReplaceRequestBody(t *testing.T) {
671	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
672	req, seekerBody := buildRequest("dynamodb", "us-east-1", "{}")
673	req.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
674
675	s := NewSigner(creds, func(signer *Signer) {
676		signer.DisableRequestBodyOverwrite = true
677	})
678
679	origBody := req.Body
680
681	_, err := s.Sign(req, seekerBody, "dynamodb", "us-east-1", time.Now())
682	if err != nil {
683		t.Fatalf("expect no error, got %v", err)
684	}
685
686	if req.Body != origBody {
687		t.Errorf("expect request body to not be chagned")
688	}
689}
690
691func TestRequestHost(t *testing.T) {
692	req, body := buildRequest("dynamodb", "us-east-1", "{}")
693	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
694	req.Host = "myhost"
695	ctx := &signingCtx{
696		ServiceName: "dynamodb",
697		Region:      "us-east-1",
698		Request:     req,
699		Body:        body,
700		Query:       req.URL.Query(),
701		Time:        time.Now(),
702		ExpireTime:  5 * time.Second,
703	}
704
705	ctx.buildCanonicalHeaders(ignoredHeaders, ctx.Request.Header)
706	if !strings.Contains(ctx.canonicalHeaders, "host:"+req.Host) {
707		t.Errorf("canonical host header invalid")
708	}
709}
710
711func BenchmarkPresignRequest(b *testing.B) {
712	signer := buildSigner()
713	req, body := buildRequestReaderSeeker("dynamodb", "us-east-1", "{}")
714	for i := 0; i < b.N; i++ {
715		signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, time.Now())
716	}
717}
718
719func BenchmarkSignRequest(b *testing.B) {
720	signer := buildSigner()
721	req, body := buildRequestReaderSeeker("dynamodb", "us-east-1", "{}")
722	for i := 0; i < b.N; i++ {
723		signer.Sign(req, body, "dynamodb", "us-east-1", time.Now())
724	}
725}
726
727var stripExcessSpaceCases = []string{
728	`AWS4-HMAC-SHA256 Credential=AKIDFAKEIDFAKEID/20160628/us-west-2/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=1234567890abcdef1234567890abcdef1234567890abcdef`,
729	`123   321   123   321`,
730	`   123   321   123   321   `,
731	`   123    321    123          321   `,
732	"123",
733	"1 2 3",
734	"  1 2 3",
735	"1  2 3",
736	"1  23",
737	"1  2  3",
738	"1  2  ",
739	" 1  2  ",
740	"12   3",
741	"12   3   1",
742	"12           3     1",
743	"12     3       1abc123",
744}
745
746func BenchmarkStripExcessSpaces(b *testing.B) {
747	for i := 0; i < b.N; i++ {
748		// Make sure to start with a copy of the cases
749		cases := append([]string{}, stripExcessSpaceCases...)
750		stripExcessSpaces(cases)
751	}
752}
753
754// readerSeekerWrapper mimics the interface provided by request.offsetReader
755type readerSeekerWrapper struct {
756	r *strings.Reader
757}
758
759func (r *readerSeekerWrapper) Read(p []byte) (n int, err error) {
760	return r.r.Read(p)
761}
762
763func (r *readerSeekerWrapper) Seek(offset int64, whence int) (int64, error) {
764	return r.r.Seek(offset, whence)
765}
766
767func (r *readerSeekerWrapper) Len() int {
768	return r.r.Len()
769}
770