aboutsummaryrefslogtreecommitdiffstats
path: root/partials/proxy_helpers.php
diff options
context:
space:
mode:
authorsillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
committersillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
commit898b52edcb47bcb3e9d6106e74ca73e74ea01e70 (patch)
tree85c6ee5ad58b860144551184d4cf86b560c62b91 /partials/proxy_helpers.php
downloadwww-898b52edcb47bcb3e9d6106e74ca73e74ea01e70.tar.gz
www-898b52edcb47bcb3e9d6106e74ca73e74ea01e70.zip
import live www.sillylaird.ca webrootHEADmain
Diffstat (limited to 'partials/proxy_helpers.php')
-rw-r--r--partials/proxy_helpers.php80
1 files changed, 80 insertions, 0 deletions
diff --git a/partials/proxy_helpers.php b/partials/proxy_helpers.php
new file mode 100644
index 0000000..59ac3cb
--- /dev/null
+++ b/partials/proxy_helpers.php
@@ -0,0 +1,80 @@
+<?php
+// partials/proxy_helpers.php — small shared helpers for the *-proxy.php endpoints.
+//
+// - lastfm_api_key(): read LASTFM_API_KEY from env (with legacy fallback).
+// - proxy_host_is_safe(): reject hosts that resolve to private/reserved IPs (SSRF guard).
+// - proxy_rate_limit(): simple per-IP sliding-window limit, file-backed.
+
+if (!function_exists('lastfm_api_key')) {
+ function lastfm_api_key(): string {
+ $env = getenv('LASTFM_API_KEY');
+ if (is_string($env) && $env !== '') return $env;
+ throw new RuntimeException('LASTFM_API_KEY is not set in the php-fpm environment');
+ }
+}
+
+if (!function_exists('proxy_host_is_safe')) {
+ function proxy_host_is_safe(string $host): bool {
+ if ($host === '') return false;
+ // Reject anything that looks like a literal IPv6 with brackets stripped earlier.
+ $records = @dns_get_record($host, DNS_A | DNS_AAAA);
+ $ips = [];
+ if (is_array($records)) {
+ foreach ($records as $r) {
+ if (!empty($r['ip'])) $ips[] = $r['ip'];
+ if (!empty($r['ipv6'])) $ips[] = $r['ipv6'];
+ }
+ }
+ // Also check if the host is itself an IP literal.
+ if (filter_var($host, FILTER_VALIDATE_IP)) {
+ $ips[] = $host;
+ }
+ if (!$ips) return false; // unresolvable -> deny
+ foreach ($ips as $ip) {
+ $ok = filter_var(
+ $ip,
+ FILTER_VALIDATE_IP,
+ FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
+ );
+ if ($ok === false) return false;
+ }
+ return true;
+ }
+}
+
+if (!function_exists('proxy_rate_limit')) {
+ /**
+ * Returns true if the request is allowed. Uses a tiny file per
+ * (endpoint, client IP) holding recent unix timestamps.
+ */
+ function proxy_rate_limit(string $endpoint, int $max = 30, int $windowSec = 60): bool {
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $key = preg_replace('/[^A-Za-z0-9._-]/', '_', $endpoint . '_' . $ip);
+ $dir = sys_get_temp_dir() . '/sillylaird_ratelimit';
+ if (!is_dir($dir)) @mkdir($dir, 0700, true);
+ $file = $dir . '/' . $key;
+
+ $now = time();
+ $cutoff = $now - $windowSec;
+
+ $fp = @fopen($file, 'c+');
+ if (!$fp) return true; // fail-open: don't block legit traffic on disk error
+ try {
+ flock($fp, LOCK_EX);
+ $data = stream_get_contents($fp);
+ $stamps = $data === '' ? [] : array_map('intval', explode("\n", trim($data)));
+ $stamps = array_values(array_filter($stamps, fn($t) => $t >= $cutoff));
+ if (count($stamps) >= $max) {
+ return false;
+ }
+ $stamps[] = $now;
+ ftruncate($fp, 0);
+ rewind($fp);
+ fwrite($fp, implode("\n", $stamps));
+ return true;
+ } finally {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ }
+ }
+}