Skip to content

Commit 6a1cfb6

Browse files
committed
feat(js): Add Axios Instance support and add tests
1 parent f48012a commit 6a1cfb6

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ module ClientRequest {
254254
method = "request" and
255255
result = this.getOptionArgument(0, "data")
256256
or
257-
method = ["post", "put"] and
257+
method = ["post", "put", "patch"] and
258258
result = [this.getArgument(1), this.getOptionArgument(2, "data")]
259259
or
260260
method = ["postForm", "putForm", "patchForm"] and result = this.getArgument(1)
@@ -289,6 +289,68 @@ module ClientRequest {
289289
}
290290
}
291291

292+
class AxiosInstanceRequest extends ClientRequest::Range, API::CallNode {
293+
string method;
294+
API::CallNode instance;
295+
296+
// Instances of axios, e.g. `axios.create({ ... })`
297+
AxiosInstanceRequest() {
298+
instance = axios().getMember(["create", "createInstance"]).getACall() and
299+
method = [httpMethodName(), "request", "postForm", "putForm", "patchForm", "getUri"] and
300+
this = instance.getReturn().getMember(method).getACall()
301+
}
302+
303+
private int getOptionsArgIndex() {
304+
(method = "get" or method = "delete" or method = "head") and
305+
result = 0
306+
or
307+
(method = "post" or method = "put" or method = "patch") and
308+
result = 1
309+
}
310+
311+
private DataFlow::Node getOptionArgument(string name) {
312+
result = this.getOptionArgument(this.getOptionsArgIndex(), name)
313+
}
314+
315+
override DataFlow::Node getUrl() {
316+
result = this.getArgument(0) or
317+
result = this.getOptionArgument(urlPropertyName())
318+
}
319+
320+
override DataFlow::Node getHost() {
321+
result = instance.getOptionArgument(0, "baseURL")
322+
}
323+
324+
override DataFlow::Node getADataNode() {
325+
method = ["post", "put", "patch"] and
326+
result = [this.getArgument(1), this.getOptionArgument(2, "data")]
327+
or
328+
method = ["postForm", "putForm", "patchForm"] and result = this.getArgument(1)
329+
or
330+
result = this.getOptionArgument([0 .. 2], ["headers", "params"])
331+
}
332+
333+
/** Gets the response type from the options passed in. */
334+
string getResponseType() {
335+
exists(DataFlow::Node option | option = instance.getOptionArgument(0, "responseType") |
336+
option.mayHaveStringValue(result)
337+
)
338+
or
339+
not exists(this.getOptionArgument("responseType")) and
340+
result = "json"
341+
}
342+
343+
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
344+
responseType = this.getResponseType() and
345+
promise = true and
346+
result = this
347+
or
348+
responseType = this.getResponseType() and
349+
promise = false and
350+
result = this.getReturn().getPromisedError().getMember("response").asSource()
351+
}
352+
}
353+
292354
/** An expression that is used as a credential in a request. */
293355
private class AuthorizationHeader extends CredentialsNode {
294356
AuthorizationHeader() {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ test_ClientRequest
55
| apollo.js:17:1:17:34 | new Pre ... yurl"}) |
66
| apollo.js:20:1:20:77 | createN ... phql'}) |
77
| apollo.js:23:1:23:31 | new Web ... wsUri}) |
8+
| axios.ts:13:5:14:37 | api\\n ... repo}`) |
9+
| axios.ts:25:5:26:45 | api\\n ... , data) |
810
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) |
911
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) |
1012
| puppeteer.ts:6:11:6:42 | page.go ... e.com') |
@@ -111,6 +113,7 @@ test_ClientRequest
111113
| tst.js:349:5:349:30 | axios.g ... url }) |
112114
| tst.js:352:5:352:66 | axiosIn ... text"}) |
113115
test_getADataNode
116+
| axios.ts:25:5:26:45 | api\\n ... , data) | axios.ts:26:41:26:44 | data |
114117
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:15:18:15:55 | { 'Cont ... json' } |
115118
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:16:15:16:35 | {x: 'te ... 'test'} |
116119
| superagent.js:6:5:6:32 | superag ... st(url) | superagent.js:6:39:6:42 | data |
@@ -159,6 +162,8 @@ test_getADataNode
159162
| tst.js:347:5:347:30 | axios.p ... , data) | tst.js:347:26:347:29 | data |
160163
| tst.js:348:5:348:38 | axios.p ... config) | tst.js:348:26:348:29 | data |
161164
test_getHost
165+
| axios.ts:13:5:14:37 | api\\n ... repo}`) | axios.ts:4:14:4:37 | "https: ... ub.com" |
166+
| axios.ts:25:5:26:45 | api\\n ... , data) | axios.ts:4:14:4:37 | "https: ... ub.com" |
162167
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
163168
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
164169
| tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host |
@@ -173,6 +178,8 @@ test_getUrl
173178
| apollo.js:17:1:17:34 | new Pre ... yurl"}) | apollo.js:17:26:17:32 | "myurl" |
174179
| apollo.js:20:1:20:77 | createN ... phql'}) | apollo.js:20:30:20:75 | 'https: ... raphql' |
175180
| apollo.js:23:1:23:31 | new Web ... wsUri}) | apollo.js:23:25:23:29 | wsUri |
181+
| axios.ts:13:5:14:37 | api\\n ... repo}`) | axios.ts:14:12:14:36 | `/repos ... {repo}` |
182+
| axios.ts:25:5:26:45 | api\\n ... , data) | axios.ts:26:14:26:38 | `/repos ... {repo}` |
176183
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:11:7:5 | {\\n ... ,\\n } |
177184
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:6:14:6:16 | url |
178185
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:11:17:5 | {\\n ... }\\n } |
@@ -289,6 +296,8 @@ test_getUrl
289296
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:19:352:65 | {method ... "text"} |
290297
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:40:352:42 | url |
291298
test_getAResponseDataNode
299+
| axios.ts:13:5:14:37 | api\\n ... repo}`) | axios.ts:13:5:14:37 | api\\n ... repo}`) | json | true |
300+
| axios.ts:25:5:26:45 | api\\n ... , data) | axios.ts:25:5:26:45 | api\\n ... , data) | json | true |
292301
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:5:7:6 | axios({ ... \\n }) | json | true |
293302
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | json | true |
294303
| superagent.js:4:5:4:26 | superag ... ', url) | superagent.js:4:5:4:26 | superag ... ', url) | stream | true |
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import axios from "axios";
2+
3+
let api = axios.create({
4+
baseURL: "https://api.github.com",
5+
timeout: 1000,
6+
responseType: "json",
7+
headers: { "X-Custom-Header": "foobar" }
8+
});
9+
10+
export default api;
11+
12+
export async function getRepo(owner: string, repo: string) {
13+
api
14+
.get(`/repos/${owner}/${repo}`)
15+
.then((response) => {
16+
console.log("Repository data:", response.data);
17+
return response.data;
18+
})
19+
.catch((error) => {
20+
console.error("Error fetching user:", error);
21+
});
22+
}
23+
24+
export async function updateUser(owner: string, repo: string, data: any) {
25+
api
26+
.patch(`/repos/${owner}/${repo}`, data)
27+
.then((response) => {
28+
console.log("User updated:", response.data);
29+
})
30+
.catch((error) => {
31+
console.error("Error updating user:", error);
32+
});
33+
}

0 commit comments

Comments
 (0)