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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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.exists(), "installation directory must exist.");
ensure!(
target_dir.is_dir(),
"installation location 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)
}
#[doc(hidden)]
pub fn as_str<'a>(&self) -> &'a str {
match self {
Self::Chrome => "chromedriver",
Self::Gecko => "geckodriver",
}
}
#[doc(hidden)]
pub fn from_str(s: &str) -> Option<Self> {
match s {
"chromedriver" => Some(Self::Chrome),
"geckodriver" => Some(Self::Gecko),
_ => None,
}
}
}
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
))
}