#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 #include #include #include #include #include #include #include #include #include #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); } }