Skip to content

HDDS-16672. Support RFC 2047 encoding for S3 user metadata - #11387

Open
F64116045 wants to merge 5 commits into
apache:masterfrom
F64116045:HDDS-16672
Open

F64116045 wants to merge 5 commits into
apache:masterfrom
F64116045:HDDS-16672

Conversation

@F64116045

@F64116045 F64116045 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

The ceph/s3-tests test_object_set_get_unicode_metadata exposed that S3 Gateway does not handle non-ASCII user metadata correctly.

Investigation showed that S3 uses RFC 2047 for non-ASCII x-amz-meta-* values. Ozone currently stores encoded values as ordinary text and does not encode them when returning response headers.

This patch decodes RFC 2047 Q/B values before storing metadata and encodes non-ASCII values in GET and HEAD responses.

References:

Note:
I kept this PR to the single encoded-word case so we can agree on the basic read&write behavior first.
Multiple words and long values need more work. If this direction looks good, I’ll take those on in a follow-up.

What is the link to the Apache JIRA?

https://issues.apache.org/jira/browse/HDDS-16672

How was this patch tested?

CI: https://github.com/F64116045/ozone/actions/runs/37118609154

Uploaded an object with this RFC 2047 encoded café metadata:

x-amz-meta-meta1: =?UTF-8?B?Y2Fmw6k=?=

The same request was tested against Ozone before and after this patch, and against S3.

B and Q are two RFC 2047 representations of the same café value.

Ozone before Ozone after S3
Stored value Encoded header text café Not observable
HEAD response B-encoded café Q-encoded café Q-encoded café

@github-actions github-actions Bot added the s3 S3 Gateway label Oct 2, 2026
@F64116045 F64116045 changed the title HDDS-16672. Preserve Unicode values in S3 user metadata HDDS-16672. Support Unicode values in S3 user metadata Oct 2, 2026
@F64116045 F64116045 changed the title HDDS-16672. Support Unicode values in S3 user metadata HDDS-16672. Support RFC 2047 encoding for S3 user metadata Oct 2, 2026
@F64116045

Copy link
Copy Markdown
Contributor Author

The original ceph/s3-tests case still fails because it sends raw non-ASCII metadata instead of RFC 2047. S3 does not round-trip that input either.

@F64116045
F64116045 marked this pull request as ready for review October 3, 2026 14:33
int encodingEnd = charsetEnd < 0 ? -1 : value.indexOf('?', charsetEnd + 1);
int encodedTextEnd = encodingEnd < 0
? -1 : value.indexOf("?=", encodingEnd + 1);
if (encodedTextEnd != value.length() - 2) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=?UTF-8?Q?a?b?= passes this check, but QCodec.decode() returns a. I reproduced this in a PUT endpoint test: the stored metadata is silently truncated. Please also check that the first ? after the encoding token is the terminating ?=, preserving the original value otherwise, and add a regression test alongside the invalid-Base64 case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the catch! fixed.

@Russole
Russole self-requested a review October 7, 2026 12:25

@Russole Russole left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @F64116045 for the patch. Left some comment.

? -1 : value.indexOf("?=", encodingEnd + 1);
if (encodedTextEnd != value.length() - 2
|| value.indexOf('?', encodingEnd + 1) != encodedTextEnd) {
return value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For malformed RFC 2047 values, should we return the original value instead of decoding it? For example, =?UTF-8?Q??= currently becomes an empty string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops I missed that case, thanks for catching it.
Also I checked a few other malformed values while fixing it, found similar issues, and added tests for those too.

@adoroszlai

Copy link
Copy Markdown
Contributor

Thanks for the patch. I would like to defer merging this after #11735, which will (hopefully) allow skipping most integration tests for S3-only changes.

@F64116045

Copy link
Copy Markdown
Contributor Author

I would like to defer merging this after #11735, which will (hopefully) allow skipping most integration tests for S3-only changes.

Sure. Also thanks for all your recent work on improving CI!

@peterxcli peterxcli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

should we use mature lib to do this? or do you have any reference for your decodeRfc2047MetadataValue implementation?

@F64116045

Copy link
Copy Markdown
Contributor Author

@peterxcli Thanks for the question

should we use mature lib to do this or do you have any reference for your decodeRfc2047MetadataValue implementation?

Actually we’re already using Ozone’s existing Commons Codec here for decoding and encoding.
The extra check is there because decoding malformed values directly can silently change the metadata we store.
(For example, =?UTF-8?B?!!!?= gets decoded to an empty string without an error.)
Also FYI the check is based on the encoded-word syntax in RFC 2047, Sections 2 and 4

I also just looked into MimeUtility, and found it supports multiple encoded-words, which we were considering for a follow-up, so I think it’s worth using here.
That said, it can also partially decode malformed values, so we'd still need to check the input first, as we do now.
Wonder what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s3 S3 Gateway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants