Release 2.3.0
A share can now hold a private text (a password, a key, a short note) with its files or on its own. The upload page's new "Private text" card takes up to 100 KB; the browser encrypts the text and sends it through the same chunk pipeline as a file, flagged is_text on share_files, so it gets the share's password, expiry, download limit and cleanup. The recipient sees the text only after pressing "Show text", which counts as their download, so a messenger link preview cannot use up a share limited to one download. The text is left out of the file list, the ZIP and the file counts; the admin dashboard marks shares that hold one with "Text". The website gains a Private text feature card and a fifth phone screenshot; every screenshot is retaken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
202813a1e6
commit
f9a7839ad3
@@ -23,14 +23,17 @@ class DownloadController extends Controller
|
||||
|
||||
/**
|
||||
* Download all files as a ZIP archive, streamed file by file as it is decrypted: stored without
|
||||
* compression, with ZIP64 for files over 4 GB, and never held in memory or written to disk.
|
||||
* compression, with ZIP64 for files over 4 GB, and never held in memory or written to disk. The
|
||||
* private text is left out: it is only ever shown on the page.
|
||||
*/
|
||||
public function download(Request $request, Share $share): StreamedResponse
|
||||
{
|
||||
abort_if(! $share->isCompleted() || $share->isExpired(), 404);
|
||||
|
||||
$share->load('files');
|
||||
$key = $this->resolveDecryptionKey($share);
|
||||
$share->load(['files' => fn ($query) => $query->where('is_text', false)]);
|
||||
abort_if($share->files->isEmpty(), 404);
|
||||
|
||||
$key = $this->shareService->sessionDecryptionKey($share, $request->session());
|
||||
|
||||
// Counted before the body streams: the session is saved by then.
|
||||
abort_unless($this->shareService->claimDownload($share, $request->session()), 404);
|
||||
@@ -73,14 +76,14 @@ class DownloadController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a single file.
|
||||
* Download a single file; never the private text, which is only ever shown on the page.
|
||||
*/
|
||||
public function downloadFile(Request $request, Share $share, ShareFile $shareFile): StreamedResponse
|
||||
{
|
||||
abort_if(! $share->isCompleted() || $share->isExpired(), 404);
|
||||
abort_if($shareFile->share_id !== $share->id, 404);
|
||||
abort_if($shareFile->share_id !== $share->id || $shareFile->is_text, 404);
|
||||
|
||||
$key = $this->resolveDecryptionKey($share);
|
||||
$key = $this->shareService->sessionDecryptionKey($share, $request->session());
|
||||
|
||||
abort_unless($this->shareService->claimDownload($share, $request->session()), 404);
|
||||
|
||||
@@ -122,20 +125,4 @@ class DownloadController extends Controller
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the decryption key from session or share.
|
||||
*/
|
||||
private function resolveDecryptionKey(Share $share): string
|
||||
{
|
||||
if ($share->isPasswordProtected()) {
|
||||
$key = session('share_key_'.$share->token);
|
||||
|
||||
abort_if(! $key, 403, 'Password required');
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
return $this->shareService->getDecryptionKey($share);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user