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