Skip to content

🐛 Insert queryParameters before the fragment in RequestOptions.uri - #2614

Open
Yasser-Ameur wants to merge 1 commit into
cfug:mainfrom
Yasser-Ameur:fix/query-before-fragment
Open

Yasser-Ameur wants to merge 1 commit into
cfug:mainfrom
Yasser-Ameur:fix/query-before-fragment

Conversation

@Yasser-Ameur

Copy link
Copy Markdown

New Pull Request Checklist

  • I have read the Documentation
  • I have read the Agent Contribution Guidelines (required if any part of the change was produced with AI assistance)
  • I have searched for a similar pull request in the project and found none
  • I have updated this branch with the latest main branch to avoid conflicts (via merge from master or rebase)
  • I have added the required tests to prove the fix/feature I'm adding
  • I have updated the documentation (if necessary) (not applicable, no public API or doc-comment change, only getter behavior corrected to match the documented ordering)
  • I have run the tests without failures
  • I have updated the CHANGELOG.md in the corresponding package

Additional context and info (if any)

AI assistance, per AGENTS.md: Claude drafted the fix and the tests; I reviewed the diff, reran the suite, and wrote this description.

This defect was found while testing dio's URL construction, not reported by anyone.

RequestOptions.uri appended the encoded queryParameters after an existing #fragment in path, so dio.get('/page#section1', queryParameters: {'x': '1'}) built .../page#section1?x=1. The query text landed inside the fragment and a receiving server never saw x=1 as its query string. RFC 3986 Section 3 orders a URI as scheme:authority/path?query#fragment; the query must precede the fragment.

The fix splits any #fragment off the built URL before the query is appended, then re-appends the fragment after the query. One change site in dio/lib/src/options.dart's uri getter. Every other case (no fragment, an existing ?query merged with queryParameters, baseUrl joining, absolute-URL paths, listFormat) is unaffected because the split-off fragment is empty for them.

Added a regression test (dio/test/uri_fragment_query_test.dart) that proves the fix through a real dart:io HttpServer receiving the request, plus a boundary-value test group in dio/test/options_test.dart (fragment with/without query, existing ?query plus queryParameters, an empty fragment #, a fragment containing a literal ?, and baseUrl + relative path + fragment). All new tests were confirmed to fail on main before the fix and pass after.

Locally: dart format --set-exit-if-changed on the touched files, dart analyze --fatal-infos ., and the full dio/ VM test suite (dart test --preset=default,stable, 261 passed / 5 pre-existing unrelated skips) are all clean.

This does not touch TLS, redirects, cookies, header handling, timeouts/cancellation, the interceptor pipeline, or request-body encoding, so no maintainer @-mention for the extra-scrutiny areas in AGENTS.md applies.

Problem: when path carried a #fragment and queryParameters were also
given, RequestOptions.uri appended the encoded query after the
fragment instead of before it, so `?x=1` (or `&x=1`) became part of
the fragment string and the server never received it as a query.

Cause: the uri getter built url from path/baseUrl (which may already
end in a fragment) and then string-concatenated the query onto
whatever url already was, with no awareness of a trailing fragment.

Change: split any #fragment off url before appending the query, then
re-append the fragment after the query, so the component order
matches scheme:authority/path?query#fragment as RFC 3986 Section 3
defines it. Every other case (no fragment, existing ?query merged
with queryParameters, baseUrl joining, absolute paths, listFormat)
is unaffected since the fragment is empty in those cases.

Tests: options_test.dart gains a group of boundary-value assertions
on the built Uri (fragment with/without queryParameters, existing
?query plus queryParameters, empty fragment, a literal ? inside the
fragment, baseUrl+relative path with a fragment). A new
uri_fragment_query_test.dart proves the fix end to end against an
in-process HttpServer, asserting the server actually receives the
query string. All new tests were confirmed to fail on unmodified
main before the fix.

Co-Authored-By: Claude <[email protected]>
@Yasser-Ameur
Yasser-Ameur requested a review from a team as a code owner September 16, 2026 22:01

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

The fragment is now separated before query parameters are appended, and reattached afterward. I found no blocking issue in the changed behavior.

Local verification in an isolated worktree at 774c81e, using Dart 3.13.5: 36 tests passed across options_test.dart, uri_fragment_query_test.dart, encoding_test.dart, and parameter_test.dart; analysis of lib/test and formatting of the changed Dart files were clean. The real-HttpServer regression fails against base 4684e29 because the server receives an empty query, then passes on this head.

GitHub's package-verification and CodeQL runs are still action_required; the full hosted matrix has not run. Review performed with OpenAI Codex assistance.

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