Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 55ad3bb

Browse files
committed
JS: add ClientRequest.getAResponseDataNode()
1 parent 257dadd commit 55ad3bb

1 file changed

Lines changed: 237 additions & 9 deletions

File tree

javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll

Lines changed: 237 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,36 @@ class ClientRequest extends DataFlow::InvokeNode {
3737
* Gets a node that contributes to the data-part this request.
3838
*/
3939
DataFlow::Node getADataNode() { result = self.getADataNode() }
40+
41+
/**
42+
* Gets a data flow node that refers to some representation of the response, possibly
43+
* wrapped in a promise object.
44+
*
45+
* The `responseType` describes how the response is represented as a JavaScript value
46+
* (after resolving promises).
47+
*
48+
* The response type may be any of the values supported by
49+
* [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType),
50+
* namely `arraybuffer`, `blob`, `document`, `json`, or `text`.
51+
*
52+
* Additionally, the `responseType` may have one of the following values:
53+
* - `fetch.response`: The result is a `Response` object as defined by the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Response).
54+
* - `stream`: The result is a Node.js stream
55+
* - `error`: The result is an error in an unspecified format, possibly containing information from the response
56+
*
57+
*
58+
* Custom implementations of `ClientRequest` may use other formats.
59+
* If the responseType is not known the convention is to use an empty string.
60+
*/
61+
DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
62+
result = self.getAResponseDataNode(responseType, promise)
63+
}
64+
65+
/**
66+
* Gets a node that refers to data from the response, possibly
67+
* wrapped in a promise object.
68+
*/
69+
DataFlow::Node getAResponseDataNode() { result = getAResponseDataNode(_, _) }
4070
}
4171

4272
module ClientRequest {
@@ -62,6 +92,14 @@ module ClientRequest {
6292
* Gets a node that contributes to the data-part this request.
6393
*/
6494
abstract DataFlow::Node getADataNode();
95+
96+
/**
97+
* Gets a data flow node that refers to some representation of the response, possibly
98+
* wrapped in a promise object.
99+
*
100+
* See the decription of `responseType` in the corresponding predicate in `ClientRequest`.
101+
*/
102+
DataFlow::Node getAResponseDataNode(string responseType, boolean promise) { none() }
65103
}
66104
}
67105

