gdfgd
This commit is contained in:
@@ -174,8 +174,8 @@ export async function getOrCreateAnonUser(pubkey, fingerprint, req = null, hwFin
|
||||
const userId = userRows[0].id;
|
||||
|
||||
await db`
|
||||
INSERT INTO user_options (user_id, mode, theme, fullscreen, avatar, display_name)
|
||||
VALUES (${userId}, 0, 'amoled', 0, null, 'Anonymous')
|
||||
INSERT INTO user_options (user_id, mode, theme, fullscreen, avatar, display_name, use_alternative_infobox)
|
||||
VALUES (${userId}, 0, 'amoled', 0, null, 'Anonymous', ${cfg.websrv.user_alternative_infobox !== false})
|
||||
ON CONFLICT (user_id) DO NOTHING
|
||||
`;
|
||||
|
||||
|
||||
@@ -18,16 +18,12 @@ const computeBaseMode = (mode, ratings, session) => {
|
||||
let effMode = Number(mode ?? 0);
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
|
||||
// For guest sessions, sanitize ratingsArr to only allow permitted ratings
|
||||
// For guest sessions, strictly force SFW mode and SFW rating
|
||||
let safeRatingsArr = ratingsArr;
|
||||
if (!session && ratingsArr) {
|
||||
const allowedRatings = ['sfw'];
|
||||
if (cfg.websrv.public_nsfw) allowedRatings.push('nsfw');
|
||||
if (cfg.websrv.public_untagged) allowedRatings.push('untagged');
|
||||
safeRatingsArr = ratingsArr.filter(r => allowedRatings.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
return "1 = 0";
|
||||
}
|
||||
const isGuest = !session || !session.user;
|
||||
if (isGuest) {
|
||||
effMode = 0;
|
||||
safeRatingsArr = ['sfw'];
|
||||
} else if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
const canFilter = canAnonDo('filter');
|
||||
@@ -35,10 +31,14 @@ const computeBaseMode = (mode, ratings, session) => {
|
||||
if (!canFilter) {
|
||||
safeRatingsArr = null;
|
||||
effMode = 0;
|
||||
} else if (safeRatingsArr) {
|
||||
safeRatingsArr = safeRatingsArr.filter(r => allowedModes.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
return "1 = 0";
|
||||
} else {
|
||||
if (safeRatingsArr) {
|
||||
safeRatingsArr = safeRatingsArr.filter(r => allowedModes.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
safeRatingsArr = (allowedModes.length === 1 && !allowedModes.includes('all'))
|
||||
? [allowedModes[0]]
|
||||
: (allowedModes.length < 5 ? [...allowedModes] : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,11 +84,11 @@ const computeBaseMode = (mode, ratings, session) => {
|
||||
}
|
||||
} else if (effMode === 2) {
|
||||
if (!cfg.websrv.public_untagged) {
|
||||
baseMode = "1 = 0";
|
||||
baseMode = "items.id in (select item_id from tags_assign where tag_id = 1)";
|
||||
}
|
||||
} else if (effMode === 1) {
|
||||
if (!cfg.websrv.public_nsfw) {
|
||||
baseMode = "1 = 0";
|
||||
baseMode = "items.id in (select item_id from tags_assign where tag_id = 1)";
|
||||
}
|
||||
} else if (effMode === 4) {
|
||||
baseMode = "1 = 0";
|
||||
|
||||
@@ -34,10 +34,11 @@ export default (router, tpl) => {
|
||||
|
||||
if (cfg.main.development) console.log(`[${new Date().toISOString()}] [AJAX] Starting item load for ${req.params.itemid}`);
|
||||
|
||||
const isGuest = !req.session || !req.session.user;
|
||||
const isRandom = query.random === '1' || req.cookies.random_mode === '1';
|
||||
const reqMode = query.mode !== undefined ? +query.mode : req.mode;
|
||||
const reqMode = isGuest ? 0 : (query.mode !== undefined ? +query.mode : req.mode);
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = (reqMode === 2 || reqMode === 3) ? null : (ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null);
|
||||
const ratingsArr = isGuest ? ['sfw'] : ((reqMode === 2 || reqMode === 3) ? null : (ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null));
|
||||
|
||||
const itemid = req.params.itemid || req.url.pathname.match(/\/ajax\/item\/([a-zA-Z0-9_-]{11}|\d+)/)?.[1];
|
||||
const data = await f0cklib.getf0ck({
|
||||
@@ -256,10 +257,11 @@ export default (router, tpl) => {
|
||||
query = req.url.qs || {};
|
||||
}
|
||||
|
||||
const isGuest = !req.session || !req.session.user;
|
||||
const page = parseInt(query.page) || 1;
|
||||
const isRandom = query.random === '1' || req.cookies.random_mode === '1';
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
const ratingsArr = isGuest ? ['sfw'] : (ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null);
|
||||
|
||||
const data = await f0cklib.getf0cks({
|
||||
page: page,
|
||||
@@ -269,7 +271,7 @@ export default (router, tpl) => {
|
||||
userHall: query.userHall || null,
|
||||
userHallOwner: query.userHallOwner || null,
|
||||
mime: query.mime || (req.cookies.mime || null),
|
||||
mode: query.mode !== undefined ? +query.mode : req.mode,
|
||||
mode: isGuest ? 0 : (query.mode !== undefined ? +query.mode : req.mode),
|
||||
ratings: ratingsArr,
|
||||
session: req.session,
|
||||
user_id: req.session?.id,
|
||||
|
||||
@@ -9,6 +9,259 @@ import { logAnonActivity } from "../../anon_auth.mjs";
|
||||
import { canAnonDo, isAnonSession } from "../../settings.mjs";
|
||||
|
||||
export default router => {
|
||||
router.post(/^\/api\/v2\/tags\/bulk\/?$/, lib.loggedin, async (req, res) => {
|
||||
const isModOrAdmin = !!(req.session?.admin || req.session?.is_moderator);
|
||||
if (!isModOrAdmin) {
|
||||
return res.json({ success: false, msg: 'Bulk selection is only available to administrators and moderators' }, 403);
|
||||
}
|
||||
|
||||
const action = req.body?.action || req.post?.action || 'add';
|
||||
const rawIds = req.body?.item_ids || req.post?.item_ids;
|
||||
if (!rawIds || !Array.isArray(rawIds) || rawIds.length === 0) {
|
||||
return res.json({ success: false, msg: 'No items selected' }, 400);
|
||||
}
|
||||
|
||||
// Sanitize item IDs
|
||||
const itemIds = [...new Set(rawIds.map(id => +id).filter(id => Number.isInteger(id) && id > 0))].slice(0, 500);
|
||||
if (itemIds.length === 0) {
|
||||
return res.json({ success: false, msg: 'Invalid item IDs' }, 400);
|
||||
}
|
||||
|
||||
// 1. ADD TAGS
|
||||
if (action === 'add') {
|
||||
if (isAnonSession(req.session) && !canAnonDo('tag')) {
|
||||
return res.json({ success: false, msg: 'Anonymous tagging is disabled' }, 403);
|
||||
}
|
||||
|
||||
let rawTags = req.body?.tags || req.post?.tags;
|
||||
if (typeof rawTags === 'string') {
|
||||
rawTags = rawTags.split(/[\n,]+/).map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
if (!Array.isArray(rawTags) || rawTags.length === 0) {
|
||||
return res.json({ success: false, msg: 'No tags provided' }, 400);
|
||||
}
|
||||
|
||||
const protectedTags = ['sfw', 'nsfw', 'nsfl'];
|
||||
const validTags = [...new Set(
|
||||
rawTags
|
||||
.map(t => (typeof t === 'string' ? t.trim() : ''))
|
||||
.filter(t => t.length > 0 && t.length <= 85 && !protectedTags.includes(t.toLowerCase()))
|
||||
)];
|
||||
|
||||
if (validTags.length === 0) {
|
||||
return res.json({ success: false, msg: 'Tags invalid or contain only reserved names' }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const tagIds = [];
|
||||
for (const tagname of validTags) {
|
||||
let tagRow = await db`
|
||||
SELECT id FROM "tags"
|
||||
WHERE normalized = slugify(${tagname})
|
||||
LIMIT 1
|
||||
`;
|
||||
let tagid = tagRow?.[0]?.id;
|
||||
if (!tagid) {
|
||||
const created = await db`
|
||||
INSERT INTO "tags" ${db({ tag: tagname })}
|
||||
RETURNING id
|
||||
`;
|
||||
tagid = created?.[0]?.id;
|
||||
}
|
||||
if (tagid) tagIds.push({ id: +tagid, name: tagname });
|
||||
}
|
||||
|
||||
// Insert assignments
|
||||
for (const postid of itemIds) {
|
||||
for (const { id: tagid } of tagIds) {
|
||||
try {
|
||||
await db`
|
||||
INSERT INTO "tags_assign" (tag_id, item_id, user_id)
|
||||
VALUES (${tagid}, ${postid}, ${+req.session.id})
|
||||
ON CONFLICT DO NOTHING
|
||||
`;
|
||||
} catch (assignErr) {
|
||||
// Ignore duplicate errors if table has constraint
|
||||
if (assignErr.code !== '23505') {
|
||||
console.warn(`[BULK_TAG_ASSIGN_WARN] Item ${postid}, tag ${tagid}:`, assignErr.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await audit.log(req.session.id, 'bulk_add_tags', 'items', null, { item_ids: itemIds, tags: validTags }).catch(() => {});
|
||||
if (req.session?.is_anon) {
|
||||
await logAnonActivity(req, {
|
||||
action: 'bulk_tag',
|
||||
targetId: itemIds[0],
|
||||
details: { item_ids: itemIds, tags: validTags }
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Notify affected items asynchronously
|
||||
(async () => {
|
||||
for (const postid of itemIds) {
|
||||
try {
|
||||
const freshTags = await lib.getTags(postid);
|
||||
await db.notify('tags', JSON.stringify({ item_id: postid, fresh: true, tags: freshTags }));
|
||||
} catch {}
|
||||
}
|
||||
})().catch(() => {});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
count: itemIds.length,
|
||||
tags: validTags,
|
||||
msg: `Successfully added ${validTags.length} tag(s) to ${itemIds.length} item(s)`
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[BULK_ADD_TAGS_ERROR]', err);
|
||||
return res.json({ success: false, msg: 'Failed to add tags' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. REMOVE TAGS
|
||||
if (action === 'remove') {
|
||||
const isModOrAdmin = !!(req.session.admin || req.session.is_moderator);
|
||||
|
||||
let rawTags = req.body?.tags || req.post?.tags;
|
||||
if (typeof rawTags === 'string') {
|
||||
rawTags = rawTags.split(/[\n,]+/).map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
if (!Array.isArray(rawTags) || rawTags.length === 0) {
|
||||
return res.json({ success: false, msg: 'No tags provided to remove' }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// Resolve tags to remove
|
||||
const tagRows = await db`
|
||||
SELECT id, tag, normalized FROM "tags"
|
||||
WHERE normalized IN ${db(rawTags.map(t => t.trim().toLowerCase()))}
|
||||
`;
|
||||
if (tagRows.length === 0) {
|
||||
return res.json({ success: false, msg: 'Tags not found' }, 404);
|
||||
}
|
||||
const tagIds = tagRows.map(r => r.id);
|
||||
|
||||
if (isModOrAdmin) {
|
||||
await db`
|
||||
DELETE FROM "tags_assign"
|
||||
WHERE item_id IN ${db(itemIds)}
|
||||
AND tag_id IN ${db(tagIds)}
|
||||
`;
|
||||
} else {
|
||||
// Normal user can only remove tags from items they own
|
||||
await db`
|
||||
DELETE FROM "tags_assign"
|
||||
WHERE item_id IN ${db(itemIds)}
|
||||
AND tag_id IN ${db(tagIds)}
|
||||
AND item_id IN (
|
||||
SELECT id FROM items WHERE username = ${req.session.user}
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
await audit.log(req.session.id, 'bulk_remove_tags', 'items', null, { item_ids: itemIds, tags: tagRows.map(r => r.tag) }).catch(() => {});
|
||||
|
||||
// Realtime notifications
|
||||
(async () => {
|
||||
for (const postid of itemIds) {
|
||||
try {
|
||||
const freshTags = await lib.getTags(postid);
|
||||
await db.notify('tags', JSON.stringify({ item_id: postid, fresh: true, tags: freshTags }));
|
||||
} catch {}
|
||||
}
|
||||
})().catch(() => {});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
count: itemIds.length,
|
||||
msg: `Successfully removed tag(s) from ${itemIds.length} item(s)`
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[BULK_REMOVE_TAGS_ERROR]', err);
|
||||
return res.json({ success: false, msg: 'Failed to remove tags' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SET RATING (sfw / nsfw / nsfl)
|
||||
if (action === 'set_rating') {
|
||||
if (isAnonSession(req.session) && !canAnonDo('rate_item')) {
|
||||
return res.json({ success: false, msg: 'Anonymous rating is disabled' }, 403);
|
||||
}
|
||||
|
||||
const rating = (req.body?.rating || req.post?.rating || '').toLowerCase();
|
||||
if (!['sfw', 'nsfw', 'nsfl'].includes(rating)) {
|
||||
return res.json({ success: false, msg: 'Invalid rating. Choose sfw, nsfw, or nsfl' }, 400);
|
||||
}
|
||||
|
||||
const isModOrAdmin = !!(req.session.admin || req.session.is_moderator);
|
||||
const nsflTagRow = await db`SELECT id FROM tags WHERE normalized = 'nsfl' LIMIT 1`;
|
||||
const nsflId = nsflTagRow.length > 0 ? nsflTagRow[0].id : (cfg.nsfl_tag_id || 11517);
|
||||
const targetTagId = rating === 'sfw' ? 1 : (rating === 'nsfw' ? 2 : nsflId);
|
||||
|
||||
try {
|
||||
let eligibleIds = itemIds;
|
||||
if (!isModOrAdmin) {
|
||||
const ownedItems = await db`
|
||||
SELECT id FROM items
|
||||
WHERE id IN ${db(itemIds)}
|
||||
AND username = ${req.session.user}
|
||||
AND active = true AND is_deleted = false
|
||||
`;
|
||||
eligibleIds = ownedItems.map(r => r.id);
|
||||
}
|
||||
|
||||
if (eligibleIds.length === 0) {
|
||||
return res.json({ success: false, msg: 'You do not have permission to rate the selected items' }, 403);
|
||||
}
|
||||
|
||||
await db.begin(async sql => {
|
||||
// Delete existing rating tags
|
||||
await sql`
|
||||
DELETE FROM tags_assign
|
||||
WHERE item_id IN ${sql(eligibleIds)}
|
||||
AND (tag_id IN (1, 2, ${nsflId}) OR tag_id IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl')))
|
||||
`;
|
||||
|
||||
// Insert new rating tags
|
||||
for (const id of eligibleIds) {
|
||||
await sql`
|
||||
INSERT INTO tags_assign (item_id, tag_id, user_id)
|
||||
VALUES (${id}, ${targetTagId}, ${+req.session.id})
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
await audit.log(req.session.id, 'bulk_set_rating', 'items', null, { item_ids: eligibleIds, rating }).catch(() => {});
|
||||
|
||||
// Queue blur thumbnail verification where needed
|
||||
(async () => {
|
||||
for (const id of eligibleIds) {
|
||||
try {
|
||||
const blurPath = path.join(cfg.paths.t, `${id}_blur.webp`);
|
||||
await fs.promises.access(blurPath).catch(() => queue.genBlurredThumbnail(id, false));
|
||||
const freshTags = await lib.getTags(id);
|
||||
await db.notify('tags', JSON.stringify({ item_id: id, fresh: true, tags: freshTags }));
|
||||
} catch {}
|
||||
}
|
||||
})().catch(() => {});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
count: eligibleIds.length,
|
||||
rating,
|
||||
msg: `Successfully set rating to ${rating.toUpperCase()} for ${eligibleIds.length} item(s)`
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[BULK_SET_RATING_ERROR]', err);
|
||||
return res.json({ success: false, msg: 'Failed to update rating' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({ success: false, msg: 'Unknown bulk action' }, 400);
|
||||
});
|
||||
|
||||
router.group(/^\/api\/v2\/tags\/(?<postid>\d+)/, group => {
|
||||
group.get(/$/, lib.loggedin, async (req, res) => {
|
||||
// get tags
|
||||
|
||||
@@ -645,7 +645,19 @@ export default (router, tpl) => {
|
||||
const modeMatch = req.url.pathname.match(/^\/mode\/(\d)/);
|
||||
const mode = modeMatch ? +modeMatch[1] : 0;
|
||||
|
||||
if (isAnonSession(req.session)) {
|
||||
const isGuest = !req.session || !req.session.user;
|
||||
if (isGuest) {
|
||||
if (mode !== 0) {
|
||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
|
||||
return res.reply({
|
||||
code: 403,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ success: false, msg: 'Only SFW mode is allowed for guests' })
|
||||
});
|
||||
}
|
||||
return res.redirect('/');
|
||||
}
|
||||
} else if (isAnonSession(req.session)) {
|
||||
if (!canAnonDo('filter') || !canAnonMode(mode)) {
|
||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
|
||||
return res.reply({
|
||||
|
||||
+112
-8
@@ -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)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user