s3_test.v

5.2 KB · raw

1module remote23import time45// The vectors below come from two independent sources:6//7//   1. the official AWS `get-vanilla` case from the SigV4 test suite, whose8//      signature is published with the specification;9//   2. an S3-shaped conditional PUT, computed by a separate implementation10//      written from the same specification in another language.11//12// Between them they cover the canonical request, the key derivation chain, the13// string-to-sign and the header set that S3 actually sends.14fn example_time() time.Time {15	return time.Time{16		year:     201517		month:    818		day:      3019		hour:     1220		minute:   3621		second:   022		is_local: false23	}24}2526fn test_the_official_aws_get_vanilla_vector() {27	signer := Signer{28		access_key:   'AKIDEXAMPLE'29		secret_key:   'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'30		region:       'us-east-1'31		service:      'service'32		sign_payload: false33	}34	headers := signer.sign('GET', 'https://example.amazonaws.com/', '', map[string]string{},35		[]u8{}, example_time())36	assert headers['authorization'] == 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31'37	assert headers['host'] == 'example.amazonaws.com'38	assert headers['x-amz-date'] == '20150830T123600Z'39}4041fn test_an_s3_conditional_put_signs_the_payload_and_the_condition() {42	signer := Signer{43		access_key: 'SCWACCESSKEY'44		secret_key: 'scw-secret-key'45		region:     'fr-par'46		service:    s3_service47	}48	headers := signer.sign('PUT', 'https://s3.fr-par.scw.cloud/bucket/repo/objects/abc', '', {49		'If-None-Match': '*'50	}, 'compressed-bytes'.bytes(), time.Time{51		year:     202652		month:    953		day:      2354		hour:     1255		minute:   056		second:   057		is_local: false58	})59	assert headers['authorization'] == 'AWS4-HMAC-SHA256 Credential=SCWACCESSKEY/20260923/fr-par/s3/aws4_request, SignedHeaders=host;if-none-match;x-amz-content-sha256;x-amz-date, Signature=9790aa767fc3e4c36fce70c92476933a90b401200068f06c340bf1108435580a'60	// The body is part of the signature: a different payload must change it.61	other := signer.sign('PUT', 'https://s3.fr-par.scw.cloud/bucket/repo/objects/abc', '', {62		'If-None-Match': '*'63	}, 'different-bytes'.bytes(), time.Time{64		year:     202665		month:    966		day:      2367		hour:     1268		minute:   069		second:   070		is_local: false71	})72	assert other['authorization'] != headers['authorization']73}7475fn test_urls_are_split_into_host_and_encoded_path() {76	parts := split_url('https://s3.fr-par.scw.cloud/bucket/repo/objects/abc')77	assert parts.host == 's3.fr-par.scw.cloud'78	assert parts.path == '/bucket/repo/objects/abc'7980	// Slashes in the key stay slashes; anything else is encoded.81	spaces := split_url('https://example.com/bucket/a file.txt')82	assert spaces.path == '/bucket/a%20file.txt'8384	// An endpoint with a path prefix keeps it.85	prefixed := split_url('https://example.com/prefix/bucket/key?x=1')86	assert prefixed.host == 'example.com'87	assert prefixed.path == '/prefix/bucket/key'88}8990fn test_canonical_query_is_sorted_and_encoded() {91	assert canonical_query('') == ''92	// The query arrives raw, so a slash in a key is encoded here and only here.93	assert canonical_query('prefix=b/a&list-type=2') == 'list-type=2&prefix=b%2Fa'94	assert canonical_query('prefix=repo/objects/x&continuation-token=a=b') == 'continuation-token=a%3Db&prefix=repo%2Fobjects%2Fx'95	assert canonical_query('prefix=a b') == 'prefix=a%20b'96}9798fn test_etags_lose_their_quotes() {99	assert clean_etag('"3b1f"') == '3b1f'100	assert clean_etag('  "abc123"  ') == 'abc123'101	assert clean_etag('abc123') == 'abc123'102	assert clean_etag('') == ''103}104105fn test_list_objects_responses_are_read() {106	body := '<ListBucketResult><IsTruncated>false</IsTruncated>' +107		'<Contents><Key>repo/objects/aaa</Key></Contents>' +108		'<Contents><Key>repo/objects/bbb</Key></Contents></ListBucketResult>'109	assert elements(body, 'Key') == ['repo/objects/aaa', 'repo/objects/bbb']110	assert first_element(body, 'NextContinuationToken') == none111	assert !body.contains('<IsTruncated>true</IsTruncated>')112113	paged := '<ListBucketResult><IsTruncated>true</IsTruncated>' +114		'<NextContinuationToken>aGVsbG8=</NextContinuationToken>' +115		'<Contents><Key>repo/objects/c&amp;c</Key></Contents></ListBucketResult>'116	assert first_element(paged, 'NextContinuationToken') or { '' } == 'aGVsbG8='117	assert elements(paged, 'Key') == ['repo/objects/c&c']118	assert paged.contains('<IsTruncated>true</IsTruncated>')119}120121fn test_the_constructor_refuses_settings_that_cannot_work() {122	if _ := new_s3('', 'bucket', 'region', 'ak', 'sk') {123		assert false, 'a missing endpoint must be refused'124	}125	if _ := new_s3('s3.fr-par.scw.cloud', 'bucket', 'region', 'ak', 'sk') {126		assert false, 'an endpoint without a scheme must be refused'127	}128	if _ := new_s3('https://s3.fr-par.scw.cloud', 'bucket', 'region', '', '') {129		assert false, 'missing keys must be refused'130	}131	backend := new_s3('https://s3.fr-par.scw.cloud/', 'bucket', 'fr-par', 'ak', 'sk')!132	assert backend.root == 'https://s3.fr-par.scw.cloud'133	assert backend.url_for('repo/objects/abc', '') == 'https://s3.fr-par.scw.cloud/bucket/repo/objects/abc'134	assert backend.url_for('', 'list-type=2&prefix=x') == 'https://s3.fr-par.scw.cloud/bucket?list-type=2&prefix=x'135}