Model the Excon HTTP client

This commit is contained in:
Harry Maclean
2021-09-22 08:57:11 +01:00
parent 83705c5787
commit ee51298633
5 changed files with 93 additions and 0 deletions

View File

@@ -3,3 +3,4 @@
*/
private import codeql.ruby.frameworks.http_clients.NetHTTP
private import codeql.ruby.frameworks.http_clients.Excon

View File

@@ -0,0 +1,46 @@
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
/**
* A call that makes an HTTP request using `Excon`.
* ```ruby
* # one-off request
* Excon.get("http://example.com").body
*
* # connection re-use
* connection = Excon.new("http://example.com")
* connection.get(path: "/").body
* connection.request(method: :get, path: "/")
* ```
*
* TODO: pipelining, streaming responses
* https://github.com/excon/excon/blob/master/README.md
*/
class ExconHTTPRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
ExconHTTPRequest() {
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
requestNode =
[
// one-off requests
API::getTopLevelMember("Excon"),
// connection re-use
API::getTopLevelMember("Excon").getInstance()
]
.getReturn([
// Excon#request exists but Excon.request doesn't.
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
]) and
responseBody = requestNode.getAMethodCall("body") and
this = request.asExpr().getExpr()
)
}
override DataFlow::Node getResponseBody() { result = responseBody }
override string getFramework() { result = "Excon" }
}

View File

@@ -0,0 +1,10 @@
| Excon.rb:3:9:3:40 | call to get | Excon.rb:4:1:4:10 | call to body |
| Excon.rb:6:9:6:60 | call to post | Excon.rb:7:1:7:10 | call to body |
| Excon.rb:9:9:9:59 | call to put | Excon.rb:10:1:10:10 | call to body |
| Excon.rb:12:9:12:61 | call to patch | Excon.rb:13:1:13:10 | call to body |
| Excon.rb:15:9:15:43 | call to delete | Excon.rb:16:1:16:10 | call to body |
| Excon.rb:18:9:18:41 | call to head | Excon.rb:19:1:19:10 | call to body |
| Excon.rb:21:9:21:44 | call to options | Excon.rb:22:1:22:10 | call to body |
| Excon.rb:24:9:24:42 | call to trace | Excon.rb:25:1:25:10 | call to body |
| Excon.rb:28:9:28:33 | call to get | Excon.rb:29:1:29:10 | call to body |
| Excon.rb:31:10:31:38 | call to post | Excon.rb:32:1:32:11 | call to body |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.Excon
import codeql.ruby.DataFlow
query DataFlow::Node exconHTTPRequests(ExconHTTPRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,32 @@
require "excon"
resp1 = Excon.get("http://example.com/")
resp1.body
resp2 = Excon.post("http://example.com/", body: "some_data")
resp2.body
resp3 = Excon.put("http://example.com/", body: "some_data")
resp3.body
resp4 = Excon.patch("http://example.com/", body: "some_data")
resp4.body
resp5 = Excon.delete("http://example.com/")
resp5.body
resp6 = Excon.head("http://example.com/")
resp6.body
resp7 = Excon.options("http://example.com/")
resp7.body
resp8 = Excon.trace("http://example.com/")
resp8.body
connection = Excon.new("http://example.com")
resp9 = connection.get(path: "/")
resp9.body
resp10 = connection.post(path: "/foo")
resp10.body