Skip to content

Fix HeaderUnmarshaller to compare headers ignoring cases #1105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-f8f1130.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"category": "AWS SDK for Java v2",
"type": "bugfix",
"description": "Fix HeaderUnmarshaller to compare header ignoring cases."
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

package software.amazon.awssdk.protocols.xml.internal.unmarshall;

import static software.amazon.awssdk.utils.StringUtils.replacePrefixIgnoreCase;
import static software.amazon.awssdk.utils.StringUtils.startsWithIgnoreCase;

import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.SdkField;
import software.amazon.awssdk.protocols.core.StringToValueConverter;
Expand All @@ -39,9 +41,9 @@ public final class HeaderUnmarshaller {
public static final XmlUnmarshaller<Map<String, ?>> MAP = ((context, content, field) -> {
Map<String, String> result = new HashMap<>();
context.response().headers().entrySet().stream()
.filter(e -> e.getKey().startsWith(field.locationName()))
.forEach(e -> result.put(e.getKey().replace(field.locationName(), ""),
e.getValue().stream().collect(Collectors.joining(","))));
.filter(e -> startsWithIgnoreCase(e.getKey(), field.locationName()))
.forEach(e -> result.put(replacePrefixIgnoreCase(e.getKey(), field.locationName(), ""),
String.join(",", e.getValue())));
return result;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class UserMetadataIntegrationTest extends S3IntegrationTestBase {
* The S3 bucket created and used by these tests.
*/
private static final String BUCKET_NAME = temporaryBucketName("user-metadata-integ-test");

private static final String KEY = "UserMetadataIntegrationTest";
/**
* Length of the data uploaded to S3.
*/
Expand All @@ -54,6 +56,10 @@ public static void initializeTestData() throws Exception {
createBucket(BUCKET_NAME);

file = new RandomTempFile("user-metadata-integ-test-" + new Date().getTime(), CONTENT_LENGTH);
s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET_NAME)
.key(KEY)
.build(), file.toPath());
}

@AfterClass
Expand All @@ -67,11 +73,15 @@ public void putObject_PutsUserMetadata() throws Exception {
userMetadata.put("thing1", "IAmThing1");
userMetadata.put("thing2", "IAmThing2");

String mixedCasePrefix = "x-AmZ-mEtA-";
String metadataKey = "test";

final String key = "user-metadata-key";
s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET_NAME)
.key(key)
.metadata(userMetadata)
.overrideConfiguration(b -> b.putHeader(mixedCasePrefix + metadataKey, "test"))
.build(),
RequestBody.fromFile(file));

Expand All @@ -83,5 +93,6 @@ public void putObject_PutsUserMetadata() throws Exception {
Map<String, String> returnedMetadata = response.metadata();

assertThat(returnedMetadata).containsAllEntriesOf(userMetadata);
assertThat(returnedMetadata).containsKey(metadataKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.assertj.core.api.Assertions.assertThat;
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH;
import static software.amazon.awssdk.http.Header.CONTENT_TYPE;

Expand All @@ -41,6 +42,7 @@
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.testutils.RandomTempFile;

Expand Down Expand Up @@ -135,6 +137,22 @@ public void putObjectWithContentTypeHeader_shouldNotOverrideContentTypeInRawConf
verify(putRequestedFor(anyUrl()).withHeader(CONTENT_TYPE, equalTo(contentType)));
}

@Test
public void headObject_userMetadataReturnMixedCaseMetadata() {
String lowerCaseMetadataPrefix = "x-amz-meta-";
String mixedCaseMetadataPrefix = "X-AmZ-MEta-";
String metadataKey = "foo";
String mixedCaseMetadataKey = "bAr";

stubFor(any(urlMatching(".*"))
.willReturn(response().withHeader(lowerCaseMetadataPrefix + metadataKey, "test")
.withHeader(mixedCaseMetadataPrefix + mixedCaseMetadataKey, "test")));
HeadObjectResponse headObjectResponse = s3Client.headObject(b -> b.key("key").bucket("bucket"));

assertThat(headObjectResponse.metadata()).containsKey(metadataKey);
assertThat(headObjectResponse.metadata()).containsKey(mixedCaseMetadataKey);
}

private ResponseDefinitionBuilder response() {
return aResponse().withStatus(200).withHeader(CONTENT_LENGTH, "0").withBody("");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,5 +310,29 @@
"stringMember": ""
}
}
},
{
"description": "Map headers are unmarshalled correctly",
"given": {
"response": {
"status_code": 200,
"headers": {
"x-amz-meta-FoO": "foo",
"X-aMZ-mEtA-bAr": "bar"
}
}
},
"when": {
"action": "unmarshall",
"operation": "MembersInHeaders"
},
"then": {
"deserializedAs": {
"MetadataMember": {
"FoO": "foo",
"bAr": "bar"
}
}
}
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@
"shape":"Timestamp",
"location":"header",
"locationName":"x-amz-timestamp"
},
"MetadataMember":{
"shape":"Metadata",
"location":"header",
"locationName":"x-amz-meta-"
}
}
},
Expand Down Expand Up @@ -535,12 +540,6 @@
},
"exception":true
},
"EmptyModeledException":{
"type":"structure",
"members":{
},
"exception":true
},
"ExplicitPayloadAndHeadersException":{
"type":"structure",
"members":{
Expand Down Expand Up @@ -584,6 +583,13 @@
"exception":true,
"payload":"PayloadMember"
},
"Timestamp":{"type":"timestamp"}
"Timestamp":{"type":"timestamp"},
"Metadata":{
"type":"map",
"key":{"shape":"MetadataKey"},
"value":{"shape":"MetadataValue"}
},
"MetadataKey":{"type":"string"},
"MetadataValue":{"type":"string"}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -578,4 +578,31 @@ public static String fromBytes(byte[] bytes, Charset charset) throws UncheckedIO
throw new UncheckedIOException("Cannot encode string.", e);
}
}

