<?php
declare(strict_types=1); namespace App\Infrastructure; use App\Domain\HttpClientInterface; use RuntimeException; class CurlHttpClient implements HttpClientInterface { public function get(string $url, int $timeoutSeconds = 3, int $maxBytes = 51200): string { if (!preg_match('/^https?:\/\//i', $url)) { throw new RuntimeException('Invalid URL scheme. Only HTTP and HTTPS are allowed.'); } $ch = curl_init(); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 3); curl_setopt($ch, CURLOPT_TIMEOUT, $timeoutSeconds); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeoutSeconds); curl_setopt($ch, CURLOPT_USERAGENT, 'LoreBinder/1.0 (URL Fetcher)'); $body = ''; curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($handle, $data) use (&$body, $maxBytes) { $length = strlen($data); $body .= $data; if (strlen($body) > $maxBytes) { return 0; } return $length; }); curl_exec($ch); $error = curl_error($ch); $errno = curl_errno($ch); curl_close($ch); if ($errno !== 0 && $errno !== CURLE_WRITE_ERROR) { throw new RuntimeException('cURL error: ' . $error); } return $body; } } 