@@ -83,14 +121,21 @@ private string urlPropertyName() {
83121
/**
84122
* A model of a URL request made using the `request` library.
85123
*/
86-
private class RequestUrlRequest extends ClientRequest::Range {
124+
private class RequestUrlRequest extends ClientRequest::Range, DataFlow::CallNode {
125+
boolean promise;
126+
87127
RequestUrlRequest() {
88128
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
89129
(
90-
moduleName = "request" or
91-
moduleName = "request-promise" or
92-
moduleName = "request-promise-any" or
93-
moduleName = "request-promise-native"
130+
promise = false and
131+
moduleName = "request"
132+
or
133+
promise = true and
134+
(
135+
moduleName = "request-promise" or
136+
moduleName = "request-promise-any" or
137+
moduleName = "request-promise-native"
138+
)
94139
) and
95140
(
96141
callee = DataFlow::moduleImport(moduleName) or
@@ -106,6 +151,29 @@ private class RequestUrlRequest extends ClientRequest::Range {
106151

107152
override DataFlow::Node getHost() { none() }
108153

154+
string getResponseFormat() {
155+
if getOptionArgument(0, "json").mayHaveBooleanValue(true) then
156+
result = "json"
157+
else
158+
result = "text"
159+
}
160+
161+
override DataFlow::Node getAResponseDataNode(string responseType, boolean pr) {
162+
responseType = getResponseFormat() and
163+
promise = true and
164+
pr = true and
165+
result = this
166+
or
167+
responseType = getResponseFormat() and
168+
promise = false and
169+
pr = false and
170+
(
171+
result = getCallback([1..2]).getParameter(2)
172+
or
173+
result = getCallback([1..2]).getParameter(1).getAPropertyRead("body")
174+
)
175+
}
176+
109177
override DataFlow::Node getADataNode() { result = getArgument(1) }
110178
}
111179

@@ -150,6 +218,24 @@ private class AxiosUrlRequest extends ClientRequest::Range {
150218
result = getOptionArgument([0 .. 2], name)
151219
)
152220
}
221+
222+
string getResponseFormat() {
223+
exists(DataFlow::Node option | option = getOptionArgument([0 .. 2], "responseType") |
224+
result = option.getStringValue()
225+
or
226+
not exists(option.getStringValue()) and
227+
result = ""
228+
)
229+
or
230+
not exists(getOptionArgument([0 .. 2], "responseType")) and
231+
result = "json"
232+
}
233+
234+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
235+
responseType = getResponseFormat() and
236+
promise = true and
237+
result = this
238+
}
153239
}
154240

155241
/**
@@ -180,6 +266,12 @@ private class FetchUrlRequest extends ClientRequest::Range {
180266
override DataFlow::Node getADataNode() {
181267
exists(string name | name = "headers" or name = "body" | result = getOptionArgument(1, name))
182268
}
269+
270+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
271+
responseType = "fetch.response" and
272+
promise = true and
273+
result = this
274+
}
183275
}
184276

185277
/**
@@ -215,6 +307,34 @@ private class GotUrlRequest extends ClientRequest::Range {
215307
result = getOptionArgument(1, name)
216308
)
217309
}
310+
311+
predicate isStream() {
312+
getOptionArgument(1, "stream").mayHaveBooleanValue(true)
313+
or
314+
this = DataFlow::moduleMember("got", "stream").getACall()
315+
}
316+
317+
predicate isJson() {
318+
getOptionArgument(1, "json").mayHaveBooleanValue(true)
319+
}
320+
321+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
322+
result = this and
323+
(
324+
isStream() and
325+
responseType = "stream" and
326+
promise = false
327+
or
328+
isJson() and
329+
responseType = "json" and
330+
promise = true
331+
or
332+
not isStream() and
333+
not isJson() and
334+
responseType = "text" and
335+
promise = true
336+
)
337+
}
218338
}
219339

220340
/**
@@ -240,6 +360,22 @@ private class SuperAgentUrlRequest extends ClientRequest::Range {
240360
result = this.getAChainedMethodCall(name).getAnArgument()
241361
)
242362
}
363+
364+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
365+
responseType = "text" and
366+
promise = true and
367+
result = this
368+
or
369+
exists(DataFlow::FunctionNode callback |
370+
callback = getAChainedMethodCall("end").getCallback(0) and
371+
promise = false and
372+
(
373+
responseType = "error" and result = callback.getParameter(0)
374+
or
375+
responseType = "text" and result = callback.getParameter(1)
376+
)
377+
)
378+
}
243379
}
244380

245381
/**
@@ -250,25 +386,73 @@ private class XMLHttpRequest extends ClientRequest::Range {
250386
this = DataFlow::globalVarRef("XMLHttpRequest").getAnInstantiation()
251387
or
252388
// closure shim for XMLHttpRequest
253-
this = Closure::moduleImport("goog.net.XmlHttp").getAnInstantiation()
389+
this = Closure::moduleImport("goog.net.XmlHttp").getAnInvocation()
254390
}
255391

256392
override DataFlow::Node getUrl() { result = getAMethodCall("open").getArgument(1) }
257393

258394
override DataFlow::Node getHost() { none() }
259395

260396
override DataFlow::Node getADataNode() { result = getAMethodCall("send").getArgument(0) }
397+
398+
private string getAssignedResponseType() {
399+
getAPropertyWrite("responseType").mayHaveStringValue(result)
400+
or
401+
getAPropertyWrite("responseType").mayHaveStringValue("") and
402+
result = "text"
403+
or
404+
not exists(getAPropertyWrite("responseType")) and
405+
result = "text"
406+
}
407+
408+
private DataFlow::FunctionNode getAnEventListener() {
409+
result = getAPropertyWrite("on" + any(string s)).getRhs().getAFunctionValue()
410+
or
411+
result = getAMethodCall("addEventListener").getArgument(1).getAFunctionValue()
412+
}
413+
414+
private DataFlow::SourceNode getAnAlias() {
415+
result = this
416+
or
417+
// The value of `this` in an event listener refers to the XHR object
418+
result = getAnEventListener().getReceiver()
419+
}
420+
421+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
422+
promise = false and
423+
(
424+
exists(string prop | result = getAnAlias().getAPropertyRead(prop) |
425+
prop = "response" and responseType = getAssignedResponseType()
426+
or
427+
prop = "responseText" and responseType = "text"
428+
or
429+
prop = "statusText" and responseType = "text"
430+
or
431+
prop = "responseXML" and responseType = "document"
432+
)
433+
or
434+
responseType = "text" and
435+
exists(string method | result = getAnAlias().getAMethodCall(method) |
436+
method = "getAllResponseHeaders" or
437+
method = "getResponseHeader"
438+
)
439+
)
440+
}
261441
}
262442

263443
/**
264444
* A model of a URL request made using the `XhrIo` class from the closure library.
265445
*/
266446
private class ClosureXhrIoRequest extends ClientRequest::Range {
447+
DataFlow::SourceNode xhrIo;
448+
boolean static;
449+
267450
ClosureXhrIoRequest() {
268-
exists(DataFlow::SourceNode xhrIo | xhrIo = Closure::moduleImport("goog.net.XhrIo") |
269-
this = xhrIo.getAMethodCall("send")
451+
xhrIo = Closure::moduleImport("goog.net.XhrIo") and
452+
(
453+
this = xhrIo.getAMethodCall("send") and static = true
270454
or
271-
this = xhrIo.getAnInstantiation().getAMethodCall("send")
455+
this = xhrIo.getAnInstantiation().getAMethodCall("send") and static = false
272456
)
273457
}
274458

@@ -280,4 +464,48 @@ private class ClosureXhrIoRequest extends ClientRequest::Range {
280464
result = getArgument(2) or
281465
result = getArgument(3)
282466
}
467+
468+
/** Gets an event listener with `this` bound to this object. */
469+
private DataFlow::FunctionNode getAnEventListener() {
470+
result = getAnArgument().getAFunctionValue()
471+
or
472+
static = false and
473+
exists(DataFlow::MethodCallNode listen, string name |
474+
listen = getAMethodCall(name) and
475+
(name = "listen" or name = "listenOnce") and
476+
xhrIo.flowsTo(listen.getArgument(3)) and
477+
result = listen
478+
)
479+
}
480+
481+
private DataFlow::SourceNode getAnAlias() {
482+
static = false and
483+
result = xhrIo
484+
or
485+
result = getAnEventListener().getReceiver()
486+
}
487+
488+
private string getAssignedResponseType() {
489+
getAMethodCall("setResponseType").getArgument(0).mayHaveStringValue(result)
490+
or
491+
not exists(getAMethodCall("setResponseType")) and
492+
result = "text"
493+
}
494+
495+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
496+
promise = false and
497+
exists(string method | result = getAnAlias().getAMethodCall(method) |
498+
method = "getResponse" and responseType = getAssignedResponseType()
499+
or
500+
method = "getResponseHeader" and responseType = "text"
501+
or
502+
method = "getResponseJson" and responseType = "json"
503+
or
504+
method = "getResponseText" and responseType = "text"
505+
or
506+
method = "getResponseXml" and responseType = "document"
507+
or
508+
method = "getStatusText" and responseType = "text"
509+
)
510+
}
283511
}

0 commit comments

Comments
 (0)