mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(js): resolve literal URL from Request constructor in fetch calls (#6364)
* fix(js): resolve literal URL from Request constructor in fetch calls * fix(js): gate Request URL extraction on fetch and require new operator
This commit is contained in:
1 parent
ee0ec027db
commit
3984d12a30
2 files changed
+153
-8
No files matched your search
@@ -263,3 +263,114 @@ it("keeps exact literal targets exact", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves inline Request constructor targets with literal URLs", () => {
|
||||
expect(
|
||||
endpoints(`
|
||||
fetch(new Request("/api/chat", { method: "POST" }));
|
||||
window.fetch(new Request("/api/window"));
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "network",
|
||||
value: "/api/chat",
|
||||
mechanism: "call:fetch",
|
||||
resolution: undefined,
|
||||
},
|
||||
{
|
||||
kind: "network",
|
||||
value: "/api/window",
|
||||
mechanism: "call:window.fetch",
|
||||
resolution: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps Request calls without new partial", () => {
|
||||
expect(endpoints('fetch(Request("/api/stream"));')).toEqual([
|
||||
{
|
||||
kind: "network",
|
||||
value: 'Request("/api/stream")',
|
||||
mechanism: "call:fetch",
|
||||
resolution: "partial",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves unchanged Request objects and aliases", () => {
|
||||
expect(
|
||||
endpoints(`
|
||||
const req = new Request("/api/chat", { method: "POST" });
|
||||
const alias = req;
|
||||
fetch(alias);
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "network",
|
||||
value: "/api/chat",
|
||||
mechanism: "call:fetch",
|
||||
resolution: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps Request objects partial when called with toString", () => {
|
||||
expect(
|
||||
endpoints(`
|
||||
const req = new Request("/api/chat");
|
||||
fetch(req.toString());
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "network",
|
||||
value: "req.toString()",
|
||||
mechanism: "call:fetch",
|
||||
resolution: "partial",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps Request targets partial on non-fetch network APIs", () => {
|
||||
expect(
|
||||
endpoints(`
|
||||
const socketReq = new Request("wss://host/socket");
|
||||
new WebSocket(new Request("wss://host/direct"));
|
||||
new WebSocket(socketReq);
|
||||
navigator.sendBeacon(new Request("https://api.example.test/beacon"));
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "network",
|
||||
value: 'new Request("wss://host/direct")',
|
||||
mechanism: "call:WebSocket",
|
||||
resolution: "partial",
|
||||
},
|
||||
{
|
||||
kind: "network",
|
||||
value: "socketReq",
|
||||
mechanism: "call:WebSocket",
|
||||
resolution: "partial",
|
||||
},
|
||||
{
|
||||
kind: "network",
|
||||
value: 'new Request("https://api.example.test/beacon")',
|
||||
mechanism: "call:navigator.sendBeacon",
|
||||
resolution: "partial",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps Request objects partial when Request is locally shadowed", () => {
|
||||
expect(
|
||||
endpoints(`
|
||||
function test(Request) {
|
||||
fetch(new Request("/api/shadowed"));
|
||||
}
|
||||
`),
|
||||
).toContainEqual({
|
||||
kind: "network",
|
||||
value: 'new Request("/api/shadowed")',
|
||||
mechanism: "call:fetch",
|
||||
resolution: "partial",
|
||||
});
|
||||
});
|
||||
@@ -434,6 +434,26 @@ export const indexVitePreloadDependencyTable = (
|
||||
return escapedOrMutated ? dependencies.map(() => undefined) : dependencies;
|
||||
};
|
||||
|
||||
const isFetchCall = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): boolean =>
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "fetch");
|
||||
|
||||
const requestConstructorUrl = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): string | undefined => {
|
||||
const target = unwrapJavaScriptExpression(node).node;
|
||||
if (
|
||||
t.isNewExpression(target) &&
|
||||
isUnmodifiedGlobalReference(target.callee, bindings, "Request")
|
||||
)
|
||||
return stringValue(target.arguments[0]);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const inspectEndpointCall = (
|
||||
node: JavaScriptCallLike,
|
||||
name: string,
|
||||
@@ -444,7 +464,11 @@ const inspectEndpointCall = (
|
||||
if (isKnownNetworkRequest(node, bindings)) {
|
||||
const target = argumentNode(node.arguments[0]);
|
||||
if (target === undefined) return;
|
||||
const literal = stringValue(target);
|
||||
const literal =
|
||||
stringValue(target) ??
|
||||
(isFetchCall(node, bindings)
|
||||
? requestConstructorUrl(target, bindings)
|
||||
: undefined);
|
||||
if (literal !== undefined) {
|
||||
addEndpoint(context, {
|
||||
node,
|
||||
@@ -529,11 +553,7 @@ const isKnownNetworkRequest = (
|
||||
node: JavaScriptCallLike,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
if (
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "fetch")
|
||||
)
|
||||
return true;
|
||||
if (isFetchCall(node, bindings)) return true;
|
||||
if (
|
||||
t.isNewExpression(node) &&
|
||||
(isUnmodifiedGlobalReference(node.callee, bindings, "WebSocket") ||
|
||||
@@ -655,6 +675,7 @@ const urlObjectRequestTarget = (
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
index: JavaScriptBindingIndex,
|
||||
seen = new Set<string>(),
|
||||
isToString = false,
|
||||
): string | undefined => {
|
||||
let target = unwrapJavaScriptExpression(node).node;
|
||||
if (
|
||||
@@ -666,8 +687,10 @@ const urlObjectRequestTarget = (
|
||||
(t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
|
||||
semanticStaticPropertyName(callee.property, callee.computed) ===
|
||||
"toString"
|
||||
)
|
||||
) {
|
||||
target = unwrapJavaScriptExpression(callee.object).node;
|
||||
isToString = true;
|
||||
}
|
||||
}
|
||||
if (!t.isIdentifier(target) || index.referenceMayHaveChangedAt(site, target))
|
||||
return undefined;
|
||||
@@ -688,7 +711,18 @@ const urlObjectRequestTarget = (
|
||||
if (initializer === undefined) return undefined;
|
||||
const initialized = unwrapJavaScriptExpression(initializer).node;
|
||||
if (t.isIdentifier(initialized))
|
||||
return urlObjectRequestTarget(initialized, site, bindings, index, seen);
|
||||
return urlObjectRequestTarget(
|
||||
initialized,
|
||||
site,
|
||||
bindings,
|
||||
index,
|
||||
seen,
|
||||
isToString,
|
||||
);
|
||||
if (!isToString && isFetchCall(site, bindings)) {
|
||||
const requestUrl = requestConstructorUrl(initialized, bindings);
|
||||
if (requestUrl !== undefined) return requestUrl;
|
||||
}
|
||||
if (!t.isNewExpression(initialized)) return undefined;
|
||||
if (!isUnmodifiedGlobalReference(initialized.callee, bindings, "URL"))
|
||||
return undefined;
|
||||
|
||||
Reference in new issue
Block a user