Skip to content

fix: mark files as scanned on upload#600

Open
Antreesy wants to merge 2 commits into
masterfrom
fix/71/mark-scanned-on-upload
Open

fix: mark files as scanned on upload#600
Antreesy wants to merge 2 commits into
masterfrom
fix/71/mark-scanned-on-upload

Conversation

@Antreesy
Copy link
Copy Markdown
Contributor

@Antreesy Antreesy commented May 11, 2026

Commit 1

The initial implementation tried to mark files as scanned immediately after the AV scan in AvirWrapper, but this failed because:

  • Files are not yet in oc_filecache during the write operation
  • File IDs don't exist until after the write completes and the cache is synced
  • Attempting to mark files with invalid IDs simply fails silently

Additionally, the follow-up NodeWrittenListener that was introduced to fix the timing issue marked every uploaded file as scanned unconditionally, with no knowledge of whether the scan actually succeeded — meaning unreachable-AV uploads were still silently marked as clean.

Commit 2

Using NodeWrittenEvent to mark files after they're fully written and synced, combined with a ScannedPathsRegistry — a request-scoped in-memory map that bridges the scanning layer and the post-upload persistence layer:

  1. AvirWrapper writes to the registry only when a file is genuinely accepted after scanning (status CLEAN, or UNSCANNABLE with block_unscannable disabled). UNCHECKED results (unreachable scanner) are deliberately not registered.
  2. NodeWrittenListener fires after the file is persisted and oc_filecache is updated — at this point the file has a valid ID. It checks the registry before writing to the database. If no scan result is present, it does nothing — leaving the file for the background scanner.

The registry is a plain PHP object resolved as a per-request singleton by Nextcloud's DI container, so it is naturally shared between FilesystemSetupListener (which constructs AvirWrapper) and NodeWrittenListener without any explicit container registration.

Nextcloud DAV uploads use hashed temporary filenames (e.g. ab3cd4.ocTransferId123.part), making it impossible to recover the real filename from the storage path alone. getRegistryPath() detects this case and falls back to the DAV request URI (/dav/files/{user}/{real_path}), transforming it to match the absolute path returned by Node::getPath() (/{user}/files/{real_path}).

Testing

  • Upload with reachable scanner → file marked as scanned ✅
  • Upload with unreachable scanner (block_unreachable: false) → file not marked, picked up by background scanner ✅
  • Upload with unreachable scanner (block_unreachable: true) → upload blocked ✅

After upload, verify the file is marked:

SELECT fileid, check_time FROM oc_files_antivirus WHERE fileid = <FILE_ID>;

The file should have a check_time timestamp instead of NULL.

@Antreesy Antreesy self-assigned this May 11, 2026
@Antreesy Antreesy force-pushed the fix/71/mark-scanned-on-upload branch 4 times, most recently from 1d7eefc to dbeaed8 Compare May 11, 2026 13:51
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
@Antreesy Antreesy force-pushed the fix/71/mark-scanned-on-upload branch from dbeaed8 to 62753d6 Compare May 11, 2026 16:05
@Antreesy Antreesy changed the base branch from master to fix/396/clam-daemon-stop May 11, 2026 16:05
@Antreesy Antreesy force-pushed the fix/71/mark-scanned-on-upload branch from 62753d6 to 28d1422 Compare May 11, 2026 16:13
…load

Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
@Antreesy Antreesy force-pushed the fix/71/mark-scanned-on-upload branch from 28d1422 to e8d9786 Compare May 11, 2026 16:19
@Antreesy Antreesy requested review from icewind1991 and susnux May 11, 2026 16:26
@Antreesy Antreesy marked this pull request as ready for review May 11, 2026 16:26
Base automatically changed from fix/396/clam-daemon-stop to master May 12, 2026 08:36
Comment thread lib/AvirWrapper.php
Comment on lines +188 to +199
if (preg_match('/\.ocTransferId\d+\.part$/i', $path)) {
$davFilesPrefix = '/dav/files';
$pathInfo = $this->request->getPathInfo();
if (str_starts_with($pathInfo, $davFilesPrefix)) {
$afterPrefix = substr($pathInfo, strlen($davFilesPrefix)); // /{username}/{relative}
$secondSlash = strpos($afterPrefix, '/', 1);
if ($secondSlash !== false) {
// Insert /files between the username segment and the rest of the path
return substr($afterPrefix, 0, $secondSlash) . '/files' . substr($afterPrefix, $secondSlash);
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this differ from getPathForScanner?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it adds a /files/ suffix, so when the file upload is finished, Node:getPath() matches that.

There might be an easy alternative way, but from I understand, entry in oc_filecache does not exist yet, when AvirWrapper finished scanning; so we either need to:

  • know the final path of the clean file to edit the entry
  • use another identificator for such file.

Is there something we can use for it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants