Skip to content

Commit 13e8559

Browse files
committed
add more ClientRequest models for JQuery
1 parent c690e25 commit 13e8559

3 files changed

Lines changed: 133 additions & 16 deletions

File tree

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

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,26 +258,99 @@ private class JQueryChainedElement extends DOM::Element, InvokeExpr {
258258
}
259259

260260
/**
261-
* A model of a URL request made using the `jQuery.ajax` or `jQuery.getJSON`.
261+
* A model of a URL request made using the `jQuery.ajax`.
262262
*/
263-
private class JQueryClientRequest extends ClientRequest::Range {
264-
JQueryClientRequest() {
265-
exists(string name |
266-
name = "ajax" or
267-
name = "getJSON"
268-
|
269-
this = jquery().getAMemberCall(name)
270-
)
271-
}
263+
private class JQueryAjaxCall extends ClientRequest::Range {
264+
JQueryAjaxCall() { this = jquery().getAMemberCall("ajax") }
272265

273266
override DataFlow::Node getUrl() {
274-
result = getArgument(0) or
267+
result = getArgument(0) and not exists(getOptionArgument(0, _)) or
275268
result = getOptionArgument([0 .. 1], "url")
276269
}
277270

278271
override DataFlow::Node getHost() { none() }
279272

280273
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
274+
275+
private string getResponseType() {
276+
getOptionArgument([0 .. 1], "dataType").mayHaveStringValue(result)
277+
}
278+
279+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
280+
(
281+
responseType = getResponseType()
282+
or
283+
not exists(getResponseType()) and responseType = ""
284+
) and
285+
promise = false and
286+
result =
287+
getOptionArgument([0 .. 1], "success")
288+
.getALocalSource()
289+
.(DataFlow::FunctionNode)
290+
.getParameter(0)
291+
}
292+
}
293+
294+
/**
295+
* A model of a URL request made using a `jQuery.ajax` shorthand.
296+
* E.g. `jQuery.getJSON`, `jQuery.post` etc.
297+
* See: https://api.jquery.com/category/ajax/shorthand-methods/.
298+
*
299+
* The method signatures:
300+
* jQuery.get( url [, data ] [, success ] [, dataType ] )
301+
* jQuery.getJSON( url [, data ] [, success ] )
302+
* jQuery.getScript( url [, success ] )
303+
* jQuery.post( url [, data ] [, success ] [, dataType ] )
304+
* .load( url [, data ] [, complete ] )
305+
*/
306+
private class JQueryAjaxShortHand extends ClientRequest::Range {
307+
string name;
308+
309+
JQueryAjaxShortHand() {
310+
(
311+
name = "get" or
312+
name = "getJSON" or
313+
name = "getScript" or
314+
name = "post"
315+
) and
316+
this = jquery().getAMemberCall(name)
317+
or
318+
name = "load" and
319+
this = JQuery::objectRef().getAMethodCall(name)
320+
}
321+
322+
override DataFlow::Node getUrl() { result = getArgument(0) }
323+
324+
override DataFlow::Node getHost() { none() }
325+
326+
override DataFlow::Node getADataNode() {
327+
result = getArgument(1) and
328+
not name = "getScript" and // doesn't have a data-node.
329+
not result.getALocalSource() instanceof DataFlow::FunctionNode // looks like the success callback.
330+
}
331+
332+
string getResponseType() {
333+
(name = "get" or name = "post") and
334+
getLastArgument().mayHaveStringValue(result)
335+
or
336+
name = "getJSON" and result = "json"
337+
or
338+
(name = "getScript" or name = "load") and
339+
result = "text"
340+
}
341+
342+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
343+
(
344+
responseType = getResponseType()
345+
or
346+
not exists(getResponseType()) and responseType = ""
347+
) and
348+
promise = false and
349+
// one of the two last arguments
350+
result =
351+
getCallback([getNumArgument() - 2 .. getNumArgument() - 1])
352+
.getParameter(0)
353+
}
281354
}
282355

