Skip to content

Fix extractQuerystring treating a missing '?' as index 0 - #10234

Open
Zuhef wants to merge 1 commit into
firebase:mainfrom
Zuhef:fix/alpha-extract-querystring-indexof
Open

Fix extractQuerystring treating a missing '?' as index 0#10234
Zuhef wants to merge 1 commit into
firebase:mainfrom
Zuhef:fix/alpha-extract-querystring-indexof

Conversation

@Zuhef

@Zuhef Zuhef commented Jul 30, 2026

Copy link
Copy Markdown

Problem

extractQuerystring() in packages/util/src/query.ts guards with if (!queryStart):

export function extractQuerystring(url: string): string {
  const queryStart = url.indexOf('?');
  if (!queryStart) {
    return '';
  }
  const fragmentStart = url.indexOf('#', queryStart);
  return url.substring(queryStart, fragmentStart > 0 ? fragmentStart : undefined);
}

indexOf returns -1 when the character is absent, and !(-1) is false, so the early return never fires for a URL without a query string. Execution falls through to url.substring(-1, …), which JavaScript clamps to substring(0, …) — returning the entire URL. And !0 is true, so an input that legitimately starts with ? returns '' and loses its query string.

Three of five cases violate the documented contract ("Extract the query string part of a URL, including the leading question mark (if present)"):

input before expected
https://example.com/path 'https://example.com/path' ''
https://example.com/path#frag 'https://example.com/path' ''
?a=1&b=2 '' '?a=1&b=2'
https://example.com/p?a=1 '?a=1' '?a=1'
https://example.com/p?a=1#frag '?a=1' '?a=1'

Fix

Compare against queryStart < 0.

Blast radius

The only consumer in this repo is packages/auth/src/core/action_code_url.ts, which uses it to parse email action links:

const link = querystringDecode(extractQuerystring(url))['link'];

I traced both changed paths there and neither alters current auth behaviour:

  • No-query URL — before, the whole URL was fed to querystringDecode, which split it into a single junk key ({'https://x/y': 'undefined'}), so ['link'] was undefined. After, it decodes ''{}, so ['link'] is still undefined. Same result, less nonsense in between.
  • Input starting with ? — before, the query was dropped and lookups failed; after, a nested deep_link_id / link in such a value is found, which is what the function is documented to do.

So the fix is strictly more correct and does not loosen anything. Flagging it explicitly because the caller sits in auth.

Tests

packages/util/src/query.ts had no test file, so this adds packages/util/test/query.test.ts covering querystring, querystringDecode and extractQuerystring, including the three cases above.

$ cd packages/util && mocha "test/**/*.test.ts" --config ../../config/mocharc.node.js
69 passing

That is 58 passing on an unmodified checkout, so the 11 additions are all new coverage. Reverting only src/query.ts fails exactly the three regression cases:

AssertionError: expected 'https://example.com/path' to equal ''
AssertionError: expected 'https://example.com/path' to equal ''
AssertionError: expected '' to equal '?a=1&b=2'

prettier --check (3.9.4, the pinned version) passes on both files, and tsc --noEmit reports nothing for either — the only diagnostics in my environment come from a newer @types/node than TypeScript 5.5.4 expects, in fs.d.ts/stream.d.ts. I could not run eslint locally because the version npx resolved wants flat config while the repo uses .eslintrc; CI will cover it.

Not included

querystringDecode('?a') returns { a: 'undefined' } — the literal string, because decodeURIComponent(undefined) stringifies — where URLSearchParams would give ''. That is a separate function and changing it could affect callers, so I left it alone and only noted it here. Happy to follow up if you'd like it fixed.

extractQuerystring guarded with `if (!queryStart)`, but indexOf returns -1
when the character is absent, and !(-1) is false. So the early return never
fired for a URL without a query string; execution fell through to
url.substring(-1, ...), which JavaScript clamps to substring(0, ...) and
returns the entire URL. Conversely !0 is true, so an input that begins with
'?' returned '' and lost its query string.

    extractQuerystring('https://example.com/path')       // 'https://example.com/path', want ''
    extractQuerystring('https://example.com/path#frag')  // 'https://example.com/path', want ''
    extractQuerystring('?a=1&b=2')                       // '', want '?a=1&b=2'

Compare against `queryStart < 0` instead.

packages/util/src/query.ts had no test file, so add one covering
querystring, querystringDecode and extractQuerystring, including the three
cases above.
@Zuhef
Zuhef requested review from a team as code owners July 30, 2026 08:49
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6eca4e3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
@firebase/util Patch
@firebase/ai Patch
@firebase/analytics-compat Patch
@firebase/analytics Patch
@firebase/app-check-compat Patch
@firebase/app-check Patch
@firebase/app-compat Patch
@firebase/app Patch
@firebase/auth-compat Patch
@firebase/auth Patch
@firebase/component Patch
@firebase/data-connect Patch
@firebase/database-compat Patch
@firebase/database-types Patch
@firebase/database Patch
firebase Patch
@firebase/firestore-compat Patch
@firebase/firestore Patch
@firebase/functions-compat Patch
@firebase/functions Patch
@firebase/installations-compat Patch
@firebase/installations Patch
@firebase/messaging-compat Patch
@firebase/messaging Patch
@firebase/performance-compat Patch
@firebase/performance Patch
@firebase/remote-config-compat Patch
@firebase/remote-config Patch
@firebase/storage-compat Patch
@firebase/storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request fixes a bug in extractQuerystring() where it incorrectly handled cases when the query string was not found (returning -1) or when the URL started with a question mark (returning 0). The check !queryStart was replaced with queryStart < 0. Additionally, a comprehensive set of unit tests was added to verify the behavior of query string utility functions. There are no review comments, and I have no feedback to provide.

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.

1 participant