openvibers/openvibe.chat Commits
Unknown pages answer an HTML 404 page to a browser (browser check, WS-Q task 3: openvibe.chat answered every unknown path with JSON). A GET or HEAD that asks for text/html, outside /api/, /internal/ and /ws/, gets the site's own page inside the Frame: 404, noindex, the path escaped, links to global chat, rooms and messages. The API, those prefixes, other methods and clients not asking for HTML keep the JSON 404. Test: web.test.js
Changes
@@@ -476,6 +476,19 @@ ${can.manage ? `<form class="oc-form" method="post" action="/r/${esc(room.slug)}router.get('/sitemap.xml', (req, res) => res.type('application/xml').set('Cache-Control', 'public, max-age=3600').send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${['/', '/rooms', '/updates', ...rooms.list(null, { limit: 100 }).public.map((r) => `/r/${r.slug}`)].map((p) => `<url><loc>${site}${p}</loc></url>`).join('')}</urlset>\n`));++ // ── Nothing here: a browser asking for a page gets the site's 404 page (the browser check found++ // JSON). The API, /internal/, /ws/ and any client not asking for HTML keep the JSON 404 that++ // server/app.js answers after this router.++ router.use(async (req, res, next) => {++ if ((req.method !== 'GET' && req.method !== 'HEAD') || /^\/(api|internal|ws)(\/|$)/.test(req.path) || !/\btext\/html\b/.test(req.get('accept') || '')) return next();++ try {++ await page(req, res, 404, {++ title: 'Page not found', path: req.path, robots: 'noindex',++ body: `<h1>Page not found</h1><p>There is no page at <code>${esc(req.path.slice(0, 200))}</code>. It may have moved, or the link may be wrong.</p><p><a href="/">Global chat</a> · <a href="/rooms">Rooms</a> · <a href="/messages">Messages</a></p>`,++ });++ } catch (err) { next(err); }++ });++return router;}@@@ -159,4 +159,26 @@ t('sign-in, the Frame, robots, sitemap and /updates', async () => {assert.strictEqual((await req('GET', '/api/nope')).status, 404, 'the API keeps its JSON 404');});++t('unknown pages: a browser gets the site\'s HTML 404 page, the API and other clients keep JSON', async () => {++ const html = { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' };++ const r = await req('GET', '/__ovcheck-404', { headers: html });++ assert.strictEqual(r.status, 404);++ assert.match(r.headers.get('content-type'), /^text\/html/);++ assert.match(r.text, /<h1>Page not found<\/h1>/);++ assert.match(r.text, /<meta name="robots" content="noindex">/);++ assert.match(r.text, /<code>\/__ovcheck-404<\/code>/);++ assert.match(r.text, /\/shared\/navbar\.js\?v=/, 'inside the Frame, like every page');++ const x = await req('GET', '/%3Cscript%3Ex', { headers: html });++ assert.strictEqual(x.status, 404);++ assert.ok(!x.text.includes('<script>x'), 'the path is escaped');++ for (const [path, headers] of [['/api/nope', html], ['/internal/nope', html], ['/nope', {}], ['/nope', { accept: 'application/json' }]]) {++ const j = await req('GET', path, { headers });++ assert.strictEqual(j.status, 404, path);++ assert.match(j.headers.get('content-type'), /^application\/json/, `${path} ${JSON.stringify(headers)}: JSON`);++ }++ const post = await req('POST', '/nope', { headers: html });++ assert.strictEqual(post.status, 404);++ assert.match(post.headers.get('content-type'), /^application\/json/, 'a form post to nowhere keeps JSON');++});++t.run(async () => { if (h && h.close) await h.close(); });
@@ -476,6 +476,19 @@ ${can.manage ? `<form class="oc-form" method="post" action="/r/${esc(room.slug)}
@@ -476,6 +476,19 @@ ${can.manage ? `<form class="oc-form" method="post" action="/r/${esc(room.slug)}
router.get('/sitemap.xml', (req, res) => res.type('application/xml').set('Cache-Control', 'public, max-age=3600')
router.get('/sitemap.xml', (req, res) => res.type('application/xml').set('Cache-Control', 'public, max-age=3600')
.send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${['/', '/rooms', '/updates', ...rooms.list(null, { limit: 100 }).public.map((r) => `/r/${r.slug}`)].map((p) => `<url><loc>${site}${p}</loc></url>`).join('')}</urlset>\n`));
.send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${['/', '/rooms', '/updates', ...rooms.list(null, { limit: 100 }).public.map((r) => `/r/${r.slug}`)].map((p) => `<url><loc>${site}${p}</loc></url>`).join('')}</urlset>\n`));
+ // ── Nothing here: a browser asking for a page gets the site's 404 page (the browser check found
+ // JSON). The API, /internal/, /ws/ and any client not asking for HTML keep the JSON 404 that
+ // server/app.js answers after this router.
+ router.use(async (req, res, next) => {
+ if ((req.method !== 'GET' && req.method !== 'HEAD') || /^\/(api|internal|ws)(\/|$)/.test(req.path) || !/\btext\/html\b/.test(req.get('accept') || '')) return next();
+ try {
+ await page(req, res, 404, {
+ title: 'Page not found', path: req.path, robots: 'noindex',
+ body: `<h1>Page not found</h1><p>There is no page at <code>${esc(req.path.slice(0, 200))}</code>. It may have moved, or the link may be wrong.</p><p><a href="/">Global chat</a> · <a href="/rooms">Rooms</a> · <a href="/messages">Messages</a></p>`,
+ });
+ } catch (err) { next(err); }
+ });
+
return router;
return router;
}
}
@@ -159,4 +159,26 @@ t('sign-in, the Frame, robots, sitemap and /updates', async () => {
@@ -159,4 +159,26 @@ t('sign-in, the Frame, robots, sitemap and /updates', async () => {
assert.strictEqual((await req('GET', '/api/nope')).status, 404, 'the API keeps its JSON 404');
assert.strictEqual((await req('GET', '/api/nope')).status, 404, 'the API keeps its JSON 404');
});
});
+t('unknown pages: a browser gets the site\'s HTML 404 page, the API and other clients keep JSON', async () => {
+ const html = { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' };
+ const r = await req('GET', '/__ovcheck-404', { headers: html });
+ assert.strictEqual(r.status, 404);
+ assert.match(r.headers.get('content-type'), /^text\/html/);
+ assert.match(r.text, /<h1>Page not found<\/h1>/);
+ assert.match(r.text, /<meta name="robots" content="noindex">/);
+ assert.match(r.text, /<code>\/__ovcheck-404<\/code>/);
+ assert.match(r.text, /\/shared\/navbar\.js\?v=/, 'inside the Frame, like every page');
+ const x = await req('GET', '/%3Cscript%3Ex', { headers: html });
+ assert.strictEqual(x.status, 404);
+ assert.ok(!x.text.includes('<script>x'), 'the path is escaped');
+ for (const [path, headers] of [['/api/nope', html], ['/internal/nope', html], ['/nope', {}], ['/nope', { accept: 'application/json' }]]) {
+ const j = await req('GET', path, { headers });
+ assert.strictEqual(j.status, 404, path);
+ assert.match(j.headers.get('content-type'), /^application\/json/, `${path} ${JSON.stringify(headers)}: JSON`);
+ }
+ const post = await req('POST', '/nope', { headers: html });
+ assert.strictEqual(post.status, 404);
+ assert.match(post.headers.get('content-type'), /^application\/json/, 'a form post to nowhere keeps JSON');
+});
+
t.run(async () => { if (h && h.close) await h.close(); });
t.run(async () => { if (h && h.close) await h.close(); });