Skip to content

fix(services): encode version id for cos and tos - #7888

Open
QuakeWang wants to merge 5 commits into
apache:mainfrom
QuakeWang:versionid-query
Open

fix(services): encode version id for cos and tos#7888
QuakeWang wants to merge 5 commits into
apache:mainfrom
QuakeWang:versionid-query

Conversation

@QuakeWang

Copy link
Copy Markdown
Member

Which issue does this PR close?

Closes #.

Rationale for this change

COS and TOS used decoded version IDs when building versionId query parameters for read/stat/delete requests. If a version ID contains query-reserved characters like &, =, +, or %, the generated URI can be parsed as different query parameters instead of a single encoded versionId.

What changes are included in this PR?

  • Encode COS and TOS versionId query values before appending them to request URIs.
  • Preserve encoded COS query parameters after reqsign rebuilds signed URIs.
  • Extract delete request builders for COS/TOS so delete URI construction can be covered directly.
  • Add tests for read/stat/delete version ID URI encoding, including COS signed and presigned paths.

Are there any user-facing changes?

Yes. COS and TOS read/stat/delete operations with version IDs containing query-reserved characters now build valid request URIs.

AI Usage Statement

I used OpenAI Codex to implement the test in this change.

Signed-off-by: QuakeWang <wangfuzheng0814@foxmail.com>
@QuakeWang
QuakeWang requested a review from Xuanwo as a code owner July 8, 2026 12:08
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. releases-note/fix The PR fixes a bug or has a title that begins with "fix" labels Jul 8, 2026
@erickguan

Copy link
Copy Markdown
Member

Can we have a test for versioned object in behavior test?

Signed-off-by: QuakeWang <wangfuzheng0814@foxmail.com>
@QuakeWang

Copy link
Copy Markdown
Member Author

Can we have a test for versioned object in behavior test?

@erickguan Thanks for the suggestion. I have updated the behavior tests to cover versioned object operations more explicitly. PTAL 👀

@erickguan

Copy link
Copy Markdown
Member

Alright, I tried S3. S3 seems to respond with proper URI without breaking anything at least in behavior tests. Will have a look.

Comment thread core/services/cos/src/core.rs Outdated
fn restore_encoded_query(
uri: &mut Uri,
encoded_query: Option<&str>,
keep_signed_query: bool,

@erickguan erickguan Jul 9, 2026

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.

Do we need this? For URI to be correct, I believe we always will need encoded query param.

Comment thread core/services/cos/src/core.rs Outdated
.await
.map_err(|e| new_request_sign_error(e.into()))?;

Self::restore_encoded_query(&mut parts.uri, encoded_query.as_deref(), true)?;

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.

I didn't look into details. Can we build a new parts instead of mutating it?

Signed-off-by: QuakeWang <wangfuzheng0814@foxmail.com>

@Xuanwo Xuanwo 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.

I believe the real problem is reqsign didn't keep the raw query as is, we should fix at reqsign side so we don't need to have this query restore logic.

And before we are going too crazy: version id returned by services is always picked and not a randomly generated string. So I'm ok to really fix it but I don't want to merge PR like this one.

Both percent_encode_path and percent_decode_path here are all no-op (at least for 99% cases, I didn't see a real report).

@QuakeWang

Copy link
Copy Markdown
Member Author

I believe the real problem is reqsign didn't keep the raw query as is, we should fix at reqsign side so we don't need to have this query restore logic.

And before we are going too crazy: version id returned by services is always picked and a randomly generated string. So I'm ok to really fix it but I don't want to merge PR like this one.

Both percent_encode_path and percent_decode_path here are all no-op (at least for 99% cases, I didn't see a real report).

@Xuanwo Thanks for the review. I agree that the root fix should happen in reqsign-core instead of adding service-local query restoration logic in OpenDAL.

I've opened an issue to track it: apache/opendal-reqsign#778

I'll pause this PR and will fix the reqsign side firstly, then revisit this change with the workaround removed.

@Xuanwo

Xuanwo commented Jul 31, 2026

Copy link
Copy Markdown
Member

Hi, reqsign has been upgraded, please check this again

@QuakeWang
QuakeWang requested a review from PsiACE as a code owner July 31, 2026 12:38
Xuanwo pushed a commit that referenced this pull request Aug 14, 2026
…8073)

QueryPairsWriter::push does no escaping of its own and says so:

    /// The input key and value must already been percent
    /// encoded correctly.

Five list builders hand it a raw marker. The value is whatever the
server returned as the resume point -- an object key for obs, cos and
swift, an opaque continuation token for azblob and azfile -- and for
swift's first page it is the user's own start_after argument, so no
pagination is needed to reach it.

Feeding the exact strings to http::Request::get shows what is sent:

    report 2024.csv   BUILD ERROR: invalid uri character
    a&b=c             marker=a  plus a stray b=c parameter
    note#1.txt        marker=note -- the rest is taken as a fragment
    AB+cd==           marker=AB+cd==, whose + a server decodes as a space

The middle two are the dangerous ones: the marker silently rewinds, the
server resends a page that was already delivered, and since done is
computed from the marker being empty the listing can repeat that page
indefinitely.

The repository already does this correctly in three places, one of them
in the same file as a site being fixed here:

    s3/src/core.rs:803    push("marker", &percent_encode_path(marker))
    cos/src/core.rs:606   push("key-marker", &percent_encode_path(key_marker))
    oss/src/core.rs:478   push("continuation-token", &percent_encode_path(token))

percent_encode_path is the right helper for a marker specifically: it
leaves `/` alone, which an object key needs. That is the difference from
#7888, which is encoding cos and tos versionId values in the same family
of defect and introduces a stricter set for them -- a version id is not
a path, a marker is.

No new tests. Each change is one call to a helper the file already
imports, and asserting on it would mean restructuring five URL builders,
which I would rather not fold into a fix. All 39 existing unit tests
across the five crates pass, and none of them assert on a marker URL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

releases-note/fix The PR fixes a bug or has a title that begins with "fix" size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants