Bug 1383931 - Accept base64-encoded addons in the addon install command. draft
authorJason Juang <juangj@gmail.com>
Mon, 24 Jul 2017 15:43:05 -0700
changeset 616148 266d1f13fdc58e106871688014c59780ada17ada
parent 614730 bf4634a50775537b4e791b6d294a275b83737472
child 639397 b4603767cd500ef58fcb866a67ced358d4954992
push id70604
push userbmo:juangj@gmail.com
push dateWed, 26 Jul 2017 19:01:20 +0000
bugs1383931
milestone56.0a1
Bug 1383931 - Accept base64-encoded addons in the addon install command. This allows tests that use Geckodriver remotely to more easily install addons. The base64 blob is written to a temporary file before being passed on to Marionette. MozReview-Commit-ID: DnaBqoXCj5
testing/geckodriver/CHANGES.md
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
--- a/testing/geckodriver/CHANGES.md
+++ b/testing/geckodriver/CHANGES.md
@@ -1,12 +1,17 @@
 # Change log
 
 All notable changes to this program is documented in this file.
 
+## Unreleased
+
+### Changed
+- `/moz/addon/install` command accepts an `addon` parameter, in lieu of `path`, containing an add-on as a base64 string.
+
 ## 0.18.0 (2017-07-10)
 
 ### Changed
 - [`RectResponse`](https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html) permits returning floats for `width` and `height` fields
 - New type [`CookieResponse`](https://docs.rs/webdriver/0.27.0/webdriver/response/struct.CookieResponse.html) for the [`GetNamedCookie` command](https://docs.rs/webdriver/0.27.0/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie) returns a single cookie, as opposed to an array of a single cookie
 - To pick up a prepared profile from the filesystem, it is now possible to pass `["-profile", "/path/to/profile"]` in the `args` array on `moz:firefoxOptions`
 - geckodriver now recommends Firefox 53 and greater
 - Version information (`--version`) contains the hash from from the commit used to build geckodriver
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -9,16 +9,17 @@ extern crate mozrunner;
 extern crate mozversion;
 extern crate regex;
 extern crate rustc_serialize;
 #[macro_use]
 extern crate slog;
 extern crate slog_atomic;
 extern crate slog_stdlog;
 extern crate slog_stream;
+extern crate uuid;
 extern crate zip;
 extern crate webdriver;
 
 #[macro_use]
 extern crate log;
 
 use std::fmt;
 use std::io::Write;
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,28 +1,32 @@
 use hyper::method::Method;
 use logging;
 use logging::LogLevel;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::{Runner, FirefoxRunner};
 use regex::Captures;
+use rustc_serialize::base64::FromBase64;
 use rustc_serialize::json;
 use rustc_serialize::json::{Json, ToJson};
 use std::collections::BTreeMap;
+use std::env;
 use std::error::Error;
+use std::fs::File;
 use std::io::Error as IoError;
 use std::io::ErrorKind;
 use std::io::prelude::*;
 use std::path::PathBuf;
 use std::io::Result as IoResult;
 use std::net::{TcpListener, TcpStream};
 use std::sync::Mutex;
 use std::thread::sleep;
 use std::time::Duration;
+use uuid::Uuid;
 use webdriver::capabilities::CapabilitiesMatching;
 use webdriver::command::{WebDriverCommand, WebDriverMessage, Parameters,
                          WebDriverExtensionCommand};
 use webdriver::command::WebDriverCommand::{
     NewSession, DeleteSession, Status, Get, GetCurrentUrl,
     GoBack, GoForward, Refresh, GetTitle, GetPageSource, GetWindowHandle,
     GetWindowHandles, CloseWindow, SetWindowRect,
     GetWindowRect, MaximizeWindow, FullscreenWindow, SwitchToWindow, SwitchToFrame,
@@ -258,32 +262,55 @@ pub struct AddonInstallParameters {
 }
 
 impl Parameters for AddonInstallParameters {
     fn from_json(body: &Json) -> WebDriverResult<AddonInstallParameters> {
         let data = try!(body.as_object().ok_or(
             WebDriverError::new(ErrorStatus::InvalidArgument,
                                 "Message body was not an object")));
 
-        let path = try_opt!(
-            try_opt!(data.get("path"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'path' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "'path' is not a string").to_string();
+        let base64 = match data.get("addon") {
+            Some(x) => {
+                let s = try_opt!(x.as_string(),
+                                 ErrorStatus::InvalidArgument,
+                                 "'addon' is not a string").to_string();
+
+                let addon_path = env::temp_dir().as_path()
+                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
+                let mut addon_file = try!(File::create(&addon_path));
+                let addon_buf = try!(s.from_base64());
+                try!(addon_file.write(addon_buf.as_slice()));
+
+                Some(try_opt!(addon_path.to_str(),
+                              ErrorStatus::UnknownError,
+                              "could not write addon to file").to_string())
+            },
+            None => None,
+        };
+        let path = match data.get("path") {
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::InvalidArgument,
+                                     "'path' is not a string").to_string()),
+            None => None,
+        };
+        if (base64.is_none() && path.is_none()) || (base64.is_some() && path.is_some()) {
+            return Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Must specify exactly one of 'path' and 'addon'"));
+        }
 
         let temporary = match data.get("temporary") {
             Some(x) => try_opt!(x.as_boolean(),
                                 ErrorStatus::InvalidArgument,
                                 "Failed to convert 'temporary' to boolean"),
             None => false
         };
 
         return Ok(AddonInstallParameters {
-            path: path,
+            path: base64.or(path).unwrap(),
             temporary: temporary,
         })
     }
 }
 
 impl ToJson for AddonInstallParameters {
     fn to_json(&self) -> Json {
         let mut data = BTreeMap::new();
@@ -1583,8 +1610,54 @@ impl ToMarionette for FrameId {
             FrameId::Short(x) => data.insert("id".to_string(), x.to_json()),
             FrameId::Element(ref x) => data.insert("element".to_string(),
                                                    Json::Object(try!(x.to_marionette()))),
             FrameId::Null => None
         };
         Ok(data)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use marionette::{AddonInstallParameters, Parameters};
+    use rustc_serialize::json::Json;
+    use std::io::Read;
+    use std::fs::File;
+    use webdriver::error::WebDriverResult;
+
+    #[test]
+    fn test_addon_install_params_missing_path() {
+        let json_data: Json = Json::from_str(r#"{"temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn test_addon_install_params_with_both_path_and_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn test_addon_install_params_with_path() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
+        assert_eq!(parameters.path, "/path/to.xpi");
+        assert_eq!(parameters.temporary, true);
+    }
+
+    #[test]
+    fn test_addon_install_params_with_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
+
+        assert_eq!(parameters.temporary, true);
+        let mut file = File::open(parameters.path).unwrap();
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).unwrap();
+        assert_eq!("hello", contents);
+    }
+}