use std::fmt;
use std::hash::{Hash, Hasher};
use zeroize::Zeroize;
pub const JSON_BODY_LIMIT: u64 = 1024 * 1024;
pub const LARGE_BODY_LIMIT: u64 = 16 * 1024 * 1024;
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct SecretString(String);
impl SecretString {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn expose(&self) -> &str {
&self.0
}
}
impl fmt::Debug for SecretString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[REDACTED]")
}
}
impl fmt::Display for SecretString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[REDACTED]")
}
}
impl PartialEq for SecretString {
fn eq(&self, other: &Self) -> bool {
constant_time_compare(&self.0, &other.0)
}
}
impl Eq for SecretString {}
impl Hash for SecretString {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl From<String> for SecretString {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for SecretString {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl serde::Serialize for SecretString {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str("[REDACTED]")
}
}
impl<'de> serde::Deserialize<'de> for SecretString {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer).map(SecretString)
}
}
impl schemars::JsonSchema for SecretString {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("SecretString")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
String::json_schema(generator)
}
}
pub fn constant_time_compare(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
let len = a.len().max(b.len());
let mut result = (a.len() != b.len()) as u8;
for i in 0..len {
let x = if i < a.len() { a[i] } else { 0 };
let y = if i < b.len() { b[i] } else { 0 };
result |= x ^ y;
}
result == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_equal_strings() {
assert!(constant_time_compare("abc", "abc"));
assert!(constant_time_compare("", ""));
assert!(constant_time_compare(
"longer-string-123",
"longer-string-123"
));
}
#[test]
fn test_different_strings() {
assert!(!constant_time_compare("abc", "abd"));
}
#[test]
fn test_different_lengths() {
assert!(!constant_time_compare("abc", "ab"));
assert!(!constant_time_compare("abc", "abcd"));
assert!(!constant_time_compare("", "a"));
assert!(!constant_time_compare("a", ""));
}
#[test]
fn test_body_limits() {
assert_eq!(JSON_BODY_LIMIT, 1024 * 1024);
assert_eq!(LARGE_BODY_LIMIT, 16 * 1024 * 1024);
}
}