This commit is contained in:
2026-09-13 06:12:37 +02:00
parent 33c6d0e76d
commit 466d3c4f28
20 changed files with 2156 additions and 134 deletions
+112 -8
View File
@@ -20,7 +20,7 @@ import { handleMetaExtract } from "./meta_extract_handler.mjs";
import { handleMetaStrip } from "./meta_strip_handler.mjs";
import { handleCommentUpload, handleCommentUploadCancel } from "./comment_upload_handler.mjs";
import { handleDmAttachmentUpload, handleDmAttachmentDownload, handleDmAttachmentDelete } from "./dm_attachment_handler.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getCleanupIncludeEngaged, setCleanupIncludeEngaged, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode, getAllowCommentDeletion, setAllowCommentDeletion, getNsfpIds, setNsfpIds, getEnableExpiringUploads, getEnableItemSlugs, getEnableAnonymousAccess, getAnonPermissions, getAnonAnonymize, isAnonymizeSession, ensureAllItemsHaveSlugs } from "./inc/settings.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getCleanupIncludeEngaged, setCleanupIncludeEngaged, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode, getAllowCommentDeletion, setAllowCommentDeletion, getNsfpIds, setNsfpIds, getEnableExpiringUploads, getEnableItemSlugs, getEnableAnonymousAccess, getAnonPermissions, getAnonAnonymize, isAnonymizeSession, ensureAllItemsHaveSlugs, isAnonSession, canAnonDo, getAnonAllowedModes, getAnonAllowedMimes } from "./inc/settings.mjs";
import { updateHallsCache, getHalls } from "./inc/halls_cache.mjs";
import { createI18n } from "./inc/i18n.mjs";
import { safeDeleteMediaFile, purgeExpiredUploads } from "./inc/lib_delete.mjs";
@@ -32,6 +32,18 @@ import { verifySignature } from "./inc/anon_auth.mjs";
import { createRequire } from 'module';
const _require = createRequire(import.meta.url);
const addCookieHeader = (res, cookieStr) => {
if (!res || !res.setHeader) return;
const existing = res.getHeader('Set-Cookie');
if (!existing) {
res.setHeader('Set-Cookie', [cookieStr]);
} else if (Array.isArray(existing)) {
res.setHeader('Set-Cookie', [...existing, cookieStr]);
} else {
res.setHeader('Set-Cookie', [existing, cookieStr]);
}
};
// ─── Private Society Gate ────────────────────────────────────────────────────
// Powered by the cloudflare-error-page package.
// Each request gets a fresh page with a unique Ray ID + current UTC timestamp.
@@ -1112,13 +1124,105 @@ process.on('uncaughtException', err => {
const queryMode = req.url.qs?.mode !== undefined ? +req.url.qs.mode : undefined;
req.mode = queryMode !== undefined ? queryMode : (req.session ? +(req.session.mode ?? 0) : +(req.cookies?.mode ?? 0));
// public_nsfw: when true, the *default* for guests is mode 3 (all content).
// Only applies when the guest has no explicit mode preference (no ?mode= param, no cookie).
// If the guest has chosen a filter (e.g. SFW), that choice is always respected.
if (!req.session) {
const hasExplicitMode = queryMode !== undefined || req.cookies?.mode !== undefined;
if (!hasExplicitMode) {
req.mode = cfg.websrv.public_nsfw ? 3 : 0;
const isGuest = !req.session || !req.session.user;
if (isGuest) {
req.mode = 0;
// Ensure guests have mode=0 cookie
if (req.cookies?.mode !== '0') {
req.cookies = req.cookies || {};
req.cookies.mode = '0';
addCookieHeader(res, `mode=0; ${lib.getCookieOptions(31536000, false)}`);
}
// Ensure guests have ratings=sfw cookie
if (req.cookies?.ratings !== 'sfw') {
req.cookies = req.cookies || {};
req.cookies.ratings = 'sfw';
addCookieHeader(res, `ratings=sfw; ${lib.getCookieOptions(31536000, false)}`);
}
// Reconcile MIME cookie for guests
const allowedMimes = cfg.anonymous_permissions?.allowed_mimes || ['image', 'video', 'audio'];
if (req.cookies?.mime) {
const rawMimes = decodeURIComponent(req.cookies.mime).split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
const validMimes = rawMimes.filter(m => allowedMimes.includes(m));
if (validMimes.length !== rawMimes.length) {
if (validMimes.length > 0) {
req.cookies.mime = validMimes.join(',');
addCookieHeader(res, `mime=${req.cookies.mime}; ${lib.getCookieOptions(31536000, false)}`);
} else {
delete req.cookies.mime;
addCookieHeader(res, `mime=; ${lib.getCookieOptions(0, false)}`);
}
}
}
} else if (isAnonSession(req.session)) {
const canFilter = canAnonDo('filter');
const allowedModes = getAnonAllowedModes();
const allowedMimes = getAnonAllowedMimes();
const modeNames = ['sfw', 'nsfw', 'untagged', 'all', 'nsfl'];
// Validate & reconcile req.mode
const curModeName = modeNames[req.mode] || 'sfw';
if (!canFilter || !allowedModes.includes(curModeName)) {
const fallbackName = allowedModes.find(m => modeNames.includes(m)) || 'sfw';
const fallbackIdx = modeNames.indexOf(fallbackName);
req.mode = fallbackIdx >= 0 ? fallbackIdx : 0;
}
// If client sent a mode cookie that doesn't match the validated req.mode, auto-update it
if (req.cookies?.mode !== undefined && +req.cookies.mode !== req.mode) {
req.cookies.mode = String(req.mode);
addCookieHeader(res, `mode=${req.mode}; ${lib.getCookieOptions(31536000, false)}`);
}
// Validate & reconcile ratings cookie
if (req.cookies?.ratings) {
const rawRatings = decodeURIComponent(req.cookies.ratings).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r));
let validRatings = canFilter ? rawRatings.filter(r => allowedModes.includes(r)) : [];
if (validRatings.length !== rawRatings.length) {
if (validRatings.length === 0) {
if (allowedModes.length === 1 && !allowedModes.includes('all')) {
validRatings = [allowedModes[0]];
}
}
if (validRatings.length > 0) {
req.cookies.ratings = validRatings.join('|');
addCookieHeader(res, `ratings=${req.cookies.ratings}; ${lib.getCookieOptions(31536000, false)}`);
} else {
delete req.cookies.ratings;
addCookieHeader(res, `ratings=; ${lib.getCookieOptions(0, false)}`);
}
}
}
// Validate & reconcile mime cookie
if (req.cookies?.mime) {
const rawMimes = decodeURIComponent(req.cookies.mime).split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
const isSingleMime = allowedMimes.length === 1;
if (isSingleMime) {
const singleMime = allowedMimes[0];
if (req.cookies.mime !== singleMime) {
req.cookies.mime = singleMime;
addCookieHeader(res, `mime=${singleMime}; ${lib.getCookieOptions(31536000, false)}`);
}
} else if (!canFilter) {
delete req.cookies.mime;
addCookieHeader(res, `mime=; ${lib.getCookieOptions(0, false)}`);
} else {
const validMimes = rawMimes.filter(m => allowedMimes.includes(m));
if (validMimes.length !== rawMimes.length) {
if (validMimes.length > 0) {
req.cookies.mime = validMimes.join(',');
addCookieHeader(res, `mime=${req.cookies.mime}; ${lib.getCookieOptions(31536000, false)}`);
} else {
delete req.cookies.mime;
addCookieHeader(res, `mime=; ${lib.getCookieOptions(0, false)}`);
}
}
}
}
}