{"record":{"id":"388dcb4259966fa0","repo":"nextcloud/server","slug":"x-oc-mtime-header-must-be-a-valid-integer-unix-ti","errorCode":null,"errorMessage":"X-OC-MTime header must be a valid integer (unix timestamp), got \"%s\".","messagePattern":"X-OC-MTime header must be a valid integer \\(unix timestamp\\), got \"(.+?)\"\\.","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"warning","filePath":"apps/dav/lib/Connector/Sabre/MtimeSanitizer.php","lineNumber":19,"sourceCode":"<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-only\n */\n\nnamespace OCA\\DAV\\Connector\\Sabre;\n\nclass MtimeSanitizer {\n\tpublic static function sanitizeMtime(string $mtimeFromRequest): int {\n\t\t// In PHP 5.X \"is_numeric\" returns true for strings in hexadecimal\n\t\t// notation. This is no longer the case in PHP 7.X, so this check\n\t\t// ensures that strings with hexadecimal notations fail too in PHP 5.X.\n\t\t$isHexadecimal = preg_match('/^\\s*0[xX]/', $mtimeFromRequest);\n\t\tif ($isHexadecimal || !is_numeric($mtimeFromRequest)) {\n\t\t\tthrow new \\InvalidArgumentException(\n\t\t\t\tsprintf(\n\t\t\t\t\t'X-OC-MTime header must be a valid integer (unix timestamp), got \"%s\".',\n\t\t\t\t\t$mtimeFromRequest\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Prevent writing invalid mtime (timezone-proof)\n\t\tif ((int)$mtimeFromRequest <= 24 * 60 * 60) {\n\t\t\tthrow new \\InvalidArgumentException(\n\t\t\t\tsprintf(\n\t\t\t\t\t'X-OC-MTime header must be a valid positive unix timestamp greater than one day, got \"%s\".',\n\t\t\t\t\t$mtimeFromRequest\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\treturn (int)$mtimeFromRequest;","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php#L1-L37","documentation":"Thrown by OCA\\DAV\\Connector\\Sabre\\MtimeSanitizer::sanitizeMtime() (apps/dav/lib/Connector/Sabre/MtimeSanitizer.php:19) when the mtime a client sends is not a usable number. Nextcloud lets upload clients set the file modification time via the X-OC-MTime (also X-OC-CTime and x-file-mtime in bulk upload) request header, and this sanitizer rejects anything non-numeric — including hexadecimal-looking strings like 0x1F, which would pass is_numeric() on old PHP versions.","triggerScenarios":"A PUT /remote.php/dav/files/... with X-OC-MTime: abc, X-OC-MTime: 0x513A4B7F, an empty header, or a locale-formatted timestamp like '1.706.544.000'; also bulk upload (DELETION-list multipart POST) with a non-numeric x-file-mtime / x-oc-mtime per-file header, and chunked upload MOVE with X-OC-MTime from a client that formatted the value incorrectly.","commonSituations":"Desktop/mobile sync clients or shell scripts (curl) that copy a display-formatted date into the header instead of a unix timestamp; code doing date('Y-m-d H:i:s') instead of time(); leading/trailing garbage or a BOM in the header; clients porting from other WebDAV servers that accept ISO dates.","solutions":["Send a plain base-10 unix timestamp in seconds, e.g. X-OC-MTime: 1706544000 — verify with echo time();","If the value comes from a date string, convert it first: strtotime($dateString) or (new DateTime($dateString))->getTimestamp().","Check for invisible characters (BOM, whitespace, quotes) in generated headers; the whole header value must match is_numeric().","If you intentionally do not want to set mtime, omit the X-OC-MTime / X-OC-CTime header entirely — the server then uses the upload time."],"exampleFix":"# before\ncurl -T file.txt -H \"X-OC-MTime: 2024-01-29 12:00:00\" https://cloud/remote.php/dav/files/alice/file.txt\n\n# after\nMTIME=$(stat -c %Y file.txt)\ncurl -T file.txt -H \"X-OC-MTime: ${MTIME}\" https://cloud/remote.php/dav/files/alice/file.txt","handlingStrategy":"validation","validationCode":"// before uploading, validate the header value exactly like the server does\nfunction isValidMtimeHeader(string $value): bool {\n    if (preg_match('/^\\s*0[xX]/', $value) === 1) {\n        return false; // hex notation rejected\n    }\n    if (!is_numeric($value)) {\n        return false;\n    }\n    return (int)$value > 86400; // also covers the 'greater than one day' rule\n}\n\n$mtime = (string)filemtime($localPath);\nif (!isValidMtimeHeader($mtime)) {\n    $headers = []; // omit the header, let the server use upload time\n} else {\n    $headers = ['X-OC-MTime' => $mtime];\n}","typeGuard":"function isValidMtimeHeader(string $value): bool {\n    return preg_match('/^\\s*0[xX]/', $value) !== 1 && is_numeric($value) && (int)$value > 86400;\n}","tryCatchPattern":"try {\n    MtimeSanitizer::sanitizeMtime($mtimeFromRequest);\n} catch (\\InvalidArgumentException $e) {\n    // 400-class client error: fix the header, never retry unchanged\n    throw new \\Sabre\\DAV\\Exception\\BadRequest($e->getMessage());\n}","preventionTips":["Always derive X-OC-MTime from filemtime()/stat, not from formatted dates.","Unit-test header generation against is_numeric() before release.","Omit the header rather than sending 0 or placeholder values."],"tags":["dav","header-validation","mtime","upload","bad-request"],"backgroundTag":"invalid-request-header","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}