Compare commits
60 Commits
indev
...
e48c6366d0
| Author | SHA1 | Date | |
|---|---|---|---|
| e48c6366d0 | |||
| 2bd19a8b7b | |||
|
|
b8fd03344d | ||
|
|
c26a08c6a2 | ||
|
|
5808459c75 | ||
|
|
3c856f93ed | ||
|
|
ba6dae676a | ||
|
|
a9db276fd8 | ||
|
|
92dd87eb57 | ||
|
|
7665efca73 | ||
|
|
6738ab0eac | ||
|
|
3a99a37b0c | ||
|
|
4368e16368 | ||
|
|
3570cb5fb5 | ||
|
|
896b6cd266 | ||
|
|
eb2773f91c | ||
|
|
f6c8242e72 | ||
|
|
e0c209c974 | ||
|
|
deb3c308b8 | ||
|
|
7c5062dd07 | ||
|
|
8cd861fd86 | ||
|
|
a675d19d4f | ||
|
|
ccbb5d71de | ||
|
|
627a219bea | ||
|
|
db02c4cc80 | ||
|
|
c9709029ca | ||
|
|
0eff62bf68 | ||
|
|
96b57648dd | ||
|
|
37f18d148f | ||
|
|
62e6d0c3d6 | ||
|
|
08c1aa8abe | ||
|
|
b9f775fc2d | ||
|
|
821e670ddd | ||
|
|
2fb5f975de | ||
|
|
d2e0c7f481 | ||
|
|
f66686a959 | ||
|
|
f29fa38398 | ||
|
|
478aafcf87 | ||
|
|
1382d73d53 | ||
|
|
8176078105 | ||
|
|
614bd26cb3 | ||
|
|
c6bdeecb2a | ||
|
|
116069c8e9 | ||
|
|
c41ab84738 | ||
|
|
335b6f4683 | ||
|
|
71d3d0dcb0 | ||
|
|
0ea4bc726c | ||
|
|
f38fe3c42e | ||
|
|
c3ed901738 | ||
|
|
9e6e763064 | ||
|
|
6b8a278b4f | ||
|
|
252ed2fcfb | ||
|
|
ebdbbc2ff0 | ||
|
|
82075a664e | ||
|
|
1b9187b153 | ||
|
|
86a9ebb90a | ||
|
|
783a58d954 | ||
|
|
e9b01902d9 | ||
| 0b426487ca | |||
| 48faeb3e01 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
/bin
|
/bin
|
||||||
/obj
|
/obj
|
||||||
config.ini
|
config.ini
|
||||||
|
.session
|
||||||
|
|||||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# ---------- Build stage ----------
|
||||||
|
FROM alpine:3.20 AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
curl-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
openssl-dev \
|
||||||
|
git \
|
||||||
|
pkgconf
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
RUN git clone https://git.bwaaa.monster/beaker /tmp/beaker && \
|
||||||
|
cd /tmp/beaker && \
|
||||||
|
make && make install && \
|
||||||
|
rm -rf /tmp/beaker
|
||||||
|
|
||||||
|
RUN make clean && make && strip bin/omnisearch
|
||||||
|
|
||||||
|
# ---------- Runtime stage ----------
|
||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
libcurl \
|
||||||
|
libxml2 \
|
||||||
|
openssl
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy only required artifacts
|
||||||
|
COPY --from=builder /app/bin/omnisearch /app/omnisearch
|
||||||
|
COPY --from=builder /usr/lib/libbeaker.so /usr/lib/libbeaker.so
|
||||||
|
COPY --from=builder /app/templates /app/templates
|
||||||
|
COPY --from=builder /app/static /app/static
|
||||||
|
|
||||||
|
# Security: non-root user
|
||||||
|
RUN adduser -D appuser && chmod +x /app/omnisearch
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
ENV LD_LIBRARY_PATH=/usr/lib
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["/app/omnisearch"]
|
||||||
43
Makefile
43
Makefile
@@ -3,13 +3,22 @@ UNAME_S := $(shell uname -s)
|
|||||||
PKG_CONFIG ?= pkg-config
|
PKG_CONFIG ?= pkg-config
|
||||||
PKG_DEPS := libxml-2.0 libcurl openssl
|
PKG_DEPS := libxml-2.0 libcurl openssl
|
||||||
|
|
||||||
|
GIT_HASH := $(shell git rev-parse --short HEAD)
|
||||||
|
GIT_DATE := $(shell git log -1 --format='%ad' --date='format:%y.%m.%d')
|
||||||
|
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
GIT_REMOTE := $(shell git remote get-url origin)
|
||||||
|
|
||||||
|
VERSION := $(GIT_DATE)+$(GIT_HASH)_$(GIT_BRANCH)
|
||||||
|
|
||||||
|
CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' -DGIT_REMOTE='"$(GIT_REMOTE)"'
|
||||||
|
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
DEP_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_DEPS) 2>/dev/null)
|
DEP_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_DEPS) 2>/dev/null)
|
||||||
DEP_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_DEPS) 2>/dev/null)
|
DEP_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_DEPS) 2>/dev/null)
|
||||||
CFLAGS := -Wall -Wextra -O2 -Isrc $(DEP_CFLAGS)
|
CFLAGS += $(DEP_CFLAGS)
|
||||||
LIBS := -lbeaker $(DEP_LIBS) -lpthread -lm
|
LIBS := -lbeaker $(DEP_LIBS) -lpthread -lm
|
||||||
else
|
else
|
||||||
CFLAGS := -Wall -Wextra -O2 -Isrc -I/usr/include/libxml2
|
CFLAGS += -I/usr/include/libxml2
|
||||||
LIBS := -lbeaker -lcurl -lxml2 -lpthread -lm -lssl -lcrypto
|
LIBS := -lbeaker -lcurl -lxml2 -lpthread -lm -lssl -lcrypto
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -76,6 +85,7 @@ USER := omnisearch
|
|||||||
GROUP := omnisearch
|
GROUP := omnisearch
|
||||||
|
|
||||||
SYSTEMD_DIR := /etc/systemd/system
|
SYSTEMD_DIR := /etc/systemd/system
|
||||||
|
RUNIT_DIR ?= $(error Please set RUNIT_DIR to your services directory)
|
||||||
OPENRC_DIR := /etc/init.d
|
OPENRC_DIR := /etc/init.d
|
||||||
DINIT_DIR := /etc/dinit.d
|
DINIT_DIR := /etc/dinit.d
|
||||||
LAUNCHD_DIR ?= /Library/LaunchDaemons
|
LAUNCHD_DIR ?= /Library/LaunchDaemons
|
||||||
@@ -94,9 +104,10 @@ install:
|
|||||||
@echo "Example: doas/sudo make install-openrc"
|
@echo "Example: doas/sudo make install-openrc"
|
||||||
|
|
||||||
install-launchd: $(TARGET)
|
install-launchd: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(INSTALL_BIN_DIR) $(LOG_DIR)
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(INSTALL_BIN_DIR) $(LOG_DIR)
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@mkdir -p $(LAUNCHD_DIR)
|
@mkdir -p $(LAUNCHD_DIR)
|
||||||
@@ -115,9 +126,10 @@ install-launchd: $(TARGET)
|
|||||||
@echo "Start with: sudo launchctl kickstart -k system/$(LAUNCHD_LABEL)"
|
@echo "Start with: sudo launchctl kickstart -k system/$(LAUNCHD_LABEL)"
|
||||||
|
|
||||||
install-systemd: $(TARGET)
|
install-systemd: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR)
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR)
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@echo "Setting up user '$(USER)'..."
|
@echo "Setting up user '$(USER)'..."
|
||||||
@@ -133,9 +145,10 @@ install-systemd: $(TARGET)
|
|||||||
@echo "Run 'systemctl enable --now omnisearch' to start"
|
@echo "Run 'systemctl enable --now omnisearch' to start"
|
||||||
|
|
||||||
install-openrc: $(TARGET)
|
install-openrc: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR)
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR)
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@echo "Setting up user '$(USER)'..."
|
@echo "Setting up user '$(USER)'..."
|
||||||
@@ -151,9 +164,10 @@ install-openrc: $(TARGET)
|
|||||||
@echo "Run 'rc-update add omnisearch default' to enable"
|
@echo "Run 'rc-update add omnisearch default' to enable"
|
||||||
|
|
||||||
install-runit: $(TARGET)
|
install-runit: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) /etc/service/omnisearch/log/
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) $(RUNIT_DIR)/omnisearch/log/
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@echo "Setting up user '$(USER)'..."
|
@echo "Setting up user '$(USER)'..."
|
||||||
@@ -161,20 +175,21 @@ install-runit: $(TARGET)
|
|||||||
@id -u $(USER) >/dev/null 2>&1 || useradd --system --home $(DATA_DIR) --shell /usr/sbin/nologin -g $(GROUP) $(USER)
|
@id -u $(USER) >/dev/null 2>&1 || useradd --system --home $(DATA_DIR) --shell /usr/sbin/nologin -g $(GROUP) $(USER)
|
||||||
@chown -R $(USER):$(GROUP) $(LOG_DIR) $(CACHE_DIR) $(VAR_DIR) $(DATA_DIR) 2>/dev/null || true
|
@chown -R $(USER):$(GROUP) $(LOG_DIR) $(CACHE_DIR) $(VAR_DIR) $(DATA_DIR) 2>/dev/null || true
|
||||||
@chown $(USER):$(GROUP) $(DATA_DIR)/config.ini 2>/dev/null || true
|
@chown $(USER):$(GROUP) $(DATA_DIR)/config.ini 2>/dev/null || true
|
||||||
install -m 755 init/runit/run /etc/service/omnisearch/run
|
install -m 755 init/runit/run $(RUNIT_DIR)/omnisearch/run
|
||||||
install -m 755 init/runit/log/run /etc/service/omnisearch/log/run
|
install -m 755 init/runit/log/run $(RUNIT_DIR)/omnisearch/log/run
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Config: $(DATA_DIR)/config.ini"
|
@echo "Config: $(DATA_DIR)/config.ini"
|
||||||
@echo "Edit config with: nano $(DATA_DIR)/config.ini"
|
@echo "Edit config with: nano $(DATA_DIR)/config.ini"
|
||||||
@echo "Installed runit service to /etc/service/omnisearch"
|
@echo "Installed runit service to $(RUNIT_DIR)/omnisearch"
|
||||||
@echo "You need to start the service manually"
|
@echo "You need to start the service manually"
|
||||||
@echo "Void: ln -s /etc/service/omnisearch/ /var/service"
|
@echo "Void: ln -s $(RUNIT_DIR)/omnisearch/ /var/service/"
|
||||||
@echo "Artix: ln -s /etc/service/omnisearch/ /run/runit/"
|
@echo "Artix: ln -s $(RUNIT_DIR)/omnisearch/ /run/runit/"
|
||||||
|
|
||||||
install-s6: $(TARGET)
|
install-s6: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR)
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR)
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@echo "Setting up user '$(USER)'..."
|
@echo "Setting up user '$(USER)'..."
|
||||||
@@ -193,9 +208,10 @@ install-s6: $(TARGET)
|
|||||||
@echo "Service will start automatically"
|
@echo "Service will start automatically"
|
||||||
|
|
||||||
install-dinit: $(TARGET)
|
install-dinit: $(TARGET)
|
||||||
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR)
|
@mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR)
|
||||||
@cp -rf templates/* $(DATA_DIR)/templates/
|
@cp -rf templates/* $(DATA_DIR)/templates/
|
||||||
@cp -rf static/* $(DATA_DIR)/static/
|
@cp -rf static/* $(DATA_DIR)/static/
|
||||||
|
@cp -rf locales/* $(DATA_DIR)/locales/
|
||||||
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
@cp -n example-config.ini $(DATA_DIR)/config.ini || true
|
||||||
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch
|
||||||
@echo "Setting up user '$(USER)'..."
|
@echo "Setting up user '$(USER)'..."
|
||||||
@@ -218,6 +234,7 @@ uninstall:
|
|||||||
rm -f $(DINIT_DIR)/omnisearch
|
rm -f $(DINIT_DIR)/omnisearch
|
||||||
rm -rf /etc/service/omnisearch
|
rm -rf /etc/service/omnisearch
|
||||||
rm -rf /var/service/omnisearch
|
rm -rf /var/service/omnisearch
|
||||||
|
@echo "You might need to unlink omnisearch if you're using runit"
|
||||||
@id -u $(USER) >/dev/null 2>&1 && userdel $(USER) 2>/dev/null || true
|
@id -u $(USER) >/dev/null 2>&1 && userdel $(USER) 2>/dev/null || true
|
||||||
@grep -q '^$(GROUP):' /etc/group 2>/dev/null && groupdel $(GROUP) 2>/dev/null || true
|
@grep -q '^$(GROUP):' /etc/group 2>/dev/null && groupdel $(GROUP) 2>/dev/null || true
|
||||||
@echo "Uninstalled omnisearch"
|
@echo "Uninstalled omnisearch"
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -23,7 +23,7 @@ Depending on your system, you may first need to install libcurl and libxml2.
|
|||||||
|
|
||||||
### Debian/Ubuntu
|
### Debian/Ubuntu
|
||||||
```
|
```
|
||||||
# apt install libxml2-dev libcurl4-openssl-dev
|
# apt install build-essential libssl-dev libxml2-dev libcurl4-openssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
@@ -50,7 +50,7 @@ On Alpine, `shadow` is needed for the user creation process during the install.
|
|||||||
### NixOS
|
### NixOS
|
||||||
Add the flake to your inputs and import the module. That is all you need.
|
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:
|
Here's an example of using the modules in a flake:
|
||||||
```
|
```nix
|
||||||
# flake.nix
|
# flake.nix
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
@@ -108,6 +108,20 @@ On macOS, use `install-launchd`.
|
|||||||
## Hosting
|
## Hosting
|
||||||
Run it normally behind a reverse proxy (like nginx)
|
Run it normally behind a reverse proxy (like nginx)
|
||||||
|
|
||||||
|
## Deploy with Docker Compose
|
||||||
|
|
||||||
|
You need Docker or Podman and Docker Compose installed on your system.
|
||||||
|
|
||||||
|
Run the container:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://git.bwaaa.monster/omnisearch
|
||||||
|
$ cd omnisearch
|
||||||
|
$ docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
By default it can be reached on port 8087.
|
||||||
|
|
||||||
## Customisation
|
## Customisation
|
||||||
To make your own changes while still being able to receive upstream updates:
|
To make your own changes while still being able to receive upstream updates:
|
||||||
|
|
||||||
|
|||||||
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
omnisearch:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8087:8087"
|
||||||
|
volumes:
|
||||||
|
- ./locales:/app/locales
|
||||||
|
- ./example-config.ini:/etc/omnisearch/config.ini
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
[server]
|
[server]
|
||||||
host = 0.0.0.0
|
host = 0.0.0.0
|
||||||
port = 8087
|
port = 8087
|
||||||
domain = https://search.example.com
|
|
||||||
|
# Default locale (default: en_gb)
|
||||||
|
#locale = en_gb
|
||||||
|
|
||||||
[proxy]
|
[proxy]
|
||||||
# Single proxy (comment out to use list_file instead)
|
# Single proxy (comment out to use list_file instead)
|
||||||
@@ -31,3 +33,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
|
||||||
|
|||||||
14
flake.lock
generated
14
flake.lock
generated
@@ -3,11 +3,11 @@
|
|||||||
"beaker-src": {
|
"beaker-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773884524,
|
"lastModified": 1780436704,
|
||||||
"narHash": "sha256-1dnlofWaxI/YRID+WPz2jHZNDyloBubDt/bAQk9ePLU=",
|
"narHash": "sha256-3aMH1YblnpiXKdkQVMRLmMZ5/8G7vB4HAfLg7+izyHg=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "abc598baf15d6f8a4de395a27ba34b1e769558e1",
|
"rev": "360d6271e1a20d128430e52637d5d35f4c706ca5",
|
||||||
"revCount": 21,
|
"revCount": 34,
|
||||||
"shallow": false,
|
"shallow": false,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bwaaa.monster/beaker"
|
"url": "https://git.bwaaa.monster/beaker"
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773734432,
|
"lastModified": 1780243769,
|
||||||
"narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=",
|
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cda48547b432e8d3b18b4180ba07473762ec8558",
|
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin $out/share/omnisearch
|
mkdir -p $out/bin $out/share/omnisearch
|
||||||
install -Dm755 bin/omnisearch $out/bin/omnisearch
|
install -Dm755 bin/omnisearch $out/bin/omnisearch
|
||||||
cp -r templates static -t $out/share/omnisearch/
|
cp -r templates static locales -t $out/share/omnisearch/
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
|
|||||||
34
locales/af_za.ini
Normal file
34
locales/af_za.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "af_za"
|
||||||
|
Name = "Afrikaans (ZA)"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Soek op die web..."
|
||||||
|
search_button = "Soek"
|
||||||
|
surprise_me_button = "Verras my"
|
||||||
|
all_tab = "Alles"
|
||||||
|
images_tab = "Foto's"
|
||||||
|
settings_tab = "Instellings"
|
||||||
|
settings_title = "Instellings"
|
||||||
|
theme_label = "Kleurinstelling"
|
||||||
|
theme_desc = "Kies jou voorkeur kleurskema."
|
||||||
|
theme_system = "Stelsel"
|
||||||
|
theme_light = "Lig"
|
||||||
|
theme_dark = "Donker"
|
||||||
|
language_label = "Taal"
|
||||||
|
display_language_label = "Vertoontaal"
|
||||||
|
language_desc = "Kies jou voorkeurtaal."
|
||||||
|
engines_label = "Soekenjins"
|
||||||
|
engines_desc = "Kies watter soekenjins om te gebruik. Slegs enjins wat op die bediener geaktiveer is, word gewys.."
|
||||||
|
save_settings_button = "Stoor Instellings"
|
||||||
|
no_results = "Geen resultate gevind nie"
|
||||||
|
error_images = "Fout tydens die haal van foto's"
|
||||||
|
rate_limit = "Stadiger! Te veel soektogte van jou!"
|
||||||
|
warning_fetch_error = "versoek het misluk voordat OmniSearch soekresultate kon lees."
|
||||||
|
warning_parse_mismatch = "het soekresultate in 'n formaat teruggegee wat OmniSearch nie kon ontleed nie."
|
||||||
|
warning_blocked = "het 'n captcha of 'n ander blokkerende bladsy in plaas van soekresultate teruggegee."
|
||||||
|
read_more = "Lees Meer"
|
||||||
|
view_cached = "Gekasgeheue"
|
||||||
|
view_image = "Foto"
|
||||||
|
visit_site = "Webwerf"
|
||||||
34
locales/bn_bd.ini
Normal file
34
locales/bn_bd.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "bn_bd"
|
||||||
|
Name = "বাংলা"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "ওয়েব অনুসন্ধান করুন..."
|
||||||
|
search_button = "অনুসন্ধান"
|
||||||
|
surprise_me_button = "চমক প্রদর্শন"
|
||||||
|
all_tab = "যাবতীয়"
|
||||||
|
images_tab = "ছবি"
|
||||||
|
settings_tab = "সেটিংস"
|
||||||
|
settings_title = "সেটিংস"
|
||||||
|
theme_label = "বাহ্যরূপ"
|
||||||
|
theme_desc = "আপনার পছন্দের বর্ণবিন্যাস নির্ধারণ করুন।"
|
||||||
|
theme_system = "সিস্টেম"
|
||||||
|
theme_light = "আলো"
|
||||||
|
theme_dark = "আঁধার"
|
||||||
|
language_label = "ভাষা"
|
||||||
|
display_language_label = "প্রদর্শিত ভাষা"
|
||||||
|
language_desc = "আপনার পছন্দের ভাষা নির্ধারণ করুন।"
|
||||||
|
engines_label = "সার্চ ইঞ্জিন"
|
||||||
|
engines_desc = "ব্যবহৃত সার্চ ইঞ্জিন নির্ধারণ করুন। শুধু সার্ভারে চলনকৃত সার্চ ইঞ্জিন দেখানো।"
|
||||||
|
save_settings_button = "সেটিংস সংরক্ষণ করুন"
|
||||||
|
no_results = "কোনো ফলাফল পাওয়া যায়নি"
|
||||||
|
error_images = "ছবি অনুসন্ধান করায় অক্ষম"
|
||||||
|
rate_limit = "আস্তে-ধীরে! বহুত অনুসন্ধান করছেন দেখি!"
|
||||||
|
warning_fetch_error = "অমনিসার্চ ফলাফল বোঝার আগেই আবেদন ব্যার্থ হয়েছে।"
|
||||||
|
warning_parse_mismatch = "অমনিসার্চ বুঝতে অক্ষম এমনপ্রকার ফলাফল ফিরেছে।"
|
||||||
|
warning_blocked = "অনুসন্ধানের ফলাফলের বদলে ক্যাপ্চা অথবা অন্য ব্লককারী পেজ ফিরেছে।"
|
||||||
|
read_more = "পড়তে থাকুন"
|
||||||
|
view_cached = "ক্যাশকৃত"
|
||||||
|
view_image = "ছবি"
|
||||||
|
visit_site = "সাইট"
|
||||||
34
locales/en_gb.ini
Normal file
34
locales/en_gb.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "en_gb"
|
||||||
|
Name = "English"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Search the web..."
|
||||||
|
search_button = "Search"
|
||||||
|
surprise_me_button = "Surprise me"
|
||||||
|
all_tab = "All"
|
||||||
|
images_tab = "Images"
|
||||||
|
settings_tab = "Settings"
|
||||||
|
settings_title = "Settings"
|
||||||
|
theme_label = "Appearance"
|
||||||
|
theme_desc = "Choose your preferred colour scheme."
|
||||||
|
theme_system = "System"
|
||||||
|
theme_light = "Light"
|
||||||
|
theme_dark = "Dark"
|
||||||
|
language_label = "Language"
|
||||||
|
display_language_label = "Display Language"
|
||||||
|
language_desc = "Choose your preferred language."
|
||||||
|
engines_label = "Search Engines"
|
||||||
|
engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown."
|
||||||
|
save_settings_button = "Save Settings"
|
||||||
|
no_results = "No results found"
|
||||||
|
error_images = "Error fetching images"
|
||||||
|
rate_limit = "Slow down! Too many searches from you!"
|
||||||
|
warning_fetch_error = "request failed before OmniSearch could read search results."
|
||||||
|
warning_parse_mismatch = "returned search results in a format OmniSearch could not parse."
|
||||||
|
warning_blocked = "returned a captcha or another blocking page instead of search results."
|
||||||
|
read_more = "Read More"
|
||||||
|
view_cached = "Cached"
|
||||||
|
view_image = "Image"
|
||||||
|
visit_site = "Site"
|
||||||
34
locales/en_us.ini
Normal file
34
locales/en_us.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "en_us"
|
||||||
|
Name = "English (US)"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Search the web..."
|
||||||
|
search_button = "Search"
|
||||||
|
surprise_me_button = "Surprise me"
|
||||||
|
all_tab = "All"
|
||||||
|
images_tab = "Images"
|
||||||
|
settings_tab = "Settings"
|
||||||
|
settings_title = "Settings"
|
||||||
|
theme_label = "Appearance"
|
||||||
|
theme_desc = "Choose your preferred color scheme."
|
||||||
|
theme_system = "System"
|
||||||
|
theme_light = "Light"
|
||||||
|
theme_dark = "Dark"
|
||||||
|
language_label = "Language"
|
||||||
|
display_language_label = "Display Language"
|
||||||
|
language_desc = "Choose your preferred language."
|
||||||
|
engines_label = "Search Engines"
|
||||||
|
engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown."
|
||||||
|
save_settings_button = "Save Settings"
|
||||||
|
no_results = "No results found"
|
||||||
|
error_images = "Error fetching images"
|
||||||
|
rate_limit = "Slow down! Too many searches from you!"
|
||||||
|
warning_fetch_error = "request failed before OmniSearch could read search results."
|
||||||
|
warning_parse_mismatch = "returned search results in a format OmniSearch could not parse."
|
||||||
|
warning_blocked = "returned a captcha or another blocking page instead of search results."
|
||||||
|
read_more = "Read More"
|
||||||
|
view_cached = "Cached"
|
||||||
|
view_image = "Image"
|
||||||
|
visit_site = "Site"
|
||||||
34
locales/fr_fr.ini
Normal file
34
locales/fr_fr.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "fr_fr"
|
||||||
|
Name = "Français"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Rechercher le web..."
|
||||||
|
search_button = "Rechercher"
|
||||||
|
surprise_me_button = "Surprenez-moi"
|
||||||
|
all_tab = "Tout"
|
||||||
|
images_tab = "Images"
|
||||||
|
settings_tab = "Paramètres"
|
||||||
|
settings_title = "Paramètres"
|
||||||
|
theme_label = "Apparence"
|
||||||
|
theme_desc = "Choisissez votre palette de couleurs préférée."
|
||||||
|
theme_system = "Système"
|
||||||
|
theme_light = "Clair"
|
||||||
|
theme_dark = "Sombre"
|
||||||
|
language_label = "Langue"
|
||||||
|
display_language_label = "Afficher les Langues"
|
||||||
|
language_desc = "Choisissez votre langue."
|
||||||
|
engines_label = "Moteur de Recherche"
|
||||||
|
engines_desc = "Choisissez les moteurs de recherche que vous souhaitez utiliser. Seulement les moteurs de recherche activer sur le serveur sont affichés."
|
||||||
|
save_settings_button = "Enregistrer les paramètres"
|
||||||
|
no_results = "Aucun résultats"
|
||||||
|
error_images = "Erreur lors du chargement des images"
|
||||||
|
rate_limit = "Woah ralentissez! Vous effectuer trop de Rechercher!"
|
||||||
|
warning_fetch_error = "La requête a échoué avant que OmniSearch puisse lire les résultats de la recherche."
|
||||||
|
warning_parse_mismatch = "Les résultats de la recherche ont été retourné dans un format que OmniSearch ne peux pas analyser."
|
||||||
|
warning_blocked = "Un captcha ou une autre page bloquante a été retourné au lieu d'un résultat de recherche."
|
||||||
|
read_more = "Lire Plus"
|
||||||
|
view_cached = "En cache"
|
||||||
|
view_image = "Image"
|
||||||
|
visit_site = "Site"
|
||||||
34
locales/lv_lv.ini
Normal file
34
locales/lv_lv.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "lv_lv"
|
||||||
|
Name = "Latviešu"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Meklēt tīmeklī..."
|
||||||
|
search_button = "Meklēt"
|
||||||
|
surprise_me_button = "Pārsteidz mani"
|
||||||
|
all_tab = "Viss"
|
||||||
|
images_tab = "Attēli"
|
||||||
|
settings_tab = "Iestatījumi"
|
||||||
|
settings_title = "Iestatījumi"
|
||||||
|
theme_label = "Izskats"
|
||||||
|
theme_desc = "Izvēlieties vēlamo krāsu shēmu."
|
||||||
|
theme_system = "Sistēma"
|
||||||
|
theme_light = "Gaišs"
|
||||||
|
theme_dark = "Tumšs"
|
||||||
|
language_label = "Valoda"
|
||||||
|
display_language_label = "Saskarnes valoda"
|
||||||
|
language_desc = "Izvēlieties vēlamo valodu."
|
||||||
|
save_settings_button = "Saglabāt iestatījumus"
|
||||||
|
no_results = "Rezultāti nav atrasti"
|
||||||
|
error_images = "Kļūda, ielādējot attēlus"
|
||||||
|
rate_limit = "Lēnāk! Pārāk daudz jūsu meklēšanas vaicājumu!"
|
||||||
|
warning_fetch_error = "Neizdevās izpildīt pieprasījumu, pirms OmniSearch varēja nolasīt meklēšanas rezultātus."
|
||||||
|
warning_parse_mismatch = "Meklēšanas rezultāti tika atgriezti formātā, kuru OmniSearch nevarēja apstrādāt."
|
||||||
|
warning_blocked = "Tika atgriezta captcha vai cita bloķējoša lapa meklēšanas rezultātu vietā."
|
||||||
|
read_more = "Lasīt vairāk"
|
||||||
|
view_cached = "Saglabātā kopija"
|
||||||
|
view_image = "Skatīt attēlu"
|
||||||
|
visit_site = "Apmeklēt vietni"
|
||||||
|
engines_label = "Meklēšanas dzinēji"
|
||||||
|
engines_desc = "Izvēlieties, kurus meklēšanas dzinējus izmantot. Tiek rādīti tikai serverī iespējotie."
|
||||||
34
locales/nl_nl.ini
Normal file
34
locales/nl_nl.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "nl_nl"
|
||||||
|
Name = "Nederlands"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Zoek op het web..."
|
||||||
|
search_button = "Zoek"
|
||||||
|
surprise_me_button = "Verras me"
|
||||||
|
all_tab = "Alle"
|
||||||
|
images_tab = "Plaatjes"
|
||||||
|
settings_tab = "Instellingen"
|
||||||
|
settings_title = "Instellingen"
|
||||||
|
theme_label = "Kleurinstelling"
|
||||||
|
theme_desc = "Kies welke stijl je gebruikt."
|
||||||
|
theme_system = "Systeemkleur"
|
||||||
|
theme_light = "Licht"
|
||||||
|
theme_dark = "Donker"
|
||||||
|
language_label = "Taal"
|
||||||
|
display_language_label = "Geselecteerde taal"
|
||||||
|
language_desc = "Kies de taal van OmniSearch."
|
||||||
|
engines_label = "Zoekmachines"
|
||||||
|
engines_desc = "Kies welke zoekmachines je gebruikt. Alleen de ingeschakelde zoekmachines worden gebruikt."
|
||||||
|
save_settings_button = "Opslaan"
|
||||||
|
no_results = "Geen resultaten"
|
||||||
|
error_images = "Foutmelding bij het zoeken naar plaatjes."
|
||||||
|
rate_limit = "Te veel zoekopdrachten! Ga wat langzamer!"
|
||||||
|
warning_fetch_error = "stuurde een foutmelding voordat OmniSearch de resultaten kon lezen."
|
||||||
|
warning_parse_mismatch = "stuurde een foutmelding in een format dat OmniSearch niet kon lezen."
|
||||||
|
warning_blocked = "stuurde een captcha of een andere pagina, maar geen zoekresultaten."
|
||||||
|
read_more = "Lees meer"
|
||||||
|
view_cached = "Tijdelijk Opgeslagen"
|
||||||
|
view_image = "Bekijk"
|
||||||
|
visit_site = "Website"
|
||||||
34
locales/pt_br.ini
Normal file
34
locales/pt_br.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "pt_br"
|
||||||
|
Name = "Portuguese (Brazil)"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Pesquisar na internet..."
|
||||||
|
search_button = "Pesquisar"
|
||||||
|
surprise_me_button = "Me surpreenda"
|
||||||
|
all_tab = "Tudo"
|
||||||
|
images_tab = "Imagens"
|
||||||
|
settings_tab = "Configurações"
|
||||||
|
settings_title = "Configurações"
|
||||||
|
theme_label = "Aparência"
|
||||||
|
theme_desc = "Escolha o esquema de cores de sua preferência."
|
||||||
|
theme_system = "Padrão do Sistema"
|
||||||
|
theme_light = "Claro"
|
||||||
|
theme_dark = "Escuro"
|
||||||
|
language_label = "Idioma"
|
||||||
|
display_language_label = "Idioma de exibição"
|
||||||
|
language_desc = "Escolha o idioma de sua preferência"
|
||||||
|
engines_label = "Mecanismos de pesquisa"
|
||||||
|
engines_desc = "Escolha quais mecanismos de pesquisa serão utilizados. Apenas mecanismos habilitados no servidor serão mostrados."
|
||||||
|
save_settings_button = "Salvar configurações"
|
||||||
|
no_results = "Nenhum resultado encontrado"
|
||||||
|
error_images = "Erro ao buscar imagens"
|
||||||
|
rate_limit = "Calma aí! Muitas pesquisas vindo de você!"
|
||||||
|
warning_fetch_error = "pedido falhou antes que o Omnisearch pudesse ler resultados de pesquisa."
|
||||||
|
warning_parse_mismatch = "retornou resultados em um formato que o Omnisearch não pode processar."
|
||||||
|
warning_blocked = "retornou um captcha ou outra página de bloqueio ao invés de resultados de pesquisa."
|
||||||
|
read_more = "Ler mais"
|
||||||
|
view_cached = "Armazenado em cache"
|
||||||
|
view_image = "Imagem"
|
||||||
|
visit_site = "Site"
|
||||||
34
locales/ru_ru.ini
Normal file
34
locales/ru_ru.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "ru_ru"
|
||||||
|
Name = "Русский"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "Поиск в интернете..."
|
||||||
|
search_button = "Найти"
|
||||||
|
surprise_me_button = "Удиви меня"
|
||||||
|
all_tab = "Все"
|
||||||
|
images_tab = "Изображения"
|
||||||
|
settings_tab = "Настройки"
|
||||||
|
settings_title = "Настройки"
|
||||||
|
theme_label = "Внешний вид"
|
||||||
|
theme_desc = "Выберите предпочитаемую цветовую схему."
|
||||||
|
theme_system = "Системная"
|
||||||
|
theme_light = "Светлая"
|
||||||
|
theme_dark = "Тёмная"
|
||||||
|
language_label = "Язык"
|
||||||
|
display_language_label = "Язык интерфейса"
|
||||||
|
language_desc = "Выберите предпочитаемый язык."
|
||||||
|
save_settings_button = "Сохранить настройки"
|
||||||
|
no_results = "Ничего не найдено"
|
||||||
|
error_images = "Ошибка загрузки изображений"
|
||||||
|
rate_limit = "Слишком много поисковых запросов. Пожалуйста, попробуйте позже."
|
||||||
|
warning_fetch_error = "Не удалось выполнить запрос до того, как OmniSearch смог прочитать результаты поиска."
|
||||||
|
warning_parse_mismatch = "Результаты поиска возвращены в формате, который OmniSearch не смог обработать."
|
||||||
|
warning_blocked = "Вместо результатов поиска возвращена капча или страница блокировки."
|
||||||
|
read_more = "Подробнее"
|
||||||
|
view_cached = "Сохранённая копия"
|
||||||
|
view_image = "Просмотр изображения"
|
||||||
|
visit_site = "Перейти на сайт"
|
||||||
|
engines_label = "Поисковые системы"
|
||||||
|
engines_desc = "Выберите, какие поисковые системы использовать. Отображаются только включённые на сервере."
|
||||||
34
locales/vi_vn.ini
Normal file
34
locales/vi_vn.ini
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[Meta]
|
||||||
|
Id = "vi_vn"
|
||||||
|
Name = "tiếng việt"
|
||||||
|
Direction = "ltr"
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
search_placeholder = "tìm kiếm trên web..."
|
||||||
|
search_button = "tìm kiếm"
|
||||||
|
surprise_me_button = "bất ngờ"
|
||||||
|
all_tab = "tất cả"
|
||||||
|
images_tab = "hình ảnh"
|
||||||
|
settings_tab = "cài đặt"
|
||||||
|
settings_title = "cài đặt"
|
||||||
|
theme_label = "giao diện"
|
||||||
|
theme_desc = "chọn bảng màu mà bạn muốn"
|
||||||
|
theme_system = "hệ thống"
|
||||||
|
theme_light = "sáng"
|
||||||
|
theme_dark = "tối"
|
||||||
|
language_label = "ngôn ngữ"
|
||||||
|
display_language_label = "ngôn ngữ hiển thị"
|
||||||
|
language_desc = "chọn ngôn ngữ mà bn muốn"
|
||||||
|
engines_label = "công cụ tìm kiếm"
|
||||||
|
engines_desc = "chọn các công cụ tìm kiếm để sử dụng, chỉ những công cụ dc bật trên máy chủ ms đc hiển thị."
|
||||||
|
save_settings_button = "lưu cài đặt"
|
||||||
|
no_results = "k tìm thấy kết quả"
|
||||||
|
error_images = "lỗi khi tải hình ảnh"
|
||||||
|
rate_limit = "chậm lại! dừng lại! bạn đã tìm kiếm quá nhiều lần!"
|
||||||
|
warning_fetch_error = "yêu cầu thất bại trước khi omnisearch có thể đọc kết quả tìm kiếm."
|
||||||
|
warning_parse_mismatch = "kết quả tìm kiếm được trả về ở định dạng omnisearch k thể phân tích."
|
||||||
|
warning_blocked = "trang trả về captcha hoặc trang chặn khác thay vì kết quả tìm kiếm."
|
||||||
|
read_more = "đọc thêm"
|
||||||
|
view_cached = "bản lưu"
|
||||||
|
view_image = "hình ảnh"
|
||||||
|
visit_site = "truy cập trang"
|
||||||
@@ -20,6 +20,7 @@ let
|
|||||||
host = ${cfg.settings.server.host}
|
host = ${cfg.settings.server.host}
|
||||||
port = ${toString cfg.settings.server.port}
|
port = ${toString cfg.settings.server.port}
|
||||||
domain = ${cfg.settings.server.domain}
|
domain = ${cfg.settings.server.domain}
|
||||||
|
${lib.optionalString (cfg.settings.server.locale != null) "locale = ${cfg.settings.server.locale}"}
|
||||||
|
|
||||||
[proxy]
|
[proxy]
|
||||||
${lib.optionalString (cfg.settings.proxy.proxy != null) "proxy = \"${cfg.settings.proxy.proxy}\""}
|
${lib.optionalString (cfg.settings.proxy.proxy != null) "proxy = \"${cfg.settings.proxy.proxy}\""}
|
||||||
@@ -64,7 +65,11 @@ in
|
|||||||
};
|
};
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "http://localhost:8087";
|
default = "http://localhost:${toString cfg.settings.server.port}";
|
||||||
|
};
|
||||||
|
locale = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
proxy = {
|
proxy = {
|
||||||
@@ -122,6 +127,7 @@ in
|
|||||||
BindReadOnlyPaths = [
|
BindReadOnlyPaths = [
|
||||||
"${pkg}/share/omnisearch/templates:/var/lib/omnisearch/templates"
|
"${pkg}/share/omnisearch/templates:/var/lib/omnisearch/templates"
|
||||||
"${pkg}/share/omnisearch/static:/var/lib/omnisearch/static"
|
"${pkg}/share/omnisearch/static:/var/lib/omnisearch/static"
|
||||||
|
"${pkg}/share/omnisearch/locales:/var/lib/omnisearch/locales"
|
||||||
"${finalConfigFile}:/var/lib/omnisearch/config.ini"
|
"${finalConfigFile}:/var/lib/omnisearch/config.ini"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,20 @@
|
|||||||
static char cache_dir[BUFFER_SIZE_MEDIUM] = {0};
|
static char cache_dir[BUFFER_SIZE_MEDIUM] = {0};
|
||||||
static int cache_ttl_search_val = DEFAULT_CACHE_TTL_SEARCH;
|
static int cache_ttl_search_val = DEFAULT_CACHE_TTL_SEARCH;
|
||||||
static int cache_ttl_infobox_val = DEFAULT_CACHE_TTL_INFOBOX;
|
static int cache_ttl_infobox_val = DEFAULT_CACHE_TTL_INFOBOX;
|
||||||
|
static int cache_ttl_image_val = DEFAULT_CACHE_TTL_IMAGE;
|
||||||
|
|
||||||
void set_cache_ttl_search(int ttl) { cache_ttl_search_val = ttl; }
|
void set_cache_ttl_search(int ttl) { cache_ttl_search_val = ttl; }
|
||||||
|
|
||||||
void set_cache_ttl_infobox(int ttl) { cache_ttl_infobox_val = ttl; }
|
void set_cache_ttl_infobox(int ttl) { cache_ttl_infobox_val = ttl; }
|
||||||
|
|
||||||
|
void set_cache_ttl_image(int ttl) { cache_ttl_image_val = ttl; }
|
||||||
|
|
||||||
int get_cache_ttl_search(void) { return cache_ttl_search_val; }
|
int get_cache_ttl_search(void) { return cache_ttl_search_val; }
|
||||||
|
|
||||||
int get_cache_ttl_infobox(void) { return cache_ttl_infobox_val; }
|
int get_cache_ttl_infobox(void) { return cache_ttl_infobox_val; }
|
||||||
|
|
||||||
|
int get_cache_ttl_image(void) { return cache_ttl_image_val; }
|
||||||
|
|
||||||
static void md5_hash(const char *str, char *output) {
|
static void md5_hash(const char *str, char *output) {
|
||||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||||
unsigned int hash_len;
|
unsigned int hash_len;
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ char *cache_compute_key(const char *query, int page, const char *engine_name);
|
|||||||
|
|
||||||
void set_cache_ttl_search(int ttl);
|
void set_cache_ttl_search(int ttl);
|
||||||
void set_cache_ttl_infobox(int ttl);
|
void set_cache_ttl_infobox(int ttl);
|
||||||
|
void set_cache_ttl_image(int ttl);
|
||||||
int get_cache_ttl_search(void);
|
int get_cache_ttl_search(void);
|
||||||
int get_cache_ttl_infobox(void);
|
int get_cache_ttl_infobox(void);
|
||||||
|
int get_cache_ttl_image(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
19
src/Config.c
19
src/Config.c
@@ -65,9 +65,10 @@ int load_config(const char *filename, Config *config) {
|
|||||||
config->host[sizeof(config->host) - 1] = '\0';
|
config->host[sizeof(config->host) - 1] = '\0';
|
||||||
} else if (strcmp(key, "port") == 0) {
|
} else if (strcmp(key, "port") == 0) {
|
||||||
config->port = atoi(value);
|
config->port = atoi(value);
|
||||||
} else if (strcmp(key, "domain") == 0) {
|
} else if (strcmp(key, "locale") == 0) {
|
||||||
strncpy(config->domain, value, sizeof(config->domain) - 1);
|
strncpy(config->default_locale, value,
|
||||||
config->domain[sizeof(config->domain) - 1] = '\0';
|
sizeof(config->default_locale) - 1);
|
||||||
|
config->default_locale[sizeof(config->default_locale) - 1] = '\0';
|
||||||
}
|
}
|
||||||
} else if (strcmp(section, "proxy") == 0) {
|
} else if (strcmp(section, "proxy") == 0) {
|
||||||
if (strcmp(key, "proxy") == 0) {
|
if (strcmp(key, "proxy") == 0) {
|
||||||
@@ -92,12 +93,24 @@ int load_config(const char *filename, Config *config) {
|
|||||||
config->cache_ttl_search = atoi(value);
|
config->cache_ttl_search = atoi(value);
|
||||||
} else if (strcmp(key, "ttl_infobox") == 0) {
|
} else if (strcmp(key, "ttl_infobox") == 0) {
|
||||||
config->cache_ttl_infobox = atoi(value);
|
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) {
|
} else if (strcmp(section, "engines") == 0) {
|
||||||
if (strcmp(key, "engines") == 0) {
|
if (strcmp(key, "engines") == 0) {
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#define DEFAULT_CACHE_DIR "/tmp/omnisearch_cache"
|
#define DEFAULT_CACHE_DIR "/tmp/omnisearch_cache"
|
||||||
#define DEFAULT_CACHE_TTL_SEARCH 3600
|
#define DEFAULT_CACHE_TTL_SEARCH 3600
|
||||||
#define DEFAULT_CACHE_TTL_INFOBOX 86400
|
#define DEFAULT_CACHE_TTL_INFOBOX 86400
|
||||||
|
#define DEFAULT_CACHE_TTL_IMAGE 604800
|
||||||
#define DEFAULT_MAX_PROXY_RETRIES 3
|
#define DEFAULT_MAX_PROXY_RETRIES 3
|
||||||
|
|
||||||
#define BUFFER_SIZE_SMALL 256
|
#define BUFFER_SIZE_SMALL 256
|
||||||
@@ -34,6 +35,7 @@ typedef struct {
|
|||||||
char host[256];
|
char host[256];
|
||||||
int port;
|
int port;
|
||||||
char domain[256];
|
char domain[256];
|
||||||
|
char default_locale[32];
|
||||||
char proxy[256];
|
char proxy[256];
|
||||||
char proxy_list_file[256];
|
char proxy_list_file[256];
|
||||||
int max_proxy_retries;
|
int max_proxy_retries;
|
||||||
@@ -42,7 +44,12 @@ typedef struct {
|
|||||||
char cache_dir[512];
|
char cache_dir[512];
|
||||||
int cache_ttl_search;
|
int cache_ttl_search;
|
||||||
int cache_ttl_infobox;
|
int cache_ttl_infobox;
|
||||||
|
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);
|
||||||
|
|||||||
166
src/Infobox/ColourCode.c
Normal file
166
src/Infobox/ColourCode.c
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#include "ColourCode.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const unsigned char HEX_VALS[256] = {
|
||||||
|
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5,
|
||||||
|
['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['a'] = 10, ['b'] = 11,
|
||||||
|
['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11,
|
||||||
|
['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int is_hex_digit(char c) {
|
||||||
|
return HEX_VALS[(unsigned char)c] || c == '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_valid_hex(const char *s, int len) {
|
||||||
|
if (len != 3 && len != 6)
|
||||||
|
return 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (!is_hex_digit(s[i]))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hex_to_rgb(const char *hex, int *r, int *g, int *b, int len) {
|
||||||
|
*r = *g = *b = 0;
|
||||||
|
if (len == 3) {
|
||||||
|
*r = HEX_VALS[(unsigned char)hex[0]] * 17;
|
||||||
|
*g = HEX_VALS[(unsigned char)hex[1]] * 17;
|
||||||
|
*b = HEX_VALS[(unsigned char)hex[2]] * 17;
|
||||||
|
} else if (len == 6) {
|
||||||
|
*r = HEX_VALS[(unsigned char)hex[0]] * 16 + HEX_VALS[(unsigned char)hex[1]];
|
||||||
|
*g = HEX_VALS[(unsigned char)hex[2]] * 16 + HEX_VALS[(unsigned char)hex[3]];
|
||||||
|
*b = HEX_VALS[(unsigned char)hex[4]] * 16 + HEX_VALS[(unsigned char)hex[5]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgb_to_hsl(int r, int g, int b, int *h, int *s, int *l) {
|
||||||
|
int max = r > g ? (r > b ? r : b) : (g > b ? g : b);
|
||||||
|
int min = r < g ? (r < b ? r : b) : (g < b ? g : b);
|
||||||
|
int delta = max - min;
|
||||||
|
*l = (max + min) / 2;
|
||||||
|
*s = 0;
|
||||||
|
*h = 0;
|
||||||
|
if (delta > 0) {
|
||||||
|
*s = (int)((double)delta / (1.0 - fabs(2.0 * *l / 255.0 - 1.0)) * 100.0);
|
||||||
|
if (max == r) {
|
||||||
|
*h = (int)(60.0 * fmod((double)(g - b) / delta, 6.0));
|
||||||
|
} else if (max == g) {
|
||||||
|
*h = (int)(60.0 * (((b - r) / delta) + 2.0));
|
||||||
|
} else {
|
||||||
|
*h = (int)(60.0 * (((r - g) / delta) + 4.0));
|
||||||
|
}
|
||||||
|
if (*h < 0)
|
||||||
|
*h += 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgb_to_hsv(int r, int g, int b, int *h, int *s, int *v) {
|
||||||
|
int max = r > g ? (r > b ? r : b) : (g > b ? g : b);
|
||||||
|
int min = r < g ? (r < b ? r : b) : (g < b ? g : b);
|
||||||
|
int delta = max - min;
|
||||||
|
*v = max * 100 / 255;
|
||||||
|
*s = max > 0 ? delta * 100 / max : 0;
|
||||||
|
*h = 0;
|
||||||
|
if (delta > 0) {
|
||||||
|
if (max == r) {
|
||||||
|
*h = (int)(60.0 * fmod((double)(g - b) / delta, 6.0));
|
||||||
|
} else if (max == g) {
|
||||||
|
*h = (int)(60.0 * (((b - r) / delta) + 2.0));
|
||||||
|
} else {
|
||||||
|
*h = (int)(60.0 * (((r - g) / delta) + 4.0));
|
||||||
|
}
|
||||||
|
if (*h < 0)
|
||||||
|
*h += 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgb_to_cmyk(int r, int g, int b, int *c, int *m, int *y, int *k) {
|
||||||
|
int max = r > g ? (r > b ? r : b) : (g > b ? g : b);
|
||||||
|
double kf = 1.0 - max / 255.0;
|
||||||
|
if (kf >= 0.99) {
|
||||||
|
*c = *m = *y = *k = 100;
|
||||||
|
} else {
|
||||||
|
*c = (int)((1.0 - r / 255.0 - kf) / (1.0 - kf) * 100);
|
||||||
|
*m = (int)((1.0 - g / 255.0 - kf) / (1.0 - kf) * 100);
|
||||||
|
*y = (int)((1.0 - b / 255.0 - kf) / (1.0 - kf) * 100);
|
||||||
|
*k = (int)(kf * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_colour_code_query(const char *query) {
|
||||||
|
if (!query)
|
||||||
|
return 0;
|
||||||
|
const char *p = query;
|
||||||
|
if (p[0] == '#') {
|
||||||
|
p++;
|
||||||
|
while (*p == ' ')
|
||||||
|
p++;
|
||||||
|
const char *end = p;
|
||||||
|
while (is_hex_digit(*end))
|
||||||
|
end++;
|
||||||
|
int len = end - p;
|
||||||
|
return is_valid_hex(p, len);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoBox fetch_colour_data(char *query) {
|
||||||
|
InfoBox info = {NULL, NULL, NULL, NULL};
|
||||||
|
if (!query)
|
||||||
|
return info;
|
||||||
|
|
||||||
|
const char *p = query;
|
||||||
|
if (p[0] != '#')
|
||||||
|
return info;
|
||||||
|
p++;
|
||||||
|
while (*p == ' ')
|
||||||
|
p++;
|
||||||
|
const char *end = p;
|
||||||
|
while (is_hex_digit(*end))
|
||||||
|
end++;
|
||||||
|
int len = end - p;
|
||||||
|
if (!is_valid_hex(p, len))
|
||||||
|
return info;
|
||||||
|
|
||||||
|
int r, g, b;
|
||||||
|
hex_to_rgb(p, &r, &g, &b, len);
|
||||||
|
|
||||||
|
int h, s, l;
|
||||||
|
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||||
|
|
||||||
|
int h2, s2, v;
|
||||||
|
rgb_to_hsv(r, g, b, &h2, &s2, &v);
|
||||||
|
|
||||||
|
int c, m, y, k;
|
||||||
|
rgb_to_cmyk(r, g, b, &c, &m, &y, &k);
|
||||||
|
|
||||||
|
char html[1024];
|
||||||
|
snprintf(html, sizeof(html),
|
||||||
|
"<div class='colour-container' style='line-height: 1.6;'>"
|
||||||
|
"<div style='display: flex; align-items: flex-start; gap: 16px;'>"
|
||||||
|
"<div style='width: 100px; height: 100px; border-radius: 12px; "
|
||||||
|
"border: 1px solid #ddd; background-color: #%.*s; flex-shrink: 0;'></div>"
|
||||||
|
"<div>"
|
||||||
|
"<div style='font-size: 1.3em; font-weight: bold; margin-bottom: 8px;'>"
|
||||||
|
"#%.*s</div>"
|
||||||
|
"<div style='color: #666; margin-bottom: 4px;'>RGB(%d, %d, %d)</div>"
|
||||||
|
"<div style='color: #666; margin-bottom: 4px;'>HSL(%d, %d%%, %d%%)</div>"
|
||||||
|
"<div style='color: #666; margin-bottom: 4px;'>HSV(%d, %d%%, %d%%)</div>"
|
||||||
|
"<div style='color: #666;'>CMYK(%d%%, %d%%, %d%%, %d%%)</div>"
|
||||||
|
"</div>"
|
||||||
|
"</div>"
|
||||||
|
"</div>",
|
||||||
|
len, p, len, p, r, g, b, h, s, l, h2, s2, v, c, m, y, k);
|
||||||
|
|
||||||
|
info.title = strdup("Colour");
|
||||||
|
info.extract = strdup(html);
|
||||||
|
info.thumbnail_url = NULL;
|
||||||
|
info.url = strdup("#");
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
9
src/Infobox/ColourCode.h
Normal file
9
src/Infobox/ColourCode.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef COLOURCODE_H
|
||||||
|
#define COLOURCODE_H
|
||||||
|
|
||||||
|
#include "Infobox.h"
|
||||||
|
|
||||||
|
int is_colour_code_query(const char *query);
|
||||||
|
InfoBox fetch_colour_data(char *query);
|
||||||
|
|
||||||
|
#endif
|
||||||
210
src/Limiter/RateLimit.c
Normal file
210
src/Limiter/RateLimit.c
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#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 const char *str_case_str(const char *haystack, const char *needle) {
|
||||||
|
size_t nlen = strlen(needle);
|
||||||
|
for (; *haystack; haystack++) {
|
||||||
|
if (tolower((unsigned char)*haystack) == tolower((unsigned char)*needle)) {
|
||||||
|
size_t i;
|
||||||
|
for (i = 1; i < nlen; i++) {
|
||||||
|
if (tolower((unsigned char)haystack[i]) !=
|
||||||
|
tolower((unsigned char)needle[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == nlen)
|
||||||
|
return haystack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = str_case_str(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
|
||||||
56
src/Main.c
56
src/Main.c
@@ -13,20 +13,44 @@
|
|||||||
#include "Routes/ImageProxy.h"
|
#include "Routes/ImageProxy.h"
|
||||||
#include "Routes/Images.h"
|
#include "Routes/Images.h"
|
||||||
#include "Routes/Search.h"
|
#include "Routes/Search.h"
|
||||||
|
#include "Routes/Settings.h"
|
||||||
|
#include "Routes/SettingsSave.h"
|
||||||
#include "Scraping/Scraping.h"
|
#include "Scraping/Scraping.h"
|
||||||
|
#include "Utility/Utility.h"
|
||||||
|
|
||||||
Config global_config;
|
Config global_config;
|
||||||
|
|
||||||
int handle_opensearch(UrlParams *params) {
|
int handle_opensearch(UrlParams *params) {
|
||||||
(void)params;
|
(void)params;
|
||||||
extern Config global_config;
|
|
||||||
TemplateContext ctx = new_context();
|
TemplateContext ctx = new_context();
|
||||||
context_set(&ctx, "domain", global_config.domain);
|
|
||||||
|
const char *http_host = beaker_get_header("Host");
|
||||||
|
if (http_host == NULL) {
|
||||||
|
http_host = "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *req_scheme =
|
||||||
|
"https"; // not sure if it's a good idea to just assume https, but you
|
||||||
|
// should probably be using https for anything other than testing
|
||||||
|
// or local network anyways.
|
||||||
|
|
||||||
|
if (strncmp(http_host, "localhost", 9) == 0 ||
|
||||||
|
strncmp(http_host, "127.", 4) == 0 ||
|
||||||
|
strncmp(http_host, "192.168.", 8) == 0 ||
|
||||||
|
strncmp(http_host, "10.", 3) == 0) {
|
||||||
|
req_scheme = "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
context_set(&ctx, "domain", http_host);
|
||||||
|
context_set(&ctx, "scheme", req_scheme);
|
||||||
|
|
||||||
char *rendered = render_template("opensearch.xml", &ctx);
|
char *rendered = render_template("opensearch.xml", &ctx);
|
||||||
serve_data(rendered, strlen(rendered), "application/opensearchdescription+xml");
|
serve_data(rendered, strlen(rendered),
|
||||||
|
"application/opensearchdescription+xml");
|
||||||
|
|
||||||
free(rendered);
|
free(rendered);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +67,7 @@ int main() {
|
|||||||
|
|
||||||
Config cfg = {.host = DEFAULT_HOST,
|
Config cfg = {.host = DEFAULT_HOST,
|
||||||
.port = DEFAULT_PORT,
|
.port = DEFAULT_PORT,
|
||||||
.domain = "",
|
.default_locale = "en_gb",
|
||||||
.proxy = "",
|
.proxy = "",
|
||||||
.proxy_list_file = "",
|
.proxy_list_file = "",
|
||||||
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
|
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
|
||||||
@@ -52,14 +76,30 @@ int main() {
|
|||||||
.cache_dir = DEFAULT_CACHE_DIR,
|
.cache_dir = DEFAULT_CACHE_DIR,
|
||||||
.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,
|
||||||
.engines = ""};
|
.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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_default_locale(cfg.default_locale);
|
||||||
|
init_themes("static");
|
||||||
|
|
||||||
global_config = cfg;
|
global_config = cfg;
|
||||||
|
|
||||||
|
int loaded = beaker_load_locales();
|
||||||
|
if (loaded > 0) {
|
||||||
|
fprintf(stderr, "[INFO] Loaded %d locales\n", loaded);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "[WARN] No locales loaded (make sure to run from "
|
||||||
|
"omnisearch directory)\n");
|
||||||
|
}
|
||||||
|
|
||||||
apply_engines_config(cfg.engines);
|
apply_engines_config(cfg.engines);
|
||||||
|
|
||||||
if (cache_init(cfg.cache_dir) != 0) {
|
if (cache_init(cfg.cache_dir) != 0) {
|
||||||
@@ -72,6 +112,7 @@ int main() {
|
|||||||
|
|
||||||
set_cache_ttl_search(cfg.cache_ttl_search);
|
set_cache_ttl_search(cfg.cache_ttl_search);
|
||||||
set_cache_ttl_infobox(cfg.cache_ttl_infobox);
|
set_cache_ttl_infobox(cfg.cache_ttl_infobox);
|
||||||
|
set_cache_ttl_image(cfg.cache_ttl_image);
|
||||||
|
|
||||||
if (cfg.proxy_list_file[0] != '\0') {
|
if (cfg.proxy_list_file[0] != '\0') {
|
||||||
if (load_proxy_list(cfg.proxy_list_file) < 0) {
|
if (load_proxy_list(cfg.proxy_list_file) < 0) {
|
||||||
@@ -95,6 +136,8 @@ 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("/settings", settings_handler);
|
||||||
|
set_handler("/save_settings", settings_save_handler);
|
||||||
|
|
||||||
fprintf(stderr, "[INFO] Starting Omnisearch on %s:%d\n", cfg.host, cfg.port);
|
fprintf(stderr, "[INFO] Starting Omnisearch on %s:%d\n", cfg.host, cfg.port);
|
||||||
|
|
||||||
@@ -109,6 +152,7 @@ int main() {
|
|||||||
|
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
xmlCleanupParser();
|
xmlCleanupParser();
|
||||||
|
beaker_free_locales();
|
||||||
free_proxy_list();
|
free_proxy_list();
|
||||||
cache_shutdown();
|
cache_shutdown();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
#include "Home.h"
|
#include "Home.h"
|
||||||
|
#include "../Utility/Utility.h"
|
||||||
|
#include <beaker.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
int home_handler(UrlParams *params) {
|
int home_handler(UrlParams *params) {
|
||||||
(void)params;
|
(void)params;
|
||||||
|
char *theme = get_theme("");
|
||||||
|
char *locale = get_locale(NULL);
|
||||||
|
|
||||||
|
char **themes = NULL;
|
||||||
|
int themes_count = 0;
|
||||||
|
get_available_themes(&themes, &themes_count);
|
||||||
|
|
||||||
TemplateContext ctx = new_context();
|
TemplateContext ctx = new_context();
|
||||||
|
context_set(&ctx, "theme", theme);
|
||||||
|
context_set(&ctx, "version", VERSION);
|
||||||
|
context_set(&ctx, "git_remote", GIT_REMOTE);
|
||||||
|
beaker_set_locale(&ctx, locale);
|
||||||
char *rendered_html = render_template("home.html", &ctx);
|
char *rendered_html = render_template("home.html", &ctx);
|
||||||
send_response(rendered_html);
|
send_response(rendered_html);
|
||||||
|
|
||||||
free(rendered_html);
|
free(rendered_html);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
|
free(theme);
|
||||||
|
free(locale);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
#include "ImageProxy.h"
|
#include "ImageProxy.h"
|
||||||
|
#include "../Cache/Cache.h"
|
||||||
#include "../Proxy/Proxy.h"
|
#include "../Proxy/Proxy.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
#include <curl/urlapi.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#define MAX_IMAGE_SIZE (10 * 1024 * 1024)
|
#define MAX_IMAGE_SIZE (10 * 1024 * 1024)
|
||||||
|
#define DNS_CACHE_TTL 300
|
||||||
|
|
||||||
|
typedef struct DnsCacheEntry {
|
||||||
|
char hostname[256];
|
||||||
|
char ip_str[INET_ADDRSTRLEN];
|
||||||
|
time_t resolved_at;
|
||||||
|
struct DnsCacheEntry *next;
|
||||||
|
} DnsCacheEntry;
|
||||||
|
|
||||||
|
static DnsCacheEntry *dns_cache = NULL;
|
||||||
|
static pthread_mutex_t dns_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
@@ -13,7 +33,157 @@ typedef struct {
|
|||||||
size_t capacity;
|
size_t capacity;
|
||||||
} MemoryBuffer;
|
} MemoryBuffer;
|
||||||
|
|
||||||
static int is_allowed_domain(const char *url) {
|
static int dns_cache_lookup(const char *hostname, char *out_ip) {
|
||||||
|
time_t now = time(NULL);
|
||||||
|
pthread_mutex_lock(&dns_cache_mutex);
|
||||||
|
for (DnsCacheEntry *e = dns_cache; e; e = e->next) {
|
||||||
|
if (strcmp(e->hostname, hostname) == 0) {
|
||||||
|
if ((now - e->resolved_at) < DNS_CACHE_TTL) {
|
||||||
|
strcpy(out_ip, e->ip_str);
|
||||||
|
pthread_mutex_unlock(&dns_cache_mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&dns_cache_mutex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dns_cache_insert(const char *hostname, const char *ip_str) {
|
||||||
|
time_t now = time(NULL);
|
||||||
|
pthread_mutex_lock(&dns_cache_mutex);
|
||||||
|
|
||||||
|
DnsCacheEntry **cursor = &dns_cache;
|
||||||
|
while (*cursor) {
|
||||||
|
DnsCacheEntry *entry = *cursor;
|
||||||
|
if ((now - entry->resolved_at) >= DNS_CACHE_TTL) {
|
||||||
|
*cursor = entry->next;
|
||||||
|
free(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strcmp(entry->hostname, hostname) == 0) {
|
||||||
|
strcpy(entry->ip_str, ip_str);
|
||||||
|
entry->resolved_at = now;
|
||||||
|
pthread_mutex_unlock(&dns_cache_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cursor = &entry->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsCacheEntry *new_entry = malloc(sizeof(DnsCacheEntry));
|
||||||
|
if (new_entry) {
|
||||||
|
strncpy(new_entry->hostname, hostname, sizeof(new_entry->hostname) - 1);
|
||||||
|
new_entry->hostname[sizeof(new_entry->hostname) - 1] = '\0';
|
||||||
|
strcpy(new_entry->ip_str, ip_str);
|
||||||
|
new_entry->resolved_at = now;
|
||||||
|
new_entry->next = dns_cache;
|
||||||
|
dns_cache = new_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&dns_cache_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 const char *is_private_hostname(const char *hostname, char *out_ip) {
|
||||||
|
if (dns_cache_lookup(hostname, out_ip) == 0) {
|
||||||
|
return out_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(res);
|
||||||
|
strcpy(out_ip, ip_str);
|
||||||
|
dns_cache_insert(hostname, ip_str);
|
||||||
|
return out_ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(res);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_allowed_domain(const char *url, char *resolved_ip) {
|
||||||
|
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, "://");
|
const char *protocol = strstr(url, "://");
|
||||||
if (!protocol) {
|
if (!protocol) {
|
||||||
protocol = url;
|
protocol = url;
|
||||||
@@ -30,21 +200,18 @@ static int is_allowed_domain(const char *url) {
|
|||||||
}
|
}
|
||||||
strncpy(host, protocol, host_len);
|
strncpy(host, protocol, host_len);
|
||||||
|
|
||||||
const char *allowed_domains[] = {"mm.bing.net", "th.bing.com", NULL};
|
char *colon = strchr(host, ':');
|
||||||
|
if (colon) {
|
||||||
for (int i = 0; allowed_domains[i] != NULL; i++) {
|
*colon = '\0';
|
||||||
size_t domain_len = strlen(allowed_domains[i]);
|
|
||||||
size_t host_str_len = strlen(host);
|
|
||||||
|
|
||||||
if (host_str_len >= domain_len) {
|
|
||||||
const char *suffix = host + host_str_len - domain_len;
|
|
||||||
if (strcmp(suffix, allowed_domains[i]) == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
if (!is_private_hostname(host, resolved_ip)) {
|
||||||
|
curl_url_cleanup(h);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_url_cleanup(h);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t write_callback(void *contents, size_t size, size_t nmemb,
|
static size_t write_callback(void *contents, size_t size, size_t nmemb,
|
||||||
@@ -73,6 +240,31 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb,
|
|||||||
return realsize;
|
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) {
|
int image_proxy_handler(UrlParams *params) {
|
||||||
const char *url = NULL;
|
const char *url = NULL;
|
||||||
for (int i = 0; i < params->count; i++) {
|
for (int i = 0; i < params->count; i++) {
|
||||||
@@ -87,13 +279,67 @@ int image_proxy_handler(UrlParams *params) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_allowed_domain(url)) {
|
char resolved_ip[INET_ADDRSTRLEN] = {0};
|
||||||
send_response("Domain not allowed");
|
int domain_check = is_allowed_domain(url, resolved_ip);
|
||||||
|
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) {
|
||||||
|
if (!cached_data || cached_size == 0) {
|
||||||
|
free(cached_data);
|
||||||
|
free(cache_key);
|
||||||
|
send_response("Empty cached image response");
|
||||||
|
return 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CURL *curl = curl_easy_init();
|
CURL *curl = curl_easy_init();
|
||||||
if (!curl) {
|
if (!curl) {
|
||||||
|
free(cache_key);
|
||||||
send_response("Failed to initialize curl");
|
send_response("Failed to initialize curl");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -102,6 +348,7 @@ int image_proxy_handler(UrlParams *params) {
|
|||||||
|
|
||||||
if (!buf.data) {
|
if (!buf.data) {
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
free(cache_key);
|
||||||
send_response("Memory allocation failed");
|
send_response("Memory allocation failed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -111,8 +358,37 @@ int image_proxy_handler(UrlParams *params) {
|
|||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
|
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);
|
apply_proxy_settings(curl);
|
||||||
|
|
||||||
|
struct curl_slist *resolves = NULL;
|
||||||
|
if (resolved_ip[0] != '\0') {
|
||||||
|
CURLU *u = curl_url();
|
||||||
|
if (u) {
|
||||||
|
curl_url_set(u, CURLUPART_URL, url, 0);
|
||||||
|
char *rhost = NULL;
|
||||||
|
curl_url_get(u, CURLUPART_HOST, &rhost, 0);
|
||||||
|
if (rhost) {
|
||||||
|
char *rscheme = NULL;
|
||||||
|
curl_url_get(u, CURLUPART_SCHEME, &rscheme, 0);
|
||||||
|
int port = (rscheme && strcasecmp(rscheme, "https") == 0) ? 443 : 80;
|
||||||
|
if (rscheme)
|
||||||
|
curl_free(rscheme);
|
||||||
|
|
||||||
|
char resolve_str[512];
|
||||||
|
snprintf(resolve_str, sizeof(resolve_str), "%s:%d:%s", rhost, port,
|
||||||
|
resolved_ip);
|
||||||
|
resolves = curl_slist_append(NULL, resolve_str);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_RESOLVE, resolves);
|
||||||
|
curl_free(rhost);
|
||||||
|
}
|
||||||
|
curl_url_cleanup(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
long response_code;
|
long response_code;
|
||||||
@@ -126,18 +402,40 @@ int image_proxy_handler(UrlParams *params) {
|
|||||||
strncpy(content_type, content_type_ptr, sizeof(content_type) - 1);
|
strncpy(content_type, content_type_ptr, sizeof(content_type) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resolves)
|
||||||
|
curl_slist_free_all(resolves);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK || response_code != 200) {
|
if (res != CURLE_OK || response_code != 200) {
|
||||||
free(buf.data);
|
free(buf.data);
|
||||||
|
free(cache_key);
|
||||||
send_response("Failed to fetch image");
|
send_response("Failed to fetch image");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buf.size == 0) {
|
||||||
|
free(buf.data);
|
||||||
|
free(cache_key);
|
||||||
|
send_response("Empty image response");
|
||||||
|
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 =
|
const char *mime_type =
|
||||||
strlen(content_type) > 0 ? content_type : "image/jpeg";
|
strlen(content_type) > 0 ? content_type : "image/jpeg";
|
||||||
|
|
||||||
|
cache_set(cache_key, buf.data, buf.size);
|
||||||
|
|
||||||
serve_data(buf.data, buf.size, mime_type);
|
serve_data(buf.data, buf.size, mime_type);
|
||||||
|
|
||||||
free(buf.data);
|
free(buf.data);
|
||||||
|
free(cache_key);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,48 @@
|
|||||||
#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 "Config.h"
|
#include "Config.h"
|
||||||
|
#include <beaker.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *build_images_href(const char *query, int page) {
|
||||||
|
const char *safe_query = query ? query : "";
|
||||||
|
size_t needed = strlen("/images?q=") + strlen(safe_query) + 1;
|
||||||
|
if (page > 1)
|
||||||
|
needed += strlen("&p=") + 16;
|
||||||
|
|
||||||
|
char *href = (char *)malloc(needed);
|
||||||
|
if (!href)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
snprintf(href, needed, "/images?q=%s", safe_query);
|
||||||
|
if (page > 1) {
|
||||||
|
char page_buf[16];
|
||||||
|
snprintf(page_buf, sizeof(page_buf), "%d", page);
|
||||||
|
strcat(href, "&p=");
|
||||||
|
strcat(href, page_buf);
|
||||||
|
}
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *images_href_builder(int page, void *data) {
|
||||||
|
return build_images_href((const char *)data, page);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -20,38 +59,94 @@ int images_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char page_str[16], prev_str[16], next_str[16], two_prev_str[16],
|
|
||||||
two_next_str[16];
|
|
||||||
|
|
||||||
snprintf(page_str, sizeof(page_str), "%d", page);
|
|
||||||
snprintf(prev_str, sizeof(prev_str), "%d", page > 1 ? page - 1 : 0);
|
|
||||||
snprintf(next_str, sizeof(next_str), "%d", page + 1);
|
|
||||||
snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0);
|
|
||||||
snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2);
|
|
||||||
context_set(&ctx, "query", raw_query);
|
context_set(&ctx, "query", raw_query);
|
||||||
context_set(&ctx, "page", page_str);
|
|
||||||
context_set(&ctx, "prev_page", prev_str);
|
char *theme = get_theme("");
|
||||||
context_set(&ctx, "next_page", next_str);
|
context_set(&ctx, "theme", theme);
|
||||||
context_set(&ctx, "two_prev_page", two_prev_str);
|
free(theme);
|
||||||
context_set(&ctx, "two_next_page", two_next_str);
|
|
||||||
|
char *locale = get_locale(NULL);
|
||||||
|
beaker_set_locale(&ctx, locale);
|
||||||
|
|
||||||
|
const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit");
|
||||||
|
if (!rate_limit_msg)
|
||||||
|
rate_limit_msg = "Slow down! Too many image searches from you!";
|
||||||
|
const char *error_images_msg =
|
||||||
|
beaker_get_locale_value(locale, "error_images");
|
||||||
|
if (!error_images_msg)
|
||||||
|
error_images_msg = "Error fetching images";
|
||||||
|
|
||||||
|
char ***pager_matrix = NULL;
|
||||||
|
int *pager_inner_counts = NULL;
|
||||||
|
int pager_count =
|
||||||
|
build_pagination(page, images_href_builder, (void *)raw_query,
|
||||||
|
&pager_matrix, &pager_inner_counts);
|
||||||
|
if (pager_count > 0) {
|
||||||
|
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);
|
||||||
context_set(&ctx, "query", display_query);
|
context_set(&ctx, "query", display_query);
|
||||||
|
|
||||||
if (!raw_query || strlen(raw_query) == 0) {
|
if (!raw_query || strlen(raw_query) == 0) {
|
||||||
send_response("<h1>No query provided</h1>");
|
send_redirect("/");
|
||||||
if (display_query)
|
if (display_query)
|
||||||
free(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
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>%s</h1>", rate_limit_msg);
|
||||||
|
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>");
|
char error_html[128];
|
||||||
|
snprintf(error_html, sizeof(error_html), "<h1>%s</h1>", error_images_msg);
|
||||||
|
send_response(error_html);
|
||||||
|
free(request_cache_key);
|
||||||
free(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -66,6 +161,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;
|
||||||
@@ -99,7 +195,18 @@ int images_handler(UrlParams *params) {
|
|||||||
free(image_matrix);
|
free(image_matrix);
|
||||||
free(inner_counts);
|
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(display_query);
|
free(display_query);
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,102 @@
|
|||||||
#include "Search.h"
|
#include "Search.h"
|
||||||
|
#include "../Cache/Cache.h"
|
||||||
#include "../Infobox/Calculator.h"
|
#include "../Infobox/Calculator.h"
|
||||||
|
#include "../Infobox/ColourCode.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"
|
||||||
|
#include "../Utility/Utility.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
#define URL_HASH_TABLE_SIZE 64
|
||||||
|
|
||||||
|
typedef struct UrlHashEntry {
|
||||||
|
char *url;
|
||||||
|
struct UrlHashEntry *next;
|
||||||
|
} UrlHashEntry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UrlHashEntry *buckets[URL_HASH_TABLE_SIZE];
|
||||||
|
} UrlHashTable;
|
||||||
|
|
||||||
|
static void url_hash_init(UrlHashTable *ht) {
|
||||||
|
for (int i = 0; i < URL_HASH_TABLE_SIZE; i++) {
|
||||||
|
ht->buckets[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int url_hash(const char *url) {
|
||||||
|
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||||
|
unsigned int hash_len;
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
if (!ctx)
|
||||||
|
return 0;
|
||||||
|
EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, url, strlen(url));
|
||||||
|
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
unsigned int h = 0;
|
||||||
|
for (unsigned int i = 0; i < hash_len; i++) {
|
||||||
|
h = h * 31 + hash[i];
|
||||||
|
}
|
||||||
|
return h % URL_HASH_TABLE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int url_hash_contains(UrlHashTable *ht, const char *url) {
|
||||||
|
unsigned int idx = url_hash(url);
|
||||||
|
for (UrlHashEntry *e = ht->buckets[idx]; e; e = e->next) {
|
||||||
|
if (strcmp(e->url, url) == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int url_hash_insert(UrlHashTable *ht, const char *url) {
|
||||||
|
unsigned int idx = url_hash(url);
|
||||||
|
for (UrlHashEntry *e = ht->buckets[idx]; e; e = e->next) {
|
||||||
|
if (strcmp(e->url, url) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UrlHashEntry *new_entry = malloc(sizeof(UrlHashEntry));
|
||||||
|
if (!new_entry)
|
||||||
|
return -1;
|
||||||
|
new_entry->url = strdup(url);
|
||||||
|
if (!new_entry->url) {
|
||||||
|
free(new_entry);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
new_entry->next = ht->buckets[idx];
|
||||||
|
ht->buckets[idx] = new_entry;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void url_hash_free(UrlHashTable *ht) {
|
||||||
|
for (int i = 0; i < URL_HASH_TABLE_SIZE; i++) {
|
||||||
|
UrlHashEntry *e = ht->buckets[i];
|
||||||
|
while (e) {
|
||||||
|
UrlHashEntry *next = e->next;
|
||||||
|
free(e->url);
|
||||||
|
free(e);
|
||||||
|
e = next;
|
||||||
|
}
|
||||||
|
ht->buckets[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *query;
|
const char *query;
|
||||||
InfoBox result;
|
InfoBox result;
|
||||||
@@ -29,8 +111,6 @@ typedef struct {
|
|||||||
|
|
||||||
enum {
|
enum {
|
||||||
RESULT_FIELD_COUNT = 6,
|
RESULT_FIELD_COUNT = 6,
|
||||||
LINK_FIELD_COUNT = 3,
|
|
||||||
PAGER_WINDOW_SIZE = 5,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static InfoBox fetch_wiki_wrapper(char *query) {
|
static InfoBox fetch_wiki_wrapper(char *query) {
|
||||||
@@ -137,11 +217,16 @@ static int is_calculator_query(const char *query) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static InfoBox fetch_colour_wrapper(char *query) {
|
||||||
|
return fetch_colour_data(query);
|
||||||
|
}
|
||||||
|
|
||||||
static InfoBoxHandler handlers[] = {
|
static InfoBoxHandler handlers[] = {
|
||||||
{is_dictionary_query, fetch_dict_wrapper, NULL},
|
{is_dictionary_query, fetch_dict_wrapper, NULL},
|
||||||
{is_calculator_query, fetch_calc_wrapper, NULL},
|
{is_calculator_query, fetch_calc_wrapper, NULL},
|
||||||
{is_unit_conv_query, fetch_unit_wrapper, NULL},
|
{is_unit_conv_query, fetch_unit_wrapper, NULL},
|
||||||
{is_currency_query, fetch_currency_wrapper, NULL},
|
{is_currency_query, fetch_currency_wrapper, NULL},
|
||||||
|
{is_colour_code_query, fetch_colour_wrapper, NULL},
|
||||||
{always_true, fetch_wiki_wrapper, construct_wiki_url},
|
{always_true, fetch_wiki_wrapper, construct_wiki_url},
|
||||||
};
|
};
|
||||||
enum { HANDLER_COUNT = sizeof(handlers) / sizeof(handlers[0]) };
|
enum { HANDLER_COUNT = sizeof(handlers) / sizeof(handlers[0]) };
|
||||||
@@ -160,7 +245,7 @@ static void *infobox_thread_func(void *arg) {
|
|||||||
|
|
||||||
data->result = h->fetch_fn((char *)data->query);
|
data->result = h->fetch_fn((char *)data->query);
|
||||||
data->success = (data->result.title != NULL && data->result.extract != NULL &&
|
data->success = (data->result.title != NULL && data->result.extract != NULL &&
|
||||||
strlen(data->result.extract) > 10);
|
data->result.extract[0] != '\0');
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,66 +271,6 @@ static int add_infobox_to_collection(InfoBox *infobox, char ****collection,
|
|||||||
return current_count + 1;
|
return current_count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_link_to_collection(const char *href, const char *label,
|
|
||||||
const char *class_name, char ****collection,
|
|
||||||
int **inner_counts, int current_count) {
|
|
||||||
char ***old_collection = *collection;
|
|
||||||
int *old_inner_counts = *inner_counts;
|
|
||||||
char ***new_collection =
|
|
||||||
(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) {
|
|
||||||
memcpy(new_collection, *collection, sizeof(char **) * current_count);
|
|
||||||
}
|
|
||||||
if (*inner_counts && current_count > 0) {
|
|
||||||
memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
*collection = new_collection;
|
|
||||||
*inner_counts = new_inner_counts;
|
|
||||||
|
|
||||||
(*collection)[current_count] =
|
|
||||||
(char **)malloc(sizeof(char *) * LINK_FIELD_COUNT);
|
|
||||||
if (!(*collection)[current_count]) {
|
|
||||||
*collection = old_collection;
|
|
||||||
*inner_counts = old_inner_counts;
|
|
||||||
free(new_collection);
|
|
||||||
free(new_inner_counts);
|
|
||||||
return current_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
(*collection)[current_count][0] = strdup(href ? href : "");
|
|
||||||
(*collection)[current_count][1] = strdup(label ? label : "");
|
|
||||||
(*collection)[current_count][2] = strdup(class_name ? class_name : "");
|
|
||||||
|
|
||||||
if (!(*collection)[current_count][0] || !(*collection)[current_count][1] ||
|
|
||||||
!(*collection)[current_count][2]) {
|
|
||||||
free((*collection)[current_count][0]);
|
|
||||||
free((*collection)[current_count][1]);
|
|
||||||
free((*collection)[current_count][2]);
|
|
||||||
free((*collection)[current_count]);
|
|
||||||
*collection = old_collection;
|
|
||||||
*inner_counts = old_inner_counts;
|
|
||||||
free(new_collection);
|
|
||||||
free(new_inner_counts);
|
|
||||||
return current_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
(*inner_counts)[current_count] = LINK_FIELD_COUNT;
|
|
||||||
|
|
||||||
free(old_collection);
|
|
||||||
free(old_inner_counts);
|
|
||||||
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,
|
char ****collection, int **inner_counts,
|
||||||
@@ -293,15 +318,20 @@ static int add_warning_to_collection(const char *engine_name,
|
|||||||
return current_count + 1;
|
return current_count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *warning_message_for_job(const ScrapeJob *job) {
|
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: {
|
||||||
return "request failed before OmniSearch could read search results.";
|
const char *msg = beaker_get_locale_value(locale, "warning_fetch_error");
|
||||||
case SCRAPE_STATUS_PARSE_MISMATCH:
|
return msg ? msg : "request failed before OmniSearch could read search results.";
|
||||||
return "returned search results in a format OmniSearch could not parse.";
|
}
|
||||||
case SCRAPE_STATUS_BLOCKED:
|
case SCRAPE_STATUS_PARSE_MISMATCH: {
|
||||||
return "returned a captcha or another blocking page instead of search "
|
const char *msg = beaker_get_locale_value(locale, "warning_parse_mismatch");
|
||||||
"results.";
|
return msg ? msg : "returned search results in a format OmniSearch could not parse.";
|
||||||
|
}
|
||||||
|
case SCRAPE_STATUS_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.";
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -344,6 +374,13 @@ static const SearchEngine *find_enabled_engine(const char *engine_id) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int engine_allowed_for_user(const SearchEngine *eng, char **user_ids,
|
||||||
|
int user_count, int has_pref) {
|
||||||
|
if (!has_pref)
|
||||||
|
return 1;
|
||||||
|
return user_engines_contains(eng->id, user_ids, user_count);
|
||||||
|
}
|
||||||
|
|
||||||
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 : "";
|
||||||
@@ -377,46 +414,37 @@ static char *build_search_href(const char *query, const char *engine_id,
|
|||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *build_result_sources(unsigned int source_mask, ScrapeJob *jobs,
|
typedef struct {
|
||||||
int job_count) {
|
const char *query;
|
||||||
size_t needed = 1;
|
const char *engine_id;
|
||||||
int source_count = 0;
|
} SearchHrefData;
|
||||||
|
|
||||||
for (int i = 0; i < job_count; i++) {
|
static char *search_href_builder(int page, void *data) {
|
||||||
if (source_mask & (1u << i)) {
|
SearchHrefData *d = (SearchHrefData *)data;
|
||||||
needed += strlen(jobs[i].engine->name);
|
return build_search_href(d->query, d->engine_id, page);
|
||||||
if (source_count > 0)
|
}
|
||||||
needed += strlen(" · ");
|
|
||||||
source_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *sources = (char *)malloc(needed);
|
static char *build_search_request_cache_key(const char *query,
|
||||||
if (!sources)
|
const char *engine_id, int page,
|
||||||
return NULL;
|
const char *client_key) {
|
||||||
|
char scope_key[BUFFER_SIZE_MEDIUM];
|
||||||
sources[0] = '\0';
|
snprintf(scope_key, sizeof(scope_key), "search_request:%s:%s",
|
||||||
source_count = 0;
|
engine_id ? engine_id : "all", client_key ? client_key : "unknown");
|
||||||
|
return cache_compute_key(query, page, scope_key);
|
||||||
for (int i = 0; i < job_count; i++) {
|
|
||||||
if (source_mask & (1u << i)) {
|
|
||||||
if (source_count > 0)
|
|
||||||
strcat(sources, " · ");
|
|
||||||
strcat(sources, jobs[i].engine->name);
|
|
||||||
source_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sources;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
||||||
int page = 1;
|
int page = 1;
|
||||||
int btnI = 0;
|
int btnI = 0;
|
||||||
|
|
||||||
|
char **user_engines = NULL;
|
||||||
|
int 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++) {
|
||||||
if (strcmp(params->params[i].key, "q") == 0) {
|
if (strcmp(params->params[i].key, "q") == 0) {
|
||||||
@@ -434,12 +462,40 @@ int results_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context_set(&ctx, "query", raw_query);
|
context_set(&ctx, "query", raw_query);
|
||||||
|
|
||||||
|
char *theme = get_theme("");
|
||||||
|
context_set(&ctx, "theme", theme);
|
||||||
|
free(theme);
|
||||||
|
|
||||||
|
char *locale = get_locale(NULL);
|
||||||
|
beaker_set_locale(&ctx, locale);
|
||||||
|
|
||||||
|
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!";
|
||||||
|
const char *no_results_msg = beaker_get_locale_value(locale, "no_results");
|
||||||
|
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);
|
||||||
context_set(&ctx, "page", page_str);
|
context_set(&ctx, "page", page_str);
|
||||||
|
|
||||||
|
char prev_str[16], next_str[16], two_prev_str[16], two_next_str[16];
|
||||||
|
snprintf(prev_str, sizeof(prev_str), "%d", page > 1 ? page - 1 : 0);
|
||||||
|
snprintf(next_str, sizeof(next_str), "%d", page + 1);
|
||||||
|
snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0);
|
||||||
|
snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2);
|
||||||
|
context_set(&ctx, "prev_page", prev_str);
|
||||||
|
context_set(&ctx, "next_page", next_str);
|
||||||
|
context_set(&ctx, "two_prev_page", two_prev_str);
|
||||||
|
context_set(&ctx, "two_next_page", two_next_str);
|
||||||
|
|
||||||
if (!raw_query || strlen(raw_query) == 0) {
|
if (!raw_query || strlen(raw_query) == 0) {
|
||||||
send_response("<h1>No query provided</h1>");
|
send_redirect("/");
|
||||||
|
if (has_user_pref) {
|
||||||
|
for (int i = 0; i < user_engine_count; i++)
|
||||||
|
free(user_engines[i]);
|
||||||
|
free(user_engines);
|
||||||
|
}
|
||||||
free_context(&ctx);
|
free_context(&ctx);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -456,7 +512,9 @@ int results_handler(UrlParams *params) {
|
|||||||
int enabled_engine_count = 0;
|
int enabled_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 &&
|
||||||
(!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) {
|
(!selected_engine || &ENGINE_REGISTRY[i] == selected_engine) &&
|
||||||
|
engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines,
|
||||||
|
user_engine_count, has_user_pref)) {
|
||||||
enabled_engine_count++;
|
enabled_engine_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,7 +541,9 @@ int results_handler(UrlParams *params) {
|
|||||||
int engine_idx = 0;
|
int engine_idx = 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 &&
|
||||||
(!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) {
|
(!selected_engine || &ENGINE_REGISTRY[i] == selected_engine) &&
|
||||||
|
engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines,
|
||||||
|
user_engine_count, has_user_pref)) {
|
||||||
all_results[engine_idx] = NULL;
|
all_results[engine_idx] = NULL;
|
||||||
jobs[engine_idx].engine = &ENGINE_REGISTRY[i];
|
jobs[engine_idx].engine = &ENGINE_REGISTRY[i];
|
||||||
jobs[engine_idx].query = raw_query;
|
jobs[engine_idx].query = raw_query;
|
||||||
@@ -501,9 +561,56 @@ 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>%s</h1>", rate_limit_msg);
|
||||||
|
send_response(response);
|
||||||
|
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 -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 &&
|
||||||
|
engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines,
|
||||||
|
user_engine_count, has_user_pref))
|
||||||
filter_engine_count++;
|
filter_engine_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +627,9 @@ int results_handler(UrlParams *params) {
|
|||||||
free(all_href);
|
free(all_href);
|
||||||
|
|
||||||
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 ||
|
||||||
|
!engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines,
|
||||||
|
user_engine_count, has_user_pref))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
char *filter_href =
|
char *filter_href =
|
||||||
@@ -578,6 +687,12 @@ int results_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
free_context(&ctx);
|
||||||
if (redirect_url) {
|
if (redirect_url) {
|
||||||
send_redirect(redirect_url);
|
send_redirect(redirect_url);
|
||||||
@@ -596,8 +711,16 @@ int results_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
free_context(&ctx);
|
||||||
send_response("<h1>No results found</h1>");
|
char no_results_html[128];
|
||||||
|
snprintf(no_results_html, sizeof(no_results_html), "<h1>%s</h1>", no_results_msg);
|
||||||
|
send_response(no_results_html);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,7 +752,7 @@ int results_handler(UrlParams *params) {
|
|||||||
|
|
||||||
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++) {
|
||||||
if (warning_message_for_job(&jobs[i]))
|
if (warning_message_for_job(&jobs[i], locale))
|
||||||
warning_count++;
|
warning_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +762,7 @@ int results_handler(UrlParams *params) {
|
|||||||
int warning_index = 0;
|
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]);
|
const char *warning_message = warning_message_for_job(&jobs[i], locale);
|
||||||
if (!warning_message)
|
if (!warning_message)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -673,18 +796,7 @@ int results_handler(UrlParams *params) {
|
|||||||
if (total_results > 0) {
|
if (total_results > 0) {
|
||||||
char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results);
|
char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results);
|
||||||
int *results_inner_counts = (int *)malloc(sizeof(int) * total_results);
|
int *results_inner_counts = (int *)malloc(sizeof(int) * total_results);
|
||||||
char **seen_urls = (char **)malloc(sizeof(char *) * total_results);
|
if (!results_matrix || !results_inner_counts) {
|
||||||
unsigned int *source_masks =
|
|
||||||
(unsigned int *)calloc(total_results, sizeof(unsigned int));
|
|
||||||
if (!results_matrix || !results_inner_counts || !seen_urls || !source_masks) {
|
|
||||||
if (results_matrix)
|
|
||||||
free(results_matrix);
|
|
||||||
if (results_inner_counts)
|
|
||||||
free(results_inner_counts);
|
|
||||||
if (seen_urls)
|
|
||||||
free(seen_urls);
|
|
||||||
if (source_masks)
|
|
||||||
free(source_masks);
|
|
||||||
char *html = render_template("results.html", &ctx);
|
char *html = render_template("results.html", &ctx);
|
||||||
if (html) {
|
if (html) {
|
||||||
send_response(html);
|
send_response(html);
|
||||||
@@ -699,42 +811,35 @@ int results_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
free_context(&ctx);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int unique_count = 0;
|
int unique_count = 0;
|
||||||
|
UrlHashTable url_table;
|
||||||
|
url_hash_init(&url_table);
|
||||||
|
|
||||||
for (int i = 0; i < enabled_engine_count; i++) {
|
for (int i = 0; i < enabled_engine_count; i++) {
|
||||||
for (int j = 0; j < jobs[i].results_count; j++) {
|
for (int j = 0; j < jobs[i].results_count; j++) {
|
||||||
char *display_url = all_results[i][j].url;
|
char *display_url = all_results[i][j].url;
|
||||||
|
|
||||||
int is_duplicate = 0;
|
if (url_hash_contains(&url_table, display_url)) {
|
||||||
for (int k = 0; k < unique_count; k++) {
|
|
||||||
if (strcmp(seen_urls[k], display_url) == 0) {
|
|
||||||
is_duplicate = 1;
|
|
||||||
source_masks[k] |= (1u << i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_duplicate) {
|
|
||||||
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);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
seen_urls[unique_count] = strdup(display_url);
|
url_hash_insert(&url_table, display_url);
|
||||||
if (!seen_urls[unique_count]) {
|
|
||||||
free(all_results[i][j].url);
|
|
||||||
free(all_results[i][j].title);
|
|
||||||
free(all_results[i][j].snippet);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results_matrix[unique_count] =
|
results_matrix[unique_count] =
|
||||||
(char **)malloc(sizeof(char *) * RESULT_FIELD_COUNT);
|
(char **)malloc(sizeof(char *) * RESULT_FIELD_COUNT);
|
||||||
if (!results_matrix[unique_count]) {
|
if (!results_matrix[unique_count]) {
|
||||||
free(seen_urls[unique_count]);
|
|
||||||
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);
|
||||||
@@ -752,9 +857,8 @@ int results_handler(UrlParams *params) {
|
|||||||
all_results[i][j].snippet ? strdup(all_results[i][j].snippet)
|
all_results[i][j].snippet ? strdup(all_results[i][j].snippet)
|
||||||
: strdup("");
|
: strdup("");
|
||||||
results_matrix[unique_count][4] = strdup(base_url ? base_url : "");
|
results_matrix[unique_count][4] = strdup(base_url ? base_url : "");
|
||||||
results_matrix[unique_count][5] = NULL;
|
results_matrix[unique_count][5] = strdup("");
|
||||||
|
|
||||||
source_masks[unique_count] = (1u << i);
|
|
||||||
results_inner_counts[unique_count] = RESULT_FIELD_COUNT;
|
results_inner_counts[unique_count] = RESULT_FIELD_COUNT;
|
||||||
|
|
||||||
free(pretty_url);
|
free(pretty_url);
|
||||||
@@ -768,55 +872,15 @@ int results_handler(UrlParams *params) {
|
|||||||
free(all_results[i]);
|
free(all_results[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < unique_count; i++) {
|
|
||||||
results_matrix[i][5] =
|
|
||||||
build_result_sources(source_masks[i], jobs, enabled_engine_count);
|
|
||||||
if (!results_matrix[i][5])
|
|
||||||
results_matrix[i][5] = strdup("");
|
|
||||||
}
|
|
||||||
|
|
||||||
context_set_array_of_arrays(&ctx, "results", results_matrix, unique_count,
|
context_set_array_of_arrays(&ctx, "results", results_matrix, unique_count,
|
||||||
results_inner_counts);
|
results_inner_counts);
|
||||||
|
|
||||||
char ***pager_matrix = NULL;
|
char ***pager_matrix = NULL;
|
||||||
int *pager_inner_counts = NULL;
|
int *pager_inner_counts = NULL;
|
||||||
int pager_count = 0;
|
SearchHrefData href_data = { .query = raw_query, .engine_id = selected_engine_id };
|
||||||
int pager_start = page <= 3 ? 1 : page - 2;
|
int pager_count = build_pagination(page, search_href_builder,
|
||||||
int pager_end = pager_start + PAGER_WINDOW_SIZE - 1;
|
&href_data, &pager_matrix,
|
||||||
|
&pager_inner_counts);
|
||||||
if (page > 3) {
|
|
||||||
char *first_href = build_search_href(raw_query, selected_engine_id, 1);
|
|
||||||
pager_count = add_link_to_collection(first_href, "First", "pagination-btn",
|
|
||||||
&pager_matrix, &pager_inner_counts,
|
|
||||||
pager_count);
|
|
||||||
free(first_href);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page > 1) {
|
|
||||||
char *prev_href =
|
|
||||||
build_search_href(raw_query, selected_engine_id, page - 1);
|
|
||||||
pager_count = add_link_to_collection(prev_href, "Prev", "pagination-btn",
|
|
||||||
&pager_matrix, &pager_inner_counts,
|
|
||||||
pager_count);
|
|
||||||
free(prev_href);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = pager_start; i <= pager_end; i++) {
|
|
||||||
char label[16];
|
|
||||||
snprintf(label, sizeof(label), "%d", i);
|
|
||||||
char *page_href = build_search_href(raw_query, selected_engine_id, i);
|
|
||||||
pager_count = add_link_to_collection(
|
|
||||||
page_href, label,
|
|
||||||
i == page ? "pagination-btn pagination-current" : "pagination-btn",
|
|
||||||
&pager_matrix, &pager_inner_counts, pager_count);
|
|
||||||
free(page_href);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *next_href = build_search_href(raw_query, selected_engine_id, page + 1);
|
|
||||||
pager_count = add_link_to_collection(next_href, "Next", "pagination-btn",
|
|
||||||
&pager_matrix, &pager_inner_counts,
|
|
||||||
pager_count);
|
|
||||||
free(next_href);
|
|
||||||
|
|
||||||
if (pager_count > 0) {
|
if (pager_count > 0) {
|
||||||
context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix,
|
context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix,
|
||||||
@@ -840,12 +904,10 @@ int results_handler(UrlParams *params) {
|
|||||||
for (int j = 0; j < RESULT_FIELD_COUNT; j++)
|
for (int j = 0; j < RESULT_FIELD_COUNT; j++)
|
||||||
free(results_matrix[i][j]);
|
free(results_matrix[i][j]);
|
||||||
free(results_matrix[i]);
|
free(results_matrix[i]);
|
||||||
free(seen_urls[i]);
|
|
||||||
}
|
}
|
||||||
free(seen_urls);
|
|
||||||
free(source_masks);
|
|
||||||
free(results_matrix);
|
free(results_matrix);
|
||||||
free(results_inner_counts);
|
free(results_inner_counts);
|
||||||
|
url_hash_free(&url_table);
|
||||||
} else {
|
} else {
|
||||||
char *html = render_template("results.html", &ctx);
|
char *html = render_template("results.html", &ctx);
|
||||||
if (html) {
|
if (html) {
|
||||||
@@ -858,6 +920,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) {
|
||||||
@@ -865,6 +929,12 @@ int results_handler(UrlParams *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(locale);
|
||||||
|
if (has_user_pref) {
|
||||||
|
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;
|
||||||
|
|||||||
140
src/Routes/Settings.c
Normal file
140
src/Routes/Settings.c
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#include "Settings.h"
|
||||||
|
#include "../Scraping/Scraping.h"
|
||||||
|
#include "../Utility/Utility.h"
|
||||||
|
#include <beaker.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int settings_handler(UrlParams *params) {
|
||||||
|
const char *query = "";
|
||||||
|
if (params) {
|
||||||
|
for (int i = 0; i < params->count; i++) {
|
||||||
|
if (strcmp(params->params[i].key, "q") == 0) {
|
||||||
|
query = params->params[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *theme = get_theme("system");
|
||||||
|
char *locale = get_locale(NULL);
|
||||||
|
|
||||||
|
LocaleInfo locales[32];
|
||||||
|
int locale_count = beaker_get_all_locales(locales, 32);
|
||||||
|
|
||||||
|
char **locale_data[32];
|
||||||
|
int inner_counts[32];
|
||||||
|
for (int i = 0; i < locale_count; i++) {
|
||||||
|
locale_data[i] = malloc(sizeof(char *) * 2);
|
||||||
|
locale_data[i][0] = locales[i].meta.id;
|
||||||
|
locale_data[i][1] = locales[i].meta.name;
|
||||||
|
inner_counts[i] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **user_engines = NULL;
|
||||||
|
int user_engine_count = 0;
|
||||||
|
int has_user_pref =
|
||||||
|
(get_user_engines(&user_engines, &user_engine_count) == 0);
|
||||||
|
|
||||||
|
int enabled_count = 0;
|
||||||
|
for (int i = 0; i < ENGINE_COUNT; i++) {
|
||||||
|
if (ENGINE_REGISTRY[i].enabled)
|
||||||
|
enabled_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ***engine_data = NULL;
|
||||||
|
int *engine_inner = NULL;
|
||||||
|
|
||||||
|
if (enabled_count > 0) {
|
||||||
|
engine_data = malloc(sizeof(char **) * enabled_count);
|
||||||
|
engine_inner = malloc(sizeof(int) * enabled_count);
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (int i = 0; i < ENGINE_COUNT; i++) {
|
||||||
|
if (!ENGINE_REGISTRY[i].enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int is_selected = !has_user_pref;
|
||||||
|
if (has_user_pref) {
|
||||||
|
for (int j = 0; j < user_engine_count; j++) {
|
||||||
|
if (strcmp(user_engines[j], ENGINE_REGISTRY[i].id) == 0) {
|
||||||
|
is_selected = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_data[idx] = malloc(sizeof(char *) * 3);
|
||||||
|
engine_data[idx][0] = (char *)ENGINE_REGISTRY[i].id;
|
||||||
|
engine_data[idx][1] = (char *)ENGINE_REGISTRY[i].name;
|
||||||
|
engine_data[idx][2] = is_selected ? "checked" : "";
|
||||||
|
engine_inner[idx] = 3;
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplateContext ctx = new_context();
|
||||||
|
beaker_set_locale(&ctx, locale);
|
||||||
|
context_set(&ctx, "query", query);
|
||||||
|
context_set(&ctx, "theme", theme);
|
||||||
|
context_set(&ctx, "locale", locale);
|
||||||
|
context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count,
|
||||||
|
inner_counts);
|
||||||
|
|
||||||
|
char **themes = NULL;
|
||||||
|
int themes_count = 0;
|
||||||
|
get_available_themes(&themes, &themes_count);
|
||||||
|
|
||||||
|
if (themes_count > 0) {
|
||||||
|
char ***theme_ptrs = malloc(sizeof(char **) * themes_count);
|
||||||
|
int *theme_inner = malloc(sizeof(int) * themes_count);
|
||||||
|
for (int i = 0; i < themes_count; i++) {
|
||||||
|
theme_ptrs[i] = malloc(sizeof(char *) * 2);
|
||||||
|
theme_ptrs[i][0] = themes[i];
|
||||||
|
theme_ptrs[i][1] = strdup(themes[i]);
|
||||||
|
if (theme_ptrs[i][1][0] >= 'a' && theme_ptrs[i][1][0] <= 'z') {
|
||||||
|
theme_ptrs[i][1][0] = theme_ptrs[i][1][0] - 'a' + 'A';
|
||||||
|
}
|
||||||
|
theme_inner[i] = 2;
|
||||||
|
}
|
||||||
|
context_set_array_of_arrays(&ctx, "themes", theme_ptrs, themes_count,
|
||||||
|
theme_inner);
|
||||||
|
for (int i = 0; i < themes_count; i++) {
|
||||||
|
free(theme_ptrs[i][1]);
|
||||||
|
free(theme_ptrs[i]);
|
||||||
|
}
|
||||||
|
free(theme_ptrs);
|
||||||
|
free(theme_inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled_count > 0) {
|
||||||
|
context_set_array_of_arrays(&ctx, "enabled_engines", engine_data,
|
||||||
|
enabled_count, engine_inner);
|
||||||
|
context_set(&ctx, "has_enabled_engines", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < locale_count; i++) {
|
||||||
|
free(locale_data[i]);
|
||||||
|
}
|
||||||
|
if (engine_data) {
|
||||||
|
for (int i = 0; i < enabled_count; i++)
|
||||||
|
free(engine_data[i]);
|
||||||
|
free(engine_data);
|
||||||
|
}
|
||||||
|
free(engine_inner);
|
||||||
|
if (has_user_pref) {
|
||||||
|
for (int i = 0; i < user_engine_count; i++)
|
||||||
|
free(user_engines[i]);
|
||||||
|
free(user_engines);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *rendered_html = render_template("settings.html", &ctx);
|
||||||
|
send_response(rendered_html);
|
||||||
|
|
||||||
|
free(rendered_html);
|
||||||
|
free(theme);
|
||||||
|
free(locale);
|
||||||
|
free_context(&ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
8
src/Routes/Settings.h
Normal file
8
src/Routes/Settings.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef SETTINGS_H
|
||||||
|
#define SETTINGS_H
|
||||||
|
|
||||||
|
#include <beaker.h>
|
||||||
|
|
||||||
|
int settings_handler(UrlParams *params);
|
||||||
|
|
||||||
|
#endif
|
||||||
66
src/Routes/SettingsSave.c
Normal file
66
src/Routes/SettingsSave.c
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include "SettingsSave.h"
|
||||||
|
#include "../Scraping/Scraping.h"
|
||||||
|
#include "../Utility/Utility.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX_ENGINE_IDS ENGINE_COUNT
|
||||||
|
|
||||||
|
int settings_save_handler(UrlParams *params) {
|
||||||
|
const char *theme = "";
|
||||||
|
const char *locale = "";
|
||||||
|
const char *query = "";
|
||||||
|
int engines_present = 0;
|
||||||
|
char selected_ids[ENGINE_COUNT][32];
|
||||||
|
int selected_count = 0;
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
for (int i = 0; i < params->count; i++) {
|
||||||
|
if (strcmp(params->params[i].key, "theme") == 0) {
|
||||||
|
theme = params->params[i].value;
|
||||||
|
} else if (strcmp(params->params[i].key, "locale") == 0) {
|
||||||
|
locale = params->params[i].value;
|
||||||
|
} else if (strcmp(params->params[i].key, "q") == 0) {
|
||||||
|
query = params->params[i].value;
|
||||||
|
} else if (strcmp(params->params[i].key, "engines_present") == 0) {
|
||||||
|
engines_present = 1;
|
||||||
|
} else if (strncmp(params->params[i].key, "engine_", 7) == 0 &&
|
||||||
|
strcmp(params->params[i].value, "1") == 0) {
|
||||||
|
const char *engine_id = params->params[i].key + 7;
|
||||||
|
if (engine_id[0] != '\0' && is_engine_id_enabled(engine_id) &&
|
||||||
|
selected_count < ENGINE_COUNT) {
|
||||||
|
strncpy(selected_ids[selected_count], engine_id,
|
||||||
|
sizeof(selected_ids[selected_count]) - 1);
|
||||||
|
selected_ids[selected_count][sizeof(selected_ids[selected_count]) - 1] =
|
||||||
|
'\0';
|
||||||
|
selected_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(theme) > 0) {
|
||||||
|
set_cookie("theme", theme, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false);
|
||||||
|
}
|
||||||
|
if (strlen(locale) > 0) {
|
||||||
|
set_cookie("locale", locale, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engines_present) {
|
||||||
|
char cookie_value[512];
|
||||||
|
cookie_value[0] = '\0';
|
||||||
|
for (int i = 0; i < selected_count; i++) {
|
||||||
|
if (i > 0)
|
||||||
|
strcat(cookie_value, ",");
|
||||||
|
strcat(cookie_value, selected_ids[i]);
|
||||||
|
}
|
||||||
|
set_cookie("engines", cookie_value, "Fri, 31 Dec 2038 23:59:59 GMT", "/",
|
||||||
|
false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
char redirect_url[512];
|
||||||
|
snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query);
|
||||||
|
send_redirect(redirect_url);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
8
src/Routes/SettingsSave.h
Normal file
8
src/Routes/SettingsSave.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef SETTINGS_SAVE_H
|
||||||
|
#define SETTINGS_SAVE_H
|
||||||
|
|
||||||
|
#include <beaker.h>
|
||||||
|
|
||||||
|
int settings_save_handler(UrlParams *params);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -28,113 +28,82 @@ static char *build_proxy_url(const char *image_url) {
|
|||||||
return proxy_url;
|
return proxy_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_image_node(xmlNodePtr node, ImageResult *result) {
|
static char *extract_json_string(const char *json, const char *key) {
|
||||||
xmlNodePtr img_node = NULL;
|
if (!json || !key)
|
||||||
xmlNodePtr tit_node = NULL;
|
return NULL;
|
||||||
xmlNodePtr des_node = NULL;
|
|
||||||
xmlNodePtr thumb_link = NULL;
|
|
||||||
|
|
||||||
for (xmlNodePtr child = node->children; child; child = child->next) {
|
char search_key[64];
|
||||||
if (child->type != XML_ELEMENT_NODE)
|
snprintf(search_key, sizeof(search_key), "\"%s\"", key);
|
||||||
continue;
|
|
||||||
|
|
||||||
if (xmlStrcmp(child->name, (const xmlChar *)"a") == 0) {
|
const char *key_pos = strstr(json, search_key);
|
||||||
xmlChar *class = xmlGetProp(child, (const xmlChar *)"class");
|
if (!key_pos)
|
||||||
if (class) {
|
return NULL;
|
||||||
if (xmlStrstr(class, (const xmlChar *)"thumb") != NULL) {
|
|
||||||
thumb_link = child;
|
const char *colon = strchr(key_pos + strlen(search_key), ':');
|
||||||
for (xmlNodePtr thumb_child = child->children; thumb_child;
|
if (!colon)
|
||||||
thumb_child = thumb_child->next) {
|
return NULL;
|
||||||
if (xmlStrcmp(thumb_child->name, (const xmlChar *)"div") == 0) {
|
|
||||||
xmlChar *div_class =
|
colon++;
|
||||||
xmlGetProp(thumb_child, (const xmlChar *)"class");
|
while (*colon == ' ' || *colon == '\t' || *colon == '\n' || *colon == '\r')
|
||||||
if (div_class &&
|
colon++;
|
||||||
xmlStrcmp(div_class, (const xmlChar *)"cico") == 0) {
|
|
||||||
for (xmlNodePtr cico_child = thumb_child->children; cico_child;
|
if (*colon != '"')
|
||||||
cico_child = cico_child->next) {
|
return NULL;
|
||||||
if (xmlStrcmp(cico_child->name, (const xmlChar *)"img") ==
|
colon++;
|
||||||
0) {
|
|
||||||
img_node = cico_child;
|
size_t len = 0;
|
||||||
break;
|
const char *start = colon;
|
||||||
}
|
while (*colon && *colon != '"') {
|
||||||
}
|
if (*colon == '\\' && *(colon + 1))
|
||||||
}
|
colon++;
|
||||||
if (div_class)
|
colon++;
|
||||||
xmlFree(div_class);
|
len++;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (xmlStrstr(class, (const xmlChar *)"tit") != NULL) {
|
|
||||||
tit_node = child;
|
|
||||||
}
|
|
||||||
xmlFree(class);
|
|
||||||
}
|
|
||||||
} else if (xmlStrcmp(child->name, (const xmlChar *)"div") == 0) {
|
|
||||||
xmlChar *class = xmlGetProp(child, (const xmlChar *)"class");
|
|
||||||
if (class && xmlStrcmp(class, (const xmlChar *)"meta") == 0) {
|
|
||||||
for (xmlNodePtr meta_child = child->children; meta_child;
|
|
||||||
meta_child = meta_child->next) {
|
|
||||||
if (xmlStrcmp(meta_child->name, (const xmlChar *)"div") == 0) {
|
|
||||||
xmlChar *div_class =
|
|
||||||
xmlGetProp(meta_child, (const xmlChar *)"class");
|
|
||||||
if (div_class) {
|
|
||||||
if (xmlStrcmp(div_class, (const xmlChar *)"des") == 0) {
|
|
||||||
des_node = meta_child;
|
|
||||||
}
|
|
||||||
xmlFree(div_class);
|
|
||||||
}
|
|
||||||
} else if (xmlStrcmp(meta_child->name, (const xmlChar *)"a") == 0) {
|
|
||||||
xmlChar *a_class = xmlGetProp(meta_child, (const xmlChar *)"class");
|
|
||||||
if (a_class && xmlStrstr(a_class, (const xmlChar *)"tit") != NULL) {
|
|
||||||
tit_node = meta_child;
|
|
||||||
}
|
|
||||||
if (a_class)
|
|
||||||
xmlFree(a_class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (class)
|
|
||||||
xmlFree(class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlChar *iurl =
|
char *result = malloc(len + 1);
|
||||||
img_node ? xmlGetProp(img_node, (const xmlChar *)"src") : NULL;
|
if (!result)
|
||||||
xmlChar *full_url =
|
return NULL;
|
||||||
thumb_link ? xmlGetProp(thumb_link, (const xmlChar *)"href") : NULL;
|
|
||||||
xmlChar *title = des_node ? xmlNodeGetContent(des_node)
|
|
||||||
: (tit_node ? xmlNodeGetContent(tit_node) : NULL);
|
|
||||||
xmlChar *rurl =
|
|
||||||
tit_node ? xmlGetProp(tit_node, (const xmlChar *)"href") : NULL;
|
|
||||||
|
|
||||||
if (!iurl || strlen((char *)iurl) == 0) {
|
colon = start;
|
||||||
if (iurl)
|
size_t i = 0;
|
||||||
xmlFree(iurl);
|
while (*colon && *colon != '"') {
|
||||||
if (title)
|
if (*colon == '\\' && *(colon + 1))
|
||||||
xmlFree(title);
|
colon++;
|
||||||
if (rurl)
|
result[i++] = *colon++;
|
||||||
xmlFree(rurl);
|
}
|
||||||
if (full_url)
|
result[i] = '\0';
|
||||||
xmlFree(full_url);
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_iusc_node(xmlNodePtr node, ImageResult *result) {
|
||||||
|
xmlChar *m_attr = xmlGetProp(node, (const xmlChar *)"m");
|
||||||
|
if (!m_attr)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
char *turl = extract_json_string((const char *)m_attr, "turl");
|
||||||
|
char *murl = extract_json_string((const char *)m_attr, "murl");
|
||||||
|
char *purl = extract_json_string((const char *)m_attr, "purl");
|
||||||
|
char *title = extract_json_string((const char *)m_attr, "t");
|
||||||
|
|
||||||
|
int ok = (turl != NULL && strlen(turl) > 0);
|
||||||
|
if (ok) {
|
||||||
|
char *proxy_url = build_proxy_url(turl);
|
||||||
|
result->thumbnail_url = proxy_url ? strdup(proxy_url) : strdup(turl);
|
||||||
|
free(proxy_url);
|
||||||
|
result->title =
|
||||||
|
title && strlen(title) > 0 ? strdup(title) : strdup("Image");
|
||||||
|
result->page_url = purl && strlen(purl) > 0 ? strdup(purl) : strdup("#");
|
||||||
|
result->full_url = murl && strlen(murl) > 0 ? strdup(murl) : strdup("#");
|
||||||
}
|
}
|
||||||
|
|
||||||
char *proxy_url = build_proxy_url((char *)iurl);
|
free(turl);
|
||||||
result->thumbnail_url = proxy_url ? strdup(proxy_url) : strdup((char *)iurl);
|
free(murl);
|
||||||
free(proxy_url);
|
free(purl);
|
||||||
result->title = strdup(title ? (char *)title : "Image");
|
free(title);
|
||||||
result->page_url = strdup(rurl ? (char *)rurl : "#");
|
|
||||||
result->full_url = strdup(full_url ? (char *)full_url : "#");
|
|
||||||
|
|
||||||
if (iurl)
|
xmlFree(m_attr);
|
||||||
xmlFree(iurl);
|
return ok;
|
||||||
if (title)
|
|
||||||
xmlFree(title);
|
|
||||||
if (rurl)
|
|
||||||
xmlFree(rurl);
|
|
||||||
if (full_url)
|
|
||||||
xmlFree(full_url);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int scrape_images(const char *query, int page, ImageResult **out_results,
|
int scrape_images(const char *query, int page, ImageResult **out_results,
|
||||||
@@ -157,13 +126,16 @@ int scrape_images(const char *query, int page, ImageResult **out_results,
|
|||||||
|
|
||||||
char url[BUFFER_SIZE_LARGE];
|
char url[BUFFER_SIZE_LARGE];
|
||||||
int first = (page - 1) * IMAGE_RESULTS_PER_PAGE + 1;
|
int first = (page - 1) * IMAGE_RESULTS_PER_PAGE + 1;
|
||||||
snprintf(url, sizeof(url), "%s?q=%s&first=%d", BING_IMAGE_URL, encoded_query,
|
snprintf(
|
||||||
first);
|
url, sizeof(url),
|
||||||
|
"https://www.bing.com/images/async?q=%s&async=content&first=%d&count=%d",
|
||||||
|
encoded_query, first, 35);
|
||||||
free(encoded_query);
|
free(encoded_query);
|
||||||
|
|
||||||
HttpResponse resp = http_get(
|
HttpResponse resp = http_get(
|
||||||
url,
|
url,
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
||||||
|
"like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||||
if (!resp.memory) {
|
if (!resp.memory) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -183,7 +155,7 @@ int scrape_images(const char *query, int page, ImageResult **out_results,
|
|||||||
}
|
}
|
||||||
|
|
||||||
xmlXPathObjectPtr xpathObj =
|
xmlXPathObjectPtr xpathObj =
|
||||||
xmlXPathEvalExpression((const xmlChar *)"//div[@class='item']", xpathCtx);
|
xmlXPathEvalExpression((const xmlChar *)"//a[@class='iusc']", xpathCtx);
|
||||||
|
|
||||||
if (!xpathObj || !xpathObj->nodesetval) {
|
if (!xpathObj || !xpathObj->nodesetval) {
|
||||||
if (xpathObj)
|
if (xpathObj)
|
||||||
@@ -210,7 +182,7 @@ int scrape_images(const char *query, int page, ImageResult **out_results,
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < nodes && count < IMAGE_RESULTS_PER_PAGE; i++) {
|
for (int i = 0; i < nodes && count < IMAGE_RESULTS_PER_PAGE; i++) {
|
||||||
xmlNodePtr node = xpathObj->nodesetval->nodeTab[i];
|
xmlNodePtr node = xpathObj->nodesetval->nodeTab[i];
|
||||||
if (parse_image_node(node, &results[count])) {
|
if (parse_iusc_node(node, &results[count])) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ static int response_is_startpage_captcha(const ScrapeJob *job,
|
|||||||
|
|
||||||
return response_contains(response, "<title>Startpage Captcha</title>") ||
|
return response_contains(response, "<title>Startpage Captcha</title>") ||
|
||||||
response_contains(response, "Startpage Captcha") ||
|
response_contains(response, "Startpage Captcha") ||
|
||||||
response_contains(response, "/static-pages-assets/page-data/captcha/");
|
response_contains(response, "/static-pages-assets/page-data/captcha/") ||
|
||||||
|
response_contains(response, ">Startpage Blocked</title>");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int response_looks_like_results_page(const ScrapeJob *job,
|
static int response_looks_like_results_page(const ScrapeJob *job,
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ char *pretty_display_url(const char *input) {
|
|||||||
strncpy(temp, start, sizeof(temp) - 1);
|
strncpy(temp, start, sizeof(temp) - 1);
|
||||||
temp[sizeof(temp) - 1] = '\0';
|
temp[sizeof(temp) - 1] = '\0';
|
||||||
|
|
||||||
|
char *query = strchr(temp, '?');
|
||||||
|
if (query) {
|
||||||
|
*query = '\0';
|
||||||
|
input_len = strlen(temp);
|
||||||
|
}
|
||||||
|
|
||||||
if (input_len > 0 && temp[input_len - 1] == '/') {
|
if (input_len > 0 && temp[input_len - 1] == '/') {
|
||||||
temp[input_len - 1] = '\0';
|
temp[input_len - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb,
|
|||||||
return realsize;
|
return realsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct curl_slist *build_http_headers(void) {
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
headers = curl_slist_append(
|
||||||
|
headers,
|
||||||
|
"Accept: "
|
||||||
|
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||||
|
headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.5");
|
||||||
|
headers = curl_slist_append(headers, "DNT: 1");
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
HttpResponse http_get(const char *url, const char *user_agent) {
|
HttpResponse http_get(const char *url, const char *user_agent) {
|
||||||
HttpResponse resp = {.memory = NULL, .size = 0, .capacity = 0};
|
HttpResponse resp = {.memory = NULL, .size = 0, .capacity = 0};
|
||||||
|
|
||||||
@@ -51,16 +62,24 @@ HttpResponse http_get(const char *url, const char *user_agent) {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct curl_slist *headers = build_http_headers();
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT,
|
curl_easy_setopt(curl, CURLOPT_USERAGENT,
|
||||||
user_agent ? user_agent : "libcurl-agent/1.0");
|
user_agent ? user_agent : "libcurl-agent/1.0");
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT_SECS);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, CURL_DNS_TIMEOUT_SECS);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
apply_proxy_settings(curl);
|
apply_proxy_settings(curl);
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
curl_slist_free_all(headers);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
|
|||||||
@@ -1,4 +1,87 @@
|
|||||||
#include "Utility.h"
|
#include "Utility.h"
|
||||||
|
#include "../Scraping/Scraping.h"
|
||||||
|
#include <beaker.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char global_default_locale[32] = "en_gb";
|
||||||
|
static char **themes_list = NULL;
|
||||||
|
static int themes_count = 0;
|
||||||
|
static int themes_initialized = 0;
|
||||||
|
|
||||||
|
void init_themes(const char *static_path) {
|
||||||
|
if (themes_initialized)
|
||||||
|
return;
|
||||||
|
themes_initialized = 1;
|
||||||
|
|
||||||
|
char themes_dir[512];
|
||||||
|
snprintf(themes_dir, sizeof(themes_dir), "%s/themes", static_path);
|
||||||
|
|
||||||
|
DIR *dir = opendir(themes_dir);
|
||||||
|
if (!dir)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int capacity = 4;
|
||||||
|
themes_list = malloc(sizeof(char *) * capacity);
|
||||||
|
themes_count = 0;
|
||||||
|
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
size_t len = strlen(entry->d_name);
|
||||||
|
if (len > 4 && strcmp(entry->d_name + len - 4, ".css") == 0) {
|
||||||
|
if (themes_count >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
themes_list = realloc(themes_list, sizeof(char *) * capacity);
|
||||||
|
}
|
||||||
|
themes_list[themes_count] = strndup(entry->d_name, len - 4);
|
||||||
|
themes_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
for (int i = 0; i < themes_count; i++) {
|
||||||
|
for (int j = i + 1; j < themes_count; j++) {
|
||||||
|
int priority_i = 0, priority_j = 0;
|
||||||
|
if (strcmp(themes_list[i], "system") == 0)
|
||||||
|
priority_i = 0;
|
||||||
|
else if (strcmp(themes_list[i], "light") == 0)
|
||||||
|
priority_i = 1;
|
||||||
|
else if (strcmp(themes_list[i], "dark") == 0)
|
||||||
|
priority_i = 2;
|
||||||
|
else
|
||||||
|
priority_i = 3;
|
||||||
|
if (strcmp(themes_list[j], "system") == 0)
|
||||||
|
priority_j = 0;
|
||||||
|
else if (strcmp(themes_list[j], "light") == 0)
|
||||||
|
priority_j = 1;
|
||||||
|
else if (strcmp(themes_list[j], "dark") == 0)
|
||||||
|
priority_j = 2;
|
||||||
|
else
|
||||||
|
priority_j = 3;
|
||||||
|
if (priority_i > priority_j ||
|
||||||
|
(priority_i == priority_j &&
|
||||||
|
strcmp(themes_list[i], themes_list[j]) > 0)) {
|
||||||
|
char *tmp = themes_list[i];
|
||||||
|
themes_list[i] = themes_list[j];
|
||||||
|
themes_list[j] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_available_themes(char ***out_themes, int *out_count) {
|
||||||
|
*out_themes = themes_list;
|
||||||
|
*out_count = themes_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_default_locale(const char *locale) {
|
||||||
|
if (locale && strlen(locale) > 0) {
|
||||||
|
strncpy(global_default_locale, locale, sizeof(global_default_locale) - 1);
|
||||||
|
global_default_locale[sizeof(global_default_locale) - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int hex_to_int(char c) {
|
int hex_to_int(char c) {
|
||||||
if (c >= '0' && c <= '9')
|
if (c >= '0' && c <= '9')
|
||||||
@@ -9,3 +92,212 @@ int hex_to_int(char c) {
|
|||||||
return c - 'A' + 10;
|
return c - 'A' + 10;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *get_theme(const char *default_theme) {
|
||||||
|
char *cookie = get_cookie("theme");
|
||||||
|
if (cookie && strlen(cookie) > 0) {
|
||||||
|
for (int i = 0; i < themes_count; i++) {
|
||||||
|
if (strcmp(cookie, themes_list[i]) == 0) {
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(cookie);
|
||||||
|
return strdup(default_theme && strlen(default_theme) > 0 ? default_theme
|
||||||
|
: "system");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *get_locale(const char *default_locale) {
|
||||||
|
char *cookie = get_cookie("locale");
|
||||||
|
if (cookie && beaker_get_locale_meta(cookie) != NULL) {
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
free(cookie);
|
||||||
|
const char *fallback =
|
||||||
|
default_locale ? default_locale : global_default_locale;
|
||||||
|
return strdup(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int engine_id_casecmp(const char *a, const char *b) {
|
||||||
|
while (*a && *b) {
|
||||||
|
char la = *a;
|
||||||
|
char lb = *b;
|
||||||
|
if (la >= 'A' && la <= 'Z')
|
||||||
|
la = la - 'A' + 'a';
|
||||||
|
if (lb >= 'A' && lb <= 'Z')
|
||||||
|
lb = lb - 'A' + 'a';
|
||||||
|
if (la != lb)
|
||||||
|
return 0;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_engine_id_enabled(const char *engine_id) {
|
||||||
|
if (!engine_id)
|
||||||
|
return 0;
|
||||||
|
for (int i = 0; i < ENGINE_COUNT; i++) {
|
||||||
|
if (ENGINE_REGISTRY[i].enabled &&
|
||||||
|
engine_id_casecmp(ENGINE_REGISTRY[i].id, engine_id)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_user_engines(char ***out_ids, int *out_count) {
|
||||||
|
*out_ids = NULL;
|
||||||
|
*out_count = 0;
|
||||||
|
|
||||||
|
char *cookie = get_cookie("engines");
|
||||||
|
if (!cookie || cookie[0] == '\0') {
|
||||||
|
free(cookie);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **ids = NULL;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
char *copy = strdup(cookie);
|
||||||
|
if (!copy) {
|
||||||
|
free(cookie);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *saveptr;
|
||||||
|
char *token = strtok_r(copy, ",", &saveptr);
|
||||||
|
while (token) {
|
||||||
|
while (*token == ' ' || *token == '\t')
|
||||||
|
token++;
|
||||||
|
|
||||||
|
if (token[0] != '\0' && is_engine_id_enabled(token)) {
|
||||||
|
char **new_ids = realloc(ids, sizeof(char *) * (count + 1));
|
||||||
|
if (new_ids) {
|
||||||
|
ids = new_ids;
|
||||||
|
ids[count] = strdup(token);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = strtok_r(NULL, ",", &saveptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(copy);
|
||||||
|
free(cookie);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
free(ids);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_ids = ids;
|
||||||
|
*out_count = count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int user_engines_contains(const char *engine_id, char **ids, int count) {
|
||||||
|
if (!engine_id || !ids)
|
||||||
|
return 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (engine_id_casecmp(ids[i], engine_id))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_link_to_collection(const char *href, const char *label,
|
||||||
|
const char *class_name, char ****collection,
|
||||||
|
int **inner_counts, int current_count) {
|
||||||
|
char ***old_collection = *collection;
|
||||||
|
int *old_inner_counts = *inner_counts;
|
||||||
|
char ***new_collection =
|
||||||
|
(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) {
|
||||||
|
memcpy(new_collection, *collection, sizeof(char **) * current_count);
|
||||||
|
}
|
||||||
|
if (*inner_counts && current_count > 0) {
|
||||||
|
memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
*collection = new_collection;
|
||||||
|
*inner_counts = new_inner_counts;
|
||||||
|
|
||||||
|
(*collection)[current_count] =
|
||||||
|
(char **)malloc(sizeof(char *) * LINK_FIELD_COUNT);
|
||||||
|
if (!(*collection)[current_count]) {
|
||||||
|
*collection = old_collection;
|
||||||
|
*inner_counts = old_inner_counts;
|
||||||
|
free(new_collection);
|
||||||
|
free(new_inner_counts);
|
||||||
|
return current_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*collection)[current_count][0] = strdup(href ? href : "");
|
||||||
|
(*collection)[current_count][1] = strdup(label ? label : "");
|
||||||
|
(*collection)[current_count][2] = strdup(class_name ? class_name : "");
|
||||||
|
|
||||||
|
if (!(*collection)[current_count][0] || !(*collection)[current_count][1] ||
|
||||||
|
!(*collection)[current_count][2]) {
|
||||||
|
free((*collection)[current_count][0]);
|
||||||
|
free((*collection)[current_count][1]);
|
||||||
|
free((*collection)[current_count][2]);
|
||||||
|
free((*collection)[current_count]);
|
||||||
|
*collection = old_collection;
|
||||||
|
*inner_counts = old_inner_counts;
|
||||||
|
free(new_collection);
|
||||||
|
free(new_inner_counts);
|
||||||
|
return current_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*inner_counts)[current_count] = LINK_FIELD_COUNT;
|
||||||
|
|
||||||
|
free(old_collection);
|
||||||
|
free(old_inner_counts);
|
||||||
|
return current_count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int build_pagination(int page, char *(*href_builder)(int page, void *data),
|
||||||
|
void *data, char ****out_matrix, int **out_inner_counts) {
|
||||||
|
enum { PAGER_WINDOW_SIZE = 5 };
|
||||||
|
|
||||||
|
*out_matrix = NULL;
|
||||||
|
*out_inner_counts = NULL;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
int pager_start = page <= 3 ? 1 : page - 2;
|
||||||
|
int pager_end = pager_start + PAGER_WINDOW_SIZE - 1;
|
||||||
|
|
||||||
|
if (page > 1) {
|
||||||
|
char *href = href_builder(page - 1, data);
|
||||||
|
count = add_link_to_collection(href, "←", "pagination-btn prev", out_matrix,
|
||||||
|
out_inner_counts, count);
|
||||||
|
free(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = pager_start; i <= pager_end; i++) {
|
||||||
|
char label[16];
|
||||||
|
snprintf(label, sizeof(label), "%d", i);
|
||||||
|
char *href = href_builder(i, data);
|
||||||
|
count = add_link_to_collection(
|
||||||
|
href, label,
|
||||||
|
i == page ? "pagination-btn pagination-current" : "pagination-btn",
|
||||||
|
out_matrix, out_inner_counts, count);
|
||||||
|
free(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *href = href_builder(page + 1, data);
|
||||||
|
count = add_link_to_collection(href, "→", "pagination-btn next", out_matrix,
|
||||||
|
out_inner_counts, count);
|
||||||
|
free(href);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,34 @@
|
|||||||
#ifndef UTILITY_H
|
#ifndef UTILITY_H
|
||||||
#define UTILITY_H
|
#define UTILITY_H
|
||||||
|
|
||||||
|
#include <beaker.h>
|
||||||
|
|
||||||
|
#ifndef VERSION
|
||||||
|
#define VERSION "unknown"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GIT_REMOTE
|
||||||
|
#define GIT_REMOTE "https://git.bwaaa.monster/omnisearch"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LINK_FIELD_COUNT 3
|
||||||
|
|
||||||
int hex_to_int(char c);
|
int hex_to_int(char c);
|
||||||
|
char *get_theme(const char *default_theme);
|
||||||
|
void init_themes(const char *static_path);
|
||||||
|
void get_available_themes(char ***out_themes, int *out_count);
|
||||||
|
void set_default_locale(const char *locale);
|
||||||
|
char *get_locale(const char *default_locale);
|
||||||
|
|
||||||
|
int is_engine_id_enabled(const char *engine_id);
|
||||||
|
int get_user_engines(char ***out_ids, int *out_count);
|
||||||
|
int user_engines_contains(const char *engine_id, char **ids, int count);
|
||||||
|
|
||||||
|
int add_link_to_collection(const char *href, const char *label,
|
||||||
|
const char *class_name, char ****collection,
|
||||||
|
int **inner_counts, int current_count);
|
||||||
|
|
||||||
|
int build_pagination(int page, char *(*href_builder)(int page, void *data),
|
||||||
|
void *data, char ****out_matrix, int **out_inner_counts);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
1291
static/main.css
1291
static/main.css
File diff suppressed because it is too large
Load Diff
4
static/settings.svg
Normal file
4
static/settings.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 829 B |
10
static/themes/catppuccin mocha.css
Normal file
10
static/themes/catppuccin mocha.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
:root {
|
||||||
|
--bg-main: #1e1e2e;
|
||||||
|
--bg-card: #313244;
|
||||||
|
--border: #6c7086;
|
||||||
|
--text-primary: #cdd6f4;
|
||||||
|
--text-secondary: #a6adc8;
|
||||||
|
--text-muted: #6c7086;
|
||||||
|
--accent: #cba6f7;
|
||||||
|
--accent-glow: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
10
static/themes/dark.css
Normal file
10
static/themes/dark.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
:root {
|
||||||
|
--bg-main: #121212;
|
||||||
|
--bg-card: #1e1e1e;
|
||||||
|
--border: #333333;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a0a0a0;
|
||||||
|
--text-muted: #d1d1d1;
|
||||||
|
--accent: #e2e2e2;
|
||||||
|
--accent-glow: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
10
static/themes/light.css
Normal file
10
static/themes/light.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
:root {
|
||||||
|
--bg-main: #ffffff;
|
||||||
|
--bg-card: #f8f9fa;
|
||||||
|
--border: #e0e0e0;
|
||||||
|
--text-primary: #1a1a1a;
|
||||||
|
--text-secondary: #5f6368;
|
||||||
|
--text-muted: #757575;
|
||||||
|
--accent: #202124;
|
||||||
|
--accent-glow: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
22
static/themes/system.css
Normal file
22
static/themes/system.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
:root {
|
||||||
|
--bg-main: #ffffff;
|
||||||
|
--bg-card: #f8f9fa;
|
||||||
|
--border: #e0e0e0;
|
||||||
|
--text-primary: #1a1a1a;
|
||||||
|
--text-secondary: #5f6368;
|
||||||
|
--text-muted: #757575;
|
||||||
|
--accent: #202124;
|
||||||
|
--accent-glow: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg-main: #121212;
|
||||||
|
--bg-card: #1e1e1e;
|
||||||
|
--border: #333333;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a0a0a0;
|
||||||
|
--text-muted: #d1d1d1;
|
||||||
|
--accent: #e2e2e2;
|
||||||
|
--accent-glow: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{{__locale_id}}" dir="{{__locale_direction}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
OmniSearch
|
OmniSearch
|
||||||
</title>
|
</title>
|
||||||
<link rel="stylesheet" href="static/main.css">
|
<link rel="stylesheet" href="static/main.css">
|
||||||
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
<link rel="stylesheet" href="static/themes/{{theme}}.css">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link rel="search"
|
<link rel="search"
|
||||||
type="application/opensearchdescription+xml"
|
type="application/opensearchdescription+xml"
|
||||||
title="OmniSearch" href="/opensearch.xml">
|
title="OmniSearch" href="/opensearch.xml">
|
||||||
@@ -18,23 +19,28 @@
|
|||||||
<div class="view-home">
|
<div class="view-home">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="hero-logo">
|
<h1 class="hero-logo">
|
||||||
Omni<span>Search</span>
|
<span>Void</span>arc
|
||||||
</h1>
|
</h1>
|
||||||
<form action="/search" class="home-search-form">
|
<form action="/search" class="home-search-form">
|
||||||
<div class="search-input-wrapper">
|
<div class="search-input-wrapper">
|
||||||
<input name="q" type="text" class="search-box" placeholder="Search the web..."
|
<input name="q" type="text" class="search-box" placeholder="{{l("search_placeholder")}}"
|
||||||
autofocus autocomplete="off">
|
autofocus autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="btn-primary">
|
<button type="submit" class="btn-primary">
|
||||||
Search
|
{{l("search_button")}}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="btnI" value="1" class="btn-secondary">
|
<button type="submit" name="btnI" value="1" class="btn-secondary">
|
||||||
Surprise me
|
{{l("surprise_me_button")}}
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a href="/settings" class="home-settings-btn" title="{{l("settings_tab")}}"></a>
|
||||||
|
<footer class="home-footer">
|
||||||
|
<a href="{{git_remote}}" class="version-link" target="_blank" rel="noopener noreferrer">{{version}}</a>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{{__locale_id}}" dir="{{__locale_direction}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@@ -7,27 +7,32 @@
|
|||||||
<title>
|
<title>
|
||||||
OmniSearch Images - {{query}}
|
OmniSearch Images - {{query}}
|
||||||
</title>
|
</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link rel="stylesheet" href="static/main.css">
|
<link rel="stylesheet" href="static/main.css">
|
||||||
|
<link rel="stylesheet" href="static/themes/{{theme}}.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="images-view">
|
<body class="images-view">
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<a href="/" class="logo-link"><h1>
|
||||||
Omni<span>Search</span>
|
Omni<span>Search</span>
|
||||||
</h1>
|
</h1></a>
|
||||||
<form action="/images" method="GET" class="search-form">
|
<form action="/images" method="GET" class="search-form">
|
||||||
<input name="q" autocomplete="off"="text" class="search-box" placeholder="Search for images..."
|
<input name="q" autocomplete="off"="text" class="search-box" placeholder="{{l("search_placeholder")}}"
|
||||||
value="{{query}}">
|
value="{{query}}">
|
||||||
</form>
|
</form>
|
||||||
|
<a href="/settings?q={{query}}" class="nav-settings-icon" title="{{l("settings_tab")}}"></a>
|
||||||
</header>
|
</header>
|
||||||
<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}}">
|
||||||
All
|
{{l("all_tab")}}
|
||||||
</a>
|
</a>
|
||||||
<a href="/images?q={{query}}" class="active">
|
<a href="/images?q={{query}}" class="active">
|
||||||
Images
|
{{l("images_tab")}}
|
||||||
|
</a>
|
||||||
|
<a href="/settings?q={{query}}" class="nav-settings-link">
|
||||||
|
{{l("settings_tab")}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -40,10 +45,10 @@
|
|||||||
<div class="image-overlay">
|
<div class="image-overlay">
|
||||||
<div class="overlay-buttons">
|
<div class="overlay-buttons">
|
||||||
<a href="{{img[3]}}" target="_blank" class="overlay-btn primary">
|
<a href="{{img[3]}}" target="_blank" class="overlay-btn primary">
|
||||||
View Image
|
{{l("view_image")}}
|
||||||
</a>
|
</a>
|
||||||
<a href="{{img[2]}}" target="_blank" class="overlay-btn secondary">
|
<a href="{{img[2]}}" target="_blank" class="overlay-btn secondary">
|
||||||
Visit Site
|
{{l("visit_site")}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,49 +64,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{{endfor}}
|
{{endfor}}
|
||||||
</div>
|
</div>
|
||||||
<nav class="pagination">
|
{{if exists pagination_links}}
|
||||||
<a class="pagination-btn prev" href="/images?q={{query}}&p={{prev_page}}">
|
<nav class="pagination">
|
||||||
←
|
{{for link in pagination_links}}
|
||||||
</a>
|
<a class="{{link[2]}}" href="{{link[0]}}">
|
||||||
|
{{link[1]}}
|
||||||
{{if two_prev_page != 0}}
|
</a>
|
||||||
<a class="pagination-btn prev" href="/images?q={{query}}&p={{two_prev_page}}">
|
{{endfor}}
|
||||||
{{two_prev_page}}
|
</nav>
|
||||||
</a>
|
{{endif}}
|
||||||
{{endif}}
|
|
||||||
|
|
||||||
{{if prev_page != 0}}
|
|
||||||
<a class="pagination-btn prev" href="/images?q={{query}}&p={{prev_page}}">
|
|
||||||
{{prev_page}}
|
|
||||||
</a>
|
|
||||||
{{endif}}
|
|
||||||
|
|
||||||
<a class="pagination-current" href="/images?q={{query}}&p={{page}}">
|
|
||||||
{{page}}
|
|
||||||
</a>
|
|
||||||
<a class="pagination-btn next" href="/images?q={{query}}&p={{next_page}}">
|
|
||||||
{{next_page}}
|
|
||||||
</a>
|
|
||||||
<a class="pagination-btn next" href="/images?q={{query}}&p={{two_next_page}}">
|
|
||||||
{{two_next_page}}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{{if prev_page == 0}}
|
|
||||||
<a class="pagination-btn prev" href="/images?q={{query}}&p=4">
|
|
||||||
4
|
|
||||||
</a>
|
|
||||||
{{endif}}
|
|
||||||
|
|
||||||
{{if two_prev_page == 0}}
|
|
||||||
<a class="pagination-btn prev" href="/images?q={{query}}&p=5">
|
|
||||||
5
|
|
||||||
</a>
|
|
||||||
{{endif}}
|
|
||||||
<a class="pagination-btn next" href="/images?q={{query}}&p={{next_page}}">
|
|
||||||
→
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
||||||
<ShortName>OmniSearch</ShortName>
|
<ShortName>OmniSearch</ShortName>
|
||||||
<Description>Lightweight metasearch engine</Description>
|
<Description>Lightweight metasearch engine</Description>
|
||||||
<Url type="text/html" method="get" template="{{domain}}/search?q={searchTerms}"/>
|
<Url type="text/html" method="get" template="{{scheme}}://{{domain}}/search?q={searchTerms}"/>
|
||||||
<InputEncoding>UTF-8</InputEncoding>
|
<InputEncoding>UTF-8</InputEncoding>
|
||||||
<OutputEncoding>UTF-8</OutputEncoding>
|
<OutputEncoding>UTF-8</OutputEncoding>
|
||||||
<moz:SearchForm>{{domain}}/</moz:SearchForm>
|
<moz:SearchForm>{{domain}}/</moz:SearchForm>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{{__locale_id}}" dir="{{__locale_direction}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
OmniSearch - {{query}}
|
OmniSearch - {{query}}
|
||||||
</title>
|
</title>
|
||||||
<link rel="stylesheet" href="static/main.css">
|
<link rel="stylesheet" href="static/main.css">
|
||||||
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
<link rel="stylesheet" href="static/themes/{{theme}}.css">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link rel="search"
|
<link rel="search"
|
||||||
type="application/opensearchdescription+xml"
|
type="application/opensearchdescription+xml"
|
||||||
title="OmniSearch" href="/opensearch.xml">
|
title="OmniSearch" href="/opensearch.xml">
|
||||||
@@ -16,22 +17,26 @@
|
|||||||
|
|
||||||
<body class="results-view">
|
<body class="results-view">
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<a href="/" class="logo-link"><h1>
|
||||||
Omni<span>Search</span>
|
VOID<span>ARC</span>
|
||||||
</h1>
|
</h1></a>
|
||||||
<form action="/search" method="GET" class="search-form">
|
<form action="/search" method="GET" class="search-form">
|
||||||
<input name="engine" type="hidden" value="{{selected_engine}}">
|
<input name="engine" type="hidden" value="{{selected_engine}}">
|
||||||
<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="{{l("search_placeholder")}}"
|
||||||
value="{{query}}">
|
value="{{query}}">
|
||||||
</form>
|
</form>
|
||||||
|
<a href="/settings?q={{query}}" class="nav-settings-icon" title="{{l("settings_tab")}}"></a>
|
||||||
</header>
|
</header>
|
||||||
<nav class="nav-tabs">
|
<nav class="nav-tabs">
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<a href="{{search_href}}" class="active">
|
<a href="{{search_href}}" class="active">
|
||||||
All
|
{{l("all_tab")}}
|
||||||
</a>
|
</a>
|
||||||
<a href="/images?q={{query}}">
|
<a href="/images?q={{query}}">
|
||||||
Images
|
{{l("images_tab")}}
|
||||||
|
</a>
|
||||||
|
<a href="/settings?q={{query}}" class="nav-settings-link">
|
||||||
|
{{l("settings_tab")}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -68,13 +73,11 @@
|
|||||||
<div class="result">
|
<div class="result">
|
||||||
<div class="result-header">
|
<div class="result-header">
|
||||||
<div class="result-favicon"
|
<div class="result-favicon"
|
||||||
style="background-image: url('https://{{result[4]}}/favicon.ico'), url('https://{{result[4]}}/favicon.png');">
|
style="background-image: url('/proxy?url=https://{{result[4]}}/favicon.ico'), url('/proxy?url=https://{{result[4]}}/favicon.png');">
|
||||||
</div>
|
</div>
|
||||||
<span class="url">
|
<span class="url">
|
||||||
{{result[1]}}
|
{{result[1]}}
|
||||||
</span>
|
</span>
|
||||||
<span class="result-sources">
|
|
||||||
{{result[5]}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{result[0]}}">
|
<a href="{{result[0]}}">
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
{{result[3]}}
|
{{result[3]}}
|
||||||
</p>
|
</p>
|
||||||
<span>
|
<span>
|
||||||
<a class="cached" href="https://web.archive.org/web/{{result[0]|safe}}">View Cached</a>
|
<a class="cached" href="https://web.archive.org/web/{{result[0]|safe}}">{{l("view_cached")}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{endfor}}
|
{{endfor}}
|
||||||
@@ -112,10 +115,12 @@
|
|||||||
<img src="{{info[1]}}" alt="{{info[0]}}" class="infobox-image">
|
<img src="{{info[1]}}" alt="{{info[0]}}" class="infobox-image">
|
||||||
<div class="infobox-content"> <p class="infobox-summary">
|
<div class="infobox-content"> <p class="infobox-summary">
|
||||||
{{info[2]|safe}}
|
{{info[2]|safe}}
|
||||||
</p>
|
</p>
|
||||||
|
{{if info[3] != "#"}}
|
||||||
<a class="read-more" href="{{info[3]}}">
|
<a class="read-more" href="{{info[3]}}">
|
||||||
Read More
|
{{l("read_more")}}
|
||||||
</a>
|
</a>
|
||||||
|
{{endif}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
99
templates/settings.html
Normal file
99
templates/settings.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{__locale_id}}" dir="{{__locale_direction}}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
||||||
|
<title>
|
||||||
|
OmniSearch - {{l("settings_title")}}
|
||||||
|
</title>
|
||||||
|
<link rel="stylesheet" href="static/main.css">
|
||||||
|
<link rel="stylesheet" href="static/themes/{{theme}}.css">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
<link rel="search"
|
||||||
|
type="application/opensearchdescription+xml"
|
||||||
|
title="OmniSearch" href="/opensearch.xml">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="settings-view">
|
||||||
|
<header>
|
||||||
|
<a href="/" class="logo-link"><h1>
|
||||||
|
Omni<span>Search</span>
|
||||||
|
</h1></a>
|
||||||
|
{{if query != ""}}
|
||||||
|
<form action="/search" method="GET" class="search-form">
|
||||||
|
<input name="q" type="text" class="search-box" autocomplete="off" placeholder="{{l("search_placeholder")}}"
|
||||||
|
value="{{query}}">
|
||||||
|
</form>
|
||||||
|
{{endif}}
|
||||||
|
{{if query != ""}}
|
||||||
|
<a href="/search?q={{query}}" class="nav-settings-icon active" title="{{l("settings_tab")}}"></a>
|
||||||
|
{{else}}
|
||||||
|
<a href="/" class="nav-settings-icon active" title="{{l("settings_tab")}}"></a>
|
||||||
|
{{endif}}
|
||||||
|
</header>
|
||||||
|
{{if query != ""}}
|
||||||
|
<nav class="nav-tabs">
|
||||||
|
<div class="nav-container">
|
||||||
|
<a href="/search?q={{query}}">
|
||||||
|
{{l("all_tab")}}
|
||||||
|
</a>
|
||||||
|
<a href="/images?q={{query}}">
|
||||||
|
{{l("images_tab")}}
|
||||||
|
</a>
|
||||||
|
<a href="/settings" class="active nav-settings-link">
|
||||||
|
{{l("settings_tab")}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{{endif}}
|
||||||
|
<div class="settings-layout">
|
||||||
|
<main class="settings-container">
|
||||||
|
<form action="/save_settings" method="GET">
|
||||||
|
<input type="hidden" name="q" value="{{query}}">
|
||||||
|
<section class="settings-section">
|
||||||
|
<h3 class="settings-section-title">{{l("theme_label")}}</h3>
|
||||||
|
<p class="settings-section-desc">{{l("theme_desc")}}</p>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label class="settings-label" for="theme">{{l("theme_label")}}</label>
|
||||||
|
<select id="theme" name="theme" class="settings-select">
|
||||||
|
{{for t in themes}}
|
||||||
|
<option value="{{t[0]}}" {{if theme == t[0]}}selected{{endif}}>{{t[1]}}</option>
|
||||||
|
{{endfor}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="settings-section">
|
||||||
|
<h3 class="settings-section-title">{{l("language_label")}}</h3>
|
||||||
|
<p class="settings-section-desc">{{l("language_desc")}}</p>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label class="settings-label" for="locale">{{l("display_language_label")}}</label>
|
||||||
|
<select id="locale" name="locale" class="settings-select">
|
||||||
|
{{for loc in locales}}
|
||||||
|
<option value="{{loc[0]}}" {{if __locale_id == loc[0]}}selected{{endif}}>{{loc[1]}}</option>
|
||||||
|
{{endfor}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{if has_enabled_engines}}
|
||||||
|
<section class="settings-section">
|
||||||
|
<h3 class="settings-section-title">{{l("engines_label")}}</h3>
|
||||||
|
<p class="settings-section-desc">{{l("engines_desc")}}</p>
|
||||||
|
<input type="hidden" name="engines_present" value="1">
|
||||||
|
{{for eng in enabled_engines}}
|
||||||
|
<div class="settings-field">
|
||||||
|
<label class="settings-label" for="engine_{{eng[0]}}">{{eng[1]}}</label>
|
||||||
|
<input type="checkbox" id="engine_{{eng[0]}}" name="engine_{{eng[0]}}" value="1" class="settings-checkbox" {{eng[2]}}>
|
||||||
|
</div>
|
||||||
|
{{endfor}}
|
||||||
|
</section>
|
||||||
|
{{endif}}
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button type="submit" class="btn-primary">{{l("save_settings_button")}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user