Add readFile function

This commit is contained in:
acalcutt 2025-01-05 01:18:01 -05:00
parent 3fedd5bb77
commit 1f693003ed
2 changed files with 164 additions and 152 deletions

View file

@ -7,7 +7,7 @@ import clone from 'clone';
import express from 'express'; import express from 'express';
import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec'; import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec';
import { fixUrl, allowedOptions } from './utils.js'; import { fixUrl, allowedOptions, readFile } from './utils.js';
const httpTester = /^https?:\/\//i; const httpTester = /^https?:\/\//i;
const allowedSpriteFormats = allowedOptions(['png', 'json']); const allowedSpriteFormats = allowedOptions(['png', 'json']);
@ -101,7 +101,9 @@ export const serve_style = {
* @param {string} req.params.format - Format of the sprite file, 'png' or 'json'. * @param {string} req.params.format - Format of the sprite file, 'png' or 'json'.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
app.get(`/:id/sprite{/:spriteID}{@:scale}{.:format}`, (req, res, next) => { app.get(
`/:id/sprite{/:spriteID}{@:scale}{.:format}`,
async (req, res, next) => {
const { spriteID = 'default', id, format, scale } = req.params; const { spriteID = 'default', id, format, scale } = req.params;
const sanitizedId = String(id).replace(/\n|\r/g, ''); const sanitizedId = String(id).replace(/\n|\r/g, '');
const sanitizedScale = scale ? String(scale).replace(/\n|\r/g, '') : ''; const sanitizedScale = scale ? String(scale).replace(/\n|\r/g, '') : '';
@ -131,7 +133,9 @@ export const serve_style = {
); );
return res.sendStatus(404); return res.sendStatus(404);
} }
const sprite = item.spritePaths.find((sprite) => sprite.id === spriteID); const sprite = item.spritePaths.find(
(sprite) => sprite.id === spriteID,
);
const spriteScale = allowedSpriteScales(scale); const spriteScale = allowedSpriteScales(scale);
if (!sprite || spriteScale === null) { if (!sprite || spriteScale === null) {
if (verbose) if (verbose)
@ -159,18 +163,8 @@ export const serve_style = {
const sanitizedSpritePath = sprite.path.replace(/^(\.\.\/)+/, ''); const sanitizedSpritePath = sprite.path.replace(/^(\.\.\/)+/, '');
const filename = `${sanitizedSpritePath}${spriteScale}.${validatedFormat}`; const filename = `${sanitizedSpritePath}${spriteScale}.${validatedFormat}`;
if (verbose) console.log(`Loading sprite from: %s`, filename); if (verbose) console.log(`Loading sprite from: %s`, filename);
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename const data = await readFile(filename);
fs.readFile(filename, (err, data) => {
if (err) {
if (verbose)
console.error(
'Sprite load error: %s, Error: %s',
filename,
String(err),
);
return res.sendStatus(404);
}
if (validatedFormat === 'json') { if (validatedFormat === 'json') {
res.header('Content-type', 'application/json'); res.header('Content-type', 'application/json');
@ -187,8 +181,18 @@ export const serve_style = {
); );
res.set({ 'Last-Modified': item.lastModified }); res.set({ 'Last-Modified': item.lastModified });
return res.send(data); return res.send(data);
}); } catch (err) {
}); if (verbose) {
console.error(
'Sprite load error: %s, Error: %s',
filename,
String(err),
);
}
return res.sendStatus(404);
}
},
);
return app; return app;
}, },

View file

@ -185,6 +185,24 @@ export function fixTileJSONCenter(tileJSON) {
} }
} }
/**
* Reads a file and returns a Promise with the file data.
* @param {string} filename - Path to the file to read.
* @returns {Promise<Buffer>} - A Promise that resolves with the file data as a Buffer or rejects with an error.
*/
export function readFile(filename) {
return new Promise((resolve, reject) => {
// eslint-disable-next-line security/detect-non-literal-fs-filename
fs.readFile(filename, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
/** /**
* Retrieves font data for a given font and range. * Retrieves font data for a given font and range.
* @param {object} allowedFonts - An object of allowed fonts. * @param {object} allowedFonts - An object of allowed fonts.
@ -194,22 +212,16 @@ export function fixTileJSONCenter(tileJSON) {
* @param {object} [fallbacks] - Optional fallback font list. * @param {object} [fallbacks] - Optional fallback font list.
* @returns {Promise<Buffer>} A promise that resolves with the font data Buffer or rejects with an error. * @returns {Promise<Buffer>} A promise that resolves with the font data Buffer or rejects with an error.
*/ */
function getFontPbf(allowedFonts, fontPath, name, range, fallbacks) { async function getFontPbf(allowedFonts, fontPath, name, range, fallbacks) {
return new Promise((resolve, reject) => {
if (!allowedFonts || (allowedFonts[name] && fallbacks)) { if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
const fontMatch = name?.match(/^[\w\s-]+$/); const fontMatch = name?.match(/^[\w\s-]+$/);
const sanitizedName = fontMatch?.[0] || 'invalid'; const sanitizedName = fontMatch?.[0] || 'invalid';
if ( if (!name || typeof name !== 'string' || name.trim() === '' || !fontMatch) {
!name ||
typeof name !== 'string' ||
name.trim() === '' ||
!fontMatch
) {
console.error( console.error(
'ERROR: Invalid font name: %s', 'ERROR: Invalid font name: %s',
sanitizedName.replace(/\n|\r/g, ''), sanitizedName.replace(/\n|\r/g, ''),
); );
return reject('Invalid font name'); throw new Error('Invalid font name');
} }
const rangeMatch = range?.match(/^[\d-]+$/); const rangeMatch = range?.match(/^[\d-]+$/);
@ -219,20 +231,23 @@ function getFontPbf(allowedFonts, fontPath, name, range, fallbacks) {
'ERROR: Invalid range: %s', 'ERROR: Invalid range: %s',
sanitizedRange.replace(/\n|\r/g, ''), sanitizedRange.replace(/\n|\r/g, ''),
); );
return reject('Invalid range'); throw new Error('Invalid range');
} }
const filename = path.join( const filename = path.join(
fontPath, fontPath,
sanitizedName, sanitizedName,
`${sanitizedRange}.pbf`, `${sanitizedRange}.pbf`,
); );
if (!fallbacks) { if (!fallbacks) {
fallbacks = clone(allowedFonts || {}); fallbacks = clone(allowedFonts || {});
} }
delete fallbacks[name]; delete fallbacks[name];
// eslint-disable-next-line security/detect-non-literal-fs-filename
fs.readFile(filename, (err, data) => { try {
if (err) { const data = await readFile(filename);
return data;
} catch (err) {
console.error( console.error(
'ERROR: Font not found: %s, Error: %s', 'ERROR: Font not found: %s, Error: %s',
filename.replace(/\n|\r/g, ''), filename.replace(/\n|\r/g, ''),
@ -258,21 +273,14 @@ function getFontPbf(allowedFonts, fontPath, name, range, fallbacks) {
sanitizedName, sanitizedName,
); );
delete fallbacks[fallbackName]; delete fallbacks[fallbackName];
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then( return getFontPbf(null, fontPath, fallbackName, range, fallbacks);
resolve,
reject,
);
} else { } else {
reject('Font load error'); throw new Error('Font load error');
}
} }
} else { } else {
resolve(data); throw new Error('Font not allowed');
} }
});
} else {
reject('Font not allowed');
}
});
} }
/** /**
* Combines multiple font pbf buffers into one. * Combines multiple font pbf buffers into one.