Skip to content

Commit a76c70d

Browse files
committed
JS: model fastify
1 parent 8df7b7c commit a76c70d

20 files changed

+445
-0
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Support for the following frameworks and libraries has been improved:
66
- [bluebird](http://bluebirdjs.com/)
77
- [express](https://www.npmjs.com/package/express)
8+
- [fastify](https://www.npmjs.com/package/fastify)
89
- [fstream](https://www.npmjs.com/package/fstream)
910
- [jGrowl](https://github.com/stanlemon/jGrowl)
1011
- [jQuery](https://jquery.com/)
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/**
2+
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
3+
*/
4+
5+
import javascript
6+
import semmle.javascript.frameworks.HTTP
7+
8+
module Fastify {
9+
/**
10+
* An expression that creates a new Fastify server.
11+
*/
12+
abstract class ServerDefinition extends HTTP::Servers::StandardServerDefinition { }
13+
14+
/**
15+
* A standard way to create a Fastify server.
16+
*/
17+
class StandardServerDefinition extends ServerDefinition {
18+
StandardServerDefinition() {
19+
this = DataFlow::moduleImport("fastify").getAnInvocation().asExpr()
20+
}
21+
}
22+
23+
/**
24+
* A function used as a Fastify route handler.
25+
*
26+
* By default, only handlers installed by an Fastify route setup are recognized,
27+
* but support for other kinds of route handlers can be added by implementing
28+
* additional subclasses of this class.
29+
*/
30+
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
31+
/**
32+
* Gets the parameter of the route handler that contains the request object.
33+
*/
34+
abstract SimpleParameter getRequestParameter();
35+
36+
/**
37+
* Gets the parameter of the route handler that contains the reply object.
38+
*/
39+
abstract SimpleParameter getReplyParameter();
40+
}
41+
42+
/**
43+
* A Fastify route handler installed by a route setup.
44+
*/
45+
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
46+
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
47+
48+
override SimpleParameter getRequestParameter() { result = this.getParameter(0).getParameter() }
49+
50+
override SimpleParameter getReplyParameter() { result = this.getParameter(1).getParameter() }
51+
}
52+
53+
/**
54+
* A Fastify reply source, that is, the `reply` parameter of a
55+
* route handler.
56+
*/
57+
private class ReplySource extends HTTP::Servers::ResponseSource {
58+
RouteHandler rh;
59+
60+
ReplySource() { this = DataFlow::parameterNode(rh.getReplyParameter()) }
61+
62+
/**
63+
* Gets the route handler that provides this response.
64+
*/
65+
override RouteHandler getRouteHandler() { result = rh }
66+
}
67+
68+
/**
69+
* A Fastify request source, that is, the request parameter of a
70+
* route handler.
71+
*/
72+
private class RequestSource extends HTTP::Servers::RequestSource {
73+
RouteHandler rh;
74+
75+
RequestSource() { this = DataFlow::parameterNode(rh.getRequestParameter()) }
76+
77+
/**
78+
* Gets the route handler that handles this request.
79+
*/
80+
override RouteHandler getRouteHandler() { result = rh }
81+
}
82+
83+
/**
84+
* A call to a Fastify method that sets up a route.
85+
*/
86+
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
87+
ServerDefinition server;
88+
string methodName;
89+
90+
RouteSetup() {
91+
this.getMethodName() = methodName and
92+
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] and
93+
server.flowsTo(this.getReceiver())
94+
}
95+
96+
override DataFlow::SourceNode getARouteHandler() {
97+
result = getARouteHandler(DataFlow::TypeBackTracker::end())
98+
}
99+
100+
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
101+
t.start() and
102+
result = this.getARouteHandlerExpr().getALocalSource()
103+
or
104+
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
105+
}
106+
107+
override Expr getServer() { result = server }
108+
109+
/** Gets an argument that represents a route handler being registered. */
110+
private DataFlow::Node getARouteHandlerExpr() {
111+
if methodName = "route"
112+
then
113+
result =
114+
this
115+
.flow()
116+
.(DataFlow::MethodCallNode)
117+
.getOptionArgument(0,
118+
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
119+
"onSend", "onResponse", "handler"])
120+
else result = getLastArgument().flow()
121+
}
122+
}
123+
124+
/**
125+
* An access to a user-controlled Fastify request input.
126+
*/
127+
private class RequestInputAccess extends HTTP::RequestInputAccess {
128+
RouteHandler rh;
129+
string kind;
130+
131+
RequestInputAccess() {
132+
exists(string name | this.(DataFlow::PropRead).accesses(rh.getARequestExpr().flow(), name) |
133+
kind = "parameter" and
134+
name = ["params", "query"]
135+
or
136+
kind = "body" and
137+
name = "body"
138+
)
139+
}
140+
141+
override RouteHandler getRouteHandler() { result = rh }
142+
143+
override string getKind() { result = kind }
144+
}
145+
146+
/**
147+
* An access to a header on a Fastify request.
148+
*/
149+
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
150+
RouteHandler rh;
151+
152+
RequestHeaderAccess() {
153+
exists(DataFlow::PropRead headers |
154+
headers.accesses(rh.getARequestExpr().flow(), "headers") and
155+
this = headers.getAPropertyRead()
156+
)
157+
}
158+
159+
override string getAHeaderName() {
160+
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
161+
}
162+
163+
override RouteHandler getRouteHandler() { result = rh }
164+
165+
override string getKind() { result = "header" }
166+
}
167+
168+
/**
169+
* An argument passed to the `send` or `end` method of an HTTP response object.
170+
*/
171+
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
172+
RouteHandler rh;
173+
174+
ResponseSendArgument() {
175+
exists(MethodCallExpr mce |
176+
mce.calls(rh.getAResponseExpr(), "send") and
177+
this = mce.getArgument(0)
178+
)
179+
or
180+
exists(Function f |
181+
f = rh.(DataFlow::FunctionNode).getFunction() and
182+
f.isAsync() and
183+
f.getAReturnedExpr() = this
184+
)
185+
}
186+
187+
override RouteHandler getRouteHandler() { result = rh }
188+
}
189+
190+
/**
191+
* An invocation of the `redirect` method of an HTTP response object.
192+
*/
193+
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
194+
RouteHandler rh;
195+
196+
RedirectInvocation() { this.calls(rh.getAResponseExpr(), "redirect") }
197+
198+
override Expr getUrlArgument() { result = this.getLastArgument() }
199+
200+
override RouteHandler getRouteHandler() { result = rh }
201+
}
202+
203+
/**
204+
* An invocation that sets a single header of the HTTP response.
205+
*/
206+
private class SetOneHeader extends HTTP::Servers::StandardHeaderDefinition {
207+
RouteHandler rh;
208+
209+
SetOneHeader() {
210+
astNode.calls(rh.getAResponseExpr(), "header") and
211+
astNode.getNumArgument() = 2
212+
}
213+
214+
override RouteHandler getRouteHandler() { result = rh }
215+
}
216+
217+
/**
218+
* An invocation that sets any number of headers of the HTTP response.
219+
*/
220+
class SetMultipleHeaders extends HTTP::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
221+
RouteHandler rh;
222+
223+
SetMultipleHeaders() {
224+
this.calls(rh.getAResponseExpr().flow(), "headers") and
225+
this.getNumArgument() = 1
226+
}
227+
228+
/**
229+
* Gets a reference to the multiple headers object that is to be set.
230+
*/
231+
private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(this.getArgument(0)) }
232+
233+
override predicate definesExplicitly(string headerName, Expr headerValue) {
234+
exists(string header |
235+
getAHeaderSource().hasPropertyWrite(header, headerValue.flow()) and
236+
headerName = header.toLowerCase()
237+
)
238+
}
239+
240+
override RouteHandler getRouteHandler() { result = rh }
241+
242+
override Expr getNameExpr() {
243+
exists(DataFlow::PropWrite write |
244+
this.getAHeaderSource().flowsTo(write.getBase()) and
245+
result = write.getPropertyNameExpr()
246+
)
247+
}
248+
}
249+
}

