cargo_unmaintained/github/real/
util.rs1use 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 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#[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}