Skip to content

Commit

Permalink
Canonicalize Mapbox tile URLs from inline TileJSON as well (#9217)
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaefer authored Jan 28, 2020
1 parent 99bfc7f commit 77cfc5c
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 74 deletions.
5 changes: 1 addition & 4 deletions src/source/load_tilejson.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ export default function(options: any, requestManager: RequestManager, callback:
result.vectorLayerIds = result.vectorLayers.map((layer) => { return layer.id; });
}

// only canonicalize tile tileset if source is declared using a tilejson url
if (options.url) {
result.tiles = requestManager.canonicalizeTileset(result, options.url);
}
result.tiles = requestManager.canonicalizeTileset(result, options.url);
callback(null, result);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/source/raster_dem_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class RasterDEMTileSource extends RasterTileSource implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.url, this.tileSize);
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.tileSize);
tile.request = getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), imageLoaded.bind(this));

tile.neighboringTiles = this._getNeighboringTiles(tile.tileID);
Expand Down
2 changes: 1 addition & 1 deletion src/source/raster_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class RasterTileSource extends Evented implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.url, this.tileSize);
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.tileSize);
tile.request = getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), (err, img) => {
delete tile.request;

Expand Down
2 changes: 1 addition & 1 deletion src/source/vector_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class VectorTileSource extends Evented implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.url, null);
const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme));
const params = {
request: this.map._requestManager.transformRequest(url, ResourceType.Tile),
uid: tile.uid,
Expand Down
39 changes: 28 additions & 11 deletions src/util/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ export class RequestManager {
return this._makeAPIURL(urlObject, this._customAccessToken || accessToken);
}

normalizeTileURL(tileURL: string, sourceURL?: ?string, tileSize?: ?number): string {
normalizeTileURL(tileURL: string, tileSize?: ?number): string {
if (this._isSkuTokenExpired()) {
this._createSkuToken();
}

if (!sourceURL || !isMapboxURL(sourceURL)) return tileURL;
if (tileURL && !isMapboxURL(tileURL) && !isMapboxHTTPURL(tileURL)) return tileURL;

const urlObject = parseUrl(tileURL);
const imageExtensionRe = /(\.(png|jpg)\d*)(?=$)/;
Expand All @@ -121,14 +121,15 @@ export class RequestManager {
urlObject.path = urlObject.path.replace(tileURLAPIPrefixRe, '/');
urlObject.path = `/v4${urlObject.path}`;

if (config.REQUIRE_ACCESS_TOKEN && (config.ACCESS_TOKEN || this._customAccessToken) && this._skuToken) {
const accessToken = this._customAccessToken || getAccessToken(urlObject.params) || config.ACCESS_TOKEN;
if (config.REQUIRE_ACCESS_TOKEN && accessToken && this._skuToken) {
urlObject.params.push(`sku=${this._skuToken}`);
}

return this._makeAPIURL(urlObject, this._customAccessToken);
return this._makeAPIURL(urlObject, accessToken);
}

canonicalizeTileURL(url: string) {
canonicalizeTileURL(url: string, removeAccessToken: boolean) {
const version = "/v4/";
// matches any file extension specified by a dot and one or more alphanumeric characters
const extensionRe = /\.[\w]+$/;
Expand All @@ -145,17 +146,23 @@ export class RequestManager {
result += urlObject.path.replace(version, '');

// Append the query string, minus the access token parameter.
const params = urlObject.params.filter(p => !p.match(/^access_token=/));
let params = urlObject.params;
if (removeAccessToken) {
params = params.filter(p => !p.match(/^access_token=/));
}
if (params.length) result += `?${params.join('&')}`;
return result;
}

canonicalizeTileset(tileJSON: TileJSON, sourceURL: string) {
if (!isMapboxURL(sourceURL)) return tileJSON.tiles || [];
canonicalizeTileset(tileJSON: TileJSON, sourceURL?: string) {
const removeAccessToken = sourceURL ? isMapboxURL(sourceURL) : false;
const canonical = [];
for (const url of tileJSON.tiles) {
const canonicalUrl = this.canonicalizeTileURL(url);
canonical.push(canonicalUrl);
for (const url of tileJSON.tiles || []) {
if (isMapboxHTTPURL(url)) {
canonical.push(this.canonicalizeTileURL(url, removeAccessToken));
} else {
canonical.push(url);
}
}
return canonical;
}
Expand Down Expand Up @@ -197,6 +204,16 @@ function hasCacheDefeatingSku(url: string) {
return url.indexOf('sku=') > 0 && isMapboxHTTPURL(url);
}

function getAccessToken(params: Array<string>): string | null {
for (const param of params) {
const match = param.match(/^access_token=(.*)$/);
if (match) {
return match[1];
}
}
return null;
}

const urlRe = /^(\w+):\/\/([^/?]*)(\/[^?]+)?\??(.+)?/;

function parseUrl(url: string): UrlObject {
Expand Down
27 changes: 27 additions & 0 deletions test/unit/source/vector_tile_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,33 @@ test('VectorTileSource', (t) => {
window.server.respond();
});

t.test('canonicalizes tile URLs in inline TileJSON', (t) => {
const source = createSource({
minzoom: 1,
maxzoom: 10,
attribution: "Mapbox",
tiles: ["https://api.mapbox.com/v4/user.map/{z}/{x}/{y}.png?access_token=key"]
});
const transformSpy = t.spy(source.map._requestManager, 'transformRequest');
source.on('data', (e) => {
if (e.sourceDataType === 'metadata') {
t.deepEqual(source.tiles, ["mapbox://tiles/user.map/{z}/{x}/{y}.png?access_token=key"]);
const tile = {
tileID: new OverscaledTileID(10, 0, 10, 5, 5),
state: 'loading',
loadVectorData () {},
setExpiryData() {}
};
source.loadTile(tile, () => {});
t.ok(transformSpy.calledOnce);
t.equal(transformSpy.getCall(0).args[0], `https://api.mapbox.com/v4/user.map/10/5/5.png?sku=${source.map._requestManager._skuToken}&access_token=key`);
t.equal(transformSpy.getCall(0).args[1], 'Tile');
t.end();
}
});

});

t.test('reloads a loading tile properly', (t) => {
const source = createSource({
tiles: ["http://example.com/{z}/{x}/{y}.png"]
Expand Down
5 changes: 4 additions & 1 deletion test/unit/ui/control/logo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ function createMap(t, logoPosition, logoRequired) {
function createSource(options, logoRequired) {
const source = new VectorTileSource('id', options, {send () {}});
source.onAdd({
_requestManager: {_skuToken: '1234567890123'},
_requestManager: {
_skuToken: '1234567890123',
canonicalizeTileset: tileJSON => tileJSON.tiles
},
transform: {angle: 0, pitch: 0, showCollisionBoxes: false},
_getMapId: () => 1
});
Expand Down
Loading

0 comments on commit 77cfc5c

Please sign in to comment.