1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::{chromedriver::Chromedriver, geckodriver::Geckodriver, DriverFetcher};
use dirs::home_dir;
use eyre::{ensure, eyre, Result};
use flate2::read::GzDecoder;
use tar::Archive;
use tracing::debug;
use std::fs::File;
use std::io::{Cursor, Read};
use std::path::PathBuf;
static DRIVER_EXECUTABLES: &[&'static str] = &["geckodriver", "chromedriver", "chromedriver.exe", "geckodriver.exe"];
pub enum Driver {
Chrome,
Gecko,
}
impl Driver {
pub fn install(&self) -> Result<PathBuf> {
let target_dir = home_dir().unwrap().join(".webdrivers");
std::fs::create_dir_all(&target_dir)?;
self.install_into(target_dir)
}
pub fn install_into(&self, target_dir: PathBuf) -> Result<PathBuf> {
ensure!(target_dir.is_dir(), "`target_dir` must be a directory.");
let download_url = match self {
Self::Gecko => {
let version = Geckodriver::new().latest_version()?;
Geckodriver::new().direct_download_url(&version)?
}
Self::Chrome => {
let version = Chromedriver::new().latest_version()?;
Chromedriver::new().direct_download_url(&version)?
}
};
let resp = reqwest::blocking::get(download_url.clone())?;
let archive_content = &resp.bytes()?;
let archive_filename = download_url
.path_segments()
.and_then(|s| s.last())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or("tmp.bin");
let executable_path = decompress(archive_filename, archive_content, target_dir.clone())?;
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
use std::fs;
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&executable_path, fs::Permissions::from_mode(0o775)).unwrap();
}
debug!("stored at {:?}", executable_path);
Ok(executable_path)
}
}
fn decompress(archive_filename: &str, bytes: &[u8], target_dir: PathBuf) -> Result<PathBuf> {
match archive_filename {
name if name.ends_with("tar.gz") => {
let tar = GzDecoder::new(Cursor::new(bytes));
let mut archive = Archive::new(tar);
let driver_executable = archive.entries()?.filter_map(Result::ok).filter(|e| {
let filename = e.path().unwrap();
debug!("filename: {:?}", filename);
DRIVER_EXECUTABLES.contains(&filename.as_os_str().to_str().unwrap())
});
for mut exec in driver_executable {
let final_path = target_dir.join(exec.path()?);
exec.unpack(&final_path)?;
return Ok(final_path);
}
}
name if name.ends_with("zip") => {
debug!("zip file name: {}", name);
let mut zip = zip::ZipArchive::new(Cursor::new(bytes))?;
let mut zip_bytes: Vec<u8> = vec![];
let mut filename: Option<String> = None;
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
if DRIVER_EXECUTABLES.contains(&file.name()) {
filename = Some(file.name().to_string());
file.read_to_end(&mut zip_bytes)?;
break;
}
}
if let Some(name) = filename {
debug!("saving zip file: {}", name);
let executable_path = target_dir.join(name);
let mut f = File::create(&executable_path)?;
std::io::copy(&mut zip_bytes.as_slice(), &mut f)?;
return Ok(executable_path);
}
}
ext => return Err(eyre!("No support for unarchiving {}, yet", ext)),
}
Err(eyre!("This installer code should be unreachable! archive_filename: {}", archive_filename))
}