Skip to content

Commit c3ab867

Browse files
committed
ruby: start restructuring rack
1 parent f8d2cbb commit c3ab867

File tree

5 files changed

+1416
-76
lines changed

5 files changed

+1416
-76
lines changed
Lines changed: 1 addition & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,6 @@
1-
/**
2-
* Provides modeling for the Rack library.
3-
*/
4-
5-
private import codeql.ruby.Concepts
6-
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
7-
private import codeql.ruby.DataFlow
8-
private import codeql.ruby.typetracking.TypeTracker
9-
101
/**
112
* Provides modeling for the Rack library.
123
*/
134
module Rack {
14-
/**
15-
* A class that may be a rack application.
16-
* This is a class that has a `call` method that takes a single argument
17-
* (traditionally called `env`) and returns a rack-compatible response.
18-
*/
19-
class AppCandidate extends DataFlow::ClassNode {
20-
private DataFlow::MethodNode call;
21-
private PotentialResponseNode resp;
22-
23-
AppCandidate() {
24-
call = this.getInstanceMethod("call") and
25-
call.getNumberOfParameters() = 1 and
26-
call.getReturn() = trackRackResponse(resp)
27-
}
28-
29-
/**
30-
* Gets the environment of the request, which is the lone parameter to the `call` method.
31-
*/
32-
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
33-
34-
/** Gets the response returned from the request. */
35-
PotentialResponseNode getResponse() { result = resp }
36-
}
37-
38-
private DataFlow::LocalSourceNode trackInt(TypeTracker t, int i) {
39-
t.start() and
40-
result.getConstantValue().isInt(i)
41-
or
42-
exists(TypeTracker t2 | result = trackInt(t2, i).track(t2, t))
43-
}
44-
45-
private DataFlow::Node trackInt(int i) { trackInt(TypeTracker::end(), i).flowsTo(result) }
46-
47-
private class PotentialResponseNode extends DataFlow::ArrayLiteralNode {
48-
// [status, headers, body]
49-
PotentialResponseNode() { this.getNumberOfArguments() = 3 }
50-
51-
/**
52-
* Gets an HTTP status code that may be returned in this response.
53-
*/
54-
int getAStatusCode() { this.getElement(0) = trackInt(result) }
55-
}
56-
57-
private DataFlow::LocalSourceNode trackRackResponse(TypeTracker t, PotentialResponseNode n) {
58-
t.start() and
59-
result = n
60-
or
61-
exists(TypeTracker t2 | result = trackRackResponse(t2, n).track(t2, t))
62-
}
63-
64-
private DataFlow::Node trackRackResponse(PotentialResponseNode n) {
65-
trackRackResponse(TypeTracker::end(), n).flowsTo(result)
66-
}
67-
68-
/** A `DataFlow::Node` returned from a rack request. */
69-
class ResponseNode extends PotentialResponseNode, Http::Server::HttpResponse::Range {
70-
ResponseNode() { this = any(AppCandidate app).getResponse() }
71-
72-
override DataFlow::Node getBody() { result = this.getElement(2) }
73-
74-
// TODO
75-
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
76-
77-
// TODO
78-
override string getMimetypeDefault() { none() }
79-
}
5+
import rack.Rack
806
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Provides modeling for the Rack library.
3+
*/
4+
5+
private import codeql.ruby.AST
6+
private import codeql.ruby.ApiGraphs
7+
private import codeql.ruby.Concepts
8+
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
9+
private import codeql.ruby.DataFlow
10+
private import codeql.ruby.typetracking.TypeTracker
11+
private import internal.MimeTypes
12+
13+
/**
14+
* A class that may be a rack application.
15+
* This is a class that has a `call` method that takes a single argument
16+
* (traditionally called `env`) and returns a rack-compatible response.
17+
*/
18+
class AppCandidate extends DataFlow::ClassNode {
19+
private DataFlow::MethodNode call;
20+
private PotentialResponseNode resp;
21+
22+
AppCandidate() {
23+
call = this.getInstanceMethod("call") and
24+
call.getNumberOfParameters() = 1 and
25+
call.getReturn() = trackRackResponse(resp)
26+
}
27+
28+
/**
29+
* Gets the environment of the request, which is the lone parameter to the `call` method.
30+
*/
31+
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
32+
33+
/** Gets the response returned from the request. */
34+
PotentialResponseNode getResponse() { result = resp }
35+
}
36+
37+
private DataFlow::LocalSourceNode trackInt(TypeTracker t, int i) {
38+
t.start() and
39+
result.getConstantValue().isInt(i)
40+
or
41+
exists(TypeTracker t2 | result = trackInt(t2, i).track(t2, t))
42+
}
43+
44+
private DataFlow::Node trackInt(int i) { trackInt(TypeTracker::end(), i).flowsTo(result) }
45+
46+
private class PotentialResponseNode extends DataFlow::ArrayLiteralNode {
47+
// [status, headers, body]
48+
PotentialResponseNode() { this.getNumberOfArguments() = 3 }
49+
50+
/**
51+
* Gets an HTTP status code that may be returned in this response.
52+
*/
53+
int getAStatusCode() { this.getElement(0) = trackInt(result) }
54+
55+
/** Gets the headers returned with this response. */
56+
DataFlow::Node getHeaders() { result = this.getElement(1) }
57+
58+
/** Gets the body of this response. */
59+
DataFlow::Node getBody() { result = this.getElement(2) }
60+
}
61+
62+
private DataFlow::LocalSourceNode trackRackResponse(TypeTracker t, PotentialResponseNode n) {
63+
t.start() and
64+
result = n
65+
or
66+
exists(TypeTracker t2 | result = trackRackResponse(t2, n).track(t2, t))
67+
}
68+
69+
private DataFlow::Node trackRackResponse(PotentialResponseNode n) {
70+
trackRackResponse(TypeTracker::end(), n).flowsTo(result)
71+
}
72+
73+
class MimetypeCall extends DataFlow::CallNode {
74+
MimetypeCall() {
75+
this = API::getTopLevelMember("Rack").getMember("Mime").getAMethodCall("mime_type")
76+
}
77+
78+
private string getExtension() {
79+
result = this.getArgument(0).getConstantValue().getStringlikeValue()
80+
}
81+
82+
string getMimeType() { mimeTypeMatches(this.getExtension(), result) }
83+
}
84+
85+
/** A `DataFlow::Node` returned from a rack request. */
86+
class ResponseNode extends PotentialResponseNode, Http::Server::HttpResponse::Range {
87+
ResponseNode() { this = any(AppCandidate app).getResponse() }
88+
89+
override DataFlow::Node getBody() { result = this.getElement(2) }
90+
91+
override DataFlow::Node getMimetypeOrContentTypeArg() {
92+
exists(DataFlow::Node headers | headers = this.getHeaders() |
93+
// set via `headers.content_type=`
94+
exists(
95+
DataFlow::CallNode contentTypeAssignment, Assignment assignment,
96+
DataFlow::PostUpdateNode postUpdateHeaders
97+
|
98+
contentTypeAssignment.getMethodName() = "content_type=" and
99+
assignment =
100+
contentTypeAssignment.getArgument(0).(DataFlow::OperationNode).asOperationAstNode() and
101+
postUpdateHeaders.(DataFlow::LocalSourceNode).flowsTo(headers) and
102+
postUpdateHeaders.getPreUpdateNode() = contentTypeAssignment.getReceiver()
103+
|
104+
result.asExpr().getExpr() = assignment.getRightOperand()
105+
)
106+
or
107+
// set within a hash
108+
exists(DataFlow::HashLiteralNode headersHash | headersHash.flowsTo(headers) |
109+
result =
110+
headersHash
111+
.getElementFromKey(any(ConstantValue v |
112+
v.getStringlikeValue().toLowerCase() = "content-type"
113+
))
114+
)
115+
)
116+
}
117+
118+
// TODO
119+
override string getMimetypeDefault() { none() }
120+
}

0 commit comments

Comments
 (0)