Compare commits

...
Author SHA1 Message Date
1f9ac58baa
build: fix nix support, update flake 2026-10-08 09:51:28 -07:00
6d087046b8
Revert "fix: remove nix support"
This reverts commit 499bb9b126.
2026-10-08 09:46:17 -07:00
frosty
3fb6f96d13
feat: add /info endpoint for getting information about an instance 2026-10-03 20:01:30 -04:00
frosty
a17f023821
chore: swap out email + domain 2026-09-29 13:44:34 -04:00
frosty
f41fa9e4b3
refactor: clean up config parsing with minor QoL tweaks 2026-09-06 17:19:08 -04:00
frosty
acdff2d3a2
Merge branch 'master' into indev 2026-09-03 23:59:40 -04:00
frosty
892acdc7f2
feat: add HTTP status to warning message 2026-09-03 23:59:20 -04:00
axunaattori
4a705bd1b8
locale: update finnish locale 2026-08-22 14:23:59 -04:00
axunaattori
aace4b8f91
locale: update finnish locale 2026-08-22 14:23:51 -04:00
frosty
e352971bc5
Merge branch 'indev' 2026-08-16 23:45:43 -04:00
frosty
12f046669e
chore: use git tag as version in Makefile 2026-08-16 23:43:55 -04:00
frosty
68d9d46e5e
fix: improve memory handling in search cleanup paths 2026-08-15 02:58:24 -04:00
axunaattori
bc0bc73044
locale: add finnish locale 2026-08-13 23:00:31 -04:00
frosty
b23e5eeb6d
feat: removed other settings, moved default engine to search engines field, tweaked ui 2026-08-13 13:55:57 -04:00
frosty
f1c35fde85
fix: fixed issue with opensearch.xml 2026-08-13 12:10:38 -04:00
frosty
a20a9e8878
feat: add default search engine option 2026-08-12 20:21:12 -04:00
frosty
10d44e7ff5
fix: improvements allocation safety and string processing 2026-08-12 15:40:41 -04:00
frosty
f20d8f7115
refactor: add capacity-aware template string matrices 2026-08-08 13:25:16 -04:00
frosty
d93fead0ae
refactor: centralise matrix ownership and request cleanup 2026-08-08 12:56:26 -04:00
frosty
296021e0ce Merge branch 'indev-yacy' into indev 2026-08-05 18:57:18 -04:00
frosty
11acc93f43 fix: remove nix support 2026-08-04 00:54:53 -04:00
frosty
2419dec7f8 fix: remove nix support 2026-08-04 00:51:28 -04:00
frosty
7b587d82c8 feat: add yacy support 2026-08-03 23:21:41 -04:00
29 changed files with 1283 additions and 599 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
/bin /bin
/obj /obj
config.ini config.ini
/result

View file

@ -12,7 +12,7 @@ RUN apk add --no-cache \
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
RUN git clone https://git.bwaaa.monster/beaker /tmp/beaker && \ RUN git clone https://git.bwaaa.space/beaker /tmp/beaker && \
cd /tmp/beaker && \ cd /tmp/beaker && \
make && make install && \ make && make install && \
rm -rf /tmp/beaker rm -rf /tmp/beaker

View file

@ -7,8 +7,9 @@ GIT_HASH := $(shell git rev-parse --short HEAD)
GIT_DATE := $(shell git log -1 --format='%ad' --date='format:%y.%m.%d') GIT_DATE := $(shell git log -1 --format='%ad' --date='format:%y.%m.%d')
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
GIT_REMOTE := $(shell git remote get-url origin) GIT_REMOTE := $(shell git remote get-url origin)
GIT_TAG := $(shell git describe --tags --exact-match HEAD 2>/dev/null)
VERSION := $(GIT_DATE)+$(GIT_HASH)_$(GIT_BRANCH) VERSION := $(if $(GIT_TAG),$(GIT_TAG),$(GIT_DATE)+$(GIT_HASH)_$(GIT_BRANCH))
CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' -DGIT_REMOTE='"$(GIT_REMOTE)"' CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' -DGIT_REMOTE='"$(GIT_REMOTE)"'

View file

