JavaScript: Introduce representation of socket.io servers and namespaces.

This commit is contained in:
Max Schaefer
2019-02-26 13:13:22 +00:00
parent 94e4bd8b01
commit eb07754eee
15 changed files with 218 additions and 95 deletions

View File

@@ -27,17 +27,24 @@ module SocketIO {
result = "setMaxListeners"
}
/** Gets a data flow node that creates a new socket.io server. */
private DataFlow::SourceNode newServer() {
result = DataFlow::moduleImport("socket.io").getAnInvocation()
or
// alias for `Server`
result = DataFlow::moduleImport("socket.io").getAMemberCall("listen")
}
/** A data flow node that may produce (that is, create or return) a socket.io server. */
class ServerNode extends DataFlow::SourceNode {
ServerObject srv;
ServerNode() {
// server creation
this = DataFlow::moduleImport("socket.io").getAnInvocation()
or
// alias for `Server`
this = DataFlow::moduleImport("socket.io").getAMemberCall("listen")
this = newServer() and
srv = MkServer(this)
or
// invocation of a chainable method
exists(DataFlow::MethodCallNode mcn, string m |
exists(ServerNode base, DataFlow::MethodCallNode mcn, string m |
m = "adapter" or
m = "attach" or
m = "bind" or
@@ -48,22 +55,34 @@ module SocketIO {
m = "serveClient" or
m = "set"
|
mcn = any(ServerNode srv).getAMethodCall(m) and
mcn = base.getAMethodCall(m) and
// exclude getter versions
not mcn.getNumArgument() = 0 and
this = mcn
this = mcn and
srv = base.getServer()
)
}
/** Gets the server to which this node refers. */
ServerObject getServer() { result = srv }
}
/** A data flow node that may produce a namespace object. */
class NamespaceNode extends DataFlow::SourceNode {
NamespaceObject ns;
NamespaceNode() {
// namespace lookup
exists(ServerNode srv |
this = srv.getAPropertyRead("sockets")
this = srv.getAPropertyRead("sockets") and
ns = srv.getServer().getDefaultNamespace()
or
this = srv.getAMethodCall("of")
exists(DataFlow::MethodCallNode mcn, string path |
mcn = srv.getAMethodCall("of") and
mcn.getArgument(0).mayHaveStringValue(path) and
this = mcn and
ns = MkNamespace(srv.getServer(), path)
)
)
or
// invocation of a chainable method
@@ -79,29 +98,44 @@ module SocketIO {
m = "write" or
m = chainableEventEmitterMethod()
|
this = any(NamespaceNode ns).getAMethodCall(m)
exists(NamespaceNode base |
this = base.getAMethodCall(m) and
ns = base.getNamespace()
)
or
// server objects forward these methods to their default namespace
this = any(ServerNode srv).getAMethodCall(m)
exists(ServerNode srv |
this = srv.getAMethodCall(m) and
ns = srv.getServer().getDefaultNamespace()
)
)
or
// invocation of chainable getter method
exists(string m |
exists(NamespaceNode base, string m |
m = "json" or
m = "local" or
m = "volatile"
|
this = any(NamespaceNode base).getAPropertyRead(m)
this = base.getAPropertyRead(m) and
ns = base.getNamespace()
)
}
/** Gets the namespace to which this node refers. */
NamespaceObject getNamespace() { result = ns }
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
NamespaceObject ns;
SocketNode() {
// callback accepting a socket
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
(base instanceof ServerNode or base instanceof NamespaceNode) and
(
ns = base.(ServerNode).getServer().getDefaultNamespace() or
ns = base.(NamespaceNode).getNamespace()
) and
(connect = "connect" or connect = "connection")
|
on = base.getAMethodCall("on") and
@@ -110,7 +144,7 @@ module SocketIO {
)
or
// invocation of a chainable method
exists(string m |
exists(SocketNode base, string m |
m = "binary" or
m = "compress" or
m = "disconnect" or
@@ -124,19 +158,24 @@ module SocketIO {
m = "write" or
m = chainableEventEmitterMethod()
|
this = any(SocketNode base).getAMethodCall(m)
this = base.getAMethodCall(m) and
ns = base.getNamespace()
)
or
// invocation of a chainable getter method
exists(string m |
exists(SocketNode base, string m |
m = "broadcast" or
m = "json" or
m = "local" or
m = "volatile"
|
this = any(SocketNode base).getAPropertyRead(m)
this = base.getAPropertyRead(m) and
ns = base.getNamespace()
)
}
/** Gets the namespace to which this socket belongs. */
NamespaceObject getNamespace() { result = ns }
}
/**
@@ -211,6 +250,15 @@ module SocketIO {
*/
SocketNode getSocket() { result = base }
/**
* Gets the namespace to which data is sent.
*/
NamespaceObject getNamespace() {
result = base.(ServerNode).getServer().getDefaultNamespace() or
result = base.(NamespaceNode).getNamespace() or
result = base.(SocketNode).getNamespace()
}
/** Gets the event name associated with the data, if it can be determined. */
string getEventName() {
if firstDataIndex = 1 then getArgument(0).mayHaveStringValue(result) else result = "message"
@@ -236,4 +284,64 @@ module SocketIO {
result = getLastArgument().getALocalSource()
}
}
/** A socket.io server, identified by its creation site. */
private newtype TServer = MkServer(DataFlow::SourceNode nd) { nd = newServer() }
/** A socket.io namespace, identified by its server and its path. */
private newtype TNamespace =
MkNamespace(ServerObject srv, string path) {
path = "/"
or
exists(ServerNode nd | nd.getServer() = srv |
nd.getAMethodCall("of").getArgument(0).mayHaveStringValue(path)
)
}
/** A socket.io server. */
class ServerObject extends TServer {
DataFlow::SourceNode origin;
ServerObject() { this = MkServer(origin) }
/** Gets the data flow node where this server is created. */
DataFlow::SourceNode getOrigin() { result = origin }
/** Gets the default namespace of this server. */
NamespaceObject getDefaultNamespace() { result = MkNamespace(this, "/") }
/**
* Holds if this server is created at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
origin.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this server. */
string toString() { result = "socket.io server" }
}
/** A socket.io namespace. */
class NamespaceObject extends TNamespace {
ServerObject srv;
string path;
NamespaceObject() { this = MkNamespace(srv, path) }
/** Gets the server to which this namespace belongs. */
ServerObject getServer() { result = srv }
/** Gets the path of this namespace. */
string getPath() { result = path }
/** Gets a textual representation of this namespace. */
string toString() { result = "socket.io namespace with path '" + path + "'" }
}
}

View File

@@ -1,27 +1,27 @@
| tst.js:25:10:25:19 | io.sockets |
| tst.js:26:1:26:10 | io.of("/") |
| tst.js:27:1:27:12 | ns.use(auth) |
| tst.js:28:1:28:11 | ns.to(room) |
| tst.js:29:1:29:11 | ns.in(room) |
| tst.js:30:1:30:28 | ns.emit ... event') |
| tst.js:31:1:31:20 | ns.send('a message') |
| tst.js:32:1:32:21 | ns.writ ... ssage') |
| tst.js:33:1:33:14 | ns.clients(cb) |
| tst.js:34:1:34:17 | ns.compress(true) |
| tst.js:35:1:35:16 | ns.binary(false) |
| tst.js:36:1:36:12 | io.use(auth) |
| tst.js:37:1:37:11 | io.to(room) |
| tst.js:38:1:38:11 | io.in(room) |
| tst.js:39:1:39:31 | io.emit ... ssage') |
| tst.js:40:1:40:20 | io.send('a message') |
| tst.js:41:1:41:21 | io.writ ... ssage') |
| tst.js:42:1:42:14 | io.clients(cb) |
| tst.js:43:1:43:17 | io.compress(true) |
| tst.js:44:1:44:16 | io.binary(false) |
| tst.js:45:1:45:7 | ns.json |
| tst.js:46:1:46:11 | ns.volatile |
| tst.js:47:1:47:8 | ns.local |
| tst.js:50:1:66:2 | io.on(' ... cal;\\n}) |
| tst.js:67:1:67:35 | io.on(' ... => {}) |
| tst.js:68:1:68:32 | ns.on(' ... => {}) |
| tst.js:69:1:73:2 | ns.on(' ... {});\\n}) |
| tst.js:25:10:25:19 | io.sockets | socket.io namespace with path '/' |
| tst.js:26:11:26:23 | io.of("/foo") | socket.io namespace with path '/foo' |
| tst.js:27:1:27:12 | ns.use(auth) | socket.io namespace with path '/' |
| tst.js:28:1:28:11 | ns.to(room) | socket.io namespace with path '/' |
| tst.js:29:1:29:11 | ns.in(room) | socket.io namespace with path '/' |
| tst.js:30:1:30:28 | ns.emit ... event') | socket.io namespace with path '/' |
| tst.js:31:1:31:20 | ns.send('a message') | socket.io namespace with path '/' |
| tst.js:32:1:32:22 | ns2.wri ... ssage') | socket.io namespace with path '/foo' |
| tst.js:33:1:33:14 | ns.clients(cb) | socket.io namespace with path '/' |
| tst.js:34:1:34:17 | ns.compress(true) | socket.io namespace with path '/' |
| tst.js:35:1:35:16 | ns.binary(false) | socket.io namespace with path '/' |
| tst.js:36:1:36:12 | io.use(auth) | socket.io namespace with path '/' |
| tst.js:37:1:37:11 | io.to(room) | socket.io namespace with path '/' |
| tst.js:38:1:38:11 | io.in(room) | socket.io namespace with path '/' |
| tst.js:39:1:39:31 | io.emit ... ssage') | socket.io namespace with path '/' |
| tst.js:40:1:40:20 | io.send('a message') | socket.io namespace with path '/' |
| tst.js:41:1:41:21 | io.writ ... ssage') | socket.io namespace with path '/' |
| tst.js:42:1:42:14 | io.clients(cb) | socket.io namespace with path '/' |
| tst.js:43:1:43:17 | io.compress(true) | socket.io namespace with path '/' |
| tst.js:44:1:44:16 | io.binary(false) | socket.io namespace with path '/' |
| tst.js:45:1:45:7 | ns.json | socket.io namespace with path '/' |
| tst.js:46:1:46:11 | ns.volatile | socket.io namespace with path '/' |
| tst.js:47:1:47:8 | ns.local | socket.io namespace with path '/' |
| tst.js:50:1:66:2 | io.on(' ... cal;\\n}) | socket.io namespace with path '/' |
| tst.js:67:1:67:35 | io.on(' ... => {}) | socket.io namespace with path '/' |
| tst.js:68:1:68:32 | ns.on(' ... => {}) | socket.io namespace with path '/' |
| tst.js:69:1:73:2 | ns.on(' ... {});\\n}) | socket.io namespace with path '/' |

View File

@@ -1,4 +1,4 @@
import javascript
from SocketIO::NamespaceNode ns
select ns
select ns, ns.getNamespace()

View File

@@ -0,0 +1,4 @@
| socket.io namespace with path '/' | tst.js:1:12:1:33 | socket.io server | / |
| socket.io namespace with path '/' | tst.js:4:13:4:24 | socket.io server | / |
| socket.io namespace with path '/' | tst.js:6:13:6:27 | socket.io server | / |
| socket.io namespace with path '/foo' | tst.js:1:12:1:33 | socket.io server | /foo |

View File

@@ -0,0 +1,4 @@
import javascript
from SocketIO::NamespaceObject ns
select ns, ns.getServer(), ns.getPath()

View File

@@ -1,9 +1,9 @@
| tst.js:30:1:30:28 | ns.emit ... event') |
| tst.js:31:1:31:20 | ns.send('a message') |
| tst.js:32:1:32:21 | ns.writ ... ssage') |
| tst.js:39:1:39:31 | io.emit ... ssage') |
| tst.js:40:1:40:20 | io.send('a message') |
| tst.js:41:1:41:21 | io.writ ... ssage') |
| tst.js:51:3:51:22 | socket.emit('event') |
| tst.js:54:3:54:43 | socket. ... => {}) |
| tst.js:55:3:55:27 | socket. ... ssage') |
| tst.js:30:1:30:28 | ns.emit ... event') | socket.io namespace with path '/' |
| tst.js:31:1:31:20 | ns.send('a message') | socket.io namespace with path '/' |
| tst.js:32:1:32:22 | ns2.wri ... ssage') | socket.io namespace with path '/foo' |
| tst.js:39:1:39:31 | io.emit ... ssage') | socket.io namespace with path '/' |
| tst.js:40:1:40:20 | io.send('a message') | socket.io namespace with path '/' |
| tst.js:41:1:41:21 | io.writ ... ssage') | socket.io namespace with path '/' |
| tst.js:51:3:51:22 | socket.emit('event') | socket.io namespace with path '/' |
| tst.js:54:3:54:43 | socket. ... => {}) | socket.io namespace with path '/' |
| tst.js:55:3:55:27 | socket. ... ssage') | socket.io namespace with path '/' |

View File

@@ -1,4 +1,4 @@
import javascript
from SocketIO::SendNode sn
select sn
select sn, sn.getNamespace()

View File

@@ -1,6 +1,6 @@
| tst.js:30:1:30:28 | ns.emit ... event') | tst.js:30:18:30:27 | 'an event' |
| tst.js:31:1:31:20 | ns.send('a message') | tst.js:31:9:31:19 | 'a message' |
| tst.js:32:1:32:21 | ns.writ ... ssage') | tst.js:32:10:32:20 | 'a message' |
| tst.js:32:1:32:22 | ns2.wri ... ssage') | tst.js:32:11:32:21 | 'a message' |
| tst.js:39:1:39:31 | io.emit ... ssage') | tst.js:39:20:39:30 | 'a message' |
| tst.js:40:1:40:20 | io.send('a message') | tst.js:40:9:40:19 | 'a message' |
| tst.js:41:1:41:21 | io.writ ... ssage') | tst.js:41:10:41:20 | 'a message' |

View File

@@ -1,12 +1,12 @@
| tst.js:1:12:1:33 | require ... .io')() |
| tst.js:4:13:4:24 | new Server() |
| tst.js:6:13:6:27 | Server.listen() |
| tst.js:9:1:9:21 | io.serv ... (false) |
| tst.js:10:1:10:21 | io.set( ... s', []) |
| tst.js:11:1:11:21 | io.path ... npath') |
| tst.js:12:1:12:15 | io.adapter(foo) |
| tst.js:13:1:13:14 | io.origins([]) |
| tst.js:14:1:14:15 | io.listen(http) |
| tst.js:15:1:15:15 | io.attach(http) |
| tst.js:16:1:16:15 | io.bind(engine) |
| tst.js:17:1:17:23 | io.onco ... socket) |
| tst.js:1:12:1:33 | require ... .io')() | tst.js:1:12:1:33 | socket.io server |
| tst.js:4:13:4:24 | new Server() | tst.js:4:13:4:24 | socket.io server |
| tst.js:6:13:6:27 | Server.listen() | tst.js:6:13:6:27 | socket.io server |
| tst.js:9:1:9:21 | io.serv ... (false) | tst.js:1:12:1:33 | socket.io server |
| tst.js:10:1:10:21 | io.set( ... s', []) | tst.js:1:12:1:33 | socket.io server |
| tst.js:11:1:11:21 | io.path ... npath') | tst.js:1:12:1:33 | socket.io server |
| tst.js:12:1:12:15 | io.adapter(foo) | tst.js:1:12:1:33 | socket.io server |
| tst.js:13:1:13:14 | io.origins([]) | tst.js:1:12:1:33 | socket.io server |
| tst.js:14:1:14:15 | io.listen(http) | tst.js:1:12:1:33 | socket.io server |
| tst.js:15:1:15:15 | io.attach(http) | tst.js:1:12:1:33 | socket.io server |
| tst.js:16:1:16:15 | io.bind(engine) | tst.js:1:12:1:33 | socket.io server |
| tst.js:17:1:17:23 | io.onco ... socket) | tst.js:1:12:1:33 | socket.io server |

View File

@@ -1,4 +1,4 @@
import javascript
from SocketIO::ServerNode srv
select srv
select srv, srv.getServer()

View File

@@ -0,0 +1,3 @@
| tst.js:1:12:1:33 | socket.io server | tst.js:1:12:1:33 | require ... .io')() | socket.io namespace with path '/' |
| tst.js:4:13:4:24 | socket.io server | tst.js:4:13:4:24 | new Server() | socket.io namespace with path '/' |
| tst.js:6:13:6:27 | socket.io server | tst.js:6:13:6:27 | Server.listen() | socket.io namespace with path '/' |

View File

@@ -0,0 +1,4 @@
import javascript
from SocketIO::ServerObject srv
select srv, srv.getOrigin(), srv.getDefaultNamespace()

View File

@@ -1,21 +1,21 @@
| tst.js:50:19:50:24 | socket |
| tst.js:51:3:51:22 | socket.emit('event') |
| tst.js:52:3:52:17 | socket.to(room) |
| tst.js:53:3:53:17 | socket.in(room) |
| tst.js:54:3:54:43 | socket. ... => {}) |
| tst.js:55:3:55:27 | socket. ... ssage') |
| tst.js:56:3:56:19 | socket.join(room) |
| tst.js:57:3:57:20 | socket.leave(room) |
| tst.js:58:3:58:16 | socket.use(cb) |
| tst.js:59:3:59:23 | socket. ... s(true) |
| tst.js:60:3:60:22 | socket.binary(false) |
| tst.js:61:3:61:25 | socket. ... t(true) |
| tst.js:62:3:62:13 | socket.json |
| tst.js:63:3:63:17 | socket.volatile |
| tst.js:64:3:64:18 | socket.broadcast |
| tst.js:65:3:65:14 | socket.local |
| tst.js:67:22:67:27 | socket |
| tst.js:68:19:68:24 | socket |
| tst.js:69:22:69:27 | socket |
| tst.js:70:3:70:35 | socket. ... => {}) |
| tst.js:71:3:71:46 | socket. ... => {}) |
| tst.js:50:19:50:24 | socket | socket.io namespace with path '/' |
| tst.js:51:3:51:22 | socket.emit('event') | socket.io namespace with path '/' |
| tst.js:52:3:52:17 | socket.to(room) | socket.io namespace with path '/' |
| tst.js:53:3:53:17 | socket.in(room) | socket.io namespace with path '/' |
| tst.js:54:3:54:43 | socket. ... => {}) | socket.io namespace with path '/' |
| tst.js:55:3:55:27 | socket. ... ssage') | socket.io namespace with path '/' |
| tst.js:56:3:56:19 | socket.join(room) | socket.io namespace with path '/' |
| tst.js:57:3:57:20 | socket.leave(room) | socket.io namespace with path '/' |
| tst.js:58:3:58:16 | socket.use(cb) | socket.io namespace with path '/' |
| tst.js:59:3:59:23 | socket. ... s(true) | socket.io namespace with path '/' |
| tst.js:60:3:60:22 | socket.binary(false) | socket.io namespace with path '/' |
| tst.js:61:3:61:25 | socket. ... t(true) | socket.io namespace with path '/' |
| tst.js:62:3:62:13 | socket.json | socket.io namespace with path '/' |
| tst.js:63:3:63:17 | socket.volatile | socket.io namespace with path '/' |
| tst.js:64:3:64:18 | socket.broadcast | socket.io namespace with path '/' |
| tst.js:65:3:65:14 | socket.local | socket.io namespace with path '/' |
| tst.js:67:22:67:27 | socket | socket.io namespace with path '/' |
| tst.js:68:19:68:24 | socket | socket.io namespace with path '/' |
| tst.js:69:22:69:27 | socket | socket.io namespace with path '/' |
| tst.js:70:3:70:35 | socket. ... => {}) | socket.io namespace with path '/' |
| tst.js:71:3:71:46 | socket. ... => {}) | socket.io namespace with path '/' |

View File

@@ -1,4 +1,4 @@
import javascript
from SocketIO::SocketNode sn
select sn
select sn, sn.getNamespace()

View File

@@ -23,13 +23,13 @@ io.origins();
// SocketIO::NamespaceNodes:
var ns = io.sockets;
io.of("/");
var ns2 = io.of("/foo");
ns.use(auth);
ns.to(room);
ns.in(room);
ns.emit('event', 'an event');
ns.send('a message');
ns.write('a message');
ns2.write('a message');
ns.clients(cb);
ns.compress(true);
ns.binary(false);