This commit is contained in:
parent
bd940dd806
commit
9355fcd309
117
README.md
117
README.md
@ -1,6 +1,8 @@
|
|||||||
# rproxy
|
# rproxy
|
||||||
|
|
||||||
rproxy is a high-performance reverse proxy server written in C. It routes HTTP and WebSocket requests to upstream services based on hostname, with support for SSL/TLS termination and connection pooling.
|
Author: retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
A high-performance reverse proxy server written in C. Routes HTTP and WebSocket requests to upstream services based on hostname, with support for SSL/TLS connections and real-time monitoring.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -14,7 +16,8 @@ rproxy is a high-performance reverse proxy server written in C. It routes HTTP a
|
|||||||
- Epoll-based event handling for high concurrency
|
- Epoll-based event handling for high concurrency
|
||||||
- Graceful shutdown with connection draining
|
- Graceful shutdown with connection draining
|
||||||
- Live configuration reload via SIGHUP
|
- Live configuration reload via SIGHUP
|
||||||
- Dashboard authentication (HTTP Basic Auth)
|
- Per-route authentication (HTTP Basic Auth)
|
||||||
|
- Dashboard authentication
|
||||||
- Rate limiting per client IP
|
- Rate limiting per client IP
|
||||||
- Health checks for upstream servers
|
- Health checks for upstream servers
|
||||||
- Automatic upstream connection retries
|
- Automatic upstream connection retries
|
||||||
@ -27,7 +30,7 @@ rproxy is a high-performance reverse proxy server written in C. It routes HTTP a
|
|||||||
- OpenSSL (libssl, libcrypto)
|
- OpenSSL (libssl, libcrypto)
|
||||||
- SQLite3
|
- SQLite3
|
||||||
- pthreads
|
- pthreads
|
||||||
- cJSON library
|
- cJSON (bundled)
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@ -35,7 +38,16 @@ rproxy is a high-performance reverse proxy server written in C. It routes HTTP a
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
This compiles the source files in `src/` and produces the `rproxy` executable.
|
Compiles the source files in `src/` and produces the `rproxy` executable.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # Run unit tests
|
||||||
|
make coverage # Run tests with coverage report (minimum 60% required)
|
||||||
|
make coverage-html # Generate HTML coverage report
|
||||||
|
make valgrind # Run tests with memory leak detection
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -51,9 +63,11 @@ Configuration is defined in `proxy_config.json`:
|
|||||||
"upstream_port": 5000,
|
"upstream_port": 5000,
|
||||||
"use_ssl": false,
|
"use_ssl": false,
|
||||||
"rewrite_host": true,
|
"rewrite_host": true,
|
||||||
|
"use_auth": true,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "secret",
|
||||||
"patch": {
|
"patch": {
|
||||||
"old_string": "new_string",
|
"old_string": "new_string",
|
||||||
"secret_key": "[REDACTED]",
|
|
||||||
"blocked_content": null
|
"blocked_content": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,18 +75,23 @@ Configuration is defined in `proxy_config.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `port`: Listening port for incoming connections
|
### Route Options
|
||||||
- `reverse_proxy`: Array of routing rules
|
|
||||||
- `hostname`: Host header to match for routing
|
| Option | Type | Description |
|
||||||
- `upstream_host`: Target server hostname/IP
|
|--------|------|-------------|
|
||||||
- `upstream_port`: Target server port
|
| `hostname` | string | Host header to match for routing |
|
||||||
- `use_ssl`: Enable SSL for upstream connection
|
| `upstream_host` | string | Target server hostname or IP |
|
||||||
- `rewrite_host`: Rewrite Host header to upstream hostname
|
| `upstream_port` | integer | Target server port (1-65535) |
|
||||||
- `patch`: Optional object for stream data patching (see below)
|
| `use_ssl` | boolean | Enable SSL/TLS for upstream connection |
|
||||||
|
| `rewrite_host` | boolean | Rewrite Host header to upstream hostname |
|
||||||
|
| `use_auth` | boolean | Enable HTTP Basic Auth for this route |
|
||||||
|
| `username` | string | Authentication username |
|
||||||
|
| `password` | string | Authentication password |
|
||||||
|
| `patch` | object | Stream data patching rules |
|
||||||
|
|
||||||
### Data Patching
|
### Data Patching
|
||||||
|
|
||||||
The `patch` configuration allows rewriting or blocking content in HTTP streams. Patch rules are applied to textual content only (text/*, application/json, application/xml, etc.). Binary content passes through unmodified.
|
The `patch` configuration allows rewriting or blocking content in HTTP streams. Patch rules are applied to textual content only. Binary content passes through unmodified.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -86,19 +105,17 @@ The `patch` configuration allows rewriting or blocking content in HTTP streams.
|
|||||||
|
|
||||||
- **String replacement**: Each key-value pair defines a find-replace rule
|
- **String replacement**: Each key-value pair defines a find-replace rule
|
||||||
- **Content blocking**: Setting value to `null` blocks the entire response/request when the key is found
|
- **Content blocking**: Setting value to `null` blocks the entire response/request when the key is found
|
||||||
- **Bidirectional**: Patches apply to both requests (client → upstream) and responses (upstream → client)
|
- **Bidirectional**: Patches apply to both requests and responses
|
||||||
|
|
||||||
When content is blocked:
|
Blocked responses return `502 Bad Gateway`. Blocked requests return `403 Forbidden`.
|
||||||
- Blocked responses return `502 Bad Gateway` to the client
|
|
||||||
- Blocked requests return `403 Forbidden` to the client
|
|
||||||
|
|
||||||
Supported textual content types:
|
Supported textual content types:
|
||||||
- `text/*` (text/html, text/plain, text/css, etc.)
|
- `text/*`
|
||||||
- `application/json`
|
- `application/json`
|
||||||
- `application/xml`
|
- `application/xml`
|
||||||
- `application/javascript`
|
- `application/javascript`
|
||||||
- `application/x-www-form-urlencoded`
|
- `application/x-www-form-urlencoded`
|
||||||
- Any content type with `+xml` or `+json` suffix
|
- Content types with `+xml` or `+json` suffix
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
@ -121,35 +138,23 @@ Supported textual content types:
|
|||||||
|
|
||||||
If no config file is specified, defaults to `proxy_config.json`.
|
If no config file is specified, defaults to `proxy_config.json`.
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Basic usage
|
|
||||||
./rproxy
|
./rproxy
|
||||||
|
|
||||||
# With custom config
|
|
||||||
./rproxy /etc/rproxy/config.json
|
./rproxy /etc/rproxy/config.json
|
||||||
|
|
||||||
# With debug logging
|
|
||||||
DEBUG=1 ./rproxy
|
DEBUG=1 ./rproxy
|
||||||
|
|
||||||
# With file logging
|
|
||||||
LOG_FILE=/var/log/rproxy.log ./rproxy
|
LOG_FILE=/var/log/rproxy.log ./rproxy
|
||||||
|
|
||||||
# With rate limiting (100 requests/minute)
|
|
||||||
RATE_LIMIT=100 ./rproxy
|
RATE_LIMIT=100 ./rproxy
|
||||||
|
|
||||||
# With dashboard authentication
|
|
||||||
DASHBOARD_USER=admin DASHBOARD_PASS=secret ./rproxy
|
DASHBOARD_USER=admin DASHBOARD_PASS=secret ./rproxy
|
||||||
|
SSL_VERIFY=0 ./rproxy
|
||||||
# Reload configuration
|
kill -HUP $(pidof rproxy) # Reload configuration
|
||||||
kill -HUP $(pidof rproxy)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
- Dashboard: `http://localhost:{port}/rproxy/dashboard`
|
| Path | Description |
|
||||||
- API Stats: `http://localhost:{port}/rproxy/api/stats`
|
|------|-------------|
|
||||||
|
| `/rproxy/dashboard` | Web-based monitoring dashboard |
|
||||||
|
| `/rproxy/api/stats` | JSON API for statistics |
|
||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
@ -161,24 +166,22 @@ kill -HUP $(pidof rproxy)
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- **main.c**: Entry point, event loop, signal handling
|
| Module | Description |
|
||||||
- **connection.c**: Connection management, epoll handling
|
|--------|-------------|
|
||||||
- **http.c**: HTTP request/response parsing
|
| `main.c` | Entry point, event loop, signal handling |
|
||||||
- **ssl_handler.c**: SSL/TLS connection handling
|
| `connection.c` | Connection management, epoll handling |
|
||||||
- **monitor.c**: System and per-vhost statistics collection
|
| `http.c` | HTTP request/response parsing |
|
||||||
- **dashboard.c**: Web dashboard generation
|
| `ssl_handler.c` | SSL/TLS connection handling |
|
||||||
- **config.c**: JSON configuration parsing
|
| `monitor.c` | System and per-vhost statistics collection |
|
||||||
- **buffer.c**: Circular buffer implementation
|
| `dashboard.c` | Web dashboard generation |
|
||||||
- **logging.c**: Logging utilities
|
| `config.c` | JSON configuration parsing with hot-reload |
|
||||||
- **rate_limit.c**: Per-IP rate limiting
|
| `buffer.c` | Circular buffer implementation |
|
||||||
- **auth.c**: Dashboard authentication
|
| `logging.c` | Logging utilities |
|
||||||
- **health_check.c**: Upstream health monitoring
|
| `rate_limit.c` | Per-IP rate limiting with sliding window |
|
||||||
- **patch.c**: Stream data patching engine
|
| `auth.c` | HTTP Basic Auth implementation |
|
||||||
|
| `health_check.c` | Upstream health monitoring |
|
||||||
|
| `patch.c` | Stream data patching engine |
|
||||||
|
|
||||||
## Testing
|
## License
|
||||||
|
|
||||||
```bash
|
See LICENSE file for details.
|
||||||
make test
|
|
||||||
```
|
|
||||||
|
|
||||||
Runs unit tests for core components.
|
|
||||||
|
|||||||
20
src/config.c
20
src/config.c
@ -86,19 +86,27 @@ static char* read_file_to_string(const char *filename) {
|
|||||||
FILE *f = fopen(filename, "rb");
|
FILE *f = fopen(filename, "rb");
|
||||||
if (!f) return NULL;
|
if (!f) return NULL;
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
if (fseek(f, 0, SEEK_END) != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
long length = ftell(f);
|
long length = ftell(f);
|
||||||
if (length < 0 || length > 1024*1024) {
|
if (length < 0 || length > 1024*1024) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(f, 0, SEEK_SET);
|
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||||
char *buffer = malloc(length + 1);
|
fclose(f);
|
||||||
if (buffer) {
|
return NULL;
|
||||||
size_t read_len = fread(buffer, 1, length, f);
|
|
||||||
buffer[read_len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
char *buffer = malloc((size_t)length + 1);
|
||||||
|
if (!buffer) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
size_t read_len = fread(buffer, 1, (size_t)length, f);
|
||||||
|
buffer[read_len] = '\0';
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
@ -46,13 +47,21 @@ int connection_set_non_blocking(int fd) {
|
|||||||
|
|
||||||
void connection_set_tcp_keepalive(int fd) {
|
void connection_set_tcp_keepalive(int fd) {
|
||||||
int yes = 1;
|
int yes = 1;
|
||||||
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
|
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) < 0) {
|
||||||
|
log_debug("setsockopt SO_KEEPALIVE failed for fd %d: %s", fd, strerror(errno));
|
||||||
|
}
|
||||||
int idle = 60;
|
int idle = 60;
|
||||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) < 0) {
|
||||||
|
log_debug("setsockopt TCP_KEEPIDLE failed for fd %d: %s", fd, strerror(errno));
|
||||||
|
}
|
||||||
int interval = 10;
|
int interval = 10;
|
||||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)) < 0) {
|
||||||
|
log_debug("setsockopt TCP_KEEPINTVL failed for fd %d: %s", fd, strerror(errno));
|
||||||
|
}
|
||||||
int maxpkt = 6;
|
int maxpkt = 6;
|
||||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt));
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt)) < 0) {
|
||||||
|
log_debug("setsockopt TCP_KEEPCNT failed for fd %d: %s", fd, strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void connection_add_to_epoll(int fd, uint32_t events) {
|
void connection_add_to_epoll(int fd, uint32_t events) {
|
||||||
@ -297,7 +306,12 @@ void connection_send_error_response(connection_t *conn, int code, const char* st
|
|||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
struct tm *gmt = gmtime(&now);
|
struct tm *gmt = gmtime(&now);
|
||||||
char date_buf[64];
|
char date_buf[64];
|
||||||
strftime(date_buf, sizeof(date_buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
|
if (gmt) {
|
||||||
|
strftime(date_buf, sizeof(date_buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
|
||||||
|
} else {
|
||||||
|
strncpy(date_buf, "Thu, 01 Jan 1970 00:00:00 GMT", sizeof(date_buf) - 1);
|
||||||
|
date_buf[sizeof(date_buf) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
char response[ERROR_RESPONSE_SIZE];
|
char response[ERROR_RESPONSE_SIZE];
|
||||||
int len = snprintf(response, sizeof(response),
|
int len = snprintf(response, sizeof(response),
|
||||||
@ -331,7 +345,12 @@ void connection_send_auth_required(connection_t *conn, const char *realm) {
|
|||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
struct tm *gmt = gmtime(&now);
|
struct tm *gmt = gmtime(&now);
|
||||||
char date_buf[64];
|
char date_buf[64];
|
||||||
strftime(date_buf, sizeof(date_buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
|
if (gmt) {
|
||||||
|
strftime(date_buf, sizeof(date_buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
|
||||||
|
} else {
|
||||||
|
strncpy(date_buf, "Thu, 01 Jan 1970 00:00:00 GMT", sizeof(date_buf) - 1);
|
||||||
|
date_buf[sizeof(date_buf) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
const char *body = "401 Unauthorized - Authentication required";
|
const char *body = "401 Unauthorized - Authentication required";
|
||||||
char response[ERROR_RESPONSE_SIZE];
|
char response[ERROR_RESPONSE_SIZE];
|
||||||
@ -385,6 +404,17 @@ static int try_upstream_connect(struct sockaddr_in *addr, int *out_fd) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cleanup_upstream_partial(connection_t *up, int up_fd, connection_t *client, int free_read, int free_write) {
|
||||||
|
if (free_read) buffer_free(&up->read_buf);
|
||||||
|
if (free_write) buffer_free(&up->write_buf);
|
||||||
|
if (up->config) config_ref_dec(up->config);
|
||||||
|
close(up_fd);
|
||||||
|
memset(up, 0, sizeof(connection_t));
|
||||||
|
up->type = CONN_TYPE_UNUSED;
|
||||||
|
up->fd = -1;
|
||||||
|
if (client) client->pair = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void connection_connect_to_upstream(connection_t *client, const char *data, size_t data_len) {
|
void connection_connect_to_upstream(connection_t *client, const char *data, size_t data_len) {
|
||||||
if (!client || !data) return;
|
if (!client || !data) return;
|
||||||
|
|
||||||
@ -466,21 +496,12 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
|
|||||||
config_ref_inc(up->config);
|
config_ref_inc(up->config);
|
||||||
|
|
||||||
if (buffer_init(&up->read_buf, CHUNK_SIZE) < 0) {
|
if (buffer_init(&up->read_buf, CHUNK_SIZE) < 0) {
|
||||||
close(up_fd);
|
cleanup_upstream_partial(up, up_fd, client, 0, 0);
|
||||||
memset(up, 0, sizeof(connection_t));
|
|
||||||
up->type = CONN_TYPE_UNUSED;
|
|
||||||
up->fd = -1;
|
|
||||||
client->pair = NULL;
|
|
||||||
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
|
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buffer_init(&up->write_buf, CHUNK_SIZE) < 0) {
|
if (buffer_init(&up->write_buf, CHUNK_SIZE) < 0) {
|
||||||
buffer_free(&up->read_buf);
|
cleanup_upstream_partial(up, up_fd, client, 1, 0);
|
||||||
close(up_fd);
|
|
||||||
memset(up, 0, sizeof(connection_t));
|
|
||||||
up->type = CONN_TYPE_UNUSED;
|
|
||||||
up->fd = -1;
|
|
||||||
client->pair = NULL;
|
|
||||||
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
|
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -521,10 +542,14 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer_ensure_capacity(&up->write_buf, len_to_send) == 0) {
|
if (buffer_ensure_capacity(&up->write_buf, len_to_send) < 0) {
|
||||||
memcpy(up->write_buf.data, data_to_send, len_to_send);
|
if (modified_request) free(modified_request);
|
||||||
up->write_buf.tail = len_to_send;
|
cleanup_upstream_partial(up, up_fd, client, 1, 1);
|
||||||
|
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
memcpy(up->write_buf.data, data_to_send, len_to_send);
|
||||||
|
up->write_buf.tail = len_to_send;
|
||||||
|
|
||||||
if (modified_request) {
|
if (modified_request) {
|
||||||
free(modified_request);
|
free(modified_request);
|
||||||
@ -533,7 +558,8 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
|
|||||||
if (route->use_ssl) {
|
if (route->use_ssl) {
|
||||||
up->ssl = SSL_new(ssl_ctx);
|
up->ssl = SSL_new(ssl_ctx);
|
||||||
if (!up->ssl) {
|
if (!up->ssl) {
|
||||||
connection_close(client->fd);
|
cleanup_upstream_partial(up, up_fd, client, 1, 1);
|
||||||
|
connection_send_error_response(client, 502, "Bad Gateway", "SSL initialization failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,7 +691,12 @@ static void handle_client_read(connection_t *conn) {
|
|||||||
route_config_t *route = config_find_route(conn->request.host);
|
route_config_t *route = config_find_route(conn->request.host);
|
||||||
if (route && route->use_auth) {
|
if (route && route->use_auth) {
|
||||||
char auth_header[1024] = "";
|
char auth_header[1024] = "";
|
||||||
const char *headers_start = data_start + (strstr(data_start, "\r\n") - data_start + 2);
|
const char *first_crlf = strstr(data_start, "\r\n");
|
||||||
|
if (!first_crlf || first_crlf >= data_start + headers_len - 2) {
|
||||||
|
connection_send_error_response(conn, 400, "Bad Request", "Malformed HTTP request headers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char *headers_start = first_crlf + 2;
|
||||||
http_find_header_value(headers_start, headers_len - (headers_start - data_start), "Authorization", auth_header, sizeof(auth_header));
|
http_find_header_value(headers_start, headers_len - (headers_start - data_start), "Authorization", auth_header, sizeof(auth_header));
|
||||||
|
|
||||||
char error_msg[256] = "";
|
char error_msg[256] = "";
|
||||||
@ -838,7 +869,12 @@ static void handle_forwarding(connection_t *conn) {
|
|||||||
if (result.output_len > 0 && result.size_delta != 0) {
|
if (result.output_len > 0 && result.size_delta != 0) {
|
||||||
output_data = patched_buf;
|
output_data = patched_buf;
|
||||||
output_len = result.output_len;
|
output_len = result.output_len;
|
||||||
conn->content_length_delta += result.size_delta;
|
if ((result.size_delta > 0 && conn->content_length_delta > LONG_MAX - result.size_delta) ||
|
||||||
|
(result.size_delta < 0 && conn->content_length_delta < LONG_MIN - result.size_delta)) {
|
||||||
|
log_debug("Content-length delta overflow detected on fd %d", conn->fd);
|
||||||
|
} else {
|
||||||
|
conn->content_length_delta += result.size_delta;
|
||||||
|
}
|
||||||
log_debug("Patched data: %zu -> %zu bytes (delta: %ld)", src_len, output_len, result.size_delta);
|
log_debug("Patched data: %zu -> %zu bytes (delta: %ld)", src_len, output_len, result.size_delta);
|
||||||
} else if (result.output_len > 0) {
|
} else if (result.output_len > 0) {
|
||||||
output_data = patched_buf;
|
output_data = patched_buf;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user