cargo_unmaintained/github/real/
util.rs

1use anyhow::{Result, anyhow};
2use elaborate::std::{
3    env::var_wc,
4    fs::{OpenOptionsContext, create_dir_all_wc, read_to_string_wc},
5    io::{StdinContext, WriteContext},
6    path::PathContext,
7};
8use std::{
9    fs::{File, OpenOptions},
10    io::stdin,
11    path::PathBuf,
12    sync::{LazyLock, OnceLock},
13};
14
15#[allow(clippy::unwrap_used)]
16static CONFIG_DIRECTORY: LazyLock<PathBuf> = LazyLock::new(|| {
17    #[cfg(not(windows))]
18    {
19        let base_directories = xdg::BaseDirectories::new();
20        base_directories
21            .create_config_directory("cargo-unmaintained")
22            .unwrap()
23    }
24    #[cfg(windows)]
25    {
26        let local_app_data = var_wc("LOCALAPPDATA").unwrap();
27        PathBuf::from(local_app_data).join("cargo-unmaintained")
28    }
29});
30
31static TOKEN_PATH: LazyLock<PathBuf> = LazyLock::new(|| CONFIG_DIRECTORY.join("token.txt"));
32
33pub(super) static PERSONAL_TOKEN: OnceLock<String> = OnceLock::new();
34
35pub fn load_token(f: impl FnOnce(&str) -> Result<()>) -> Result<bool> {
36    let token_untrimmed = if let Ok(path) = var_wc("GITHUB_TOKEN_PATH") {
37        read_to_string_wc(&path)?
38    } else if let Ok(token) = var_wc("GITHUB_TOKEN") {
39        // smoelius: Suppress warning if `CI` is set, i.e., if running on GitHub.
40        if var_wc("CI").is_err() {
41            #[cfg(__warnings)]
42            crate::warn!(
43                "found a token in `GITHUB_TOKEN`; consider using the more secure method of \
44                 setting `GITHUB_TOKEN_PATH` to the path of a file containing the token",
45            );
46        }
47        token
48    } else if TOKEN_PATH.try_exists_wc()? {
49        read_to_string_wc(&*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().read_line_wc(&mut buf)?;
74        assert_eq!(buf.len(), n);
75    }
76
77    create_dir_all_wc(&*CONFIG_DIRECTORY)?;
78
79    let mut file = OpenOptions::new()
80        .create(true)
81        .truncate(true)
82        .write(true)
83        .open_wc(&*TOKEN_PATH)?;
84    set_permissions(&file, 0o600)?;
85    file.write_all_wc(buf.as_bytes())?;
86
87    println!(
88        "Personal access token written to `{}`",
89        TOKEN_PATH.display()
90    );
91
92    Ok(())
93}
94
95type CargoResult<T> = Result<T>;
96
97// smoelius: The below definitions of `set_permissions` were copied from:
98// https://github.com/rust-lang/cargo/blob/1e6828485eea0f550ed7be46ef96107b46aeb162/src/cargo/util/config.rs#L1010-L1024
99#[cfg(unix)]
100#[cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))]
101#[allow(clippy::disallowed_methods)]
102fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
103    use std::os::unix::fs::PermissionsExt;
104
105    let mut perms = file.metadata()?.permissions();
106    perms.set_mode(mode);
107    file.set_permissions(perms)?;
108    Ok(())
109}
110
111#[cfg(not(unix))]
112#[allow(unused, clippy::disallowed_methods, clippy::unnecessary_wraps)]
113fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
114    Ok(())
115}