Repository navigation
Encode path parameters and keep Set-Cookie out of serialized responses - #81
Merged
Merged
Conversation
Changepacks@devup-api/fetch@0.1.24 → 0.1.25 - packages/fetch/package.jsonPatch
@devup-api/hookform@0.1.8 → 0.1.9 - packages/hookform/package.jsonPatch
@devup-api/react-query@0.1.18 → 0.1.19 - packages/react-query/package.jsonPatch
@devup-api/ui@0.1.5 → 0.1.6 - packages/ui/package.jsonPatch
@devup-api/zod@0.1.6 → 0.1.7 - packages/zod/package.jsonPatch
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two bugs in
@devup-api/fetch, reproduced by callingcreateApi(...)against a localBun.serveserver (port 0, plain Bun runtime with nativefetch).Path parameters are inserted raw
getApiEndpointdidret.replace(`{${key}}`, value), so the URL parser read the value as part of the path.api.delete('/admin/notices/{id}', { params: { id } })reached the server as:id../membersDELETE /admin/members(another endpoint)DELETE /admin/notices/..%2Fmembers..DELETE /admin/.DELETE /admin/notices/a/bDELETE /admin/notices/a/bDELETE /admin/notices/a%2Fbx?y=1DELETE /admin/notices/x?y=1(became a query)DELETE /admin/notices/x%3Fy%3D1x#yDELETE /admin/notices/x(rest cut as a fragment)DELETE /admin/notices/x%23y$`DELETE /admin/notices/http://localhost:<port>/admin/notices/DELETE /admin/notices/%24%60$&DELETE /admin/notices/%7Bid%7DDELETE /admin/notices/%24%26The last two rows are
String.prototype.replacesubstitution patterns. A name used twice in one path was also replaced only once.Serialized responses carry
Set-CookieserializeResponsecopied every response header. Generated Server Actions (df/server.ts) returnserializeApiResponse(...)to Client Components, so the API'sSet-Cookie, HttpOnly session included, became readable by browser JavaScript. Serialized headers from the same repro:{"content-length":"11","content-type":"application/json","date":"...","set-cookie":"braillify_admin_session=SECRET-TOKEN; HttpOnly; Path=/; SameSite=Lax"}Browser
fetchnever exposes the forbidden response-header namesSet-CookieandSet-Cookie2to JavaScript.Fix
getApiEndpoint(packages/fetch/src/utils.ts): every value whose{name}appears in the path is encoded withencodeURIComponent(String(value))and inserted withreplaceAlland a replacer function, so$patterns are never interpreted and every occurrence is replaced.encodeURIComponentleaves.and..unchanged, and the WHATWG URL parser resolves them (and%2eforms) as dot segments, so they cannot be sent as one segment; they now throwPath parameter "id" cannot be "..": it would change the request pathbefore any request is made. The empty string is still inserted as-is because the parser keeps an empty segment. Params that are not in the path are still ignored. Query and body serialization are unchanged.serializeResponse(packages/fetch/src/server-utils.ts): dropsset-cookieandset-cookie2; all other headers are kept.README.md,packages/fetch/README.mdandSKILL.md, plus a@devup-api/fetchpatch changepack.Compatibility
/in a path parameter on purpose to build a multi-segment path now sends one encoded segment (a/bbecomesa%2Fb). Such paths need one placeholder per segment.%20becomes%2520)..and..as path parameter values now reject instead of reaching a different endpoint. A string with a lone surrogate now throwsURIErrorfromencodeURIComponent.-,_,~, dots inside a value, numbers, UUIDs) produce the same URL as before.SerializedResponse.headersno longer containsset-cookieorset-cookie2; the type is unchanged. Only code that read cookies from the serialized object is affected.Tests
utils.test.ts:getApiEndpointcases for../members,a/b,x?y=1,x#y,%, a space, Korean text,$&,$`, a number and a name used twice; the empty string and an unused..param stay unchanged;.and..throw. Existing cases are untouched.api.test.ts:createApi(...).delete('/admin/notices/{id}', { params: { id } })with the suite's mockedfetchasserts the URL of theRequestthat is actually sent, and that.and..reject without callingfetch. The test preload (happy-dom) replaces the globalfetchandResponse, so the real-server repro above was run as a standalone Bun script.server.test.ts: theisOkandisErrorcases now carrySet-CookieandSet-Cookie2and still serialize to exactlycontent-type(andx-test).Verified TDD: 20 of the 23 new or updated tests fail without the fix and all pass with it; the 3 that pass in both states pin unchanged behavior (number, empty string, unused param).
In CI order,
bun i,bun lint,bun run build,bun lintandbun test --coverageall exit 0: 1169 pass / 0 fail, 100% function and line coverage.