mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-23 03:53:21 +00:00
Merge pull request #1787 from projectsend/public-unscanned-notice
Tell whoever opens a public link that nothing checked the file
This commit is contained in:
@@ -12,6 +12,7 @@ use App\Modules\Files\Delivery\StoredFileResponse;
|
||||
use App\Modules\Files\Models\Category;
|
||||
use App\Modules\Files\Models\File;
|
||||
use App\Modules\Files\Scanning\FileAvailability;
|
||||
use App\Modules\Files\Scanning\ScanningConfig;
|
||||
use App\Modules\Files\Scanning\ScanStatus;
|
||||
use App\Modules\Files\Models\ShareLink;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -32,6 +33,7 @@ class PublicShareController extends Controller
|
||||
private readonly DownloadAllowance $allowance,
|
||||
private readonly StoredFileResponse $bytes,
|
||||
private readonly FileAvailability $availability,
|
||||
private readonly ScanningConfig $scanning,
|
||||
) {}
|
||||
|
||||
public function show(string $token): InertiaResponse
|
||||
@@ -84,6 +86,11 @@ class PublicShareController extends Controller
|
||||
])->values()->all(),
|
||||
],
|
||||
'download_url' => route('share.download', $token),
|
||||
// Said to the one person who can neither see the setting nor
|
||||
// chose it. The uploader and the staff library both show this
|
||||
// file as "not scanned"; whoever follows the link had no way
|
||||
// of knowing.
|
||||
'unscanned' => $this->scanning->enabled() && $file->wasLetThrough(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,21 @@ class File extends Model
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a file went out unchecked. Deliberately not
|
||||
* NotScannedReason::BeforeScanning: a file stored while this
|
||||
* installation did not scan at all is not a scanner letting something
|
||||
* past, and on an installation that has never scanned it would mean
|
||||
* saying it about every file there is.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private const LET_THROUGH_REASONS = [
|
||||
NotScannedReason::ScannerUnavailable->value,
|
||||
NotScannedReason::TooLarge->value,
|
||||
NotScannedReason::Encrypted->value,
|
||||
];
|
||||
|
||||
/**
|
||||
* Files people can download that nothing checked: let through while
|
||||
* the scanner was down, or because it could not open them.
|
||||
@@ -334,11 +349,17 @@ class File extends Model
|
||||
public function scopeLetThrough(Builder $query): void
|
||||
{
|
||||
$query->where('scan_status', ScanStatus::NotScanned)
|
||||
->whereIn('scan_note', [
|
||||
NotScannedReason::ScannerUnavailable->value,
|
||||
NotScannedReason::TooLarge->value,
|
||||
NotScannedReason::Encrypted->value,
|
||||
]);
|
||||
->whereIn('scan_note', self::LET_THROUGH_REASONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this particular file is one of those — the row's own answer
|
||||
* to scopeLetThrough(), for a page that already has the file.
|
||||
*/
|
||||
public function wasLetThrough(): bool
|
||||
{
|
||||
return $this->scan_status === ScanStatus::NotScanned
|
||||
&& in_array((string) $this->scan_note, self::LET_THROUGH_REASONS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Modules\Files\Delivery\StoredFileResponse;
|
||||
use App\Modules\Files\Models\Category;
|
||||
use App\Modules\Files\Models\File;
|
||||
use App\Modules\Files\Scanning\FileAvailability;
|
||||
use App\Modules\Files\Scanning\ScanningConfig;
|
||||
use App\Modules\Files\Models\Folder;
|
||||
use App\Modules\Files\Preview\PreviewKind;
|
||||
use App\Modules\Files\Preview\PreviewLog;
|
||||
@@ -228,6 +229,9 @@ class PublicGroupsController extends Controller
|
||||
// is allowed.
|
||||
'preview_url' => $this->previewUrlFor($file, $publicSlug),
|
||||
'download_url' => route('public.download', [$publicSlug, $file->slug]),
|
||||
// See PublicShareController::show — the same sentence, to the
|
||||
// same person, on the other public surface.
|
||||
'unscanned' => app(ScanningConfig::class)->enabled() && $file->wasLetThrough(),
|
||||
// Same decided shape the listings send, so a theme's single
|
||||
// file page disables its button for the same reason a row
|
||||
// does — see DownloadAllowance::summaryFor.
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Un fitxer d'aquest arxiu ja no està disponible. Torna a descarregar la selecció.",
|
||||
"A scan is already running. Wait for it to finish.": "Ja hi ha una anàlisi en curs. Espera que acabi.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Alguna cosa ha respost a :address, però no és un escàner ClamAV.",
|
||||
"Available until :date": "Disponible fins al :date"
|
||||
"Available until :date": "Disponible fins al :date",
|
||||
"This file was not checked for viruses.": "Aquest fitxer no s'ha analitzat a la recerca de virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Soubor z tohoto archivu už není k dispozici. Stáhněte výběr znovu.",
|
||||
"A scan is already running. Wait for it to finish.": "Kontrola už probíhá. Počkejte, až skončí.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Na adrese :address něco odpovědělo, ale není to skener ClamAV.",
|
||||
"Available until :date": "K dispozici do :date"
|
||||
"Available until :date": "K dispozici do :date",
|
||||
"This file was not checked for viruses.": "Tento soubor nebyl zkontrolován na viry."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Eine Datei in diesem Archiv ist nicht mehr verfügbar. Lade die Auswahl erneut herunter.",
|
||||
"A scan is already running. Wait for it to finish.": "Es läuft bereits ein Scan. Warte, bis er fertig ist.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Unter :address hat etwas geantwortet, aber es ist kein ClamAV-Scanner.",
|
||||
"Available until :date": "Verfügbar bis :date"
|
||||
"Available until :date": "Verfügbar bis :date",
|
||||
"This file was not checked for viruses.": "Diese Datei wurde nicht auf Viren geprüft."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Un archivo de este comprimido ya no está disponible. Vuelve a descargar la selección.",
|
||||
"A scan is already running. Wait for it to finish.": "Ya hay un análisis en curso. Espera a que termine.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Algo respondió en :address, pero no es un analizador ClamAV.",
|
||||
"Available until :date": "Disponible hasta el :date"
|
||||
"Available until :date": "Disponible hasta el :date",
|
||||
"This file was not checked for viruses.": "Este archivo no fue analizado en busca de virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Un fichier de cette archive n'est plus disponible. Téléchargez à nouveau la sélection.",
|
||||
"A scan is already running. Wait for it to finish.": "Une analyse est déjà en cours. Attendez qu'elle se termine.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Quelque chose a répondu à :address, mais ce n'est pas un scanner ClamAV.",
|
||||
"Available until :date": "Disponible jusqu'au :date"
|
||||
"Available until :date": "Disponible jusqu'au :date",
|
||||
"This file was not checked for viruses.": "Ce fichier n'a pas été analysé à la recherche de virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Sebuah file dalam arsip ini sudah tidak tersedia. Unduh ulang pilihan tersebut.",
|
||||
"A scan is already running. Wait for it to finish.": "Pemindaian sudah berjalan. Tunggu hingga selesai.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Ada yang menjawab di :address, tetapi itu bukan pemindai ClamAV.",
|
||||
"Available until :date": "Tersedia hingga :date"
|
||||
"Available until :date": "Tersedia hingga :date",
|
||||
"This file was not checked for viruses.": "Berkas ini belum dipindai dari virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Un file di questo archivio non è più disponibile. Scarica di nuovo la selezione.",
|
||||
"A scan is already running. Wait for it to finish.": "È già in corso una scansione. Attendi che finisca.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Qualcosa ha risposto su :address, ma non è uno scanner ClamAV.",
|
||||
"Available until :date": "Disponibile fino al :date"
|
||||
"Available until :date": "Disponibile fino al :date",
|
||||
"This file was not checked for viruses.": "Questo file non è stato controllato alla ricerca di virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "このアーカイブ内のファイルは利用できなくなりました。もう一度選択をダウンロードしてください。",
|
||||
"A scan is already running. Wait for it to finish.": "スキャンはすでに実行中です。終了するまでお待ちください。",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": ":address で応答がありましたが、ClamAV スキャナーではありません。",
|
||||
"Available until :date": ":date まで利用可能"
|
||||
"Available until :date": ":date まで利用可能",
|
||||
"This file was not checked for viruses.": "このファイルはウイルス検査を受けていません。"
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Een bestand in dit archief is niet meer beschikbaar. Download de selectie opnieuw.",
|
||||
"A scan is already running. Wait for it to finish.": "Er loopt al een scan. Wacht tot die klaar is.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Er antwoordde iets op :address, maar het is geen ClamAV-scanner.",
|
||||
"Available until :date": "Beschikbaar tot :date"
|
||||
"Available until :date": "Beschikbaar tot :date",
|
||||
"This file was not checked for viruses.": "Dit bestand is niet op virussen gecontroleerd."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Plik z tego archiwum nie jest już dostępny. Pobierz zaznaczenie ponownie.",
|
||||
"A scan is already running. Wait for it to finish.": "Skanowanie już trwa. Poczekaj, aż się zakończy.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Coś odpowiedziało pod adresem :address, ale to nie jest skaner ClamAV.",
|
||||
"Available until :date": "Dostępny do :date"
|
||||
"Available until :date": "Dostępny do :date",
|
||||
"This file was not checked for viruses.": "Ten plik nie został sprawdzony pod kątem wirusów."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Um arquivo deste pacote não está mais disponível. Baixe a seleção novamente.",
|
||||
"A scan is already running. Wait for it to finish.": "Já há uma verificação em andamento. Aguarde até que termine.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Algo respondeu em :address, mas não é um scanner ClamAV.",
|
||||
"Available until :date": "Disponível até :date"
|
||||
"Available until :date": "Disponível até :date",
|
||||
"This file was not checked for viruses.": "Este arquivo não foi verificado em busca de vírus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Один из файлов этого архива больше недоступен. Скачайте выбранное заново.",
|
||||
"A scan is already running. Wait for it to finish.": "Проверка уже идёт. Дождитесь её завершения.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "По адресу :address что-то ответило, но это не сканер ClamAV.",
|
||||
"Available until :date": "Доступен до :date"
|
||||
"Available until :date": "Доступен до :date",
|
||||
"This file was not checked for viruses.": "Этот файл не проверялся на вирусы."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Faili moja katika kumbukumbu hii halipatikani tena. Pakua uteuzi upya.",
|
||||
"A scan is already running. Wait for it to finish.": "Uchanganuzi tayari unaendelea. Subiri umalizike.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Kitu kimejibu kwenye :address, lakini si kichanganuzi cha ClamAV.",
|
||||
"Available until :date": "Inapatikana hadi :date"
|
||||
"Available until :date": "Inapatikana hadi :date",
|
||||
"This file was not checked for viruses.": "Faili hii haijakaguliwa dhidi ya virusi."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Bu arşivdeki bir dosya artık kullanılamıyor. Seçimi yeniden indirin.",
|
||||
"A scan is already running. Wait for it to finish.": "Zaten bir tarama çalışıyor. Bitmesini bekleyin.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": ":address adresinde bir şey yanıt verdi, ancak bu bir ClamAV tarayıcısı değil.",
|
||||
"Available until :date": ":date tarihine kadar kullanılabilir"
|
||||
"Available until :date": ":date tarihine kadar kullanılabilir",
|
||||
"This file was not checked for viruses.": "Bu dosya virüslere karşı taranmadı."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "Một tệp trong gói nén này không còn khả dụng. Hãy tải lại phần đã chọn.",
|
||||
"A scan is already running. Wait for it to finish.": "Đã có một lượt quét đang chạy. Hãy đợi nó hoàn tất.",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": "Có dịch vụ trả lời tại :address, nhưng đó không phải là trình quét ClamAV.",
|
||||
"Available until :date": "Có sẵn đến :date"
|
||||
"Available until :date": "Có sẵn đến :date",
|
||||
"This file was not checked for viruses.": "Tệp này chưa được quét virus."
|
||||
}
|
||||
|
||||
+2
-1
@@ -2249,5 +2249,6 @@
|
||||
"A file in this archive is no longer available. Download the selection again.": "此压缩包中的某个文件已不可用。请重新下载所选内容。",
|
||||
"A scan is already running. Wait for it to finish.": "已有扫描正在进行。请等待其完成。",
|
||||
"Something answered at :address, but it is not a ClamAV scanner.": ":address 上有程序应答,但它不是 ClamAV 扫描器。",
|
||||
"Available until :date": "可用至 :date"
|
||||
"Available until :date": "可用至 :date",
|
||||
"This file was not checked for viruses.": "此文件未经过病毒检查。"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ShieldQuestion } from 'lucide-react';
|
||||
|
||||
import { useTranslation } from '@/hooks/use-translation';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface UnscannedNoticeProps {
|
||||
/** Whether this file went out without being checked. Decided by the server. */
|
||||
unscanned?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Told to whoever opens a public link: this file was never checked.
|
||||
*
|
||||
* It happens on an installation that scans and chose to let files through
|
||||
* anyway — too large for the scanner, an archive it could not open, or an
|
||||
* upload that arrived while the scanner was down. The uploader and the
|
||||
* staff library both see that state on the file; the person following the
|
||||
* link neither chose the policy nor can see the setting, and until this
|
||||
* they were the only one with no signal at all.
|
||||
*
|
||||
* Stated plainly and without alarm: nothing is known to be wrong with the
|
||||
* file. What is known is that nothing looked.
|
||||
*/
|
||||
export function UnscannedNotice({ unscanned, className }: UnscannedNoticeProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!unscanned) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p className={cn('text-muted-foreground flex items-center justify-center gap-1.5 text-xs', className)}>
|
||||
<ShieldQuestion className="size-3.5 shrink-0" />
|
||||
{t('This file was not checked for viruses.')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { File as FileIcon } from 'lucide-react';
|
||||
|
||||
import { CommentsShellCompact } from '@/components/comments/shells/comments-shell-compact';
|
||||
import { DownloadAction } from '@/components/download-action';
|
||||
import { UnscannedNotice } from '@/components/files/unscanned-notice';
|
||||
import { PreviewAction } from '@/components/preview-action';
|
||||
import { CategoryBadges, type CategoryTag } from '@/components/files/category-badges';
|
||||
import { type VersionLinks } from '@/components/files/version-badge';
|
||||
@@ -33,6 +34,8 @@ interface PublicFileShowProps {
|
||||
preview_url: string | null;
|
||||
download_url: string;
|
||||
download_limit: DownloadLimit;
|
||||
/** Whether the file went out unchecked — see UnscannedNotice. */
|
||||
unscanned?: boolean;
|
||||
comments_enabled: boolean;
|
||||
comments_endpoint: string;
|
||||
}
|
||||
@@ -43,6 +46,7 @@ export default function PublicFileShowCompact({
|
||||
preview_url,
|
||||
download_url,
|
||||
download_limit,
|
||||
unscanned,
|
||||
comments_enabled,
|
||||
comments_endpoint,
|
||||
}: PublicFileShowProps) {
|
||||
@@ -86,6 +90,8 @@ export default function PublicFileShowCompact({
|
||||
/>
|
||||
<DownloadAction href={download_url} limit={download_limit} size="sm" />
|
||||
</div>
|
||||
|
||||
<UnscannedNotice unscanned={unscanned} className="justify-start" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { File as FileIcon } from 'lucide-react';
|
||||
|
||||
import { CommentsShellDefault } from '@/components/comments/shells/comments-shell-default';
|
||||
import { DownloadAction } from '@/components/download-action';
|
||||
import { UnscannedNotice } from '@/components/files/unscanned-notice';
|
||||
import { PreviewAction } from '@/components/preview-action';
|
||||
import { CategoryBadges, type CategoryTag } from '@/components/files/category-badges';
|
||||
import { type VersionLinks } from '@/components/files/version-badge';
|
||||
@@ -33,6 +34,8 @@ interface PublicFileShowProps {
|
||||
preview_url: string | null;
|
||||
download_url: string;
|
||||
download_limit: DownloadLimit;
|
||||
/** Whether the file went out unchecked — see UnscannedNotice. */
|
||||
unscanned?: boolean;
|
||||
comments_enabled: boolean;
|
||||
comments_endpoint: string;
|
||||
}
|
||||
@@ -43,6 +46,7 @@ export default function PublicFileShow({
|
||||
preview_url,
|
||||
download_url,
|
||||
download_limit,
|
||||
unscanned,
|
||||
comments_enabled,
|
||||
comments_endpoint,
|
||||
}: PublicFileShowProps) {
|
||||
@@ -81,6 +85,8 @@ export default function PublicFileShow({
|
||||
<DownloadAction href={download_url} limit={download_limit} size="default" />
|
||||
</div>
|
||||
|
||||
<UnscannedNotice unscanned={unscanned} />
|
||||
|
||||
{comments_enabled && (
|
||||
<div className="w-full max-w-lg">
|
||||
<CommentsShellDefault fileName={file.name} endpoint={comments_endpoint} inline />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Head } from '@inertiajs/react';
|
||||
|
||||
import { CommentsShellDrive } from '@/components/comments/shells/comments-shell-drive';
|
||||
import { DownloadAction } from '@/components/download-action';
|
||||
import { UnscannedNotice } from '@/components/files/unscanned-notice';
|
||||
import { PreviewAction } from '@/components/preview-action';
|
||||
import { CategoryBadges, type CategoryTag } from '@/components/files/category-badges';
|
||||
import { type VersionLinks } from '@/components/files/version-badge';
|
||||
@@ -33,6 +34,8 @@ interface PublicFileShowProps {
|
||||
preview_url: string | null;
|
||||
download_url: string;
|
||||
download_limit: DownloadLimit;
|
||||
/** Whether the file went out unchecked — see UnscannedNotice. */
|
||||
unscanned?: boolean;
|
||||
comments_enabled: boolean;
|
||||
comments_endpoint: string;
|
||||
}
|
||||
@@ -43,6 +46,7 @@ export default function PublicFileShowDrive({
|
||||
preview_url,
|
||||
download_url,
|
||||
download_limit,
|
||||
unscanned,
|
||||
comments_enabled,
|
||||
comments_endpoint,
|
||||
}: PublicFileShowProps) {
|
||||
@@ -92,6 +96,8 @@ export default function PublicFileShowDrive({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UnscannedNotice unscanned={unscanned} />
|
||||
|
||||
{comments_enabled && (
|
||||
<div className="w-full">
|
||||
<CommentsShellDrive fileName={file.name} endpoint={comments_endpoint} inline />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { File as FileIcon } from 'lucide-react';
|
||||
|
||||
import { CommentsShellGallery } from '@/components/comments/shells/comments-shell-gallery';
|
||||
import { DownloadAction } from '@/components/download-action';
|
||||
import { UnscannedNotice } from '@/components/files/unscanned-notice';
|
||||
import { PreviewAction } from '@/components/preview-action';
|
||||
import { CategoryBadges, type CategoryTag } from '@/components/files/category-badges';
|
||||
import { type VersionLinks } from '@/components/files/version-badge';
|
||||
@@ -33,6 +34,8 @@ interface PublicFileShowProps {
|
||||
preview_url: string | null;
|
||||
download_url: string;
|
||||
download_limit: DownloadLimit;
|
||||
/** Whether the file went out unchecked — see UnscannedNotice. */
|
||||
unscanned?: boolean;
|
||||
comments_enabled: boolean;
|
||||
comments_endpoint: string;
|
||||
}
|
||||
@@ -43,6 +46,7 @@ export default function PublicFileShowGallery({
|
||||
preview_url,
|
||||
download_url,
|
||||
download_limit,
|
||||
unscanned,
|
||||
comments_enabled,
|
||||
comments_endpoint,
|
||||
}: PublicFileShowProps) {
|
||||
@@ -84,6 +88,8 @@ export default function PublicFileShowGallery({
|
||||
<DownloadAction href={download_url} limit={download_limit} size="lg" />
|
||||
</div>
|
||||
|
||||
<UnscannedNotice unscanned={unscanned} />
|
||||
|
||||
{comments_enabled && (
|
||||
<div className="w-full">
|
||||
<CommentsShellGallery fileName={file.name} endpoint={comments_endpoint} inline />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Head } from '@inertiajs/react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
import { CategoryBadges, type CategoryTag } from '@/components/files/category-badges';
|
||||
import { UnscannedNotice } from '@/components/files/unscanned-notice';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useTranslation } from '@/hooks/use-translation';
|
||||
import AuthLayout from '@/layouts/auth-layout';
|
||||
@@ -15,9 +16,11 @@ interface ShareShowProps {
|
||||
categories: CategoryTag[];
|
||||
};
|
||||
download_url?: string;
|
||||
/** Whether the file went out unchecked — see UnscannedNotice. */
|
||||
unscanned?: boolean;
|
||||
}
|
||||
|
||||
export default function ShareShow({ status, file, download_url }: ShareShowProps) {
|
||||
export default function ShareShow({ status, file, download_url, unscanned }: ShareShowProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (status !== 'active' || !file || !download_url) {
|
||||
@@ -56,6 +59,8 @@ export default function ShareShow({ status, file, download_url }: ShareShowProps
|
||||
<Download className="size-4" /> {t('Download')}
|
||||
</a>
|
||||
</Button>
|
||||
|
||||
<UnscannedNotice unscanned={unscanned} className="mt-4" />
|
||||
</AuthLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use App\Modules\Files\Models\File;
|
||||
use App\Modules\Files\Scanning\NotScannedReason;
|
||||
use App\Modules\Files\Scanning\ScanStatus;
|
||||
use App\Modules\Files\Sharing\CreateShareLink;
|
||||
use App\Modules\Groups\Models\Group;
|
||||
use App\Modules\Platform\Settings\Setting;
|
||||
use App\Modules\Platform\Settings\Settings;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
/**
|
||||
* What somebody following a public link is told about a file nothing
|
||||
* checked.
|
||||
*
|
||||
* An installation that scans can still let files through — too large for
|
||||
* the scanner, an archive it could not open, or an upload that arrived
|
||||
* while the scanner was down. The uploader sees that on their own file and
|
||||
* staff see it in the library. The person holding the link sees the same
|
||||
* page as for a file that passed, and they neither chose the policy nor
|
||||
* can see the setting.
|
||||
*/
|
||||
beforeEach(function () {
|
||||
Storage::fake('files');
|
||||
$this->admin = User::factory()->create();
|
||||
$this->settings = app(Settings::class);
|
||||
|
||||
$this->settings->set(Setting::VirusScanningEnabled, true);
|
||||
$this->settings->set(Setting::VirusScannerAddress, 'tcp://scanner.test:3310');
|
||||
$this->settings->set(Setting::PublicListingEnabled, true);
|
||||
$this->settings->set(Setting::PublicListingSlug, 'public');
|
||||
$this->settings->set(Setting::Theme, 'default');
|
||||
});
|
||||
|
||||
/** The `unscanned` prop on the share page for a file in this state. */
|
||||
function sharedFileNotice(array $scan): bool
|
||||
{
|
||||
$file = File::factory()->create(array_merge(['uploaded_by' => test()->admin->id], $scan));
|
||||
$link = app(CreateShareLink::class)->for($file, test()->admin);
|
||||
|
||||
$notice = null;
|
||||
|
||||
test()->get("/s/{$link->token}")->assertInertia(function (AssertableInertia $page) use (&$notice) {
|
||||
$notice = $page->toArray()['props']['unscanned'];
|
||||
});
|
||||
|
||||
return $notice;
|
||||
}
|
||||
|
||||
test('a link to a file nothing checked says so', function () {
|
||||
expect(sharedFileNotice([
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::TooLarge->value,
|
||||
]))->toBeTrue();
|
||||
});
|
||||
|
||||
test('the same for a file that went out while the scanner was down, or that it could not open', function () {
|
||||
foreach ([NotScannedReason::ScannerUnavailable, NotScannedReason::Encrypted] as $reason) {
|
||||
expect(sharedFileNotice(['scan_status' => ScanStatus::NotScanned, 'scan_note' => $reason->value]))
|
||||
->toBeTrue($reason->value);
|
||||
}
|
||||
});
|
||||
|
||||
test('a file that passed says nothing', function () {
|
||||
expect(sharedFileNotice(['scan_status' => ScanStatus::Clean]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('a file from before this installation scanned says nothing', function () {
|
||||
// Every file on an installation that has only just switched scanning
|
||||
// on is in this state. Saying it about all of them says nothing about
|
||||
// any of them.
|
||||
expect(sharedFileNotice([
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::BeforeScanning->value,
|
||||
]))->toBeFalse();
|
||||
|
||||
expect(sharedFileNotice(['scan_status' => ScanStatus::NotScanned, 'scan_note' => null]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('an installation that does not scan says nothing about any of it', function () {
|
||||
$this->settings->set(Setting::VirusScanningEnabled, false);
|
||||
|
||||
expect(sharedFileNotice([
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::TooLarge->value,
|
||||
]))->toBeFalse();
|
||||
});
|
||||
|
||||
test('the public file page says it too, in every theme', function (string $theme) {
|
||||
$this->settings->set(Setting::Theme, $theme);
|
||||
|
||||
$group = Group::query()->create(['name' => 'Showcase', 'public' => true]);
|
||||
$file = File::factory()->public()->create([
|
||||
'uploaded_by' => $this->admin->id,
|
||||
'scan_status' => ScanStatus::NotScanned,
|
||||
'scan_note' => NotScannedReason::ScannerUnavailable->value,
|
||||
]);
|
||||
shareFileWithGroup($file, $group);
|
||||
|
||||
$this->get("/public/files/{$file->slug}")->assertInertia(
|
||||
fn (AssertableInertia $page) => $page
|
||||
->component("public/themes/{$theme}/file")
|
||||
->where('unscanned', true),
|
||||
);
|
||||
})->with(['default', 'compact', 'drive', 'gallery']);
|
||||
Reference in New Issue
Block a user