mirror of https://github.com/apache/jclouds.git
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).
This commit is contained in:
parent
0d0d5a0a03
commit
aee3c10a93
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue