aboutsummaryrefslogtreecommitdiff
path: root/src/Routes/ImageProxy.c
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-03-28 15:01:13 -0400
committerfrosty <gabriel@bwaaa.monster>2026-03-28 15:01:13 -0400
commit1b9187b1534992c0235dad064238b2d836489ca8 (patch)
treecdd7b7fd5c37a6ae2197ac6188607584de91893f /src/Routes/ImageProxy.c
parent86a9ebb90a2ac07d836c1408e3a15feb8615bd62 (diff)
downloadomnisearch-1b9187b1534992c0235dad064238b2d836489ca8.tar.gz
feat: changes to image proxy, proxy favicons
Diffstat (limited to 'src/Routes/ImageProxy.c')
-rw-r--r--src/Routes/ImageProxy.c211
1 files changed, 197 insertions, 14 deletions
diff --git a/src/Routes/ImageProxy.c b/src/Routes/ImageProxy.c
index c2d1a9a..670da68 100644
--- a/src/Routes/ImageProxy.c
+++ b/src/Routes/ImageProxy.c
@@ -1,9 +1,16 @@
#include "ImageProxy.h"
+#include "../Cache/Cache.h"
#include "../Proxy/Proxy.h"
+#include <arpa/inet.h>
#include <curl/curl.h>
+#include <curl/urlapi.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/socket.h>
#define MAX_IMAGE_SIZE (10 * 1024 * 1024)
@@ -13,7 +20,97 @@ typedef struct {
size_t capacity;
} MemoryBuffer;
+static int is_private_ip(const char *ip_str) {
+ struct in_addr addr;
+ if (inet_pton(AF_INET, ip_str, &addr) != 1) {
+ return 0;
+ }
+
+ uint32_t ip = ntohl(addr.s_addr);
+
+ // 10.0.0.0/8
+ if ((ip >> 24) == 10) {
+ return 1;
+ }
+
+ // 172.16.0.0/12
+ if ((ip >> 20) == 0xAC) {
+ uint8_t second = (ip >> 16) & 0xFF;
+ if (second >= 16 && second <= 31) {
+ return 1;
+ }
+ }
+
+ // 192.168.0.0/16
+ if ((ip >> 16) == 0xC0A8) {
+ return 1;
+ }
+
+ // 127.0.0.0/8
+ if ((ip >> 24) == 127) {
+ return 1;
+ }
+
+ // 169.254.0.0/16
+ if ((ip >> 16) == 0xA9FE) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int is_private_hostname(const char *hostname) {
+ struct addrinfo hints, *res, *p;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = getaddrinfo(hostname, NULL, &hints, &res);
+ if (err != 0) {
+ return 0;
+ }
+
+ for (p = res; p != NULL; p = p->ai_next) {
+ if (p->ai_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
+ char ip_str[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &(ipv4->sin_addr), ip_str, INET_ADDRSTRLEN);
+
+ if (is_private_ip(ip_str)) {
+ freeaddrinfo(res);
+ return 1;
+ }
+ }
+ }
+
+ freeaddrinfo(res);
+ return 0;
+}
+
static int is_allowed_domain(const char *url) {
+ CURLU *h = curl_url();
+ if (!h) {
+ return -1;
+ }
+
+ curl_url_set(h, CURLUPART_URL, url, 0);
+
+ char *scheme = NULL;
+ curl_url_get(h, CURLUPART_SCHEME, &scheme, 0);
+
+ int valid_scheme = 0;
+ if (scheme && (strcasecmp(scheme, "http") == 0 || strcasecmp(scheme, "https") == 0)) {
+ valid_scheme = 1;
+ }
+
+ if (scheme)
+ curl_free(scheme);
+
+ if (!valid_scheme) {
+ curl_url_cleanup(h);
+ return -1;
+ }
+
const char *protocol = strstr(url, "://");
if (!protocol) {
protocol = url;
@@ -30,21 +127,18 @@ static int is_allowed_domain(const char *url) {
}
strncpy(host, protocol, host_len);
- const char *allowed_domains[] = {"mm.bing.net", "th.bing.com", NULL};
-
- for (int i = 0; allowed_domains[i] != NULL; i++) {
- size_t domain_len = strlen(allowed_domains[i]);
- size_t host_str_len = strlen(host);
+ char *colon = strchr(host, ':');
+ if (colon) {
+ *colon = '\0';
+ }
- if (host_str_len >= domain_len) {
- const char *suffix = host + host_str_len - domain_len;
- if (strcmp(suffix, allowed_domains[i]) == 0) {
- return 1;
- }
- }
+ if (is_private_hostname(host)) {
+ curl_url_cleanup(h);
+ return 0;
}
- return 0;
+ curl_url_cleanup(h);
+ return 1;
}
static size_t write_callback(void *contents, size_t size, size_t nmemb,
@@ -73,6 +167,31 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb,
return realsize;
}
+static char *url_encode_key(const char *url) {
+ char *hash = malloc(33);
+ if (!hash)
+ return NULL;
+
+ unsigned char md5hash[16];
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ if (!ctx) {
+ free(hash);
+ return NULL;
+ }
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, url, strlen(url));
+ EVP_DigestFinal_ex(ctx, md5hash, NULL);
+ EVP_MD_CTX_free(ctx);
+
+ for (int i = 0; i < 16; i++) {
+ sprintf(hash + (i * 2), "%02x", md5hash[i]);
+ }
+ hash[32] = '\0';
+
+ return hash;
+}
+
int image_proxy_handler(UrlParams *params) {
const char *url = NULL;
for (int i = 0; i < params->count; i++) {
@@ -87,13 +206,59 @@ int image_proxy_handler(UrlParams *params) {
return 0;
}
- if (!is_allowed_domain(url)) {
- send_response("Domain not allowed");
+ int domain_check = is_allowed_domain(url);
+ if (domain_check == -1) {
+ send_response("Invalid URL scheme");
+ return 0;
+ }
+ if (domain_check == 0) {
+ send_response("Private addresses are not allowed");
+ return 0;
+ }
+
+ char *cache_key = url_encode_key(url);
+ if (!cache_key) {
+ send_response("Failed to generate cache key");
+ return 0;
+ }
+
+ char *cached_data = NULL;
+ size_t cached_size = 0;
+ int cache_ttl = get_cache_ttl_image();
+
+ if (cache_get(cache_key, cache_ttl, &cached_data, &cached_size) == 0) {
+ char content_type[64] = {0};
+
+ const char *ext = strrchr(url, '.');
+ if (ext) {
+ if (strcasecmp(ext, ".png") == 0) {
+ strncpy(content_type, "image/png", sizeof(content_type) - 1);
+ } else if (strcasecmp(ext, ".gif") == 0) {
+ strncpy(content_type, "image/gif", sizeof(content_type) - 1);
+ } else if (strcasecmp(ext, ".webp") == 0) {
+ strncpy(content_type, "image/webp", sizeof(content_type) - 1);
+ } else if (strcasecmp(ext, ".svg") == 0) {
+ strncpy(content_type, "image/svg+xml", sizeof(content_type) - 1);
+ } else if (strcasecmp(ext, ".ico") == 0) {
+ strncpy(content_type, "image/x-icon", sizeof(content_type) - 1);
+ } else if (strcasecmp(ext, ".bmp") == 0) {
+ strncpy(content_type, "image/bmp", sizeof(content_type) - 1);
+ }
+ }
+
+ if (strlen(content_type) == 0) {
+ strncpy(content_type, "image/jpeg", sizeof(content_type) - 1);
+ }
+
+ serve_data(cached_data, cached_size, content_type);
+ free(cached_data);
+ free(cache_key);
return 0;
}
CURL *curl = curl_easy_init();
if (!curl) {
+ free(cache_key);
send_response("Failed to initialize curl");
return 0;
}
@@ -102,6 +267,7 @@ int image_proxy_handler(UrlParams *params) {
if (!buf.data) {
curl_easy_cleanup(curl);
+ free(cache_key);
send_response("Memory allocation failed");
return 0;
}
@@ -111,6 +277,10 @@ int image_proxy_handler(UrlParams *params) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT,
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/120.0.0.0 Safari/537.36");
apply_proxy_settings(curl);
CURLcode res = curl_easy_perform(curl);
@@ -130,14 +300,27 @@ int image_proxy_handler(UrlParams *params) {
if (res != CURLE_OK || response_code != 200) {
free(buf.data);
+ free(cache_key);
send_response("Failed to fetch image");
return 0;
}
+ if (strlen(content_type) == 0 ||
+ strncmp(content_type, "image/", 6) != 0) {
+ free(buf.data);
+ free(cache_key);
+ send_response("Invalid content type");
+ return 0;
+ }
+
const char *mime_type =
strlen(content_type) > 0 ? content_type : "image/jpeg";
+
+ cache_set(cache_key, buf.data, buf.size);
+
serve_data(buf.data, buf.size, mime_type);
free(buf.data);
+ free(cache_key);
return 0;
}