Skip to content

Commit cdc44c3

Browse files
Model tornado websockets
1 parent 4d4a677 commit cdc44c3

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

python/ql/lib/semmle/python/frameworks/Tornado.qll

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ module Tornado {
135135
API::Node subclassRef() {
136136
result = web().getMember("RequestHandler").getASubclass*()
137137
or
138+
result = WebSocket::WebSocketHandler::subclassRef()
139+
or
138140
result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
139141
}
140142

@@ -428,6 +430,42 @@ module Tornado {
428430
}
429431
}
430432
}
433+
434+
// ---------------------------------------------------------------------------
435+
// tornado.websocket
436+
// ---------------------------------------------------------------------------
437+
/** Gets a reference to the `tornado.websocket` module. */
438+
API::Node websocket() { result = Tornado::tornado().getMember("websocket") }
439+
440+
module WebSocket {
441+
module WebSocketHandler {
442+
/** Gets a reference to the `tornado.websocket.WebSocketHandler` class or any subclass. */
443+
API::Node subclassRef() {
444+
result = websocket().getMember("WebSocketHandler").getASubclass*()
445+
or
446+
result =
447+
ModelOutput::getATypeNode("tornado.websocket.WebSocketHandler~Subclass").getASubclass*()
448+
}
449+
450+
class WebSocketHandlerClass extends Web::RequestHandler::RequestHandlerClass {
451+
WebSocketHandlerClass() { this.getParent() = subclassRef().asSource().asExpr() }
452+
453+
override Function getARequestHandler() {
454+
result = super.getARequestHandler()
455+
or
456+
result = this.getAMethod() and
457+
result.getName() = "open"
458+
}
459+
460+
/** Gets a function that could handle incoming websocket events, if any. */
461+
Function getAWebSocketEventHandler() {
462+
result = this.getAMethod() and
463+
result.getName() =
464+
["on_message", "on_close", "on_ping", "on_pong", "select_subprotocol", "check_origin"]
465+
}
466+
}
467+
}
468+
}
431469
}
432470

433471
// ---------------------------------------------------------------------------
@@ -542,6 +580,27 @@ module Tornado {
542580
override string getFramework() { result = "Tornado" }
543581
}
544582

583+
/** A request handler for WebSocket events */
584+
private class TornadoWebSocketEventHandler extends Http::Server::RequestHandler::Range {
585+
TornadoWebSocketEventHandler() {
586+
exists(TornadoModule::WebSocket::WebSocketHandler::WebSocketHandlerClass cls |
587+
cls.getAWebSocketEventHandler() = this
588+
)
589+
}
590+
591+
override Parameter getARoutedParameter() {
592+
// The `open` method is handled as a normal request handler in `TornadoRouteSetup` or `TornadoRequestHandlerWithoutKnownRoute`.
593+
// For other event handlers (such as `on_message`), all parameters should be remote flow sources, as they are not affected by routing.
594+
result in [
595+
this.getArg(_), this.getArgByName(_), this.getVararg().(Parameter),
596+
this.getKwarg().(Parameter)
597+
] and
598+
not result = this.getArg(0)
599+
}
600+
601+
override string getFramework() { result = "Tornado" }
602+
}
603+
545604
// ---------------------------------------------------------------------------
546605
// Response modeling
547606
// ---------------------------------------------------------------------------

python/ql/test/library-tests/frameworks/tornado/routing_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ class PossiblyNotRouted(tornado.web.RequestHandler):
5454
def get(self): # $ requestHandler
5555
self.write("NotRouted") # $ HttpResponse
5656

57+
class WebSocket(tornado.websocket.WebSocketHandler):
58+
def open(self, x): # $ requestHandler routedParameter=x
59+
self.write_message("WebSocket open {}".format(x))
60+
61+
def on_message(self, data): # $ requestHandler routedParameter=data
62+
self.write_message("WebSocket on_message {}".format(data))
63+
64+
def on_ping(self, data): # $ requestHandler routedParameter=data
65+
print("ping", data)
66+
67+
def on_pong(self, data): # $ requestHandler routedParameter=data
68+
print("pong", data)
69+
70+
def select_subprotocol(self, subs): # $ requestHandler routedParameter=subs
71+
print("select_subprotocol", subs)
72+
73+
def check_origin(self, origin): # $ requestHandler routedParameter=origin
74+
print("check_origin", origin)
75+
return True
76+
77+
5778

5879
def make_app():
5980
# see https://www.tornadoweb.org/en/stable/routing.html for even more examples
@@ -74,6 +95,7 @@ def make_app():
7495
(tornado.routing.HostMatches(r"(localhost|127\.0\.0\.1)"), [
7596
("/only-localhost", OnlyLocalhost) # $ routeSetup="/only-localhost"
7697
]),
98+
(r"/websocket/([0-9]+)", WebSocket), # $ routeSetup="/websocket/([0-9]+)"
7799

78100
],
79101
debug=True,

0 commit comments

Comments
 (0)