[Bug]: DAV Forbidden/InvalidPath responses break on "&" in the message (DOMDocument::createElementNS(): unterminated entity reference)
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 72/100
Línea de trabajo
El problema está en serialize() de apps/dav/lib/Connector/Sabre/Exception/Forbidden.php, alrededor de la línea 46, donde el mensaje se pasa como argumento de valor de createElementNS para o:reason. El mismo patrón aparece en InvalidPath.php, alrededor de la línea 58, y la línea 247 de Server.php muestra el escapado correcto para s:message. Empieza reproduciendo el problema con el breve fragmento de PHP del issue y luego cambia ambas clases para añadir un nodo de texto en su lugar. Está terminado cuando el elemento o:reason contiene el mensaje escapado sin ninguna advertencia de PHP y las pruebas unitarias cubren ambas clases de excepción.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
⚠️ 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 DAV request fails with OCA\DAV\Connector\Sabre\Exception\Forbidden and the exception message contains an & (e.g. a folder named Muster GmbH & Co. KG), the XML error response is not built correctly. With libxml2 2.9.14 (Debian-based PHP images) PHP logs a warning and <o:reason> is sent empty. With libxml2 2.13.9 (Alpine) there is no warning, but the & is silently dropped from the message.
Cause: Forbidden::serialize() passes the message as the value argument of DOMDocument::createElementNS():
$error = $errorNode->ownerDocument->createElementNS('o:', 'o:reason', $this->getMessage());
PHP does not escape & in that argument and treats it as the start of an entity reference (documented in the PHP manual, see also PHP bug #31191, "Won't fix"). The same pattern was reported for ownCloud's bundled Sabre back in ownCloud 4.5.5: owncloud/core#1223. apps/dav/lib/Connector/Sabre/Exception/InvalidPath.php#L58 has the same pattern. apps/dav/lib/Connector/Sabre/Server.php#L247 escapes the same message correctly for <s:message>, so only these two exception classes are affected.
Seen in practice with a desktop client that tried to move a received share into another share. The message comes from lib/private/Files/View.php#L923 (Moving a storage (%s) into a share (%s) is not allowed).
Suggested fix
Create the element empty and append a text node (escapes automatically), in both Forbidden.php and InvalidPath.php. The dav app already does this in apps/dav/lib/Exception/UidConflict.php#L76-L77. Verified with the PHP reproduction above on both libxml2 versions, and end to end on the instance above (both files patched, same steps). <o:reason> then carries the escaped message and no warning is logged:
<o:reason xmlns:o="o:">Moving a storage (Muster GmbH & Co. KG) into a share (Projekt) is not allowed</o:reason>
A PR with unit tests for both classes follows:
$error = $errorNode->ownerDocument->createElementNS('o:', 'o:reason');
$error->appendChild($errorNode->ownerDocument->createTextNode($this->getMessage()));
Side note: when the root of a received share is moved, View::rename() passes the parent folder's mount as $sourceMount to validateMountMove(), so the message says "Moving a storage" although a share is being moved.
Environment
- Nextcloud Server: 35.0.2 (Docker, Apache image)
- Database: MariaDB 11.8
- PHP: 8.5.12, libxml2 2.9.14
- Bug also present in current master (checked at 0554acf).
Steps to reproduce
Minimal reproduction in plain PHP, same calls as Forbidden::serialize():
<?php
$msg = 'Moving a storage (Muster GmbH & Co. KG) into a share (Projekt) is not allowed';
$DOM = new DOMDocument('1.0', 'utf-8');
$errorNode = $DOM->createElementNS('DAV:', 'd:error');
$DOM->appendChild($errorNode);
// current code
$old = $errorNode->ownerDocument->createElementNS('o:', 'o:reason', $msg);
echo $DOM->saveXML($old), PHP_EOL;
// proposed fix
$new = $errorNode->ownerDocument->createElementNS('o:', 'o:reason');
$new->appendChild($errorNode->ownerDocument->createTextNode($msg));
echo $DOM->saveXML($new), PHP_EOL;
Output with php:8.5-cli (PHP 8.5.12, libxml2 2.9.14):
Warning: DOMDocument::createElementNS(): unterminated entity reference Co. KG) into a share (Projekt) is not allowed in /d.php on line 8
<o:reason xmlns:o="o:"/>
<o:reason xmlns:o="o:">Moving a storage (Muster GmbH & Co. KG) into a share (Projekt) is not allowed</o:reason>
Output with php:8.5-cli-alpine (PHP 8.5.12, libxml2 2.13.9), no warning:
<o:reason xmlns:o="o:">Moving a storage (Muster GmbH Co. KG) into a share (Projekt) is not allowed</o:reason>
<o:reason xmlns:o="o:">Moving a storage (Muster GmbH & Co. KG) into a share (Projekt) is not allowed</o:reason>
End to end, verified on a Nextcloud 35.0.2 instance (PHP 8.5.12, libxml2 2.9.14):
- As
bugtest-alice, create the foldersMuster GmbH & Co. KGandProjektand share both withbugtest-bob. - As
bugtest-bob, moveMuster GmbH & Co. KGintoProjektvia WebDAV:
curl -u bugtest-bob:PASS -X MOVE "https://HOST/remote.php/dav/files/bugtest-bob/Muster%20GmbH%20%26%20Co.%20KG" \
-H "Destination: https://HOST/remote.php/dav/files/bugtest-bob/Projekt/Muster%20GmbH%20%26%20Co.%20KG"
- Check the response body and
nextcloud.log.
Expected behavior
HTTP 403, with the full message in <o:reason>, and no PHP warning.
Actual behavior
HTTP 403. Note that <s:message> (built in Server.php) is escaped correctly, while <o:reason> in the same response is empty:
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:o="http://owncloud.org/ns">
<s:exception>OCA\DAV\Connector\Sabre\Exception\Forbidden</s:exception>
<s:message>Moving a storage (Muster GmbH & Co. KG) into a share (Projekt) is not allowed</s:message>
<o:retry xmlns:o="o:">false</o:retry>
<o:reason xmlns:o="o:"/>
</d:error>
The log gets this warning (from the reproduction run; first seen in production with the desktop client):
{
"reqId": "xp5UETOUhmbX1eFxZSwi",
"level": 2,
"time": "2026-11-02T10:14:09+00:00",
"remoteAddr": "fd00:beef:1337::1",
"user": "bugtest-bob",
"app": "PHP",
"method": "MOVE",
"url": "/remote.php/dav/files/bugtest-bob/Muster%20GmbH%20%26%20Co.%20KG",
"scriptName": "/remote.php",
"message": "DOMDocument::createElementNS(): unterminated entity reference Co. KG) into a share (Projekt) is not allowed at /var/www/html/apps/dav/lib/Connector/Sabre/Exception/Forbidden.php#46",
"userAgent": "curl/8.14.1",
"version": "35.0.2.2",
"data": {
"app": "PHP"
}
}
message in <o:reason>, and no PHP warning.
Nextcloud Server version
35
Operating system
Debian/Ubuntu
PHP engine version
PHP 8.5
Web server
Apache (supported)
Database engine version
MariaDB
Is this bug present after an update or on a fresh install?
Fresh Nextcloud Server install
Are you using the Nextcloud Server Encryption module?
Encryption is Disabled
What user-backends are you using?
- Default user-backend (database)
- LDAP/ Active Directory
- SSO - SAML
- Other
Configuration report
{
"system": {
"htaccess.RewriteBase": "\/",
"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
}
],
"instanceid": "***REMOVED SENSITIVE VALUE***",
"passwordsalt": "***REMOVED SENSITIVE VALUE***",
"secret": "***REMOVED SENSITIVE VALUE***",
"trusted_domains": [
"example.com"
],
"trusted_proxies": "***REMOVED SENSITIVE VALUE***",
"datadirectory": "***REMOVED SENSITIVE VALUE***",
"dbtype": "mysql",
"version": "35.0.2.2",
"overwrite.cli.url": "https:\/\/example.com",
"overwriteprotocol": "https",
"dbname": "***REMOVED SENSITIVE VALUE***",
"dbhost": "***REMOVED SENSITIVE VALUE***",
"dbport": "",
"dbtableprefix": "oc_",
"default_phone_region": "DE",
"mysql.utf8mb4": true,
"dbuser": "***REMOVED SENSITIVE VALUE***",
"dbpassword": "***REMOVED SENSITIVE VALUE***",
"installed": true,
"mail_from_address": "***REMOVED SENSITIVE VALUE***",
"mail_smtpmode": "smtp",
"mail_sendmailmode": "smtp",
"mail_domain": "***REMOVED SENSITIVE VALUE***",
"mail_smtpsecure": "tls",
"mail_smtphost": "***REMOVED SENSITIVE VALUE***",
"mail_smtpauthtype": "LOGIN",
"mail_smtpauth": 1,
"mail_smtpname": "***REMOVED SENSITIVE VALUE***",
"mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
"mail_smtpport": "587",
"loglevel": 2,
"maintenance": false,
"memcache.distributed": "\\OC\\Memcache\\Redis",
"memcache.locking": "\\OC\\Memcache\\Redis",
"redis": {
"host": "***REMOVED SENSITIVE VALUE***",
"password": "***REMOVED SENSITIVE VALUE***",
"port": 6379
},
"theme": "",
"maintenance_window_start": 3,
"upgrade.disable-web": true,
"forbidden_filename_basenames": [
"con",
"prn",
"aux",
"nul",
"com0",
"com1",
"com2",
"com3",
"com4",
"com5",
"com6",
"com7",
"com8",
"com9",
"com\u00b9",
"com\u00b2",
"com\u00b3",
"lpt0",
"lpt1",
"lpt2",
"lpt3",
"lpt4",
"lpt5",
"lpt6",
"lpt7",
"lpt8",
"lpt9",
"lpt\u00b9",
"lpt\u00b2",
"lpt\u00b3"
],
"forbidden_filename_characters": [
"<",
">",
":",
"\"",
"|",
"?",
"*",
"\\",
"\/"
],
"forbidden_filename_extensions": [
" ",
".",
".filepart",
".part"
],
"overwritehost": "example.com"
}
}
List of activated Apps
Enabled:
- activity: 8.0.0
- admin_audit: 2.0.0
- app_api: 35.0.0
- appstore: 2.0.0
- bruteforcesettings: 8.0.0
- calendar: 6.6.3
- circles: 35.0.0
- cloud_federation_api: 2.0.0
- comments: 2.0.0
- contactsinteraction: 2.0.0
- dashboard: 8.0.0
- dav: 2.0.0
- federatedfilesharing: 2.0.0
- federation: 2.0.0
- files: 3.0.0
- files_downloadlimit: 5.3.0
- files_fulltextsearch: 35.0.0
- files_lock: 35.0.1
- files_pdfviewer: 8.0.0
- files_reminders: 2.0.0
- files_sharing: 2.0.0
- files_trashbin: 2.0.0
- files_versions: 2.0.0
- firstrunwizard: 8.0.0
- fulltextsearch: 35.0.0
- fulltextsearch_elasticsearch: 35.0.1
- logreader: 8.0.0
- lookup_server_connector: 2.0.0
- nextcloud_announcements: 7.0.0
- notifications: 8.0.1
- oauth2: 2.0.0
- office: 1.1.0
- password_policy: 7.0.0
- photos: 8.0.0
- privacy: 7.0.0
- profile: 2.0.0
- provisioning_api: 2.0.0
- recommendations: 8.0.0
- related_resources: 6.0.0
- serverinfo: 7.0.0
- settings: 2.0.0
- sharebymail: 2.0.0
- sharing: 1.0.4
- spreed: 25.0.6
- support: 7.0.0
- survey_client: 7.0.0
- suspicious_login: 13.0.0
- systemtags: 2.0.0
- text: 9.0.1
- theming: 3.0.0
- twofactor_backupcodes: 2.0.0
- twofactor_totp: 17.1.0
- updatenotification: 2.0.0
- user_status: 2.0.0
- viewer: 8.0.0
- weather_status: 2.0.0
- webhook_listeners: 2.0.0
- workflowengine: 3.0.0
Disabled:
- cfg_share_links: 7.0.1 (installed 7.0.1)
- encryption: 3.0.1
- files_downloadactivity: 1.18.1 (installed 1.18.1)
- files_external: 2.0.0
- files_fulltextsearch_tesseract: 32.0.0 (installed 32.0.0)
- twofactor_nextcloud_notification: 9.0.0
- user_ldap: 2.0.0
- whiteboard: 2.1.0 (installed 2.1.0)
Nextcloud Signing status
No errors have been found.
Nextcloud Logs
{
"reqId": "xp5UETOUhmbX1eFxZSwi",
"level": 2,
"time": "2026-11-02T10:14:09+00:00",
"remoteAddr": "fd00:beef:1337::1",
"user": "bugtest-bob",
"app": "PHP",
"method": "MOVE",
"url": "/remote.php/dav/files/bugtest-bob/Muster%20GmbH%20%26%20Co.%20KG",
"scriptName": "/remote.php",
"message": "DOMDocument::createElementNS(): unterminated entity reference Co. KG) into a share (Projekt) is not allowed at /var/www/html/apps/dav/lib/Connector/Sabre/Exception/Forbidden.php#46",
"userAgent": "curl/8.14.1",
"version": "35.0.2.2",
"data": {
"app": "PHP"
}
}
- Lenguaje dominante
- PHP
- Estrellas
- 37.4k
- Forks
- 5.3k
- Merge medio
- 2 d 7 h
- PR fusionados (30 d)
- 709
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de nextcloud/server
-
Unified search: screen reader hears "No matching results" on mobile even when there are resultsPosiblemente ocupada @lazerg la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
34-feedback needs info
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
nextcloud/server#66249 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
[Bug]: Files folder-tree API eagerly lists the children of every subfolder at the last requested depth, then discards them (N extra directory listings per request)Posiblemente ocupada @lazerg la tomó hace 7 días. Abierto0. Needs triage 34-feedback bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
nextcloud/server#65904 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[Bug]: Contradictory notifications when cancelling welcome email confirmationPosiblemente ocupada @Jashan32 la tomó hace 8 días. Abierto0. Needs triage 36-feedback bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
Los mantenedores suelen responder en 1 día
Todos los issues de nextcloud/server
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
getgrav/grav-plugin-form#664 ·
-
claude
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
sprain/sprainocchio#26 ·
-
sync-en
Dificultad 1/5 1-3 horas Aptitud para principiantes 84/100
Los mantenedores suelen responder en 1 día
-
sync-en
Dificultad 1/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 83/100
GSA/digital-gov-drupal#569 ·
Los mantenedores suelen responder en 2 días