@ -11,7 +11,7 @@ Create a config.ini, there is an example included in the root. Or if you install
## Dependencies ## Dependencies
- libxml2 - libxml2
- libcurl (may be replaced in the future with curl-impersonate) - libcurl (may be replaced in the future with curl-impersonate)
- beaker [(source)](https://git.bwaaa.monster/beaker/) - beaker [(source)](https://git.bwaaa.space/beaker/)
## First Setup ## First Setup
Depending on your system, you may first need to install libcurl and libxml2. Depending on your system, you may first need to install libcurl and libxml2.
@ -47,6 +47,32 @@ On Alpine, `shadow` is needed for the user creation process during the install.
# xbps-install -S libxml2-devel libcurl-devel # xbps-install -S libxml2-devel libcurl-devel
``` ```
### NixOS
Add the flake to your inputs and import the module. That is all you need.
Here's an example of using the modules in a flake:
```nix
# flake.nix
{
inputs = {
omnisearch = {
url = "git+https://git.bwaaa.monster/omnisearch";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, omnisearch, ... }: {
nixosConfigurations.mySystem = nixpkgs.lib.nixosSystem {
modules = [
omnisearch.nixosModules.default
{
services.omnisearch.enable = true;
}
];
};
};
}
```
### macOS (Homebrew) ### macOS (Homebrew)
``` ```
$ brew install libxml2 curl openssl pkg-config $ brew install libxml2 curl openssl pkg-config
@ -61,7 +87,7 @@ On macOS the build uses `pkg-config` to discover `libxml2`, `libcurl` and OpenSS
Install libbeaker: Install libbeaker:
``` ```
$ git clone https://git.bwaaa.monster/beaker $ git clone https://git.bwaaa.space/beaker
$ cd beaker $ cd beaker
$ make $ make
# make install # make install
@ -71,7 +97,7 @@ On macOS `beaker` installs to `/usr/local/` by default and installs `libbeaker.d
And then install omnisearch: And then install omnisearch:
``` ```
$ git clone https://git.bwaaa.monster/omnisearch $ git clone https://git.bwaaa.space/omnisearch
$ cd omnisearch $ cd omnisearch
$ make $ make
# make install-<init> # make install-<init>
@ -89,7 +115,7 @@ You need Docker or Podman and Docker Compose installed on your system.
Run the container: Run the container:
``` ```
$ git clone https://git.bwaaa.monster/omnisearch $ git clone https://git.bwaaa.space/omnisearch
$ cd omnisearch $ cd omnisearch
$ docker compose up -d --build $ docker compose up -d --build
``` ```
@ -113,6 +139,6 @@ $ git merge origin/master
If there are conflicts in the files you modified, resolve them manually. You should also make the changes to the cloned repository, and then run the install command again if you installed omnisearch. Changes made directly to the configuration/assets folder will be overwritten on reinstall. If there are conflicts in the files you modified, resolve them manually. You should also make the changes to the cloned repository, and then run the install command again if you installed omnisearch. Changes made directly to the configuration/assets folder will be overwritten on reinstall.
## Contribution ## Contribution
Generate a patch with ```git format-patch HEAD~1``` and email to [gabriel@bwaaa.monster](mailto:gabriel@bwaaa.monster), beginning the subject line with [PATCH omnisearch] Generate a patch with ```git format-patch HEAD~1``` and email to [frostinator33@airmail.cc](mailto:frostinator33@airmail.cc), beginning the subject line with [PATCH omnisearch]
*If you are sending a revised version of a previous patch, please use [PATCH omnisearch v2, v3, etc].* *If you are sending a revised version of a previous patch, please use [PATCH omnisearch v2, v3, etc].*

View file

@ -31,8 +31,11 @@ port = 8087
[engines] [engines]
# Use * for all engines, or specify comma-separated list (e.g., ddg,yahoo) # Use * for all engines, or specify comma-separated list (e.g., ddg,yahoo)
# 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, yacy
engines="*" engines="*"
# YaCy instance to use for the yacy engine (optional, default: http://127.0.0.1:8090)
# Requires "yacy" to be in the engines list above.
#yacy_instance = http://127.0.0.1:8090
[rate_limit] [rate_limit]
# Rate limit searches per interval # Rate limit searches per interval

43
flake.lock generated Normal file
View file

@ -0,0 +1,43 @@
{
"nodes": {
"beaker-src": {
"flake": false,
"locked": {
"lastModified": 1790704008,
"narHash": "sha256-Al/BE7uVRSmUGF/t1m0pWxF9jNgzdCVUrc5ZHkcPiQg=",
"ref": "refs/heads/master",
"rev": "10be7f581fb828e74f064ea7ad4fb0534ee8540a",
"revCount": 46,
"shallow": false,
"type": "git",
"url": "https://git.bwaaa.space/beaker"
},
"original": {
"shallow": false,
"type": "git",
"url": "https://git.bwaaa.space/beaker"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1791456040,
"narHash": "sha256-gRZ/s8WQJ2CsXdhcDVQH34qxuL0MuMCOs3aBiK1Prsw=",
"rev": "e7439b6b14ad3cc35d05608ebca9bce01a25f5f8",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1087755.e7439b6b14ad/nixexprs.tar.zst?lastModified=1791456040&rev=e7439b6b14ad3cc35d05608ebca9bce01a25f5f8"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst"
}
},
"root": {
"inputs": {
"beaker-src": "beaker-src",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

76
flake.nix Normal file
View file

@ -0,0 +1,76 @@
{
inputs = {
nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst";
beaker-src = {
url = "git+https://git.bwaaa.space/beaker?shallow=0";
flake = false;
};
};
outputs =
{
self,
nixpkgs,
beaker-src,
}:
let
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in
{
formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt);
packages = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
beaker = pkgs.stdenv.mkDerivation {
pname = "beaker";
version = "git";
src = beaker-src;
makeFlags = [
"INSTALL_PREFIX=$(out)/"
"LDCONFIG=true"
];
};
in
{
default = pkgs.stdenv.mkDerivation {
pname = "omnisearch";
version = "git";
src = ./.;
buildInputs = [
pkgs.libxml2.dev
pkgs.curl.dev
pkgs.openssl
beaker
];
preBuild = ''
makeFlagsArray+=(
"PREFIX=$out"
"CFLAGS=-Wall -Wextra -O2 -Isrc -I${pkgs.libxml2.dev}/include/libxml2 -I${beaker}/include/beaker.h"
"LIBS=-lbeaker -lcurl -lxml2 -lpthread -lm -lssl -lcrypto"
)
'';
installPhase = ''
mkdir -p $out/bin $out/share/omnisearch
install -Dm755 bin/omnisearch $out/bin/omnisearch
cp -r templates static locales -t $out/share/omnisearch/
'';
meta = {
description = "Lightweight metasearch engine in C";
platforms = pkgs.lib.platforms.linux;
};
};
}
);
nixosModules.default = import ./module.nix self;
};
}

View file

@ -21,6 +21,8 @@ display_language_label = "Display Language"
language_desc = "Choose your preferred language." language_desc = "Choose your preferred language."
engines_label = "Search Engines" engines_label = "Search Engines"
engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown." engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown."
default_engine_label = "Default Search Engine"
all_engines_option = "All enabled engines"
save_settings_button = "Save Settings" save_settings_button = "Save Settings"
no_results = "No results found" no_results = "No results found"
error_images = "Error fetching images" error_images = "Error fetching images"

View file

@ -21,6 +21,8 @@ display_language_label = "Display Language"
language_desc = "Choose your preferred language." language_desc = "Choose your preferred language."
engines_label = "Search Engines" engines_label = "Search Engines"
engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown." engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown."
default_engine_label = "Default Search Engine"
all_engines_option = "All enabled engines"
save_settings_button = "Save Settings" save_settings_button = "Save Settings"
no_results = "No results found" no_results = "No results found"
error_images = "Error fetching images" error_images = "Error fetching images"

36
locales/fi_fi.ini Normal file
View file

@ -0,0 +1,36 @@
[Meta]
Id = "fi_fi"
Name = "Suomi"
Direction = "ltr"
[Keys]
search_placeholder = "Hae netistä..."
search_button = "Hae"
surprise_me_button = "Kokeilen onneani"
all_tab = "Kaikki"
images_tab = "Kuvat"
settings_tab = "Asetukset"
settings_title = "Asetukset"
theme_label = "Ulkonäkö"
theme_desc = "Valitse haluamasi väriteema."
theme_system = "Järjestelmä"
theme_light = "Vaalea"
theme_dark = "Tumma"
language_label = "Kieli"
display_language_label = "Näyttökieli"
language_desc = "Valitse haluamasi kieli."
engines_label = "Hakukoneet"
engines_desc = "Valitse mitä hakukoneita käytetään. Vain palvelimella käyttöön otetut hakukoneet näytetään."
default_engine_label = "Oletushakukone"
all_engines_option = "Kaikki valitut hakukoneet"
save_settings_button = "Tallenna asetukset"
no_results = "Ei tuloksia"
error_images = "Virhe kuvien haussa"
rate_limit = "Hidasta hieman! Sinulta on tullut liian monta hakupyyntöä!"
warning_fetch_error = "pyyntö epäonnistui ennen kuin OmniSearch ehti lukea hakutuloksia."
warning_parse_mismatch = "palautti hakutulokset muodossa jota OmniSearch ei osaa jäsentää."
warning_blocked = "palautti captchan tai muun estosivun hakutulosten sijaan."
read_more = "Lue Lisää"
view_cached = "Välimuistissa"
view_image = "Kuva"
visit_site = "Sivu"

142
module.nix Normal file
View file

@ -0,0 +1,142 @@
self:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.omnisearch;
pkg = cfg.package;
finalConfigFile =
if cfg.configFile != null then
cfg.configFile
else
pkgs.writeText "omnisearch.ini" ''
[server]
host = ${cfg.settings.server.host}
port = ${toString cfg.settings.server.port}
domain = ${cfg.settings.server.domain}
${lib.optionalString (cfg.settings.server.locale != null) "locale = ${cfg.settings.server.locale}"}
[proxy]
${lib.optionalString (cfg.settings.proxy.proxy != null) "proxy = \"${cfg.settings.proxy.proxy}\""}
${lib.optionalString (
cfg.settings.proxy.list_file != null
) "list_file = ${cfg.settings.proxy.list_file}"}
max_retries = ${toString cfg.settings.proxy.max_retries}
randomize_username = ${lib.boolToString cfg.settings.proxy.randomize_username}
randomize_password = ${lib.boolToString cfg.settings.proxy.randomize_password}
[cache]
dir = ${cfg.settings.cache.dir}
ttl_search = ${toString cfg.settings.cache.ttl_search}
ttl_infobox = ${toString cfg.settings.cache.ttl_infobox}
'';
in
{
options.services.omnisearch = {
enable = lib.mkEnableOption "OmniSearch metasearch engine";
package = lib.mkOption {
type = lib.types.package;
default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
description = "The omnisearch package to use.";
};
configFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to a custom config.ini. Overrides 'settings'.";
};
settings = {
server = {
host = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0";
};
port = lib.mkOption {
type = lib.types.port;
default = 8087;
};
domain = lib.mkOption {
type = lib.types.str;
default = "http://localhost:${toString cfg.settings.server.port}";
};
locale = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
};
proxy = {
proxy = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
list_file = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
max_retries = lib.mkOption {
type = lib.types.int;
default = 3;
};
randomize_username = lib.mkOption {
type = lib.types.bool;
default = true;
};
randomize_password = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
cache = {
dir = lib.mkOption {
type = lib.types.str;
default = "/var/cache/omnisearch";
};
ttl_search = lib.mkOption {
type = lib.types.int;
default = 3600;
};
ttl_infobox = lib.mkOption {
type = lib.types.int;
default = 86400;
};
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.omnisearch = {
description = "OmniSearch Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkg}/bin/omnisearch";
WorkingDirectory = "/var/lib/omnisearch";
StateDirectory = "omnisearch";
CacheDirectory = "omnisearch";
BindReadOnlyPaths = [
"${pkg}/share/omnisearch/templates:/var/lib/omnisearch/templates"
"${pkg}/share/omnisearch/static:/var/lib/omnisearch/static"
"${pkg}/share/omnisearch/locales:/var/lib/omnisearch/locales"
"${finalConfigFile}:/var/lib/omnisearch/config.ini"
];
DynamicUser = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
Restart = "always";
};
};
};
}

View file

@ -1,120 +1,189 @@
#include "Config.h" #include "Config.h"
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h>
typedef enum {
CONFIG_STRING,
CONFIG_INTEGER,
CONFIG_BOOLEAN,
} ConfigValueType;
typedef struct {
const char *section;
const char *key;
ConfigValueType type;
size_t offset;
size_t size;
} ConfigOption;
#define STRING_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_STRING, offsetof(Config, field), \
sizeof(((Config *)0)->field)}
#define INTEGER_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_INTEGER, offsetof(Config, field), 0}
#define BOOLEAN_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_BOOLEAN, offsetof(Config, field), 0}
static const ConfigOption CONFIG_OPTIONS[] = {
STRING_OPTION("server", "host", host),
INTEGER_OPTION("server", "port", port),
STRING_OPTION("server", "locale", default_locale),
STRING_OPTION("proxy", "proxy", proxy),
STRING_OPTION("proxy", "list_file", proxy_list_file),
INTEGER_OPTION("proxy", "max_retries", max_proxy_retries),
BOOLEAN_OPTION("proxy", "randomize_username", randomize_username),
BOOLEAN_OPTION("proxy", "randomize_password", randomize_password),
STRING_OPTION("cache", "dir", cache_dir),
INTEGER_OPTION("cache", "ttl_search", cache_ttl_search),
INTEGER_OPTION("cache", "ttl_infobox", cache_ttl_infobox),
INTEGER_OPTION("cache", "ttl_image", cache_ttl_image),
STRING_OPTION("engines", "engines", engines),
STRING_OPTION("engines", "yacy_instance", yacy_instance),
INTEGER_OPTION("rate_limit", "search_requests", rate_limit_search_requests),
INTEGER_OPTION("rate_limit", "search_interval", rate_limit_search_interval),
INTEGER_OPTION("rate_limit", "images_requests", rate_limit_images_requests),
INTEGER_OPTION("rate_limit", "images_interval", rate_limit_images_interval),
};
void config_set_defaults(Config *config) {
if (!config) {
return;
}
*config = (Config){
.host = DEFAULT_HOST,
.port = DEFAULT_PORT,
.default_locale = "en_gb",
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
.cache_dir = DEFAULT_CACHE_DIR,
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
};
}
static char *trim(char *value) {
while (*value == ' ' || *value == '\t') {
value++;
}
char *end = value + strlen(value);
while (end > value && (end[-1] == ' ' || end[-1] == '\t')) {
end--;
}
*end = '\0';
return value;
}
static char *unquote(char *value) {
size_t length = strlen(value);
if (length >= 2 && (value[0] == '"' || value[0] == '\'') &&
value[length - 1] == value[0]) {
value[length - 1] = '\0';
return value + 1;
}
return value;
}
static int parse_integer(const char *value, int *result) {
char *end;
errno = 0;
long parsed = strtol(value, &end, 10);
if (value == end || *end != '\0' || errno == ERANGE || parsed < INT_MIN ||
parsed > INT_MAX) {
return -1;
}
*result = (int)parsed;
return 0;
}
static int parse_boolean(const char *value, int *result) {
if (strcmp(value, "1") == 0 || strcasecmp(value, "true") == 0 ||
strcasecmp(value, "yes") == 0 || strcasecmp(value, "on") == 0) {
*result = 1;
return 0;
}
if (strcmp(value, "0") == 0 || strcasecmp(value, "false") == 0 ||
strcasecmp(value, "no") == 0 || strcasecmp(value, "off") == 0) {
*result = 0;
return 0;
}
return -1;
}
static void set_option(Config *config, const char *section, const char *key,
const char *value) {
size_t option_count = sizeof(CONFIG_OPTIONS) / sizeof(CONFIG_OPTIONS[0]);
for (size_t i = 0; i < option_count; i++) {
const ConfigOption *option = &CONFIG_OPTIONS[i];
if (strcmp(section, option->section) != 0 ||
strcmp(key, option->key) != 0) {
continue;
}
void *destination = (char *)config + option->offset;
if (option->type == CONFIG_STRING) {
snprintf(destination, option->size, "%s", value);
} else if (option->type == CONFIG_INTEGER) {
parse_integer(value, destination);
} else {
parse_boolean(value, destination);
}
return;
}
}
int load_config(const char *filename, Config *config) { int load_config(const char *filename, Config *config) {
if (!filename || !config) {
return -1;
}
FILE *file = fopen(filename, "r"); FILE *file = fopen(filename, "r");
if (!file) { if (!file) {
return -1; return -1;
} }
char line[512]; char line[1024];
char section[64] = ""; char section[64] = "";
while (fgets(line, sizeof(line), file)) { while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n")] = 0; line[strcspn(line, "\r\n")] = '\0';
char *content = trim(line);
if (line[0] == '\0' || line[0] == '#' || line[0] == ';') { if (*content == '\0' || *content == '#' || *content == ';') {
continue; continue;
} }
if (line[0] == '[') { if (*content == '[') {
char *end = strchr(line, ']'); char *end = strchr(content + 1, ']');
if (end) { if (end) {
*end = '\0'; *end = '\0';
snprintf(section, sizeof(section), "%.*s", (int)(sizeof(section) - 1), snprintf(section, sizeof(section), "%s", trim(content + 1));
line + 1);
section[sizeof(section) - 1] = '\0';
} }
continue; continue;
} }
char *delimiter = strchr(line, '='); char *delimiter = strchr(content, '=');
if (delimiter) { if (!delimiter) {
continue;
}
*delimiter = '\0'; *delimiter = '\0';
char *key = line; char *key = trim(content);
char *value = delimiter + 1; char *value = unquote(trim(delimiter + 1));
set_option(config, section, key, value);
while (*key == ' ' || *key == '\t')
key++;
while (*value == ' ' || *value == '\t')
value++;
char *key_end = key + strlen(key) - 1;
while (key_end > key && (*key_end == ' ' || *key_end == '\t')) {
*key_end = '\0';
key_end--;
}
char *value_end = value + strlen(value) - 1;
while (value_end > value && (*value_end == ' ' || *value_end == '\t' ||
*value_end == '"' || *value_end == '\'')) {
*value_end = '\0';
value_end--;
}
while (*value == ' ' || *value == '\t')
value++;
while (*value == '"' || *value == '\'')
value++;
if (strcmp(section, "server") == 0) {
if (strcmp(key, "host") == 0) {
strncpy(config->host, value, sizeof(config->host) - 1);
config->host[sizeof(config->host) - 1] = '\0';
} else if (strcmp(key, "port") == 0) {
config->port = atoi(value);
} else if (strcmp(key, "locale") == 0) {
strncpy(config->default_locale, value,
sizeof(config->default_locale) - 1);
config->default_locale[sizeof(config->default_locale) - 1] = '\0';
}
} else if (strcmp(section, "proxy") == 0) {
if (strcmp(key, "proxy") == 0) {
strncpy(config->proxy, value, sizeof(config->proxy) - 1);
config->proxy[sizeof(config->proxy) - 1] = '\0';
} else if (strcmp(key, "list_file") == 0) {
strncpy(config->proxy_list_file, value,
sizeof(config->proxy_list_file) - 1);
config->proxy_list_file[sizeof(config->proxy_list_file) - 1] = '\0';
} else if (strcmp(key, "max_retries") == 0) {
config->max_proxy_retries = atoi(value);
} else if (strcmp(key, "randomize_username") == 0) {
config->randomize_username = atoi(value);
} else if (strcmp(key, "randomize_password") == 0) {
config->randomize_password = atoi(value);
}
} else if (strcmp(section, "cache") == 0) {
if (strcmp(key, "dir") == 0) {
strncpy(config->cache_dir, value, sizeof(config->cache_dir) - 1);
config->cache_dir[sizeof(config->cache_dir) - 1] = '\0';
} else if (strcmp(key, "ttl_search") == 0) {
config->cache_ttl_search = atoi(value);
} else if (strcmp(key, "ttl_infobox") == 0) {
config->cache_ttl_infobox = atoi(value);
} else if (strcmp(key, "ttl_image") == 0) {
config->cache_ttl_image = atoi(value);
}
} else if (strcmp(section, "engines") == 0) {
if (strcmp(key, "engines") == 0) {
strncpy(config->engines, value, sizeof(config->engines) - 1);
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);
}
}
}
} }
int result = ferror(file) ? -1 : 0;
fclose(file); fclose(file);
return 0; return result;
} }

View file

@ -8,6 +8,7 @@
#define DEFAULT_CACHE_TTL_INFOBOX 86400 #define DEFAULT_CACHE_TTL_INFOBOX 86400
#define DEFAULT_CACHE_TTL_IMAGE 604800 #define DEFAULT_CACHE_TTL_IMAGE 604800
#define DEFAULT_MAX_PROXY_RETRIES 3 #define DEFAULT_MAX_PROXY_RETRIES 3
#define DEFAULT_YACY_INSTANCE "http://127.0.0.1:8090"
#define BUFFER_SIZE_SMALL 256 #define BUFFER_SIZE_SMALL 256
#define BUFFER_SIZE_MEDIUM 512 #define BUFFER_SIZE_MEDIUM 512
@ -46,12 +47,14 @@ typedef struct {
int cache_ttl_infobox; int cache_ttl_infobox;
int cache_ttl_image; int cache_ttl_image;
char engines[512]; char engines[512];
char yacy_instance[512];
int rate_limit_search_requests; int rate_limit_search_requests;
int rate_limit_search_interval; int rate_limit_search_interval;
int rate_limit_images_requests; int rate_limit_images_requests;
int rate_limit_images_interval; int rate_limit_images_interval;
} Config; } Config;
void config_set_defaults(Config *config);
int load_config(const char *filename, Config *config); int load_config(const char *filename, Config *config);
#endif #endif

View file

@ -12,6 +12,7 @@
#include "Routes/Home.h" #include "Routes/Home.h"
#include "Routes/ImageProxy.h" #include "Routes/ImageProxy.h"
#include "Routes/Images.h" #include "Routes/Images.h"
#include "Routes/Info.h"
#include "Routes/Search.h" #include "Routes/Search.h"
#include "Routes/Settings.h" #include "Routes/Settings.h"
#include "Routes/SettingsSave.h" #include "Routes/SettingsSave.h"
@ -65,23 +66,8 @@ int main() {
curl_global_init(CURL_GLOBAL_DEFAULT); curl_global_init(CURL_GLOBAL_DEFAULT);
Config cfg = {.host = DEFAULT_HOST, Config cfg;
.port = DEFAULT_PORT, config_set_defaults(&cfg);
.default_locale = "en_gb",
.proxy = "",
.proxy_list_file = "",
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
.randomize_username = 0,
.randomize_password = 0,
.cache_dir = DEFAULT_CACHE_DIR,
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
.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");
@ -101,6 +87,23 @@ int main() {
} }
apply_engines_config(cfg.engines); apply_engines_config(cfg.engines);
configure_yacy_engine(cfg.yacy_instance);
if (cfg.yacy_instance[0] != '\0') {
int yacy_enabled = 0;
for (int i = 0; i < ENGINE_COUNT; i++) {
if (strcmp(ENGINE_REGISTRY[i].id, "yacy") == 0) {
yacy_enabled = ENGINE_REGISTRY[i].enabled;
break;
}
}
if (!yacy_enabled) {
fprintf(
stderr,
"[INFO] YaCy instance configured but the yacy engine is not "
"enabled (add it to the engines list, e.g. engines=\"*,yacy\")\n");
}
}
if (cache_init(cfg.cache_dir) != 0) { if (cache_init(cfg.cache_dir) != 0) {
fprintf(stderr, fprintf(stderr,
@ -136,6 +139,7 @@ int main() {
set_handler("/search", results_handler); set_handler("/search", results_handler);
set_handler("/images", images_handler); set_handler("/images", images_handler);
set_handler("/proxy", image_proxy_handler); set_handler("/proxy", image_proxy_handler);
set_handler("/info", info_handler);
set_handler("/settings", settings_handler); set_handler("/settings", settings_handler);
set_handler("/save_settings", settings_save_handler); set_handler("/save_settings", settings_save_handler);

View file

@ -76,14 +76,12 @@ int images_handler(UrlParams *params) {
if (!error_images_msg) if (!error_images_msg)
error_images_msg = "Error fetching images"; error_images_msg = "Error fetching images";
char ***pager_matrix = NULL; StringMatrix pagination;
int *pager_inner_counts = NULL; string_matrix_build_pagination(page, images_href_builder, (void *)raw_query,
int pager_count = &pagination);
build_pagination(page, images_href_builder, (void *)raw_query, if (pagination.count > 0) {
&pager_matrix, &pager_inner_counts); context_set_array_of_arrays(&ctx, "pagination_links", pagination.rows,
if (pager_count > 0) { pagination.count, pagination.field_counts);
context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix,
pager_count, pager_inner_counts);
} }
char *display_query = url_decode_query(raw_query); char *display_query = url_decode_query(raw_query);
@ -91,8 +89,9 @@ int images_handler(UrlParams *params) {
if (!raw_query || strlen(raw_query) == 0) { if (!raw_query || strlen(raw_query) == 0) {
send_redirect("/"); send_redirect("/");
if (display_query) string_matrix_free(&pagination);
free(display_query); free(display_query);
free(locale);
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
@ -128,7 +127,9 @@ int images_handler(UrlParams *params) {
snprintf(response, sizeof(response), "<h1>%s</h1>", rate_limit_msg); snprintf(response, sizeof(response), "<h1>%s</h1>", rate_limit_msg);
send_response(response); send_response(response);
free(request_cache_key); free(request_cache_key);
string_matrix_free(&pagination);
free(display_query); free(display_query);
free(locale);
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
@ -147,37 +148,37 @@ int images_handler(UrlParams *params) {
snprintf(error_html, sizeof(error_html), "<h1>%s</h1>", error_images_msg); snprintf(error_html, sizeof(error_html), "<h1>%s</h1>", error_images_msg);
send_response(error_html); send_response(error_html);
free(request_cache_key); free(request_cache_key);
string_matrix_free(&pagination);
free(display_query); free(display_query);
free(locale);
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
char ***image_matrix = malloc(sizeof(char **) * result_count); StringMatrix images;
int *inner_counts = malloc(sizeof(int) * result_count); string_matrix_init(&images);
if (!image_matrix || !inner_counts) { for (int i = 0; i < result_count; i++) {
if (image_matrix) const char *values[IMAGE_RESULT_FIELDS] = {
free(image_matrix); results[i].thumbnail_url,
if (inner_counts) results[i].title,
free(inner_counts); results[i].page_url,
results[i].full_url,
};
if (string_matrix_append(&images, values, IMAGE_RESULT_FIELDS) != 0) {
string_matrix_free(&images);
string_matrix_free(&pagination);
free_image_results(results, result_count); free_image_results(results, result_count);
free(request_cache_key); free(request_cache_key);
free(display_query); free(display_query);
free(locale);
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
for (int i = 0; i < result_count; i++) {
image_matrix[i] = malloc(sizeof(char *) * IMAGE_RESULT_FIELDS);
image_matrix[i][0] = strdup(results[i].thumbnail_url);
image_matrix[i][1] = strdup(results[i].title);
image_matrix[i][2] = strdup(results[i].page_url);
image_matrix[i][3] = strdup(results[i].full_url);
inner_counts[i] = IMAGE_RESULT_FIELDS;
} }
context_set_array_of_arrays(&ctx, "images", image_matrix, result_count, context_set_array_of_arrays(&ctx, "images", images.rows, images.count,
inner_counts); images.field_counts);
char *rendered = render_template("images.html", &ctx); char *rendered = render_template("images.html", &ctx);
if (rendered) { if (rendered) {
@ -187,27 +188,13 @@ int images_handler(UrlParams *params) {
send_response("<h1>Error rendering image results</h1>"); send_response("<h1>Error rendering image results</h1>");
} }
for (int i = 0; i < result_count; i++) { string_matrix_free(&images);
for (int j = 0; j < IMAGE_RESULT_FIELDS; j++) string_matrix_free(&pagination);
free(image_matrix[i][j]);
free(image_matrix[i]);
}
free(image_matrix);
free(inner_counts);
if (pager_count > 0) {
for (int i = 0; i < pager_count; i++) {
for (int j = 0; j < LINK_FIELD_COUNT; j++)
free(pager_matrix[i][j]);
free(pager_matrix[i]);
}
free(pager_matrix);
free(pager_inner_counts);
}
free_image_results(results, result_count); free_image_results(results, result_count);
free(request_cache_key); free(request_cache_key);
free(display_query); free(display_query);
free(locale);
free_context(&ctx); free_context(&ctx);
return 0; return 0;

69
src/Routes/Info.c Normal file
View file

@ -0,0 +1,69 @@
#include "Info.h"
#include "../Config.h"
#include "../Scraping/Scraping.h"
#include "../Utility/Utility.h"
#include <beaker.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
static int append_json(char *buf, size_t size, size_t *used, const char *format,
...) {
if (*used >= size) {
return -1;
}
va_list args;
va_start(args, format);
int written = vsnprintf(buf + *used, size - *used, format, args);
va_end(args);
if (written < 0 || (size_t)written >= size - *used) {
return -1;
}
*used += (size_t)written;
return 0;
}
int info_handler(UrlParams *params) {
(void)params;
extern Config global_config;
char json[INITIAL_BUFFER_SIZE];
size_t used = 0;
if (append_json(json, sizeof(json), &used, "{\"version\":\"%s\",", VERSION) !=
0 ||
append_json(json, sizeof(json), &used, "\"git_remote\":\"%s\",",
GIT_REMOTE) != 0 ||
append_json(json, sizeof(json), &used, "\"locale\":\"%s\",",
global_config.default_locale) != 0 ||
append_json(json, sizeof(json), &used, "\"engines\":[") != 0) {
send_response("Failed to build instance info");
return -1;
}
int first = 1;
for (int i = 0; i < ENGINE_COUNT; i++) {
if (!ENGINE_REGISTRY[i].enabled) {
continue;
}
if (append_json(json, sizeof(json), &used,
"%s{\"id\":\"%s\",\"name\":\"%s\"}", first ? "" : ",",
ENGINE_REGISTRY[i].id, ENGINE_REGISTRY[i].name) != 0) {
send_response("Failed to build instance info");
return -1;
}
first = 0;
}
if (append_json(json, sizeof(json), &used, "]}") != 0) {
send_response("Failed to build instance info");
return -1;
}
serve_data(json, used, "application/json");
return 0;
}

8
src/Routes/Info.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef INFO_H
#define INFO_H
#include <beaker.h>
int info_handler(UrlParams *params);
#endif

View file

@ -140,7 +140,8 @@ static InfoBox fetch_currency_wrapper(char *query) {
return fetch_currency_data(query); return fetch_currency_data(query);
} }
char *get_base_url(const char *input) { char *get_base_url(const char *input) {
if (!input) return NULL; if (!input)
return NULL;
const char *start = input; const char *start = input;
@ -157,7 +158,8 @@ char *get_base_url(const char *input) {
size_t len = end - start; size_t len = end - start;
char *domain = (char *)malloc(len + 1); char *domain = (char *)malloc(len + 1);
if (!domain) return NULL; if (!domain)
return NULL;
strncpy(domain, start, len); strncpy(domain, start, len);
domain[len] = '\0'; domain[len] = '\0';
@ -249,88 +251,65 @@ static void *infobox_thread_func(void *arg) {
return NULL; return NULL;
} }
static int add_infobox_to_collection(InfoBox *infobox, char ****collection, static int add_infobox_to_collection(InfoBox *infobox,
int **inner_counts, int current_count) { StringMatrix *collection) {
*collection = const char *values[INFOBOX_FIELD_COUNT] = {
(char ***)realloc(*collection, sizeof(char **) * (current_count + 1)); infobox->title, infobox->thumbnail_url, infobox->extract,
*inner_counts = infobox->url, infobox->url,
(int *)realloc(*inner_counts, sizeof(int) * (current_count + 1)); };
return string_matrix_append(collection, values, INFOBOX_FIELD_COUNT);
(*collection)[current_count] =
(char **)malloc(sizeof(char *) * INFOBOX_FIELD_COUNT);
(*collection)[current_count][0] =
infobox->title ? strdup(infobox->title) : NULL;
(*collection)[current_count][1] =
infobox->thumbnail_url ? strdup(infobox->thumbnail_url) : NULL;
(*collection)[current_count][2] =
infobox->extract ? strdup(infobox->extract) : NULL;
(*collection)[current_count][3] = infobox->url ? strdup(infobox->url) : NULL;
(*collection)[current_count][4] = infobox->url ? strdup(infobox->url) : NULL;
(*inner_counts)[current_count] = INFOBOX_FIELD_COUNT;
return current_count + 1;
} }
static int add_warning_to_collection(const char *engine_name, static int add_warning_to_collection(const char *engine_name,
const char *warning_message, const char *warning_message,
char ****collection, int **inner_counts, StringMatrix *collection) {
int current_count) { const char *values[] = {engine_name, warning_message};
char ***new_collection = return string_matrix_append(collection, values, 2);
(char ***)malloc(sizeof(char **) * (current_count + 1));
int *new_inner_counts =
(int *)malloc(sizeof(int) * (current_count + 1));
if (!new_collection || !new_inner_counts) {
free(new_collection);
free(new_inner_counts);
return current_count;
} }
if (*collection && current_count > 0) { static void free_user_engine_list(char **user_engines, int user_engine_count) {
memcpy(new_collection, *collection, sizeof(char **) * current_count); for (int i = 0; i < user_engine_count; i++)
} free(user_engines[i]);
if (*inner_counts && current_count > 0) { free(user_engines);
memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count);
} }
free(*collection); static void free_search_results(SearchResult **all_results, ScrapeJob *jobs,
free(*inner_counts); int job_count) {
for (int i = 0; i < job_count; i++) {
*collection = new_collection; for (int j = 0; all_results[i] && j < jobs[i].results_count; j++) {
*inner_counts = new_inner_counts; free(all_results[i][j].url);
free(all_results[i][j].title);
(*collection)[current_count] = (char **)malloc(sizeof(char *) * 2); free(all_results[i][j].snippet);
if (!(*collection)[current_count]) }
return current_count; free(all_results[i]);
}
(*collection)[current_count][0] = strdup(engine_name ? engine_name : "");
(*collection)[current_count][1] =
strdup(warning_message ? warning_message : "");
if (!(*collection)[current_count][0] || !(*collection)[current_count][1]) {
free((*collection)[current_count][0]);
free((*collection)[current_count][1]);
free((*collection)[current_count]);
return current_count;
} }
(*inner_counts)[current_count] = 2; static void free_infobox_results(InfoBoxThreadData *data) {
return current_count + 1; for (int i = 0; i < HANDLER_COUNT; i++) {
free_infobox(&data[i].result);
}
} }
static const char *warning_message_for_job(const ScrapeJob *job, const char *locale) { static const char *warning_message_for_job(const ScrapeJob *job,
const char *locale) {
switch (job->status) { switch (job->status) {
case SCRAPE_STATUS_FETCH_ERROR: { case SCRAPE_STATUS_FETCH_ERROR: {
const char *msg = beaker_get_locale_value(locale, "warning_fetch_error"); const char *msg = beaker_get_locale_value(locale, "warning_fetch_error");
return msg ? msg : "request failed before OmniSearch could read search results."; return msg ? msg
: "request failed before OmniSearch could read search results.";
} }
case SCRAPE_STATUS_PARSE_MISMATCH: { case SCRAPE_STATUS_PARSE_MISMATCH: {
const char *msg = beaker_get_locale_value(locale, "warning_parse_mismatch"); const char *msg = beaker_get_locale_value(locale, "warning_parse_mismatch");
return msg ? msg : "returned search results in a format OmniSearch could not parse."; return msg ? msg
: "returned search results in a format OmniSearch could not "
"parse.";
} }
case SCRAPE_STATUS_BLOCKED: { case SCRAPE_STATUS_BLOCKED: {
const char *msg = beaker_get_locale_value(locale, "warning_blocked"); const char *msg = beaker_get_locale_value(locale, "warning_blocked");
return msg ? msg : "returned a captcha or another blocking page instead of search results."; return msg ? msg
: "returned a captcha or another blocking page instead of "
"search results.";
} }
default: default:
return NULL; return NULL;
@ -384,8 +363,8 @@ static int engine_allowed_for_user(const SearchEngine *eng, char **user_ids,
static char *build_search_href(const char *query, const char *engine_id, static char *build_search_href(const char *query, const char *engine_id,
int page) { int page) {
const char *safe_query = query ? query : ""; const char *safe_query = query ? query : "";
int use_engine = engine_id && engine_id[0] != '\0' && int use_engine =
!engine_id_matches(engine_id, "all"); engine_id && engine_id[0] != '\0' && !engine_id_matches(engine_id, "all");
size_t needed = strlen("/search?q=") + strlen(safe_query) + 1; size_t needed = strlen("/search?q=") + strlen(safe_query) + 1;
if (use_engine) if (use_engine)
@ -437,13 +416,18 @@ int results_handler(UrlParams *params) {
extern Config global_config; extern Config global_config;
TemplateContext ctx = new_context(); TemplateContext ctx = new_context();
char *raw_query = ""; char *raw_query = "";
const char *selected_engine_id = "all"; char selected_engine_id[32];
char *default_engine = get_default_search_engine();
snprintf(selected_engine_id, sizeof(selected_engine_id), "%s",
default_engine);
free(default_engine);
int page = 1; int page = 1;
int btnI = 0; int btnI = 0;
char **user_engines = NULL; char **user_engines = NULL;
int user_engine_count = 0; int user_engine_count = 0;
int has_user_pref = (get_user_engines(&user_engines, &user_engine_count) == 0); int has_user_pref =
(get_user_engines(&user_engines, &user_engine_count) == 0);
if (params) { if (params) {
for (int i = 0; i < params->count; i++) { for (int i = 0; i < params->count; i++) {
@ -454,7 +438,8 @@ int results_handler(UrlParams *params) {
if (parsed > 1) if (parsed > 1)
page = parsed; page = parsed;
} else if (strcmp(params->params[i].key, "engine") == 0) { } else if (strcmp(params->params[i].key, "engine") == 0) {
selected_engine_id = params->params[i].value; snprintf(selected_engine_id, sizeof(selected_engine_id), "%s",
params->params[i].value);
} else if (strcmp(params->params[i].key, "btnI") == 0) { } else if (strcmp(params->params[i].key, "btnI") == 0) {
btnI = atoi(params->params[i].value); btnI = atoi(params->params[i].value);
} }
@ -471,9 +456,11 @@ int results_handler(UrlParams *params) {
beaker_set_locale(&ctx, locale); beaker_set_locale(&ctx, locale);
const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit");
if (!rate_limit_msg) rate_limit_msg = "Slow down! Too many searches from you!"; if (!rate_limit_msg)
rate_limit_msg = "Slow down! Too many searches from you!";
const char *no_results_msg = beaker_get_locale_value(locale, "no_results"); const char *no_results_msg = beaker_get_locale_value(locale, "no_results");
if (!no_results_msg) no_results_msg = "No results found"; if (!no_results_msg)
no_results_msg = "No results found";
char page_str[16]; char page_str[16];
snprintf(page_str, sizeof(page_str), "%d", page); snprintf(page_str, sizeof(page_str), "%d", page);
@ -491,18 +478,24 @@ int results_handler(UrlParams *params) {
if (!raw_query || strlen(raw_query) == 0) { if (!raw_query || strlen(raw_query) == 0) {
send_redirect("/"); send_redirect("/");
if (has_user_pref) { free(locale);
for (int i = 0; i < user_engine_count; i++) free_user_engine_list(user_engines, user_engine_count);
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
const SearchEngine *selected_engine = find_enabled_engine(selected_engine_id); const SearchEngine *selected_engine = find_enabled_engine(selected_engine_id);
if (!selected_engine) if (selected_engine &&
selected_engine_id = "all"; !engine_allowed_for_user(selected_engine, user_engines, user_engine_count,
has_user_pref)) {
selected_engine = NULL;
}
if (selected_engine) {
snprintf(selected_engine_id, sizeof(selected_engine_id), "%s",
selected_engine->id);
} else {
snprintf(selected_engine_id, sizeof(selected_engine_id), "all");
}
context_set(&ctx, "selected_engine", selected_engine_id); context_set(&ctx, "selected_engine", selected_engine_id);
char *search_href = build_search_href(raw_query, selected_engine_id, 1); char *search_href = build_search_href(raw_query, selected_engine_id, 1);
@ -528,13 +521,6 @@ int results_handler(UrlParams *params) {
infobox_data[i].result = (InfoBox){NULL}; infobox_data[i].result = (InfoBox){NULL};
} }
if (page == 1) {
for (int i = 0; i < HANDLER_COUNT; i++) {
pthread_create(&infobox_threads[i], NULL, infobox_thread_func,
&infobox_data[i]);
}
}
ScrapeJob jobs[ENGINE_COUNT]; ScrapeJob jobs[ENGINE_COUNT];
SearchResult *all_results[ENGINE_COUNT]; SearchResult *all_results[ENGINE_COUNT];
@ -592,11 +578,8 @@ int results_handler(UrlParams *params) {
snprintf(response, sizeof(response), "<h1>%s</h1>", rate_limit_msg); snprintf(response, sizeof(response), "<h1>%s</h1>", rate_limit_msg);
send_response(response); send_response(response);
free(request_cache_key); free(request_cache_key);
if (has_user_pref) { free(locale);
for (int i = 0; i < user_engine_count; i++) free_user_engine_list(user_engines, user_engine_count);
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx); free_context(&ctx);
return -1; return -1;
} }
@ -615,15 +598,14 @@ int results_handler(UrlParams *params) {
} }
if (filter_engine_count > 1) { if (filter_engine_count > 1) {
char ***filter_matrix = NULL; StringMatrix filter_matrix;
int *filter_inner_counts = NULL; string_matrix_init(&filter_matrix);
int filter_count = 0;
char *all_href = build_search_href(raw_query, "all", 1); char *all_href = build_search_href(raw_query, "all", 1);
const char *all_values[LINK_FIELD_COUNT] = {
filter_count = add_link_to_collection(
all_href, "All", all_href, "All",
selected_engine ? "engine-filter" : "engine-filter active", selected_engine ? "engine-filter" : "engine-filter active"};
&filter_matrix, &filter_inner_counts, filter_count);
string_matrix_append(&filter_matrix, all_values, LINK_FIELD_COUNT);
free(all_href); free(all_href);
for (int i = 0; i < ENGINE_COUNT; i++) { for (int i = 0; i < ENGINE_COUNT; i++) {
@ -639,22 +621,24 @@ int results_handler(UrlParams *params) {
? "engine-filter active" ? "engine-filter active"
: "engine-filter"; : "engine-filter";
filter_count = add_link_to_collection(filter_href, ENGINE_REGISTRY[i].name, const char *filter_values[LINK_FIELD_COUNT] = {
filter_class, &filter_matrix, filter_href, ENGINE_REGISTRY[i].name, filter_class};
&filter_inner_counts, filter_count); string_matrix_append(&filter_matrix, filter_values, LINK_FIELD_COUNT);
free(filter_href); free(filter_href);
} }
if (filter_count > 0) { if (filter_matrix.count > 0) {
context_set_array_of_arrays(&ctx, "engine_filters", filter_matrix, context_set_array_of_arrays(&ctx, "engine_filters", filter_matrix.rows,
filter_count, filter_inner_counts); filter_matrix.count,
for (int i = 0; i < filter_count; i++) { filter_matrix.field_counts);
for (int j = 0; j < LINK_FIELD_COUNT; j++)
free(filter_matrix[i][j]);
free(filter_matrix[i]);
} }
free(filter_matrix); string_matrix_free(&filter_matrix);
free(filter_inner_counts); }
if (page == 1) {
for (int i = 0; i < HANDLER_COUNT; i++) {
pthread_create(&infobox_threads[i], NULL, infobox_thread_func,
&infobox_data[i]);
} }
} }
@ -672,27 +656,12 @@ int results_handler(UrlParams *params) {
for (int i = 0; i < engine_idx; i++) { for (int i = 0; i < engine_idx; i++) {
if (jobs[i].results_count > 0 && all_results[i][0].url) { if (jobs[i].results_count > 0 && all_results[i][0].url) {
char *redirect_url = strdup(all_results[i][0].url); char *redirect_url = strdup(all_results[i][0].url);
for (int j = 0; j < enabled_engine_count; j++) { free_search_results(all_results, jobs, engine_idx);
for (int k = 0; k < jobs[j].results_count; k++) { if (page == 1)
free(all_results[j][k].url); free_infobox_results(infobox_data);
free(all_results[j][k].title);
free(all_results[j][k].snippet);
}
free(all_results[j]);
}
if (page == 1) {
for (int j = 0; j < HANDLER_COUNT; j++) {
if (infobox_data[j].success) {
free_infobox(&infobox_data[j].result);
}
}
}
free(request_cache_key); free(request_cache_key);
if (has_user_pref) { free(locale);
for (int i = 0; i < user_engine_count; i++) free_user_engine_list(user_engines, user_engine_count);
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx); free_context(&ctx);
if (redirect_url) { if (redirect_url) {
send_redirect(redirect_url); send_redirect(redirect_url);
@ -701,54 +670,37 @@ int results_handler(UrlParams *params) {
return 0; return 0;
} }
} }
for (int i = 0; i < enabled_engine_count; i++) { free_search_results(all_results, jobs, engine_idx);
free(all_results[i]); if (page == 1)
} free_infobox_results(infobox_data);
if (page == 1) {
for (int i = 0; i < HANDLER_COUNT; i++) {
if (infobox_data[i].success) {
free_infobox(&infobox_data[i].result);
}
}
}
free(request_cache_key); free(request_cache_key);
if (has_user_pref) { free(locale);
for (int i = 0; i < user_engine_count; i++) free_user_engine_list(user_engines, user_engine_count);
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx); free_context(&ctx);
char no_results_html[128]; char no_results_html[128];
snprintf(no_results_html, sizeof(no_results_html), "<h1>%s</h1>", no_results_msg); snprintf(no_results_html, sizeof(no_results_html), "<h1>%s</h1>",
no_results_msg);
send_response(no_results_html); send_response(no_results_html);
return 0; return 0;
} }
char ***infobox_matrix = NULL; StringMatrix infobox_matrix;
int *infobox_inner_counts = NULL; string_matrix_init(&infobox_matrix);
int infobox_count = 0;
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) {
infobox_count = add_infobox_to_collection(&infobox_data[i].result, &infobox_matrix);
add_infobox_to_collection(&infobox_data[i].result, &infobox_matrix,
&infobox_inner_counts, infobox_count);
} }
} }
} }
if (infobox_count > 0) { if (infobox_matrix.count > 0) {
context_set_array_of_arrays(&ctx, "infoboxes", infobox_matrix, context_set_array_of_arrays(&ctx, "infoboxes", infobox_matrix.rows,
infobox_count, infobox_inner_counts); infobox_matrix.count,
for (int i = 0; i < infobox_count; i++) { infobox_matrix.field_counts);
for (int j = 0; j < INFOBOX_FIELD_COUNT; j++)
free(infobox_matrix[i][j]);
free(infobox_matrix[i]);
}
free(infobox_matrix);
free(infobox_inner_counts);
} }
string_matrix_free(&infobox_matrix);
int warning_count = 0; int warning_count = 0;
for (int i = 0; i < enabled_engine_count; i++) { for (int i = 0; i < enabled_engine_count; i++) {
@ -757,35 +709,33 @@ int results_handler(UrlParams *params) {
} }
if (warning_count > 0) { if (warning_count > 0) {
char ***warning_matrix = NULL; StringMatrix warning_matrix;
int *warning_inner_counts = NULL; string_matrix_init(&warning_matrix);
int warning_index = 0;
for (int i = 0; i < enabled_engine_count; i++) { for (int i = 0; i < enabled_engine_count; i++) {
const char *warning_message = warning_message_for_job(&jobs[i], locale); const char *warning_message = warning_message_for_job(&jobs[i], locale);
if (!warning_message) if (!warning_message)
continue; continue;
warning_index = add_warning_to_collection( if (jobs[i].http_status >= 400) {
jobs[i].engine->name, warning_message, &warning_matrix, char warning_with_status[BUFFER_SIZE_LARGE];
&warning_inner_counts, warning_index); snprintf(warning_with_status, sizeof(warning_with_status),
"%s (HTTP %ld)", warning_message, jobs[i].http_status);
add_warning_to_collection(jobs[i].engine->name, warning_with_status,
&warning_matrix);
} else {
add_warning_to_collection(jobs[i].engine->name, warning_message,
&warning_matrix);
}
} }
if (warning_index > 0) { if (warning_matrix.count > 0) {
context_set_array_of_arrays(&ctx, "engine_warnings", warning_matrix, context_set_array_of_arrays(&ctx, "engine_warnings", warning_matrix.rows,
warning_index, warning_inner_counts); warning_matrix.count,
warning_matrix.field_counts);
} }
if (warning_matrix) { string_matrix_free(&warning_matrix);
for (int i = 0; i < warning_index; i++) {
free(warning_matrix[i][0]);
free(warning_matrix[i][1]);
free(warning_matrix[i]);
}
free(warning_matrix);
}
if (warning_inner_counts)
free(warning_inner_counts);
} }
int total_results = 0; int total_results = 0;
@ -794,33 +744,8 @@ int results_handler(UrlParams *params) {
} }
if (total_results > 0) { if (total_results > 0) {
char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results); StringMatrix results_matrix;
int *results_inner_counts = (int *)malloc(sizeof(int) * total_results); string_matrix_init(&results_matrix);
if (!results_matrix || !results_inner_counts) {
char *html = render_template("results.html", &ctx);
if (html) {
send_response(html);
free(html);
}
for (int i = 0; i < enabled_engine_count; i++)
free(all_results[i]);
if (page == 1) {
for (int i = 0; i < HANDLER_COUNT; i++) {
if (infobox_data[i].success) {
free_infobox(&infobox_data[i].result);
}
}
}
free(request_cache_key);
if (has_user_pref) {
for (int i = 0; i < user_engine_count; i++)
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx);
return 0;
}
int unique_count = 0;
UrlHashTable url_table; UrlHashTable url_table;
url_hash_init(&url_table); url_hash_init(&url_table);
@ -837,61 +762,45 @@ int results_handler(UrlParams *params) {
url_hash_insert(&url_table, display_url); url_hash_insert(&url_table, display_url);
results_matrix[unique_count] =
(char **)malloc(sizeof(char *) * RESULT_FIELD_COUNT);
if (!results_matrix[unique_count]) {
free(all_results[i][j].url);
free(all_results[i][j].title);
free(all_results[i][j].snippet);
continue;
}
char *pretty_url = pretty_display_url(display_url); char *pretty_url = pretty_display_url(display_url);
char *base_url = get_base_url(display_url); char *base_url = get_base_url(display_url);
const char *values[RESULT_FIELD_COUNT] = {
results_matrix[unique_count][0] = strdup(display_url); display_url,
results_matrix[unique_count][1] = strdup(pretty_url); pretty_url,
results_matrix[unique_count][2] = all_results[i][j].title all_results[i][j].title ? all_results[i][j].title : "Untitled",
? strdup(all_results[i][j].title) all_results[i][j].snippet ? all_results[i][j].snippet : "",
: strdup("Untitled"); base_url,
results_matrix[unique_count][3] = "",
all_results[i][j].snippet ? strdup(all_results[i][j].snippet) };
: strdup(""); int append_result =
results_matrix[unique_count][4] = strdup(base_url ? base_url : ""); string_matrix_append(&results_matrix, values, RESULT_FIELD_COUNT);
results_matrix[unique_count][5] = strdup("");
results_inner_counts[unique_count] = RESULT_FIELD_COUNT;
free(pretty_url); free(pretty_url);
free(base_url); free(base_url);
free(all_results[i][j].url); free(all_results[i][j].url);
free(all_results[i][j].title); free(all_results[i][j].title);
free(all_results[i][j].snippet); free(all_results[i][j].snippet);
if (append_result != 0) {
unique_count++; continue;
}
} }
free(all_results[i]); free(all_results[i]);
} }
context_set_array_of_arrays(&ctx, "results", results_matrix, unique_count, context_set_array_of_arrays(&ctx, "results", results_matrix.rows,
results_inner_counts); results_matrix.count,
results_matrix.field_counts);
char ***pager_matrix = NULL; StringMatrix pager_matrix;
int *pager_inner_counts = NULL; SearchHrefData href_data = {.query = raw_query,
SearchHrefData href_data = { .query = raw_query, .engine_id = selected_engine_id }; .engine_id = selected_engine_id};
int pager_count = build_pagination(page, search_href_builder, int pagination_result = string_matrix_build_pagination(
&href_data, &pager_matrix, page, search_href_builder, &href_data, &pager_matrix);
&pager_inner_counts);
if (pager_count > 0) { if (pagination_result == 0 && pager_matrix.count > 0) {
context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix, context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix.rows,
pager_count, pager_inner_counts); pager_matrix.count,
for (int i = 0; i < pager_count; i++) { pager_matrix.field_counts);
for (int j = 0; j < LINK_FIELD_COUNT; j++)
free(pager_matrix[i][j]);
free(pager_matrix[i]);
}
free(pager_matrix);
free(pager_inner_counts);
} }
char *html = render_template("results.html", &ctx); char *html = render_template("results.html", &ctx);
@ -900,13 +809,8 @@ int results_handler(UrlParams *params) {
free(html); free(html);
} }
for (int i = 0; i < unique_count; i++) { string_matrix_free(&pager_matrix);
for (int j = 0; j < RESULT_FIELD_COUNT; j++) string_matrix_free(&results_matrix);
free(results_matrix[i][j]);
free(results_matrix[i]);
}
free(results_matrix);
free(results_inner_counts);
url_hash_free(&url_table); url_hash_free(&url_table);
} else { } else {
char *html = render_template("results.html", &ctx); char *html = render_template("results.html", &ctx);
@ -915,26 +819,15 @@ int results_handler(UrlParams *params) {
free(html); free(html);
} }
for (int i = 0; i < enabled_engine_count; i++) { free_search_results(all_results, jobs, engine_idx);
free(all_results[i]);
}
} }
free(request_cache_key); free(request_cache_key);
if (page == 1) { if (page == 1)
for (int i = 0; i < HANDLER_COUNT; i++) { free_infobox_results(infobox_data);
if (infobox_data[i].success) {
free_infobox(&infobox_data[i].result);
}
}
}
free(locale); free(locale);
if (has_user_pref) { free_user_engine_list(user_engines, user_engine_count);
for (int i = 0; i < user_engine_count; i++)
free(user_engines[i]);
free(user_engines);
}
free_context(&ctx); free_context(&ctx);
return 0; return 0;

View file

@ -34,6 +34,14 @@ int settings_handler(UrlParams *params) {
int user_engine_count = 0; int user_engine_count = 0;
int has_user_pref = int has_user_pref =
(get_user_engines(&user_engines, &user_engine_count) == 0); (get_user_engines(&user_engines, &user_engine_count) == 0);
char *default_engine = get_default_search_engine();
if (strcmp(default_engine, "all") != 0 && has_user_pref &&
!user_engines_contains(default_engine, user_engines,
user_engine_count)) {
free(default_engine);
default_engine = strdup("all");
}
int enabled_count = 0; int enabled_count = 0;
for (int i = 0; i < ENGINE_COUNT; i++) { for (int i = 0; i < ENGINE_COUNT; i++) {
@ -63,11 +71,13 @@ int settings_handler(UrlParams *params) {
} }
} }
engine_data[idx] = malloc(sizeof(char *) * 3); engine_data[idx] = malloc(sizeof(char *) * 4);
engine_data[idx][0] = (char *)ENGINE_REGISTRY[i].id; engine_data[idx][0] = (char *)ENGINE_REGISTRY[i].id;
engine_data[idx][1] = (char *)ENGINE_REGISTRY[i].name; engine_data[idx][1] = (char *)ENGINE_REGISTRY[i].name;
engine_data[idx][2] = is_selected ? "checked" : ""; engine_data[idx][2] = is_selected ? "checked" : "";
engine_inner[idx] = 3; engine_data[idx][3] =
strcmp(default_engine, ENGINE_REGISTRY[i].id) == 0 ? "selected" : "";
engine_inner[idx] = 4;
idx++; idx++;
} }
@ -78,6 +88,7 @@ int settings_handler(UrlParams *params) {
context_set(&ctx, "query", query); context_set(&ctx, "query", query);
context_set(&ctx, "theme", theme); context_set(&ctx, "theme", theme);
context_set(&ctx, "locale", locale); context_set(&ctx, "locale", locale);
context_set(&ctx, "default_engine", default_engine);
context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count, context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count,
inner_counts); inner_counts);
@ -132,6 +143,7 @@ int settings_handler(UrlParams *params) {
send_response(rendered_html); send_response(rendered_html);
free(rendered_html); free(rendered_html);
free(default_engine);
free(theme); free(theme);
free(locale); free(locale);
free_context(&ctx); free_context(&ctx);

View file

@ -10,6 +10,8 @@ int settings_save_handler(UrlParams *params) {
const char *theme = ""; const char *theme = "";
const char *locale = ""; const char *locale = "";
const char *query = ""; const char *query = "";
const char *default_engine = "all";
int default_engine_present = 0;
int engines_present = 0; int engines_present = 0;
char selected_ids[ENGINE_COUNT][32]; char selected_ids[ENGINE_COUNT][32];
int selected_count = 0; int selected_count = 0;
@ -22,6 +24,9 @@ int settings_save_handler(UrlParams *params) {
locale = params->params[i].value; locale = params->params[i].value;
} else if (strcmp(params->params[i].key, "q") == 0) { } else if (strcmp(params->params[i].key, "q") == 0) {
query = params->params[i].value; query = params->params[i].value;
} else if (strcmp(params->params[i].key, "default_engine") == 0) {
default_engine = params->params[i].value;
default_engine_present = 1;
} else if (strcmp(params->params[i].key, "engines_present") == 0) { } else if (strcmp(params->params[i].key, "engines_present") == 0) {
engines_present = 1; engines_present = 1;
} else if (strncmp(params->params[i].key, "engine_", 7) == 0 && } else if (strncmp(params->params[i].key, "engine_", 7) == 0 &&
@ -58,6 +63,31 @@ int settings_save_handler(UrlParams *params) {
false, false); false, false);
} }
if (default_engine_present) {
const char *validated_default = "all";
for (int i = 0; i < ENGINE_COUNT; i++) {
if (!ENGINE_REGISTRY[i].enabled ||
strcmp(default_engine, ENGINE_REGISTRY[i].id) != 0)
continue;
int selected = !engines_present;
for (int j = 0; j < selected_count; j++) {
if (strcmp(selected_ids[j], ENGINE_REGISTRY[i].id) == 0) {
selected = 1;
break;
}
}
if (selected)
validated_default = ENGINE_REGISTRY[i].id;
break;
}
set_cookie("default_engine", validated_default,
"Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false);
}
char redirect_url[512]; char redirect_url[512];
snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query); snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query);
send_redirect(redirect_url); send_redirect(redirect_url);

Some files were not shown because too many files have changed in this diff Show more