Skip to content

Commit 7e093a8

Browse files
authored
Merge pull request github#3041 from erik-krogh/JQueryAjax
Approved by esbena
2 parents ff03478 + 486efba commit 7e093a8

File tree

4 files changed

+210
-19
lines changed

4 files changed

+210
-19
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- [for-in](https://www.npmjs.com/package/for-in)
3333
- [for-own](https://www.npmjs.com/package/for-own)
3434
- [http2](https://nodejs.org/api/http2.html)
35+
- [jQuery](https://jquery.com/)
3536
- [lazy-cache](https://www.npmjs.com/package/lazy-cache)
3637
- [mongodb](https://www.npmjs.com/package/mongodb)
3738
- [react](https://www.npmjs.com/package/react)

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

Lines changed: 133 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -258,26 +258,145 @@ 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+
* Classes and predicates for modelling `ClientRequest`s in JQuery.
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-
)
263+
private module JQueryClientRequest {
264+
/**
265+
* A model of a URL request made using the `jQuery.ajax`.
266+
*/
267+
private class JQueryAjaxCall extends ClientRequest::Range {
268+
JQueryAjaxCall() { this = jquery().getAMemberCall("ajax") }
269+
270+
override DataFlow::Node getUrl() {
271+
result = getArgument(0) and not exists(getOptionArgument(0, _))
272+
or
273+
result = getOptionArgument([0 .. 1], "url")
274+
}
275+
276+
override DataFlow::Node getHost() { none() }
277+
278+
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
279+
280+
private string getResponseType() {
281+
getOptionArgument([0 .. 1], "dataType").mayHaveStringValue(result)
282+
}
283+
284+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
285+
(
286+
responseType = getResponseType()
287+
or
288+
not exists(getResponseType()) and responseType = ""
289+
) and
290+
promise = false and
291+
(
292+
result =
293+
getOptionArgument([0 .. 1], "success")
294+
.getALocalSource()
295+
.(DataFlow::FunctionNode)
296+
.getParameter(0)
297+
or
298+
result =
299+
getAResponseNodeFromAnXHRObject(getOptionArgument([0 .. 1],
300+
any(string method | method = "error" or method = "complete"))
301+
.getALocalSource()
302+
.(DataFlow::FunctionNode)
303+
.getParameter(0))
304+
or
305+
result = getAnAjaxCallbackDataNode(this)
306+
)
307+
}
271308
}
272309

273-
override DataFlow::Node getUrl() {
274-
result = getArgument(0) or
275-
result = getOptionArgument([0 .. 1], "url")
310+
/**
311+
* Gets a response data node from a call to a method on jqXHR Object `request`.
312+
*/
313+
private DataFlow::Node getAnAjaxCallbackDataNode(ClientRequest::Range request) {
314+
result =
315+
request
316+
.getAMemberCall(any(string s | s = "done" or s = "then"))
317+
.getCallback(0)
318+
.getParameter(0)
319+
or
320+
result =
321+
getAResponseNodeFromAnXHRObject(request.getAMemberCall("fail").getCallback(0).getParameter(0))
276322
}
277323

278-
override DataFlow::Node getHost() { none() }
324+
/**
325+
* Gets a node refering to the response contained in an `jqXHR` object.
326+
*/
327+
private DataFlow::SourceNode getAResponseNodeFromAnXHRObject(DataFlow::SourceNode obj) {
328+
result =
329+
obj
330+
.getAPropertyRead(any(string s |
331+
s = "responseText" or
332+
s = "responseXML"
333+
))
334+
}
279335

280-
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
336+
/**
337+
* A model of a URL request made using a `jQuery.ajax` shorthand.
338+
* E.g. `jQuery.getJSON`, `jQuery.post` etc.
339+
* See: https://api.jquery.com/category/ajax/shorthand-methods/.
340+
*
341+
* Models the following method signatures:
342+
* - `jQuery.get( url [, data ] [, success ] [, dataType ] )`
343+
* - `jQuery.getJSON( url [, data ] [, success ] )`
344+
* - `jQuery.getScript( url [, success ] )`
345+
* - `jQuery.post( url [, data ] [, success ] [, dataType ] )`
346+
* - `.load( url [, data ] [, complete ] )`
347+
*/
348+
private class JQueryAjaxShortHand extends ClientRequest::Range {
349+
string name;
350+
351+
JQueryAjaxShortHand() {
352+
(
353+
name = "get" or
354+
name = "getJSON" or
355+
name = "getScript" or
356+
name = "post"
357+
) and
358+
this = jquery().getAMemberCall(name)
359+
or
360+
name = "load" and
361+
this = JQuery::objectRef().getAMethodCall(name)
362+
}
363+
364+
override DataFlow::Node getUrl() { result = getArgument(0) }
365+
366+
override DataFlow::Node getHost() { none() }
367+
368+
override DataFlow::Node getADataNode() {
369+
result = getArgument(1) and
370+
not name = "getScript" and // doesn't have a data-node.
371+
not result.getALocalSource() instanceof DataFlow::FunctionNode // looks like the success callback.
372+
}
373+
374+
private string getResponseType() {
375+
(name = "get" or name = "post") and
376+
getLastArgument().mayHaveStringValue(result) and
377+
getNumArgument() > 1
378+
or
379+
name = "getJSON" and result = "json"
380+
or
381+
(name = "getScript" or name = "load") and
382+
result = "text"
383+
}
384+
385+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
386+
(
387+
responseType = getResponseType()
388+
or
389+
not exists(getResponseType()) and responseType = ""
390+
) and
391+
promise = false and
392+
(
393+
// one of the two last arguments
394+
result = getCallback([getNumArgument() - 2 .. getNumArgument() - 1]).getParameter(0)
395+
or
396+
result = getAnAjaxCallbackDataNode(this)
397+
)
398+
}
399+
}
281400
}
282401

283402
module JQuery {

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ 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") |
65+
| tst.js:197:2:197:45 | $.ajax( ... blob"}) |
66+
| tst.js:200:2:200:21 | $.get("example.php") |
67+
| tst.js:202:5:208:7 | $.ajax( ... }}) |
68+
| tst.js:210:2:210:21 | $.get("example.php") |
5869
test_getADataNode
5970
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
6071
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -74,13 +85,16 @@ test_getADataNode
7485
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:52:68:60 | queryData |
7586
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:48:69:56 | queryData |
7687
| 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 |
88+
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:20:77:31 | {data: data} |
7889
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:82:14:82:17 | data |
7990
| tst.js:98:15:98:34 | new XMLHttpRequest() | tst.js:101:14:101:17 | data |
8091
| tst.js:106:16:106:35 | new XMLHttpRequest() | tst.js:108:15:108:18 | data |
8192
| tst.js:117:5:121:6 | request ... \\n }) | tst.js:117:18:121:5 | functio ... ;\\n } |
8293
| tst.js:123:5:127:6 | request ... \\n }) | tst.js:123:18:123:29 | {json: true} |
8394
| tst.js:129:5:129:37 | request ... true}) | tst.js:129:25:129:36 | {json: true} |
95+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:31:179:38 | "MyData" |
96+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" |
97+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" |
8498
test_getHost
8599
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
86100
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
@@ -121,11 +135,9 @@ test_getUrl
121135
| tst.js:68:5:68:23 | superagent.get(url) | tst.js:68:20:68:22 | url |
122136
| tst.js:69:5:69:23 | superagent.get(url) | tst.js:69:20:69:22 | url |
123137
| 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} |
125138
| tst.js:75:5:75:35 | $.ajax( ... data}) | tst.js:75:18:75:20 | url |
126139
| tst.js:77:5:77:32 | $.getJS ... data}) | tst.js:77:15:77:17 | url |
127140
| 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 |
129141
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:81:17:81:19 | url |
130142
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:14:87:24 | relativeUrl |
131143
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:11:89:22 | {host: host} |
@@ -150,6 +162,17 @@ test_getUrl
150162
| tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url |
151163
| tst.js:172:2:172:14 | variant1(url) | tst.js:172:11:172:13 | url |
152164
| tst.js:173:2:173:14 | variant2(url) | tst.js:173:11:173:13 | url |
165+
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:12:177:27 | "ajax/test.html" |
166+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:13:179:28 | "ajax/test.json" |
167+
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:15:181:28 | "ajax/test.js" |
168+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:10:183:25 | "ajax/test.html" |
169+
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:23:185:38 | "ajax/test.html" |
170+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:189:8:189:27 | "http://example.org" |
171+
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:9:195:24 | "ajax/test.json" |
172+
| tst.js:197:2:197:45 | $.ajax( ... blob"}) | tst.js:197:15:197:25 | "ajax/blob" |
173+
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:8:200:20 | "example.php" |
174+
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:203:10:203:22 | "example.php" |
175+
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:8:210:20 | "example.php" |
153176
test_getAResponseDataNode
154177
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
155178
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
@@ -199,3 +222,14 @@ test_getAResponseDataNode
199222
| tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:35:151:37 | res | stream | false |
200223
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:162:9:162:29 | xhr.get ... eJson() | json | false |
201224
| tst.js:160:5:160:17 | xhr.send(url) | tst.js:163:9:163:32 | xhr.get ... aders() | headers | false |
225+
| tst.js:177:5:177:49 | $.get( ... a ) {}) | tst.js:177:40:177:43 | data | | false |
226+
| tst.js:179:2:179:60 | $.getJS ... a ) {}) | tst.js:179:51:179:54 | data | json | false |
227+
| tst.js:181:2:181:69 | $.getSc ... r ) {}) | tst.js:181:41:181:44 | data | text | false |
228+
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:50:183:53 | data | | false |
229+
| tst.js:185:2:185:60 | $( "#re ... lt) {}) | tst.js:185:50:185:55 | result | text | false |
230+
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:191:15:191:22 | ajaxData | json | false |
231+
| tst.js:195:2:195:54 | $.get( ... "json") | tst.js:195:37:195:40 | data | json | false |
232+
| tst.js:197:2:197:45 | $.ajax( ... blob"}) | tst.js:198:23:198:26 | data | blob | false |
233+
| tst.js:200:2:200:21 | $.get("example.php") | tst.js:200:37:200:44 | response | | false |
234+
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:207:21:207:36 | err.responseText | json | false |
235+
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:55:210:70 | xhr.responseText | | false |

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

Lines changed: 39 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,40 @@ 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+
197+
$.ajax({url: "ajax/blob", dataType: "blob"})
198+
.done(function( data ) {});
199+
200+
$.get("example.php").done(function(response) {})
201+
202+
$.ajax({
203+
url: "example.php",
204+
type: 'POST',
205+
dataType: "json",
206+
error: function (err) {
207+
console.log(err.responseText)
208+
}});
209+
210+
$.get("example.php").fail(function(xhr) {console.log(xhr.responseText)});
211+
});

0 commit comments

Comments
 (0)