/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", FileUtils: "resource://gre/modules/FileUtils.sys.mjs", NetUtil: "resource://gre/modules/NetUtil.sys.mjs", }); loader.lazyGetter(this, "UNPACKED_ROOT_PATH", () => { return PathUtils.join(PathUtils.localProfileDir, "adb"); }); loader.lazyGetter(this, "EXTENSION_ID", () => { return Services.prefs.getCharPref("devtools.remote.adb.extensionID"); }); loader.lazyGetter(this, "ADB_BINARY_PATH", () => { let adbBinaryPath = PathUtils.join(UNPACKED_ROOT_PATH, "adb"); if (Services.appinfo.OS === "WINNT") { adbBinaryPath += ".exe"; } return adbBinaryPath; }); const MANIFEST = "manifest.json"; /** * Read contents from a given uri in the devtools-adb-extension and parse the * contents as JSON. */ async function readFromExtension(fileUri) { return new Promise(resolve => { lazy.NetUtil.asyncFetch( { uri: fileUri, loadUsingSystemPrincipal: true, }, input => { try { const string = lazy.NetUtil.readInputStreamToString( input, input.available() ); resolve(JSON.parse(string)); } catch (e) { dumpn(`Could not read ${fileUri} in the extension: ${e}`); resolve(null); } } ); }); } /** * Unpack file from the extension. * Uses NetUtil to read and write, since it's required for reading. * * @param {string} file * The path name of the file in the extension. */ async function unpackFile(file) { const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); if (!policy) { return; } // Assumes that destination dir already exists. const basePath = file.substring(file.lastIndexOf("/") + 1); const filePath = PathUtils.join(UNPACKED_ROOT_PATH, basePath); await new Promise((resolve, reject) => { lazy.NetUtil.asyncFetch( { uri: policy.getURL(file), loadUsingSystemPrincipal: true, }, input => { try { // Since we have to use NetUtil to read, probably it's okay to use for // writing, rather than bouncing to IOUtils...? const outputFile = new lazy.FileUtils.File(filePath); const output = lazy.FileUtils.openAtomicFileOutputStream(outputFile); lazy.NetUtil.asyncCopy(input, output, resolve); } catch (e) { dumpn(`Could not unpack file ${file} in the extension: ${e}`); reject(e); } } ); }); // Mark binaries as executable. await IOUtils.setPermissions(filePath, 0o744); } /** * Extract files in the extension into local profile directory and returns * if it fails. */ async function extractFiles() { const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); if (!policy) { return false; } const uri = policy.getURL("adb.json"); const adbInfo = await readFromExtension(uri); if (!adbInfo) { return false; } let filesForAdb; try { // The adbInfo is an object looks like this; // // { // "Linux": { // "x86": [ // "linux/adb" // ], // "x86_64": [ // "linux64/adb" // ] // }, // ... // XPCOMABI looks this; x86_64-gcc3, so drop the compiler name. let architecture = Services.appinfo.XPCOMABI.split("-")[0]; if (architecture === "aarch64") { // Fallback on x86 or x86_64 binaries for aarch64 - See Bug 1522149 const hasX86Binary = !!adbInfo[Services.appinfo.OS].x86; architecture = hasX86Binary ? "x86" : "x86_64"; } filesForAdb = adbInfo[Services.appinfo.OS][architecture]; } catch (e) { return false; } // manifest.json isn't in adb.json but has to be unpacked for version // comparison filesForAdb.push(MANIFEST); await IOUtils.makeDirectory(UNPACKED_ROOT_PATH); for (const file of filesForAdb) { try { await unpackFile(file); } catch (e) { return false; } } return true; } /** * Read the manifest from inside the devtools-adb-extension. * Uses NetUtil since data is packed inside the extension, not a local file. */ async function getManifestFromExtension() { const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); if (!policy) { return null; } const manifestUri = policy.getURL(MANIFEST); return readFromExtension(manifestUri); } /** * Returns whether manifest.json has already been unpacked. */ async function isManifestUnpacked() { const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST); return IOUtils.exists(manifestPath); } /** * Read the manifest from the unpacked binary directory. * Uses IOUtils since this is a local file. */ async function getManifestFromUnpacked() { if (!(await isManifestUnpacked())) { throw new Error("Manifest doesn't exist at unpacked path"); } const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST); const binary = await IOUtils.read(manifestPath); const json = new TextDecoder().decode(binary); let data; try { data = JSON.parse(json); } catch (e) {} return data; } /** * Check state of binary unpacking, including the location and manifest. */ async function isUnpacked() { if (!(await isManifestUnpacked())) { dumpn("Needs unpacking, no manifest found"); return false; } const manifestInExtension = await getManifestFromExtension(); const unpackedManifest = await getManifestFromUnpacked(); if (manifestInExtension.version != unpackedManifest.version) { dumpn( `Needs unpacking, extension version ${manifestInExtension.version} != ` + `unpacked version ${unpackedManifest.version}` ); return false; } dumpn("Already unpacked"); return true; } /** * Get a file object for the adb binary from the 'adb@mozilla.org' extension * which has been already installed. * * @return {nsIFile} * File object for the binary. */ async function getFileForBinary() { if (!(await isUnpacked()) && !(await extractFiles())) { return null; } const file = new lazy.FileUtils.File(ADB_BINARY_PATH); if (!file.exists()) { return null; } return file; } exports.getFileForBinary = getFileForBinary;