From 3325a8ba8edde6190608e077600b6b5160c104be Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Wed, 31 Aug 2016 16:21:01 +0100 Subject: [PATCH] Cachable, proxy-friendly selendroid jar download. * Download using http from maven central, not https from github. Using http avoids many proxy-related problems and makes caching easier. Not using github means can use http and also avoids a redirection to a temporary and therefore extra-hard-to-cache URL. * Download via temporary file, throwing exception if fail, to ensure other code can't see bad download. * Use SHA256 not MD5 fingerprint, to compensate for use of http. * Only check fingerprint on download, but fail if it is wrong instead of just logging warning. * Use Buffer, not String to hold body. Download using http (from maven central, not github) via temporary file, throwing exception if fail. --- lib/installer.js | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/installer.js b/lib/installer.js index 4e62630..e9b3687 100644 --- a/lib/installer.js +++ b/lib/installer.js @@ -3,15 +3,18 @@ import path from 'path'; import { fs } from 'appium-support'; import { exec } from 'teen_process'; import log from './logger'; +import crypto from 'crypto'; const SE_VER = "0.17.0"; -const SE_DOWNLOAD = `https://github.com/selendroid/selendroid/releases/` + - `download/${SE_VER}/selendroid-standalone-${SE_VER}-with` + - `-dependencies.jar`; -const SE_DOWNLOAD_MD5 = "5e1f7de5e4d2eb77b68675d76c5edf6a"; +const SE_DOWNLOAD = `http://repo1.maven.org/maven2/io/selendroid/selendroid-standalone/${SE_VER}/selendroid-standalone-${SE_VER}-with-dependencies.jar`; +const SE_DOWNLOAD_SHA256 = "7cf7163ac47f1c46eff95b62f78b58c1dabdec534acc6632da3784739f6e9d82"; const SE_DIR = path.resolve(__dirname, "..", "..", "selendroid"); const SE_DOWNLOAD_DIR = path.resolve(SE_DIR, "download"); -const SE_JAR_PATH = path.resolve(SE_DOWNLOAD_DIR, "selendroid-server.jar"); +// Use of temporary file means that SE_JAR_PATH can only exist if it has +// verified content. +const SE_JAR_PATH_TMP = path.resolve(SE_DOWNLOAD_DIR, "selendroid-server.jar.tmp"); +// Putting fingerprint in file name means download triggered if fingerprint changed. +const SE_JAR_PATH = path.resolve(SE_DOWNLOAD_DIR, `selendroid-server-${SE_DOWNLOAD_SHA256}.jar`); const SE_APK_PATH = path.resolve(SE_DIR, "selendroid-server.apk"); const SE_MANIFEST_PATH = path.resolve(SE_DIR, "AndroidManifest.xml"); @@ -26,9 +29,8 @@ async function setupSelendroid () { return; } } - if (await fs.exists(SE_JAR_PATH) && - await fs.md5(SE_JAR_PATH) === SE_DOWNLOAD_MD5) { - log.info("Standalone jar exists and has correct hash, skipping download"); + if (await fs.exists(SE_JAR_PATH)) { + log.info("Standalone jar exists, skipping download: " + SE_JAR_PATH); } else { await downloadSelendroid(); } @@ -63,18 +65,27 @@ async function downloadSelendroid () { await fs.mkdir(SE_DOWNLOAD_DIR); log.info(`Downloading Selendroid standalone server version ${SE_VER} from ` + `${SE_DOWNLOAD} --> ${SE_JAR_PATH}`); - let body = await request.get({url: SE_DOWNLOAD, encoding: 'binary'}); - log.info(`Writing binary content to ${SE_JAR_PATH}`); - await fs.writeFile(SE_JAR_PATH, body, {encoding: 'binary'}); - await fs.chmod(SE_JAR_PATH, 0o0644); - if (await fs.md5(SE_JAR_PATH) === SE_DOWNLOAD_MD5) { + let body = await request.get({url: SE_DOWNLOAD, encoding: null}); + if (!body instanceof Buffer) { + throw new Error(Object.prototype.toString.call(body)); + } + log.info(`Writing binary content to ${SE_JAR_PATH_TMP}`); + await fs.writeFile(SE_JAR_PATH_TMP, body); + await fs.chmod(SE_JAR_PATH_TMP, 0o0644); + let fingerprint = await sha256(body); + if (fingerprint === SE_DOWNLOAD_SHA256) { + await fs.rename(SE_JAR_PATH_TMP, SE_JAR_PATH); log.info("Selendroid standalone server downloaded"); } else { - log.warn("Selendroid standalone server downloaded, but MD5 hash did not " + - "match, please be careful"); + log.errorAndThrow("bad SHA256 fingerprint: " + fingerprint + " bytes: " + body.length); } } +async function sha256 (buffer) { + const hash = crypto.createHash('sha256'); + return hash.update(buffer).digest('hex'); +} + async function getFilePathFromJar (fileRegex, jarPath) { let {stdout} = await exec('jar', ['tf', jarPath]); for (let line of stdout.split("\n")) {