@@ -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
4272module 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 */
266446private 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