eh
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
fastfetch_server
|
||||
341
ansi2html.h
Normal file
341
ansi2html.h
Normal file
@@ -0,0 +1,341 @@
|
||||
// usage:
|
||||
// #define VTERM_ROWS 50
|
||||
// #define VTERM_COLS 200
|
||||
// #include "ansi2html.h
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ansislop_t {
|
||||
int fg, bg;
|
||||
unsigned char bold;
|
||||
unsigned char italic;
|
||||
unsigned char underline;
|
||||
unsigned char dim;
|
||||
unsigned char blink;
|
||||
unsigned char strikethrough;
|
||||
// 2 wasted bytes 🥀🥀🥀🥀🥀
|
||||
} ansislop_t;
|
||||
|
||||
typedef struct {
|
||||
char utf8[5];
|
||||
ansislop_t slop;
|
||||
} cell_t;
|
||||
|
||||
typedef struct {
|
||||
cell_t cells[VTERM_ROWS][VTERM_COLS];
|
||||
int row, col;
|
||||
ansislop_t slop;
|
||||
int last_row, last_col;
|
||||
} vterm_t;
|
||||
|
||||
static void vterm_put(vterm_t* vt, const char* utf8, const int len) {
|
||||
while (vt->col < 0) vt->col += VTERM_COLS;
|
||||
while (vt->row < 0) vt->col += VTERM_ROWS;
|
||||
while (vt->row >= VTERM_ROWS) vt->row -= VTERM_ROWS;
|
||||
while (vt->col >= VTERM_COLS) vt->col -= VTERM_COLS;
|
||||
cell_t* c = &vt->cells[vt->row][vt->col];
|
||||
if (vt->row > vt->last_row) vt->last_row = vt->row;
|
||||
if (vt->col > vt->last_col) vt->last_col = vt->col;
|
||||
memset(c->utf8, 0, 5);
|
||||
memcpy(c->utf8, utf8, len);
|
||||
c->slop = vt->slop;
|
||||
vt->col++;
|
||||
}
|
||||
|
||||
static const char* ansi16[16] = {
|
||||
"#241f31", "#c01c28", "#2ec27e", "#f5c211", "#1e78e4", "#9841bb", "#0ab9dc", "#c0bfbc",
|
||||
"#5e5c64", "#ed333b", "#57e389", "#f8e45c", "#51a1ff", "#c061cb", "#4fd2fd", "#f6f5f4"
|
||||
};
|
||||
|
||||
static int color_css(char* buf, size_t size, int32_t color, int is_bg) {
|
||||
if (color == -1) {
|
||||
if (is_bg) strcpy(buf, ansi16[0]);
|
||||
else strcpy(buf, ansi16[15]);
|
||||
return 7;
|
||||
}
|
||||
if ((color & 0xff) < 16) {
|
||||
strcpy(buf, ansi16[color & 0xff]);
|
||||
return 7;
|
||||
}
|
||||
|
||||
int r, g, b;
|
||||
|
||||
if (color & 0x1000000) {
|
||||
r = (color >> 16) & 0xff;
|
||||
g = (color >> 8) & 0xff;
|
||||
b = color & 0xff;
|
||||
}
|
||||
else if ((color & 0xff) < 232) {
|
||||
int i = (color & 0xff) - 16;
|
||||
b = (i % 6) * 51; i /= 6;
|
||||
g = (i % 6) * 51; i /= 6;
|
||||
r = i * 51;
|
||||
}
|
||||
else {
|
||||
r = g = b = 8 + ((color & 0xff) - 232) * 10;
|
||||
}
|
||||
return snprintf(buf, size, "#%02x%02x%02x", r, g, b);
|
||||
}
|
||||
|
||||
static char* vterm_render(const vterm_t* vt) {
|
||||
const size_t cap =
|
||||
+ (size_t)(vt->last_row + 1) * 4
|
||||
+ (size_t)(vt->last_row + 1) * (vt->last_col + 1) *
|
||||
(sizeof("<span class=\" b i u s d k\" style=\"color:#xxxxxx;background:#xxxxxx;\">&</span>") - 1);
|
||||
char* html = malloc(cap);
|
||||
size_t pos = 0;
|
||||
|
||||
#define APPEND(...) do { \
|
||||
int _n = snprintf(html + pos, cap - pos, __VA_ARGS__); \
|
||||
if (_n > 0) pos += (size_t)_n; \
|
||||
} while (0)
|
||||
|
||||
APPEND(
|
||||
"<!doctype html>"
|
||||
"<html lang=\"en\">"
|
||||
"<head>\n"
|
||||
"<meta charset=\"UTF-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
|
||||
"<title>fastfetch_server</title>"
|
||||
"<style>"
|
||||
"* { box-sizing: border-box; margin: 0; padding: 0; }"
|
||||
"html, body {"
|
||||
"background: #000000;"
|
||||
"font-family: 'Adwaita Mono', monospace;"
|
||||
"font-size: 24px;"
|
||||
//"display: flex; justify-content: center;"
|
||||
"}"
|
||||
".b { font-weight: bold; }"
|
||||
".i { font-style: italic; }"
|
||||
".u { text-decoration: underline; }"
|
||||
".s { text-decoration: line-through; }"
|
||||
".d { opacity: 0.5; }"
|
||||
".k { animation: blink 1s step-start infinite; }"
|
||||
"@keyframes blink { 50%% { opacity: 0; } }"
|
||||
"</style>"
|
||||
"</head>"
|
||||
"<body><pre>"
|
||||
);
|
||||
|
||||
for (int r = 0; r <= vt->last_row; r++) {
|
||||
ansislop_t slop;
|
||||
for (int c = 0; c <= vt->last_col; c++) {
|
||||
const cell_t* cell = &vt->cells[r][c];
|
||||
if (c == 0 || (memcmp(&cell->slop, &slop, sizeof(ansislop_t)) != 0)) {
|
||||
slop = cell->slop;
|
||||
if (c != 0) {
|
||||
APPEND("</span>");
|
||||
}
|
||||
struct {
|
||||
char data[128];
|
||||
size_t size;
|
||||
} style = {{0}, 0};
|
||||
|
||||
char fg[16];
|
||||
color_css(fg, sizeof(fg), slop.fg, 0);
|
||||
style.size += snprintf(style.data + style.size, sizeof(style.data) - style.size, "color:%s;", fg);
|
||||
|
||||
char bg[16];
|
||||
color_css(bg, sizeof(bg), slop.bg, 1);
|
||||
style.size += snprintf(style.data + style.size, sizeof(style.data) - style.size, "background:%s;", bg);
|
||||
|
||||
struct {
|
||||
char data[128];
|
||||
size_t size;
|
||||
} cls = {{0}, 0};
|
||||
|
||||
if (slop.bold) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " b");
|
||||
if (slop.italic) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " i");
|
||||
if (slop.underline) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " u");
|
||||
if (slop.strikethrough) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " s");
|
||||
if (slop.dim) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " d");
|
||||
if (slop.blink) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " k");
|
||||
|
||||
const int has_style = style.data[0] != '\0';
|
||||
const int has_cls = cls.data[0] != '\0';
|
||||
|
||||
APPEND("<span");
|
||||
if (has_cls) APPEND(" class=\"%s\"", cls.data + 1);
|
||||
if (has_style) APPEND(" style=\"%s\"", style.data);
|
||||
APPEND(">");
|
||||
}
|
||||
const char* ch = cell->utf8[0] ? cell->utf8 : " ";
|
||||
for (const char* q = ch; *q; q++) {
|
||||
if (*q == '<') APPEND("<");
|
||||
else if (*q == '>') APPEND(">");
|
||||
else if (*q == '&') APPEND("&");
|
||||
else { html[pos++] = *q; }
|
||||
}
|
||||
}
|
||||
APPEND("</span>");
|
||||
APPEND("<br>");
|
||||
}
|
||||
|
||||
APPEND("</pre></body></html>\n");
|
||||
#undef APPEND
|
||||
return html;
|
||||
}
|
||||
|
||||
static const char* vterm_csi(vterm_t* vt, const char* p) {
|
||||
struct {
|
||||
int data[32];
|
||||
int size;
|
||||
} params = {{0}, 0};
|
||||
|
||||
while (*p < 0x40 || *p == ';') {
|
||||
if (*p == ';') {
|
||||
params.size++;
|
||||
if (params.size == 32) params.size = 31;
|
||||
}
|
||||
else if ('0' <= *p && *p <= '9') {
|
||||
params.data[params.size] = params.data[params.size] * 10 + (*p - '0');
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
params.size++;
|
||||
// en.wikipedia.org/wiki/ANSI_escape_code <3
|
||||
switch (*p++) {
|
||||
case 'A':
|
||||
vt->row -= params.data[0] > 0 ? params.data[0] : 1;
|
||||
break;
|
||||
case 'B':
|
||||
vt->row += params.data[0] > 0 ? params.data[0] : 1;
|
||||
break;
|
||||
case 'C':
|
||||
vt->col += params.data[0] > 0 ? params.data[0] : 1;
|
||||
break;
|
||||
case 'D':
|
||||
vt->col -= params.data[0] > 0 ? params.data[0] : 1;
|
||||
break;
|
||||
case 'E':
|
||||
vt->row += params.data[0] > 0 ? params.data[0] : 1;
|
||||
vt->col = 0;
|
||||
break;
|
||||
case 'F':
|
||||
vt->row -= params.data[0] > 0 ? params.data[0] : 1;
|
||||
vt->col = 0;
|
||||
break;
|
||||
case 'G':
|
||||
vt->col = (params.data[0] > 0 ? params.data[0] : 1) - 1;
|
||||
break;
|
||||
case 'H':
|
||||
case 'f':
|
||||
vt->row = (params.data[0] > 0 ? params.data[0] : 1) - 1;
|
||||
vt->col = (params.data[1] > 0 ? params.data[1] : 1) - 1;
|
||||
break;
|
||||
case 'J':
|
||||
case 'K':
|
||||
// no
|
||||
break;
|
||||
case 'm':
|
||||
for (int i = 0; i < params.size; i++) {
|
||||
const int p0 = params.data[i];
|
||||
if (p0 == 0) {
|
||||
vt->slop.fg = vt->slop.bg = -1;
|
||||
vt->slop.bold = vt->slop.italic = vt->slop.underline = vt->slop.dim = vt->slop.blink = vt->slop.strikethrough = 0;
|
||||
}
|
||||
else if (p0 == 1) vt->slop.bold = 1;
|
||||
else if (p0 == 2) vt->slop.dim = 1;
|
||||
else if (p0 == 3) vt->slop.italic = 1;
|
||||
else if (p0 == 4) vt->slop.underline = 1;
|
||||
else if (p0 == 5 || p0 == 6) vt->slop.blink = 1;
|
||||
else if (p0 == 9) vt->slop.strikethrough = 1;
|
||||
else if (p0 == 22) vt->slop.bold = vt->slop.dim = 0;
|
||||
else if (p0 == 23) vt->slop.italic = 0;
|
||||
else if (p0 == 24) vt->slop.underline = 0;
|
||||
else if (p0 == 25) vt->slop.blink = 0;
|
||||
else if (p0 == 29) vt->slop.strikethrough = 0;
|
||||
else if (p0 >= 30 && p0 <= 37) vt->slop.fg = p0 - 30;
|
||||
else if (p0 == 38 && i + 2 < params.size && params.data[i + 1] == 5) {
|
||||
vt->slop.fg = params.data[i + 2];
|
||||
i += 2;
|
||||
}
|
||||
else if (p0 == 38 && i + 4 < params.size && params.data[i + 1] == 2) {
|
||||
vt->slop.fg = 0x1000000 | (params.data[i + 2] << 16) | (params.data[i + 3] << 8) | params.data[i + 4];
|
||||
i += 4;
|
||||
}
|
||||
else if (p0 == 39) vt->slop.fg = -1;
|
||||
else if (p0 >= 40 && p0 <= 47) vt->slop.bg = p0 - 40;
|
||||
else if (p0 == 48 && i + 2 < params.size && params.data[i + 1] == 5) {
|
||||
vt->slop.bg = params.data[i + 2];
|
||||
i += 2;
|
||||
}
|
||||
else if (p0 == 48 && i + 4 < params.size && params.data[i + 1] == 2) {
|
||||
vt->slop.bg = 0x1000000 | (params.data[i + 2] << 16) | (params.data[i + 3] << 8) | params.data[i + 4];
|
||||
i += 4;
|
||||
}
|
||||
else if (p0 == 49) vt->slop.bg = -1;
|
||||
else if (p0 >= 90 && p0 <= 97) vt->slop.fg = p0 - 90 + 8;
|
||||
else if (p0 >= 100 && p0 <= 107) vt->slop.bg = p0 - 100 + 8;
|
||||
}
|
||||
break;
|
||||
default: printf("unsupported escape: %c\n", *p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static int utf8_len(const unsigned char c) {
|
||||
if (c < 0x80) return 1;
|
||||
if ((c & 0xe0) == 0xc0) return 2;
|
||||
if ((c & 0xf0) == 0xe0) return 3;
|
||||
if ((c & 0xf8) == 0xf0) return 4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char* ansi2html(const char* ansislop) {
|
||||
vterm_t vt = {0};
|
||||
|
||||
vt.slop.fg = vt.slop.bg = -1;
|
||||
for (int r = 0; r < VTERM_ROWS; r++) {
|
||||
for (int c = 0; c < VTERM_COLS; c++) {
|
||||
vt.cells[r][c].slop.fg = vt.cells[r][c].slop.bg = -1;
|
||||
vt.cells[r][c].utf8[0] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
for (const char* p = ansislop; *p;) {
|
||||
if (*p == '\e') {
|
||||
p++;
|
||||
if (*p == '[') {
|
||||
p = vterm_csi(&vt, p + 1);
|
||||
}
|
||||
else if (*p == '(') {
|
||||
p += 2;
|
||||
}
|
||||
else {
|
||||
while (0x40 > *p || *p > 0x7e) p++;
|
||||
}
|
||||
}
|
||||
else if (*p == '\r') {
|
||||
p++;
|
||||
vt.col = 0;
|
||||
}
|
||||
else if (*p == '\n') {
|
||||
p++;
|
||||
vt.col = 0;
|
||||
vt.row++;
|
||||
}
|
||||
else if (*p == '\t') {
|
||||
p++;
|
||||
vt.col = (vt.col + 8) & ~7;
|
||||
if (vt.col >= VTERM_COLS) {
|
||||
vt.col = 0;
|
||||
vt.row++;
|
||||
}
|
||||
}
|
||||
else if ((uint8_t)*p < 0x20) {
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
const int l = utf8_len((unsigned char)*p);
|
||||
vterm_put(&vt, p, l);
|
||||
p += l;
|
||||
}
|
||||
}
|
||||
return vterm_render(&vt);
|
||||
}
|
||||
2
build.sh
Executable file
2
build.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
cc fastfetch_server.c -o fastfetch_server -O3 -lpthread
|
||||
235
fastfetch_server.c
Normal file
235
fastfetch_server.c
Normal file
@@ -0,0 +1,235 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define VTERM_ROWS 50
|
||||
#define VTERM_COLS 200
|
||||
#include "ansi2html.h"
|
||||
|
||||
#include "stdint.h"
|
||||
#define RATELIMIT_THREADSAFE
|
||||
typedef uint8_t ratelimit_table_type[16];
|
||||
#include "ratelimit.h"
|
||||
|
||||
typedef struct pthread_input_t {
|
||||
int client_fd;
|
||||
float window;
|
||||
int rate_limit;
|
||||
const char* command;
|
||||
} pthread_input_t;
|
||||
|
||||
#define CURL_HEADERS(status) \
|
||||
"HTTP/1.1 " status "\r\n" \
|
||||
"Content-Type: text/plain\r\n" \
|
||||
"Content-Length: %zu\r\n" \
|
||||
"Connection: close\r\n" \
|
||||
"\r\n" \
|
||||
"%s"
|
||||
|
||||
#define BROWSER_HEADERS(status) \
|
||||
"HTTP/1.1 " status "\r\n" \
|
||||
"Content-Type: text/html; charset=utf-8\r\n" \
|
||||
"Content-Length: %zu\r\n" \
|
||||
"Connection: close\r\n" \
|
||||
"\r\n" \
|
||||
"%s"
|
||||
|
||||
#define FS_HELP \
|
||||
"usage:\n" \
|
||||
"\tfastfetch_server --addr 127.0.01 (optional)\n" \
|
||||
"\t --window 60 (optional)\n" \
|
||||
"\t --ratelimit 5 (optional)\n" \
|
||||
"\t --hyfetch (optional)\n" \
|
||||
"\t port (optional)\n" \
|
||||
"\t --addr expects an address in an IPv4 or IPv6 format\n" \
|
||||
"\t --window sets a rate limit delay in seconds\n" \
|
||||
"\t --ratelimit sets the rate limit amount\n" \
|
||||
"\t --hyfetch uses hyfetch with fastfetch backend instead of fastfetch\n" \
|
||||
"\t port specifies the port, 80 by default, might need superuser for\n" \
|
||||
"\t ports below 1024\n" \
|
||||
"so far tested on:\n" \
|
||||
"\t GNU/Linux on x86/x86_64 with glibc/musl\n"
|
||||
|
||||
static void send_fastfetch(int client_fd, const char* command, int is_browser) {
|
||||
char fastfetch[16384];
|
||||
FILE* fp = popen(command, "r");
|
||||
const size_t n = fp ? fread(fastfetch, sizeof(fastfetch[0]), sizeof(fastfetch) - 1, fp) : 0;
|
||||
if (fp) pclose(fp);
|
||||
fastfetch[n] = '\0';
|
||||
|
||||
if (!is_browser) {
|
||||
dprintf(client_fd, CURL_HEADERS("200 OK"), n, fastfetch);
|
||||
}
|
||||
else {
|
||||
char* html = ansi2html(fastfetch);
|
||||
dprintf(client_fd, BROWSER_HEADERS("200 OK"), strlen(html), html);
|
||||
free(html);
|
||||
}
|
||||
}
|
||||
|
||||
static void* handle_client(void* _arg) {
|
||||
pthread_input_t* arg = _arg;
|
||||
int client_fd = arg->client_fd;
|
||||
float window = arg->window;
|
||||
int rate_limit = arg->rate_limit;
|
||||
const char* command = arg->command;
|
||||
|
||||
struct sockaddr_in6 peer;
|
||||
socklen_t peerlen = sizeof(peer);
|
||||
getpeername(client_fd, (struct sockaddr*)&peer, &peerlen);
|
||||
|
||||
if (rate_limited(peer.sin6_addr.s6_addr, window, rate_limit)) {
|
||||
dprintf(client_fd, CURL_HEADERS("429 Too Many Requests"), 19, "Too Many Requests\r\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char what_to_do[256] = {0};
|
||||
int is_browser = 0;
|
||||
{
|
||||
struct {
|
||||
char buf[8192];
|
||||
size_t len;
|
||||
} req = {.len = 0};
|
||||
|
||||
while (req.len < sizeof(req.buf) - 1) {
|
||||
const ssize_t n = recv(client_fd, req.buf + req.len, sizeof(req.buf) - 1 - req.len, 0);
|
||||
if (n <= 0) break;
|
||||
req.len += n;
|
||||
req.buf[req.len] = '\0';
|
||||
const char* body = memmem(req.buf, req.len, "\r\n\r\n", 4);
|
||||
|
||||
if (body) {
|
||||
for (const char* p = req.buf; p < body;) {
|
||||
const char* nl = memchr(p, '\n', body - p);
|
||||
const size_t line_len = nl ? (size_t)(nl - p) : (size_t)(body - p);
|
||||
|
||||
if (line_len >= 14 && strncasecmp(p, "get /", 5) == 0) {
|
||||
const char* protocol = memmem(p, line_len, " HTTP/1.1", 9);
|
||||
if (!protocol) goto continue_label;
|
||||
const size_t get_str_len = protocol - p - 5;
|
||||
if (get_str_len >= sizeof(what_to_do)) goto cleanup;
|
||||
memcpy(what_to_do, p + 5, get_str_len);
|
||||
}
|
||||
else if (line_len >= 12 && strncasecmp(p, "user-agent:", 11) == 0) {
|
||||
const char* ua = p + 11;
|
||||
while (*ua == ' ' || *ua == '\t') ua++;
|
||||
is_browser = strncmp(ua, "Mozilla", 7) == 0 && (
|
||||
memmem(ua, line_len, "Chrome", 6) ||
|
||||
memmem(ua, line_len, "Firefox", 7) ||
|
||||
memmem(ua, line_len, "Safari", 6));
|
||||
goto we_now_know_if_we_got_a_browser;
|
||||
}
|
||||
continue_label:
|
||||
p = nl ? nl + 1 : body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
we_now_know_if_we_got_a_browser:
|
||||
if (what_to_do[0] == '\0') {
|
||||
send_fastfetch(client_fd, command, is_browser);
|
||||
goto cleanup;
|
||||
}
|
||||
if (strcmp(what_to_do, "help") == 0) {
|
||||
dprintf(client_fd, CURL_HEADERS("200 OK"), sizeof(FS_HELP) - 1, FS_HELP);
|
||||
goto cleanup;
|
||||
}
|
||||
dprintf(client_fd, CURL_HEADERS("404 Not Found"), 11, "Not Found\r\n");
|
||||
|
||||
cleanup:
|
||||
shutdown(client_fd, SHUT_WR);
|
||||
close(client_fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define streq(a, b) (strcmp((a), (b)) == 0)
|
||||
#define exit_error(...) do { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
#define exit_perror(s) do { \
|
||||
perror(s); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int port = 80;
|
||||
float window = 60.0f;
|
||||
int rate_limit = 5;
|
||||
const char* command = "fastfetch --pipe false 2>/dev/null";
|
||||
struct in6_addr bind_addr = in6addr_any;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (streq(argv[i], "--help")) {
|
||||
printf(FS_HELP);
|
||||
return 0;
|
||||
}
|
||||
if (streq(argv[i], "--addr") && i + 1 < argc) {
|
||||
if (inet_pton(AF_INET6, argv[++i], &bind_addr) != 1) {
|
||||
struct in_addr v4;
|
||||
if (inet_pton(AF_INET, argv[i], &v4) != 1) exit_error("invalid address: %s\n", argv[i]);
|
||||
memset(&bind_addr, 0, sizeof(bind_addr));
|
||||
bind_addr.s6_addr[10] = 0xff;
|
||||
bind_addr.s6_addr[11] = 0xff;
|
||||
memcpy(&bind_addr.s6_addr[12], &v4, 4);
|
||||
}
|
||||
}
|
||||
else if (streq(argv[i], "--window") && i + 1 < argc) {
|
||||
window = atof(argv[++i]);
|
||||
}
|
||||
else if (streq(argv[i], "--ratelimit") && i + 1 < argc) {
|
||||
rate_limit = atoi(argv[++i]);
|
||||
}
|
||||
else if (streq(argv[i], "--hyfetch")) {
|
||||
command = "hyfetch --backend=fastfetch --args=\"--pipe false\" 2>/dev/null";
|
||||
}
|
||||
else {
|
||||
port = atoi(argv[i]);
|
||||
if (port <= 0) exit_error("unknown argument: %s\n", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const int server_fd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) exit_perror("socket");
|
||||
|
||||
int opt = 0;
|
||||
setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
|
||||
opt = 1;
|
||||
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
const struct sockaddr_in6 addr = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = bind_addr,
|
||||
.sin6_port = htons(port)
|
||||
};
|
||||
|
||||
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) exit_perror("bind");
|
||||
if (listen(server_fd, 128) < 0) exit_perror("listen");
|
||||
|
||||
char addr_str[INET6_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, &bind_addr, addr_str, sizeof(addr_str));
|
||||
printf("listening on [%s]:%d (limit: %d req / %fs per IP)\n",
|
||||
addr_str, port, rate_limit, window);
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_in6 client_addr;
|
||||
socklen_t addrlen = sizeof(client_addr);
|
||||
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addrlen);
|
||||
if (client_fd < 0) continue;
|
||||
|
||||
pthread_input_t pi = {.client_fd = client_fd, .window = window, .rate_limit = rate_limit, .command = command};
|
||||
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid, NULL, handle_client, (void*)(intptr_t)&pi) == 0)
|
||||
pthread_detach(tid);
|
||||
else {
|
||||
perror("pthread_create");
|
||||
close(client_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
ratelimit.h
Normal file
65
ratelimit.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef RATELIMIT_THREADSAFE
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
ratelimit_table_type value;
|
||||
int count;
|
||||
double window_start;
|
||||
} ratelimit_t;
|
||||
|
||||
static struct {
|
||||
ratelimit_t data[256];
|
||||
size_t next;
|
||||
#ifdef RATELIMIT_THREADSAFE
|
||||
pthread_mutex_t lock;
|
||||
#endif
|
||||
} ratelimit_table = { {0}, 0
|
||||
#ifdef RATELIMIT_THREADSAFE
|
||||
, PTHREAD_MUTEX_INITIALIZER
|
||||
#endif
|
||||
};
|
||||
|
||||
static int rate_limited(ratelimit_table_type value, float window, int rate_limit) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
double now = ts.tv_sec + ts.tv_nsec * 1e-9;
|
||||
|
||||
#ifdef RATELIMIT_THREADSAFE
|
||||
pthread_mutex_lock(&ratelimit_table.lock);
|
||||
#endif
|
||||
|
||||
ratelimit_t* e = (void*)0;
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
if (memcmp(ratelimit_table.data[i].value, value, sizeof(ratelimit_table_type)) == 0) {
|
||||
e = &ratelimit_table.data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
e = &ratelimit_table.data[ratelimit_table.next++];
|
||||
memcpy(e->value, value, sizeof(ratelimit_table_type));
|
||||
e->window_start = now;
|
||||
e->count = 0;
|
||||
}
|
||||
|
||||
if (now - e->window_start >= window) {
|
||||
e->window_start = now;
|
||||
e->count = 0;
|
||||
}
|
||||
|
||||
const int allowed = e->count < rate_limit;
|
||||
if (allowed) e->count++;
|
||||
|
||||
#ifdef RATELIMIT_THREADSAFE
|
||||
pthread_mutex_unlock(&ratelimit_table.lock);
|
||||
#endif
|
||||
|
||||
return allowed ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user