tileserver-gl-light 5.4.1-pre.0 → 5.5.0-pre.0

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/src/serve_data.js CHANGED
@@ -12,7 +12,8 @@ import { SphericalMercator } from '@mapbox/sphericalmercator';
12
12
  import {
13
13
  fixTileJSONCenter,
14
14
  getTileUrls,
15
- isValidHttpUrl,
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
- if (isValidHttpUrl(params.pmtiles)) {
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
- if (isValidHttpUrl(params.mbtiles)) {
355
+ // MBTiles does not support remote URLs
356
+ if (isValidRemoteUrl(params.mbtiles)) {
351
357
  console.log(
352
- `ERROR: MBTiles does not support web based files. "${params.mbtiles}" is not a valid data file.`,
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
- if (!isValidHttpUrl(inputFile)) {
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(inputFile);
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,
@@ -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 {Function} callback The mlgl callback.
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>} coordinatePair Coordinate pair.
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 {Function} transformer Optional transform function.
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 {Function} transformer Optional transform function.
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 {Function} transformer Optional transform function.
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 {Function} next - Express next middleware function.
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 {object} res - Express response object.
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 width parameter.
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 {Function} next - Express next middleware function.
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 {Function} dataResolver Function to resolve data.
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 {Function} createCallback Function that returns the renderer when created
1067
- * @returns {void}
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
- if (!httpTester.test(spriteItem.url)) {
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
- if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
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
- if (!isValidHttpUrl(inputFile)) {
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
- map.sources[name] = openPMtiles(inputFile);
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
- const metadata = await getPMtilesInfo(map.sources[name]);
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 {object} data The background color (or empty string for transparent).
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) => {
@@ -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 {Function} reportTiles Function for reporting tile sources.
201
- * @param {Function} reportFont Function for reporting font usage
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 (!httpTester.test(styleJSON.sprite)) {
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 (!httpTester.test(spriteItem.url)) {
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 && !httpTester.test(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,