Added rate limiting and settings fixes.
This commit is contained in:
parent
c3ed901738
commit
f38fe3c42e
9 changed files with 363 additions and 1 deletions
|
|
@ -31,3 +31,14 @@ domain = https://search.example.com
|
||||||
# Use *,-engine to exclude specific engines (e.g., *,-startpage)
|
# Use *,-engine to exclude specific engines (e.g., *,-startpage)
|
||||||
# Available engines: ddg, startpage, yahoo, mojeek
|
# Available engines: ddg, startpage, yahoo, mojeek
|
||||||
engines="*"
|
engines="*"
|
||||||
|
|
||||||
|
[rate_limit]
|
||||||
|
# Rate limit searches per interval
|
||||||
|
|
||||||
|
# /search
|
||||||
|
#search_requests = 10
|
||||||
|
#search_interval = 60
|
||||||
|
|
||||||
|
# /images
|
||||||
|
#images_requests = 20
|
||||||
|
#images_interval = 60
|
||||||
|
|
|
||||||
10
src/Config.c
10
src/Config.c
|
|
@ -100,6 +100,16 @@ int load_config(const char *filename, Config *config) {
|
||||||
strncpy(config->engines, value, sizeof(config->engines) - 1);
|
strncpy(config->engines, value, sizeof(config->engines) - 1);
|
||||||
config->engines[sizeof(config->engines) - 1] = '\0';
|
config->engines[sizeof(config->engines) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(section, "rate_limit") == 0) {
|
||||||
|
if (strcmp(key, "search_requests") == 0) {
|
||||||
|
config->rate_limit_search_requests = atoi(value);
|
||||||
|
} else if (strcmp(key, "search_interval") == 0) {
|
||||||
|
config->rate_limit_search_interval = atoi(value);
|
||||||
|
} else if (strcmp(key, "images_requests") == 0) {
|
||||||
|
config->rate_limit_images_requests = atoi(value);
|
||||||
|
} else if (strcmp(key, "images_interval") == 0) {
|
||||||
|
config->rate_limit_images_interval = atoi(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ typedef struct {
|
||||||
int cache_ttl_infobox;
|
int cache_ttl_infobox;
|
||||||
int cache_ttl_image;
|
int cache_ttl_image;
|
||||||
char engines[512];
|
char engines[512];
|
||||||
|
int rate_limit_search_requests;
|
||||||
|
int rate_limit_search_interval;
|
||||||
|
int rate_limit_images_requests;
|
||||||
|
int rate_limit_images_interval;
|
||||||
} Config;
|
} Config;
|
||||||
|
|
||||||
int load_config(const char *filename, Config *config);
|
int load_config(const char *filename, Config *config);
|
||||||
|
|
|
||||||
193
src/Limiter/RateLimit.c
Normal file
193
src/Limiter/RateLimit.c
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
#include "RateLimit.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <beaker.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
typedef struct RateLimitEntry {
|
||||||
|
char client_key[64];
|
||||||
|
char scope[32];
|
||||||
|
time_t window_start;
|
||||||
|
time_t last_seen;
|
||||||
|
int count;
|
||||||
|
struct RateLimitEntry *next;
|
||||||
|
} RateLimitEntry;
|
||||||
|
|
||||||
|
extern __thread int current_client_socket;
|
||||||
|
extern __thread char current_request_buffer[];
|
||||||
|
|
||||||
|
static pthread_mutex_t rate_limit_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static RateLimitEntry *rate_limit_entries = NULL;
|
||||||
|
|
||||||
|
static int is_blank_char(char c) {
|
||||||
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trim_copy(char *dest, size_t dest_size, const char *src,
|
||||||
|
size_t src_len) {
|
||||||
|
while (src_len > 0 && is_blank_char(*src)) {
|
||||||
|
src++;
|
||||||
|
src_len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src_len > 0 && is_blank_char(src[src_len - 1])) {
|
||||||
|
src_len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dest_size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (src_len >= dest_size)
|
||||||
|
src_len = dest_size - 1;
|
||||||
|
|
||||||
|
memcpy(dest, src, src_len);
|
||||||
|
dest[src_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_client_key(char *client_key, size_t client_key_size) {
|
||||||
|
const char *header = strstr(current_request_buffer, "X-Forwarded-For:");
|
||||||
|
if (!header)
|
||||||
|
return;
|
||||||
|
|
||||||
|
header += strlen("X-Forwarded-For:");
|
||||||
|
const char *line_end = strpbrk(header, "\r\n");
|
||||||
|
size_t line_len = line_end ? (size_t)(line_end - header) : strlen(header);
|
||||||
|
const char *comma = memchr(header, ',', line_len);
|
||||||
|
size_t value_len = comma ? (size_t)(comma - header) : line_len;
|
||||||
|
|
||||||
|
trim_copy(client_key, client_key_size, header, value_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_client_key_from_socket(char *client_key,
|
||||||
|
size_t client_key_size) {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t addr_len = sizeof(addr);
|
||||||
|
|
||||||
|
if (getpeername(current_client_socket, (struct sockaddr *)&addr, &addr_len) !=
|
||||||
|
0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr.ss_family == AF_INET) {
|
||||||
|
struct sockaddr_in *ipv4 = (struct sockaddr_in *)&addr;
|
||||||
|
inet_ntop(AF_INET, &ipv4->sin_addr, client_key, client_key_size);
|
||||||
|
} else if (addr.ss_family == AF_INET6) {
|
||||||
|
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&addr;
|
||||||
|
inet_ntop(AF_INET6, &ipv6->sin6_addr, client_key, client_key_size);
|
||||||
|
} else if (addr.ss_family == AF_UNIX) {
|
||||||
|
snprintf(client_key, client_key_size, "unix:%d", current_client_socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rate_limit_get_client_key(char *client_key, size_t client_key_size) {
|
||||||
|
if (!client_key || client_key_size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
client_key[0] = '\0';
|
||||||
|
get_client_key(client_key, client_key_size);
|
||||||
|
|
||||||
|
if (client_key[0] == '\0') {
|
||||||
|
get_client_key_from_socket(client_key, client_key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_key[0] == '\0') {
|
||||||
|
snprintf(client_key, client_key_size, "nun");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prune_stale_entries(time_t now) {
|
||||||
|
RateLimitEntry **cursor = &rate_limit_entries;
|
||||||
|
|
||||||
|
while (*cursor) {
|
||||||
|
RateLimitEntry *entry = *cursor;
|
||||||
|
if (now - entry->last_seen > 9999) {
|
||||||
|
*cursor = entry->next;
|
||||||
|
free(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cursor = &entry->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RateLimitEntry *find_entry(const char *client_key, const char *scope) {
|
||||||
|
for (RateLimitEntry *entry = rate_limit_entries; entry; entry = entry->next) {
|
||||||
|
if (strcmp(entry->client_key, client_key) == 0 &&
|
||||||
|
strcmp(entry->scope, scope) == 0) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RateLimitEntry *create_entry(const char *client_key, const char *scope,
|
||||||
|
time_t now) {
|
||||||
|
RateLimitEntry *entry = (RateLimitEntry *)calloc(1, sizeof(RateLimitEntry));
|
||||||
|
if (!entry)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
snprintf(entry->client_key, sizeof(entry->client_key), "%s", client_key);
|
||||||
|
snprintf(entry->scope, sizeof(entry->scope), "%s", scope);
|
||||||
|
entry->window_start = now;
|
||||||
|
entry->last_seen = now;
|
||||||
|
entry->next = rate_limit_entries;
|
||||||
|
rate_limit_entries = entry;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimitResult rate_limit_check(const char *scope,
|
||||||
|
const RateLimitConfig *config) {
|
||||||
|
RateLimitResult result = {.limited = 0, .retry_after_seconds = 0};
|
||||||
|
|
||||||
|
if (!scope || !config || config->max_requests <= 0 ||
|
||||||
|
config->interval_seconds <= 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char client_key[64];
|
||||||
|
time_t now = time(NULL);
|
||||||
|
|
||||||
|
rate_limit_get_client_key(client_key, sizeof(client_key));
|
||||||
|
|
||||||
|
pthread_mutex_lock(&rate_limit_mutex);
|
||||||
|
|
||||||
|
prune_stale_entries(now);
|
||||||
|
|
||||||
|
RateLimitEntry *entry = find_entry(client_key, scope);
|
||||||
|
if (!entry) {
|
||||||
|
entry = create_entry(client_key, scope, now);
|
||||||
|
if (!entry) {
|
||||||
|
pthread_mutex_unlock(&rate_limit_mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->last_seen = now;
|
||||||
|
|
||||||
|
if (now - entry->window_start >= config->interval_seconds) {
|
||||||
|
entry->window_start = now;
|
||||||
|
entry->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->count >= config->max_requests) {
|
||||||
|
result.limited = 1;
|
||||||
|
result.retry_after_seconds =
|
||||||
|
config->interval_seconds - (int)(now - entry->window_start);
|
||||||
|
if (result.retry_after_seconds < 1) {
|
||||||
|
result.retry_after_seconds = 1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&rate_limit_mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->count++;
|
||||||
|
pthread_mutex_unlock(&rate_limit_mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
20
src/Limiter/RateLimit.h
Normal file
20
src/Limiter/RateLimit.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef RATE_LIMIT_H
|
||||||
|
#define RATE_LIMIT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int max_requests;
|
||||||
|
int interval_seconds;
|
||||||
|
} RateLimitConfig;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int limited;
|
||||||
|
int retry_after_seconds;
|
||||||
|
} RateLimitResult;
|
||||||
|
|
||||||
|
void rate_limit_get_client_key(char *client_key, size_t client_key_size);
|
||||||
|
RateLimitResult rate_limit_check(const char *scope,
|
||||||
|
const RateLimitConfig *config);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -55,7 +55,11 @@ int main() {
|
||||||
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
|
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
|
||||||
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
|
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
|
||||||
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
|
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
|
||||||
.engines = ""};
|
.engines = "",
|
||||||
|
.rate_limit_search_requests = 0,
|
||||||
|
.rate_limit_search_interval = 0,
|
||||||
|
.rate_limit_images_requests = 0,
|
||||||
|
.rate_limit_images_interval = 0};
|
||||||
|
|
||||||
if (load_config("config.ini", &cfg) != 0) {
|
if (load_config("config.ini", &cfg) != 0) {
|
||||||
fprintf(stderr, "[WARN] Could not load config file, using defaults\n");
|
fprintf(stderr, "[WARN] Could not load config file, using defaults\n");
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
#include "Images.h"
|
#include "Images.h"
|
||||||
|
#include "../Cache/Cache.h"
|
||||||
|
#include "../Limiter/RateLimit.h"
|
||||||
#include "../Scraping/ImageScraping.h"
|
#include "../Scraping/ImageScraping.h"
|
||||||
#include "../Utility/Unescape.h"
|
#include "../Utility/Unescape.h"
|
||||||
#include "../Utility/Utility.h"
|
#include "../Utility/Utility.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
|
static char *build_images_request_cache_key(const char *query, int page,
|
||||||
|
const char *client_key) {
|
||||||
|
char scope_key[BUFFER_SIZE_MEDIUM];
|
||||||
|
snprintf(scope_key, sizeof(scope_key), "images_request:%s",
|
||||||
|
client_key ? client_key : "unknown");
|
||||||
|
return cache_compute_key(query, page, scope_key);
|
||||||
|
}
|
||||||
|
|
||||||
int images_handler(UrlParams *params) {
|
int images_handler(UrlParams *params) {
|
||||||
|
extern Config global_config;
|
||||||
TemplateContext ctx = new_context();
|
TemplateContext ctx = new_context();
|
||||||
char *raw_query = "";
|
char *raw_query = "";
|
||||||
int page = 1;
|
int page = 1;
|
||||||
|
|
@ -52,12 +63,55 @@ int images_handler(UrlParams *params) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char client_key[BUFFER_SIZE_SMALL];
|
||||||
|
rate_limit_get_client_key(client_key, sizeof(client_key));
|
||||||
|
|
||||||
|
char *request_cache_key =
|
||||||
|
build_images_request_cache_key(raw_query, page, client_key);
|
||||||
|
int request_is_cached = 0;
|
||||||
|
|
||||||
|
if (request_cache_key && get_cache_ttl_image() > 0) {
|
||||||
|
char *cached_marker = NULL;
|
||||||
|
size_t cached_marker_size = 0;
|
||||||
|
|
||||||
|
if (cache_get(request_cache_key, (time_t)get_cache_ttl_image(),
|
||||||
|
&cached_marker, &cached_marker_size) == 0) {
|
||||||
|
request_is_cached = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cached_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request_is_cached) {
|
||||||
|
RateLimitConfig rate_limit_config = {
|
||||||
|
.max_requests = global_config.rate_limit_images_requests,
|
||||||
|
.interval_seconds = global_config.rate_limit_images_interval,
|
||||||
|
};
|
||||||
|
RateLimitResult rate_limit_result =
|
||||||
|
rate_limit_check("images", &rate_limit_config);
|
||||||
|
if (rate_limit_result.limited) {
|
||||||
|
char response[256];
|
||||||
|
snprintf(response, sizeof(response),
|
||||||
|
"<h1>Slow down!</h1><p>Too many image searches from you!</p>");
|
||||||
|
send_response(response);
|
||||||
|
free(request_cache_key);
|
||||||
|
free(display_query);
|
||||||
|
free_context(&ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request_cache_key && get_cache_ttl_image() > 0) {
|
||||||
|
cache_set(request_cache_key, "1", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImageResult *results = NULL;
|
ImageResult *results = NULL;
|
||||||
int result_count = 0;
|
int result_count = 0;
|
||||||
|
|
||||||
if (scrape_images(raw_query, page, &results, &result_count) != 0 ||
|
if (scrape_images(raw_query, page, &results, &result_count) != 0 ||
|
||||||
!results) {
|
!results) {
|
||||||
send_response("<h1>Error fetching images</h1>");
|
send_response("<h1>Error fetching images</h1>");
|
||||||
|
free(request_cache_key);
|
||||||
free(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -72,6 +126,7 @@ int images_handler(UrlParams *params) {
|
||||||
if (inner_counts)
|
if (inner_counts)
|
||||||
free(inner_counts);
|
free(inner_counts);
|
||||||
free_image_results(results, result_count);
|
free_image_results(results, result_count);
|
||||||
|
free(request_cache_key);
|
||||||
free(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -106,6 +161,7 @@ int images_handler(UrlParams *params) {
|
||||||
free(inner_counts);
|
free(inner_counts);
|
||||||
|
|
||||||
free_image_results(results, result_count);
|
free_image_results(results, result_count);
|
||||||
|
free(request_cache_key);
|
||||||
free(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#include "Search.h"
|
#include "Search.h"
|
||||||
|
#include "../Cache/Cache.h"
|
||||||
#include "../Infobox/Calculator.h"
|
#include "../Infobox/Calculator.h"
|
||||||
#include "../Infobox/CurrencyConversion.h"
|
#include "../Infobox/CurrencyConversion.h"
|
||||||
#include "../Infobox/Dictionary.h"
|
#include "../Infobox/Dictionary.h"
|
||||||
#include "../Infobox/UnitConversion.h"
|
#include "../Infobox/UnitConversion.h"
|
||||||
#include "../Infobox/Wikipedia.h"
|
#include "../Infobox/Wikipedia.h"
|
||||||
|
#include "../Limiter/RateLimit.h"
|
||||||
#include "../Scraping/Scraping.h"
|
#include "../Scraping/Scraping.h"
|
||||||
#include "../Utility/Display.h"
|
#include "../Utility/Display.h"
|
||||||
#include "../Utility/Unescape.h"
|
#include "../Utility/Unescape.h"
|
||||||
|
|
@ -378,7 +380,17 @@ static char *build_search_href(const char *query, const char *engine_id,
|
||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *build_search_request_cache_key(const char *query,
|
||||||
|
const char *engine_id, int page,
|
||||||
|
const char *client_key) {
|
||||||
|
char scope_key[BUFFER_SIZE_MEDIUM];
|
||||||
|
snprintf(scope_key, sizeof(scope_key), "search_request:%s:%s",
|
||||||
|
engine_id ? engine_id : "all", client_key ? client_key : "unknown");
|
||||||
|
return cache_compute_key(query, page, scope_key);
|
||||||
|
}
|
||||||
|
|
||||||
int results_handler(UrlParams *params) {
|
int results_handler(UrlParams *params) {
|
||||||
|
extern Config global_config;
|
||||||
TemplateContext ctx = new_context();
|
TemplateContext ctx = new_context();
|
||||||
char *raw_query = "";
|
char *raw_query = "";
|
||||||
const char *selected_engine_id = "all";
|
const char *selected_engine_id = "all";
|
||||||
|
|
@ -474,6 +486,47 @@ int results_handler(UrlParams *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char client_key[BUFFER_SIZE_SMALL];
|
||||||
|
rate_limit_get_client_key(client_key, sizeof(client_key));
|
||||||
|
|
||||||
|
char *request_cache_key = build_search_request_cache_key(
|
||||||
|
raw_query, selected_engine_id, page, client_key);
|
||||||
|
int request_is_cached = 0;
|
||||||
|
|
||||||
|
if (request_cache_key && get_cache_ttl_search() > 0) {
|
||||||
|
char *cached_marker = NULL;
|
||||||
|
size_t cached_marker_size = 0;
|
||||||
|
|
||||||
|
if (cache_get(request_cache_key, (time_t)get_cache_ttl_search(),
|
||||||
|
&cached_marker, &cached_marker_size) == 0) {
|
||||||
|
request_is_cached = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cached_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_idx > 0 && !request_is_cached) {
|
||||||
|
RateLimitConfig rate_limit_config = {
|
||||||
|
.max_requests = global_config.rate_limit_search_requests,
|
||||||
|
.interval_seconds = global_config.rate_limit_search_interval,
|
||||||
|
};
|
||||||
|
RateLimitResult rate_limit_result =
|
||||||
|
rate_limit_check("search", &rate_limit_config);
|
||||||
|
if (rate_limit_result.limited) {
|
||||||
|
char response[256];
|
||||||
|
snprintf(response, sizeof(response),
|
||||||
|
"<h1>Slow down!</h1><p>Too many searches from you!</p>");
|
||||||
|
send_response(response);
|
||||||
|
free(request_cache_key);
|
||||||
|
free_context(&ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request_cache_key && get_cache_ttl_search() > 0) {
|
||||||
|
cache_set(request_cache_key, "1", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int filter_engine_count = 0;
|
int filter_engine_count = 0;
|
||||||
for (int i = 0; i < ENGINE_COUNT; i++) {
|
for (int i = 0; i < ENGINE_COUNT; i++) {
|
||||||
if (ENGINE_REGISTRY[i].enabled)
|
if (ENGINE_REGISTRY[i].enabled)
|
||||||
|
|
@ -551,6 +604,7 @@ int results_handler(UrlParams *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(request_cache_key);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
if (redirect_url) {
|
if (redirect_url) {
|
||||||
send_redirect(redirect_url);
|
send_redirect(redirect_url);
|
||||||
|
|
@ -569,6 +623,7 @@ int results_handler(UrlParams *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(request_cache_key);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
send_response("<h1>No results found</h1>");
|
send_response("<h1>No results found</h1>");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -668,6 +723,7 @@ int results_handler(UrlParams *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(request_cache_key);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -817,6 +873,8 @@ int results_handler(UrlParams *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(request_cache_key);
|
||||||
|
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
for (int i = 0; i < HANDLER_COUNT; i++) {
|
for (int i = 0; i < HANDLER_COUNT; i++) {
|
||||||
if (infobox_data[i].success) {
|
if (infobox_data[i].success) {
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,17 @@
|
||||||
<h1>
|
<h1>
|
||||||
Omni<span>Search</span>
|
Omni<span>Search</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
{{if query != ""}}
|
||||||
<form action="/search" method="GET" class="search-form">
|
<form action="/search" method="GET" class="search-form">
|
||||||
<input name="q" type="text" class="search-box" autocomplete="off" placeholder="Search the web..."
|
<input name="q" type="text" class="search-box" autocomplete="off" placeholder="Search the web..."
|
||||||
value="{{query}}">
|
value="{{query}}">
|
||||||
</form>
|
</form>
|
||||||
|
{{endif}}
|
||||||
|
{{if query != ""}}
|
||||||
<a href="/settings?q={{query}}" class="nav-settings-icon active" title="Settings"></a>
|
<a href="/settings?q={{query}}" class="nav-settings-icon active" title="Settings"></a>
|
||||||
|
{{endif}}
|
||||||
</header>
|
</header>
|
||||||
|
{{if query != ""}}
|
||||||
<nav class="nav-tabs">
|
<nav class="nav-tabs">
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<a href="/search?q={{query}}">
|
<a href="/search?q={{query}}">
|
||||||
|
|
@ -40,6 +45,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
{{endif}}
|
||||||
<div class="settings-layout">
|
<div class="settings-layout">
|
||||||
<main class="settings-container">
|
<main class="settings-container">
|
||||||
<form action="/save_settings" method="GET">
|
<form action="/save_settings" method="GET">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue