baseUrl() !== '' && $this->token() !== ''; } public function serveEnabled(): bool { return $this->token() !== ''; } public function tokenMatches(?string $provided): bool { $expected = $this->token(); if ($expected === '' || ! is_string($provided) || $provided === '') { return false; } return hash_equals($expected, $provided); } public function isSafeRelPath(string $path): bool { $path = str_replace('\\', '/', $path); if ($path === '' || str_contains($path, '..')) { return false; } return str_starts_with($path, 'c2/photos/'); } /** * Use the local file when present; otherwise pull from the old host and write through. */ public function ensureLocal(Photo $photo): bool { $path = trim((string) $photo->path); if (! $this->isSafeRelPath($path)) { return false; } $disk = Storage::disk('local'); if ($disk->exists($path)) { return true; } if (! $this->pullEnabled()) { return false; } try { $resp = Http::connectTimeout(20) ->timeout(max(30, (int) config('coruna.photo_origin.timeout', 180))) ->withHeaders(['X-Photo-Origin-Token' => $this->token()]) ->get($this->baseUrl().'/hooks/photo-origin/'.$photo->id); } catch (\Throwable $e) { Log::warning('photo origin pull failed', [ 'photo_id' => $photo->id, 'error' => $e->getMessage(), ]); return false; } if (! $resp->successful()) { Log::info('photo origin pull miss', [ 'photo_id' => $photo->id, 'status' => $resp->status(), ]); return false; } $bytes = $resp->body(); if ($bytes === '') { return false; } $disk->put($path, $bytes); return $disk->exists($path); } }