tileserver-gl-light 5.4.1-pre.0 → 5.5.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -2
- package/docs/config.rst +158 -2
- package/docs/usage.rst +66 -3
- package/package.json +7 -6
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/src/main.js +89 -14
- package/src/mbtiles_wrapper.js +5 -6
- package/src/pmtiles_adapter.js +413 -60
- package/src/render.js +48 -13
- package/src/serve_data.js +28 -9
- package/src/serve_rendered.js +78 -27
- package/src/serve_style.js +13 -8
- package/src/server.js +115 -25
- package/src/utils.js +79 -11
package/src/serve_data.js
CHANGED
|
@@ -12,7 +12,8 @@ import { SphericalMercator } from '@mapbox/sphericalmercator';
|
|
|
12
12
|
import {
|
|
13
13
|
fixTileJSONCenter,
|
|
14
14
|
getTileUrls,
|
|
15
|
-
|
|
15
|
+
isS3Url,
|
|
16
|
+
isValidRemoteUrl,
|
|
16
17
|
fetchTileData,
|
|
17
18
|
} from './utils.js';
|
|
18
19
|
import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
|
|
@@ -68,6 +69,7 @@ export const serve_data = {
|
|
|
68
69
|
String(req.params.format).replace(/\n|\r/g, ''),
|
|
69
70
|
);
|
|
70
71
|
}
|
|
72
|
+
// eslint-disable-next-line security/detect-object-injection -- req.params.id is route parameter, validated by Express
|
|
71
73
|
const item = repo[req.params.id];
|
|
72
74
|
if (!item) {
|
|
73
75
|
return res.sendStatus(404);
|
|
@@ -146,6 +148,7 @@ export const serve_data = {
|
|
|
146
148
|
features: [],
|
|
147
149
|
};
|
|
148
150
|
for (const layerName in tile.layers) {
|
|
151
|
+
// eslint-disable-next-line security/detect-object-injection -- layerName from VectorTile library internal data structure
|
|
149
152
|
const layer = tile.layers[layerName];
|
|
150
153
|
for (let i = 0; i < layer.length; i++) {
|
|
151
154
|
const feature = layer.feature(i);
|
|
@@ -190,6 +193,7 @@ export const serve_data = {
|
|
|
190
193
|
String(req.params.y).replace(/\n|\r/g, ''),
|
|
191
194
|
);
|
|
192
195
|
}
|
|
196
|
+
// eslint-disable-next-line security/detect-object-injection -- req.params.id is route parameter, validated by Express
|
|
193
197
|
const item = repo?.[req.params.id];
|
|
194
198
|
if (!item) return res.sendStatus(404);
|
|
195
199
|
if (!item.source) return res.status(404).send('Missing source');
|
|
@@ -300,6 +304,7 @@ export const serve_data = {
|
|
|
300
304
|
String(req.params.id).replace(/\n|\r/g, ''),
|
|
301
305
|
);
|
|
302
306
|
}
|
|
307
|
+
// eslint-disable-next-line security/detect-object-injection -- req.params.id is route parameter, validated by Express
|
|
303
308
|
const item = repo[req.params.id];
|
|
304
309
|
if (!item) {
|
|
305
310
|
return res.sendStatus(404);
|
|
@@ -331,25 +336,26 @@ export const serve_data = {
|
|
|
331
336
|
* @param {object} programOpts - An object containing the program options
|
|
332
337
|
* @param {string} programOpts.publicUrl Public URL for the data.
|
|
333
338
|
* @param {boolean} programOpts.verbose Whether verbose logging should be used.
|
|
334
|
-
* @param {Function} dataResolver Function to resolve data.
|
|
335
339
|
* @returns {Promise<void>}
|
|
336
340
|
*/
|
|
337
341
|
add: async function (options, repo, params, id, programOpts) {
|
|
338
|
-
const { publicUrl } = programOpts;
|
|
342
|
+
const { publicUrl, verbose } = programOpts;
|
|
339
343
|
let inputFile;
|
|
340
344
|
let inputType;
|
|
341
345
|
if (params.pmtiles) {
|
|
342
346
|
inputType = 'pmtiles';
|
|
343
|
-
|
|
347
|
+
// PMTiles supports HTTP, HTTPS, and S3 URLs
|
|
348
|
+
if (isValidRemoteUrl(params.pmtiles)) {
|
|
344
349
|
inputFile = params.pmtiles;
|
|
345
350
|
} else {
|
|
346
351
|
inputFile = path.resolve(options.paths.pmtiles, params.pmtiles);
|
|
347
352
|
}
|
|
348
353
|
} else if (params.mbtiles) {
|
|
349
354
|
inputType = 'mbtiles';
|
|
350
|
-
|
|
355
|
+
// MBTiles does not support remote URLs
|
|
356
|
+
if (isValidRemoteUrl(params.mbtiles)) {
|
|
351
357
|
console.log(
|
|
352
|
-
`ERROR: MBTiles does not support
|
|
358
|
+
`ERROR: MBTiles does not support remote files. "${params.mbtiles}" is not a valid data file.`,
|
|
353
359
|
);
|
|
354
360
|
process.exit(1);
|
|
355
361
|
} else {
|
|
@@ -357,11 +363,17 @@ export const serve_data = {
|
|
|
357
363
|
}
|
|
358
364
|
}
|
|
359
365
|
|
|
366
|
+
if (verbose && verbose >= 1) {
|
|
367
|
+
console.log(`[INFO] Loading data source '${id}' from: ${inputFile}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
360
370
|
let tileJSON = {
|
|
361
371
|
tiles: params.domains || options.domains,
|
|
362
372
|
};
|
|
363
373
|
|
|
364
|
-
|
|
374
|
+
// Only check file stats for local files, not remote URLs
|
|
375
|
+
if (!isValidRemoteUrl(inputFile)) {
|
|
376
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- inputFile is from config file, validated above
|
|
365
377
|
const inputFileStats = await fsp.stat(inputFile);
|
|
366
378
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
367
379
|
throw Error(`Not valid input file: "${inputFile}"`);
|
|
@@ -377,9 +389,15 @@ export const serve_data = {
|
|
|
377
389
|
tileJSON['sparse'] = params['sparse'];
|
|
378
390
|
|
|
379
391
|
if (inputType === 'pmtiles') {
|
|
380
|
-
source = openPMtiles(
|
|
392
|
+
source = openPMtiles(
|
|
393
|
+
inputFile,
|
|
394
|
+
params.s3Profile,
|
|
395
|
+
params.requestPayer,
|
|
396
|
+
params.s3Region,
|
|
397
|
+
verbose,
|
|
398
|
+
);
|
|
381
399
|
sourceType = 'pmtiles';
|
|
382
|
-
const metadata = await getPMtilesInfo(source);
|
|
400
|
+
const metadata = await getPMtilesInfo(source, inputFile);
|
|
383
401
|
Object.assign(tileJSON, metadata);
|
|
384
402
|
} else if (inputType === 'mbtiles') {
|
|
385
403
|
sourceType = 'mbtiles';
|
|
@@ -401,6 +419,7 @@ export const serve_data = {
|
|
|
401
419
|
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
402
420
|
}
|
|
403
421
|
|
|
422
|
+
// eslint-disable-next-line security/detect-object-injection -- id is from config file data source names
|
|
404
423
|
repo[id] = {
|
|
405
424
|
tileJSON,
|
|
406
425
|
publicUrl,
|
package/src/serve_rendered.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
listFonts,
|
|
33
33
|
getTileUrls,
|
|
34
34
|
isValidHttpUrl,
|
|
35
|
+
isValidRemoteUrl,
|
|
35
36
|
fixTileJSONCenter,
|
|
36
37
|
fetchTileData,
|
|
37
38
|
readFile,
|
|
@@ -61,8 +62,8 @@ const staticTypeRegex = new RegExp(
|
|
|
61
62
|
);
|
|
62
63
|
|
|
63
64
|
const PATH_PATTERN =
|
|
65
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Simple path pattern validation, no nested quantifiers
|
|
64
66
|
/^((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/;
|
|
65
|
-
const httpTester = /^https?:\/\//i;
|
|
66
67
|
|
|
67
68
|
const mercator = new SphericalMercator();
|
|
68
69
|
|
|
@@ -94,7 +95,7 @@ const cachedEmptyResponses = {
|
|
|
94
95
|
* Create an appropriate mlgl response for http errors.
|
|
95
96
|
* @param {string} format The format (a sharp format or 'pbf').
|
|
96
97
|
* @param {string} color The background color (or empty string for transparent).
|
|
97
|
-
* @param {
|
|
98
|
+
* @param {(err: Error|null, data: object|null) => void} callback The mlgl callback.
|
|
98
99
|
* @returns {void}
|
|
99
100
|
*/
|
|
100
101
|
function createEmptyResponse(format, color, callback) {
|
|
@@ -111,6 +112,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const cacheKey = `${format},${color}`;
|
|
115
|
+
// eslint-disable-next-line security/detect-object-injection -- cacheKey is constructed from validated format and color
|
|
114
116
|
const data = cachedEmptyResponses[cacheKey];
|
|
115
117
|
if (data) {
|
|
116
118
|
callback(null, { data: data });
|
|
@@ -136,6 +138,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
136
138
|
callback(err, null);
|
|
137
139
|
return;
|
|
138
140
|
}
|
|
141
|
+
// eslint-disable-next-line security/detect-object-injection -- cacheKey is constructed from validated format and color
|
|
139
142
|
cachedEmptyResponses[cacheKey] = buffer;
|
|
140
143
|
callback(null, { data: buffer });
|
|
141
144
|
});
|
|
@@ -148,8 +151,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
148
151
|
/**
|
|
149
152
|
* Parses coordinate pair provided to pair of floats and ensures the resulting
|
|
150
153
|
* pair is a longitude/latitude combination depending on lnglat query parameter.
|
|
151
|
-
* @param {Array<string>}
|
|
152
|
-
* @param coordinates
|
|
154
|
+
* @param {Array<string>} coordinates Coordinate pair.
|
|
153
155
|
* @param {object} query Request query parameters.
|
|
154
156
|
* @returns {Array<number>|null} Parsed coordinate pair as [longitude, latitude] or null if invalid
|
|
155
157
|
*/
|
|
@@ -175,7 +177,7 @@ function parseCoordinatePair(coordinates, query) {
|
|
|
175
177
|
* Parses a coordinate pair from query arguments and optionally transforms it.
|
|
176
178
|
* @param {Array<string>} coordinatePair Coordinate pair.
|
|
177
179
|
* @param {object} query Request query parameters.
|
|
178
|
-
* @param {
|
|
180
|
+
* @param {((coords: Array<number>) => Array<number>)|null} transformer Optional transform function.
|
|
179
181
|
* @returns {Array<number>|null} Transformed coordinate pair or null if invalid.
|
|
180
182
|
*/
|
|
181
183
|
function parseCoordinates(coordinatePair, query, transformer) {
|
|
@@ -192,7 +194,7 @@ function parseCoordinates(coordinatePair, query, transformer) {
|
|
|
192
194
|
/**
|
|
193
195
|
* Parses paths provided via query into a list of path objects.
|
|
194
196
|
* @param {object} query Request query parameters.
|
|
195
|
-
* @param {
|
|
197
|
+
* @param {((coords: Array<number>) => Array<number>)|null} transformer Optional transform function.
|
|
196
198
|
* @returns {Array<Array<Array<number>>>} Array of paths.
|
|
197
199
|
*/
|
|
198
200
|
function extractPathsFromQuery(query, transformer) {
|
|
@@ -290,7 +292,7 @@ function parseMarkerOptions(optionsList, marker) {
|
|
|
290
292
|
* Parses markers provided via query into a list of marker objects.
|
|
291
293
|
* @param {object} query Request query parameters.
|
|
292
294
|
* @param {object} options Configuration options.
|
|
293
|
-
* @param {
|
|
295
|
+
* @param {((coords: Array<number>) => Array<number>)|null} transformer Optional transform function.
|
|
294
296
|
* @returns {Array<object>} An array of marker objects.
|
|
295
297
|
*/
|
|
296
298
|
function extractMarkersFromQuery(query, options, transformer) {
|
|
@@ -327,8 +329,7 @@ function extractMarkersFromQuery(query, options, transformer) {
|
|
|
327
329
|
let iconURI = markerParts[1];
|
|
328
330
|
// Check if icon is served via http otherwise marker icons are expected to
|
|
329
331
|
// be provided as filepaths relative to configured icon path
|
|
330
|
-
const isRemoteURL =
|
|
331
|
-
iconURI.startsWith('http://') || iconURI.startsWith('https://');
|
|
332
|
+
const isRemoteURL = isValidHttpUrl(iconURI);
|
|
332
333
|
const isDataURL = iconURI.startsWith('data:');
|
|
333
334
|
if (!(isRemoteURL || isDataURL)) {
|
|
334
335
|
// Sanitize URI with sanitize-filename
|
|
@@ -461,8 +462,10 @@ async function respondImage(
|
|
|
461
462
|
const tileMargin = Math.max(options.tileMargin || 0, 0);
|
|
462
463
|
let pool;
|
|
463
464
|
if (mode === 'tile' && tileMargin === 0) {
|
|
465
|
+
// eslint-disable-next-line security/detect-object-injection -- scale is validated by allowedScales
|
|
464
466
|
pool = item.map.renderers[scale];
|
|
465
467
|
} else {
|
|
468
|
+
// eslint-disable-next-line security/detect-object-injection -- scale is validated by allowedScales
|
|
466
469
|
pool = item.map.renderersStatic[scale];
|
|
467
470
|
}
|
|
468
471
|
|
|
@@ -564,8 +567,10 @@ async function respondImage(
|
|
|
564
567
|
'WARNING: The formatQuality option is deprecated and has been replaced with formatOptions. Please see the documentation. The values from formatQuality will be used if a quality setting is not provided via formatOptions.',
|
|
565
568
|
);
|
|
566
569
|
}
|
|
570
|
+
// eslint-disable-next-line security/detect-object-injection -- format is validated above
|
|
567
571
|
const formatQuality = formatQualities[format];
|
|
568
572
|
|
|
573
|
+
// eslint-disable-next-line security/detect-object-injection -- format is validated above
|
|
569
574
|
const formatOptions = (options.formatOptions || {})[format] || {};
|
|
570
575
|
|
|
571
576
|
if (format === 'png') {
|
|
@@ -615,9 +620,9 @@ async function respondImage(
|
|
|
615
620
|
* @param {string} req.params.scale - The scale parameter.
|
|
616
621
|
* @param {string} req.params.format - The format of the image.
|
|
617
622
|
* @param {object} res - Express response object.
|
|
618
|
-
* @param {
|
|
623
|
+
* @param {object} next - Express next middleware function.
|
|
619
624
|
* @param {number} maxScaleFactor - The maximum scale factor allowed.
|
|
620
|
-
* @param defailtTileSize
|
|
625
|
+
* @param {number} defailtTileSize - Default tile size.
|
|
621
626
|
* @returns {Promise<void>}
|
|
622
627
|
*/
|
|
623
628
|
async function handleTileRequest(
|
|
@@ -638,6 +643,7 @@ async function handleTileRequest(
|
|
|
638
643
|
scale: scaleParam,
|
|
639
644
|
format,
|
|
640
645
|
} = req.params;
|
|
646
|
+
// eslint-disable-next-line security/detect-object-injection -- id is route parameter, validated by Express
|
|
641
647
|
const item = repo[id];
|
|
642
648
|
if (!item) {
|
|
643
649
|
return res.sendStatus(404);
|
|
@@ -695,16 +701,15 @@ async function handleTileRequest(
|
|
|
695
701
|
* @param {object} options - Configuration options for the server.
|
|
696
702
|
* @param {object} repo - The repository object holding style data.
|
|
697
703
|
* @param {object} req - Express request object.
|
|
698
|
-
* @param {
|
|
704
|
+
* @param {string} req.params.id - The id of the style.
|
|
699
705
|
* @param {string} req.params.p2 - The raw or static parameter.
|
|
700
706
|
* @param {string} req.params.p3 - The staticType parameter.
|
|
701
|
-
* @param {string} req.params.p4 - The
|
|
702
|
-
* @param {string} req.params.p5 - The height parameter.
|
|
707
|
+
* @param {string} req.params.p4 - The widthAndHeight parameter.
|
|
703
708
|
* @param {string} req.params.scale - The scale parameter.
|
|
704
709
|
* @param {string} req.params.format - The format of the image.
|
|
705
|
-
* @param {
|
|
710
|
+
* @param {object} res - Express response object.
|
|
711
|
+
* @param {object} next - Express next middleware function.
|
|
706
712
|
* @param {number} maxScaleFactor - The maximum scale factor allowed.
|
|
707
|
-
* @param verbose
|
|
708
713
|
* @returns {Promise<void>}
|
|
709
714
|
*/
|
|
710
715
|
async function handleStaticRequest(
|
|
@@ -723,6 +728,7 @@ async function handleStaticRequest(
|
|
|
723
728
|
scale: scaleParam,
|
|
724
729
|
format,
|
|
725
730
|
} = req.params;
|
|
731
|
+
// eslint-disable-next-line security/detect-object-injection -- id is route parameter, validated by Express
|
|
726
732
|
const item = repo[id];
|
|
727
733
|
|
|
728
734
|
let parsedWidth = null;
|
|
@@ -918,6 +924,7 @@ export const serve_rendered = {
|
|
|
918
924
|
* Handles requests for tile images.
|
|
919
925
|
* @param {object} req - Express request object.
|
|
920
926
|
* @param {object} res - Express response object.
|
|
927
|
+
* @param {object} next - Express next middleware function.
|
|
921
928
|
* @param {string} req.params.id - The id of the style.
|
|
922
929
|
* @param {string} [req.params.p1] - The tile size or static parameter, if available.
|
|
923
930
|
* @param {string} req.params.p2 - The z, static, or raw parameter.
|
|
@@ -985,11 +992,13 @@ export const serve_rendered = {
|
|
|
985
992
|
* Handles requests for rendered tilejson endpoint.
|
|
986
993
|
* @param {object} req - Express request object.
|
|
987
994
|
* @param {object} res - Express response object.
|
|
995
|
+
* @param {object} next - Express next middleware function.
|
|
988
996
|
* @param {string} req.params.id - The id of the tilejson
|
|
989
997
|
* @param {string} [req.params.tileSize] - The size of the tile, if specified.
|
|
990
998
|
* @returns {void}
|
|
991
999
|
*/
|
|
992
1000
|
app.get('{/:tileSize}/:id.json', (req, res, next) => {
|
|
1001
|
+
// eslint-disable-next-line security/detect-object-injection -- req.params.id is route parameter, validated by Express
|
|
993
1002
|
const item = repo[req.params.id];
|
|
994
1003
|
if (!item) {
|
|
995
1004
|
return res.sendStatus(404);
|
|
@@ -1029,7 +1038,7 @@ export const serve_rendered = {
|
|
|
1029
1038
|
* @param {string} id ID of the item.
|
|
1030
1039
|
* @param {object} programOpts - An object containing the program options
|
|
1031
1040
|
* @param {object} style pre-fetched/read StyleJSON object.
|
|
1032
|
-
* @param {
|
|
1041
|
+
* @param {(dataId: string) => object} dataResolver Function to resolve data.
|
|
1033
1042
|
* @returns {Promise<void>}
|
|
1034
1043
|
*/
|
|
1035
1044
|
add: async function (
|
|
@@ -1063,8 +1072,8 @@ export const serve_rendered = {
|
|
|
1063
1072
|
/**
|
|
1064
1073
|
* Creates a renderer
|
|
1065
1074
|
* @param {number} ratio Pixel ratio
|
|
1066
|
-
* @param {
|
|
1067
|
-
*
|
|
1075
|
+
* @param {(err: Error|null, renderer: object) => void} createCallback Function that returns the renderer when created
|
|
1076
|
+
* @returns {void}
|
|
1068
1077
|
*/
|
|
1069
1078
|
const createRenderer = (ratio, createCallback) => {
|
|
1070
1079
|
const renderer = new mlgl.Map({
|
|
@@ -1072,10 +1081,11 @@ export const serve_rendered = {
|
|
|
1072
1081
|
ratio,
|
|
1073
1082
|
request: async (req, callback) => {
|
|
1074
1083
|
const protocol = req.url.split(':')[0];
|
|
1075
|
-
if (verbose) {
|
|
1084
|
+
if (verbose && verbose >= 3) {
|
|
1076
1085
|
console.log('Handling request:', req);
|
|
1077
1086
|
}
|
|
1078
1087
|
if (protocol === 'sprites') {
|
|
1088
|
+
// eslint-disable-next-line security/detect-object-injection -- protocol is 'sprites', validated above
|
|
1079
1089
|
const dir = options.paths[protocol];
|
|
1080
1090
|
const file = decodeURIComponent(req.url).substring(
|
|
1081
1091
|
protocol.length + 3,
|
|
@@ -1095,6 +1105,7 @@ export const serve_rendered = {
|
|
|
1095
1105
|
try {
|
|
1096
1106
|
const concatenated = await getFontsPbf(
|
|
1097
1107
|
null,
|
|
1108
|
+
// eslint-disable-next-line security/detect-object-injection -- protocol is 'fonts', validated above
|
|
1098
1109
|
options.paths[protocol],
|
|
1099
1110
|
fontstack,
|
|
1100
1111
|
range,
|
|
@@ -1107,8 +1118,11 @@ export const serve_rendered = {
|
|
|
1107
1118
|
} else if (protocol === 'mbtiles' || protocol === 'pmtiles') {
|
|
1108
1119
|
const parts = req.url.split('/');
|
|
1109
1120
|
const sourceId = parts[2];
|
|
1121
|
+
// eslint-disable-next-line security/detect-object-injection -- sourceId from internal style source names
|
|
1110
1122
|
const source = map.sources[sourceId];
|
|
1123
|
+
// eslint-disable-next-line security/detect-object-injection -- sourceId from internal style source names
|
|
1111
1124
|
const sourceType = map.sourceTypes[sourceId];
|
|
1125
|
+
// eslint-disable-next-line security/detect-object-injection -- sourceId from internal style source names
|
|
1112
1126
|
const sourceInfo = styleJSON.sources[sourceId];
|
|
1113
1127
|
|
|
1114
1128
|
const z = parts[3] | 0;
|
|
@@ -1223,6 +1237,7 @@ export const serve_rendered = {
|
|
|
1223
1237
|
|
|
1224
1238
|
const parts = url.parse(req.url);
|
|
1225
1239
|
const extension = path.extname(parts.pathname).toLowerCase();
|
|
1240
|
+
// eslint-disable-next-line security/detect-object-injection -- extension is from path.extname, limited set
|
|
1226
1241
|
const format = extensionToFormat[extension] || '';
|
|
1227
1242
|
createEmptyResponse(format, '', callback);
|
|
1228
1243
|
}
|
|
@@ -1231,6 +1246,7 @@ export const serve_rendered = {
|
|
|
1231
1246
|
const name = decodeURI(req.url).substring(protocol.length + 3);
|
|
1232
1247
|
const file = path.join(options.paths['files'], name);
|
|
1233
1248
|
if (await existsP(file)) {
|
|
1249
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- file path constructed from configured base path and URL-decoded filename
|
|
1234
1250
|
const inputFileStats = await fsp.stat(file);
|
|
1235
1251
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1236
1252
|
throw Error(
|
|
@@ -1274,7 +1290,8 @@ export const serve_rendered = {
|
|
|
1274
1290
|
styleJSON.sprite = [{ id: 'default', url: styleJSON.sprite }];
|
|
1275
1291
|
}
|
|
1276
1292
|
styleJSON.sprite.forEach((spriteItem) => {
|
|
1277
|
-
|
|
1293
|
+
// Sprites should only be HTTP/HTTPS, not S3
|
|
1294
|
+
if (!isValidHttpUrl(spriteItem.url)) {
|
|
1278
1295
|
spriteItem.url =
|
|
1279
1296
|
'sprites://' +
|
|
1280
1297
|
spriteItem.url
|
|
@@ -1290,7 +1307,8 @@ export const serve_rendered = {
|
|
|
1290
1307
|
});
|
|
1291
1308
|
}
|
|
1292
1309
|
|
|
1293
|
-
|
|
1310
|
+
// Glyphs should only be HTTP/HTTPS, not S3
|
|
1311
|
+
if (styleJSON.glyphs && !isValidHttpUrl(styleJSON.glyphs)) {
|
|
1294
1312
|
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
|
1295
1313
|
}
|
|
1296
1314
|
|
|
@@ -1339,6 +1357,7 @@ export const serve_rendered = {
|
|
|
1339
1357
|
`Note: This property will still work with MapLibre GL JS vector maps.`,
|
|
1340
1358
|
);
|
|
1341
1359
|
}
|
|
1360
|
+
// eslint-disable-next-line security/detect-object-injection -- prop is from hillshadePropertiesToRemove array, validated property names
|
|
1342
1361
|
delete layer.paint[prop];
|
|
1343
1362
|
}
|
|
1344
1363
|
}
|
|
@@ -1386,11 +1405,13 @@ export const serve_rendered = {
|
|
|
1386
1405
|
staticAttributionText:
|
|
1387
1406
|
params.staticAttributionText || options.staticAttributionText,
|
|
1388
1407
|
};
|
|
1408
|
+
// eslint-disable-next-line security/detect-object-injection -- id is from config file style names
|
|
1389
1409
|
repo[id] = repoobj;
|
|
1390
1410
|
|
|
1391
1411
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
1392
1412
|
let sourceType;
|
|
1393
1413
|
let sparse;
|
|
1414
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1394
1415
|
let source = styleJSON.sources[name];
|
|
1395
1416
|
let url = source.url;
|
|
1396
1417
|
if (
|
|
@@ -1405,23 +1426,32 @@ export const serve_rendered = {
|
|
|
1405
1426
|
dataId = dataId.slice(1, -1);
|
|
1406
1427
|
}
|
|
1407
1428
|
|
|
1429
|
+
// eslint-disable-next-line security/detect-object-injection -- dataId is from style source URL, used for mapping lookup
|
|
1408
1430
|
const mapsTo = (params.mapping || {})[dataId];
|
|
1409
1431
|
if (mapsTo) {
|
|
1410
1432
|
dataId = mapsTo;
|
|
1411
1433
|
}
|
|
1412
1434
|
|
|
1413
1435
|
let inputFile;
|
|
1436
|
+
let s3Profile;
|
|
1437
|
+
let requestPayer;
|
|
1438
|
+
let s3Region;
|
|
1414
1439
|
const dataInfo = dataResolver(dataId);
|
|
1415
1440
|
if (dataInfo.inputFile) {
|
|
1416
1441
|
inputFile = dataInfo.inputFile;
|
|
1417
1442
|
sourceType = dataInfo.fileType;
|
|
1418
1443
|
sparse = dataInfo.sparse;
|
|
1444
|
+
s3Profile = dataInfo.s3Profile;
|
|
1445
|
+
requestPayer = dataInfo.requestPayer;
|
|
1446
|
+
s3Region = dataInfo.s3Region;
|
|
1419
1447
|
} else {
|
|
1420
1448
|
console.error(`ERROR: data "${inputFile}" not found!`);
|
|
1421
1449
|
process.exit(1);
|
|
1422
1450
|
}
|
|
1423
1451
|
|
|
1424
|
-
|
|
1452
|
+
// PMTiles supports remote URLs (HTTP and S3), skip file check for those
|
|
1453
|
+
if (!isValidRemoteUrl(inputFile)) {
|
|
1454
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- inputFile is from dataResolver, which validates against config
|
|
1425
1455
|
const inputFileStats = await fsp.stat(inputFile);
|
|
1426
1456
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1427
1457
|
throw Error(`Not valid PMTiles file: "${inputFile}"`);
|
|
@@ -1429,9 +1459,18 @@ export const serve_rendered = {
|
|
|
1429
1459
|
}
|
|
1430
1460
|
|
|
1431
1461
|
if (sourceType === 'pmtiles') {
|
|
1432
|
-
|
|
1462
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1463
|
+
map.sources[name] = openPMtiles(
|
|
1464
|
+
inputFile,
|
|
1465
|
+
s3Profile,
|
|
1466
|
+
requestPayer,
|
|
1467
|
+
s3Region,
|
|
1468
|
+
verbose,
|
|
1469
|
+
);
|
|
1470
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1433
1471
|
map.sourceTypes[name] = 'pmtiles';
|
|
1434
|
-
|
|
1472
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1473
|
+
const metadata = await getPMtilesInfo(map.sources[name], inputFile);
|
|
1435
1474
|
|
|
1436
1475
|
if (!repoobj.dataProjWGStoInternalWGS && metadata.proj4) {
|
|
1437
1476
|
// how to do this for multiple sources with different proj4 defs?
|
|
@@ -1464,13 +1503,17 @@ export const serve_rendered = {
|
|
|
1464
1503
|
}
|
|
1465
1504
|
}
|
|
1466
1505
|
} else {
|
|
1506
|
+
// MBTiles does not support remote URLs
|
|
1507
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- inputFile is from dataResolver, which validates against config
|
|
1467
1508
|
const inputFileStats = await fsp.stat(inputFile);
|
|
1468
1509
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1469
1510
|
throw Error(`Not valid MBTiles file: "${inputFile}"`);
|
|
1470
1511
|
}
|
|
1471
1512
|
const mbw = await openMbTilesWrapper(inputFile);
|
|
1472
1513
|
const info = await mbw.getInfo();
|
|
1514
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1473
1515
|
map.sources[name] = mbw.getMbTiles();
|
|
1516
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from style sources object keys
|
|
1474
1517
|
map.sourceTypes[name] = 'mbtiles';
|
|
1475
1518
|
|
|
1476
1519
|
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
@@ -1517,9 +1560,13 @@ export const serve_rendered = {
|
|
|
1517
1560
|
for (let s = 1; s <= maxScaleFactor; s++) {
|
|
1518
1561
|
const i = Math.min(minPoolSizes.length - 1, s - 1);
|
|
1519
1562
|
const j = Math.min(maxPoolSizes.length - 1, s - 1);
|
|
1563
|
+
// eslint-disable-next-line security/detect-object-injection -- i and j are calculated indices bounded by array length
|
|
1520
1564
|
const minPoolSize = minPoolSizes[i];
|
|
1565
|
+
// eslint-disable-next-line security/detect-object-injection -- i and j are calculated indices bounded by array length
|
|
1521
1566
|
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
|
1567
|
+
// eslint-disable-next-line security/detect-object-injection -- s is loop counter from 1 to maxScaleFactor
|
|
1522
1568
|
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
|
1569
|
+
// eslint-disable-next-line security/detect-object-injection -- s is loop counter from 1 to maxScaleFactor
|
|
1523
1570
|
map.renderersStatic[s] = createPool(
|
|
1524
1571
|
s,
|
|
1525
1572
|
'static',
|
|
@@ -1535,6 +1582,7 @@ export const serve_rendered = {
|
|
|
1535
1582
|
* @returns {void}
|
|
1536
1583
|
*/
|
|
1537
1584
|
remove: function (repo, id) {
|
|
1585
|
+
// eslint-disable-next-line security/detect-object-injection -- id is function parameter for removal
|
|
1538
1586
|
const item = repo[id];
|
|
1539
1587
|
if (item) {
|
|
1540
1588
|
item.map.renderers.forEach((pool) => {
|
|
@@ -1544,6 +1592,7 @@ export const serve_rendered = {
|
|
|
1544
1592
|
pool.close();
|
|
1545
1593
|
});
|
|
1546
1594
|
}
|
|
1595
|
+
// eslint-disable-next-line security/detect-object-injection -- id is function parameter for removal
|
|
1547
1596
|
delete repo[id];
|
|
1548
1597
|
},
|
|
1549
1598
|
/**
|
|
@@ -1553,6 +1602,7 @@ export const serve_rendered = {
|
|
|
1553
1602
|
*/
|
|
1554
1603
|
clear: function (repo) {
|
|
1555
1604
|
Object.keys(repo).forEach((id) => {
|
|
1605
|
+
// eslint-disable-next-line security/detect-object-injection -- id is from Object.keys() iteration
|
|
1556
1606
|
const item = repo[id];
|
|
1557
1607
|
if (item) {
|
|
1558
1608
|
item.map.renderers.forEach((pool) => {
|
|
@@ -1562,14 +1612,15 @@ export const serve_rendered = {
|
|
|
1562
1612
|
pool.close();
|
|
1563
1613
|
});
|
|
1564
1614
|
}
|
|
1615
|
+
// eslint-disable-next-line security/detect-object-injection -- id is from Object.keys() iteration
|
|
1565
1616
|
delete repo[id];
|
|
1566
1617
|
});
|
|
1567
1618
|
},
|
|
1568
1619
|
/**
|
|
1569
1620
|
* Get the elevation of terrain tile data by rendering it to a canvas image
|
|
1570
|
-
* @param {
|
|
1621
|
+
* @param {Buffer} data The terrain tile data buffer.
|
|
1571
1622
|
* @param {object} param Required parameters (coordinates e.g.)
|
|
1572
|
-
* @returns {object}
|
|
1623
|
+
* @returns {Promise<object>} Promise resolving to elevation data
|
|
1573
1624
|
*/
|
|
1574
1625
|
getTerrainElevation: async function (data, param) {
|
|
1575
1626
|
return await new Promise(async (resolve, reject) => {
|
package/src/serve_style.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
4
|
|
|
6
5
|
import clone from 'clone';
|
|
7
6
|
import express from 'express';
|
|
@@ -12,10 +11,9 @@ import {
|
|
|
12
11
|
allowedSpriteFormats,
|
|
13
12
|
fixUrl,
|
|
14
13
|
readFile,
|
|
14
|
+
isValidHttpUrl,
|
|
15
15
|
} from './utils.js';
|
|
16
16
|
|
|
17
|
-
const httpTester = /^https?:\/\//i;
|
|
18
|
-
|
|
19
17
|
export const serve_style = {
|
|
20
18
|
/**
|
|
21
19
|
* Initializes the serve_style module.
|
|
@@ -44,12 +42,14 @@ export const serve_style = {
|
|
|
44
42
|
);
|
|
45
43
|
}
|
|
46
44
|
try {
|
|
45
|
+
// eslint-disable-next-line security/detect-object-injection -- id is route parameter from URL
|
|
47
46
|
const item = repo[id];
|
|
48
47
|
if (!item) {
|
|
49
48
|
return res.sendStatus(404);
|
|
50
49
|
}
|
|
51
50
|
const styleJSON_ = clone(item.styleJSON);
|
|
52
51
|
for (const name of Object.keys(styleJSON_.sources)) {
|
|
52
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from Object.keys of style sources
|
|
53
53
|
const source = styleJSON_.sources[name];
|
|
54
54
|
source.url = fixUrl(req, source.url, item.publicUrl);
|
|
55
55
|
if (typeof source.data == 'string') {
|
|
@@ -104,6 +104,7 @@ export const serve_style = {
|
|
|
104
104
|
sanitizedFormat,
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
|
+
// eslint-disable-next-line security/detect-object-injection -- id is route parameter from URL
|
|
107
108
|
const item = repo[id];
|
|
108
109
|
const validatedFormat = allowedSpriteFormats(format);
|
|
109
110
|
if (!item || !validatedFormat) {
|
|
@@ -187,6 +188,7 @@ export const serve_style = {
|
|
|
187
188
|
* @returns {void}
|
|
188
189
|
*/
|
|
189
190
|
remove: function (repo, id) {
|
|
191
|
+
// eslint-disable-next-line security/detect-object-injection -- id is function parameter for removal
|
|
190
192
|
delete repo[id];
|
|
191
193
|
},
|
|
192
194
|
/**
|
|
@@ -197,8 +199,8 @@ export const serve_style = {
|
|
|
197
199
|
* @param {string} id ID of the style.
|
|
198
200
|
* @param {object} programOpts - An object containing the program options
|
|
199
201
|
* @param {object} style pre-fetched/read StyleJSON object.
|
|
200
|
-
* @param {
|
|
201
|
-
* @param {
|
|
202
|
+
* @param {(dataId: string, protocol: string) => string|undefined} reportTiles Function for reporting tile sources.
|
|
203
|
+
* @param {(font: string) => void} reportFont Function for reporting font usage
|
|
202
204
|
* @returns {boolean} true if add is successful
|
|
203
205
|
*/
|
|
204
206
|
add: function (
|
|
@@ -225,6 +227,7 @@ export const serve_style = {
|
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
230
|
+
// eslint-disable-next-line security/detect-object-injection -- name is from Object.keys of style sources
|
|
228
231
|
const source = styleJSON.sources[name];
|
|
229
232
|
let url = source.url;
|
|
230
233
|
if (
|
|
@@ -238,6 +241,7 @@ export const serve_style = {
|
|
|
238
241
|
dataId = dataId.slice(1, -1);
|
|
239
242
|
}
|
|
240
243
|
|
|
244
|
+
// eslint-disable-next-line security/detect-object-injection -- dataId is from style source URL, used for mapping lookup
|
|
241
245
|
const mapsTo = (params.mapping || {})[dataId];
|
|
242
246
|
if (mapsTo) {
|
|
243
247
|
dataId = mapsTo;
|
|
@@ -276,7 +280,7 @@ export const serve_style = {
|
|
|
276
280
|
let spritePaths = [];
|
|
277
281
|
if (styleJSON.sprite) {
|
|
278
282
|
if (!Array.isArray(styleJSON.sprite)) {
|
|
279
|
-
if (!
|
|
283
|
+
if (!isValidHttpUrl(styleJSON.sprite)) {
|
|
280
284
|
let spritePath = path.join(
|
|
281
285
|
options.paths.sprites,
|
|
282
286
|
styleJSON.sprite
|
|
@@ -291,7 +295,7 @@ export const serve_style = {
|
|
|
291
295
|
}
|
|
292
296
|
} else {
|
|
293
297
|
for (let spriteItem of styleJSON.sprite) {
|
|
294
|
-
if (!
|
|
298
|
+
if (!isValidHttpUrl(spriteItem.url)) {
|
|
295
299
|
let spritePath = path.join(
|
|
296
300
|
options.paths.sprites,
|
|
297
301
|
spriteItem.url
|
|
@@ -308,10 +312,11 @@ export const serve_style = {
|
|
|
308
312
|
}
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
if (styleJSON.glyphs && !
|
|
315
|
+
if (styleJSON.glyphs && !isValidHttpUrl(styleJSON.glyphs)) {
|
|
312
316
|
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
|
313
317
|
}
|
|
314
318
|
|
|
319
|
+
// eslint-disable-next-line security/detect-object-injection -- id is from config file style names
|
|
315
320
|
repo[id] = {
|
|
316
321
|
styleJSON,
|
|
317
322
|
spritePaths,
|