Skip to content

Commit 910c19e

Browse files
authored
Merge pull request github#4348 from erik-krogh/needle
Approved by esbena
2 parents 11f39a9 + 89195d7 commit 910c19e

File tree

4 files changed

+109
-0
lines changed

4 files changed

+109
-0
lines changed

change-notes/1.26/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [json-stringify-safe](https://www.npmjs.com/package/json-stringify-safe)
1515
- [json3](https://www.npmjs.com/package/json3)
1616
- [lodash](https://www.npmjs.com/package/lodash)
17+
- [needle](https://www.npmjs.com/package/needle)
1718
- [object-inspect](https://www.npmjs.com/package/object-inspect)
1819
- [pretty-format](https://www.npmjs.com/package/pretty-format)
1920
- [stringify-object](https://www.npmjs.com/package/stringify-object)

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,85 @@ module ClientRequest {
327327
}
328328
}
329329

330+
/**
331+
* Classes for modelling the url request library `needle`.
332+
*/
333+
private module Needle {
334+
/**
335+
* A model of a URL request made using `require("needle")(...)`.
336+
*/
337+
class PromisedNeedleRequest extends ClientRequest::Range {
338+
DataFlow::Node url;
339+
340+
PromisedNeedleRequest() { this = DataFlow::moduleImport("needle").getACall() }
341+
342+
override DataFlow::Node getUrl() { result = getArgument(1) }
343+
344+
override DataFlow::Node getHost() { none() }
345+
346+
override DataFlow::Node getADataNode() {
347+
result = getOptionArgument([2, 3], "headers")
348+
or
349+
result = getArgument(2)
350+
}
351+
352+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
353+
responseType = "fetch.response" and
354+
promise = true and
355+
result = this
356+
}
357+
}
358+
359+
/**
360+
* A model of a URL request made using `require("needle")[method](...)`.
361+
* E.g. `needle.get("http://example.org", (err, resp, body) => {})`.
362+
*
363+
* As opposed to the calls modeled in `PromisedNeedleRequest` these calls do not return promises.
364+
* Instead they take an optional callback as their last argument.
365+
*/
366+
class NeedleMethodRequest extends ClientRequest::Range {
367+
boolean hasData;
368+
369+
NeedleMethodRequest() {
370+
exists(string method |
371+
method = ["get", "head"] and hasData = false
372+
or
373+
method = ["post", "put", "patch", "delete"] and hasData = true
374+
or
375+
method = "request" and hasData = [true, false]
376+
|
377+
this = DataFlow::moduleMember("needle", method).getACall()
378+
)
379+
}
380+
381+
override DataFlow::Node getUrl() { result = getArgument(0) }
382+
383+
override DataFlow::Node getHost() { none() }
384+
385+
override DataFlow::Node getADataNode() {
386+
hasData = true and
387+
(
388+
result = getArgument(1)
389+
or
390+
result = getOptionArgument(2, "headers")
391+
)
392+
or
393+
hasData = false and
394+
result = getOptionArgument(1, "headers")
395+
}
396+
397+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
398+
promise = false and
399+
result = this.getABoundCallbackParameter(this.getNumArgument() - 1, 1) and
400+
responseType = "fetch.response"
401+
or
402+
promise = false and
403+
result = this.getABoundCallbackParameter(this.getNumArgument() - 1, 2) and
404+
responseType = "json"
405+
}
406+
}
407+
}
408+
330409
/**
331410
* A model of a URL request made using the `got` library.
332411
*/

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ test_ClientRequest
6767
| tst.js:202:5:208:7 | $.ajax( ... }}) |
6868
| tst.js:210:2:210:21 | $.get("example.php") |
6969
| tst.js:219:5:219:41 | data.so ... Host"}) |
70+
| tst.js:229:5:229:67 | needle( ... ptions) |
71+
| tst.js:231:5:233:6 | needle. ... \\n }) |
72+
| tst.js:235:5:237:6 | needle. ... \\n }) |
7073
test_getADataNode
7174
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
7275
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -97,6 +100,10 @@ test_getADataNode
97100
| tst.js:183:2:183:60 | $.post( ... ) { }) | tst.js:183:28:183:37 | "PostData" |
98101
| tst.js:187:2:193:3 | $.ajax( ... on"\\n\\t}) | tst.js:190:11:190:20 | "AjaxData" |
99102
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:223:23:223:30 | "foobar" |
103+
| tst.js:229:5:229:67 | needle( ... ptions) | tst.js:228:32:228:70 | { 'X-Cu ... tuna' } |
104+
| tst.js:229:5:229:67 | needle( ... ptions) | tst.js:229:50:229:57 | "MyData" |
105+
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:228:32:228:70 | { 'X-Cu ... tuna' } |
106+
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:44:235:49 | "data" |
100107
test_getHost
101108
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
102109
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
@@ -177,6 +184,9 @@ test_getUrl
177184
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:203:10:203:22 | "example.php" |
178185
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:8:210:20 | "example.php" |
179186
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:219:25:219:40 | {host: "myHost"} |
187+
| tst.js:229:5:229:67 | needle( ... ptions) | tst.js:229:20:229:47 | "http:/ ... oo/bar" |
188+
| tst.js:231:5:233:6 | needle. ... \\n }) | tst.js:231:16:231:35 | "http://example.org" |
189+
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:17:235:41 | "http:/ ... g/post" |
180190
test_getAResponseDataNode
181191
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
182192
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
@@ -238,3 +248,8 @@ test_getAResponseDataNode
238248
| tst.js:202:5:208:7 | $.ajax( ... }}) | tst.js:207:21:207:36 | err.responseText | json | false |
239249
| tst.js:210:2:210:21 | $.get("example.php") | tst.js:210:55:210:70 | xhr.responseText | | false |
240250
| tst.js:219:5:219:41 | data.so ... Host"}) | tst.js:221:29:221:32 | data | text | false |
251+
| tst.js:229:5:229:67 | needle( ... ptions) | tst.js:229:5:229:67 | needle( ... ptions) | fetch.response | true |
252+
| tst.js:231:5:233:6 | needle. ... \\n }) | tst.js:231:44:231:47 | resp | fetch.response | false |
253+
| tst.js:231:5:233:6 | needle. ... \\n }) | tst.js:231:50:231:53 | body | json | false |
254+
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:67:235:70 | resp | fetch.response | false |
255+
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:73:235:76 | body | json | false |

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,18 @@ const net = require("net");
221221
data.socket.on("data", (data) => {});
222222

223223
data.socket.write("foobar");
224+
})();
225+
226+
const needle = require("needle");
227+
(function () {
228+
const options = { headers: { 'X-Custom-Header': 'Bumbaway atuna' } };
229+
needle("POST", "http://example.org/foo/bar", "MyData", options).then(function(resp) { console.log(resp.body) });
230+
231+
needle.get("http://example.org", (err, resp, body) => {
232+
233+
});
234+
235+
needle.post("http://example.org/post", "data", options, (err, resp, body) => {
236+
237+
});
224238
})();

0 commit comments

Comments
 (0)