@matter/nodejs-shell 0.17.4 → 0.17.5-alpha.0-20260704-9ea70abfa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/esm/app.js +8 -2
- package/dist/esm/app.js.map +1 -1
- package/dist/esm/web_plumbing.js +44 -14
- package/dist/esm/web_plumbing.js.map +2 -2
- package/package.json +9 -9
- package/src/app.ts +8 -1
- package/src/web_plumbing.ts +49 -12
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ This project provides a light-weight node.js implementation of a Matter shell ap
|
|
|
4
4
|
|
|
5
5
|
> This package supports all Node.js LTS versions starting with 20.x
|
|
6
6
|
|
|
7
|
+
> **⚠️ Development and testing tool only.** The shell (and its optional WebSocket/web interface) is intended for development, testing and debugging. It is **not** hardened for production use: it has no authentication, stores credentials unencrypted on disk, and the WebSocket/web interface serves the shell without access control. Do not expose it on untrusted networks or run it as a production service.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
If you want to install just the shell app then you can do so by running:
|
|
@@ -448,6 +450,8 @@ $ more .matter-shell-1/Node.ip
|
|
|
448
450
|
|
|
449
451
|
If the matter shell is started with the parameter --webSocketInterface all interaction with the shell will be done over a websocket instead of the local terminal. The parameter --webSocketPort NNNN can be used to change from the default port of 3000 to a user-specified port. If the parameter --webServer is added, the matter shell will also start an http server that will serve files from the same directory as the application itself utilizying the same port as the websocket. The functionality of the shell will be identical to the above description with the exception that the "exit" command will only close the websocket and not exit the matter shell application.
|
|
450
452
|
|
|
453
|
+
> **⚠️ Security:** The WebSocket/web interface has **no authentication** — anyone who can reach the port gets full shell control. By default it binds to loopback (`127.0.0.1`) only; a warning is logged to remind you of this. To expose it on other interfaces pass `--webAddress <address>` (e.g. `--webAddress 0.0.0.0`), but only do so on a trusted network. This interface is meant for local development and testing, not for production or internet-facing deployments.
|
|
454
|
+
|
|
451
455
|
An example application that shows interaction from a web browser is included. The example shows how commands can be sent from html and javascript in the browser to the shell and how the results of the commands can be parsed to create a user interface.
|
|
452
456
|
|
|
453
457
|

