diff --git a/src/cdn/funcs/handleResponse.ts b/src/cdn/funcs/handleResponse.ts new file mode 100644 index 0000000..9f667b8 --- /dev/null +++ b/src/cdn/funcs/handleResponse.ts @@ -0,0 +1,48 @@ +import * as http from 'http'; + +/** Too avoid */ +const MAX_MEMORY_SIZE = 100 * 1024 * 1024; + +export default function handleResponse( + resolve: (value: ArrayBuffer) => void, + reject: (reason: string) => void, + response: http.IncomingMessage, +) { + //Check that file exists (200 HTTP status code) + if (response.statusCode !== 200) { + return reject(`Expected status code 200 but received ${response.statusCode}`); + } + + //Check file size + const headerLength = Number(response.headers['content-length']); + if (headerLength > MAX_MEMORY_SIZE) { + return reject('File size too large to be handled in memory.'); + } + + //If we receive a part of the response, store it + const chunkList: Buffer[] = []; + let totalLength = 0; + response.on('data', (chunk: Buffer) => { + totalLength += chunk.length; + + //Exit early if we received too much data + if (totalLength > headerLength) { + return reject(`Expected length ${headerLength} but received at least ${totalLength}`); + } + + //Add chunk to array + chunkList.push(chunk); + }); + + //If we finished reading response, check for correctness and return it + response.on('end', () => { + //Check that length is correct + if (totalLength !== headerLength) { + return reject(`Expected length ${headerLength} but received ${totalLength}`); + } + + //Return file contents as ArrayBuffer + const fileContents = Buffer.concat(chunkList, totalLength); + return resolve(fileContents.buffer as ArrayBuffer); + }); +} diff --git a/src/cdn/resolveDns.ts b/src/cdn/funcs/resolveDns.ts similarity index 96% rename from src/cdn/resolveDns.ts rename to src/cdn/funcs/resolveDns.ts index 827ac61..2dfcec3 100644 --- a/src/cdn/resolveDns.ts +++ b/src/cdn/funcs/resolveDns.ts @@ -1,4 +1,4 @@ -/* --- DNS resolver for the CDN storing SWTOR's patch files --- +/* --- DNS resolver for the CDN storing SWTOR’s patch files --- | | The SWTOR launcher first connects to cdn-patch.swtor.com, | there the traffic is split onto Akamai and Level3, and @@ -28,7 +28,7 @@ import { exec} from 'child_process'; import * as dns from 'dns'; -import { IDnsResult } from '../interfaces/IDnsResult'; +import { IDnsResult } from '../../interfaces/IDnsResult'; //TODO: send e-mail with the error const assert = (cond: boolean) => { if (!cond) { console.warn('Assert failed'); } }; @@ -39,7 +39,7 @@ async function resolveDns(domain: string): Promise { //check given string for correctness to prevent injection attacks if (!domain.match(/^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,3}$/)) { return resolve([]); } - //Check Level3/North_America separetely + //Check Level3/North_America separately if (domain !== 'cdn-patch.swtor.com') { dns.resolve4(domain, { ttl: true }, (err, result) => { return resolve(result.map(({ address, ttl }) => ({ address, ttl, type: 'level3-us' as IDnsResult['type'] }))); diff --git a/src/cdn/getUrlContents.ts b/src/cdn/getUrlContents.ts index c12b478..d87a925 100644 --- a/src/cdn/getUrlContents.ts +++ b/src/cdn/getUrlContents.ts @@ -1,41 +1,19 @@ import * as http from 'http'; +import handleResponse from './funcs/handleResponse'; -const MAX_MEMORY_SIZE = 100 * 1024 * 1024; - +/** Downloads the given URL into memory and returns it as an ArrayBuffer. Throws error if download fails or file is too large to be handled in memory. */ export default function getUrlContents({ host, path }: {host: string, path: string}): Promise { return new Promise((resolve, reject) => { const request = http.request({ family: 4, host, path, - }, (response) => { - if (response.statusCode !== 200) { - return reject(`Expected status code 200 but received ${response.statusCode}`); - } - const headerLength = Number(response.headers['content-length']); - if (headerLength > MAX_MEMORY_SIZE) { - reject('File size too large to be handled in memory.'); - request.abort(); - return; - } + }, handleResponse.bind(null, resolve, (reason: string) => { request.abort(); reject(reason); })); - const chunkList: Buffer[] = []; - let totalLength = 0; - response.on('data', (chunk: Buffer) => { - chunkList.push(chunk); - totalLength += chunk.length; - }); - response.on('end', () => { - if (totalLength !== headerLength) { - return reject(`Expected length ${headerLength} but received ${totalLength}`); - } - const fileContents = Buffer.concat(chunkList, totalLength); - resolve(fileContents.buffer as ArrayBuffer); - }); - }); - - request.on('error', (e) => { - reject(e); + //In case of connection errors, exit early + request.on('error', (error) => { + request.abort(); + return reject(error); }); request.end(); }); diff --git a/src/cdn/heartbeatDns.ts b/src/cdn/heartbeatDns.ts index e7cfa43..c01783d 100644 --- a/src/cdn/heartbeatDns.ts +++ b/src/cdn/heartbeatDns.ts @@ -1,5 +1,5 @@ import { IServerEntry } from '../interfaces/IDnsResult'; -import resolveDns from './resolveDns'; +import resolveDns from './funcs/resolveDns'; /** Time when this script started, for delta time calculations */ const startTime = Date.now();