nextcloud / nextcloud/whiteboard
WhiteboardContentService::getContent() throws JsonException with S3/MinIO objectstore (1-byte empty file stub)
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 215
- Forks
- 39
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 32
Description
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
- Install Nextcloud with S3/MinIO objectstore (
objectstoreclass\OC\Files\ObjectStore\S3inconfig.php) - Create a new whiteboard file from the Files app
- 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=onin test) - Storage strategy:
redis(Redis Streams adapter) - jwt_expiry default (900s) also triggers auth failures on longer sessions → recommend increasing to 86400
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with WhiteboardContentService.php and its getContent() method, then reproduce the GET /apps/whiteboard/{fileId} request using a newly created file on S3/MinIO storage. Verify handling of the one-byte stub and invalid JSON, with the empty canvas loading and content saving successfully as the completion criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100