No exceptions on missing error webpages - Create a `safeReadFileSync()` function in `src/utils.js` to wrap around `fs.readFileSync()` - Migrate error-webpage loads to use `safeReadFileSync()`, with default values of an empty string - Move the 404 error middleware to explicitly only be called *after* extensions are registered
Signed| @@ -67,6 +67,7 @@ import { | |||
| 67 | forwardFetchResponse, | 67 | forwardFetchResponse, |
| 68 | removeColorFormatting, | 68 | removeColorFormatting, |
| 69 | getSeparator, | 69 | getSeparator, |
| 70 | safeReadFileSync, | ||
| 70 | } from './src/util.js'; | 71 | } from './src/util.js'; |
| 71 | import { UPLOADS_DIRECTORY } from './src/constants.js'; | 72 | import { UPLOADS_DIRECTORY } from './src/constants.js'; |
| 72 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; | 73 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; |
| @@ -615,12 +616,6 @@ app.use('/api/backends/scale-alt', scaleAltRouter); | |||
| 615 | app.use('/api/speech', speechRouter); | 616 | app.use('/api/speech', speechRouter); |
| 616 | app.use('/api/azure', azureRouter); | 617 | app.use('/api/azure', azureRouter); |
| 617 | 618 | ||
| 618 | // If all other middlewares fail, send 404 error. | ||
| 619 | const notFoundWebpage = fs.readFileSync('./public/error/url-not-found.html', { encoding: 'utf-8' }); | ||
| 620 | app.use((req, res, next) => { | ||
| 621 | res.status(404).send(notFoundWebpage); | ||
| 622 | }); | ||
| 623 | |||
| 624 | const tavernUrlV6 = new URL( | 619 | const tavernUrlV6 = new URL( |
| 625 | (cliArguments.ssl ? 'https://' : 'http://') + | 620 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 626 | (listen ? '[::]' : '[::1]') + | 621 | (listen ? '[::]' : '[::1]') + |
| @@ -927,6 +922,16 @@ async function verifySecuritySettings() { | |||
| 927 | } | 922 | } |
| 928 | } | 923 | } |
| 929 | 924 | ||
| 925 | /** | ||
| 926 | * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. | ||
| 927 | */ | ||
| 928 | function apply404Middleware() { | ||
| 929 | const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? ''; | ||
| 930 | app.use((req, res) => { | ||
| 931 | res.status(404).send(notFoundWebpage); | ||
| 932 | }); | ||
| 933 | } | ||
| 934 | |||
| 930 | // User storage module needs to be initialized before starting the server | 935 | // User storage module needs to be initialized before starting the server |
| 931 | initUserStorage(dataRoot) | 936 | initUserStorage(dataRoot) |
| 932 | .then(ensurePublicDirectoriesExist) | 937 | .then(ensurePublicDirectoriesExist) |
| @@ -934,4 +939,5 @@ initUserStorage(dataRoot) | |||
| 934 | .then(migrateSystemPrompts) | 939 | .then(migrateSystemPrompts) |
| 935 | .then(verifySecuritySettings) | 940 | .then(verifySecuritySettings) |
| 936 | .then(preSetupTasks) | 941 | .then(preSetupTasks) |
| 942 | .then(apply404Middleware) | ||
| 937 | .finally(startServer); | 943 | .finally(startServer); |
| @@ -5,13 +5,12 @@ | |||
| 5 | import { Buffer } from 'node:buffer'; | 5 | import { Buffer } from 'node:buffer'; |
| 6 | import storage from 'node-persist'; | 6 | import storage from 'node-persist'; |
| 7 | import { getAllUserHandles, toKey, getPasswordHash } from '../users.js'; | 7 | import { getAllUserHandles, toKey, getPasswordHash } from '../users.js'; |
| 8 | import { getConfig, getConfigValue } from '../util.js'; | 8 | import { getConfig, getConfigValue, safeReadFileSync } from '../util.js'; |
| 9 | import { readFileSync } from 'node:fs'; | ||
| 10 | 9 | ||
| 11 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false); | 10 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false); |
| 12 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); | 11 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); |
| 13 | 12 | ||
| 14 | const unauthorizedWebpage = readFileSync('./public/error/unauthorized.html', { encoding: 'utf-8' }); | 13 | const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? ''; |
| 15 | const unauthorizedResponse = (res) => { | 14 | const unauthorizedResponse = (res) => { |
| 16 | res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"'); | 15 | res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"'); |
| 17 | return res.status(401).send(unauthorizedWebpage); | 16 | return res.status(401).send(unauthorizedWebpage); |
| @@ -5,13 +5,15 @@ import Handlebars from 'handlebars'; | |||
| 5 | import ipMatching from 'ip-matching'; | 5 | import ipMatching from 'ip-matching'; |
| 6 | 6 | ||
| 7 | import { getIpFromRequest } from '../express-common.js'; | 7 | import { getIpFromRequest } from '../express-common.js'; |
| 8 | import { color, getConfigValue } from '../util.js'; | 8 | import { color, getConfigValue, safeReadFileSync } from '../util.js'; |
| 9 | 9 | ||
| 10 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); | 10 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); |
| 11 | const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false); | 11 | const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false); |
| 12 | let whitelist = getConfigValue('whitelist', []); | 12 | let whitelist = getConfigValue('whitelist', []); |
| 13 | let knownIPs = new Set(); | 13 | let knownIPs = new Set(); |
| 14 | const forbiddenWebpage = Handlebars.compile(fs.readFileSync('./public/error/forbidden-by-whitelist.html', 'utf-8')); | 14 | const forbiddenWebpage = Handlebars.compile( |
| 15 | safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '', | ||
| 16 | ); | ||
| 15 | 17 | ||
| 16 | if (fs.existsSync(whitelistPath)) { | 18 | if (fs.existsSync(whitelistPath)) { |
| 17 | try { | 19 | try { |
| @@ -871,3 +871,14 @@ export class MemoryLimitedMap { | |||
| 871 | return this.map[Symbol.iterator](); | 871 | return this.map[Symbol.iterator](); |
| 872 | } | 872 | } |
| 873 | } | 873 | } |
| 874 | |||
| 875 | /** | ||
| 876 | * A 'safe' version of `fs.readFileSync()`. Returns the contents of a file if it exists, falling back to a default value if not. | ||
| 877 | * @param {string} filePath Path of the file to be read. | ||
| 878 | * @param {Parameters<typeof fs.readFileSync>[1]} options Options object to pass through to `fs.readFileSync()` (default: `{ encoding: 'utf-8' }`). | ||
| 879 | * @returns The contents at `filePath` if it exists, or `null` if not. | ||
| 880 | */ | ||
| 881 | export function safeReadFileSync(filePath, options = { encoding: 'utf-8' }) { | ||
| 882 | if (fs.existsSync(filePath)) return fs.readFileSync(filePath, options); | ||
| 883 | return null; | ||
| 884 | } | ||