/**
* Tests if this string starts with the specified prefix ignoring case considerations.
*
* @param str the string to be tested
* @param prefix the prefix
* @return true if the string starts with the prefix ignoring case
*/
public static boolean startsWithIgnoreCase(String str, String prefix) {
return str.regionMatches(true, 0, prefix, 0, prefix.length());
}

/**
* Replace the prefix of the string provided ignoring case considerations.
*
* <p>
* The unmatched part is unchanged.
*
*
* @param str the string to replace
* @param prefix the prefix to find
* @param replacement the replacement
* @return the replaced string
*/
public static String replacePrefixIgnoreCase(String str, String prefix, String replacement) {
return str.replaceFirst("(?i)" + prefix, replacement);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
package software.amazon.awssdk.utils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static software.amazon.awssdk.utils.StringUtils.replacePrefixIgnoreCase;

import org.junit.Test;

Expand Down Expand Up @@ -107,4 +111,18 @@ public void testReCapitalize() {
assertEquals("capitalize(uncapitalize(String)) failed",
FOO_CAP, StringUtils.capitalize(StringUtils.uncapitalize(FOO_CAP)));
}

@Test
public void testStartsWithIgnoreCase() {
assertTrue(StringUtils.startsWithIgnoreCase("helloworld", "hello"));
assertTrue(StringUtils.startsWithIgnoreCase("hELlOwOrlD", "hello"));
assertFalse(StringUtils.startsWithIgnoreCase("hello", "world"));
}

@Test
public void testReplacePrefixIgnoreCase() {
assertEquals("lloWorld" ,replacePrefixIgnoreCase("helloWorld", "he", ""));
assertEquals("lloWORld" ,replacePrefixIgnoreCase("helloWORld", "He", ""));
assertEquals("llOwOrld" ,replacePrefixIgnoreCase("HEllOwOrld", "he", ""));
}
}