Skip to content

Commit c45d84f

Browse files
committed
JS: Update getRouteHandlerParameter and router tracking
1 parent 9cacfab commit c45d84f

File tree

2 files changed

+118
-55
lines changed

2 files changed

+118
-55
lines changed

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

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,99 @@
77
import javascript
88

99
module ConnectExpressShared {
10+
/**
11+
* String representing the signature of a route handler, that is,
12+
* the list of parameters taken by the route handler.
13+
*
14+
* Concretely this is a comma-separated list of parameter kinds, which can be either
15+
* `request`, `response`, `next`, `error`, or `parameter`, but this is considered an
16+
* implementation detail.
17+
*/
18+
private class RouteHandlerSignature extends string {
19+
RouteHandlerSignature() {
20+
this =
21+
["request,response", "request,response,next", "request,response,next,parameter",
22+
"error,request,response,next"]
23+
}
24+
25+
/** Gets the index of the parameter corresonding to the given `kind`, if any. */
26+
pragma[noinline]
27+
int getParameterIndex(string kind) { this.splitAt(",", result) = kind }
28+
29+
/** Gets the number of parameters taken by this signature. */
30+
pragma[noinline]
31+
int getArity() { result = count(getParameterIndex(_)) }
32+
33+
/** Holds if this signature takes a parameter of the given kind. */
34+
predicate has(string kind) { exists(getParameterIndex(kind)) }
35+
}
36+
37+
private module RouteHandlerSignature {
38+
/** Gets the signature corresonding to `(req, res, next, param) => {...}`. */
39+
RouteHandlerSignature requestResponseNextParameter() {
40+
result = "request,response,next,parameter"
41+
}
42+
43+
/** Gets the signature corresonding to `(err, req, res, next) => {...}`. */
44+
RouteHandlerSignature errorRequestResponseNext() { result = "error,request,response,next" }
45+
}
46+
47+
/**
48+
* Holds if `fun` appears to match the given signature based on parameter naming.
49+
*/
50+
private predicate matchesSignature(Function function, RouteHandlerSignature sig) {
51+
function.getNumParameter() = sig.getArity() and
52+
function.getParameter(sig.getParameterIndex("request")).getName() = ["req", "request"] and
53+
function.getParameter(sig.getParameterIndex("response")).getName() = ["res", "response"] and
54+
(
55+
sig.has("next")
56+
implies
57+
function.getParameter(sig.getParameterIndex("next")).getName() = "next"
58+
)
59+
}
60+
61+
/**
62+
* Gets the parameter corresonding to the given `kind`, where `routeHandler` is interpreted as a
63+
* route handler with the signature `sig`.
64+
*
65+
* This does not check if the function is actually a route handler or matches the signature in any way,
66+
* so the caller should restrict the function accordingly.
67+
*/
68+
pragma[inline]
69+
private Parameter getRouteHandlerParameter(
70+
Function routeHandler, RouteHandlerSignature sig, string kind
71+
) {
72+
result = routeHandler.getParameter(sig.getParameterIndex(kind))
73+
}
74+
75+
/**
76+
* Gets the parameter of kind `kind` of a Connect/Express route parameter handler function.
77+
*
78+
* `kind` is one of: "error", "request", "response", "next".
79+
*/
80+
Parameter getRouteParameterHandlerParameter(Function routeHandler, string kind) {
81+
result =
82+
getRouteHandlerParameter(routeHandler, RouteHandlerSignature::requestResponseNextParameter(),
83+
kind)
84+
}
85+
1086
/**
1187
* Gets the parameter of kind `kind` of a Connect/Express route handler function.
1288
*
1389
* `kind` is one of: "error", "request", "response", "next".
1490
*/
15-
SimpleParameter getRouteHandlerParameter(Function routeHandler, string kind) {
16-
exists(int index, int offset |
17-
result = routeHandler.getParameter(index + offset) and
18-
(if routeHandler.getNumParameter() = 4 then offset = 0 else offset = -1)
19-
|
20-
kind = "error" and index = 0
21-
or
22-
kind = "request" and index = 1
23-
or
24-
kind = "response" and index = 2
25-
or
26-
kind = "next" and index = 3
27-
)
91+
Parameter getRouteHandlerParameter(Function routeHandler, string kind) {
92+
if routeHandler.getNumParameter() = 4
93+
then
94+
// For arity 4 there is ambiguity between (err, req, res, next) and (req, res, next, param)
95+
// This predicate favors the 'err' signature whereas getRouteParameterHandlerParameter favors the other.
96+
result =
97+
getRouteHandlerParameter(routeHandler, RouteHandlerSignature::errorRequestResponseNext(),
98+
kind)
99+
else
100+
result =
101+
getRouteHandlerParameter(routeHandler,
102+
RouteHandlerSignature::requestResponseNextParameter(), kind)
28103
}
29104

30105
/**
@@ -34,39 +109,16 @@ module ConnectExpressShared {
34109
*/
35110
class RouteHandlerCandidate extends HTTP::RouteHandlerCandidate {
36111
RouteHandlerCandidate() {
37-
exists(string request, string response, string next, string error |
38-
(request = "request" or request = "req") and
39-
(response = "response" or response = "res") and
40-
next = "next" and
41-
(error = "error" or error = "err")
42-
|
43-
// heuristic: parameter names match the documentation
44-
astNode.getNumParameter() >= 2 and
45-
getRouteHandlerParameter(astNode, "request").getName() = request and
46-
getRouteHandlerParameter(astNode, "response").getName() = response and
47-
(
48-
astNode.getNumParameter() >= 3
49-
implies
50-
getRouteHandlerParameter(astNode, "next").getName() = next
51-
) and
52-
(
53-
astNode.getNumParameter() = 4
54-
implies
55-
getRouteHandlerParameter(astNode, "error").getName() = error
56-
) and
57-
not (
58-
// heuristic: max four parameters (the server will only supply four arguments)
59-
astNode.getNumParameter() > 4
60-
or
61-
// heuristic: not a class method (the server invokes this with a function call)
62-
astNode = any(MethodDefinition def).getBody()
63-
or
64-
// heuristic: does not return anything (the server will not use the return value)
65-
exists(astNode.getAReturnStmt().getExpr())
66-
or
67-
// heuristic: is not invoked (the server invokes this at a call site we cannot reason precisely about)
68-
exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
69-
)
112+
matchesSignature(astNode, _) and
113+
not (
114+
// heuristic: not a class method (the server invokes this with a function call)
115+
astNode = any(MethodDefinition def).getBody()
116+
or
117+
// heuristic: does not return anything (the server will not use the return value)
118+
exists(astNode.getAReturnStmt().getExpr())
119+
or
120+
// heuristic: is not invoked (the server invokes this at a call site we cannot reason precisely about)
121+
exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
70122
)
71123
}
72124
}

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,14 @@ module Express {
127127
/**
128128
* Gets the HTTP request type this is registered for, if any.
129129
*
130-
* Has no result for `use` and `all` calls.
130+
* Has no result for `use`, `all`, or `param` calls.
131131
*/
132132
HTTP::RequestMethodName getRequestMethod() { result.toLowerCase() = getMethodName() }
133133

134134
/**
135135
* Holds if this registers a route for all request methods.
136136
*/
137-
predicate handlesAllRequestMethods() { getMethodName() = "use" or getMethodName() = "all" }
137+
predicate handlesAllRequestMethods() { getMethodName() = ["use", "all", "param"] }
138138

139139
/**
140140
* Holds if this route setup sets up a route for the same
@@ -146,6 +146,11 @@ module Express {
146146
that.handlesAllRequestMethods() or
147147
this.getRequestMethod() = that.getRequestMethod()
148148
}
149+
150+
/**
151+
* Holds if this route setup is a parameter handler, such as `app.param("foo", ...)`.
152+
*/
153+
predicate isParameterHandler() { getMethodName() = "param" }
149154
}
150155

151156
/**
@@ -314,7 +319,7 @@ module Express {
314319
/**
315320
* Gets the parameter of kind `kind` of this route handler.
316321
*
317-
* `kind` is one of: "error", "request", "response", "next".
322+
* `kind` is one of: "error", "request", "response", "next", or "parameter".
318323
*/
319324
abstract SimpleParameter getRouteHandlerParameter(string kind);
320325

@@ -340,11 +345,14 @@ module Express {
340345
class StandardRouteHandler extends RouteHandler, HTTP::Servers::StandardRouteHandler,
341346
DataFlow::ValueNode {
342347
override Function astNode;
348+
RouteSetup routeSetup;
343349

344-
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
350+
StandardRouteHandler() { this = routeSetup.getARouteHandler() }
345351

346352
override SimpleParameter getRouteHandlerParameter(string kind) {
347-
result = getRouteHandlerParameter(astNode, kind)
353+
if routeSetup.isParameterHandler()
354+
then result = getRouteParameterHandlerParameter(astNode, kind)
355+
else result = getRouteHandlerParameter(astNode, kind)
348356
}
349357
}
350358

@@ -483,8 +491,7 @@ module Express {
483491
// `value` in `router.param('foo', (req, res, next, value) => { ... })`
484492
kind = "parameter" and
485493
exists(RouteSetup setup | rh = setup.getARouteHandler() |
486-
setup.getMethodName() = "param" and
487-
this = rh.(DataFlow::FunctionNode).getParameter(3)
494+
this = DataFlow::parameterNode(rh.getRouteHandlerParameter("parameter"))
488495
)
489496
}
490497

@@ -855,10 +862,14 @@ module Express {
855862
*/
856863
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
857864
HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
858-
TrackedRouteHandlerCandidateWithSetup() { this = any(RouteSetup s).getARouteHandler() }
865+
RouteSetup routeSetup;
866+
867+
TrackedRouteHandlerCandidateWithSetup() { this = routeSetup.getARouteHandler() }
859868

860869
override SimpleParameter getRouteHandlerParameter(string kind) {
861-
result = getRouteHandlerParameter(astNode, kind)
870+
if routeSetup.isParameterHandler()
871+
then result = getRouteParameterHandlerParameter(astNode, kind)
872+
else result = getRouteHandlerParameter(astNode, kind)
862873
}
863874
}
864875

0 commit comments

Comments
 (0)