JS: model fastify

This commit is contained in:
Esben Sparre Andreasen
2020-05-19 22:49:41 +02:00
parent 8df7b7c42a
commit a76c70d2d7
20 changed files with 445 additions and 0 deletions

View File

@@ -5,6 +5,7 @@
* Support for the following frameworks and libraries has been improved:
- [bluebird](http://bluebirdjs.com/)
- [express](https://www.npmjs.com/package/express)
- [fastify](https://www.npmjs.com/package/fastify)
- [fstream](https://www.npmjs.com/package/fstream)
- [jGrowl](https://github.com/stanlemon/jGrowl)
- [jQuery](https://jquery.com/)

View File

@@ -0,0 +1,249 @@
/**
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
*/
import javascript
import semmle.javascript.frameworks.HTTP
module Fastify {
/**
* An expression that creates a new Fastify server.
*/
abstract class ServerDefinition extends HTTP::Servers::StandardServerDefinition { }
/**
* A standard way to create a Fastify server.
*/
class StandardServerDefinition extends ServerDefinition {
StandardServerDefinition() {
this = DataFlow::moduleImport("fastify").getAnInvocation().asExpr()
}
}
/**
* A function used as a Fastify route handler.
*
* By default, only handlers installed by an Fastify route setup are recognized,
* but support for other kinds of route handlers can be added by implementing
* additional subclasses of this class.
*/
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
/**
* Gets the parameter of the route handler that contains the request object.
*/
abstract SimpleParameter getRequestParameter();
/**
* Gets the parameter of the route handler that contains the reply object.
*/
abstract SimpleParameter getReplyParameter();
}
/**
* A Fastify route handler installed by a route setup.
*/
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
override SimpleParameter getRequestParameter() { result = this.getParameter(0).getParameter() }
override SimpleParameter getReplyParameter() { result = this.getParameter(1).getParameter() }
}
/**
* A Fastify reply source, that is, the `reply` parameter of a
* route handler.
*/
private class ReplySource extends HTTP::Servers::ResponseSource {
RouteHandler rh;
ReplySource() { this = DataFlow::parameterNode(rh.getReplyParameter()) }
/**
* Gets the route handler that provides this response.
*/
override RouteHandler getRouteHandler() { result = rh }
}
/**
* A Fastify request source, that is, the request parameter of a
* route handler.
*/
private class RequestSource extends HTTP::Servers::RequestSource {
RouteHandler rh;
RequestSource() { this = DataFlow::parameterNode(rh.getRequestParameter()) }
/**
* Gets the route handler that handles this request.
*/
override RouteHandler getRouteHandler() { result = rh }
}
/**
* A call to a Fastify method that sets up a route.
*/
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
ServerDefinition server;
string methodName;
RouteSetup() {
this.getMethodName() = methodName and
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] and
server.flowsTo(this.getReceiver())
}
override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = this.getARouteHandlerExpr().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
}
override Expr getServer() { result = server }
/** Gets an argument that represents a route handler being registered. */
private DataFlow::Node getARouteHandlerExpr() {
if methodName = "route"
then
result =
this
.flow()
.(DataFlow::MethodCallNode)
.getOptionArgument(0,
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
"onSend", "onResponse", "handler"])
else result = getLastArgument().flow()
}
}
/**
* An access to a user-controlled Fastify request input.
*/
private class RequestInputAccess extends HTTP::RequestInputAccess {
RouteHandler rh;
string kind;
RequestInputAccess() {
exists(string name | this.(DataFlow::PropRead).accesses(rh.getARequestExpr().flow(), name) |
kind = "parameter" and
name = ["params", "query"]
or
kind = "body" and
name = "body"
)
}
override RouteHandler getRouteHandler() { result = rh }
override string getKind() { result = kind }
}
/**
* An access to a header on a Fastify request.
*/
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
RouteHandler rh;
RequestHeaderAccess() {
exists(DataFlow::PropRead headers |
headers.accesses(rh.getARequestExpr().flow(), "headers") and
this = headers.getAPropertyRead()
)
}
override string getAHeaderName() {
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
}
override RouteHandler getRouteHandler() { result = rh }
override string getKind() { result = "header" }
}
/**
* An argument passed to the `send` or `end` method of an HTTP response object.
*/
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
RouteHandler rh;
ResponseSendArgument() {
exists(MethodCallExpr mce |
mce.calls(rh.getAResponseExpr(), "send") and
this = mce.getArgument(0)
)
or
exists(Function f |
f = rh.(DataFlow::FunctionNode).getFunction() and
f.isAsync() and
f.getAReturnedExpr() = this
)
}
override RouteHandler getRouteHandler() { result = rh }
}
/**
* An invocation of the `redirect` method of an HTTP response object.
*/
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
RouteHandler rh;
RedirectInvocation() { this.calls(rh.getAResponseExpr(), "redirect") }
override Expr getUrlArgument() { result = this.getLastArgument() }
override RouteHandler getRouteHandler() { result = rh }
}
/**
* An invocation that sets a single header of the HTTP response.
*/
private class SetOneHeader extends HTTP::Servers::StandardHeaderDefinition {
RouteHandler rh;
SetOneHeader() {
astNode.calls(rh.getAResponseExpr(), "header") and
astNode.getNumArgument() = 2
}
override RouteHandler getRouteHandler() { result = rh }
}
/**
* An invocation that sets any number of headers of the HTTP response.
*/
class SetMultipleHeaders extends HTTP::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
RouteHandler rh;
SetMultipleHeaders() {
this.calls(rh.getAResponseExpr().flow(), "headers") and
this.getNumArgument() = 1
}
/**
* Gets a reference to the multiple headers object that is to be set.
*/
private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(this.getArgument(0)) }
override predicate definesExplicitly(string headerName, Expr headerValue) {
exists(string header |
getAHeaderSource().hasPropertyWrite(header, headerValue.flow()) and
headerName = header.toLowerCase()
)
}
override RouteHandler getRouteHandler() { result = rh }
override Expr getNameExpr() {
exists(DataFlow::PropWrite write |
this.getAHeaderSource().flowsTo(write.getBase()) and
result = write.getPropertyNameExpr()
)
}
}
}