javascript/ql/src/semmle/javascript/frameworks/HttpFrameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import semmle.javascript.frameworks.Koa
44
import semmle.javascript.frameworks.NodeJSLib
55
import semmle.javascript.frameworks.Restify
66
import semmle.javascript.frameworks.Connect
7+
import semmle.javascript.frameworks.Fastify
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderAccess(HTTP::RequestHeaderAccess access, string res) {
4+
res = access.getAHeaderName()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition(HTTP::HeaderDefinition hd, Fastify::RouteHandler rh) {
4+
rh = hd.getRouteHandler()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition_defines(HTTP::HeaderDefinition hd, string name, string value) {
4+
hd.defines(name, value) and hd.getRouteHandler() instanceof Fastify::RouteHandler
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition_getAHeaderName(HTTP::HeaderDefinition hd, string res) {
4+
hd.getRouteHandler() instanceof Fastify::RouteHandler and res = hd.getAHeaderName()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_RedirectInvocation(HTTP::RedirectInvocation invk, Fastify::RouteHandler rh) {
4+
invk.getRouteHandler() = rh
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import javascript
2+
3+
query predicate test_RequestInputAccess(
4+
HTTP::RequestInputAccess ria, string res, Fastify::RouteHandler rh
5+
) {
6+
ria.getRouteHandler() = rh and res = ria.getKind()
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_ResponseSendArgument(HTTP::ResponseSendArgument arg, Fastify::RouteHandler rh) {
4+
arg.getRouteHandler() = rh
5+
}

0 commit comments

Comments
 (0)