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:
parent
78ddbf5454
commit
ea4c398f26
4 changed files with 53 additions and 6 deletions
|
|
@ -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``
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue