From 442cf41129d516188f52050c48f806b24450a3a3 Mon Sep 17 00:00:00 2001 From: cflip Date: Thu, 8 Jun 2023 14:49:46 -0600 Subject: Support POST requests for PHP scripts This makes it possible to both read from and write to the standard I/O in php-cgi so that the request body can be written to its stdin and the script can access POST request data. Unfortunately, this isn't quite enough for the user login on the cflip forum to work, since cookies (and other HTTP headers) aren't passed to the script yet. --- cfws.c | 2 +- file.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- file.h | 2 +- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/cfws.c b/cfws.c index 5776b49..f280fe4 100644 --- a/cfws.c +++ b/cfws.c @@ -69,7 +69,7 @@ static void handle_request(const struct http_request *req, int sockfd) file_read(filepath, sockfd); break; case SERVE_METHOD_PHP: - file_read_php(filepath, req->query_str, sockfd); + file_read_php(filepath, req, sockfd); break; case SERVE_METHOD_ERROR: { const char *errmsg = "Content-Type: text/plain\r\n\r\nEpic fail"; diff --git a/file.c b/file.c index b51a5f8..d8f4088 100644 --- a/file.c +++ b/file.c @@ -92,28 +92,93 @@ int file_read(const char *filepath, int sockfd) return 0; } -int file_read_php(const char *filepath, const char *query_str, int sockfd) +static void cgi_setup_env(const char *filepath, const struct http_request *req) { - FILE *fp; + if (req->method == HTTP_METHOD_GET) { + setenv("REQUEST_METHOD", "GET", 1); + } else if (req->method == HTTP_METHOD_POST) { + setenv("REQUEST_METHOD", "POST", 1); + } + + setenv("SCRIPT_FILENAME", filepath, 1); + if (req->query_str) + setenv("QUERY_STRING", req->query_str, 1); + + if (req->body) { + static char intbuf[20]; + static char *content_type = "application/x-www-form-urlencoded"; + sprintf(intbuf, "%ld", strlen(req->body)); + setenv("CONTENT_LENGTH", intbuf, 1); + setenv("CONTENT_TYPE", content_type, strlen(content_type)); + } +} + +int file_read_php(const char *filepath, const struct http_request *req, int sockfd) +{ + int readfds[2]; + int writefds[2]; + pid_t pid; + char buffer[FILE_READBUF_SIZE]; size_t bytes_read; - setenv("REQUEST_METHOD", "GET", 1); - setenv("SCRIPT_FILENAME", filepath, 1); - if (query_str) - setenv("QUERY_STRING", query_str, 1); + cgi_setup_env(filepath, req); - fp = popen("php-cgi", "r"); - if (fp == NULL) { - perror("Failed to read command"); + /* Create pipes for reading from and writing to the child process. */ + if (pipe(readfds) == -1) { + perror("read pipe"); return 1; } - while ((bytes_read = fread(buffer, 1, FILE_READBUF_SIZE, fp)) > 0) + if (req->body && pipe(writefds) == -1) { + perror("write pipe"); + return 1; + } + + pid = fork(); + if (pid == -1) { + perror("fork"); + return 1; + } + + if (pid == 0) { + close(readfds[0]); + if (readfds[1] != STDOUT_FILENO) { + dup2(readfds[1], STDOUT_FILENO); + close(readfds[1]); + } + + if (req->body != NULL) { + close(writefds[1]); + if (writefds[0] != STDIN_FILENO) { + dup2(writefds[0], STDIN_FILENO); + close(writefds[0]); + } + } + + execl("/usr/bin/php-cgi", "php-cgi", NULL); + /* We should only end up here if there's an error. */ + perror("exec"); + exit(1); + } + + close(readfds[1]); + if (req->body != NULL) { + /* Write to stdin of child process. */ + close(writefds[0]); + write(writefds[1], req->body, strlen(req->body) + 1); + } + + /* Read its output from stdout. */ + while ((bytes_read = read(readfds[0], buffer, FILE_READBUF_SIZE)) > 0) write(sockfd, buffer, bytes_read); unsetenv("QUERY_STRING"); + unsetenv("CONTENT"); + unsetenv("CONTENT_LENGTH"); + + close(readfds[0]); + close(writefds[1]); - pclose(fp); return 0; } diff --git a/file.h b/file.h index 3081244..9c48425 100644 --- a/file.h +++ b/file.h @@ -16,6 +16,6 @@ const char *file_path_for_uri(const char *); enum serve_method file_method_for_path(const char *, enum http_res_code *); int file_read(const char *, int); -int file_read_php(const char *, const char *, int); +int file_read_php(const char *, const struct http_request *, int); #endif -- cgit v1.2.3