View File

@@ -4,3 +4,4 @@ import semmle.javascript.frameworks.Koa
import semmle.javascript.frameworks.NodeJSLib
import semmle.javascript.frameworks.Restify
import semmle.javascript.frameworks.Connect
import semmle.javascript.frameworks.Fastify

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_HeaderAccess(HTTP::RequestHeaderAccess access, string res) {
res = access.getAHeaderName()
}

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_HeaderDefinition(HTTP::HeaderDefinition hd, Fastify::RouteHandler rh) {
rh = hd.getRouteHandler()
}

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_HeaderDefinition_defines(HTTP::HeaderDefinition hd, string name, string value) {
hd.defines(name, value) and hd.getRouteHandler() instanceof Fastify::RouteHandler
}

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_HeaderDefinition_getAHeaderName(HTTP::HeaderDefinition hd, string res) {
hd.getRouteHandler() instanceof Fastify::RouteHandler and res = hd.getAHeaderName()
}

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_RedirectInvocation(HTTP::RedirectInvocation invk, Fastify::RouteHandler rh) {
invk.getRouteHandler() = rh
}

View File

@@ -0,0 +1,7 @@
import javascript
query predicate test_RequestInputAccess(
HTTP::RequestInputAccess ria, string res, Fastify::RouteHandler rh
) {
ria.getRouteHandler() = rh and res = ria.getKind()
}

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_ResponseSendArgument(HTTP::ResponseSendArgument arg, Fastify::RouteHandler rh) {
arg.getRouteHandler() = rh
}

View File

@@ -0,0 +1,3 @@
import javascript
query predicate test_RouteHandler(Fastify::RouteHandler rh, Expr res) { res = rh.getServer() }

View File

@@ -0,0 +1,5 @@
import semmle.javascript.frameworks.Express
query predicate test_RouteHandler_getARequestExpr(Fastify::RouteHandler rh, HTTP::RequestExpr res) {
res = rh.getARequestExpr()
}

View File

@@ -0,0 +1,7 @@
import semmle.javascript.frameworks.Express
query predicate test_RouteHandler_getAResponseHeader(
Fastify::RouteHandler rh, string name, HTTP::HeaderDefinition res
) {
res = rh.getAResponseHeader(name)
}

View File

@@ -0,0 +1,3 @@
import javascript
query predicate test_RouteSetup(Fastify::RouteSetup rs) { any() }

View File

@@ -0,0 +1,5 @@
import javascript
query predicate test_RouteSetup_getARouteHandler(Fastify::RouteSetup r, DataFlow::SourceNode res) {
res = r.getARouteHandler()
}

View File

@@ -0,0 +1,3 @@
import javascript
query predicate test_RouteSetup_getServer(Fastify::RouteSetup rs, Expr res) { res = rs.getServer() }

View File

@@ -0,0 +1,3 @@
import javascript
query predicate test_ServerDefinition(Fastify::ServerDefinition s) { any() }

View File

@@ -0,0 +1,48 @@
var fastify = require("fastify")();
fastify.get(
"/",
/* handler */ async (request, reply) => {
return { hello: "world" }; // response
}
);
fastify.route({
method: "GET",
url: "/",
onRequest: /* handler */ (request, reply, done) => {},
preParsing: /* handler */ (request, reply, done) => {},
preValidation: /* handler */ (request, reply, done) => {},
preHandler: /* handler */ (request, reply, done) => {},
preSerialization: /* handler */ (request, reply, payload, done) => {},
onSend: /* handler */ (request, reply, payload, done) => {},
onResponse: /* handler */ (request, reply, done) => {},
handler: /* handler */ (request, reply) => {}
});
fastify.get(
"/",
opts,
/* handler */ (request, reply) => {
reply.send({ hello: "world" }); // response
}
);
fastify.post(
"/:params",
options,
/* handler */ function(request, reply) {
// request properties
request.query.name; // the parsed querystring
request.body; // the body
request.params.name; // the params matching the URL
request.headers.name; // the headers
// reply properties
reply.header("name", "value"); // Sets a response header.
reply.headers({ name: "value" }); // Sets all the keys of the object as a response headers.
reply.redirect(code, url); // Redirect to the specified url, the status code is optional (default to 302).
reply.send(payload); // Sends the payload to the user, could be a plain text, a buffer, JSON, stream
}
);
fastify.listen(3000);

