Skip to content

Commit 09c4e4d

Browse files
vapierKernelDeimos
andauthored
add --coop option to enable cross-origin isolated (#806)
In order to utilize some web features like SharedArrayBuffer via localhost, COOP & COEP headers have to be set. Add an --coop option to let people easily opt-in to them. These are important for things like WebAssembly testing. More information can be found at: https://web.dev/cross-origin-isolation-guide/ Co-authored-by: Eric Dubé <[email protected]>
1 parent 46520f0 commit 09c4e4d

File tree

9 files changed

+155
-0
lines changed

9 files changed

+155
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ with the provided Dockerfile.
7070
|`-b` or `--brotli`|When enabled it will serve `./public/some-file.js.br` in place of `./public/some-file.js` when a brotli compressed version of the file exists and the request accepts `br` encoding. If gzip is also enabled, it will try to serve brotli first. |`false`|
7171
|`-e` or `--ext` |Default file extension if none supplied |`html` |
7272
|`-s` or `--silent` |Suppress log messages from output | |
73+
|`--coop` |Enable COOP via the `Cross-Origin-Opener-Policy` header | |
74+
|`--cors` |Enable CORS via the `Access-Control-Allow-Origin` header | |
7375
|`--cors` | Enable CORS via the `Access-Control-Allow-Origin: *` header. Optionally provide comma-separated values to add to `Access-Control-Allow-Headers` | |
7476
|`-H` or `--header` |Add an extra response header (can be used several times) | |
7577
|`-o [path]` |Open browser window after starting the server. Optionally provide a URL path to open. e.g.: -o /other/dir/ | |
@@ -138,6 +140,7 @@ This is what should be output if successful:
138140
Starting up http-server, serving ./ through https
139141

140142
http-server settings:
143+
COOP: disabled
141144
CORS: disabled
142145
Cache: 3600 seconds
143146
Connection Timeout: 120 seconds

bin/http-server

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ if (argv.h || argv.help) {
4545
'',
4646
' -e --ext Default file extension if none supplied [none]',
4747
' -s --silent Suppress log messages from output',
48+
' --coop[=mode] Enable COOP via the "Cross-Origin-Opener-Policy" header',
49+
' Optionally provide COOP mode.',
4850
' --content-type Default content type for unknown file types [application/octet-stream]',
4951
' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header',
5052
' When enabled, sets Access-Control-Allow-Origin to "*"',
@@ -192,6 +194,13 @@ function listen(port) {
192194
}
193195
}
194196

197+
if (argv.coop) {
198+
options.coop = true;
199+
if (typeof argv.coop === 'string') {
200+
options.coopHeader = argv.coop;
201+
}
202+
}
203+
195204
if (websocket) {
196205
if (!proxy) {
197206
logger.warning(colors.yellow('WebSocket proxy will not be enabled because proxy is not enabled'));
@@ -272,6 +281,7 @@ function listen(port) {
272281

273282
logger.info([
274283
chalk.yellow('\nhttp-server settings: '),
284+
([chalk.yellow('COOP: '), argv.coop ? chalk.cyan(argv.coop) : chalk.red('disabled')].join('')),
275285
([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')),
276286
([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')),
277287
([chalk.yellow('Connection Timeout: '), argv.t === '0' ? chalk.red('disabled') : (argv.t ? chalk.cyan(argv.t + ' seconds') : chalk.cyan('120 seconds'))].join('')),

doc/http-server.1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ Default file extension is none is provided.
6969
.BI \-s ", " \-\-silent
7070
Suppress log messages from output.
7171

72+
.TP
73+
.BI \-\-coop " " [\fIMODE\fR]
74+
Enable COOP via the "Cross-Origin-Opener-Policy" header and sets
75+
the "Cross-Origin-Embedder-Policy" header to "require-corp".
76+
Optionally provide COOP mode which defaults to "same-origin".
77+
7278
.TP
7379
.BI \-\-cors " " [\fIHEADERS\fR]
7480
Enable CORS by setting "Access-Control-Allow-Origin" to "*".

lib/core/aliases.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"hidePermissions": ["hidePermissions", "hidepermissions", "hide-permissions"],
1414
"si": [ "si", "index" ],
1515
"handleError": [ "handleError", "handleerror" ],
16+
"coop": [ "coop", "COOP" ],
1617
"cors": [ "cors", "CORS" ],
1718
"headers": [ "H", "header", "headers" ],
1819
"contentType": [ "contentType", "contenttype", "content-type" ],

lib/core/defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"hidePermissions": false,
88
"si": false,
99
"cache": "max-age=3600",
10+
"coop": false,
1011
"cors": false,
1112
"gzip": true,
1213
"brotli": false,

lib/core/opts.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ module.exports = (opts) => {
144144
return false;
145145
});
146146

147+
aliases.coop.forEach((k) => {
148+
if (isDeclared(k) && opts[k]) {
149+
handleOptionsMethod = true;
150+
headers['Cross-Origin-Opener-Policy'] = 'same-origin';
151+
headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
152+
}
153+
});
154+
147155
aliases.cors.forEach((k) => {
148156
if (isDeclared(k) && opts[k]) {
149157
handleOptionsMethod = true;

lib/http-server.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ function HttpServer(options) {
115115
});
116116
}
117117

118+
if (options.coop) {
119+
this.headers['Cross-Origin-Opener-Policy'] = options.coopHeader || 'same-origin';
120+
this.headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
121+
}
122+
118123
// CORS configuration:
119124
// --cors enables CORS by setting Access-Control-Allow-Origin to '*'
120125
// --cors=header1,header2 also adds custom headers to Access-Control-Allow-Headers

test/coop.test.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const test = require('tap').test;
4+
const server = require('../lib/core');
5+
const http = require('http');
6+
const path = require('path');
7+
const request = require('request');
8+
9+
const root = path.join(__dirname, 'public');
10+
11+
test('coop defaults to false', (t) => {
12+
t.plan(4);
13+
14+
const httpServer = http.createServer(
15+
server({
16+
root,
17+
autoIndex: true,
18+
defaultExt: 'html',
19+
})
20+
);
21+
22+
httpServer.listen(() => {
23+
const port = httpServer.address().port;
24+
const uri = `http://localhost:${port}/subdir/index.html`;
25+
26+
request.get({ uri }, (err, res) => {
27+
t.error(err);
28+
t.equal(res.statusCode, 200);
29+
t.type(res.headers['cross-origin-opener-policy'], 'undefined');
30+
t.type(res.headers['cross-origin-embedder-policy'], 'undefined');
31+
});
32+
});
33+
t.once('end', () => {
34+
httpServer.close();
35+
});
36+
});
37+
38+
test('coop set to false', (t) => {
39+
t.plan(4);
40+
41+
const httpServer = http.createServer(
42+
server({
43+
root,
44+
coop: false,
45+
autoIndex: true,
46+
defaultExt: 'html',
47+
})
48+
);
49+
50+
httpServer.listen(() => {
51+
const port = httpServer.address().port;
52+
const uri = `http://localhost:${port}/subdir/index.html`;
53+
54+
request.get({ uri }, (err, res) => {
55+
t.error(err);
56+
t.equal(res.statusCode, 200);
57+
t.type(res.headers['cross-origin-opener-policy'], 'undefined');
58+
t.type(res.headers['cross-origin-embedder-policy'], 'undefined');
59+
});
60+
});
61+
t.once('end', () => {
62+
httpServer.close();
63+
});
64+
});
65+
66+
test('coop set to true', (t) => {
67+
t.plan(4);
68+
69+
const httpServer = http.createServer(
70+
server({
71+
root,
72+
coop: true,
73+
autoIndex: true,
74+
defaultExt: 'html',
75+
})
76+
);
77+
78+
httpServer.listen(() => {
79+
const port = httpServer.address().port;
80+
const uri = `http://localhost:${port}/subdir/index.html`;
81+
request.get({ uri }, (err, res) => {
82+
t.error(err);
83+
t.equal(res.statusCode, 200);
84+
t.equal(res.headers['cross-origin-opener-policy'], 'same-origin');
85+
t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp');
86+
});
87+
});
88+
t.once('end', () => {
89+
httpServer.close();
90+
});
91+
});
92+
93+
test('COOP set to true', (t) => {
94+
t.plan(4);
95+
96+
const httpServer = http.createServer(
97+
server({
98+
root,
99+
COOP: true,
100+
autoIndex: true,
101+
defaultExt: 'html',
102+
})
103+
);
104+
105+
httpServer.listen(() => {
106+
const port = httpServer.address().port;
107+
const uri = `http://localhost:${port}/subdir/index.html`;
108+
request.get({ uri }, (err, res) => {
109+
t.error(err);
110+
t.equal(res.statusCode, 200);
111+
t.equal(res.headers['cross-origin-opener-policy'], 'same-origin');
112+
t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp');
113+
});
114+
});
115+
t.once('end', () => {
116+
httpServer.close();
117+
});
118+
});

test/main.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ test('http-server main', (t) => {
2626
'Access-Control-Allow-Origin': '*',
2727
'Access-Control-Allow-Credentials': 'true'
2828
},
29+
coop: true,
2930
cors: true,
3031
corsHeaders: 'X-Test',
3132
ext: true,
@@ -64,6 +65,8 @@ test('http-server main', (t) => {
6465
// Custom headers
6566
t.equal(res.headers['access-control-allow-origin'], '*');
6667
t.equal(res.headers['access-control-allow-credentials'], 'true');
68+
t.equal(res.headers['cross-origin-opener-policy'], 'same-origin');
69+
t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp');
6770
}).catch(err => t.fail(err.toString())),
6871

6972
// Get robots

0 commit comments

Comments
 (0)