Updated Branches: refs/heads/1.6.x 0d0d5a0a0 -> aee3c10a9
Handle quoted ETags for OpenStack objects to fix JCLOUDS-247 This avoids a 'java.io.IOException: Unrecognized character: "' when a quoted ETag is encountered (which happens when getting a multipart blob from Rackspace Cloud). Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/commit/aee3c10a Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/tree/aee3c10a Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/diff/aee3c10a Branch: refs/heads/1.6.x Commit: aee3c10a93e92a5cd6e83b10073244d4f292ff75 Parents: 0d0d5a0 Author: Francis Devereux <[email protected]> Authored: Tue Aug 20 17:54:59 2013 +0100 Committer: Andrew Gaul <[email protected]> Committed: Wed Aug 28 13:00:47 2013 -0700 ---------------------------------------------------------------------- .../functions/ResourceToObjectInfo.java | 6 +-- .../functions/ParseObjectInfoFromHeaders.java | 7 +-- .../openstack/swift/utils/ETagUtils.java | 51 ++++++++++++++++++++ .../ParseObjectInfoFromHeadersTest.java | 20 ++++++-- .../openstack/swift/utils/ETagUtilsTest.java | 34 +++++++++++++ 5 files changed, 108 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/aee3c10a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java ---------------------------------------------------------------------- diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java index 56dad87..45d8eb4 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java @@ -16,8 +16,6 @@ */ package org.jclouds.openstack.swift.blobstore.functions; -import static com.google.common.io.BaseEncoding.base16; - import java.util.Map.Entry; import javax.inject.Singleton; @@ -29,6 +27,7 @@ import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata; import org.jclouds.openstack.swift.domain.internal.MutableObjectInfoWithMetadataImpl; import com.google.common.base.Function; +import org.jclouds.openstack.swift.utils.ETagUtils; /** * @author Adrian Cole @@ -48,7 +47,7 @@ public class ResourceToObjectInfo implements Function<StorageMetadata, MutableOb to.setContentType("application/directory"); } if (from.getETag() != null && to.getHash() == null) - to.setHash(base16().lowerCase().decode(from.getETag())); + to.setHash(ETagUtils.convertHexETagToByteArray(from.getETag())); to.setName(from.getName()); to.setLastModified(from.getLastModified()); if (from.getUserMetadata() != null) { @@ -57,5 +56,4 @@ public class ResourceToObjectInfo implements Function<StorageMetadata, MutableOb } return to; } - } http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/aee3c10a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java ---------------------------------------------------------------------- diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java index 8a73d39..bd082fe 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java @@ -17,10 +17,10 @@ package org.jclouds.openstack.swift.functions; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.io.BaseEncoding.base16; import static org.jclouds.http.HttpUtils.attemptToParseSizeAndRangeFromHeaders; import javax.inject.Inject; +import javax.ws.rs.core.HttpHeaders; import org.jclouds.blobstore.domain.BlobMetadata; import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders; @@ -28,6 +28,7 @@ import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.swift.blobstore.functions.ResourceToObjectInfo; import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata; +import org.jclouds.openstack.swift.utils.ETagUtils; import org.jclouds.rest.InvocationContext; import org.jclouds.rest.internal.GeneratedHttpRequest; @@ -60,9 +61,9 @@ public class ParseObjectInfoFromHeaders implements Function<HttpResponse, Mutabl to.setBytes(attemptToParseSizeAndRangeFromHeaders(from)); to.setContainer(container); to.setUri(base.getUri()); - String eTagHeader = from.getFirstHeaderOrNull("Etag"); + String eTagHeader = from.getFirstHeaderOrNull(HttpHeaders.ETAG); if (eTagHeader != null) { - to.setHash(base16().lowerCase().decode(eTagHeader)); + to.setHash(ETagUtils.convertHexETagToByteArray(eTagHeader)); } return to; } http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/aee3c10a/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java ---------------------------------------------------------------------- diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java new file mode 100644 index 0000000..9ffeb44 --- /dev/null +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.openstack.swift.utils; + +import java.util.regex.Pattern; + +import com.google.common.annotations.VisibleForTesting; + +import static com.google.common.io.BaseEncoding.base16; + +/** + * @author Francis Devereux + */ +public class ETagUtils { + private static final Pattern QUOTED_STRING = Pattern.compile("^\"(.*)\"$"); + + /** + * <p>Converts the ETag of an OpenStack object to a byte array.</p> + * + * <p>Not applicable to all ETags, only those of OpenStack objects. According + * to the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11">HTTP spec</a> + * an eTag can be any string, but the + * <a href="http://docs.openstack.org/trunk/openstack-object-storage/admin/content/additional-notes-on-large-objects.html">OpenStack Object Storage Administration Guide</a> + * says that the ETag of an OpenStack object will be an MD5 sum (and MD5 sums + * are conventionally represented as hex strings). This method only accepts + * hex strings as input, not arbitrary strings.</p> + */ + public static byte[] convertHexETagToByteArray(String hexETag) { + hexETag = unquote(hexETag); + return base16().lowerCase().decode(hexETag); + } + + @VisibleForTesting + static String unquote(String eTag) { + return QUOTED_STRING.matcher(eTag).replaceAll("$1"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/aee3c10a/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java ---------------------------------------------------------------------- diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java index 7e62800..8c30fdb 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java @@ -16,6 +16,7 @@ */ package org.jclouds.openstack.swift.functions; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import org.jclouds.http.HttpResponse; @@ -26,7 +27,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; /** - * Tests behavior of {@code ParseContainerListFromJsonResponse} + * Tests behavior of {@code ParseObjectInfoFromHeaders} * * @author Adrian Cole */ @@ -34,17 +35,30 @@ import com.google.common.collect.ImmutableList; public class ParseObjectInfoFromHeadersTest extends BasePayloadTest { public void testEtagCaseIssue() { + assertETagCanBeParsed("feb1", + new byte[] { (byte) 0xfe, (byte) 0xb1 } + ); + } + + public void testParseEtagWithQuotes() { + assertETagCanBeParsed("\"feb1\"", + new byte[] { (byte) 0xfe, (byte) 0xb1 } + ); + } + + private void assertETagCanBeParsed(String etag, byte[] expectedHash) { ParseObjectInfoFromHeaders parser = i.getInstance(ParseObjectInfoFromHeaders.class); - + parser.setContext(requestForArgs(ImmutableList.<Object> of("container", "key"))); HttpResponse response = HttpResponse.builder().statusCode(200).message("ok").payload("") .addHeader("Last-Modified", "Fri, 12 Jun 2007 13:40:18 GMT") .addHeader("Content-Length", "0") - .addHeader("Etag", "feb1").build(); + .addHeader("Etag", etag).build(); response.getPayload().getContentMetadata().setContentType("text/plain"); MutableObjectInfoWithMetadata md = parser.apply(response); assertNotNull(md.getHash()); + assertEquals(md.getHash(), expectedHash); } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/aee3c10a/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java ---------------------------------------------------------------------- diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java new file mode 100644 index 0000000..e46d792 --- /dev/null +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.openstack.swift.utils; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +/** + * @author Francis Devereux + */ +@Test(groups = "unit") +public class ETagUtilsTest { + @Test + public void testNoExceptionUnquotingSingleDQuote() { + String singleDQuoteCharacter = "\""; + assertEquals(ETagUtils.unquote(singleDQuoteCharacter), + singleDQuoteCharacter); + } +}
