From 3f313f93380f72c5b1df96cf89daa40aab6d3444 Mon Sep 17 00:00:00 2001
From: Michael Kainz <michael.kainz@open-xchange.com>
Date: Tue, 2 Apr 2024 10:51:13 +0200
Subject: [PATCH] Improve 404 error message

---
 src/404.html              | 94 +++++++++++++++++++++++++++++++++++++++
 src/index.js              |  8 ++++
 src/routes/serve-files.js |  7 ++-
 3 files changed, 108 insertions(+), 1 deletion(-)
 create mode 100644 src/404.html

diff --git a/src/404.html b/src/404.html
new file mode 100644
index 0000000..c91fa56
--- /dev/null
+++ b/src/404.html
@@ -0,0 +1,94 @@
+<html lang="en">
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    body {
+      width: 100%;
+      height: 100%;
+      display: grid;
+      font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+    }
+
+    #box {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      align-self: center;
+    }
+
+    img {
+      margin: 1rem 0 2rem 0;
+      max-width: 100%;
+    }
+
+    h1 {
+      font-weight: 500;
+      font-size: 1.5rem;
+      line-height: 2rem;
+      margin: 0 0 0.5rem 0;
+    }
+
+    @media screen and (min-width: 540px) {
+      #box {
+        padding: 48px;
+        border: 1px solid #ddd;
+        border-radius: 16px;
+        margin: auto;
+        box-shadow: 0 24px 80px 0 rgba(0, 0, 0, 0.10);
+      }
+    }
+  </style>
+</head>
+
+<body class="unselectable">
+
+  <div id="box" />
+
+  <script type="module">
+    (async () => {
+      const messages = {
+        'The requested page does not exist.': {
+          "de": "Die angeforderte Seite existiert nicht.",
+          "es": "La página solicitada no existe.",
+          "it": "La pagina richiesta non esiste.",
+          "fr": "La page demandée n'existe pas.",
+          "ja": "要求されたページは存在しません.",
+          "nl": "De gevraagde pagina bestaat niet."
+        },
+        'Error 404': {
+          "de": "Fehler 404",
+          "es": "Error 404",
+          "it": "Errore 404",
+          "fr": "Erreur 404",
+          "ja": "エラー404",
+          "nl": "Fout 404"
+        }
+      }
+
+      let language
+      const staticGt = (str) => {
+        if (!messages[str]) return str;
+        return messages[str][language] || str;
+      }
+
+      function getLanguage() {
+        const match = location.hash.match(/language=(\w+)/) || document.cookie.match(/locale=(\w+)/)
+        return (match?.[1] || document.documentElement.lang || navigator.language || navigator.userLanguage).substr(0, 2)
+      }
+
+      function updateMessages() {
+        language = getLanguage()
+        document.querySelector('#box').innerHTML = '<img src="./themes/default/illustrations/error-generic.svg"></img>' +
+          `<h1>${staticGt('Error 404')}</h1>` +
+          `<div>${staticGt('The requested page does not exist.')}</div>`
+      }
+
+      updateMessages()
+      document.documentElement.addEventListener('languageChange', updateMessages);
+    })()
+  </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 8ef11af..1cc3db0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -23,6 +23,7 @@
 import { config } from 'dotenv-defaults'
 import { fileURLToPath } from 'node:url'
 import { dirname, join } from 'node:path'
+import { readFileSync } from 'node:fs'
 import { randomUUID } from 'node:crypto'
 import logger from './logger.js'
 
@@ -99,6 +100,13 @@ lightship.whenFirstReady().then(async () => {
 // This hook is used to signal lightship that the service is ready to receive requests
 app.addHook('onReady', () => { lightship.signalReady() })
 
+const NOT_FOUND_HTML = readFileSync('./src/404.html')
+app.setNotFoundHandler((req, reply) => {
+  reply.code(404)
+  reply.headers({ 'content-type': 'text/html; charset=utf-8' })
+  reply.send(NOT_FOUND_HTML)
+})
+
 try {
   // Binds and listens for connections on the specified host and port
   await app.listen({ host: '::', port: Number(process.env.PORT) })
diff --git a/src/routes/serve-files.js b/src/routes/serve-files.js
index 8d000e3..1ffde08 100644
--- a/src/routes/serve-files.js
+++ b/src/routes/serve-files.js
@@ -19,6 +19,7 @@
  */
 
 import { getFile } from '../files.js'
+import logger from '../logger.js'
 import { isNotAllowedOriginError, isNotFoundError, isVersionMismatchError } from '../errors.js'
 
 export default async function serveFilePlugin (fastify, options) {
@@ -32,7 +33,11 @@ export default async function serveFilePlugin (fastify, options) {
       reply.headers(headers)
       reply.send(body)
     } catch (err) {
-      if (isNotFoundError(err) || isVersionMismatchError(err)) throw fastify.httpErrors.createError(404, `File "${req.urlData('path')}" does not exist.`, err)
+      if (isNotFoundError(err) || isVersionMismatchError(err)) {
+        reply.callNotFound()
+        logger.warn(err, `[Files] File "${req.urlData('path')}" does not exist.`)
+        return
+      }
       if (isNotAllowedOriginError(err)) throw fastify.httpErrors.createError(400, err)
       throw fastify.httpErrors.createError(err.statusCode || 500, err)
     }
-- 
GitLab