Repository navigation
Expand file tree
/
Copy pathjavascriptNetworkRequests.ts
More file actions
143 lines (138 loc) · 5.3 KB
/
Copy pathjavascriptNetworkRequests.ts
File metadata and controls
143 lines (138 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
import {
semanticStaticPropertyName,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
import { semanticOpaqueGlobalMayHaveChanged } from "./javascriptSemanticOpaqueEffects.js";
import { evaluateSemanticExpression } from "./javascriptSemanticValues.js";
import { semanticPrimitiveCandidates } from "./javascriptSemanticPrimitives.js";
import {
resolveSemanticBindingState,
semanticStableInitializer,
type JavaScriptBindingIndex,
type JavaScriptSemanticAnalysisState,
} from "./javascriptSemanticState.js";
import * as t from "@babel/types";
import {
isUnmodifiedGlobalReference,
semanticAmbientPathMayHaveChangedAt,
} from "./javascriptSemanticAmbientWrites.js";
export const isFetchCall = (
node: t.Node,
bindings: JavaScriptSemanticAnalysisState,
): boolean =>
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
isUnmodifiedGlobalReference(node.callee, bindings, "fetch");
/** Browser request identity requires an intact intrinsic, not a method spelling. */
export const isBrowserNetworkRequest = (
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
bindings: JavaScriptSemanticAnalysisState,
): boolean =>
isFetchCall(node, bindings) ||
(t.isNewExpression(node) &&
(isUnmodifiedGlobalReference(node.callee, bindings, "WebSocket") ||
isUnmodifiedGlobalReference(node.callee, bindings, "EventSource"))) ||
((t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
isUnmodifiedGlobalReference(node.callee, bindings, [
"navigator",
"sendBeacon",
]));
const isXhrOpenRequest = (
node: t.Node,
bindings: JavaScriptSemanticAnalysisState,
index?: Pick<JavaScriptBindingIndex, "referenceMayHaveChangedAt">,
purpose: "request" | "receiver" = "request",
): boolean | undefined => {
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
return false;
const callee = unwrapJavaScriptExpression(node.callee).node;
if (
(!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) ||
semanticStaticPropertyName(callee.property, callee.computed) !== "open"
)
return false;
// Native open can dispatch readystatechange. Its receiver is nonescaping
// only when no handler or prior opaque operation can have configured it.
// Primitive arguments also avoid user code during WebIDL string conversion.
if (
purpose === "receiver" &&
node.arguments.some(
(argument) =>
t.isSpreadElement(argument) ||
semanticPrimitiveCandidates(
evaluateSemanticExpression(argument, bindings),
) === null,
)
)
return false;
const prototypeOwners =
purpose === "receiver"
? ["XMLHttpRequest", "XMLHttpRequestEventTarget", "EventTarget", "Object"]
: ["XMLHttpRequest", "Object"];
if (
prototypeOwners.some((owner) =>
(purpose === "receiver"
? ["open", "__proto__", "onreadystatechange"]
: ["open", "__proto__"]
).some((member) =>
semanticAmbientPathMayHaveChangedAt(
purpose === "receiver" ? node : callee,
bindings,
["global", owner, "prototype", member],
node,
),
),
)
)
return false;
let receiver = unwrapJavaScriptExpression(callee.object).node;
const seen = new Set<string>();
while (t.isIdentifier(receiver)) {
if (
index !== undefined &&
(purpose === "receiver"
? index.referenceMayHaveChangedAt(node, receiver, [])
: index.referenceMayHaveChangedAt(node, receiver, ["open"]) ||
index.referenceMayHaveChangedAt(node, receiver, ["__proto__"]))
)
return false;
const binding = resolveSemanticBindingState(
bindings,
receiver,
receiver.name,
);
if (binding === undefined || seen.has(binding.bindingId)) return false;
seen.add(binding.bindingId);
const initializer = semanticStableInitializer(binding, bindings);
if (
initializer === undefined ||
initializer.projection.length !== 0 ||
!semanticBindingAtRead(binding, receiver, bindings).hasDefiniteInitializer
)
return false;
receiver = unwrapJavaScriptExpression(initializer.node).node;
}
if (
!t.isNewExpression(receiver) ||
!isUnmodifiedGlobalReference(receiver.callee, bindings, "XMLHttpRequest") ||
semanticOpaqueGlobalMayHaveChanged(callee, bindings)
)
return false;
return index === undefined && seen.size > 0 ? undefined : true;
};
/** A pristine native open is safe for the mutation producer, not every XHR call. */
export const isIntrinsicNonEscapingXhrOpen = (
node: t.Node,
bindings: JavaScriptSemanticAnalysisState,
index?: Pick<JavaScriptBindingIndex, "referenceMayHaveChangedAt">,
): boolean | undefined => isXhrOpenRequest(node, bindings, index, "receiver");
/** Resolve once through the shared mutation index when lexical proof needs it. */
export const createXhrOpenRequestQuery =
(index: JavaScriptBindingIndex): ((node: t.Node) => boolean) =>
(node) => {
const proof = isXhrOpenRequest(node, index.bindings);
if (proof !== undefined) return proof;
completeSemanticSteps(index.indexMemberMutationFactsSteps());
return isXhrOpenRequest(node, index.bindings, index) === true;
};