Skip to content

Commit 74982cb

Browse files
authored
Merge pull request #307 from github/hmac-outgoing-http-2
Model some more HTTP clients
2 parents 141f5f7 + 88885a2 commit 74982cb

File tree

13 files changed

+242
-0
lines changed

13 files changed

+242
-0
lines changed

ql/lib/codeql/ruby/frameworks/HTTPClients.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
*/
44

55
private import codeql.ruby.frameworks.http_clients.NetHTTP
6+
private import codeql.ruby.frameworks.http_clients.Excon
7+
private import codeql.ruby.frameworks.http_clients.Faraday
8+
private import codeql.ruby.frameworks.http_clients.RestClient
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
private import ruby
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.ApiGraphs
4+
5+
/**
6+
* A call that makes an HTTP request using `Excon`.
7+
* ```ruby
8+
* # one-off request
9+
* Excon.get("http://example.com").body
10+
*
11+
* # connection re-use
12+
* connection = Excon.new("http://example.com")
13+
* connection.get(path: "/").body
14+
* connection.request(method: :get, path: "/")
15+
* ```
16+
*
17+
* TODO: pipelining, streaming responses
18+
* https://github.com/excon/excon/blob/master/README.md
19+
*/
20+
class ExconHTTPRequest extends HTTP::Client::Request::Range {
21+
DataFlow::Node request;
22+
DataFlow::CallNode responseBody;
23+
24+
ExconHTTPRequest() {
25+
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
26+
requestNode =
27+
[
28+
// one-off requests
29+
API::getTopLevelMember("Excon"),
30+
// connection re-use
31+
API::getTopLevelMember("Excon").getInstance()
32+
]
33+
.getReturn([
34+
// Excon#request exists but Excon.request doesn't.
35+
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
36+
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
37+
]) and
38+
responseBody = requestNode.getAMethodCall("body") and
39+
this = request.asExpr().getExpr()
40+
)
41+
}
42+
43+
override DataFlow::Node getResponseBody() { result = responseBody }
44+
45+
override string getFramework() { result = "Excon" }
46+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
private import ruby
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.ApiGraphs
4+
5+
/**
6+
* A call that makes an HTTP request using `Faraday`.
7+
* ```ruby
8+
* # one-off request
9+
* Faraday.get("http://example.com").body
10+
*
11+
* # connection re-use
12+
* connection = Faraday.new("http://example.com")
13+
* connection.get("/").body
14+
* ```
15+
*/
16+
class FaradayHTTPRequest extends HTTP::Client::Request::Range {
17+
DataFlow::Node request;
18+
DataFlow::CallNode responseBody;
19+
20+
FaradayHTTPRequest() {
21+
exists(API::Node requestNode |
22+
requestNode =
23+
[
24+
// one-off requests
25+
API::getTopLevelMember("Faraday"),
26+
// connection re-use
27+
API::getTopLevelMember("Faraday").getInstance()
28+
].getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
29+
responseBody = requestNode.getAMethodCall("body") and
30+
request = requestNode.getAnImmediateUse() and
31+
this = request.asExpr().getExpr()
32+
)
33+
}
34+
35+
override DataFlow::Node getResponseBody() { result = responseBody }
36+
37+
override string getFramework() { result = "Faraday" }
38+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
private import ruby
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.ApiGraphs
4+
5+
/**
6+
* A call that makes an HTTP request using `RestClient`.
7+
* ```ruby
8+
* RestClient.get("http://example.com").body
9+
* ```
10+
*/
11+
class RestClientHTTPRequest extends HTTP::Client::Request::Range {
12+
DataFlow::Node request;
13+
DataFlow::CallNode responseBody;
14+
15+
RestClientHTTPRequest() {
16+
exists(API::Node requestNode |
17+
requestNode =
18+
API::getTopLevelMember("RestClient")
19+
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
20+
request = requestNode.getAnImmediateUse() and
21+
responseBody = requestNode.getAMethodCall("body") and
22+
this = request.asExpr().getExpr()
23+
)
24+
}
25+
26+
override DataFlow::Node getResponseBody() { result = responseBody }
27+
28+
override string getFramework() { result = "RestClient" }
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| Excon.rb:3:9:3:40 | call to get | Excon.rb:4:1:4:10 | call to body |
2+
| Excon.rb:6:9:6:60 | call to post | Excon.rb:7:1:7:10 | call to body |
3+
| Excon.rb:9:9:9:59 | call to put | Excon.rb:10:1:10:10 | call to body |
4+
| Excon.rb:12:9:12:61 | call to patch | Excon.rb:13:1:13:10 | call to body |
5+
| Excon.rb:15:9:15:43 | call to delete | Excon.rb:16:1:16:10 | call to body |
6+
| Excon.rb:18:9:18:41 | call to head | Excon.rb:19:1:19:10 | call to body |
7+
| Excon.rb:21:9:21:44 | call to options | Excon.rb:22:1:22:10 | call to body |
8+
| Excon.rb:24:9:24:42 | call to trace | Excon.rb:25:1:25:10 | call to body |
9+
| Excon.rb:28:9:28:33 | call to get | Excon.rb:29:1:29:10 | call to body |
10+
| Excon.rb:31:10:31:38 | call to post | Excon.rb:32:1:32:11 | call to body |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import codeql.ruby.frameworks.http_clients.Excon
2+
import codeql.ruby.DataFlow
3+
4+
query DataFlow::Node exconHTTPRequests(ExconHTTPRequest e) { result = e.getResponseBody() }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require "excon"
2+
3+
resp1 = Excon.get("http://example.com/")
4+
resp1.body
5+
6+
resp2 = Excon.post("http://example.com/", body: "some_data")
7+
resp2.body
8+
9+
resp3 = Excon.put("http://example.com/", body: "some_data")
10+
resp3.body
11+
12+
resp4 = Excon.patch("http://example.com/", body: "some_data")
13+
resp4.body
14+
15+
resp5 = Excon.delete("http://example.com/")
16+
resp5.body
17+
18+
resp6 = Excon.head("http://example.com/")
19+
resp6.body
20+
21+
resp7 = Excon.options("http://example.com/")
22+
resp7.body
23+
24+
resp8 = Excon.trace("http://example.com/")
25+
resp8.body
26+
27+
connection = Excon.new("http://example.com")
28+
resp9 = connection.get(path: "/")
29+
resp9.body
30+
31+
resp10 = connection.post(path: "/foo")
32+
resp10.body
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
| Faraday.rb:3:9:3:42 | call to get | Faraday.rb:4:1:4:10 | call to body |
2+
| Faraday.rb:6:9:6:62 | call to post | Faraday.rb:7:1:7:10 | call to body |
3+
| Faraday.rb:9:9:9:61 | call to put | Faraday.rb:10:1:10:10 | call to body |
4+
| Faraday.rb:12:9:12:63 | call to patch | Faraday.rb:13:1:13:10 | call to body |
5+
| Faraday.rb:15:9:15:45 | call to delete | Faraday.rb:16:1:16:10 | call to body |
6+
| Faraday.rb:18:9:18:43 | call to head | Faraday.rb:19:1:19:10 | call to body |
7+
| Faraday.rb:24:9:24:44 | call to trace | Faraday.rb:25:1:25:10 | call to body |
8+
| Faraday.rb:28:9:28:27 | call to get | Faraday.rb:29:1:29:10 | call to body |
9+
| Faraday.rb:31:10:31:46 | call to post | Faraday.rb:32:1:32:11 | call to body |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import codeql.ruby.frameworks.http_clients.Faraday
2+
import codeql.ruby.DataFlow
3+
4+
query DataFlow::Node faradayHTTPRequests(FaradayHTTPRequest e) { result = e.getResponseBody() }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require "faraday"
2+
3+
resp1 = Faraday.get("http://example.com/")
4+
resp1.body
5+
6+
resp2 = Faraday.post("http://example.com/", body: "some_data")
7+
resp2.body
8+
9+
resp3 = Faraday.put("http://example.com/", body: "some_data")
10+
resp3.body
11+
12+
resp4 = Faraday.patch("http://example.com/", body: "some_data")
13+
resp4.body
14+
15+
resp5 = Faraday.delete("http://example.com/")
16+
resp5.body
17+
18+
resp6 = Faraday.head("http://example.com/")
19+
resp6.body
20+
21+
resp7 = Faraday.options("http://example.com/")
22+
resp7.body
23+
24+
resp8 = Faraday.trace("http://example.com/")
25+
resp8.body
26+
27+
connection = Faraday.new("http://example.com")
28+
resp9 = connection.get("/")
29+
resp9.body
30+
31+
resp10 = connection.post("/foo", some: "data")
32+
resp10.body

0 commit comments

Comments
 (0)