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 (
objectstore class \OC\Files\ObjectStore\S3 in config.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=on in test)
- Storage strategy:
redis (Redis Streams adapter)
- jwt_expiry default (900s) also triggers auth failures on longer sessions → recommend increasing to 86400
Bug description
When using Nextcloud with an S3/MinIO objectstore backend, newly created
.whiteboardfiles are initialized with a 1-byte stub (space character0x20) instead of an empty file. This causesWhiteboardContentService::getContent()to fail withJsonException, making every whiteboard permanently broken — the canvas appears empty and no content is ever saved.Steps to reproduce
objectstoreclass\OC\Files\ObjectStore\S3inconfig.php)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 theGET /apps/whiteboard/{fileId}call before the frontend can initialize, the whiteboard never loads and content is never persisted.Root cause
getContent()at line ~31 ofWhiteboardContentService.php:With the S3/MinIO objectstore,
$file->getContent()returns a 1-byte non-empty string (a space0x20) for newly created files. The=== ''check evaluates tofalse, sojson_decode()is called on the 1-byte invalid content →JsonException.Note:
updateContent()already wrapsgetContent()in a try/catch for exactly this reason.getContent()itself does not.Proposed fix
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
MINIO_CI_CD=onin test)redis(Redis Streams adapter)