Skip to content

Commit e2b78df

Browse files
committed
Ruby: Change HTTP::Client::Request to have DataFlow::Node as base class
Although this is a breaking change, as explained in the change-note, it should onyl affect peopel that have created their own HTTP client request modeling, which I assume is none. The alternative would have been to keep the old class/module as deprecated, and introduce a `HTTP::Client::Requestv2` class/module that is based on `DataFlow::Node` instead. The old class could then be deprecated in 1 year, and we could do a rename from `HTTP::Client::Requestv2` -> `HTTP::Client::Request` at the same time. (and then wait 1 more year before being able to delete `HTTP::Client::Requestv2`) All in all, I think this is the right tradeoff, given that CodeQL Ruby is still in beta.
1 parent e6b4d12 commit e2b78df

File tree

10 files changed

+57
-61
lines changed

10 files changed

+57
-61
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: breaking
3+
---
4+
* Changed the `HTTP::Client::Request` concept from using `MethodCall` as base class, to using `DataFlow::Node` as base class. Any class that extend `HTTP::Client::Request::Range` must be changed, but if you only uses the member predicates of `HTTP::Client::Request` no changes are required.

ruby/ql/lib/codeql/ruby/Concepts.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ module HTTP {
480480
* Extend this class to refine existing API models. If you want to model new APIs,
481481
* extend `Request::Range` instead.
482482
*/
483-
class Request extends MethodCall instanceof Request::Range {
483+
class Request extends DataFlow::Node instanceof Request::Range {
484484
/** Gets a node which returns the body of the response */
485485
DataFlow::Node getResponseBody() { result = super.getResponseBody() }
486486

@@ -519,7 +519,7 @@ module HTTP {
519519
* Extend this class to model new APIs. If you want to refine existing API models,
520520
* extend `Request` instead.
521521
*/
522-
abstract class Range extends MethodCall {
522+
abstract class Range extends DataFlow::Node {
523523
/** Gets a node which returns the body of the response */
524524
abstract DataFlow::Node getResponseBody();
525525

ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@ private import codeql.ruby.DataFlow
2323
* TODO: pipelining, streaming responses
2424
* https://github.com/excon/excon/blob/master/README.md
2525
*/
26-
class ExconHttpRequest extends HTTP::Client::Request::Range {
27-
DataFlow::CallNode requestUse;
26+
class ExconHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
2827
API::Node requestNode;
2928
API::Node connectionNode;
3029
DataFlow::Node connectionUse;
3130

3231
ExconHttpRequest() {
33-
requestUse = requestNode.asSource() and
32+
this = requestNode.asSource() and
3433
connectionUse = connectionNode.asSource() and
3534
connectionNode =
3635
[
@@ -46,8 +45,7 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
4645
// Excon#request exists but Excon.request doesn't.
4746
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
4847
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
49-
]) and
50-
this = requestUse.asExpr().getExpr()
48+
])
5149
}
5250

5351
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
@@ -56,9 +54,9 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
5654
// For one-off requests, the URL is in the first argument of the request method call.
5755
// For connection re-use, the URL is split between the first argument of the `new` call
5856
// and the `path` keyword argument of the request method call.
59-
result = requestUse.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
57+
result = this.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
6058
or
61-
result = requestUse.getKeywordArgument("path")
59+
result = this.getKeywordArgument("path")
6260
or
6361
result = connectionUse.(DataFlow::CallNode).getArgument(0)
6462
}
@@ -77,7 +75,7 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
7775
// the request call.
7876
exists(DataFlow::CallNode disableCall |
7977
setsDefaultVerification(disableCall, false) and
80-
disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
78+
disableCall.asExpr().getASuccessor+() = this.asExpr() and
8179
disablingNode = disableCall and
8280
not exists(DataFlow::Node arg, int i |
8381
i > 0 and

ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ private import codeql.ruby.DataFlow
2222
* connection.get("/").body
2323
* ```
2424
*/
25-
class FaradayHttpRequest extends HTTP::Client::Request::Range {
25+
26+
class FaradayHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
2627
API::Node requestNode;
2728
API::Node connectionNode;
2829
DataFlow::Node connectionUse;
29-
DataFlow::CallNode requestUse;
30+
3031

3132
FaradayHttpRequest() {
3233
connectionNode =
@@ -38,15 +39,15 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
3839
] and
3940
requestNode =
4041
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
41-
requestUse = requestNode.asSource() and
42-
connectionUse = connectionNode.asSource() and
43-
this = requestUse.asExpr().getExpr()
42+
this = requestNode.asSource() and
43+
connectionUse = connectionNode.asSource()
44+
4445
}
4546

4647
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
4748

4849
override DataFlow::Node getAUrlPart() {
49-
result = requestUse.getArgument(0) or
50+
result = this.getArgument(0) or
5051
result = connectionUse.(DataFlow::CallNode).getArgument(0) or
5152
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
5253
}

ruby/ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ private import codeql.ruby.DataFlow
1414
* HTTPClient.get_content("http://example.com")
1515
* ```
1616
*/
17-
class HttpClientRequest extends HTTP::Client::Request::Range {
17+
class HttpClientRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
1818
API::Node requestNode;
1919
API::Node connectionNode;
20-
DataFlow::CallNode requestUse;
2120
string method;
2221

2322
HttpClientRequest() {
@@ -29,20 +28,19 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
2928
API::getTopLevelMember("HTTPClient").getInstance()
3029
] and
3130
requestNode = connectionNode.getReturn(method) and
32-
requestUse = requestNode.asSource() and
31+
this = requestNode.asSource() and
3332
method in [
3433
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
35-
] and
36-
this = requestUse.asExpr().getExpr()
34+
]
3735
}
3836

39-
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
37+
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
4038

4139
override DataFlow::Node getResponseBody() {
4240
// The `get_content` and `post_content` methods return the response body as
4341
// a string. The other methods return a `HTTPClient::Message` object which
4442
// has various methods that return the response body.
45-
method in ["get_content", "post_content"] and result = requestUse
43+
method in ["get_content", "post_content"] and result = this
4644
or
4745
not method in ["get_content", "put_content"] and
4846
result = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])

ruby/ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ private import codeql.ruby.DataFlow
2323
* MyClass.new("http://example.com")
2424
* ```
2525
*/
26-
class HttpartyRequest extends HTTP::Client::Request::Range {
26+
class HttpartyRequest extends HTTP::Client::Request::Range,DataFlow::CallNode {
2727
API::Node requestNode;
28-
DataFlow::CallNode requestUse;
28+
2929

3030
HttpartyRequest() {
31-
requestUse = requestNode.asSource() and
31+
this = requestNode.asSource() and
3232
requestNode =
3333
API::getTopLevelMember("HTTParty")
34-
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
35-
this = requestUse.asExpr().getExpr()
34+
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
35+
3636
}
3737

38-
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
38+
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
3939

4040
override DataFlow::Node getResponseBody() {
4141
// If HTTParty can recognise the response type, it will parse and return it
@@ -46,7 +46,7 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
4646
or
4747
// Otherwise, treat the response as the response body.
4848
not exists(requestNode.getAMethodCall("body")) and
49-
result = requestUse
49+
result = this
5050
}
5151

5252
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
@@ -55,7 +55,7 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
5555
// `{ verify_peer: false }`.
5656
exists(DataFlow::Node arg, int i |
5757
i > 0 and
58-
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i)
58+
arg.asExpr() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i)
5959
|
6060
// Either passed as an individual key:value argument, e.g.:
6161
// HTTParty.get(..., verify: false)

ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHttp.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ private import codeql.ruby.DataFlow
1818
* response = req.get("/")
1919
* ```
2020
*/
21-
class NetHttpRequest extends HTTP::Client::Request::Range {
21+
class NetHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
2222
private DataFlow::CallNode request;
2323
private DataFlow::Node responseBody;
2424
private API::Node requestNode;
2525

2626
NetHttpRequest() {
2727
exists(string method |
2828
request = requestNode.asSource() and
29-
this = request.asExpr().getExpr()
29+
this = request
3030
|
3131
// Net::HTTP.get(...)
3232
method = "get" and

ruby/ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,27 @@ private import codeql.ruby.frameworks.Core
1818
* URI.parse("http://example.com").open.read
1919
* ```
2020
*/
21-
class OpenUriRequest extends HTTP::Client::Request::Range {
21+
class OpenUriRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
2222
API::Node requestNode;
23-
DataFlow::CallNode requestUse;
2423

2524
OpenUriRequest() {
2625
requestNode =
2726
[
2827
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
2928
.getReturn("open"), API::getTopLevelMember("OpenURI").getReturn("open_uri")
3029
] and
31-
requestUse = requestNode.asSource() and
32-
this = requestUse.asExpr().getExpr()
30+
this = requestNode.asSource()
3331
}
3432

35-
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
33+
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
3634

3735
override DataFlow::Node getResponseBody() {
3836
result = requestNode.getAMethodCall(["read", "readlines"])
3937
}
4038

4139
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
4240
exists(DataFlow::Node arg |
43-
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getAnArgument() and
41+
arg.asExpr() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getAnArgument() and
4442
argumentDisablesValidation(arg, disablingNode)
4543
)
4644
}
@@ -56,26 +54,23 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
5654
* Kernel.open("http://example.com").read
5755
* ```
5856
*/
59-
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
60-
DataFlow::CallNode requestUse;
61-
57+
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
6258
OpenUriKernelOpenRequest() {
63-
requestUse instanceof KernelMethodCall and
64-
this.getMethodName() = "open" and
65-
this = requestUse.asExpr().getExpr()
59+
this instanceof KernelMethodCall and
60+
this.getMethodName() = "open"
6661
}
6762

68-
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
63+
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
6964

7065
override DataFlow::CallNode getResponseBody() {
7166
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
72-
requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
67+
this.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
7368
}
7469

7570
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
7671
exists(DataFlow::Node arg, int i |
7772
i > 0 and
78-
arg.asExpr() = requestUse.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i) and
73+
arg.asExpr() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i) and
7974
argumentDisablesValidation(arg, disablingNode)
8075
)
8176
}

ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ private import codeql.ruby.DataFlow
1616
* RestClient::Request.execute(url: "http://example.com").body
1717
* ```
1818
*/
19-
class RestClientHttpRequest extends HTTP::Client::Request::Range {
20-
DataFlow::CallNode requestUse;
19+
class RestClientHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
20+
2121
API::Node requestNode;
2222
API::Node connectionNode;
2323

2424
RestClientHttpRequest() {
25-
requestUse = requestNode.asSource() and
26-
this = requestUse.asExpr().getExpr() and
25+
this = requestNode.asSource() and
26+
2727
(
2828
connectionNode =
2929
[
@@ -39,9 +39,9 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
3939
}
4040

4141
override DataFlow::Node getAUrlPart() {
42-
result = requestUse.getKeywordArgument("url")
42+
result = this.getKeywordArgument("url")
4343
or
44-
result = requestUse.getArgument(0) and
44+
result = this.getArgument(0) and
4545
// this rules out the alternative above
4646
not result.asExpr().getExpr() instanceof Pair
4747
}

ruby/ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,26 @@ private import codeql.ruby.DataFlow
1414
* Typhoeus.get("http://example.com").body
1515
* ```
1616
*/
17-
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
18-
DataFlow::CallNode requestUse;
17+
class TyphoeusHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
18+
1919
API::Node requestNode;
2020

2121
TyphoeusHttpRequest() {
22-
requestUse = requestNode.asSource() and
22+
this = requestNode.asSource() and
2323
requestNode =
2424
API::getTopLevelMember("Typhoeus")
25-
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
26-
this = requestUse.asExpr().getExpr()
25+
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
26+
2727
}
2828

29-
override DataFlow::Node getAUrlPart() { result = requestUse.getArgument(0) }
29+
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
3030

3131
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
3232

3333
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
3434
// Check for `ssl_verifypeer: false` in the options hash.
3535
exists(DataFlow::Node arg, int i |
36-
i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
36+
i > 0 and arg.asExpr().getExpr() = this.asExpr().getExpr().(MethodCall).getArgument(i)
3737
|
3838
// Either passed as an individual key:value argument, e.g.:
3939
// Typhoeus.get(..., ssl_verifypeer: false)

0 commit comments

Comments
 (0)