Skip to content

occ fulltextsearch:search returns no results for users whose UID contains a dot #389

Description

@mklemme1

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

  1. 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.
  2. 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
    
  3. 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:

 * deck:
 * files:

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions