static handler_t cgi_response_headers(server *srv, connection *con, struct http_response_opts_t *opts) { /* response headers just completed */ handler_ctx *hctx = (handler_ctx *)opts->pdata; if (con->response.htags & HTTP_HEADER_UPGRADE) { if (hctx->conf.upgrade && con->http_status == 101) { /* 101 Switching Protocols; transition to transparent proxy */ http_response_upgrade_read_body_unknown(srv, con); } else { con->response.htags &= ~HTTP_HEADER_UPGRADE; #if 0 /* preserve prior questionable behavior; likely broken behavior * anyway if backend thinks connection is being upgraded but client * does not receive Connection: upgrade */ http_header_response_unset(con, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade")); #endif } } if (hctx->conf.upgrade && !(con->response.htags & HTTP_HEADER_UPGRADE)) { chunkqueue *cq = con->request_content_queue; hctx->conf.upgrade = 0; if (cq->bytes_out == (off_t)con->request.content_length) { cgi_connection_close_fdtocgi(srv, hctx); /*(closes hctx->fdtocgi)*/ } } return HANDLER_GO_ON; }
static void cgi_connection_close(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; /* the connection to the browser went away, but we still have a connection * to the CGI script * * close cgi-connection */ if (hctx->fd != -1) { /* close connection to the cgi-script */ fdevent_fdnode_event_del(srv->ev, hctx->fdn); /*fdevent_unregister(srv->ev, hctx->fd);*//*(handled below)*/ fdevent_sched_close(srv->ev, hctx->fd, 0); hctx->fdn = NULL; } if (hctx->fdtocgi != -1) { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } if (hctx->pid > 0) { cgi_pid_kill(p, hctx->pid); } con->plugin_ctx[p->id] = NULL; cgi_handler_ctx_free(hctx); /* finish response (if not already con->file_started, con->file_finished) */ if (con->mode == p->id) { http_response_backend_done(srv, con); } }
static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) { handler_ctx *hctx = ctx; connection *con = hctx->remote_conn; /*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/ joblist_append(srv, con); if (revents & FDEVENT_OUT) { if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) { cgi_connection_close(srv, hctx); return HANDLER_ERROR; } /* more request body to be sent to CGI */ } if (revents & FDEVENT_HUP) { /* skip sending remaining data to CGI */ if (con->request.content_length) { chunkqueue *cq = con->request_content_queue; chunkqueue_mark_written(cq, chunkqueue_length(cq)); if (cq->bytes_in != (off_t)con->request.content_length) { con->keep_alive = 0; } } cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } else if (revents & FDEVENT_ERR) { /* kill all connections to the cgi process */ #if 1 log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR"); #endif cgi_connection_close(srv, hctx); return HANDLER_ERROR; } return HANDLER_FINISHED; }
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { connection *con = hctx->remote_conn; chunkqueue *cq = con->request_content_queue; chunk *c; /* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms. * solution: if this is still a problem on windows, then substitute * socketpair() for pipe() and closesocket() for close() on windows. */ for (c = cq->first; c; c = cq->first) { ssize_t r = -1; switch(c->type) { case FILE_CHUNK: r = cgi_write_file_chunk_mmap(srv, con, fd, cq); break; case MEM_CHUNK: if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { switch(errno) { case EAGAIN: case EINTR: /* ignore and try again */ r = 0; break; case EPIPE: case ECONNRESET: /* connection closed */ r = -2; break; default: /* fatal error */ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno)); r = -1; break; } } else if (r > 0) { chunkqueue_mark_written(cq, r); } break; } if (0 == r) break; /*(might block)*/ switch (r) { case -1: /* fatal error */ return -1; case -2: /* connection reset */ log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI"); /* skip all remaining data */ chunkqueue_mark_written(cq, chunkqueue_length(cq)); break; default: break; } } if (cq->bytes_out == (off_t)con->request.content_length) { /* sent all request body input */ /* close connection to the cgi-script */ if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/ --srv->cur_fds; if (close(fd)) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno)); } } else { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } } else { off_t cqlen = cq->bytes_in - cq->bytes_out; if (cq->bytes_in != con->request.content_length && cqlen < 65536 - 16384) { /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; con->is_readable = 1; /* trigger optimistic read from client */ } } if (-1 == hctx->fdtocgi) { /*(not registered yet)*/ hctx->fdtocgi = fd; hctx->fde_ndx_tocgi = -1; fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx); } if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/ if ((fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT)) { fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, 0); } } else { /* more request body remains to be sent to CGI so register for fdevents */ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT); } } return 0; }
static void cgi_connection_close(server *srv, handler_ctx *hctx) { int status; pid_t pid; plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; #ifndef __WIN32 /* the connection to the browser went away, but we still have a connection * to the CGI script * * close cgi-connection */ if (hctx->fd != -1) { /* close connection to the cgi-script */ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); fdevent_unregister(srv->ev, hctx->fd); fdevent_sched_close(srv->ev, hctx->fd, 0); } if (hctx->fdtocgi != -1) { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } pid = hctx->pid; con->plugin_ctx[p->id] = NULL; cgi_handler_ctx_free(hctx); /* if waitpid hasn't been called by response.c yet, do it here */ if (pid) { /* check if the CGI-script is already gone */ switch(waitpid(pid, &status, WNOHANG)) { case 0: /* not finished yet */ #if 0 log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", pid); #endif break; case -1: /* */ if (errno == EINTR) break; /* * errno == ECHILD happens if _subrequest catches the process-status before * we have read the response of the cgi process * * -> catch status * -> WAIT_FOR_EVENT * -> read response * -> we get here with waitpid == ECHILD * */ if (errno != ECHILD) { log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno)); } /* anyway: don't wait for it anymore */ pid = 0; break; default: if (WIFEXITED(status)) { #if 0 log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid); #endif } else { log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid); } pid = 0; break; } if (pid) { kill(pid, SIGTERM); /* cgi-script is still alive, queue the PID for removal */ cgi_pid_add(srv, p, pid); } } #endif /* finish response (if not already con->file_started, con->file_finished) */ if (con->mode == p->id) { http_response_backend_done(srv, con); } }