minor bugfixes

This commit is contained in:
Ludwig Lehnert
2026-03-28 09:10:40 +01:00
parent 48bfe69d09
commit ee72edecb1
7 changed files with 94 additions and 22 deletions

View File

@@ -9,6 +9,7 @@ import { NextResponse } from 'next/server';
import {
maxRetentionSeconds,
maxUploadBytes,
publicBaseUrl,
shareDir,
uploadTtlSeconds,
} from '@/src/lib/config.js';
@@ -41,8 +42,49 @@ function requestPageHref(requestId, params = {}) {
return serialized ? `/_request/${encodedId}?${serialized}` : `/_request/${encodedId}`;
}
function redirectToRequest(request, requestId, params = {}) {
return NextResponse.redirect(new URL(requestPageHref(requestId, params), request.url), { status: 303 });
function redirectToRequest(requestId, params = {}) {
return new NextResponse(null, {
status: 303,
headers: {
location: requestPageHref(requestId, params),
},
});
}
function firstForwardedPart(value) {
if (!value) {
return '';
}
return value.split(',')[0].trim();
}
function requestOrigin(request) {
const forwardedHost = firstForwardedPart(request.headers.get('x-forwarded-host'));
const host = forwardedHost || request.headers.get('host') || '';
if (!host) {
return '';
}
const forwardedProto = firstForwardedPart(request.headers.get('x-forwarded-proto'));
const proto = forwardedProto || (host.startsWith('localhost') || host.startsWith('127.0.0.1') ? 'http' : 'https');
return `${proto}://${host}`;
}
function resolvePublicOrigin(request) {
if (publicBaseUrl) {
return publicBaseUrl;
}
const fromHeaders = requestOrigin(request);
if (fromHeaders) {
return fromHeaders;
}
try {
return new URL(request.url).origin;
} catch {
return '';
}
}
function toBase32(buffer) {
@@ -119,7 +161,7 @@ export async function POST(request, { params }) {
try {
formData = await request.formData();
} catch {
return redirectToRequest(request, requestId, { error: 'Ungültige Formulardaten.' });
return redirectToRequest(requestId, { error: 'Ungültige Formulardaten.' });
}
const uploadRequest = await get(
@@ -130,25 +172,25 @@ export async function POST(request, { params }) {
);
if (!uploadRequest) {
return redirectToRequest(request, requestId, { error: 'Anfrage nicht gefunden.' });
return redirectToRequest(requestId, { error: 'Anfrage nicht gefunden.' });
}
if (Number(uploadRequest.completed_at || 0) > 0) {
return redirectToRequest(request, requestId, { success: 'Diese Anfrage wurde bereits erfüllt.' });
return redirectToRequest(requestId, { success: 'Diese Anfrage wurde bereits erfüllt.' });
}
const now = Date.now();
if (Number(uploadRequest.expires_at || 0) <= now) {
return redirectToRequest(request, requestId, { error: 'Diese Anfrage ist bereits abgelaufen.' });
return redirectToRequest(requestId, { error: 'Diese Anfrage ist bereits abgelaufen.' });
}
const uploadedFile = uploadedFileFromForm(formData, 'file');
if (!uploadedFile || Number(uploadedFile.size || 0) <= 0) {
return redirectToRequest(request, requestId, { error: 'Keine Datei hochgeladen.' });
return redirectToRequest(requestId, { error: 'Keine Datei hochgeladen.' });
}
if (maxUploadBytes > 0 && Number(uploadedFile.size || 0) > maxUploadBytes) {
return redirectToRequest(request, requestId, {
return redirectToRequest(requestId, {
error: `Datei überschreitet das Größenlimit (${maxUploadBytes} Bytes).`,
});
}
@@ -171,7 +213,7 @@ export async function POST(request, { params }) {
}
if (!storedName || !storedPath) {
return redirectToRequest(request, requestId, { error: 'Upload-ID konnte nicht erzeugt werden.' });
return redirectToRequest(requestId, { error: 'Upload-ID konnte nicht erzeugt werden.' });
}
const uploadExpiry = Math.min(now + uploadTtlSeconds * 1000, now + maxRetentionSeconds * 1000);
@@ -187,7 +229,7 @@ export async function POST(request, { params }) {
);
uploadId = insertResult.lastID;
} catch {
return redirectToRequest(request, requestId, { error: 'Upload fehlgeschlagen.' });
return redirectToRequest(requestId, { error: 'Upload fehlgeschlagen.' });
}
const updateResult = await run(
@@ -206,7 +248,7 @@ export async function POST(request, { params }) {
if (!updateResult || updateResult.changes < 1) {
await run('DELETE FROM uploads WHERE id = ?', [uploadId]).catch(() => undefined);
await fs.promises.rm(storedPath, { force: true }).catch(() => undefined);
return redirectToRequest(request, requestId, { success: 'Diese Anfrage wurde bereits erfüllt.' });
return redirectToRequest(requestId, { success: 'Diese Anfrage wurde bereits erfüllt.' });
}
await logEvent(
@@ -216,7 +258,7 @@ export async function POST(request, { params }) {
await getRequestMeta()
);
const baseUrl = new URL(request.url).origin;
const baseUrl = resolvePublicOrigin(request);
const downloadUrl = `${baseUrl}/_share/${encodeURIComponent(storedName)}`;
const mailResult = await sendUploadRequestCompletedMail({
@@ -230,7 +272,7 @@ export async function POST(request, { params }) {
if (mailResult.ok) {
await run('UPDATE upload_requests SET notification_sent_at = ? WHERE id = ?', [Date.now(), requestId]);
return redirectToRequest(request, requestId, { success: 'Datei erfolgreich hochgeladen. Vielen Dank!' });
return redirectToRequest(requestId, { success: 'Datei erfolgreich hochgeladen. Vielen Dank!' });
}
await logEvent(
@@ -240,7 +282,7 @@ export async function POST(request, { params }) {
await getRequestMeta()
);
return redirectToRequest(request, requestId, {
return redirectToRequest(requestId, {
success: 'Datei hochgeladen. Hinweis: E-Mail-Benachrichtigung konnte nicht gesendet werden.',
});
}

View File

@@ -107,8 +107,12 @@ function expectsHtml(request) {
function errorResponse(request, message, status = 400) {
if (expectsHtml(request)) {
const target = new URL(dashboardHref({ error: message }), request.url);
return NextResponse.redirect(target, { status: 303 });
return new NextResponse(null, {
status: 303,
headers: {
location: dashboardHref({ error: message }),
},
});
}
return NextResponse.json({ ok: false, error: message }, { status });
@@ -117,8 +121,12 @@ function errorResponse(request, message, status = 400) {
function successResponse(request, message) {
const redirectPath = dashboardHref({ success: message });
if (expectsHtml(request)) {
const target = new URL(redirectPath, request.url);
return NextResponse.redirect(target, { status: 303 });
return new NextResponse(null, {
status: 303,
headers: {
location: redirectPath,
},
});
}
return NextResponse.json({ ok: true, redirect: redirectPath });

View File

@@ -5,15 +5,33 @@ function parseInteger(value, fallback) {
return Number.isFinite(parsed) ? parsed : fallback;
}
function normalizeBaseUrl(value) {
const raw = String(value || '').trim();
if (!raw) {
return '';
}
const withProtocol = raw.includes('://') ? raw : `https://${raw}`;
try {
const parsed = new URL(withProtocol);
return `${parsed.protocol}//${parsed.host}`;
} catch {
return '';
}
}
export const managementBasePath = '/manage';
export const dataDir = process.env.DATA_DIR || path.join(process.cwd(), 'data');
export const dbPath = process.env.DB_PATH || path.join(dataDir, 'uploads.sqlite');
export const shareDir = path.join(dataDir, '_share');
export const serviceFqdn = String(process.env.SERVICE_FQDN || '').trim();
export const adminHash = process.env.MANAGEMENT_ADMIN_HASH || '';
export const uploadTtlSeconds = parseInteger(process.env.UPLOAD_TTL_SECONDS || '604800', 604800);
export const maxRetentionSeconds = 90 * 24 * 60 * 60;
export const maxUploadBytes = parseInteger(process.env.UPLOAD_MAX_BYTES || '0', 0);
export const cookieSecure = process.env.COOKIE_SECURE === 'true';
export const publicBaseUrl = normalizeBaseUrl(process.env.PUBLIC_BASE_URL || serviceFqdn);
export const smtpHost = String(process.env.SMTP_HOST || '').trim();
export const smtpPort = parseInteger(process.env.SMTP_PORT || '587', 587);
export const smtpUser = String(process.env.SMTP_USER || '').trim();