workadventure/back/src/Services/MapFetcher.ts
David Négrier dbd5b80636 Adding support for "readableBy" and "writableBy" in back
This means that we are now loading maps from server side.
2021-07-19 10:16:43 +02:00

65 lines
2.4 KiB
TypeScript

import Axios from "axios";
import ipaddr from 'ipaddr.js';
import { Resolver } from 'dns';
import { promisify } from 'util';
import {LocalUrlError} from "./LocalUrlError";
import {ITiledMap} from "@workadventure/tiled-map-type-guard";
import {isTiledMap} from "@workadventure/tiled-map-type-guard/dist";
class MapFetcher {
async fetchMap(mapUrl: string): Promise<ITiledMap> {
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
if (await this.isLocalUrl(mapUrl)) {
throw new LocalUrlError('URL for map "'+mapUrl+'" targets a local map');
}
// Note: mapUrl is provided by the client. A possible attack vector would be to use a rogue DNS server that
// returns local URLs. Alas, Axios cannot pin a URL to a given IP. So "isLocalUrl" and Axios.get could potentially
// target to different servers (and one could trick Axios.get into loading resources on the internal network
// despite isLocalUrl checking that.
// We can deem this problem not that important because:
// - We make sure we are only passing "GET" requests
// - The result of the query is never displayed to the end user
const res = await Axios.get(mapUrl, {
maxContentLength: 50*1024*1024, // Max content length: 50MB. Maps should not be bigger
});
if (!isTiledMap(res.data)) {
throw new Error('Invalid map format for map '+mapUrl);
}
return res.data;
}
/**
* Returns true if the domain name is localhost of *.localhost
* Returns true if the domain name resolves to an IP address that is "private" (like 10.x.x.x or 192.168.x.x)
*/
private async isLocalUrl(url: string): Promise<boolean> {
const urlObj = new URL(url);
if (urlObj.hostname === 'localhost' || urlObj.hostname.endsWith('.localhost')) {
return true;
}
let addresses = [];
if (!ipaddr.isValid(urlObj.hostname)) {
const resolver = new Resolver();
addresses = await promisify(resolver.resolve)(urlObj.hostname);
} else {
addresses = [urlObj.hostname];
}
for (const address of addresses) {
const addr = ipaddr.parse(address);
if (addr.range() !== 'unicast') {
return true;
}
}
return false;
}
}
export const mapFetcher = new MapFetcher();