blob: c936df213bd5c611018320ffc1ffcdfea7036d01 [file] [edit]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::chrome::ChromeManager;
use crate::edge::EdgeManager;
use crate::files::compose_cache_folder;
use crate::firefox::FirefoxManager;
use crate::iexplorer::IExplorerManager;
use std::fs;
use crate::config::{str_to_os, ManagerConfig};
use std::collections::HashMap;
use std::error::Error;
use std::path::PathBuf;
use std::process::Command;
use crate::downloads::download_driver_to_tmp_folder;
use crate::files::{parse_version, uncompress, BrowserPath};
use crate::metadata::{
create_browser_metadata, get_browser_version_from_metadata, get_metadata, write_metadata,
};
pub mod chrome;
pub mod config;
pub mod downloads;
pub mod edge;
pub mod files;
pub mod firefox;
pub mod iexplorer;
pub mod metadata;
pub const STABLE: &str = "stable";
pub const BETA: &str = "beta";
pub const DEV: &str = "dev";
pub const CANARY: &str = "canary";
pub const NIGHTLY: &str = "nightly";
pub const WMIC_COMMAND: &str = r#"wmic datafile where name='%{}:\=\\%{}' get Version /value"#;
pub const REG_QUERY: &str = r#"REG QUERY {} /v version"#;
pub const DASH_VERSION: &str = "{} -v";
pub const DASH_DASH_VERSION: &str = "{} --version";
pub const ENV_PROGRAM_FILES: &str = "PROGRAMFILES";
pub const ENV_PROGRAM_FILES_X86: &str = "PROGRAMFILES(X86)";
pub const ENV_LOCALAPPDATA: &str = "LOCALAPPDATA";
pub trait SeleniumManager {
// ----------------------------------------------------------
// Browser-specific functions
// ----------------------------------------------------------
fn get_browser_name(&self) -> &str;
fn get_browser_path_map(&self) -> HashMap<BrowserPath, &str>;
fn discover_browser_version(&self) -> Option<String>;
fn get_driver_name(&self) -> &str;
fn request_driver_version(&self) -> Result<String, Box<dyn Error>>;
fn get_driver_url(&self) -> Result<String, Box<dyn Error>>;
fn get_driver_path_in_cache(&self) -> PathBuf;
fn get_config(&self) -> &ManagerConfig;
fn set_config(&mut self, config: ManagerConfig);
// ----------------------------------------------------------
// Shared functions
// ----------------------------------------------------------
fn download_driver(&self) -> Result<(), Box<dyn Error>> {
let driver_url = Self::get_driver_url(self)?;
let (_tmp_folder, driver_zip_file) = download_driver_to_tmp_folder(driver_url)?;
let driver_path_in_cache = Self::get_driver_path_in_cache(self);
uncompress(&driver_zip_file, driver_path_in_cache)
}
fn get_browser_path(&self) -> Option<&str> {
let mut browser_version = self.get_browser_version();
if browser_version.eq_ignore_ascii_case(CANARY) {
browser_version = NIGHTLY;
} else if browser_version.is_empty() {
browser_version = STABLE;
}
self.get_browser_path_map()
.get(&BrowserPath::new(str_to_os(self.get_os()), browser_version))
.cloned()
}
fn detect_browser_version(&self, shell: &str, flag: &str, args: Vec<String>) -> Option<String> {
let mut metadata = get_metadata();
let browser_name = &self.get_browser_name();
match get_browser_version_from_metadata(&metadata.browsers, browser_name) {
Some(version) => {
log::trace!(
"Browser with valid TTL. Getting {} version from metadata",
browser_name
);
Some(version)
}
_ => {
log::debug!("Using shell command to find out {} version", browser_name);
let mut browser_version = "".to_string();
for arg in args.iter() {
let output = match self.run_shell_command(shell, flag, arg.to_string()) {
Ok(out) => out,
Err(_e) => continue,
};
let full_browser_version = parse_version(output).unwrap_or_default();
if full_browser_version.is_empty() {
continue;
}
log::debug!(
"The version of {} is {}",
browser_name,
full_browser_version
);
match self.get_major_version(&full_browser_version) {
Ok(v) => browser_version = v,
Err(_) => return None,
}
break;
}
metadata
.browsers
.push(create_browser_metadata(browser_name, &browser_version));
write_metadata(&metadata);
if !browser_version.is_empty() {
Some(browser_version)
} else {
None
}
}
}
}
fn discover_driver_version(&mut self) -> Result<String, String> {
let browser_version = self.get_browser_version();
if browser_version.is_empty() || self.is_browser_version_unstable() {
match self.discover_browser_version() {
Some(version) => {
log::debug!("Detected browser: {} {}", self.get_browser_name(), version);
self.set_browser_version(version);
}
None => {
if self.is_browser_version_unstable() {
return Err(format!("Browser version '{browser_version}' not found"));
} else {
log::debug!(
"The version of {} cannot be detected. Trying with latest driver version",
self.get_browser_name()
);
}
}
}
}
let driver_version = self
.request_driver_version()
.unwrap_or_else(|err| err.to_string());
log::debug!(
"Required driver: {} {}",
self.get_driver_name(),
driver_version
);
Ok(driver_version)
}
fn is_browser_version_unstable(&self) -> bool {
let browser_version = self.get_browser_version();
browser_version.eq_ignore_ascii_case(BETA)
|| browser_version.eq_ignore_ascii_case(DEV)
|| browser_version.eq_ignore_ascii_case(NIGHTLY)
|| browser_version.eq_ignore_ascii_case(CANARY)
}
fn resolve_driver(&mut self) -> Result<PathBuf, Box<dyn Error>> {
if self.get_driver_version().is_empty() {
let driver_version = self.discover_driver_version()?;
self.set_driver_version(driver_version);
}
let driver_path = self.get_driver_path_in_cache();
if driver_path.exists() {
log::debug!(
"{} {} already in the cache",
self.get_driver_name(),
self.get_driver_version()
);
} else {
self.download_driver()?;
}
Ok(driver_path)
}
fn run_shell_command(
&self,
command: &str,
flag: &str,
args: String,
) -> Result<String, Box<dyn Error>> {
log::debug!("Running {} command: {:?}", command, args);
let output = Command::new(command).args([flag, args.as_str()]).output()?;
log::debug!("{:?}", output);
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
fn get_major_version(&self, full_version: &str) -> Result<String, Box<dyn Error>> {
get_index_version(full_version, 0)
}
fn get_minor_version(&self, full_version: &str) -> Result<String, Box<dyn Error>> {
get_index_version(full_version, 1)
}
fn format_one_arg(&self, string: &str, arg1: &str) -> String {
string.replacen("{}", arg1, 1)
}
fn format_two_args(&self, string: &str, arg1: &str, arg2: &str) -> String {
string.replacen("{}", arg1, 1).replacen("{}", arg2, 2)
}
// ----------------------------------------------------------
// Getters and setters for configuration parameters
// ----------------------------------------------------------
fn get_os(&self) -> &str {
self.get_config().os.as_str()
}
fn set_os(&mut self, os: String) {
let mut config = ManagerConfig::clone(self.get_config());
config.os = os;
self.set_config(config);
}
fn get_arch(&self) -> &str {
self.get_config().arch.as_str()
}
fn set_arch(&mut self, arch: String) {
let mut config = ManagerConfig::clone(self.get_config());
config.arch = arch;
self.set_config(config);
}
fn get_browser_version(&self) -> &str {
self.get_config().browser_version.as_str()
}
fn set_browser_version(&mut self, browser_version: String) {
let mut config = ManagerConfig::clone(self.get_config());
config.browser_version = browser_version;
self.set_config(config);
}
fn get_driver_version(&self) -> &str {
self.get_config().driver_version.as_str()
}
fn set_driver_version(&mut self, driver_version: String) {
let mut config = ManagerConfig::clone(self.get_config());
config.driver_version = driver_version;
self.set_config(config);
}
}
// ----------------------------------------------------------
// Public functions
// ----------------------------------------------------------
pub fn get_manager_by_browser(browser_name: String) -> Result<Box<dyn SeleniumManager>, String> {
let browser_name_lower_case = browser_name.to_ascii_lowercase();
if browser_name_lower_case.eq("chrome") {
Ok(ChromeManager::new())
} else if browser_name.eq("firefox") {
Ok(FirefoxManager::new())
} else if vec!["edge", "msedge", "microsoftedge"].contains(&browser_name_lower_case.as_str()) {
Ok(EdgeManager::new())
} else if vec![
"iexplorer",
"ie",
"internetexplorer",
"internet-explorer",
"internet_explorer",
]
.contains(&browser_name_lower_case.as_str())
{
Ok(IExplorerManager::new())
} else {
Err(format!("Invalid browser name: {browser_name}"))
}
}
pub fn get_manager_by_driver(driver_name: String) -> Result<Box<dyn SeleniumManager>, String> {
if driver_name.eq_ignore_ascii_case("chromedriver") {
Ok(ChromeManager::new())
} else if driver_name.eq_ignore_ascii_case("geckodriver") {
Ok(FirefoxManager::new())
} else if driver_name.eq_ignore_ascii_case("msedgedriver") {
Ok(EdgeManager::new())
} else if driver_name.eq_ignore_ascii_case("iedriverserver") {
Ok(IExplorerManager::new())
} else {
Err(format!("Invalid driver name: {driver_name}"))
}
}
pub fn clear_cache() {
let cache_path = compose_cache_folder();
if cache_path.exists() {
log::debug!("Clearing cache at: {}", cache_path.display());
fs::remove_dir_all(&cache_path).unwrap_or_else(|err| {
log::warn!(
"The cache {} cannot be cleared: {}",
cache_path.display(),
err
)
});
}
}
// ----------------------------------------------------------
// Private functions
// ----------------------------------------------------------
fn get_index_version(full_version: &str, index: usize) -> Result<String, Box<dyn Error>> {
let version_vec: Vec<&str> = full_version.split('.').collect();
Ok(version_vec
.get(index)
.ok_or(format!("Wrong version: {}", full_version))?
.to_string())
}