[Bug]: WebDAV PUT overwriting an existing dotfile returns 500 (data directory on CIFS/SMB, SSE enabled)
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
⚠️ This issue respects the following points: ⚠️
- This is a bug, not a question or a configuration/webserver/proxy issue.
- This issue is not already reported on Github OR Nextcloud Community Forum (I've searched it).
- Nextcloud Server is up to date. See Maintenance and Release Schedule for supported versions.
- I agree to follow Nextcloud's Code of Conduct.
Bug description
A WebDAV PUT that overwrites an existing file whose basename begins with . always returns HTTP 500. Creating the same file succeeds. Overwriting a
non-dotfile succeeds. Overwriting the very same dotfile via occ files:put succeeds.
The practical effect is that dotfiles stop syncing silently and the server keeps its last-good copy indefinitely. For .gitignore this is damaging: a
stale, shorter .gitignore stays authoritative server-side, so other clients un-ignore whole build trees.
Note that the error message is misleading. It reports copied: -1 byte, expected filesize: N bytes, but -1 is the initialiser of $count in File::put() —
writeStream() throws before ever assigning it. It is not a byte count and does not indicate a short write.
Possibly the same as #21209 ("500 error when updating dotfiles", 2020), which reported the same trigger — leading dot, overwrite, CIFS-mounted data
directory with seal. It was closed in 2021 as a duplicate of #19317, which concerns trailing dots in directory names and is a different bug; no fix ever
landed. It may not be the same defect, though: #21209 ran without encryption, reported copied bytes: 0 rather than -1, and was diagnosed as Permission
denied in Local.php — a mechanism that does not reproduce here (see Additional info).
Steps to reproduce
No Nextcloud client required:
printf one > /tmp/x1
printf twotwotwo > /tmp/x2
B=https:///remote.php/dav/files/
A=":"
curl -su "$A" -T /tmp/x1 "$B/.curlprobe" -o /dev/null -w 'dot create: %{http_code}\n'
curl -su "$A" -T /tmp/x2 "$B/.curlprobe" -o /dev/null -w 'dot overwrite: %{http_code}\n'
curl -su "$A" -T /tmp/x1 "$B/curlprobe-plain" -o /dev/null -w 'plain create: %{http_code}\n'
curl -su "$A" -T /tmp/x2 "$B/curlprobe-plain" -o /dev/null -w 'plain overwrite: %{http_code}\n'
Observed:
dot create: 201 plain create: 201
dot overwrite: 500 <--- plain overwrite: 204
Only a leading dot in the basename predicts failure, and only on overwrite. All probes in one directory:
| file | create | overwrite |
|---|---|---|
probe-plain.txt |
201 | 204 OK |
noext (no extension at all) |
201 | 204 OK |
.probe-dotfile (byte-identical to probe-plain.txt) |
201 | 500 FAIL |
.trivial-name-test (8 bytes) |
201 | 500 FAIL |
.a.b (leading dot, non-empty pathinfo filename) |
201 | 500 FAIL |
| .a.b failing rules out the pathinfo('.gitignore') empty-filename quirk; noext succeeding rules out anything extension-related. |
Expected behavior
Overwriting a dotfile via WebDAV succeeds, as it does for every other filename, and as it does for the same file via occ files:put.
Nextcloud Server version
33
Operating system
Debian/Ubuntu
PHP engine version
PHP 8.3
Web server
Apache (supported)
Database engine version
PostgreSQL
Is this bug present after an update or on a fresh install?
None
Are you using the Nextcloud Server Encryption module?
Encryption is Enabled
What user-backends are you using?
- Default user-backend (database)
- LDAP/ Active Directory
- SSO - SAML
- Other
Configuration report
{
"system": {
"one-click-instance": true,
"one-click-instance.user-limit": 100,
"memcache.local": "\\OC\\Memcache\\APCu",
"apps_paths": [
{
"path": "\/var\/www\/html\/apps",
"url": "\/apps",
"writable": false
},
{
"path": "\/var\/www\/html\/custom_apps",
"url": "\/custom_apps",
"writable": true
}
],
"check_data_directory_permissions": false,
"memcache.distributed": "\\OC\\Memcache\\Redis",
"memcache.locking": "\\OC\\Memcache\\Redis",
"redis": {
"host": "***REMOVED SENSITIVE VALUE***",
"password": "***REMOVED SENSITIVE VALUE***",
"port": 6379,
"timeout": 3,
"read_timeout": 10
},
"overwritehost": "trantor.justinscholz.de",
"overwriteprotocol": "https",
"passwordsalt": "***REMOVED SENSITIVE VALUE***",
"secret": "***REMOVED SENSITIVE VALUE***",
"trusted_domains": [
"localhost",
"trantor.justinscholz.de"
],
"datadirectory": "***REMOVED SENSITIVE VALUE***",
"dbtype": "pgsql",
"version": "33.0.7.1",
"overwrite.cli.url": "https:\/\/trantor.justinscholz.de\/",
"dbname": "***REMOVED SENSITIVE VALUE***",
"dbhost": "***REMOVED SENSITIVE VALUE***",
"dbport": "",
"dbtableprefix": "oc_",
"dbuser": "***REMOVED SENSITIVE VALUE***",
"dbpassword": "***REMOVED SENSITIVE VALUE***",
"installed": true,
"instanceid": "***REMOVED SENSITIVE VALUE***",
"maintenance": false,
"updatechecker": false,
"loglevel": "2",
"log_type": "file",
"logfile": "\/var\/www\/html\/data\/nextcloud.log",
"log_rotate_size": 10485760,
"log.condition": {
"apps": [
"admin_audit"
]
},
"preview_max_x": 2048,
"preview_max_y": 2048,
"jpeg_quality": 60,
"enable_previews": true,
"upgrade.disable-web": true,
"mail_smtpmode": "smtp",
"trashbin_retention_obligation": "auto, 30",
"versions_retention_obligation": "auto, 30",
"activity_expire_days": 30,
"simpleSignUpLink.shown": false,
"share_folder": "\/Shared",
"one-click-instance.link": "https:\/\/nextcloud.com\/all-in-one\/",
"upgrade.cli-upgrade-link": "https:\/\/github.com\/nextcloud\/all-in-one\/discussions\/2726",
"updatedirectory": "\/nc-updater",
"maintenance_window_start": 100,
"allow_local_remote_servers": true,
"davstorage.request_timeout": 7200,
"documentation_url.server_logs": "https:\/\/github.com\/nextcloud\/all-in-one\/discussions\/5425",
"htaccess.RewriteBase": "\/",
"dbpersistent": false,
"auth.bruteforce.protection.enabled": true,
"ratelimit.protection.enabled": true,
"files_external_allow_create_new_local": false,
"trusted_proxies": "***REMOVED SENSITIVE VALUE***",
"preview_imaginary_key": "***REMOVED SENSITIVE VALUE***",
"mail_sendmailmode": "smtp",
"memories.db.triggers.fcu": true,
"memories.exiftool": "\/var\/www\/html\/custom_apps\/memories\/bin-ext\/exiftool-amd64-musl",
"memories.vod.path": "\/var\/www\/html\/custom_apps\/memories\/bin-ext\/go-vod-amd64",
"memories.vod.ffmpeg": "\/usr\/bin\/ffmpeg",
"memories.vod.ffprobe": "\/usr\/bin\/ffprobe",
"memories.gis_type": 2,
"mail_from_address": "***REMOVED SENSITIVE VALUE***",
"mail_domain": "***REMOVED SENSITIVE VALUE***",
"mail_smtpauth": true,
"mail_smtphost": "***REMOVED SENSITIVE VALUE***",
"mail_smtpport": "587",
"mail_smtpname": "***REMOVED SENSITIVE VALUE***",
"mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
"default_phone_region": "DE",
"serverid": 445,
"log_type_audit": "file",
"logfile_audit": "\/var\/www\/html\/data\/audit.log",
"update_channel": "stable",
"eurooffice": {
"editors_check_interval": 0,
"jwt_secret": "2ec4989cbd196a2a8efa03b65fab8f950892afeb02806969",
"jwt_header": "AuthorizationJwt"
},
"enabledPreviewProviders": {
"24": "OCA\\Eurooffice\\Preview"
},
"filelocking.enabled": true,
"DOMAIN": "trantor.justinscholz.de",
"AIO_VERSION": "v13.4.1"
}
}
List of activated Apps
Enabled:
- activity: 6.0.0
- admin_audit: 1.23.0
- bruteforcesettings: 6.0.0
- calendar: 6.5.2
- circles: 33.0.0
- cloud_federation_api: 1.17.0
- collectives: 4.4.3
- comments: 1.23.0
- contacts: 8.7.5
- contactsinteraction: 1.14.1
- dashboard: 7.13.0
- dav: 1.36.0
- deck: 1.17.4
- encryption: 2.21.0
- eurooffice: 11.0.1
- federatedfilesharing: 1.23.0
- federation: 1.23.0
- files: 2.5.0
- files_downloadlimit: 5.1.0
- files_external: 1.25.1
- files_fulltextsearch: 33.0.0
- files_pdfviewer: 6.0.0
- files_reminders: 1.6.0
- files_sharing: 1.25.2
- files_trashbin: 1.23.0
- files_versions: 1.26.0
- firstrunwizard: 6.0.0
- forms: 5.3.5
- fulltextsearch: 33.0.0
- fulltextsearch_elasticsearch: 33.0.0
- logreader: 6.0.0
- lookup_server_connector: 1.21.0
- mail: 5.10.11
- nextcloud-aio: 0.8.0
- nextcloud_announcements: 5.0.0
- notes: 6.0.1
- notifications: 6.0.0
- notify_push: 1.3.5
- oauth2: 1.21.0
- password_policy: 5.0.0
- photos: 6.0.0
- polls: 9.1.4
- privacy: 5.0.0
- profile: 1.2.0
- provisioning_api: 1.23.0
- recommendations: 6.0.0
- related_resources: 4.0.0
- serverinfo: 5.0.0
- settings: 1.16.0
- sharebymail: 1.23.0
- spreed: 23.0.9
- support: 5.0.0
- survey_client: 5.0.0
- suspicious_login: 11.0.0
- systemtags: 1.23.0
- tasks: 0.18.1
- text: 7.0.1
- theming: 2.8.0
- twofactor_backupcodes: 1.22.0
- twofactor_totp: 15.0.0
- updatenotification: 1.23.0
- user_status: 1.13.0
- viewer: 6.0.0
- weather_status: 1.13.0
- webhook_listeners: 1.5.0
- whiteboard: 1.5.9
- workflowengine: 2.15.0
Disabled:
- app_api: 33.0.0 (installed 5.0.2)
- memories: 8.1.0 (installed 8.1.0)
- twofactor_nextcloud_notification: 7.0.0
- user_ldap: 1.24.0
Nextcloud Signing status
No errors have been found.
Nextcloud Logs
{"reqId":"...","level":3,"app":"webdav","method":"PUT",
"url":"/remote.php/dav/files/<user>/<path>/.gitignore",
"message":"Error while writing stream to storage: Failed to open files/<path>/.gitignore for writing",
"version":"33.0.7.1",
"exception":{
"Exception":"OCP\\Files\\GenericFileException",
"Message":"Failed to open files/<path>/.gitignore for writing",
"File":"/var/www/html/lib/private/Files/Storage/Wrapper/Encryption.php",
"Line":915,
"Trace":[
{"file":"/var/www/html/lib/private/Files/Storage/Wrapper/Wrapper.php","line":330,
"function":"writeStream","class":"OC\\Files\\Storage\\Wrapper\\Encryption"},
{"file":"/var/www/html/apps/dav/lib/Connector/Sabre/File.php","line":223,
"function":"writeStream","class":"OC\\Files\\Storage\\Wrapper\\Wrapper"},
{"file":"/var/www/html/3rdparty/sabre/dav/lib/DAV/Server.php","line":1137,
"function":"put","class":"OCA\\DAV\\Connector\\Sabre\\File"}
]}}
{"reqId":"...","level":3,"method":"PUT","message":"Uncaught exception",
"exception":{
"Exception":"Sabre\\DAV\\Exception",
"Message":"Error while copying file to target location (copied: -1 byte, expected filesize: 1322 bytes)",
"File":"/var/www/html/apps/dav/lib/Connector/Sabre/File.php",
"Line":244}}
Additional info
Encryption::writeStream() throws because Encryption::fopen($path, 'w') returned false.
Ruled out, each tested directly:
- Filesystem / SMB. Data directory is a CIFS/SMB3 mount (Hetzner Storage Box). Dotfile create and overwrite succeed on that mount as root and as
www-data, including aged well past actimeo=1/closetimeo=1. - PHP itself. As www-data inside the container, php -r 'var_dump(fopen("/.d","w"));' returns a resource for a dotfile on that mount, with no
error. So neither PHP nor the kernel is refusing the open. - Filename policy. blacklisted_files, forbidden_filenames, forbidden_filename_basenames, forbidden_filename_characters, forbidden_filename_extensions are
all unset. - File corruption / keys. occ files:get /dev/null decrypts the existing file cleanly.
- occ files:put overwrites the same dotfile successfully, through the same wrapper onto the same storage.
- Content, size, specific name. See the probe table — byte-identical content succeeds under a non-dot name, and an 8-byte dotfile fails.
- Source review. Encryption::fopen() on stable33 contains no basename, pathinfo, dot or hidden-file logic; its only explicit return false passes through
a non-resource from the wrapped storage.
So raw PHP fopen($path,'w') on the exact path works, while Encryption::fopen() on the same path returns false. The gap is inside the storage-wrapper
chain. One link I could not test from outside is \OC\Files\Stream\Encryption::wrap(), which fopen() returns directly and which can itself return false.
Possibly relevant: in the failing trace, writeStream() receives the live path, not a part file — no
/.ocTransferId….part. SoneedsPartFile() appears to be false here and DAV truncates the real encrypted file in place.
Mount options for the data directory:
cifs (rw,relatime,vers=3.1.1,cache=strict,seal,hard,nounix,serverino,mapposix,
forceuid,uid=33,forcegid,gid=33,file_mode=0755,dir_mode=0755,
actimeo=1,closetimeo=1,rsize=4194304,wsize=4194304)
Workaround: delete the file and let it be re-created; do not retry the overwrite. Renaming is not a workaround — the desktop client detects the rename
and issues a MOVE, which preserves the stale server-side content.
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 at apps/dav/lib/Connector/Sabre/File.php, especially put(), and follow the writeStream() path through lib/private/Files/Storage/Wrapper/Encryption.php, where Encryption::fopen() returns false. Reproduce the WebDAV create-and-overwrite sequence with a leading-dot filename, then compare it with occ files:put and a non-dotfile overwrite. Done means WebDAV overwrites dotfiles successfully without the misleading 500 response.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100