283356
module JQuery {

javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ test_ClientRequest
5555
| tst.js:171:2:171:10 | base(url) |
5656
| tst.js:172:2:172:14 | variant1(url) |
5757
| tst.js:173:2:173:14 | variant2(url) |
58+
| tst.js:177:5:177:49 | $.get( ... a ) {}) |
59+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) |
60+
| tst.js:181:2:181:69 | $.getSc ... r ) {}) |
61+
| tst.js:183:2:183:60 | $.post( ... ) { }) |
62+
| tst.js:185:2:185:60 | $( "#re ... lt) {}) |
63+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) |
64+
| tst.js:195:2:195:54 | $.get( ... "json") |
5865
test_getADataNode
5966
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
6067
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -74,13 +81,16 @@ test_getADataNode
7481
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:52:68:60 | queryData |
7582
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:48:69:56 | queryData |
7683
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:24:74:27 | data |
77-
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:27:77:30 | data |
84+
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:20:77:31 | {data: data} |
7885
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:82:14:82:17 | data |
7986
| tst.js:98:15:98:34 | new XMLHttpRequest() | tst.js:101:14:101:17 | data |
8087
| tst.js:106:16:106:35 | new XMLHttpRequest() | tst.js:108:15:108:18 | data |
8188
| tst.js:117:5:121:6 | request ... \\n }) | tst.js:117:18:121:5 | functio ... ;\\n } |
8289
| tst.js:123:5:127:6 | request ... \\n }) | tst.js:123:18:123:29 | {json: true} |
8390
| tst.js:129:5:129:37 | request ... true}) | tst.js:129:25:129:36 | {json: true} |
91+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:31:179:38 | "MyData" |
92+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" |
93+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" |
8494
test_getHost
8595
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
8696
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
@@ -121,11 +131,9 @@ test_getUrl
121131
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:20:68:22 | url |
122132
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:20:69:22 | url |
123133
| tst.js:74:5:74:29 | $.ajax( ... data}) | tst.js:74:12:74:14 | url |
124-
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:12:75:34 | {url: u ... : data} |
125134
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:18:75:20 | url |
126135
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:15:77:17 | url |
127136
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:15:78:37 | {url: u ... : data} |
128-
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:21:78:23 | url |
129137
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:81:17:81:19 | url |
130138
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:14:87:24 | relativeUrl |
131139
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:11:89:22 | {host: host} |
@@ -150,6 +158,13 @@ test_getUrl
150158
| tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url |
151159
| tst.js:172:2:172:14 | variant1(url) | tst.js:172:11:172:13 | url |
152160
| tst.js:173:2:173:14 | variant2(url) | tst.js:173:11:173:13 | url |
161+
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:12:177:27 | "ajax/test.html" |
162+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:13:179:28 | "ajax/test.json" |
163+
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:15:181:28 | "ajax/test.js" |
164+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:10:183:25 | "ajax/test.html" |
165+
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:23:185:38 | "ajax/test.html" |
166+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:189:8:189:27 | "http://example.org" |
167+
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:9:195:24 | "ajax/test.json" |
153168
test_getAResponseDataNode
154169
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
155170
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
@@ -199,3 +214,10 @@ test_getAResponseDataNode
199214
| tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:35:151:37 | res | stream | false |
200215
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:162:9:162:29 | xhr.get ... eJson() | json | false |
201216
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:163:9:163:32 | xhr.get ... aders() | headers | false |
217+
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:40:177:43 | data | | false |
218+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:51:179:54 | data | json | false |
219+
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:41:181:44 | data | text | false |
220+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:50:183:53 | data | | false |
221+
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:50:185:55 | result | text | false |
222+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:191:15:191:22 | ajaxData | json | false |
223+
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:37:195:40 | data | json | false |

javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ import {ClientRequest, net} from 'electron';
7474
$.ajax(url, {data: data});
7575
$.ajax({url: url, tdata: data});
7676

77-
$.getJSON(url, {data: data});
78-
$.getJSON({url: url, tdata: data});
77+
$.getJSON(url, {data: data}); // the entire "{data: data}" object is the data.
78+
$.getJSON({url: url, tdata: data}); // not how to use getJSON.
7979

8080
var xhr = new XMLHttpRequest();
8181
xhr.open(_, url);
@@ -172,3 +172,25 @@ import {ClientRequest, net} from 'electron';
172172
variant1(url);
173173
variant2(url);
174174
});
175+
176+
(function() {
177+
$.get( "ajax/test.html", function( data ) {});
178+
179+
$.getJSON( "ajax/test.json", "MyData", function( data ) {});
180+
181+
$.getScript( "ajax/test.js", function( data, textStatus, jqxhr ) {});
182+
183+
$.post( "ajax/test.html", "PostData", function( data ) { });
184+
185+
$( "#result" ).load( "ajax/test.html", function(result) {});
186+
187+
$.ajax({
188+
type: "POST",
189+
url: "http://example.org",
190+
data: "AjaxData",
191+
success: (ajaxData) => {},
192+
dataType: "json"
193+
});
194+
195+
$.get( "ajax/test.json", function( data ) {}, "json");
196+
});

0 commit comments

Comments
 (0)