FileNode ignores disk quota

Issue Description

With a disk quota on an account (maxDiskQuota), FileNode/set never checks it. It keeps creating files after Quota/get already shows used above hardLimit, and it also replaces a file’s blob with a bigger one past the limit. In the same state a WebDAV PUT into the same account is refused with 412, and ContactCard/set returns overQuota. So only the JMAP file path lacks the check, and any user with file storage can go past the limit the admin set, without an upper bound.

Expected Behavior

A FileNode/set create, or an update that changes blobId, fails with overQuota once the quota would be exceeded, the same way WebDAV PUT does.

Actual Behavior

With hardLimit 100000 and three creates of 60000 octets each:

create 1: used before 196     -> created, used after 60388
create 2: used before 60388   -> created, used after 120580
create 3: used before 120580  -> created, used after 180772
WebDAV PUT of 60000 octets in the same state -> 412 <D:quota-not-exceeded/>

The third create starts when the account is already over the limit. Also in the over-limit state:

FileNode/set create, 1000 octets             -> created
FileNode/set update blobId, 1000 -> 150000   -> updated (used 270774, hardLimit 100000)
WebDAV overwrite, 60000 -> 70000             -> 412
ContactCard/set create                       -> overQuota "Account quota exceeded."

Reproduction Steps

  1. As admin, give a user a disk quota of 100000.
  2. As that user, call Quota/get. It shows hardLimit 100000 with FileNode in types.
  3. Upload a 60000-octet blob and create a file from it with FileNode/set.
  4. Repeat step 3 two more times with new blobs and names. Every create succeeds.
  5. PUT a 60000-octet file over WebDAV into the same account. It gets 412.
1. ["x:Account/set", {"accountId": "<admin account>", "update": {"<user account>": {"quotas/maxDiskQuota": 100000}}}, "0"]

2. ["Quota/get", {"accountId": "<acc>", "ids": null}, "0"]
   <- {"resourceType": "octets", "used": 0, "hardLimit": 100000, "scope": "account",
       "types": ["Email", "SieveScript", "FileNode", "CalendarEvent", "ContactCard"]}

3. POST /jmap/upload/<acc>/    (60000 octets, application/octet-stream)
   ["FileNode/set", {"accountId": "<acc>", "create": {"f": {"name": "big0.bin", "parentId": null, "blobId": "<blobId>"}}}, "0"],
   ["Quota/get", {"accountId": "<acc>", "ids": null}, "q"]
   <- "created": {"f": {"id": "vf"}}

5. PUT /dav/file/<user>/via-dav.bin    (60000 octets)
   <- HTTP 412 <D:error xmlns:D="DAV:"><D:quota-not-exceeded/></D:error>

Stalwart Version

v0.16.x

Installation Method

Built from source

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

Also on the now deleted 1.0 branch

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Confirmed in the source. crates/jmap/src/file/set.rs has no quota check at all on create or update, while crates/jmap/src/contact/set.rs does call the quota check that produces overQuota, and the WebDAV file PUT path enforces quota (returning the 412 quota-not-exceeded DAV error). So this matches what you found: FileNode/set is missing the disk-quota enforcement that the other write paths (WebDAV PUT, ContactCard/set) have, letting an account’s storage grow past maxDiskQuota via JMAP file creation/update with no upper bound.

This is a genuine bug rather than a documented limitation, so I can’t point you to docs explaining it as intended behavior. A human maintainer will need to follow up on fixing the enforcement in the file-node set handler.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Fixed in v1.0.0 (yet to be released).