From 2bce0d22fd9c183fb2e706cf1c388c875994ba13 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:00:45 +0200 Subject: [PATCH] Fix JMAP: `PushSubscription/set` rejects the unpadded base64url keys the W3C Push API produces --- CHANGELOG.md | 1 + crates/jmap/src/push/set.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67954903..0fef48ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - JMAP: - Read-only sharee cannot set `isSubscribed` on a shared mailbox. - Web Push payloads with `Content-Encoding: aes128gcm` should not be base64-encoded but sent as raw bytes. + - `PushSubscription/set` rejects the unpadded base64url keys the W3C Push API produces. - `Email/import` does not send push notifications for imported messages. - `CalendarEvent/set` silently ignores `ifInState`. - MTA: DMARC is skipped when MAIL FROM SPF is unavailable. diff --git a/crates/jmap/src/push/set.rs b/crates/jmap/src/push/set.rs index 5153dd19..847a646c 100644 --- a/crates/jmap/src/push/set.rs +++ b/crates/jmap/src/push/set.rs @@ -4,7 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use base64::{Engine, engine::general_purpose}; +use base64::{ + Engine, alphabet, + engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}, +}; use common::{Server, auth::AccessToken, ipc::PushEvent}; use email::push::{Keys, PushSubscription, PushSubscriptions}; use jmap_proto::{ @@ -30,6 +33,10 @@ use utils::map::bitmap::Bitmap; const EXPIRES_MAX: i64 = 7 * 24 * 3600; // 7 days const VERIFICATION_CODE_LEN: usize = 32; +const URL_SAFE_INDIFFERENT: GeneralPurpose = GeneralPurpose::new( + &alphabet::URL_SAFE, + GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent), +); pub trait PushSubscriptionSet: Sync + Send { fn push_subscription_set( @@ -304,11 +311,11 @@ fn validate_push_value( value .get(&Key::Property(PushSubscriptionProperty::Auth)) .and_then(|v| v.as_str()) - .and_then(|v| general_purpose::URL_SAFE.decode(v.as_ref()).ok()), + .and_then(|v| URL_SAFE_INDIFFERENT.decode(v.as_ref()).ok()), value .get(&Key::Property(PushSubscriptionProperty::P256dh)) .and_then(|v| v.as_str()) - .and_then(|v| general_purpose::URL_SAFE.decode(v.as_ref()).ok()), + .and_then(|v| URL_SAFE_INDIFFERENT.decode(v.as_ref()).ok()), ) { push.keys = Some(Keys { auth, p256dh }); } else {