perf: round 19 — auth/WOPI/vCard/PROPFIND per-request & per-row alloc cuts

Benchmark-gated (examples/bench_round19_micro.rs, benches/ROUND19.md): every
section ships a BEFORE/AFTER counting-allocator arm with a byte/-value
equivalence gate and a GATE-FAIL-rollback exit. All eight pass. No Postgres.

- M1 verify_basic_auth cache key: blake3::hash(format!("{u}:{p}")) → incremental
  Hasher (byte-identical key, 2→0 allocs on every Basic-auth DAV request)
- M2 WopiTokenService: prebuild Validation/DecodingKey/EncodingKey in new()
  instead of per-call (mirrors JwtTokenService; 16→12 allocs/validate)
- V1/V2 vCard emit (contact_to_vcard/generate_vcard): FN fallback drops the
  throwaway to_string, NOTE skips the escape copy for newline-free notes, REV
  uses new common::fmt::compact_ical_utc stack renderer (11.5× vs chrono
  strftime, 3→0 allocs); per-contact 9→4 allocs
- M4 trash_service::row_to_item_dto: move name/path/blob_hash out of the owned
  row instead of cloning (3 clones/file row gone)
- M5 search cache key: Uuid::hyphenated().encode_lower stack buffer instead of
  to_string (identical u64 key, 1→0 allocs/request)
- M6 streaming PROPFIND: reuse one href buffer across the page instead of a
  format! per child (native + NC handlers; 192→3 allocs on a 64-child page)
- M7 nextcloud extract_url_user: return Cow instead of forcing into_owned
  (zero-alloc on the common ASCII-username path)

common::fmt::compact_ical_utc added with chrono-parity unit tests (CASES +
60-year sweep). cargo fmt + clippy --all-targets clean; 526 lib unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ront9bk7YMoffVQkGG47gh
This commit is contained in:
Claude
2026-07-19 22:29:48 +00:00
parent dc0c53ea0f
commit 9754aecfa9
13 changed files with 1240 additions and 70 deletions
+26 -27
View File
@@ -31,14 +31,24 @@ pub struct WopiTokenClaims {
/// Service for generating and validating WOPI access tokens.
pub struct WopiTokenService {
secret: String,
/// Pre-built signing key — `EncodingKey::from_secret` copies the secret into
/// a fresh `Vec` on each call, so build it once (mirrors `JwtTokenService`).
encoding_key: EncodingKey,
/// Pre-built verification key — same copy-per-call cost as `encoding_key`.
decoding_key: DecodingKey,
/// Pre-built HS256 validation config — `Validation::new` allocates a
/// `required_spec_claims` HashSet + an `algorithms` Vec; Office/Collabora
/// hosts poll `validate_token` continuously (benches/ROUND19.md §M2).
validation: Validation,
token_ttl_secs: i64,
}
impl WopiTokenService {
pub fn new(secret: String, token_ttl_secs: i64) -> Self {
Self {
secret,
encoding_key: EncodingKey::from_secret(secret.as_bytes()),
decoding_key: DecodingKey::from_secret(secret.as_bytes()),
validation: Validation::new(Algorithm::HS256),
token_ttl_secs,
}
}
@@ -64,12 +74,7 @@ impl WopiTokenService {
iat: now,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(self.secret.as_bytes()),
)
.map_err(|e| {
let token = encode(&Header::default(), &claims, &self.encoding_key).map_err(|e| {
DomainError::new(
ErrorKind::InternalError,
"WopiTokenService",
@@ -83,25 +88,19 @@ impl WopiTokenService {
/// Validate a WOPI access token and extract its claims.
pub fn validate_token(&self, token: &str) -> Result<WopiTokenClaims, DomainError> {
let validation = Validation::new(Algorithm::HS256);
let token_data = decode::<WopiTokenClaims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&validation,
)
.map_err(|e| match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => DomainError::new(
ErrorKind::AccessDenied,
"WopiTokenService",
"WOPI token expired",
),
_ => DomainError::new(
ErrorKind::AccessDenied,
"WopiTokenService",
format!("Invalid WOPI token: {}", e),
),
})?;
let token_data = decode::<WopiTokenClaims>(token, &self.decoding_key, &self.validation)
.map_err(|e| match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => DomainError::new(
ErrorKind::AccessDenied,
"WopiTokenService",
"WOPI token expired",
),
_ => DomainError::new(
ErrorKind::AccessDenied,
"WopiTokenService",
format!("Invalid WOPI token: {}", e),
),
})?;
let claims = token_data.claims;