diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsSigV4Helper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsSigV4Helper.java index 52c028e928..209452ad96 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsSigV4Helper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsSigV4Helper.java @@ -70,10 +70,11 @@ * HeaderConstants#ATTRIBUTE_RAW_REQUEST_HEADERS} attribute, invalidates the signature. * *
The signed path and query are those the client connector sends: a relative reference is
- * resolved against its base. S3 signs the path as is, each segment decoded then URI-encoded once.
- * The other services sign it normalized (empty and dot segments removed) and URI-encoded once more
- * as sent, so an escape in the path is encoded again. Query parameters are signed sorted and
- * URI-encoded: a {@code +} in the query is a literal plus sign, so encode spaces as {@code %20}.
+ * resolved against its base. S3 signs the path as is, decoded (an encoded slash included) then each
+ * segment URI-encoded once. The other services sign it normalized (empty and dot segments removed)
+ * and URI-encoded once more as sent, so an escape in the path is encoded again. Query parameters
+ * are signed sorted and URI-encoded: a {@code +} in the query is a literal plus sign, so encode
+ * spaces as {@code %20}.
*
* @see Create
@@ -357,8 +358,10 @@ static String getCanonicalRequest(
/**
* Returns the canonical URI.
*
- * S3 decodes the path it receives and encodes it again: each segment is decoded, then
- * URI-encoded once, and the path is otherwise signed as sent.
+ * S3 decodes the path it receives and encodes it again: the whole path is decoded, then
+ * split into segments, each URI-encoded once, and the path is otherwise signed as sent. An
+ * encoded slash ({@code %2F}) is decoded before the split, so it is signed as a separator, as
+ * S3 and MinIO do ({@code /bucket/a%2Fb} is signed as {@code /bucket/a/b}).
*
* The other services encode the path they receive once more ("double encoding"): the path is
* normalized (see {@link #normalizePath(String)}) and each raw segment is URI-encoded as is, so
@@ -373,14 +376,14 @@ static String getCanonicalUri(String rawPath, boolean doubleEncode) {
if (rawPath == null || rawPath.isEmpty()) {
return "/";
}
- String path = doubleEncode ? normalizePath(rawPath) : rawPath;
+ String path = doubleEncode ? normalizePath(rawPath) : percentDecode(rawPath);
String[] segments = path.split("/", -1);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < segments.length; i++) {
if (i > 0) {
sb.append('/');
}
- sb.append(uriEncode(doubleEncode ? segments[i] : percentDecode(segments[i])));
+ sb.append(uriEncode(segments[i]));
}
return sb.charAt(0) == '/' ? sb.toString() : "/" + sb;
}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsSigV4HelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsSigV4HelperTestCase.java
index 773abc8859..6c8a529b21 100644
--- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsSigV4HelperTestCase.java
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsSigV4HelperTestCase.java
@@ -390,6 +390,17 @@ void canonicalUri() {
assertEquals("/a%2Bb%25zz", HttpAwsSigV4Helper.getCanonicalUri("/a+b%zz", false));
}
+ /**
+ * S3 and MinIO decode the whole path before splitting it, so an encoded slash is signed as a
+ * separator. The other services sign it as sent, encoded once more.
+ */
+ @Test
+ void canonicalUriDecodesAnEncodedSlashBeforeSplittingForS3Only() {
+ assertEquals("/bucket/a/b", HttpAwsSigV4Helper.getCanonicalUri("/bucket/a%2Fb", false));
+ assertEquals("/bucket/a/b", HttpAwsSigV4Helper.getCanonicalUri("/bucket/a%2fb", false));
+ assertEquals("/bucket/a%252Fb", HttpAwsSigV4Helper.getCanonicalUri("/bucket/a%2Fb", true));
+ }
+
/**
* The services other than S3 sign the path normalized, as in the "normalize-path" cases of the
* AWS SigV4 test suite. S3 signs it as sent.