forge: use base64 crate for avatar payload
Per @mara on #328: the hand-rolled encoder was over-cautious. Swap for base64 = 0.22 from crates.io — a standard, widely-trusted dep, no maintenance surface to carry. Drops the 15-line encoder and its two RFC 4648 unit tests.
This commit is contained in:
parent
dbb2ca4393
commit
ce539559d5
4 changed files with 5 additions and 58 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -585,6 +585,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
"base64",
|
||||||
"clap",
|
"clap",
|
||||||
"hive-fr0nt",
|
"hive-fr0nt",
|
||||||
"hive-sh4re",
|
"hive-sh4re",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ must_use_candidate = "allow"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = { version = "0.8", features = ["ws"] }
|
axum = { version = "0.8", features = ["ws"] }
|
||||||
|
base64 = "0.22"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
hive-fr0nt = { path = "hive-fr0nt" }
|
hive-fr0nt = { path = "hive-fr0nt" }
|
||||||
hive-sh4re = { path = "hive-sh4re" }
|
hive-sh4re = { path = "hive-sh4re" }
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
base64.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
hive-fr0nt.workspace = true
|
hive-fr0nt.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use base64::Engine;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
|
@ -287,7 +288,7 @@ async fn ensure_core_avatar(token: &str) -> Result<()> {
|
||||||
}
|
}
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{{"image":"{}"}}"#,
|
r#"{{"image":"{}"}}"#,
|
||||||
base64_encode(CORE_AVATAR_PNG),
|
base64::engine::general_purpose::STANDARD.encode(CORE_AVATAR_PNG),
|
||||||
);
|
);
|
||||||
let url = format!("{FORGE_HTTP}/api/v1/admin/users/core/avatar");
|
let url = format!("{FORGE_HTTP}/api/v1/admin/users/core/avatar");
|
||||||
let status = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
let status = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
||||||
|
|
@ -302,63 +303,6 @@ async fn ensure_core_avatar(token: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Standard base64 encode (RFC 4648 §4, with `=` padding). Used by
|
|
||||||
/// `ensure_core_avatar` to embed the hyperhive logo PNG in the
|
|
||||||
/// Forgejo admin avatar JSON body — the payload is one small image
|
|
||||||
/// in one cold path, so pulling in a base64 crate isn't worth the
|
|
||||||
/// dep churn.
|
|
||||||
fn base64_encode(bytes: &[u8]) -> String {
|
|
||||||
const ALPHABET: &[u8; 64] =
|
|
||||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
|
|
||||||
for chunk in bytes.chunks(3) {
|
|
||||||
let b0 = chunk[0];
|
|
||||||
let b1 = chunk.get(1).copied().unwrap_or(0);
|
|
||||||
let b2 = chunk.get(2).copied().unwrap_or(0);
|
|
||||||
out.push(ALPHABET[(b0 >> 2) as usize] as char);
|
|
||||||
out.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
|
|
||||||
if chunk.len() > 1 {
|
|
||||||
out.push(ALPHABET[(((b1 & 0x0F) << 2) | (b2 >> 6)) as usize] as char);
|
|
||||||
} else {
|
|
||||||
out.push('=');
|
|
||||||
}
|
|
||||||
if chunk.len() > 2 {
|
|
||||||
out.push(ALPHABET[(b2 & 0x3F) as usize] as char);
|
|
||||||
} else {
|
|
||||||
out.push('=');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod base64_tests {
|
|
||||||
use super::base64_encode;
|
|
||||||
|
|
||||||
// RFC 4648 §10 test vectors.
|
|
||||||
#[test]
|
|
||||||
fn rfc4648_vectors() {
|
|
||||||
assert_eq!(base64_encode(b""), "");
|
|
||||||
assert_eq!(base64_encode(b"f"), "Zg==");
|
|
||||||
assert_eq!(base64_encode(b"fo"), "Zm8=");
|
|
||||||
assert_eq!(base64_encode(b"foo"), "Zm9v");
|
|
||||||
assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
|
|
||||||
assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
|
|
||||||
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn full_byte_range_roundtrip_length() {
|
|
||||||
// 256 bytes → 256 / 3 = 85 chunks + 1 leftover (chunks_of_3 walks 85
|
|
||||||
// full + 1 padded), 86 chunks * 4 chars = 344. Cheap sanity check
|
|
||||||
// that the size calc + padding don't drift on a non-multiple input.
|
|
||||||
let bytes: Vec<u8> = (0..=255u8).collect();
|
|
||||||
let encoded = base64_encode(&bytes);
|
|
||||||
assert_eq!(encoded.len(), 344);
|
|
||||||
assert!(encoded.ends_with('='));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure the bootstrap `core` admin user + a token at
|
/// Ensure the bootstrap `core` admin user + a token at
|
||||||
/// `CORE_TOKEN_PATH`. The token is what hive-c0re uses for forgejo
|
/// `CORE_TOKEN_PATH`. The token is what hive-c0re uses for forgejo
|
||||||
/// API calls (org creation now, meta-repo push later). Returns the
|
/// API calls (org creation now, meta-repo push later). Returns the
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue