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.