Skip to content

WhiteboardContentService::getContent() throws JsonException with S3/MinIO objectstore (1-byte empty file stub) #1206

Description

@oboeglen

Bug description

When using Nextcloud with an S3/MinIO objectstore backend, newly created .whiteboard files are initialized with a 1-byte stub (space character 0x20) instead of an empty file. This causes WhiteboardContentService::getContent() to fail with JsonException, making every whiteboard permanently broken — the canvas appears empty and no content is ever saved.

Steps to reproduce

  1. Install Nextcloud with S3/MinIO objectstore (objectstore class \OC\Files\ObjectStore\S3 in config.php)
  2. Create a new whiteboard file from the Files app
  3. Open the whiteboard

Expected behavior

The whiteboard opens with an empty canvas, the user can draw, and content is saved correctly.

Actual behavior

The whiteboard fails to initialize. The following errors are logged repeatedly in nextcloud.log:

{"app":"whiteboard","message":"Exception handled: JsonException",
 "data":{"file":"WhiteboardContentService.php","line":"37","status_code":"4"}}
{"app":"PHP","message":"Undefined array key 4 at AppFramework/Http.php#106"}

The whiteboard backend WebSocket does receive the connection (visible in backend logs), but since getContent() throws on the GET /apps/whiteboard/{fileId} call before the frontend can initialize, the whiteboard never loads and content is never persisted.

Root cause

getContent() at line ~31 of WhiteboardContentService.php:

public function getContent(File $file): array {
    $fileContent = $file->getContent();
    if ($fileContent === '') {          // ← strict equality misses 1-byte content
        $fileContent = '{"elements":[],"scrollToContent":true}';
    }
    return json_decode($fileContent, true, 512, JSON_THROW_ON_ERROR); // ← throws on " "
}

With the S3/MinIO objectstore, $file->getContent() returns a 1-byte non-empty string (a space 0x20) for newly created files. The === '' check evaluates to false, so json_decode() is called on the 1-byte invalid content → JsonException.

Note: updateContent() already wraps getContent() in a try/catch for exactly this reason. getContent() itself does not.

Proposed fix

public function getContent(File $file): array {
    $fileContent = $file->getContent();
    if ($fileContent === '' || trim($fileContent) === '') {
        $fileContent = '{"elements":[],"scrollToContent":true}';
    }

    try {
        return json_decode($fileContent, true, 512, JSON_THROW_ON_ERROR);
    } catch (\JsonException $e) {
        // Corrupted or 1-byte MinIO stub — return safe default
        return ['elements' => [], 'files' => [], 'scrollToContent' => true];
    }
}

Additional context

A second related bug was found: the whiteboard save endpoint (PUT /apps/whiteboard/{fileId}) must be explicitly whitelisted if a reverse proxy (e.g. HAProxy) restricts HTTP methods — since it is not under /remote.php, /public.php, or /ocs, it is easy to inadvertently block it.

Environment

  • Nextcloud: 33.0.3
  • Whiteboard app + backend: 1.5.8
  • Objectstore: MinIO S3-compatible (4 nodes, erasure coding, MINIO_CI_CD=on in test)
  • Storage strategy: redis (Redis Streams adapter)
  • jwt_expiry default (900s) also triggers auth failures on longer sessions → recommend increasing to 86400

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions