|
1 | | -const escape = require('escape-html'), |
2 | | - geojsonRandom = require('geojson-random'), |
| 1 | +const geojsonRandom = require('geojson-random'), |
3 | 2 | geojsonExtent = require('@mapbox/geojson-extent'), |
4 | 3 | geojsonFlatten = require('geojson-flatten'), |
5 | 4 | polyline = require('@mapbox/polyline'), |
6 | 5 | wkx = require('wkx'), |
7 | 6 | Buffer = require('buffer/').Buffer, |
8 | 7 | zoomextent = require('../lib/zoomextent'); |
9 | 8 |
|
10 | | -module.exports.adduserlayer = function (context, _url, _name) { |
11 | | - const url = escape(_url), |
12 | | - name = escape(_name); |
| 9 | +function isValidTileUrl(url) { |
| 10 | + try { |
| 11 | + const u = new URL(url); |
| 12 | + |
| 13 | + // Must use HTTPS |
| 14 | + if (u.protocol !== 'https:') return false; |
13 | 15 |
|
14 | | - // reset the control if a user-layer was added before |
15 | | - d3.select('.user-layer-button').remove(); |
| 16 | + // Optional: check that {z}, {x}, {y} placeholders are present |
| 17 | + const hasPlaceholders = |
| 18 | + url.includes('{z}') && url.includes('{x}') && url.includes('{y}'); |
| 19 | + if (!hasPlaceholders) return false; |
16 | 20 |
|
| 21 | + return true; |
| 22 | + } catch (e) { |
| 23 | + // Invalid URL format |
| 24 | + return false; |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +function isValidTilesetName(name) { |
| 29 | + const trimmed = name.trim(); |
| 30 | + return ( |
| 31 | + /^[a-zA-Z0-9 ]+$/.test(trimmed) && |
| 32 | + trimmed.length >= 3 && |
| 33 | + trimmed.length <= 25 |
| 34 | + ); |
| 35 | +} |
| 36 | + |
| 37 | +module.exports.adduserlayer = function (context, url, name) { |
17 | 38 | function addUserSourceAndLayer() { |
18 | 39 | // if the source and layer aren't present, add them |
19 | 40 | context.map.setStyle({ |
@@ -46,14 +67,33 @@ module.exports.adduserlayer = function (context, _url, _name) { |
46 | 67 | }); |
47 | 68 | } |
48 | 69 |
|
49 | | - // append a button to the existing style selection UI |
50 | | - d3.select('.layer-switch') |
51 | | - .append('button') |
52 | | - .attr('class', 'pad0x user-layer-button') |
53 | | - .on('click', addUserSourceAndLayer) |
54 | | - .text(name); |
55 | | - |
56 | | - addUserSourceAndLayer(); |
| 70 | + try { |
| 71 | + if (!isValidTileUrl(url)) { |
| 72 | + throw new Error( |
| 73 | + 'Invalid tile URL. Must be HTTPS and include {z}, {x}, {y}.' |
| 74 | + ); |
| 75 | + } |
| 76 | + |
| 77 | + if (!isValidTilesetName(name)) { |
| 78 | + throw new Error( |
| 79 | + 'Invalid tileset name. Must be 3-25 characters long and contain only alphanumeric characters and spaces.' |
| 80 | + ); |
| 81 | + } |
| 82 | + |
| 83 | + // reset the control if a user-layer was added before |
| 84 | + d3.select('.user-layer-button').remove(); |
| 85 | + |
| 86 | + // append a button to the existing style selection UI |
| 87 | + d3.select('.layer-switch') |
| 88 | + .append('button') |
| 89 | + .attr('class', 'pad0x user-layer-button') |
| 90 | + .on('click', addUserSourceAndLayer) |
| 91 | + .text(name); |
| 92 | + |
| 93 | + addUserSourceAndLayer(url); |
| 94 | + } catch (e) { |
| 95 | + alert(e.message); |
| 96 | + } |
57 | 97 | }; |
58 | 98 |
|
59 | 99 | module.exports.zoomextent = function (context) { |
|
0 commit comments