added allowRemoteMarkerIcons configuration option and restricted fetching of remote marker icons only when option is set to true;

asynchronously load all available icons in a settings object on server startup;
replaced fs.existsSync() call in serve_rendered when drawing marker icons with a check against available icons settings object;
This commit is contained in:
Benedikt Brandtner 2022-10-04 16:40:13 +02:00
parent 78ddbf5454
commit ea4c398f26
4 changed files with 53 additions and 6 deletions

View file

@ -32,6 +32,7 @@ Example:
"serveAllFonts": false, "serveAllFonts": false,
"serveAllStyles": false, "serveAllStyles": false,
"serveStaticMaps": true, "serveStaticMaps": true,
"allowRemoteMarkerIcons": true,
"tileMargin": 0 "tileMargin": 0
}, },
"styles": { "styles": {
@ -142,6 +143,13 @@ Optional string to be rendered into the raster tiles (and static maps) as waterm
Can be used for hard-coding attributions etc. (can also be specified per-style). Can be used for hard-coding attributions etc. (can also be specified per-style).
Not used by default. Not used by default.
``allowRemoteMarkerIcons``
--------------
Allows the rendering of marker icons fetched via http(s) hyperlinks.
For security reasons only allow this if you can control the origins from where the markers are fetched!
Default is to disallow fetching of icons from remote sources.
``styles`` ``styles``
========== ==========

View file

@ -39,7 +39,8 @@
"proj4": "2.8.0", "proj4": "2.8.0",
"request": "2.88.2", "request": "2.88.2",
"sharp": "0.31.0", "sharp": "0.31.0",
"tileserver-gl-styles": "2.0.0" "tileserver-gl-styles": "2.0.0",
"sanitize-filename": "1.6.3"
}, },
"devDependencies": { "devDependencies": {
"chai": "4.3.6", "chai": "4.3.6",

View file

@ -8,10 +8,10 @@ import util from 'util';
import zlib from 'zlib'; import zlib from 'zlib';
import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371 import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371
import pkg from 'canvas'; import pkg from 'canvas';
import Image from 'canvas';
import clone from 'clone'; import clone from 'clone';
import Color from 'color'; import Color from 'color';
import express from 'express'; import express from 'express';
import sanitize from "sanitize-filename";
import SphericalMercator from '@mapbox/sphericalmercator'; import SphericalMercator from '@mapbox/sphericalmercator';
import mlgl from '@maplibre/maplibre-gl-native'; import mlgl from '@maplibre/maplibre-gl-native';
import MBTiles from '@mapbox/mbtiles'; import MBTiles from '@mapbox/mbtiles';
@ -22,7 +22,7 @@ import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js';
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)'; const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
const httpTester = /^(http(s)?:)?\/\//; const httpTester = /^(http(s)?:)?\/\//;
const {createCanvas} = pkg; const {createCanvas, Image} = pkg;
const mercator = new SphericalMercator(); const mercator = new SphericalMercator();
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0; const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
@ -231,11 +231,20 @@ const extractMarkersFromQuery = (query, options, transformer) => {
// Check if icon is served via http otherwise marker icons are expected to // Check if icon is served via http otherwise marker icons are expected to
// be provided as filepaths relative to configured icon path // be provided as filepaths relative to configured icon path
if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) { if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
iconURI = path.resolve(options.paths.icons, iconURI); // Sanitize URI with sanitize-filename
// Ensure icon exists at provided path // https://www.npmjs.com/package/sanitize-filename#details
if (!fs.existsSync(iconURI)) { iconURI = sanitize(iconURI)
// If the selected icon is not part of available icons skip it
if (!options.paths.availableIcons.includes(iconURI)) {
continue; continue;
} }
iconURI = path.resolve(options.paths.icons, iconURI);
// When we encounter a remote icon check if the configuration explicitly allows them.
} else if (options.allowRemoteMarkerIcons !== true) {
continue;
} }
// Ensure marker location could be parsed // Ensure marker location could be parsed

View file

@ -95,6 +95,35 @@ export function server(opts) {
checkPath('mbtiles'); checkPath('mbtiles');
checkPath('icons'); checkPath('icons');
/**
* Recursively get all files within a directory.
* Inspired by https://stackoverflow.com/a/45130990/10133863
* @param {String} directory Absolute path to a directory to get files from.
*/
const getFiles = async (directory) => {
// Fetch all entries of the directory and attach type information
const dirEntries = await fs.promises.readdir(directory, { withFileTypes: true });
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
// otherwise initiate a recursive call
const files = await Promise.all(dirEntries.map((dirEntry) => {
const entryPath = path.resolve(directory, dirEntry.name);
return dirEntry.isDirectory() ?
getFiles(entryPath) : entryPath.replace(paths.icons + path.sep, "");
}));
// Flatten the list of files to a single array
return files.flat();
}
// Load all available icons into a settings object
startupPromises.push(new Promise(resolve => {
getFiles(paths.icons).then((files) => {
paths.availableIcons = files;
resolve();
});
}));
if (options.dataDecorator) { if (options.dataDecorator) {
try { try {
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator)); options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));