'rate limited']); exit; } $url = trim($_GET['url'] ?? ''); if (!$url) { http_response_code(400); echo json_encode(['error' => 'url required']); exit; } if (!preg_match('#^gemini://#i', $url)) { http_response_code(400); echo json_encode(['error' => 'gemini:// URLs only']); exit; } if (strlen($url) > 1024) { http_response_code(400); echo json_encode(['error' => 'URL too long (max 1024)']); exit; } $parsed = parse_url($url); $host = $parsed['host'] ?? ''; $port = isset($parsed['port']) ? (int)$parsed['port'] : 1965; if (!$host) { http_response_code(400); echo json_encode(['error' => 'invalid host']); exit; } if ($port < 1 || $port > 65535) { http_response_code(400); echo json_encode(['error' => 'invalid port']); exit; } // SSRF guard: reject hosts that resolve to loopback / RFC1918 / link-local / // reserved ranges, so this endpoint can't be used to probe the internal network. if (!proxy_host_is_safe($host)) { http_response_code(400); echo json_encode(['error' => 'host not allowed']); exit; } $ctx = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true, ], ]); $socket = @stream_socket_client( "ssl://{$host}:{$port}", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx ); if (!$socket) { http_response_code(502); echo json_encode(['error' => "connect failed ({$errno}): {$errstr}"]); exit; } stream_set_timeout($socket, 10); fwrite($socket, $url . "\r\n"); $raw = ''; $cap = 4 * 1024 * 1024; while (!feof($socket)) { $chunk = fread($socket, 8192); if ($chunk === false) break; $raw .= $chunk; if (strlen($raw) >= $cap) { $raw = substr($raw, 0, $cap); break; } } fclose($socket); $eol = strpos($raw, "\r\n"); if ($eol === false) { http_response_code(502); echo json_encode(['error' => 'malformed response (no CRLF)']); exit; } $header = substr($raw, 0, $eol); $body = substr($raw, $eol + 2); if (!preg_match('#^(\d{2})\s*(.*)$#s', $header, $m)) { http_response_code(502); echo json_encode(['error' => 'malformed header line']); exit; } $status = (int)$m[1]; $meta = trim($m[2]); // Ensure body is valid UTF-8 for JSON encoding if (!mb_check_encoding($body, 'UTF-8')) { $body = mb_convert_encoding($body, 'UTF-8', 'ISO-8859-1'); } echo json_encode([ 'status' => $status, 'meta' => $meta, 'body' => $body, 'url' => $url, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);