Skip to content

Match metadata header names case-insensitively - #62

Open
fusionfoto wants to merge 1 commit into
iterate-ch:masterfrom
fusionfoto:bugfix/case-insensitive-metadata-headers
Open

fusionfoto wants to merge 1 commit into
iterate-ch:masterfrom
fusionfoto:bugfix/case-insensitive-metadata-headers

Conversation

@fusionfoto

@fusionfoto fusionfoto commented Nov 10, 2026 •

Copy link
Copy Markdown

Contributed by AiNET and Blomp.

Hi there 👋, and thanks for maintaining this library!

The problem. HTTP header names are case-insensitive (RFC 9110 §5.1), but ContainerMetadataResponseHandler and ObjectMetadataResponseHandler match the X-Container-Meta- / X-Object-Meta- prefixes with a case-sensitive startsWith. When Swift sits behind a proxy that lower-cases header names (some reverse proxies do this, and HTTP/2 requires lower case), all of that metadata is silently dropped. We hit it at Blomp: X-Container-Meta-Quota-Bytes never reached Cyberduck.

A related case: Client#listObjectSegments looks up Constants.MANIFEST_HEADER and Constants.X_STATIC_LARGE_OBJECT by exact name, so behind such a proxy it doesn't recognise existing large objects.

Why it matters most. listObjectSegments is what finds a large object's segments when it is deleted, so behind such a proxy deleting a large object with Cyberduck leaves all of its segments in storage. In a test with a segmented upload and delete through Cyberduck, 2.5.14 left every segment behind and this branch left none.

The fix (small, no API change):

  • Match both prefixes with regionMatches(true, …), ignoring case.
  • MetaData keeps its map as TreeMap(String.CASE_INSENSITIVE_ORDER), so lookups by the constants work whatever case the server used. Keys still keep the header name as received. setMetaData copies into the same kind of map, and a null argument now leaves the map empty.

Tests. The new MetadataResponseHandlerCaseTest covers lower-case container and object headers, manifest/SLO lookups by constant, the canonical case, and setMetaData. mvn verify passes (20 tests). On unmodified master, 3 of the 4 new tests fail; the canonical-case one passes there too, as expected.

Live check against the Blomp service: getContainerMetaData returns the quota metadata with this branch, and nothing with 2.5.14.

This is the library-side counterpart of iterate-ch/cyberduck#18688. That PR works around the issue for quota only, by reading the HEAD response headers directly; it can switch back to getContainerMetaData once a release includes this fix. Happy to adjust anything. Thank you! 🙏

HTTP header names are case-insensitive (RFC 9110). Some reverse proxies
forward them in lower case, so x-container-meta-* and x-object-meta-* headers
were dropped from ContainerMetadata and ObjectMetadata, and lookups like
containsKey(Constants.MANIFEST_HEADER) in Client#listObjectSegments missed
x-object-manifest and x-static-large-object.

- Match the X-Container-Meta- and X-Object-Meta- prefixes ignoring case.
- Keep metadata in a case-insensitive map. Keys keep the header name as
  received.

Contributed by AiNET (https://ai.net) and Blomp (https://www.blomp.com).
@fusionfoto
fusionfoto force-pushed the bugfix/case-insensitive-metadata-headers branch from 6f9ad09 to 11c0591 Compare November 11, 2026 00:36
@dkocher
dkocher requested a balanced review from Copilot November 11, 2026 07:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Lower-case metadata keys cause the case-sensitive update logic to duplicate container and object metadata prefixes, updating the wrong fields.

1 open issue
What changed in this PR

This PR makes Swift metadata handling case-insensitive so lower-cased response headers remain available for quota retrieval and large-object segment discovery.

Changes:

  • Match container and object metadata prefixes without case sensitivity.
  • Store metadata in case-insensitive maps while preserving header spelling, including when replacing metadata.
  • Add regression tests for lower-case headers, canonical headers, manifest/SLO lookups, and null metadata replacement.
File Description
src/​test/​java/​ch/​iterate/​openstack/​swift/​handler/​MetadataResponseHandlerCaseTest.java Tests case-insensitive metadata handling and map replacement.
src/​main/​java/​ch/​iterate/​openstack/​swift/​model/​MetaData.java Uses case-insensitive maps and handles null replacement input.
src/​main/​java/​ch/​iterate/​openstack/​swift/​handler/​ObjectMetadataResponseHandler.java Accepts object metadata prefixes regardless of case.
src/​main/​java/​ch/​iterate/​openstack/​swift/​handler/​ContainerMetadataResponseHandler.java Accepts container metadata prefixes regardless of case.

Review effort: Balanced

// Header names are case-insensitive (RFC 9110) and proxies may send them in lower case
for(Header h : response.getAllHeaders()) {
if(h.getName().startsWith(Constants.X_CONTAINER_META)
if(h.getName().regionMatches(true, 0, Constants.X_CONTAINER_META, 0, Constants.X_CONTAINER_META.length())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants