Skip to content

Commit 259578c

Browse files
authored
Merge pull request #922 from mapbox/cw/sanitize-raster-url
sanitize user-added raster tile template
2 parents 99dec06 + c2802b8 commit 259578c

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

src/lib/meta.js

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
1-
const escape = require('escape-html'),
2-
geojsonRandom = require('geojson-random'),
1+
const geojsonRandom = require('geojson-random'),
32
geojsonExtent = require('@mapbox/geojson-extent'),
43
geojsonFlatten = require('geojson-flatten'),
54
polyline = require('@mapbox/polyline'),
65
wkx = require('wkx'),
76
Buffer = require('buffer/').Buffer,
87
zoomextent = require('../lib/zoomextent');
98

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;
1315

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;
1620

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) {
1738
function addUserSourceAndLayer() {
1839
// if the source and layer aren't present, add them
1940
context.map.setStyle({
@@ -46,14 +67,33 @@ module.exports.adduserlayer = function (context, _url, _name) {
4667
});
4768
}
4869

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+
}
5797
};
5898

5999
module.exports.zoomextent = function (context) {

src/ui/file_bar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ module.exports = function fileBar(context) {
7878
alt: 'Add a custom tile layer',
7979
action: function () {
8080
const layerURL = prompt(
81-
'Layer URL\ne.g. https://stamen-tiles-b.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg'
81+
'Layer URL\ne.g. https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg'
8282
);
8383
if (layerURL === null) return;
8484
const layerName = prompt('Layer name');

0 commit comments

Comments
 (0)