isr_dl_pdb/
lib.rs

1//! Download PDB files from Microsoft symbol servers.
2
3mod codeview;
4mod error;
5
6use std::{
7    fs::File,
8    path::{Path, PathBuf},
9};
10
11pub use self::{codeview::CodeView, error::Error};
12
13pub const DEFAULT_SERVER_URL: &str = "http://msdl.microsoft.com/download/symbols";
14
15pub struct PdbDownloader {
16    codeview: CodeView,
17    servers: Vec<String>,
18    output: Option<PathBuf>,
19}
20
21impl PdbDownloader {
22    pub fn new(codeview: CodeView) -> Self {
23        Self {
24            codeview,
25            servers: vec![DEFAULT_SERVER_URL.into()],
26            output: None,
27        }
28    }
29
30    pub fn from_exe(path: impl AsRef<Path>) -> Result<Self, Error> {
31        Ok(Self::new(CodeView::from_path(path)?))
32    }
33
34    pub fn with_servers(self, servers: impl IntoIterator<Item = impl Into<String>>) -> Self {
35        Self {
36            servers: servers.into_iter().map(Into::into).collect(),
37            ..self
38        }
39    }
40
41    pub fn with_output(self, output: impl Into<PathBuf>) -> Self {
42        Self {
43            output: Some(output.into()),
44            ..self
45        }
46    }
47
48    pub fn download(self) -> Result<PathBuf, Error> {
49        let CodeView { path, guid } = self.codeview;
50
51        for server in &self.servers {
52            let path_with_underscore = path.chars().rev().skip(1).collect::<String>() + "_";
53
54            for suffix in &[&path, &path_with_underscore] {
55                let url = format!("{server}/{path}/{guid}/{suffix}");
56
57                tracing::info!(url, "requesting");
58                let response = reqwest::blocking::get(&url);
59                if response.is_err() {
60                    continue;
61                }
62
63                let output = match &self.output {
64                    Some(output) => {
65                        if output.is_dir() {
66                            output.join(format!("{guid}_{path}"))
67                        }
68                        else {
69                            output.clone()
70                        }
71                    }
72                    None => PathBuf::from(format!("{guid}_{path}")),
73                };
74
75                tracing::info!(?output, "downloading");
76                let mut file = File::create(&output)?;
77                response?.copy_to(&mut file)?;
78                return Ok(output);
79            }
80        }
81
82        Err(Error::Failed)
83    }
84}