View File

@@ -0,0 +1,66 @@
test_RouteSetup
| src/fastify.js:3:1:8:1 | fastify ... e\\n }\\n) |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) |
| src/fastify.js:23:1:29:1 | fastify ... e\\n }\\n) |
| src/fastify.js:31:1:47:1 | fastify ... m\\n }\\n) |
test_RequestInputAccess
| src/fastify.js:36:5:36:17 | request.query | parameter | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
| src/fastify.js:37:5:37:16 | request.body | body | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
| src/fastify.js:38:5:38:18 | request.params | parameter | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
| src/fastify.js:39:5:39:24 | request.headers.name | header | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
test_RouteHandler_getAResponseHeader
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | name | src/fastify.js:42:5:42:33 | reply.h ... value") |
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | name | src/fastify.js:43:5:43:36 | reply.h ... lue" }) |
test_HeaderDefinition_defines
| src/fastify.js:42:5:42:33 | reply.h ... value") | name | value |
| src/fastify.js:43:5:43:36 | reply.h ... lue" }) | name | value |
test_HeaderDefinition
| src/fastify.js:42:5:42:33 | reply.h ... value") | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
| src/fastify.js:43:5:43:36 | reply.h ... lue" }) | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
test_RouteSetup_getServer
| src/fastify.js:3:1:8:1 | fastify ... e\\n }\\n) | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:23:1:29:1 | fastify ... e\\n }\\n) | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:31:1:47:1 | fastify ... m\\n }\\n) | src/fastify.js:1:15:1:34 | require("fastify")() |
test_HeaderDefinition_getAHeaderName
| src/fastify.js:42:5:42:33 | reply.h ... value") | name |
| src/fastify.js:43:5:43:36 | reply.h ... lue" }) | name |
test_ServerDefinition
| src/fastify.js:1:15:1:34 | require("fastify")() |
test_HeaderAccess
| src/fastify.js:39:5:39:24 | request.headers.name | name |
test_RouteSetup_getARouteHandler
| src/fastify.js:3:1:8:1 | fastify ... e\\n }\\n) | src/fastify.js:5:17:7:3 | async ( ... nse\\n } |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:13:28:13:55 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:14:29:14:56 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:15:32:15:59 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:16:29:16:56 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:17:35:17:71 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:18:25:18:61 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:19:29:19:56 | (reques ... ) => {} |
| src/fastify.js:10:1:21:2 | fastify ... > {}\\n}) | src/fastify.js:20:26:20:47 | (reques ... ) => {} |
| src/fastify.js:23:1:29:1 | fastify ... e\\n }\\n) | src/fastify.js:26:17:28:3 | (reques ... nse\\n } |
| src/fastify.js:31:1:47:1 | fastify ... m\\n }\\n) | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
test_RouteHandler
| src/fastify.js:5:17:7:3 | async ( ... nse\\n } | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:13:28:13:55 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:14:29:14:56 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:15:32:15:59 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:16:29:16:56 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:17:35:17:71 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:18:25:18:61 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:19:29:19:56 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:20:26:20:47 | (reques ... ) => {} | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:26:17:28:3 | (reques ... nse\\n } | src/fastify.js:1:15:1:34 | require("fastify")() |
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | src/fastify.js:1:15:1:34 | require("fastify")() |
test_RouteHandler_getARequestExpr
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | src/fastify.js:36:5:36:11 | request |
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | src/fastify.js:37:5:37:11 | request |
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | src/fastify.js:38:5:38:11 | request |
| src/fastify.js:34:17:46:3 | functio ... eam\\n } | src/fastify.js:39:5:39:11 | request |
test_ResponseSendArgument
| src/fastify.js:6:12:6:29 | { hello: "world" } | src/fastify.js:5:17:7:3 | async ( ... nse\\n } |
| src/fastify.js:27:16:27:33 | { hello: "world" } | src/fastify.js:26:17:28:3 | (reques ... nse\\n } |
| src/fastify.js:45:16:45:22 | payload | src/fastify.js:34:17:46:3 | functio ... eam\\n } |
test_RedirectInvocation
| src/fastify.js:44:5:44:29 | reply.r ... e, url) | src/fastify.js:34:17:46:3 | functio ... eam\\n } |

View File

@@ -0,0 +1,14 @@
import RouteSetup
import RequestInputAccess
import RouteHandler_getAResponseHeader
import HeaderDefinition_defines
import HeaderDefinition
import RouteSetup_getServer
import HeaderDefinition_getAHeaderName
import ServerDefinition
import HeaderAccess
import RouteSetup_getARouteHandler
import RouteHandler
import RouteHandler_getARequestExpr
import ResponseSendArgument
import RedirectInvocation