Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@
* HeaderConstants#ATTRIBUTE_RAW_REQUEST_HEADERS} attribute, invalidates the signature.
*
* <p>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 <a
* href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html">Create
Expand Down Expand Up @@ -357,8 +358,10 @@ static String getCanonicalRequest(
/**
* Returns the canonical URI.
*
* <p>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.
* <p>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}).
*
* <p>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
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading