Skip to content

feat: Add filename field to Metadata#517

Open
lcian wants to merge 8 commits into
mainfrom
lcian/add-filename-metadata-field
Open

feat: Add filename field to Metadata#517
lcian wants to merge 8 commits into
mainfrom
lcian/add-filename-metadata-field

Conversation

@lcian

@lcian lcian commented Jun 23, 2026

Copy link
Copy Markdown
Member

For Debug Files, the current Sentry endpoint returns a Content-Disposition: attachment; filename="<filename>" header, which prompts browsers to save the file under the given name.
Objectstore doesn't currently return this, so if we want downloads from e.g. the UI to go directly to Objectstore, the user will likely be prompted with the Objectstore key as the filename, which doesn't necessarily match the logical filename we show e.g. in the UI itself.

This introduces a first-class Metadata field to the server and clients, which is send by clients as x-sn-filename and rendered in GET/HEAD responses via such a Content-Disposition header.
In the GCS backend, we store that field using the Content-Disposition metadata field itself, as that's supported by the GCS JSON API.
For the batch API, which uses Content-Disposition to delimit different parts (operations in a batch request), I opted to transmit the header as a x-sn-filename per part header, this way we get de/serialization for free via Metadata.
Otherwise, we would have to change the Content-Disposition to possibly contain the filename and add some additional logic to handle it.

Close FS-134

Add an optional filename field to Metadata, transmitted via the
x-sn-filename header. When present, the server emits a
Content-Disposition: attachment; filename="<filename>" header on GET
and HEAD responses for browser/download-tool compatibility.

- Add filename field to Metadata struct with serde and header support
- Map to GCS native contentDisposition field
- Emit Content-Disposition on GET, HEAD, and batch HEAD responses
- Add .filename() builder methods to Rust client PutBuilder and
  InitiateMultipartBuilder
- Add filename parameter to Python client put() and
  initiate_multipart_upload()

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@codecov

This comment has been minimized.

Adding the filename field to Metadata pushed the Insert variant past
clippy's large_enum_variant threshold (232 bytes vs 24 for the next
largest). Box it to keep the enum small on the stack.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Comment thread clients/rust/src/multipart.rs
The filename escaping only escaped double quotes but not backslashes,
allowing a stored filename with a backslash-quote sequence to terminate
the quoted-string and inject extra Content-Disposition parameters.

Centralize format_content_disposition and parse_content_disposition in
objectstore-types so all call sites share correct escaping of both
backslash and double-quote characters (in the right order).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@lcian

This comment has been minimized.

Comment thread objectstore-server/src/endpoints/batch.rs Outdated
lcian and others added 2 commits June 23, 2026 20:26
Content-Disposition is used by the multipart framing itself to
delimit parts, so setting it on individual batch response parts would
conflict. Clients should read the filename from the x-sn-filename
metadata header instead, which to_headers() already emits.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Add filename assertions to existing e2e tests in both clients to
verify the field roundtrips through PUT and GET (including batch GET
in the Rust client).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@lcian lcian changed the title feat(types): Add filename field to Metadata feat: Add filename field to Metadata Jun 24, 2026
pub enum Operation {
/// Insert a new object.
Insert(Insert),
Insert(Box<Insert>),

@lcian lcian Jun 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

insert.metadata gained one field, so this was firing clippy's large enum variant lint.

@linear-code

linear-code Bot commented Jun 24, 2026

Copy link
Copy Markdown

FS-134

@lcian lcian marked this pull request as ready for review June 24, 2026 07:59
@lcian lcian requested a review from a team as a code owner June 24, 2026 07:59
@lcian

This comment has been minimized.

@cursor cursor Bot 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0699e52. Configure here.

/// Formats a `Content-Disposition: attachment` header value for the given filename.
///
/// Escapes `\` and `"` in the filename per RFC 6266 quoted-string rules.
pub fn format_content_disposition(filename: &str) -> String {

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.

Note that axum-extra would have utilities for this: https://docs.rs/axum-extra/latest/axum_extra/response/struct.Attachment.html

At least, we should compare with their implementation to ensure we're not missing something.

session = client.session(test_usecase, org=42, project=1337)

object_key = session.put(b"test data", origin="203.0.113.42")
object_key = session.put(b"test data", origin="203.0.113.42", filename="report.pdf")

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.

could we add tests for header injection, i.e.:

  • backslash and double quotes in the filename: need escaping in quoted headers (which we use)
  • forward slash in the filename: would create a path and we likely don't want that
  • . and .. as filename: as above
  • control characters (0x00–0x1F, 0x7F) in the filename: not permitted in header values by 7230

i currently can't think of other things we should reject.

same for rust client. i think we should sanitize this in the client and validate server-side.

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