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();
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 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#[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}