Skip to content

Commit 6a1491d

Browse files
committed
add SockJS to the existing WebSocket model
1 parent 9a18dc3 commit 6a1491d

File tree

3 files changed

+113
-21
lines changed

3 files changed

+113
-21
lines changed

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

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [ws](https://github.com/websockets/ws).
2+
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), [ws](https://github.com/websockets/ws), and [SockJS](http://sockjs.org).
33
*
44
* The model is based on the EventEmitter model, and there is therefore a
55
* data-flow step from where a WebSocket event is sent to where the message
@@ -18,26 +18,58 @@ import javascript
1818
*/
1919
private string channelName() { result = "message" }
2020

21+
/**
22+
* The names of the libraries modelled in this file.
23+
*/
24+
private module LibraryNames {
25+
string sockjs() { result = "SockJS" }
26+
27+
string websocket() { result = "WebSocket" }
28+
29+
string ws() { result = "ws" }
30+
}
31+
32+
/**
33+
* Holds if the websocket library named `client` can send a message to the library named `server`.
34+
* Both `client` and `server` are library names defined in `LibraryNames`.
35+
*/
36+
private predicate areLibrariesCompatible(string client, string server) {
37+
// sockjs is a WebSocket emulating library, but not actually an implementation of WebSockets.
38+
client = LibraryNames::sockjs() and server = LibraryNames::sockjs()
39+
or
40+
server = LibraryNames::ws() and
41+
(client = LibraryNames::ws() or client = LibraryNames::websocket())
42+
}
43+
2144
/**
2245
* Provides classes that model WebSockets clients.
2346
*/
2447
module ClientWebSocket {
48+
private import LibraryNames
49+
2550
/**
2651
* A class that can be used to instantiate a WebSocket instance.
2752
*/
2853
class SocketClass extends DataFlow::SourceNode {
29-
boolean isNode;
54+
string library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
3055

3156
SocketClass() {
32-
this = DataFlow::globalVarRef("WebSocket") and isNode = false
57+
this = DataFlow::globalVarRef("WebSocket") and library = websocket()
58+
or
59+
this = DataFlow::moduleImport("ws") and library = ws()
3360
or
34-
this = DataFlow::moduleImport("ws") and isNode = true
61+
// the sockjs-client library:https://www.npmjs.com/package/sockjs-client
62+
library = sockjs() and
63+
(
64+
this = DataFlow::moduleImport("sockjs-client") or
65+
this = DataFlow::globalVarRef("SockJS")
66+
)
3567
}
3668

3769
/**
38-
* Holds if this class is an import of the "ws" module.
70+
* Gets the WebSocket library name.
3971
*/
40-
predicate isNode() { isNode = true }
72+
string getLibrary() { result = library }
4173
}
4274

4375
/**
@@ -49,11 +81,9 @@ module ClientWebSocket {
4981
ClientSocket() { this = socketClass.getAnInstantiation() }
5082

5183
/**
52-
* Holds if this ClientSocket is created from the "ws" module.
53-
*
54-
* The predicate is used to differentiate where the behavior of the "ws" module differs from the native WebSocket in browsers.
84+
* Gets the WebSocket library name.
5585
*/
56-
predicate isNode() { socketClass.isNode() }
86+
string getLibrary() { result = socketClass.getLibrary() }
5787
}
5888

5989
/**
@@ -68,7 +98,10 @@ module ClientWebSocket {
6898

6999
override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(0) }
70100

71-
override ServerWebSocket::ReceiveNode getAReceiver() { any() }
101+
override ServerWebSocket::ReceiveNode getAReceiver() {
102+
areLibrariesCompatible(emitter.getLibrary(),
103+
result.getEmitter().(ServerWebSocket::ServerSocket).getLibrary())
104+
}
72105
}
73106

74107
/**
@@ -116,7 +149,7 @@ module ClientWebSocket {
116149
*/
117150
private class WSReceiveNode extends ClientWebSocket::ReceiveNode {
118151
WSReceiveNode() {
119-
emitter.isNode() and
152+
emitter.getLibrary() = ws() and
120153
this = getAMessageHandler(emitter, EventEmitter::on())
121154
}
122155

@@ -128,21 +161,38 @@ module ClientWebSocket {
128161
* Provides classes that model WebSocket servers.
129162
*/
130163
module ServerWebSocket {
164+
private import LibraryNames
165+
166+
/**
167+
* Gets a server created by a library named `library`.
168+
*/
169+
DataFlow::SourceNode getAServer(string library) {
170+
library = ws() and
171+
result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server")
172+
or
173+
library = sockjs() and
174+
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
175+
}
176+
131177
/**
132178
* A server WebSocket instance.
133179
*/
134180
class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode {
181+
string library;
182+
135183
ServerSocket() {
136184
exists(DataFlow::CallNode onCall |
137-
onCall =
138-
DataFlow::moduleImport("ws")
139-
.getAConstructorInvocation("Server")
140-
.getAMemberCall(EventEmitter::on()) and
185+
onCall = getAServer(library).getAMemberCall(EventEmitter::on()) and
141186
onCall.getArgument(0).mayHaveStringValue("connection")
142187
|
143188
this = onCall.getCallback(1).getParameter(0)
144189
)
145190
}
191+
192+
/**
193+
* Gets the name of the library that created this server socket.
194+
*/
195+
string getLibrary() { result = library }
146196
}
147197

148198
/**
@@ -151,7 +201,13 @@ module ServerWebSocket {
151201
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
152202
override ServerSocket emitter;
153203

154-
SendNode() { this = emitter.getAMemberCall("send") }
204+
SendNode() {
205+
emitter.getLibrary() = ws() and
206+
this = emitter.getAMemberCall("send")
207+
or
208+
emitter.getLibrary() = sockjs() and
209+
this = emitter.getAMemberCall("write")
210+
}
155211

156212
override string getChannel() { result = channelName() }
157213

@@ -160,7 +216,10 @@ module ServerWebSocket {
160216
result = getArgument(0)
161217
}
162218

163-
override ClientWebSocket::ReceiveNode getAReceiver() { any() }
219+
override ClientWebSocket::ReceiveNode getAReceiver() {
220+
areLibrariesCompatible(result.getEmitter().(ClientWebSocket::ClientSocket).getLibrary(),
221+
emitter.getLibrary())
222+
}
164223
}
165224

166225
/**
@@ -170,8 +229,14 @@ module ServerWebSocket {
170229
override ServerSocket emitter;
171230

172231
ReceiveNode() {
173-
this = emitter.getAMemberCall(EventEmitter::on()) and
174-
this.getArgument(0).mayHaveStringValue("message")
232+
exists(string eventName |
233+
emitter.getLibrary() = ws() and eventName = "message"
234+
or
235+
emitter.getLibrary() = sockjs() and eventName = "data"
236+
|
237+
this = emitter.getAMemberCall(EventEmitter::on()) and
238+
this.getArgument(0).mayHaveStringValue(eventName)
239+
)
175240
}
176241

177242
override string getChannel() { result = channelName() }

javascript/ql/test/library-tests/frameworks/WebSocket/browser.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,21 @@
1212
socket.onmessage = function (event) {
1313
console.log("Message from server 2", event.data)
1414
};
15-
})();
15+
})();
16+
17+
18+
(function () {
19+
var sock = new SockJS('http://0.0.0.0:9999/echo');
20+
sock.onopen = function () {
21+
sock.send('test');
22+
};
23+
24+
sock.onmessage = function (e) {
25+
console.log('message', e.data);
26+
sock.close();
27+
};
28+
29+
sock.addEventListener('message', function (event) {
30+
console.log('Using addEventListener ', event.data);
31+
});
32+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
clientSocket
22
| browser.js:2:17:2:52 | new Web ... :8080') |
3+
| browser.js:19:13:19:50 | new Soc ... /echo') |
34
| client.js:4:13:4:45 | new Web ... e.org') |
45
clientSend
56
| browser.js:5:3:5:33 | socket. ... wser!') |
7+
| browser.js:21:3:21:19 | sock.send('test') |
68
| client.js:7:3:7:28 | ws.send ... ient!') |
79
clientReceive
810
| browser.js:8:37:10:2 | functio ... ta);\\n\\t} |
911
| browser.js:12:21:14:2 | functio ... ata)\\n\\t} |
12+
| browser.js:24:19:27:2 | functio ... e();\\n\\t} |
13+
| browser.js:29:35:31:2 | functio ... ta);\\n\\t} |
1014
| client.js:10:19:12:2 | functio ... ta);\\n\\t} |
1115
serverSocket
1216
| server.js:6:43:6:44 | ws |
17+
| sockjs.js:8:40:8:43 | conn |
1318
serverSend
1419
| server.js:11:3:11:28 | ws.send ... rver!') |
20+
| sockjs.js:11:9:11:51 | conn.wr ... test))) |
1521
serverReceive
1622
| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
23+
| sockjs.js:9:5:12:6 | conn.on ... \\n }) |
1724
taintStep
1825
| browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message |
26+
| browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message |
1927
| client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message |
2028
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data |
2129
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data |
2230
| server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data |
31+
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data |
32+
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data |

0 commit comments

Comments
 (0)