Skip to content

Commit e0f1e38

Browse files
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;
1 parent df1395a commit e0f1e38

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

docs/config.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Example:
3232
"serveAllFonts": false,
3333
"serveAllStyles": false,
3434
"serveStaticMaps": true,
35+
"allowRemoteMarkerIcons": true,
3536
"tileMargin": 0
3637
},
3738
"styles": {
@@ -142,6 +143,13 @@ Optional string to be rendered into the raster tiles (and static maps) as waterm
142143
Can be used for hard-coding attributions etc. (can also be specified per-style).
143144
Not used by default.
144145

146+
``allowRemoteMarkerIcons``
147+
--------------
148+
149+
Allows the rendering of marker icons fetched via http(s) hyperlinks.
150+
For security reasons only allow this if you can control the origins from where the markers are fetched!
151+
Default is to disallow fetching of icons from remote sources.
152+
145153
``styles``
146154
==========
147155

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"proj4": "2.8.0",
4040
"request": "2.88.2",
4141
"sharp": "0.31.0",
42-
"tileserver-gl-styles": "2.0.0"
42+
"tileserver-gl-styles": "2.0.0",
43+
"sanitize-filename": "1.6.3"
4344
},
4445
"devDependencies": {
4546
"chai": "4.3.6",

src/serve_rendered.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import util from 'util';
88
import zlib from 'zlib';
99
import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371
1010
import pkg from 'canvas';
11-
import Image from 'canvas';
1211
import clone from 'clone';
1312
import Color from 'color';
1413
import express from 'express';
14+
import sanitize from "sanitize-filename";
1515
import SphericalMercator from '@mapbox/sphericalmercator';
1616
import mlgl from '@maplibre/maplibre-gl-native';
1717
import MBTiles from '@mapbox/mbtiles';
@@ -22,7 +22,7 @@ import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js';
2222
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
2323
const httpTester = /^(http(s)?:)?\/\//;
2424

25-
const {createCanvas} = pkg;
25+
const {createCanvas, Image} = pkg;
2626
const mercator = new SphericalMercator();
2727
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
2828

@@ -231,11 +231,20 @@ const extractMarkersFromQuery = (query, options, transformer) => {
231231
// Check if icon is served via http otherwise marker icons are expected to
232232
// be provided as filepaths relative to configured icon path
233233
if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
234-
iconURI = path.resolve(options.paths.icons, iconURI);
235-
// Ensure icon exists at provided path
236-
if (!fs.existsSync(iconURI)) {
234+
// Sanitize URI with sanitize-filename
235+
// https://www.npmjs.com/package/sanitize-filename#details
236+
iconURI = sanitize(iconURI)
237+
238+
// If the selected icon is not part of available icons skip it
239+
if (!options.paths.availableIcons.includes(iconURI)) {
237240
continue;
238241
}
242+
243+
iconURI = path.resolve(options.paths.icons, iconURI);
244+
245+
// When we encounter a remote icon check if the configuration explicitly allows them.
246+
} else if (options.allowRemoteMarkerIcons !== true) {
247+
continue;
239248
}
240249

241250
// Ensure marker location could be parsed

src/server.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,35 @@ export function server(opts) {
9595
checkPath('mbtiles');
9696
checkPath('icons');
9797

98+
/**
99+
* Recursively get all files within a directory.
100+
* Inspired by https://stackoverflow.com/a/45130990/10133863
101+
* @param {String} directory Absolute path to a directory to get files from.
102+
*/
103+
const getFiles = async (directory) => {
104+
// Fetch all entries of the directory and attach type information
105+
const dirEntries = await fs.promises.readdir(directory, { withFileTypes: true });
106+
107+
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
108+
// otherwise initiate a recursive call
109+
const files = await Promise.all(dirEntries.map((dirEntry) => {
110+
const entryPath = path.resolve(directory, dirEntry.name);
111+
return dirEntry.isDirectory() ?
112+
getFiles(entryPath) : entryPath.replace(paths.icons + path.sep, "");
113+
}));
114+
115+
// Flatten the list of files to a single array
116+
return files.flat();
117+
}
118+
119+
// Load all available icons into a settings object
120+
startupPromises.push(new Promise(resolve => {
121+
getFiles(paths.icons).then((files) => {
122+
paths.availableIcons = files;
123+
resolve();
124+
});
125+
}));
126+
98127
if (options.dataDecorator) {
99128
try {
100129
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));

0 commit comments

Comments
 (0)