How to use GitHub
- Please use the 👍 reaction to show that you are affected by the same issue.
- Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
- Subscribe to receive notifications on status change and new comments.
Steps to reproduce
- Set up
files_fulltextsearch with a search platform (here: fulltextsearch_elasticsearch) and index the files of a user whose UID contains a dot, e.g. michael.jordan@example.org. Any UID with a dot will do; email-style UIDs are the common case.
- Make sure at least one of that user's indexed files matches a search term. In our case an image whose EXIF metadata contains
ref2025.1, verified to be present in the search platform:
occ fulltextsearch:document:platform files 3422699 --content
→ 200 OK, term present in the indexed document
- Search from the command line:
occ fulltextsearch:search michael.jordan@example.org "ref2025.1"
Expected behaviour
The matching file is listed, as it is for a user whose UID contains no dot.
Actual behaviour
The files provider returns an empty result set, with no error on stdout:
nextcloud.log records one dropped document per suppressed hit:
files_fulltextsearch: Exception while improving searchresult
OC\User\NoUserException: User does not exist: michael\.jordan@example.org
at FilesService->getFileFromId('michael\.jordan@example.org', 3422699)
at SearchService->setDocumentInfo(…) SearchService.php:139
at SearchService->improveSearchResult(…) FilesProvider.php:302
Note the escaped dots in the UID. The search platform itself matched the document correctly — only the
post-processing in Nextcloud drops it. This can be confirmed independently by enabling the Elasticsearch index search
slowlog and re-running the search:
PUT /<index>/_settings {"index.search.slowlog.threshold.query.warn": "0ms"}
The captured query contains the expected fields and reports 1 hit, so the document is lost strictly between
"platform found it" and "result is returned".
Running the identical search for a user whose UID contains no dot returns results normally. That contrast is the
whole bug.
Root cause
SearchService::improveSearchRequest() assigns the output of FilesService::secureUsername() — a regex-escaping
helper — to $this->userId, which is then used as a real user ID:
https://github.com/nextcloud/files_fulltextsearch/blob/master/lib/Service/SearchService.php#L54-L56
if ($this->userId === '') {
$this->userId = $this->filesService->secureUsername($request->getAuthor());
}
public function secureUsername(string $username): string {
return str_replace('.', '\.', $username);
}
$this->userId is consumed in exactly two places, and neither wants an escaped value:
getFileFromId($this->userId, …) (line 148) needs a real UID; it throws NoUserException for the escaped string.
substr($file->getPath(), 7 + strlen($this->userId)) (line 159) needs the real length; the escaped name is one
character too long per dot, so the computed path would be truncated incorrectly even if the lookup succeeded.
The constructor sets the same field from the session as a plain UID (line 45), so the plain UID is the field's contract.
This is why the bug is limited to code paths without a user session, i.e. occ fulltextsearch:search: in the web UI
$this->userId comes from IUserSession and is never escaped, so the same search works there. It affects all file
results for such users, not just any particular content type.
The failure is silent because improveSearchResult() wraps each document in try { … } catch (Exception $e) and only
logs a warning, so affected documents are dropped without any error reaching the caller.
Suggested fix
if ($this->userId === '') {
- $this->userId = $this->filesService->secureUsername($request->getAuthor());
+ $this->userId = $request->getAuthor();
}
Verified on the setup below: the previously empty CLI search now returns the expected file with a correct path, and
results for dot-free UIDs are unchanged. I am happy to open a PR.
Side note
After this change, secureUsername() has no remaining caller that uses its return value: the only other call site,
searchQueryFiltersExtension() (line 67 on current master), discards the result — the $username assignment that
used to receive it was dropped, and the regex filter it builds interpolates only $extension. So that call is a no-op
and secureUsername() is effectively dead code. I left both untouched to keep the fix minimal, but they are probably
worth a separate cleanup — or, if the author's UID was originally meant to be part of that regex filter, a hint that
something was lost there.
Server configuration
Web server: Apache/2.4.68 (Debian), official Nextcloud Docker image
Database: MariaDB 11.4.12 (dbtype: mysql)
PHP version: 8.5.9
Nextcloud version: 34.0.2 (34.0.2.1)
Affected app version: files_fulltextsearch 34.0.0 — the code path is unchanged on current master
Search platform: fulltextsearch_elasticsearch 34.0.0 against Elasticsearch 8.19.1
List of activated apps
Enabled:
- activity: 7.0.0
- admin_audit: 1.24.0
- appstore: 1.0.0
- bruteforcesettings: 7.0.0
- calendar: 6.5.2
- cloud_federation_api: 1.18.0
- comments: 1.24.0
- contacts: 8.7.5
- dav: 1.39.0
- deck: 1.18.3
- event_update_notification: 2.9.0
- federatedfilesharing: 1.24.0
- files: 2.6.0
- files_downloadlimit: 5.2.0
- files_fulltextsearch: 34.0.0
- files_fulltextsearch_metadata: 0.1.1
- files_lock: 34.0.1
- files_pdfviewer: 7.0.0-dev.0
- files_reminders: 1.7.0
- files_sharing: 1.26.0
- files_trashbin: 1.24.0
- files_versions: 1.27.0
- firstrunwizard: 7.0.0-dev.0
- forms: 5.3.5
- fulltextsearch: 34.0.0
- fulltextsearch_elasticsearch: 34.0.0
- groupfolders: 22.0.6
- impersonate: 5.0.0
- logreader: 7.0.0
- lookup_server_connector: 1.22.0
- notifications: 7.0.0-dev.1
- oauth2: 1.22.0
- office: 1.0.0
- password_policy: 6.0.0-dev.0
- photos: 7.0.0
- piwik: 0.13.0
- polls: 9.1.4
- previewgenerator: 5.14.0
- privacy: 6.0.0-dev.1
- profile: 1.3.0
- provisioning_api: 1.24.0
- quota_warning: 1.24.0
- richdocuments: 11.1.0
- serverinfo: 6.0.0
- settings: 1.17.0
- sharebymail: 1.24.0
- suspicious_login: 12.0.0-dev.0
- text: 8.0.0
- theming: 2.9.0
- twofactor_backupcodes: 1.23.0
- twofactor_totp: 16.0.0
- updatenotification: 1.24.0
- viewer: 7.0.0-dev.0
- webhook_listeners: 1.6.0
- workflowengine: 2.16.0
Disabled:
- app_api: 34.0.0 (installed 4.0.6)
- circles: 34.0.0 (installed 22.2.0)
- contactsinteraction: 1.15.0 (installed 1.0.0)
- dashboard: 7.14.0 (installed 7.0.0)
- encryption: 2.22.0
- federation: 1.24.0 (installed 1.21.0)
- files_external: 1.26.0 (installed 1.10.0)
- metadata: 0.24.0 (installed 0.24.0)
- nextcloud_announcements: 6.0.0 (installed 1.8.0)
- recommendations: 7.0.0 (installed 0.7.0)
- related_resources: 5.0.0-dev.0 (installed 1.0.4)
- support: 6.0.0 (installed 1.2.1)
- survey_client: 6.0.0-dev.0 (installed 1.7.0)
- systemtags: 1.24.0 (installed 0.3.0)
- thesearchpage: 2.4.3 (installed 2.4.3)
- twofactor_nextcloud_notification: 8.0.0
- user_ldap: 1.25.0
- user_status: 1.14.0 (installed 1.0.1)
- weather_status: 1.14.0 (installed 1.0.0)
Nextcloud configuration
{
"system": {
"app_install_overwrite": [
"fulltextsearch",
"piwik",
"metadata"
],
"apps_paths": [
{
"path": "/var/www/html/apps",
"url": "/apps",
"writable": false
},
{
"path": "/var/www/html/custom_apps",
"url": "/custom_apps",
"writable": true
}
],
"dbhost": "***REDACTED***",
"dbname": "***REDACTED***",
"dbpassword": "***REDACTED***",
"dbport": "",
"dbtableprefix": "oc_",
"dbtype": "mysql",
"dbuser": "***REDACTED***",
"htaccess.RewriteBase": "/",
"installed": true,
"instanceid": "***REDACTED***",
"loglevel": 1,
"maintenance": false,
"maintenance_window_start": 6,
"memcache.distributed": "\\OC\\Memcache\\Redis",
"memcache.local": "\\OC\\Memcache\\APCu",
"memcache.locking": "\\OC\\Memcache\\Redis",
"mysql.utf8mb4": true,
"overwrite.cli.url": "***REDACTED***",
"overwritehost": "***REDACTED***",
"overwriteprotocol": "https",
"passwordsalt": "***REDACTED***",
"redis": "***REDACTED***",
"secret": "***REDACTED***",
"trusted_domains": "***REDACTED***",
"trusted_proxies": "***REDACTED***",
"upgrade.disable-web": true,
"version": "34.0.2.1"
}
}
Browser
Not applicable — this is a server-side, command-line-only bug; no browser is involved in reproducing it.
Browser name: n/a
Browser version: n/a
Operating system: Debian (server), official Nextcloud Docker image
Browser log
n/a — reproduced entirely via occ. The web UI is not affected: there
$this->userId comes from IUserSession and is not regex-escaped, so the
same search returns the expected results.
How to use GitHub
Steps to reproduce
files_fulltextsearchwith a search platform (here:fulltextsearch_elasticsearch) and index the files of a user whose UID contains a dot, e.g.michael.jordan@example.org. Any UID with a dot will do; email-style UIDs are the common case.ref2025.1, verified to be present in the search platform:Expected behaviour
The matching file is listed, as it is for a user whose UID contains no dot.
Actual behaviour
The
filesprovider returns an empty result set, with no error on stdout:nextcloud.logrecords one dropped document per suppressed hit:Note the escaped dots in the UID. The search platform itself matched the document correctly — only the
post-processing in Nextcloud drops it. This can be confirmed independently by enabling the Elasticsearch index search
slowlog and re-running the search:
The captured query contains the expected fields and reports
1 hit, so the document is lost strictly between"platform found it" and "result is returned".
Running the identical search for a user whose UID contains no dot returns results normally. That contrast is the
whole bug.
Root cause
SearchService::improveSearchRequest()assigns the output ofFilesService::secureUsername()— a regex-escapinghelper — to
$this->userId, which is then used as a real user ID:https://github.com/nextcloud/files_fulltextsearch/blob/master/lib/Service/SearchService.php#L54-L56
$this->userIdis consumed in exactly two places, and neither wants an escaped value:getFileFromId($this->userId, …)(line 148) needs a real UID; it throwsNoUserExceptionfor the escaped string.substr($file->getPath(), 7 + strlen($this->userId))(line 159) needs the real length; the escaped name is onecharacter too long per dot, so the computed path would be truncated incorrectly even if the lookup succeeded.
The constructor sets the same field from the session as a plain UID (line 45), so the plain UID is the field's contract.
This is why the bug is limited to code paths without a user session, i.e.
occ fulltextsearch:search: in the web UI$this->userIdcomes fromIUserSessionand is never escaped, so the same search works there. It affects all fileresults for such users, not just any particular content type.
The failure is silent because
improveSearchResult()wraps each document intry { … } catch (Exception $e)and onlylogs a warning, so affected documents are dropped without any error reaching the caller.
Suggested fix
if ($this->userId === '') { - $this->userId = $this->filesService->secureUsername($request->getAuthor()); + $this->userId = $request->getAuthor(); }Verified on the setup below: the previously empty CLI search now returns the expected file with a correct
path, andresults for dot-free UIDs are unchanged. I am happy to open a PR.
Side note
After this change,
secureUsername()has no remaining caller that uses its return value: the only other call site,searchQueryFiltersExtension()(line 67 on currentmaster), discards the result — the$usernameassignment thatused to receive it was dropped, and the regex filter it builds interpolates only
$extension. So that call is a no-opand
secureUsername()is effectively dead code. I left both untouched to keep the fix minimal, but they are probablyworth a separate cleanup — or, if the author's UID was originally meant to be part of that regex filter, a hint that
something was lost there.
Server configuration
Web server: Apache/2.4.68 (Debian), official Nextcloud Docker image
Database: MariaDB 11.4.12 (
dbtype: mysql)PHP version: 8.5.9
Nextcloud version: 34.0.2 (34.0.2.1)
Affected app version:
files_fulltextsearch34.0.0 — the code path is unchanged on currentmasterSearch platform:
fulltextsearch_elasticsearch34.0.0 against Elasticsearch 8.19.1List of activated apps
Nextcloud configuration
{ "system": { "app_install_overwrite": [ "fulltextsearch", "piwik", "metadata" ], "apps_paths": [ { "path": "/var/www/html/apps", "url": "/apps", "writable": false }, { "path": "/var/www/html/custom_apps", "url": "/custom_apps", "writable": true } ], "dbhost": "***REDACTED***", "dbname": "***REDACTED***", "dbpassword": "***REDACTED***", "dbport": "", "dbtableprefix": "oc_", "dbtype": "mysql", "dbuser": "***REDACTED***", "htaccess.RewriteBase": "/", "installed": true, "instanceid": "***REDACTED***", "loglevel": 1, "maintenance": false, "maintenance_window_start": 6, "memcache.distributed": "\\OC\\Memcache\\Redis", "memcache.local": "\\OC\\Memcache\\APCu", "memcache.locking": "\\OC\\Memcache\\Redis", "mysql.utf8mb4": true, "overwrite.cli.url": "***REDACTED***", "overwritehost": "***REDACTED***", "overwriteprotocol": "https", "passwordsalt": "***REDACTED***", "redis": "***REDACTED***", "secret": "***REDACTED***", "trusted_domains": "***REDACTED***", "trusted_proxies": "***REDACTED***", "upgrade.disable-web": true, "version": "34.0.2.1" } }Browser
Not applicable — this is a server-side, command-line-only bug; no browser is involved in reproducing it.
Browser name: n/a
Browser version: n/a
Operating system: Debian (server), official Nextcloud Docker image
Browser log