fix(javascript): keep literal HTTP methods on storage-named receivers
A receiver spelled indexedDB or caches does not prove the binding, so
indexedDB.open("GET", "/api") keeps its endpoint. Such receivers
still need a standard HTTP method literal: a computed first argument or
an uppercase database name such as "APP" reads as storage, not XHR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
898b0f62d7
commit
9f48a76e11
2 files changed
+20
-11
No files matched your search
@@ -410,7 +410,7 @@ const BROWSING_CONTEXT_KEYWORDS = new Set([
|
||||
* `window.open(url, "_blank")` share the callee name. A literal method must
|
||||
* be an HTTP method; with a computed method, the second literal is a URL
|
||||
* unless it is provably an fs flag or a browsing-context keyword, or the
|
||||
* receiver is spelled as a browsing context. XHR normalizes the standard
|
||||
* receiver is spelled as a browsing context or storage. XHR normalizes the standard
|
||||
* methods' case; extension methods such as WebDAV `PROPFIND` are
|
||||
* conventionally uppercase tokens.
|
||||
*/
|
||||
@@ -422,13 +422,18 @@ const xhrOpenUrl = (
|
||||
const url = stringValue(urlNode);
|
||||
if (url === undefined) return undefined;
|
||||
const method = stringValue(methodNode);
|
||||
// IndexedDB and Cache Storage `open` take a name and a version, so a
|
||||
// receiver spelled as storage needs a standard HTTP method literal.
|
||||
const storage = storageKind(name) !== undefined;
|
||||
if (method === undefined)
|
||||
return WINDOW_OPEN_CALL.test(name) ||
|
||||
return storage ||
|
||||
WINDOW_OPEN_CALL.test(name) ||
|
||||
FS_OPEN_FLAGS.has(url) ||
|
||||
BROWSING_CONTEXT_KEYWORDS.has(url.toLowerCase())
|
||||
? undefined
|
||||
: url;
|
||||
return XHR_METHODS.has(method.toUpperCase()) || /^[A-Z][A-Z-]*$/u.test(method)
|
||||
return XHR_METHODS.has(method.toUpperCase()) ||
|
||||
(!storage && /^[A-Z][A-Z-]*$/u.test(method))
|
||||
? url
|
||||
: undefined;
|
||||
};
|
||||
@@ -483,11 +488,7 @@ export const endpointArgument = (
|
||||
): string | undefined => {
|
||||
if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket")
|
||||
return stringValue(args[0]);
|
||||
// IndexedDB and Cache Storage `open` take a name and a version, not a URL.
|
||||
if (name.endsWith(".open"))
|
||||
return storageKind(name) === undefined
|
||||
? xhrOpenUrl(name, args[0], args[1])
|
||||
: undefined;
|
||||
if (name.endsWith(".open")) return xhrOpenUrl(name, args[0], args[1]);
|
||||
const method = ["get", "post", "put", "patch", "delete", "request"].find(
|
||||
(candidate) => name === candidate || name.endsWith(`.${candidate}`),
|
||||
);
|
||||
|
||||
@@ -213,15 +213,23 @@ describe("web bundle static-analysis parity", () => {
|
||||
const source = `
|
||||
indexedDB.open(databaseName, "2");
|
||||
window.indexedDB.open(databaseName, "/v3");
|
||||
indexedDB.open("APP", "4");
|
||||
indexedDB.open("GET", "/storage-named-xhr");
|
||||
`;
|
||||
expect(
|
||||
analyzeCapturedWebBundle(inspection(source)).observations.endpoints,
|
||||
).toEqual([]);
|
||||
analyzeCapturedWebBundle(inspection(source)).observations.endpoints.map(
|
||||
({ value }) => value,
|
||||
),
|
||||
).toEqual(["/storage-named-xhr"]);
|
||||
const analysis = analyzeJavaScriptStaticSource(source);
|
||||
expect(analysis.endpoints).toEqual([]);
|
||||
expect(analysis.endpoints.map(({ value }) => value)).toEqual([
|
||||
"/storage-named-xhr",
|
||||
]);
|
||||
expect(analysis.storage.map(({ kind }) => kind)).toEqual([
|
||||
"indexed-db",
|
||||
"indexed-db",
|
||||
"indexed-db",
|
||||
"indexed-db",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user