|
package/dist/esm/app.js
CHANGED
|
@@ -91,6 +91,11 @@ async function main() {
|
|
|
91
91
|
description: "Enable Web server when using WebSocket interface",
|
|
92
92
|
type: "boolean",
|
|
93
93
|
default: false
|
|
94
|
+
},
|
|
95
|
+
webAddress: {
|
|
96
|
+
description: "Address the WebSocket/web server binds to. Defaults to loopback (127.0.0.1); pass e.g. 0.0.0.0 to expose on all interfaces.",
|
|
97
|
+
type: "string",
|
|
98
|
+
default: void 0
|
|
94
99
|
}
|
|
95
100
|
});
|
|
96
101
|
},
|
|
@@ -108,7 +113,8 @@ async function main() {
|
|
|
108
113
|
logfile,
|
|
109
114
|
webSocketInterface,
|
|
110
115
|
webSocketPort,
|
|
111
|
-
webServer
|
|
116
|
+
webServer,
|
|
117
|
+
webAddress
|
|
112
118
|
} = argv;
|
|
113
119
|
theNode = new MatterNode(nodeNum, netInterface);
|
|
114
120
|
await theNode.initialize(factoryReset);
|
|
@@ -128,7 +134,7 @@ async function main() {
|
|
|
128
134
|
setLogLevel("default", await theNode.Store.get("LogLevel", "info"));
|
|
129
135
|
if (webSocketInterface) {
|
|
130
136
|
Logger.format = LogFormat.PLAIN;
|
|
131
|
-
initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer);
|
|
137
|
+
initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer, webAddress);
|
|
132
138
|
} else {
|
|
133
139
|
theShell = new Shell(theNode, nodeNum, PROMPT, process.stdin, process.stdout);
|
|
134
140
|
}
|
package/dist/esm/app.js.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/app.ts"],
|
|
4
|
-
"mappings": "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
|
|
4
|
+
"mappings": ";AACA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,aAAa,gBAAgB,WAAW,QAAQ,gBAAgB;AACzE,SAAS,wBAAwB;AACjC,OAAO;AACP,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,6BAA6B;AAEtC,MAAM,SAAS;AACf,MAAM,yBAAyB;AAC/B,MAAM,SAAS,OAAO,IAAI,OAAO;AACjC,IAAI;AAEJ,IAAI,QAAQ,OAAO,MAAO,QAAO,SAAS,UAAU;AAEpD,IAAI;AAEG,SAAS,YAAY,YAAoB,OAAqB;AACjE,MAAI,WAAW,SAAS;AACxB,UAAQ,OAAO;AAAA,IACX,KAAK;AACD,iBAAW,SAAS;AACpB;AAAA,IACJ,KAAK;AACD,iBAAW,SAAS;AACpB;AAAA,IACJ,KAAK;AACD,iBAAW,SAAS;AACpB;AAAA,IACJ,KAAK;AACD,iBAAW,SAAS;AACpB;AAAA,EACR;AACA,SAAO,aAAa,UAAU,EAAE,QAAQ;AAC5C;AAKA,eAAe,OAAO;AAClB,QAAM,gBAAgB,MAAM,EACvB;AAAA,IACG;AAAA,IACA;AAAA,IACA,CAAAA,WAAS;AACL,aAAOA,OACF,WAAW,YAAY;AAAA,QACpB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,WAAW,aAAa;AAAA,QACrB,UAAU;AAAA,QACV,SAAS,CAAC,YAAY;AAAA,QACtB,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,QAAQ;AAAA,QACL,cAAc;AAAA,UACV,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,UACT,OAAO;AAAA,QACX;AAAA,QACA,UAAU;AAAA,UACN,aACI;AAAA,UACJ,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,cAAc;AAAA,UACV,aAAa;AAAA,UACb,SAAS;AAAA,UACT,MAAM;AAAA,QACV;AAAA,QACA,cAAc;AAAA,UACV,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,SAAS;AAAA,UACL,aACI;AAAA,UACJ,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,oBAAoB;AAAA,UAChB,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,eAAe;AAAA,UACX,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,WAAW;AAAA,UACP,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACR,aACI;AAAA,UACJ,MAAM;AAAA,UACN,SAAS;AAAA,QACb;AAAA,MACJ,CAAC;AAAA,IACT;AAAA,IACA,OAAM,SAAQ;AACV,UAAI,KAAK,MAAM;AACX;AAAA,MACJ;AAEA,YAAM;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACJ,IAAI;AAEJ,gBAAU,IAAI,WAAW,SAAS,YAAY;AAC9C,YAAM,QAAQ,WAAW,YAAY;AAErC,UAAI,YAAY,QAAW;AACvB,cAAM,QAAQ,MAAM,IAAI,WAAW,OAAO;AAAA,MAC9C;AACA,UAAI,MAAM,QAAQ,MAAM,IAAI,SAAS,GAAG;AACpC,cAAM,oBAAoB,MAAM,QAAQ,MAAM,IAAY,SAAS;AACnE,YAAI,sBAAsB,QAAW;AACjC,iBAAO,aAAa,OAAO,eAAe;AAAA,YACtC,OAAO,MAAM,iBAAiB,iBAAiB;AAAA,YAC/C,OAAO,SAAS,MAAM,QAAQ,MAAM,IAAc,gBAAgB,SAAS,KAAK,CAAC;AAAA,YACjF,QAAQ,UAAU,OAAO;AAAA,UAC7B,CAAC;AAAA,QACL;AAAA,MACJ;AACA,kBAAY,WAAW,MAAM,QAAQ,MAAM,IAAY,YAAY,MAAM,CAAC;AAE1E,UAAI,oBAAoB;AACpB,eAAO,SAAS,UAAU;AAC1B,8BAAsB,SAAS,SAAS,eAAe,WAAW,UAAU;AAAA,MAChF,OAAO;AACH,mBAAW,IAAI,MAAM,SAAS,SAAS,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,MAChF;AAEA,UAAI,WAAW;AACX,oBAAY,QAAQ,KAAK,IAAI,cAAc,IAAI;AAAA,MACnD;AAEA,UAAI,aAAa,QAAW;AACxB,oBAAY,QAAQ,KAAK,IAAI,cAAc,QAAQ;AAAA,MACvD;AAEA,cAAQ,IAAI,iBAAiB,OAAO,WAAW,QAAQ,KAAK,YAAY,SAAS,SAAS,MAAM;AAChG,UAAI,CAAC,oBAAoB;AACrB,iBAAS,MAAM,QAAQ,eAAe;AAAA,MAC1C;AAAA,IACJ;AAAA,EACJ,EACC,QAAQ,KAAK,EACb,WAAW,OAAO,EAClB,OAAO;AACZ,QAAM,cAAc,KAAK,cAAc,cAAc,CAAC,EAAE,MAAM,QAAQ,QAAQ,IAAI,CAAC;AACvF;AAEA,QAAQ,GAAG,WAAW,SAAU,SAAS;AACrC,UAAQ,IAAI,wBAAwB,OAAO,EAAE;AAE7C,UAAQ,SAAS;AAAA,IACb,KAAK;AACD,WAAK,EAAE,MAAM,WAAS,OAAO,MAAM,qBAAqB,KAAK,CAAC;AAAA,EACtE;AACJ,CAAC;AAED,eAAsB,KAAK,OAAO,GAAG;AACjC,UAAQ,WAAW;AACnB,cAAY,QAAQ,QAAQ,OAAO;AACvC;AAEA,MAAM,UAAU,YAAY,QAAQ;AACpC,QAAQ,IAAI,KAAK,CAAC;AAClB,KAAK,QAAQ,SAAS,KAAK,MAAM,QAAQ,KAAK,QAAQ,YAAY,CAAC,CAAC;",
|
|
5
5
|
"names": ["yargs"]
|
|
6
6
|
}
|
package/dist/esm/web_plumbing.js
CHANGED
|
@@ -14,26 +14,56 @@ let client;
|
|
|
14
14
|
let server;
|
|
15
15
|
let wss;
|
|
16
16
|
const socketLogger = "websocket";
|
|
17
|
-
|
|
17
|
+
const logger = Logger.get("WebPlumbing");
|
|
18
|
+
const DEFAULT_LISTEN_ADDRESS = "127.0.0.1";
|
|
19
|
+
function initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer, listenAddress) {
|
|
20
|
+
const configuredAddress = listenAddress?.trim() || void 0;
|
|
21
|
+
const listenHost = configuredAddress ?? DEFAULT_LISTEN_ADDRESS;
|
|
22
|
+
if (configuredAddress === void 0) {
|
|
23
|
+
logger.warn(
|
|
24
|
+
`No listen address configured; binding the WebSocket/web server to loopback ${DEFAULT_LISTEN_ADDRESS} only. Pass --webAddress <address> (e.g. 0.0.0.0) to expose it on other interfaces.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
18
27
|
if (webServer) {
|
|
19
|
-
const
|
|
28
|
+
const resolvedRoot = path.resolve(__dirname);
|
|
29
|
+
let root;
|
|
30
|
+
try {
|
|
31
|
+
root = fs.realpathSync(resolvedRoot);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.warn(`Could not resolve web root real path (${resolvedRoot}): ${error}. Using unresolved path.`);
|
|
34
|
+
root = resolvedRoot;
|
|
35
|
+
}
|
|
36
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
37
|
+
const isContained = (candidate) => candidate === root || candidate.startsWith(rootWithSep);
|
|
20
38
|
server = http.createServer((req, res) => {
|
|
21
39
|
const url = req.url ?? "/";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
let decodedPath;
|
|
41
|
+
try {
|
|
42
|
+
decodedPath = decodeURIComponent(url === "/" ? "/index.html" : url);
|
|
43
|
+
} catch {
|
|
44
|
+
res.writeHead(400).end("Bad Request");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const safePath = path.normalize(path.join(root, decodedPath));
|
|
48
|
+
if (!isContained(safePath)) {
|
|
26
49
|
res.writeHead(403).end("Forbidden");
|
|
27
50
|
return;
|
|
28
51
|
}
|
|
29
|
-
fs.
|
|
30
|
-
if (
|
|
31
|
-
|
|
52
|
+
fs.realpath(safePath, (realpathErr, resolvedPath) => {
|
|
53
|
+
if (realpathErr) return res.writeHead(404).end("Not Found");
|
|
54
|
+
if (!isContained(resolvedPath)) {
|
|
55
|
+
res.writeHead(403).end("Forbidden");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
fs.readFile(resolvedPath, (err, data) => {
|
|
59
|
+
if (err) return res.writeHead(404).end("Not Found");
|
|
60
|
+
res.writeHead(200).end(data);
|
|
61
|
+
});
|
|
32
62
|
});
|
|
33
|
-
}).listen(webSocketPort);
|
|
63
|
+
}).listen(webSocketPort, listenHost);
|
|
34
64
|
wss = new WebSocketServer({ server });
|
|
35
|
-
} else wss = new WebSocketServer({ port: webSocketPort });
|
|
36
|
-
console.info(`WebSocket server
|
|
65
|
+
} else wss = new WebSocketServer({ port: webSocketPort, host: listenHost });
|
|
66
|
+
console.info(`WebSocket server listening on ${listenHost} port ${webSocketPort}`);
|
|
37
67
|
console.log = // console.debug = // too much traffic - kills the websocket
|
|
38
68
|
console.info = console.warn = console.error = (...args) => {
|
|
39
69
|
if (client && client.readyState === WebSocket.OPEN) {
|
|
@@ -50,11 +80,11 @@ function initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer) {
|
|
|
50
80
|
return;
|
|
51
81
|
}
|
|
52
82
|
client = ws;
|
|
53
|
-
createWebSocketLogger(ws).then((
|
|
83
|
+
createWebSocketLogger(ws).then((logger2) => {
|
|
54
84
|
delete Logger.destinations["Shell"];
|
|
55
85
|
Logger.destinations[socketLogger] = LogDestination({
|
|
56
86
|
name: socketLogger,
|
|
57
|
-
write: (text, message) =>
|
|
87
|
+
write: (text, message) => logger2(message.level, text)
|
|
58
88
|
});
|
|
59
89
|
}).catch((err) => {
|
|
60
90
|
console.error("Failed to add WebSocket logger: " + err);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/web_plumbing.ts"],
|
|
4
|
-
"mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,gBAAgB,cAAwB;AACjD,SAAS,UAAU,gBAAgB;AACnC,OAAO,aAAmB,uBAAuB;AAEjD,SAAS,aAAa;AAEtB,OAAO,QAAQ;AACf,OAAO,UAAsB;AAC7B,OAAO,UAAU;AAGjB,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,MAAM,eAAe;
|
|
5
|
-
"names": []
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": ["logger"]
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter/nodejs-shell",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
4
4
|
"description": "Shell app for Matter controller",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iot",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"matter-shell": "dist/cjs/app.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@matter/general": "0.17.
|
|
38
|
-
"@matter/model": "0.17.
|
|
39
|
-
"@matter/node": "0.17.
|
|
40
|
-
"@matter/nodejs": "0.17.
|
|
41
|
-
"@matter/nodejs-ble": "0.17.
|
|
42
|
-
"@matter/protocol": "0.17.
|
|
43
|
-
"@matter/types": "0.17.
|
|
44
|
-
"@project-chip/matter.js": "0.17.
|
|
37
|
+
"@matter/general": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
38
|
+
"@matter/model": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
39
|
+
"@matter/node": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
40
|
+
"@matter/nodejs": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
41
|
+
"@matter/nodejs-ble": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
42
|
+
"@matter/protocol": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
43
|
+
"@matter/types": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
44
|
+
"@project-chip/matter.js": "0.17.5-alpha.0-20260704-9ea70abfa",
|
|
45
45
|
"ws": "^8.21.0",
|
|
46
46
|
"yargs": "^18.0.0"
|
|
47
47
|
},
|
package/src/app.ts
CHANGED
|
@@ -107,6 +107,12 @@ async function main() {
|
|
|
107
107
|
type: "boolean",
|
|
108
108
|
default: false,
|
|
109
109
|
},
|
|
110
|
+
webAddress: {
|
|
111
|
+
description:
|
|
112
|
+
"Address the WebSocket/web server binds to. Defaults to loopback (127.0.0.1); pass e.g. 0.0.0.0 to expose on all interfaces.",
|
|
113
|
+
type: "string",
|
|
114
|
+
default: undefined,
|
|
115
|
+
},
|
|
110
116
|
});
|
|
111
117
|
},
|
|
112
118
|
async argv => {
|
|
@@ -125,6 +131,7 @@ async function main() {
|
|
|
125
131
|
webSocketInterface,
|
|
126
132
|
webSocketPort,
|
|
127
133
|
webServer,
|
|
134
|
+
webAddress,
|
|
128
135
|
} = argv;
|
|
129
136
|
|
|
130
137
|
theNode = new MatterNode(nodeNum, netInterface);
|
|
@@ -147,7 +154,7 @@ async function main() {
|
|
|
147
154
|
|
|
148
155
|
if (webSocketInterface) {
|
|
149
156
|
Logger.format = LogFormat.PLAIN;
|
|
150
|
-
initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer); // set up but wait for connect to create Shell
|
|
157
|
+
initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer, webAddress); // set up but wait for connect to create Shell
|
|
151
158
|
} else {
|
|
152
159
|
theShell = new Shell(theNode, nodeNum, PROMPT, process.stdin, process.stdout);
|
|
153
160
|
}
|
package/src/web_plumbing.ts
CHANGED
|
@@ -19,39 +19,76 @@ let client: WebSocket;
|
|
|
19
19
|
let server: Server;
|
|
20
20
|
let wss: WebSocketServer;
|
|
21
21
|
const socketLogger = "websocket";
|
|
22
|
+
const logger = Logger.get("WebPlumbing");
|
|
23
|
+
|
|
24
|
+
const DEFAULT_LISTEN_ADDRESS = "127.0.0.1";
|
|
22
25
|
|
|
23
26
|
export function initializeWebPlumbing(
|
|
24
27
|
theNode: MatterNode,
|
|
25
28
|
nodeNum: number,
|
|
26
29
|
webSocketPort: number,
|
|
27
30
|
webServer: boolean,
|
|
31
|
+
listenAddress?: string,
|
|
28
32
|
): void {
|
|
33
|
+
const configuredAddress = listenAddress?.trim() || undefined;
|
|
34
|
+
const listenHost = configuredAddress ?? DEFAULT_LISTEN_ADDRESS;
|
|
35
|
+
if (configuredAddress === undefined) {
|
|
36
|
+
logger.warn(
|
|
37
|
+
`No listen address configured; binding the WebSocket/web server to loopback ${DEFAULT_LISTEN_ADDRESS} only. ` +
|
|
38
|
+
`Pass --webAddress <address> (e.g. 0.0.0.0) to expose it on other interfaces.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
if (webServer) {
|
|
30
|
-
const
|
|
43
|
+
const resolvedRoot = path.resolve(__dirname);
|
|
44
|
+
let root: string;
|
|
45
|
+
try {
|
|
46
|
+
root = fs.realpathSync(resolvedRoot);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.warn(`Could not resolve web root real path (${resolvedRoot}): ${error}. Using unresolved path.`);
|
|
49
|
+
root = resolvedRoot;
|
|
50
|
+
}
|
|
51
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
52
|
+
// The trailing separator prevents sibling directories that merely share the root string as a
|
|
53
|
+
// prefix from passing the containment check.
|
|
54
|
+
const isContained = (candidate: string) => candidate === root || candidate.startsWith(rootWithSep);
|
|
31
55
|
|
|
32
56
|
server = http
|
|
33
57
|
.createServer((req, res) => {
|
|
34
58
|
const url = req.url ?? "/";
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
let decodedPath: string;
|
|
60
|
+
try {
|
|
61
|
+
decodedPath = decodeURIComponent(url === "/" ? "/index.html" : url);
|
|
62
|
+
} catch {
|
|
63
|
+
res.writeHead(400).end("Bad Request");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const safePath: string = path.normalize(path.join(root, decodedPath));
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
if (!safePath.startsWith(root)) {
|
|
68
|
+
if (!isContained(safePath)) {
|
|
41
69
|
res.writeHead(403).end("Forbidden");
|
|
42
70
|
return;
|
|
43
71
|
}
|
|
44
72
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
res.writeHead(
|
|
73
|
+
// Resolve symlinks so a link inside the root cannot redirect reads outside of it.
|
|
74
|
+
fs.realpath(safePath, (realpathErr, resolvedPath) => {
|
|
75
|
+
if (realpathErr) return res.writeHead(404).end("Not Found");
|
|
76
|
+
if (!isContained(resolvedPath)) {
|
|
77
|
+
res.writeHead(403).end("Forbidden");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fs.readFile(resolvedPath, (err, data) => {
|
|
82
|
+
if (err) return res.writeHead(404).end("Not Found");
|
|
83
|
+
res.writeHead(200).end(data);
|
|
84
|
+
});
|
|
48
85
|
});
|
|
49
86
|
})
|
|
50
|
-
.listen(webSocketPort);
|
|
87
|
+
.listen(webSocketPort, listenHost);
|
|
51
88
|
wss = new WebSocketServer({ server });
|
|
52
|
-
} else wss = new WebSocketServer({ port: webSocketPort });
|
|
89
|
+
} else wss = new WebSocketServer({ port: webSocketPort, host: listenHost });
|
|
53
90
|
|
|
54
|
-
console.info(`WebSocket server
|
|
91
|
+
console.info(`WebSocket server listening on ${listenHost} port ${webSocketPort}`);
|
|
55
92
|
|
|
56
93
|
console.log =
|
|
57
94
|
// console.debug = // too much traffic - kills the websocket
|