Skip to content

Commit 3a4ddc4

Browse files
committed
Model the HTTParty http client
We currently model direct calls like HTTParty.get("http://example.com") but we don't yet handle calls on other classes that have included the `HTTParty` module, like class MyClient include HTTParty end MyClient.get("http://example.com")
1 parent 2a4747b commit 3a4ddc4

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ private import codeql.ruby.frameworks.http_clients.NetHTTP
66
private import codeql.ruby.frameworks.http_clients.Excon
77
private import codeql.ruby.frameworks.http_clients.Faraday
88
private import codeql.ruby.frameworks.http_clients.RestClient
9+
private import codeql.ruby.frameworks.http_clients.HTTParty
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 `HTTParty`.
7+
* ```ruby
8+
* # one-off request - returns the response body
9+
* HTTParty.get("http://example.com")
10+
*
11+
* # TODO: module inclusion
12+
* class MyClass
13+
* include HTTParty
14+
* end
15+
*
16+
* MyClass.new("http://example.com")
17+
* ```
18+
*/
19+
class HTTPartyRequest extends HTTP::Client::Request::Range {
20+
DataFlow::Node request;
21+
DataFlow::CallNode responseBody;
22+
23+
HTTPartyRequest() {
24+
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
25+
requestNode =
26+
API::getTopLevelMember("HTTParty")
27+
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
28+
(
29+
// If HTTParty can recognise the response type, it will parse and return it
30+
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
31+
// object that has a `#body` method.
32+
// So if there's a call to `#body` on the response, treat that as the response body.
33+
exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | responseBody = r)
34+
or
35+
// Otherwise, treat the response as the response body.
36+
not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
37+
responseBody = request
38+
) and
39+
this = request.asExpr().getExpr()
40+
)
41+
}
42+
43+
override DataFlow::Node getResponseBody() { result = responseBody }
44+
45+
override string getFramework() { result = "HTTParty" }
46+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
| HTTParty.rb:5:1:5:35 | call to get | HTTParty.rb:5:1:5:35 | call to get |
2+
| HTTParty.rb:7:1:7:55 | call to post | HTTParty.rb:7:1:7:55 | call to post |
3+
| HTTParty.rb:9:1:9:54 | call to put | HTTParty.rb:9:1:9:54 | call to put |
4+
| HTTParty.rb:11:1:11:56 | call to patch | HTTParty.rb:11:1:11:56 | call to patch |
5+
| HTTParty.rb:15:9:15:46 | call to delete | HTTParty.rb:16:1:16:10 | call to body |
6+
| HTTParty.rb:18:9:18:44 | call to head | HTTParty.rb:19:1:19:10 | call to body |
7+
| HTTParty.rb:21:9:21:47 | call to options | HTTParty.rb:22:1:22:10 | 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.HTTParty
2+
import codeql.ruby.DataFlow
3+
4+
query DataFlow::Node httpartyRequests(HTTPartyRequest e) { result = e.getResponseBody() }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require "httparty"
2+
3+
# If the response body is not nil or an empty string, it will be parsed and returned directly.
4+
5+
HTTParty.get("http://example.com/")
6+
7+
HTTParty.post("http://example.com/", body: "some_data")
8+
9+
HTTParty.put("http://example.com/", body: "some_data")
10+
11+
HTTParty.patch("http://example.com/", body: "some_data")
12+
13+
# Otherwise, `HTTParty::Response` will be returned, which has a `#body` method.
14+
15+
resp5 = HTTParty.delete("http://example.com/")
16+
resp5.body
17+
18+
resp6 = HTTParty.head("http://example.com/")
19+
resp6.body
20+
21+
resp7 = HTTParty.options("http://example.com/")
22+
resp7.body
23+
24+
# HTTParty methods can also be included in other classes.
25+
# This is not yet modelled.
26+
27+
class MyClient
28+
inlcude HTTParty
29+
end
30+
31+
MyClient.get("http://example.com")

0 commit comments

Comments
 (0)