Repository navigation
[Bug]: Moving a folder to SMB external storage fails half-way (HTTP 500) if a file inside has a name SMB rejects; partial copy is left behind #65715
Description
⚠️ This issue respects the following points: ⚠️
- This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
- This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
- I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
- I agree to follow Nextcloud's Code of Conduct.
- I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.
Bug description
When a folder is moved from local storage to an SMB external storage and a file inside it has a name that icewind/smb rejects (? < > : * | ", e.g. Talk 14:15.mp4), the request fails with HTTP 500 in the middle of the copy:
- everything copied before that file stays on the target,
- the source is left untouched,
- the web UI shows a generic error. It does not say which file caused it, and offers no way to skip or rename the file.
Retrying the move raises a conflict for the folder. Folders cannot be merged (#33719), so the only way forward is to replace the target folder. That deletes the partial copy, and also any other content of the target folder (e.g. files added directly on the SMB share). Then the move fails again at the same file.
Steps to reproduce
- Configure an SMB external storage.
- In local storage, create a folder
Eventwitha.mp4,b 14:15.mp4andc.mp4. - In the Files app, move
Eventinto the SMB mount. - HTTP 500. On the share,
Event/contains the files copied beforeb 14:15.mp4(which ones depends on the readdir order). The localEvent/is unchanged.
Expected behavior
The move is rejected before anything is copied, with a 400 that names the offending file (just like an upload of that name to the SMB storage), or the file is skipped and reported. No half-moved tree and no 500.
Analysis
Each failing request logs two exceptions (paths shortened):
warning Failed to copy stream to storage
Icewind\SMB\Exception\InvalidPathException: Invalid path "/Event/b 14:15.mp4", ":" is not allowed
AbstractShare::verifyPath <- NativeShare::buildUrl <- NativeShare::write <- SMB::fopen
<- Common::writeStream <- Common::copyFromStorage <- Common::copyFromStorage <- Common::moveFromStorage
<- ... <- View::rename <- Directory::moveInto
error Uncaught exception (HTTP 500)
Icewind\SMB\Exception\InvalidPathException: same path
AbstractShare::verifyPath <- NativeShare::buildUrl <- NativeShare::rawStat <- NativeFileInfo::stat
<- NativeShare::stat <- SMB::getFileInfo <- SMB::filetype <- Common::is_dir <- SMB::unlink
<- Common::copyFromStorage <- Common::copyFromStorage <- ...
Common::copyFromStorage(): the failedwriteStream()is caught and logged. The cleanup ($this->unlink($targetInternalPath)) is not guarded.SMB:unlink()→is_dir()→filetype()→getFileInfo(). None of them handleIcewind\SMB\Exception\InvalidPathException. OnlyNotFound,Forbidden,ConnectandInvalidArgumentare mapped. The cleanup throws, and the exception leaves the recursivecopyFromStorage()in the middle of the traversal.SMBhas noverifyPath()override.View::rename()andDirectory::moveInto()only validate the top-level target name, and only against the global filename validator. No code checks the nested names against the target storage before the copy starts.
Possible fix
SMB::verifyPath(): reject the charactersicewind/smbrefuses by throwingInvalidCharacterInPathException. Direct uploads and renames on SMB would then get a clean 400 as well.- Cross-storage
View::rename()/copy()of a directory: verify every name in the source tree against the target storage before callingmoveFromStorage()/copyFromStorage(). Abort with anInvalidPathExceptionthat names the file.Directory::moveInto()maps that toInvalidPath(400) instead of a 500. - Defense in depth: in
Common::copyFromStorage(), wrap the cleanupunlink()in try/catch and only log. MapIcewind\SMB\Exception\InvalidPathExceptioninSMB::getFileInfo()toNotFoundException(a path the share cannot represent cannot exist there).
Happy to provide a PR if this direction is fine.
Nextcloud Server version
34
Operating system
None
PHP engine version
None
Web server
None
Database engine version
None
Is this bug present after an update or on a fresh install?
None
Are you using the Nextcloud Server Encryption module?
None
What user-backends are you using?
- Default user-backend (database)
- LDAP/ Active Directory
- SSO - SAML
- Other
Configuration report
List of activated Apps
Nextcloud Signing status
Nextcloud Logs
Additional info
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTo triage
Activity
susnux commentedon Oct 24, 2026
Workaround: enable windows filename compatibility if you need to interact with windows systems like SMB.