Skip to main content

secure_gate/traits/decoding/
base64_url.rs

1//! URL-safe Base64 decoding trait.
2//!
3//! > **Import path:** `use secure_gate::FromBase64UrlStr;`
4//!
5//! This trait provides secure, explicit decoding of base64url-encoded strings
6//! (URL-safe alphabet, no padding) to byte vectors. It is designed for handling
7//! untrusted input in cryptographic contexts, such as decoding encoded keys or tokens.
8//!
9//! Requires the `encoding-base64` feature.
10//!
11//! # Security Notes
12//!
13//! - **Treat all input as untrusted**: validate base64url strings upstream before
14//!   wrapping in secrets. Invalid input may indicate tampering or injection attempts.
15//! - **Heap allocation**: Returns `Vec<u8>` — wrap in [`Fixed`](crate::Fixed) or
16//!   [`Dynamic`](crate::Dynamic) to store as a secret.
17//! - **Strict validation**: URL-safe alphabet, no padding, per RFC 4648 §5. Invalid input fails immediately.
18//! - **URL-safe alphabet**: Uses `-` and `_` instead of `+` and `/`.
19//!
20//! # Example
21//!
22//! ```rust
23//! # #[cfg(feature = "encoding-base64")]
24//! use secure_gate::{FromBase64UrlStr, Fixed};
25//! # #[cfg(feature = "encoding-base64")]
26//! {
27//! // "AQIDBA" decodes to [1, 2, 3, 4]
28//! let bytes = "AQIDBA".try_from_base64url().unwrap();
29//! assert_eq!(bytes, vec![1, 2, 3, 4]);
30//!
31//! // Wrap result in a secret immediately
32//! let secret: Fixed<[u8; 3]> = Fixed::try_from_base64url("AQID").unwrap();
33//!
34//! // Error on invalid input
35//! assert!("!!!".try_from_base64url().is_err());
36//! }
37//! ```
38#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
39use crate::error::Base64Error;
40
41/// Extension trait for decoding URL-safe base64 strings into byte vectors.
42///
43/// *Requires features `encoding-base64` and `alloc`.*
44///
45/// Blanket-implemented for all `AsRef<str>` types. Returns `Vec<u8>` — requires heap
46/// allocation. For no-alloc targets, use `Fixed::try_from_base64url` instead, which
47/// decodes directly into a stack-allocated `[u8; N]` buffer.
48///
49/// Uses the RFC 4648 URL-safe alphabet without `=` padding. Treat all input as
50/// untrusted; validate lengths and content upstream before wrapping decoded bytes
51/// in secrets.
52#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
53pub trait FromBase64UrlStr {
54    /// Decodes a URL-safe base64 string (no padding) into a byte vector.
55    ///
56    /// # Errors
57    ///
58    /// - [`Base64Error::InvalidBase64`] — invalid characters or unexpected padding.
59    ///
60    /// # Examples
61    ///
62    /// ```rust
63    /// use secure_gate::FromBase64UrlStr;
64    ///
65    /// // "AQIDBA" decodes to [1, 2, 3, 4]
66    /// let bytes = "AQIDBA".try_from_base64url()?;
67    /// assert_eq!(bytes, [1, 2, 3, 4]);
68    ///
69    /// assert!("!!!".try_from_base64url().is_err()); // invalid chars
70    /// # Ok::<(), secure_gate::Base64Error>(())
71    /// ```
72    fn try_from_base64url(&self) -> Result<alloc::vec::Vec<u8>, Base64Error>;
73}
74
75// Blanket impl to cover any AsRef<str> (e.g., &str, String, etc.)
76// Returns Vec<u8> — alloc required.
77#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
78impl<T: AsRef<str> + ?Sized> FromBase64UrlStr for T {
79    fn try_from_base64url(&self) -> Result<alloc::vec::Vec<u8>, Base64Error> {
80        use base64ct::{Base64UrlUnpadded, Encoding};
81        Base64UrlUnpadded::decode_vec(self.as_ref()).map_err(|_| Base64Error::InvalidBase64)
82    }
83}