cargo_unmaintained/github/real/
util.rs

1use anyhow::{Context, Result, anyhow};
2use std::{
3    env::var,
4    fs::{File, OpenOptions, create_dir_all, read_to_string},
5    io::{Write, stdin},
6    path::PathBuf,
7    sync::{LazyLock, OnceLock},
8};
9
10#[allow(clippy::unwrap_used)]
11static CONFIG_DIRECTORY: LazyLock<PathBuf> = LazyLock::new(|| {
12    #[cfg(not(windows))]
13    {
14        let base_directories = xdg::BaseDirectories::new().unwrap();
15        base_directories.get_config_file("cargo-unmaintained")
16    }
17    #[cfg(windows)]
18    {
19        let local_app_data = var("LOCALAPPDATA").unwrap();
20        PathBuf::from(local_app_data).join("cargo-unmaintained")
21    }
22});
23
24static TOKEN_PATH: LazyLock<PathBuf> = LazyLock::new(|| CONFIG_DIRECTORY.join("token.txt"));
25
26pub(super) static PERSONAL_TOKEN: OnceLock<String> = OnceLock::new();
27
28pub fn load_token(f: impl FnOnce(&str) -> Result<()>) -> Result<bool> {
29    let token_untrimmed = if let Ok(path) = var("GITHUB_TOKEN_PATH") {
30        read_to_string(&path).with_context(|| format!("failed to read {path:?}"))?
31    } else if let Ok(token) = var("GITHUB_TOKEN") {
32        // smoelius: Suppress warning if `CI` is set, i.e., if running on GitHub.
33        if var("CI").is_err() {
34            #[cfg(__warnings)]
35            crate::warn!(
36                "found a token in `GITHUB_TOKEN`; consider using the more secure method of \
37                 setting `GITHUB_TOKEN_PATH` to the path of a file containing the token",
38            );
39        }
40        token
41    } else if TOKEN_PATH.try_exists().with_context(|| {
42        format!(
43            "failed to determine whether `{}` exists",
44            TOKEN_PATH.display()
45        )
46    })? {
47        read_to_string(&*TOKEN_PATH).with_context(|| format!("failed to read {TOKEN_PATH:?}"))?
48    } else {
49        #[cfg(__warnings)]
50        crate::warn!(
51            "`GITHUB_TOKEN_PATH` and `GITHUB_TOKEN` are not set and no file was found at {}; \
52             archival statuses will not be checked",
53            TOKEN_PATH.display()
54        );
55        return Ok(false);
56    };
57    let token = token_untrimmed.trim_end().to_owned();
58    PERSONAL_TOKEN
59        .set(token.clone())
60        .map_err(|_| anyhow!("`load_token` was already called"))?;
61    f(&token)?;
62    Ok(true)
63}
64
65pub(crate) fn save_token() -> Result<()> {
66    println!("Please paste a personal access token below. The token needs no scopes.");
67
68    let mut buf = String::new();
69
70    {
71        let n = stdin()
72            .read_line(&mut buf)
73            .with_context(|| "failed to read stdin")?;
74        assert_eq!(buf.len(), n);
75    }
76
77    create_dir_all(&*CONFIG_DIRECTORY).with_context(|| "failed to create config directory")?;
78
79    let mut file = OpenOptions::new()
80        .create(true)
81        .truncate(true)
82        .write(true)
83        .open(&*TOKEN_PATH)
84        .with_context(|| format!("failed to open `{}`", TOKEN_PATH.display()))?;
85    set_permissions(&file, 0o600)?;
86    file.write_all(buf.as_bytes())
87        .with_context(|| format!("failed to write `{}`", TOKEN_PATH.display()))?;
88
89    println!(
90        "Personal access token written to `{}`",
91        TOKEN_PATH.display()
92    );
93
94    Ok(())
95}
96
97type CargoResult<T> = Result<T>;
98
99// smoelius: The below definitions of `set_permissions` were copied from:
100// https://github.com/rust-lang/cargo/blob/1e6828485eea0f550ed7be46ef96107b46aeb162/src/cargo/util/config.rs#L1010-L1024
101#[cfg(unix)]
102#[cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))]
103fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
104    use std::os::unix::fs::PermissionsExt;
105
106    let mut perms = file.metadata()?.permissions();
107    perms.set_mode(mode);
108    file.set_permissions(perms)?;
109    Ok(())
110}
111
112#[cfg(not(unix))]
113#[allow(unused, clippy::unnecessary_wraps)]
114fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
115    Ok(())
116}