blob: f66dd12dc40fd885febc9a81012f9717716d0f7e [file] [log] [blame] [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 std::error::Error;
use std::fs;
use std::fs::File;
use std::io;
use std::path::MAIN_SEPARATOR;
use std::path::{Path, PathBuf};
use crate::config::OS;
use directories::BaseDirs;
use flate2::read::GzDecoder;
use regex::Regex;
use tar::Archive;
use zip::ZipArchive;
use crate::config::OS::WINDOWS;
use crate::Logger;
pub const PARSE_ERROR: &str = "Wrong browser/driver version";
const CACHE_FOLDER: &str = ".cache/selenium";
const ZIP: &str = "zip";
const GZ: &str = "gz";
const XML: &str = "xml";
#[derive(Hash, Eq, PartialEq, Debug)]
pub struct BrowserPath {
os: OS,
channel: String,
}
impl BrowserPath {
pub fn new(os: OS, channel: &str) -> BrowserPath {
BrowserPath {
os,
channel: channel.to_string(),
}
}
}
pub fn create_path_if_not_exists(path: &Path) {
if !path.exists() {
fs::create_dir_all(path).unwrap();
}
}
pub fn uncompress(
compressed_file: &str,
target: &Path,
log: &Logger,
) -> Result<(), Box<dyn Error>> {
let file = File::open(compressed_file)?;
let kind = infer::get_from_path(compressed_file)?
.ok_or(format!("Format for file {:?} cannot be inferred", file))?;
let extension = kind.extension();
log.trace(format!(
"The detected extension of the compressed file is {}",
extension
));
if extension.eq_ignore_ascii_case(ZIP) {
unzip(file, target, log)?
} else if extension.eq_ignore_ascii_case(GZ) {
untargz(file, target, log)?
} else if extension.eq_ignore_ascii_case(XML) {
log.debug(format!(
"Wrong downloaded driver: {}",
fs::read_to_string(compressed_file).unwrap_or_default()
));
return Err(PARSE_ERROR.into());
} else {
return Err(format!(
"Downloaded file cannot be uncompressed ({} extension)",
extension
)
.into());
}
Ok(())
}
pub fn untargz(file: File, target: &Path, log: &Logger) -> Result<(), Box<dyn Error>> {
log.trace(format!("Untargz file to {}", target.display()));
let tar = GzDecoder::new(&file);
let mut archive = Archive::new(tar);
let parent_path = target
.parent()
.ok_or(format!("Error getting parent of {:?}", file))?;
if !target.exists() {
archive.unpack(parent_path)?;
}
Ok(())
}
pub fn unzip(file: File, target: &Path, log: &Logger) -> Result<(), Box<dyn Error>> {
log.trace(format!("Unzipping file to {}", target.display()));
let mut archive = ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
if target.exists() {
continue;
}
let target_file_name = target.file_name().unwrap().to_str().unwrap();
if target_file_name.eq_ignore_ascii_case(file.name()) {
log.debug(format!(
"File extracted to {} ({} bytes)",
target.display(),
file.size()
));
if let Some(p) = target.parent() {
create_path_if_not_exists(p);
}
if !target.exists() {
let mut outfile = File::create(&target)?;
// Set permissions in Unix-like systems
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&target, fs::Permissions::from_mode(0o755))?;
}
io::copy(&mut file, &mut outfile)?;
}
break;
}
}
Ok(())
}
pub fn compose_cache_folder() -> PathBuf {
if let Some(base_dirs) = BaseDirs::new() {
return Path::new(base_dirs.home_dir())
.join(String::from(CACHE_FOLDER).replace('/', &MAIN_SEPARATOR.to_string()));
}
PathBuf::new()
}
pub fn get_cache_folder() -> PathBuf {
let cache_path = compose_cache_folder();
create_path_if_not_exists(&cache_path);
cache_path
}
pub fn compose_driver_path_in_cache(
driver_name: &str,
os: &str,
arch_folder: &str,
driver_version: &str,
) -> PathBuf {
get_cache_folder()
.join(driver_name)
.join(arch_folder)
.join(driver_version)
.join(get_driver_filename(driver_name, os))
}
pub fn get_driver_filename(driver_name: &str, os: &str) -> String {
format!("{}{}", driver_name, get_binary_extension(os))
}
pub fn get_binary_extension(os: &str) -> &str {
if WINDOWS.is(os) {
".exe"
} else {
""
}
}
pub fn parse_version(version_text: String, log: &Logger) -> Result<String, Box<dyn Error>> {
if version_text.to_ascii_lowercase().contains("error") {
log.debug(format!("Error parsing version: {}", version_text));
return Err(PARSE_ERROR.into());
}
let mut parsed_version = "".to_string();
let re_numbers_dots = Regex::new(r"[^\d^.]")?;
let re_versions = Regex::new(r"(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)")?;
for token in version_text.split(' ') {
parsed_version = re_numbers_dots.replace_all(token, "").to_string();
if re_versions.is_match(parsed_version.as_str()) {
break;
}
}
if parsed_version.ends_with('.') {
parsed_version = parsed_version[0..parsed_version.len() - 1].to_string();
}
Ok(parsed_version)
}