$message, 'serverTime' => (int) round(microtime(true) * 1000)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function write_all($socket, string $value): void { $written = 0; $length = strlen($value); while ($written < $length) { $result = fwrite($socket, substr($value, $written, 64 * 1024)); if ($result === false || $result === 0) { fclose($socket); fail_json(502, 'Failed to send the request to the LinkField server.'); } $written += $result; } } function request_body(int $maximum): array { $input = fopen('php://input', 'rb'); if (!is_resource($input)) fail_json(400, 'Could not read the request body.'); $declared = trim((string) ($_SERVER['CONTENT_LENGTH'] ?? '')); if ($declared !== '') { if (!preg_match('/^[0-9]+$/D', $declared)) { fclose($input); fail_json(400, 'Invalid request content length.'); } $length = (int) $declared; if ($length > $maximum) { fclose($input); fail_json(413, 'Request body is too large.'); } return [$input, $length]; } $temporary = fopen('php://temp/maxmemory:1048576', 'w+b'); if (!is_resource($temporary)) { fclose($input); fail_json(500, 'Could not stage the request body.'); } $length = 0; while (!feof($input)) { $chunk = fread($input, 64 * 1024); if ($chunk === false) { fclose($input); fclose($temporary); fail_json(400, 'Could not read the request body.'); } $length += strlen($chunk); if ($length > $maximum) { fclose($input); fclose($temporary); fail_json(413, 'Request body is too large.'); } if ($chunk !== '' && fwrite($temporary, $chunk) !== strlen($chunk)) { fclose($input); fclose($temporary); fail_json(500, 'Could not stage the request body.'); } } fclose($input); rewind($temporary); return [$temporary, $length]; } function write_body($socket, $body, int $length): void { $written = 0; while ($written < $length) { $chunk = fread($body, min(64 * 1024, $length - $written)); if ($chunk === false || $chunk === '') { fclose($body); fclose($socket); fail_json(400, 'Request body ended before its declared length.'); } write_all($socket, $chunk); $written += strlen($chunk); } fclose($body); } $portFile = __DIR__ . DIRECTORY_SEPARATOR . '.linkfield-port'; if (!is_file($portFile)) { fail_json(503, 'LinkField server is not running. Run npm start in this directory.'); } $portText = trim((string) @file_get_contents($portFile)); $port = filter_var($portText, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]]); if ($port === false) { fail_json(503, 'LinkField server port information is invalid. Restart npm start.'); } $targetPath = isset($_GET['path']) ? (string) $_GET['path'] : ''; if (!preg_match('#^/api/(?:cloud|player|realtime)(?:/[A-Za-z0-9_-]+)*$#D', $targetPath)) { fail_json(400, 'Invalid LinkField API path.'); } $query = $_GET; unset($query['path']); if ($query) { $targetPath .= '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986); } $method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')); if (!in_array($method, ['GET', 'POST', 'HEAD'], true)) { fail_json(405, 'Method not allowed.'); } [$body, $bodyLength] = request_body(8 * 1024 * 1024); $authorization = ''; if (function_exists('getallheaders')) { $headers = getallheaders(); if (is_array($headers)) { foreach ($headers as $name => $value) { if (strcasecmp((string) $name, 'Authorization') === 0) $authorization = (string) $value; if (strcasecmp((string) $name, 'X-LinkField-Authorization') === 0 && $authorization === '') $authorization = (string) $value; } } } if ($authorization === '') { $authorization = (string) ($_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? $_SERVER['HTTP_X_LINKFIELD_AUTHORIZATION'] ?? ''); } $socket = @stream_socket_client( 'tcp://127.0.0.1:' . $port, $errorNumber, $errorMessage, 3, STREAM_CLIENT_CONNECT ); if (!is_resource($socket)) { fail_json(502, 'LinkField server is not reachable on its local port. Restart npm start.'); } stream_set_timeout($socket, 30); $requestHeaders = [ $method . ' ' . $targetPath . " HTTP/1.1", 'Host: 127.0.0.1:' . $port, 'Connection: close', 'Accept: application/json', 'Content-Type: application/json', 'Content-Length: ' . $bodyLength, ]; if ($authorization !== '') $requestHeaders[] = 'Authorization: ' . str_replace(["\r", "\n"], '', $authorization); $remoteAddress = (string) ($_SERVER['REMOTE_ADDR'] ?? ''); if (preg_match('/^[0-9A-Fa-f:.]{3,64}$/', $remoteAddress)) $requestHeaders[] = 'X-Forwarded-For: ' . $remoteAddress; write_all($socket, implode("\r\n", $requestHeaders) . "\r\n\r\n"); write_body($socket, $body, $bodyLength); $response = ''; $separator = false; while (!feof($socket) && $separator === false && strlen($response) <= 64 * 1024) { $chunk = fread($socket, 8192); if ($chunk === false) break; $response .= $chunk; $separator = strpos($response, "\r\n\r\n"); } $meta = stream_get_meta_data($socket); if ($response === '' || $separator === false || !empty($meta['timed_out'])) { fclose($socket); fail_json(504, 'LinkField server did not respond in time.'); } $headerText = substr($response, 0, $separator); $responseBody = substr($response, $separator + 4); $headerLines = explode("\r\n", $headerText); $statusLine = array_shift($headerLines); if (!preg_match('#^HTTP/\d(?:\.\d)?\s+(\d{3})#', (string) $statusLine, $statusMatch)) { fail_json(502, 'Invalid status from the LinkField server.'); } http_response_code((int) $statusMatch[1]); foreach ($headerLines as $line) { $colon = strpos($line, ':'); if ($colon === false) continue; $name = trim(substr($line, 0, $colon)); $value = trim(substr($line, $colon + 1)); if (in_array(strtolower($name), ['content-type', 'cache-control', 'x-content-type-options', 'retry-after'], true)) { header($name . ': ' . $value, true); } } header('X-LinkField-Bridge: php', true); header_remove('X-Powered-By'); if ($method !== 'HEAD') { echo $responseBody; while (!feof($socket)) { $chunk = fread($socket, 64 * 1024); if ($chunk === false) break; echo $chunk; if (function_exists('flush')) flush(); } } fclose($socket);