README.md
Rendering markdown...
#define _GNU_SOURCE
/*
* server.c - Minimal HTTP server that transcodes POST bodies via iconv.
*
* Listens on port 8080. Accepts POST requests with a charset parameter
* in Content-Type and converts the body to UTF-8 using iconv().
*
* This simulates a realistic attack surface: any web service on Alpine
* Linux that processes user-supplied text in non-UTF-8 encodings.
*
* Usage: ./server
* Test: curl -X POST -H "Content-Type: text/plain; charset=gb18030" \
* --data-binary @payload.bin http://localhost:8080/
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <iconv.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define MAX_REQ 65536
static double now(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
/* Extract charset from Content-Type header, return "UTF-8" if not found */
static const char *extract_charset(const char *headers)
{
static char charset[64];
const char *p = strcasestr(headers, "charset=");
if (!p) return NULL;
p += 8;
int i = 0;
while (*p && *p != '\r' && *p != '\n' && *p != ';' && *p != ' '
&& i < (int)sizeof(charset)-1)
charset[i++] = *p++;
charset[i] = 0;
return charset;
}
/* Extract Content-Length */
static int extract_content_length(const char *headers)
{
const char *p = strcasestr(headers, "content-length:");
if (!p) return 0;
p += 15;
while (*p == ' ') p++;
return atoi(p);
}
/* Transcode body from `from_charset` to UTF-8 using iconv */
static int transcode(const char *from_charset, const char *body, size_t bodylen,
char *out, size_t outlen, double *elapsed)
{
iconv_t cd = iconv_open("UTF-8", from_charset);
if (cd == (iconv_t)-1) {
snprintf(out, outlen, "iconv_open failed for charset '%s': %s",
from_charset, strerror(errno));
return 400;
}
char *inp = (char *)body;
size_t inleft = bodylen;
char *outp = out;
size_t outleft = outlen - 1;
double t0 = now();
size_t ret = iconv(cd, &inp, &inleft, &outp, &outleft);
*elapsed = now() - t0;
iconv_close(cd);
if (ret == (size_t)-1) {
snprintf(out, outlen, "iconv failed: %s (consumed %zu/%zu bytes in %.3fs)",
strerror(errno), bodylen - inleft, bodylen, *elapsed);
return 400;
}
*outp = 0;
return 200;
}
static void handle_client(int client_fd)
{
char req[MAX_REQ];
int total = 0;
int header_end = 0;
/* Read the full request (headers + body) */
while (total < MAX_REQ - 1) {
int n = read(client_fd, req + total, MAX_REQ - 1 - total);
if (n <= 0) break;
total += n;
req[total] = 0;
/* Check if we've received the full headers */
if (!header_end) {
char *hend = strstr(req, "\r\n\r\n");
if (hend) {
header_end = (hend - req) + 4;
int clen = extract_content_length(req);
/* If we have all the body, stop reading */
if (total >= header_end + clen)
break;
}
}
}
if (!header_end) {
const char *resp = "HTTP/1.0 400 Bad Request\r\n\r\nMalformed request\n";
write(client_fd, resp, strlen(resp));
close(client_fd);
return;
}
const char *charset = extract_charset(req);
int clen = extract_content_length(req);
const char *body = req + header_end;
int bodylen = total - header_end;
if (bodylen > clen) bodylen = clen;
char response_body[MAX_REQ];
double elapsed = 0;
if (!charset || bodylen == 0) {
snprintf(response_body, sizeof(response_body),
"Send POST with Content-Type: text/plain; charset=gb18030\n"
"Body: the text to transcode\n");
} else {
printf("[request] charset=%s bodylen=%d ... ", charset, bodylen);
fflush(stdout);
int status = transcode(charset, body, bodylen,
response_body, sizeof(response_body), &elapsed);
printf("done in %.3fs (status %d)\n", elapsed, status);
fflush(stdout);
}
char response[MAX_REQ + 256];
int rlen = snprintf(response, sizeof(response),
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n"
"X-Transcode-Time: %.6f\r\n"
"\r\n"
"Transcode time: %.6f seconds\n"
"Input: %d bytes (%s)\n"
"Output:\n%s\n",
elapsed, elapsed,
bodylen, charset ? charset : "none",
response_body);
write(client_fd, response, rlen);
close(client_fd);
}
int main(void)
{
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) { perror("socket"); return 1; }
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = INADDR_ANY
};
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); return 1;
}
if (listen(server_fd, 5) < 0) {
perror("listen"); return 1;
}
printf("Listening on port %d (musl iconv transcode server)\n", PORT);
printf("Send requests like:\n");
printf(" curl -X POST -H 'Content-Type: text/plain; charset=gb18030' "
"--data-binary @payload.bin http://localhost:%d/\n\n", PORT);
fflush(stdout);
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) { perror("accept"); continue; }
handle_client(client_fd);
}
}