1
0
mirror of https://github.com/lammertb/libhttp.git synced 2025-08-09 03:22:45 +03:00
Files
libhttp/src/httplib_handle_cgi_request.c
2016-12-13 17:32:31 +01:00

263 lines
8.7 KiB
C

/*
* Copyright (c) 2016 Lammert Bies
* Copyright (c) 2013-2016 the Civetweb developers
* Copyright (c) 2004-2013 Sergey Lyubka
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "httplib_main.h"
#include "httplib_memory.h"
#include "httplib_string.h"
/*
* void XX_httplib_handle_cgi_request( struct httplib_connection *conn, const char *prog );
*
* The function XX_httplib_handle_cgi_request() handles a request for a CGI
* resource.
*/
#if !defined(NO_CGI)
void XX_httplib_handle_cgi_request( struct httplib_connection *conn, const char *prog ) {
char *buf;
size_t buflen;
int headers_len;
int data_len;
int i;
int truncated;
int fdin[2] = {-1, -1};
int fdout[2] = {-1, -1};
int fderr[2] = {-1, -1};
const char *status;
const char *status_text;
const char *connection_state;
char *pbuf;
char dir[PATH_MAX];
char *p;
struct httplib_request_info ri;
struct cgi_environment blk;
FILE *in;
FILE *out;
FILE *err;
struct file fout = STRUCT_FILE_INITIALIZER;
pid_t pid = (pid_t)-1;
if (conn == NULL) return;
in = NULL;
out = NULL;
err = NULL;
buf = NULL;
buflen = 16384;
XX_httplib_prepare_cgi_environment(conn, prog, &blk);
/* CGI must be executed in its own directory. 'dir' must point to the
* directory containing executable program, 'p' must point to the
* executable program name relative to 'dir'. */
XX_httplib_snprintf(conn, &truncated, dir, sizeof(dir), "%s", prog);
if (truncated) {
httplib_cry(conn, "Error: CGI program \"%s\": Path too long", prog);
XX_httplib_send_http_error(conn, 500, "Error: %s", "CGI path too long");
goto done;
}
if ((p = strrchr(dir, '/')) != NULL) {
*p++ = '\0';
} else {
dir[0] = '.', dir[1] = '\0';
p = (char *)prog;
}
if (pipe(fdin) != 0 || pipe(fdout) != 0 || pipe(fderr) != 0) {
status = strerror(ERRNO);
httplib_cry(conn, "Error: CGI program \"%s\": Can not create CGI pipes: %s", prog, status);
XX_httplib_send_http_error(conn, 500, "Error: Cannot create CGI pipe: %s", status);
goto done;
}
pid = XX_httplib_spawn_process(conn, p, blk.buf, blk.var, fdin, fdout, fderr, dir);
if (pid == (pid_t)-1) {
status = strerror(ERRNO);
httplib_cry(conn, "Error: CGI program \"%s\": Can not spawn CGI process: %s", prog, status);
XX_httplib_send_http_error(conn, 500, "Error: Cannot spawn CGI process [%s]: %s", prog, status);
goto done;
}
/* Make sure child closes all pipe descriptors. It must dup them to 0,1 */
XX_httplib_set_close_on_exec( (SOCKET)fdin[0], conn ); /* stdin read */
XX_httplib_set_close_on_exec( (SOCKET)fdout[1], conn ); /* stdout write */
XX_httplib_set_close_on_exec( (SOCKET)fderr[1], conn ); /* stderr write */
XX_httplib_set_close_on_exec( (SOCKET)fdin[1], conn ); /* stdin write */
XX_httplib_set_close_on_exec( (SOCKET)fdout[0], conn ); /* stdout read */
XX_httplib_set_close_on_exec( (SOCKET)fderr[0], conn ); /* stderr read */
/* Parent closes only one side of the pipes.
* If we don't mark them as closed, close() attempt before
* return from this function throws an exception on Windows.
* Windows does not like when closed descriptor is closed again. */
close( fdin[0] );
close( fdout[1] );
close( fderr[1] );
fdin[0] = -1;
fdout[1] = -1;
fderr[1] = -1;
if ((in = fdopen(fdin[1], "wb")) == NULL) {
status = strerror(ERRNO);
httplib_cry(conn, "Error: CGI program \"%s\": Can not open stdin: %s", prog, status);
XX_httplib_send_http_error(conn, 500, "Error: CGI can not open fdin\nfopen: %s", status);
goto done;
}
if ((out = fdopen(fdout[0], "rb")) == NULL) {
status = strerror(ERRNO);
httplib_cry(conn, "Error: CGI program \"%s\": Can not open stdout: %s", prog, status);
XX_httplib_send_http_error(conn, 500, "Error: CGI can not open fdout\nfopen: %s", status);
goto done;
}
if ((err = fdopen(fderr[0], "rb")) == NULL) {
status = strerror(ERRNO);
httplib_cry(conn, "Error: CGI program \"%s\": Can not open stderr: %s", prog, status);
XX_httplib_send_http_error(conn, 500, "Error: CGI can not open fdout\nfopen: %s", status);
goto done;
}
setbuf( in, NULL );
setbuf( out, NULL );
setbuf( err, NULL );
fout.fp = out;
if ((conn->request_info.content_length > 0) || conn->is_chunked) {
/* This is a POST/PUT request, or another request with body data. */
if (!XX_httplib_forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
/* Error sending the body data */
httplib_cry(conn, "Error: CGI program \"%s\": Forward body data failed", prog);
goto done;
}
}
/* Close so child gets an EOF. */
fclose(in);
in = NULL;
fdin[1] = -1;
/* Now read CGI reply into a buffer. We need to set correct
* status code, thus we need to see all HTTP headers first.
* Do not send anything back to client, until we buffer in all
* HTTP headers. */
data_len = 0;
buf = (char *)XX_httplib_malloc(buflen);
if (buf == NULL) {
XX_httplib_send_http_error(conn, 500, "Error: Not enough memory for CGI buffer (%u bytes)", (unsigned int)buflen);
httplib_cry(conn, "Error: CGI program \"%s\": Not enough memory for buffer (%u " "bytes)", prog, (unsigned int)buflen);
goto done;
}
headers_len = XX_httplib_read_request(out, conn, buf, (int)buflen, &data_len);
if (headers_len <= 0) {
/* Could not parse the CGI response. Check if some error message on
* stderr. */
i = XX_httplib_pull_all(err, conn, buf, (int)buflen);
if (i > 0) {
httplib_cry(conn, "Error: CGI program \"%s\" sent error " "message: [%.*s]", prog, i, buf);
XX_httplib_send_http_error(conn, 500, "Error: CGI program \"%s\" sent error " "message: [%.*s]", prog, i, buf);
} else {
httplib_cry(conn, "Error: CGI program sent malformed or too big " "(>%u bytes) HTTP headers: [%.*s]", (unsigned)buflen, data_len, buf);
XX_httplib_send_http_error(conn,
500,
"Error: CGI program sent malformed or too big "
"(>%u bytes) HTTP headers: [%.*s]",
(unsigned)buflen,
data_len,
buf);
}
goto done;
}
pbuf = buf;
buf[headers_len - 1] = '\0';
XX_httplib_parse_http_headers(&pbuf, &ri);
/* Make up and send the status line */
status_text = "OK";
if ((status = XX_httplib_get_header(&ri, "Status")) != NULL) {
conn->status_code = atoi(status);
status_text = status;
while (isdigit(*(const unsigned char *)status_text)
|| *status_text == ' ') {
status_text++;
}
} else if (XX_httplib_get_header(&ri, "Location") != NULL) {
conn->status_code = 302;
} else {
conn->status_code = 200;
}
connection_state = XX_httplib_get_header(&ri, "Connection");
if (!XX_httplib_header_has_option(connection_state, "keep-alive")) {
conn->must_close = 1;
}
httplib_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code, status_text);
/* Send headers */
for (i = 0; i < ri.num_headers; i++) {
httplib_printf(conn, "%s: %s\r\n", ri.http_headers[i].name, ri.http_headers[i].value);
}
httplib_write(conn, "\r\n", 2);
/* Send chunk of data that may have been read after the headers */
conn->num_bytes_sent += httplib_write(conn, buf + headers_len, (size_t)(data_len - headers_len));
/* Read the rest of CGI output and send to the client */
XX_httplib_send_file_data(conn, &fout, 0, INT64_MAX);
done:
XX_httplib_free(blk.var);
XX_httplib_free(blk.buf);
if (pid != (pid_t)-1) {
XX_httplib_kill(pid, SIGKILL);
#if !defined(_WIN32)
{
int st;
while (waitpid(pid, &st, 0) != -1)
; /* clean zombies */
}
#endif
}
if ( fdin[0] != -1 ) close( fdin[0] );
if ( fdout[1] != -1 ) close( fdout[1] );
if ( in != NULL ) fclose( in ); else if ( fdin[1] != -1 ) close( fdin[1] );
if ( out != NULL ) fclose( out ); else if ( fdout[0] != -1 ) close( fdout[0] );
if ( err != NULL ) fclose( err ); else if ( fderr[0] != -1 ) close( fderr[0] );
if ( buf != NULL ) XX_httplib_free( buf );
} /* XX_httplib_handle_cgi_request */
#endif /* !NO_CGI */