'URL parameter is required']); exit; } $imageUrl = $_GET['url']; $width = isset($_GET['w']) ? intval($_GET['w']) : null; $height = isset($_GET['h']) ? intval($_GET['h']) : null; $quality = isset($_GET['q']) ? intval($_GET['q']) : 85; // Validate URL if (!filter_var($imageUrl, FILTER_VALIDATE_URL)) { http_response_code(400); echo json_encode(['error' => 'Invalid URL']); exit; } // Allowed domains (add your CDN domains here) $allowedDomains = [ 'cdn.jsdelivr.net', 'favicon.hibbit.net', 'favicon.hibbit.cloud', 'localhost', '127.0.0.1' ]; $parsedUrl = parse_url($imageUrl); $domain = $parsedUrl['host'] ?? ''; if (!in_array($domain, $allowedDomains)) { http_response_code(403); echo json_encode(['error' => 'Domain not allowed']); exit; } // Create cache directory $cacheDir = '../data/images/cache/'; if (!is_dir($cacheDir)) { mkdir($cacheDir, 0755, true); } // Generate cache key $cacheKey = md5($imageUrl . $width . $height . $quality); $cacheFile = $cacheDir . $cacheKey; // Check if cached version exists and is fresh (1 day) if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 86400) { // Serve cached file $imageInfo = getimagesize($cacheFile); if ($imageInfo) { header('Content-Type: ' . $imageInfo['mime']); header('Content-Length: ' . filesize($cacheFile)); header('Cache-Control: public, max-age=86400'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT'); header('ETag: "' . $cacheKey . '"'); readfile($cacheFile); exit; } } // Fetch the image $context = stream_context_create([ 'http' => [ 'timeout' => 30, 'user_agent' => 'Bookmarkly Image Proxy/1.0', 'follow_location' => true, 'max_redirects' => 5 ] ]); $imageData = @file_get_contents($imageUrl, false, $context); if ($imageData === false) { http_response_code(404); echo json_encode(['error' => 'Failed to fetch image']); exit; } // Check if ImageMagick is available for processing if (extension_loaded('imagick') && ($width || $height || $quality < 100)) { try { $imagick = new Imagick(); $imagick->readImageBlob($imageData); // Resize if dimensions are specified if ($width || $height) { $imagick->resizeImage( $width ?: 0, $height ?: 0, Imagick::FILTER_LANCZOS, 1 ); } // Set quality for JPEG if ($imagick->getImageFormat() === 'JPEG') { $imagick->setImageCompressionQuality($quality); } $processedData = $imagick->getImageBlob(); $imagick->destroy(); // Save to cache file_put_contents($cacheFile, $processedData); // Set headers $imageInfo = getimagesize($cacheFile); header('Content-Type: ' . $imageInfo['mime']); header('Content-Length: ' . strlen($processedData)); header('Cache-Control: public, max-age=86400'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT'); header('ETag: "' . $cacheKey . '"'); echo $processedData; exit; } catch (Exception $e) { // Fallback to original image if processing fails error_log('Image processing failed: ' . $e->getMessage()); } } // Serve original image and cache it file_put_contents($cacheFile, $imageData); $imageInfo = @getimagesize($cacheFile); if ($imageInfo) { header('Content-Type: ' . $imageInfo['mime']); } else { header('Content-Type: image/jpeg'); // fallback } header('Content-Length: ' . strlen($imageData)); header('Cache-Control: public, max-age=86400'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT'); header('ETag: "' . $cacheKey . '"'); echo $imageData; ?>