attributes->get('service_caller') !== 'webmail') { return response()->json(['message' => 'Forbidden.'], 403); } $validated = $request->validate([ 'mailbox' => ['required', 'email', 'max:255'], 'filename' => ['required', 'string', 'max:255'], 'filesize' => ['required', 'integer', 'min:1'], ]); $mailbox = strtolower(trim((string) $validated['mailbox'])); try { $this->users->resolve($mailbox); } catch (RuntimeException $e) { return response()->json(['message' => $e->getMessage()], 422); } $uploadId = $this->uploads->init($this->ownerKey($mailbox), [ 'filename' => $validated['filename'], 'filesize' => (int) $validated['filesize'], 'mailbox' => $mailbox, ]); return response()->json([ 'success' => true, 'upload_id' => $uploadId, ]); } public function chunk(Request $request): JsonResponse { if ($request->attributes->get('service_caller') !== 'webmail') { return response()->json(['message' => 'Forbidden.'], 403); } $validated = $request->validate([ 'mailbox' => ['required', 'email', 'max:255'], 'upload_id' => ['required', 'string', 'uuid'], 'chunk_index' => ['required', 'integer', 'min:0'], 'chunk_size' => ['required', 'integer', 'min:1'], 'chunk' => ['required', 'file', 'max:10240'], ]); $mailbox = strtolower(trim((string) $validated['mailbox'])); $chunk = $request->file('chunk'); if ($chunk === null) { return response()->json(['success' => false, 'error' => 'Chunk missing.'], 422); } try { $received = $this->uploads->receiveChunk( $validated['upload_id'], $this->ownerKey($mailbox), (int) $validated['chunk_index'], $chunk, (int) $validated['chunk_size'], ); } catch (RuntimeException $e) { return response()->json(['success' => false, 'error' => $e->getMessage()], 400); } return response()->json([ 'success' => true, 'chunk_index' => (int) $validated['chunk_index'], 'chunks_received' => $received, ]); } public function finalize(Request $request): JsonResponse { if ($request->attributes->get('service_caller') !== 'webmail') { return response()->json(['message' => 'Forbidden.'], 403); } $validated = $request->validate([ 'mailbox' => ['required', 'email', 'max:255'], 'upload_id' => ['required', 'string', 'uuid'], 'total_chunks' => ['required', 'integer', 'min:1'], 'title' => ['nullable', 'string', 'max:120'], 'retention_days' => ['nullable', 'integer', 'min:1', 'max:365'], ]); $mailbox = strtolower(trim((string) $validated['mailbox'])); $ownerKey = $this->ownerKey($mailbox); try { $user = $this->users->resolve($mailbox); $assembled = $this->uploads->finalize( $validated['upload_id'], $ownerKey, (int) $validated['total_chunks'], ); $uploadedFile = $this->uploads->toUploadedFile($validated['upload_id'], $ownerKey); $title = trim((string) ($validated['title'] ?? '')); if ($title === '') { $title = Str::limit('Email attachment: '.$assembled['filename'], 120, ''); } $retentionDays = (int) ($validated['retention_days'] ?? config('transfer.mail_retention_days', config('transfer.default_retention_days', 30))); $transfer = $this->transfers->create($user, [ 'title' => $title, 'retention_days' => $retentionDays, ], [$uploadedFile]); } catch (RuntimeException $e) { $payload = ['message' => $e->getMessage()]; if (TransferWalletErrors::isInsufficientBalance($e)) { $payload['error_code'] = TransferWalletErrors::CODE_INSUFFICIENT_WALLET; } return response()->json($payload, 422); } finally { $this->uploads->cleanup($validated['upload_id']); } $transfer->load(['files', 'qrCode']); return response()->json([ 'data' => [ 'id' => $transfer->id, 'title' => $transfer->title, 'public_url' => $transfer->qrCode?->publicUrl(), 'paid_until' => $transfer->paid_until?->toIso8601String(), 'expires_at' => $transfer->paid_until?->toIso8601String(), 'files' => $transfer->files->map(fn ($file) => [ 'name' => $file->original_name, 'size_bytes' => $file->size_bytes, ])->values(), ], ], 201); } private function ownerKey(string $mailbox): string { return 'webmail:'.strtolower(trim($mailbox)); } }