cargo_unmaintained/github/
util.rs

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
use anyhow::{anyhow, Context, Result};
use once_cell::sync::Lazy;
use std::{
    env::var,
    fs::{create_dir_all, read_to_string, File, OpenOptions},
    io::{stdin, Write},
    path::PathBuf,
    sync::OnceLock,
};

#[allow(clippy::unwrap_used)]
static CONFIG_DIRECTORY: Lazy<PathBuf> = Lazy::new(|| {
    #[cfg(not(windows))]
    {
        let base_directories = xdg::BaseDirectories::new().unwrap();
        base_directories.get_config_file("cargo-unmaintained")
    }
    #[cfg(windows)]
    {
        let local_app_data = var("LOCALAPPDATA").unwrap();
        PathBuf::from(local_app_data).join("cargo-unmaintained")
    }
});

static TOKEN_PATH: Lazy<PathBuf> = Lazy::new(|| CONFIG_DIRECTORY.join("token.txt"));

pub(super) static PERSONAL_TOKEN: OnceLock<String> = OnceLock::new();

pub fn load_token(f: impl FnOnce(&str) -> Result<()>) -> Result<bool> {
    let token_untrimmed = if let Ok(path) = var("GITHUB_TOKEN_PATH") {
        read_to_string(&path).with_context(|| format!("failed to read {path:?}"))?
    } else if let Ok(token) = var("GITHUB_TOKEN") {
        // smoelius: Suppress warning if `CI` is set, i.e., if running on GitHub.
        if var("CI").is_err() {
            #[cfg(__warnings)]
            crate::warn!(
                "found a token in `GITHUB_TOKEN`; consider using the more secure method of \
                 setting `GITHUB_TOKEN_PATH` to the path of a file containing the token",
            );
        }
        token
    } else if TOKEN_PATH.try_exists().with_context(|| {
        format!(
            "failed to determine whether `{}` exists",
            TOKEN_PATH.display()
        )
    })? {
        read_to_string(&*TOKEN_PATH).with_context(|| format!("failed to read {TOKEN_PATH:?}"))?
    } else {
        #[cfg(__warnings)]
        crate::warn!(
            "`GITHUB_TOKEN_PATH` and `GITHUB_TOKEN` are not set and no file was found at {}; \
             archival statuses will not be checked",
            TOKEN_PATH.display()
        );
        return Ok(false);
    };
    let token = token_untrimmed.trim_end().to_owned();
    PERSONAL_TOKEN
        .set(token.clone())
        .map_err(|_| anyhow!("`load_token` was already called"))?;
    f(&token)?;
    Ok(true)
}

pub(crate) fn save_token() -> Result<()> {
    println!("Please paste a personal access token below. The token needs no scopes.");

    let mut buf = String::new();

    {
        let n = stdin()
            .read_line(&mut buf)
            .with_context(|| "failed to read stdin")?;
        assert_eq!(buf.len(), n);
    }

    create_dir_all(&*CONFIG_DIRECTORY).with_context(|| "failed to create config directory")?;

    let mut file = OpenOptions::new()
        .create(true)
        .truncate(true)
        .write(true)
        .open(&*TOKEN_PATH)
        .with_context(|| format!("failed to open `{}`", TOKEN_PATH.display()))?;
    set_permissions(&file, 0o600)?;
    file.write_all(buf.as_bytes())
        .with_context(|| format!("failed to write `{}`", TOKEN_PATH.display()))?;

    println!(
        "Personal access token written to `{}`",
        TOKEN_PATH.display()
    );

    Ok(())
}

type CargoResult<T> = Result<T>;

// smoelius: The below definitions of `set_permissions` were copied from:
// https://github.com/rust-lang/cargo/blob/1e6828485eea0f550ed7be46ef96107b46aeb162/src/cargo/util/config.rs#L1010-L1024
#[cfg(unix)]
#[cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))]
fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
    use std::os::unix::fs::PermissionsExt;

    let mut perms = file.metadata()?.permissions();
    perms.set_mode(mode);
    file.set_permissions(perms)?;
    Ok(())
}

#[cfg(not(unix))]
#[allow(unused, clippy::unnecessary_wraps)]
fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
    Ok(())
}