insert copyright headers for images
Sonarqube Scanner / Prepare environment (push) Successful in 7s
Sonarqube Scanner / Build and test (push) Successful in 30s
Sonarqube Scanner / SonarQube analysis (push) Successful in 1m6s
Sonarqube Scanner / Deploy to Server (push) Successful in 6s

This commit is contained in:
Florian Zumpe
2026-10-04 10:14:40 +02:00
parent 3a682f2b30
commit d3311cce06
18 changed files with 382 additions and 12 deletions
+5 -2
View File
@@ -1,6 +1,7 @@
import { Router } from 'express';
import { inspectUpload, upload } from '../middleware/upload.js';
import { config } from '../config.js';
import { UPLOAD_COPYRIGHT_HEADERS } from '../services/copyright.js';
import {
listRoutes, getRoute, getRouteGpxFile, createRoute, updateRoute, appendRoute,
listPois, getPoi, createPoi, updatePoi, deletePoi, softDeleteRoute, restoreRoute
@@ -17,12 +18,13 @@ const numberOrUndefined = value => {
return Number.isFinite(parsed) ? parsed : undefined;
};
function sendFileResource(res, file, contentType = null) {
function sendFileResource(res, file, contentType = null, headers = {}) {
if (contentType) res.type(contentType);
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`);
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate');
return res.sendFile(file.absolutePath, {
headers,
acceptRanges: true,
cacheControl: true,
immutable: false,
@@ -132,7 +134,8 @@ export function createApiRouter(db) {
res.json(getPicture(db, routeId, poiId, pictureId));
return;
}
sendFileResource(res, getPictureFile(db, routeId, poiId, pictureId));
const file = getPictureFile(db, routeId, poiId, pictureId);
sendFileResource(res, file, null, UPLOAD_COPYRIGHT_HEADERS);
});
api.post('/routes/:routeId/pois/:poiId/pictures', requireWritableRoute('routeId'), upload.single('picture'), inspectUpload('picture', 'picture', { allowJson: true }), (req, res) => {
+33
View File
@@ -0,0 +1,33 @@
import path from 'node:path';
// These are policy notices, not evidence of a particular uploader's rights.
// ASCII keeps values safe across HTTP servers and reverse proxies.
export const UPLOAD_COPYRIGHT_NOTICE = 'Urheberrechte verbleiben bei den Urhebern. Nutzungsrechte an Upload-Bildern werden den Wegwichtel-Betreibern mit dem Hochladen unter Zustimmung zur Nutzungsvereinbarung eingeraeumt. Keine freie Weiterverwendung.';
export const APP_IMAGE_COPYRIGHT_NOTICE = 'Wegwichtel-App-Grafik: Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess der jeweiligen Lizenz.';
export const ENVATO_SYMBOL_SOURCE = 'https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077';
export const GROUP_SYMBOL_COPYRIGHT_NOTICE = 'Quelle: Envato. Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess Envato-Lizenz.';
export const COPYRIGHT_HEADER_LIMITS = 'Dieser technische Hinweis ersetzt weder die sichtbare Urheberbezeichnung noch einen Nachweis ueber tatsaechlich eingeraeumte Nutzungsrechte.';
export const UPLOAD_COPYRIGHT_HEADERS = Object.freeze({
'X-Copyright': UPLOAD_COPYRIGHT_NOTICE,
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
});
const APP_IMAGE_COPYRIGHT_HEADERS = Object.freeze({
'X-Copyright': APP_IMAGE_COPYRIGHT_NOTICE,
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
});
const GROUP_SYMBOL_COPYRIGHT_HEADERS = Object.freeze({
'X-Copyright': GROUP_SYMBOL_COPYRIGHT_NOTICE,
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS,
'X-Copyright-Source': ENVATO_SYMBOL_SOURCE
});
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.ico', '.avif']);
export function setAppImageCopyright(res, filePath) {
if (IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {
const isGroupSymbol = path.basename(path.dirname(filePath)) === 'group-symbols';
const headers = isGroupSymbol ? GROUP_SYMBOL_COPYRIGHT_HEADERS : APP_IMAGE_COPYRIGHT_HEADERS;
for (const [name, value] of Object.entries(headers)) res.setHeader(name, value);
}
}