diff --git a/api/_helpers.php b/api/_helpers.php index 6745d02..8b9e206 100644 --- a/api/_helpers.php +++ b/api/_helpers.php @@ -33,7 +33,10 @@ function verify_origin(): void { function get_hmac_secret(): string { $secretFile = __DIR__ . '/../data/secret.key'; if (file_exists($secretFile)) { - return trim(file_get_contents($secretFile)); + $raw = file_get_contents($secretFile); + if (is_string($raw) && $raw !== '') { + return trim($raw); + } } $secret = bin2hex(random_bytes(32)); $dir = dirname($secretFile); @@ -53,8 +56,10 @@ function check_rate_limit(string $action, int $limit, int $window_seconds): bool $now = time(); $times = []; if (file_exists($rateFile)) { - $times = json_decode(file_get_contents($rateFile), true) ?: []; - $times = array_values(array_filter($times, fn($t) => $t > $now - $window_seconds)); + $raw = file_get_contents($rateFile); + $decoded = is_string($raw) ? json_decode($raw, true) : []; + $times = is_array($decoded) ? $decoded : []; + $times = array_values(array_filter($times, fn($t) => is_numeric($t) && (int)$t > $now - $window_seconds)); } if (count($times) >= $limit) return false; $times[] = $now; @@ -68,9 +73,15 @@ function read_json_locked(string $file): array { $dir = dirname($file); if (!is_dir($dir)) mkdir($dir, 0750, true); $fp = fopen($file, 'c+'); + if ($fp === false) { + throw new RuntimeException('Unable to open file: ' . $file); + } flock($fp, LOCK_EX); $size = filesize($file); - $data = ($size > 0) ? (json_decode(fread($fp, $size), true) ?: []) : []; + $size = is_int($size) ? $size : 0; + $raw = $size > 0 ? fread($fp, $size) : ''; + $decoded = is_string($raw) ? json_decode($raw, true) : []; + $data = is_array($decoded) ? $decoded : []; return [$fp, $data]; } diff --git a/api/check-short.php b/api/check-short.php index 80fa0e8..d7e0af6 100644 --- a/api/check-short.php +++ b/api/check-short.php @@ -18,7 +18,7 @@ if ($_SERVER['REQUEST_METHOD'] !== 'GET') { exit; } -$code = $_GET['code'] ?? ''; +$code = isset($_GET['code']) && is_string($_GET['code']) ? $_GET['code'] : ''; if (empty($code) || !preg_match('/^[a-z0-9]{4,10}$/', $code)) { http_response_code(400); echo json_encode(['error' => 'Invalid code']); @@ -32,7 +32,9 @@ if (!file_exists($dbFile)) { exit; } -$urls = json_decode(file_get_contents($dbFile), true) ?: []; +$rawUrls = file_get_contents($dbFile); +$decodedUrls = is_string($rawUrls) ? json_decode($rawUrls, true) : []; +$urls = is_array($decodedUrls) ? $decodedUrls : []; if (!isset($urls[$code])) { http_response_code(404); echo json_encode(['error' => 'Invoice not found']); @@ -40,7 +42,8 @@ if (!isset($urls[$code])) { } $data = $urls[$code]; -$hash = is_array($data) ? $data['h'] : $data; +$hash = is_array($data) ? ($data['h'] ?? '') : $data; +$hash = is_string($hash) ? $hash : ''; $signature = is_array($data) ? $data['s'] : null; // Re-derive expected signature so client can verify diff --git a/api/node.php b/api/node.php index fa658a0..432cd9f 100644 --- a/api/node.php +++ b/api/node.php @@ -40,9 +40,11 @@ $now = time(); $rateData = []; if (file_exists($rateFile)) { - $rateData = json_decode(file_get_contents($rateFile), true) ?: []; + $rawRate = file_get_contents($rateFile); + $decodedRate = is_string($rawRate) ? json_decode($rawRate, true) : []; + $rateData = is_array($decodedRate) ? $decodedRate : []; // Clean old entries - $rateData = array_filter($rateData, fn($t) => $t > $now - 60); + $rateData = array_values(array_filter($rateData, fn($t) => is_numeric($t) && (int)$t > $now - 60)); } if (count($rateData) >= 60) { @@ -55,15 +57,16 @@ $rateData[] = $now; file_put_contents($rateFile, json_encode($rateData)); // Parse request -$input = json_decode(file_get_contents('php://input'), true); -if (!$input || !isset($input['method'])) { +$rawInput = file_get_contents('php://input'); +$input = is_string($rawInput) ? json_decode($rawInput, true) : null; +if (!is_array($input) || !isset($input['method']) || !is_string($input['method'])) { http_response_code(400); echo json_encode(['error' => 'Missing method']); exit; } $method = $input['method']; -$params = $input['params'] ?? []; +$params = isset($input['params']) && is_array($input['params']) ? $input['params'] : []; // Determine endpoint type $isJsonRpc = in_array($method, $ALLOWED_JSON_RPC); @@ -79,8 +82,9 @@ if (!$isJsonRpc && !$isHttp) { $cacheFile = __DIR__ . '/../data/node_cache.json'; $cachedNode = null; if (file_exists($cacheFile)) { - $cache = json_decode(file_get_contents($cacheFile), true); - if ($cache && ($cache['time'] ?? 0) > $now - 300) { + $rawCache = file_get_contents($cacheFile); + $cache = is_string($rawCache) ? json_decode($rawCache, true) : null; + if (is_array($cache) && ($cache['time'] ?? 0) > $now - 300 && isset($cache['node']) && is_string($cache['node'])) { $cachedNode = $cache['node']; } } @@ -108,6 +112,10 @@ foreach ($orderedNodes as $node) { } $ch = curl_init($url); + if ($ch === false) { + $lastError = 'cURL init failed'; + continue; + } curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, diff --git a/api/rates.php b/api/rates.php index d101ce5..055ee42 100644 --- a/api/rates.php +++ b/api/rates.php @@ -6,8 +6,10 @@ $cacheFile = __DIR__ . '/../data/rates_cache.json'; $cacheTTL = 120; // seconds if (file_exists($cacheFile)) { - $cached = json_decode(file_get_contents($cacheFile), true); - if ($cached && (time() - ($cached['_time'] ?? 0)) < $cacheTTL) { + $rawCached = file_get_contents($cacheFile); + $cached = is_string($rawCached) ? json_decode($rawCached, true) : null; + $cachedTime = is_array($cached) && isset($cached['_time']) && is_numeric($cached['_time']) ? (int)$cached['_time'] : 0; + if (is_array($cached) && (time() - $cachedTime) < $cacheTTL) { unset($cached['_time']); header('Cache-Control: public, max-age=60'); echo json_encode($cached); @@ -15,10 +17,15 @@ if (file_exists($cacheFile)) { } } -$currencies = $_GET['c'] ?? 'eur,usd,chf,gbp,jpy,rub,brl'; +$currencies = isset($_GET['c']) && is_string($_GET['c']) ? $_GET['c'] : 'eur,usd,chf,gbp,jpy,rub,brl'; $currencies = preg_replace('/[^a-z,]/', '', strtolower($currencies)); $url = 'https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=' . $currencies; $ch = curl_init($url); +if ($ch === false) { + http_response_code(502); + echo json_encode(['error' => 'Failed to initialize request']); + exit; +} curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, @@ -30,7 +37,7 @@ curl_close($ch); if ($response !== false && $httpCode === 200) { $data = json_decode($response, true); - if ($data) { + if (is_array($data)) { $data['_time'] = time(); file_put_contents($cacheFile, json_encode($data)); unset($data['_time']); @@ -42,8 +49,9 @@ if ($response !== false && $httpCode === 200) { // On error, serve stale cache if available if (file_exists($cacheFile)) { - $cached = json_decode(file_get_contents($cacheFile), true); - if ($cached) { + $rawCached = file_get_contents($cacheFile); + $cached = is_string($rawCached) ? json_decode($rawCached, true) : null; + if (is_array($cached)) { unset($cached['_time']); header('Cache-Control: public, max-age=30'); echo json_encode($cached); diff --git a/api/shorten.php b/api/shorten.php index b018cab..10e8e45 100644 --- a/api/shorten.php +++ b/api/shorten.php @@ -21,8 +21,9 @@ if (!check_rate_limit('shorten', 20, 3600)) { $dbFile = __DIR__ . '/../data/urls.json'; -$input = json_decode(file_get_contents('php://input'), true); -$hash = $input['hash'] ?? ''; +$rawInput = file_get_contents('php://input'); +$input = is_string($rawInput) ? json_decode($rawInput, true) : null; +$hash = is_array($input) && isset($input['hash']) && is_string($input['hash']) ? $input['hash'] : ''; if (empty($hash) || strlen($hash) > 500 || !preg_match('/^[a-zA-Z0-9%+_=&.-]{1,500}$/', $hash)) { http_response_code(400); @@ -33,10 +34,16 @@ if (empty($hash) || strlen($hash) > 500 || !preg_match('/^[a-zA-Z0-9%+_=&.-]{1,5 $secret = get_hmac_secret(); [$fp, $urls] = read_json_locked($dbFile); +if (!is_array($urls)) { + $urls = []; +} // Check if this hash already exists foreach ($urls as $code => $data) { - $stored_hash = is_array($data) ? $data['h'] : $data; + $stored_hash = is_array($data) ? ($data['h'] ?? null) : $data; + if (!is_string($stored_hash)) { + continue; + } if ($stored_hash === $hash) { flock($fp, LOCK_UN); fclose($fp); diff --git a/api/verify.php b/api/verify.php index bbc5c74..3c8f62e 100644 --- a/api/verify.php +++ b/api/verify.php @@ -22,14 +22,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { echo json_encode(['error' => 'Rate limit exceeded']); exit; } - $code = $_GET['code'] ?? ''; + $code = isset($_GET['code']) && is_string($_GET['code']) ? $_GET['code'] : ''; if (empty($code) || !preg_match('/^[a-z0-9]{4,10}$/', $code)) { echo json_encode(['verified' => false]); exit; } - $proofs = file_exists($dbFile) ? (json_decode(file_get_contents($dbFile), true) ?: []) : []; + $rawProofs = file_exists($dbFile) ? file_get_contents($dbFile) : null; + $decodedProofs = is_string($rawProofs) ? json_decode($rawProofs, true) : []; + $proofs = is_array($decodedProofs) ? $decodedProofs : []; if (isset($proofs[$code])) { - echo json_encode(array_merge(['verified' => true], $proofs[$code])); + $response = ['verified' => true]; + $proofEntry = $proofs[$code]; + if (is_array($proofEntry)) { + foreach ($proofEntry as $k => $v) { + if (is_string($k)) { + $response[$k] = $v; + } + } + } + echo json_encode($response); } else { echo json_encode(['verified' => false]); } @@ -51,15 +62,16 @@ if (!check_rate_limit('verify_post', 10, 3600)) { exit; } -$input = json_decode(file_get_contents('php://input'), true); -if (!$input) { +$rawInput = file_get_contents('php://input'); +$input = is_string($rawInput) ? json_decode($rawInput, true) : null; +if (!is_array($input)) { http_response_code(400); echo json_encode(['error' => 'Invalid JSON']); exit; } -$code = $input['code'] ?? ''; -$txHash = $input['tx_hash'] ?? ''; +$code = isset($input['code']) && is_string($input['code']) ? $input['code'] : ''; +$txHash = isset($input['tx_hash']) && is_string($input['tx_hash']) ? $input['tx_hash'] : ''; $amount = floatval($input['amount'] ?? 0); $confirmations = intval($input['confirmations'] ?? 0); @@ -82,7 +94,9 @@ if (!file_exists($urlsFile)) { echo json_encode(['error' => 'Invoice not found']); exit; } -$urls = json_decode(file_get_contents($urlsFile), true) ?: []; +$rawUrls = file_get_contents($urlsFile); +$decodedUrls = is_string($rawUrls) ? json_decode($rawUrls, true) : []; +$urls = is_array($decodedUrls) ? $decodedUrls : []; if (!isset($urls[$code])) { http_response_code(404); echo json_encode(['error' => 'Invoice not found']); @@ -91,6 +105,9 @@ if (!isset($urls[$code])) { // Store proof with atomic lock [$fp, $proofs] = read_json_locked($dbFile); +if (!is_array($proofs)) { + $proofs = []; +} $status = ($input['status'] ?? 'paid') === 'pending' ? 'pending' : 'paid'; diff --git a/s.php b/s.php index 8362e05..8d02343 100644 --- a/s.php +++ b/s.php @@ -1,5 +1,7 @@