/* * Run the Ejscript request. The routine runs when all input data has been received. */ static void runEjs(MaQueue *q) { MaConn *conn; MaRequest *req; EjsWeb *web; char msg[MPR_MAX_STRING]; conn = q->conn; req = conn->request; web = q->queueData = conn->response->handlerData; maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); maDontCacheResponse(conn); maPutForService(q, maCreateHeaderPacket(conn), 0); if (ejsRunWebRequest(web) < 0) { // TODO - refactor. Want request failed to have an option which says send this output to the client also. if (web->flags & EJS_WEB_FLAG_BROWSER_ERRORS) { // TODO - this API should allocate a buffer and not use a static buffer mprEscapeHtml(msg, sizeof(msg), web->error); maFormatBody(conn, "Request Failed", "<h1>Ejscript error for \"%s\"</h1>\r\n<h2>%s</h2>\r\n" "<p>To prevent errors being displayed in the browser, " "use <b>\"EjsErrors log\"</b> in the config file.</p>\r\n", web->url, web->error); } maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, web->error); } maPutForService(q, maCreateEndPacket(conn), 1); }
static void runDir(MaQueue *q) { MaConn *conn; MaResponse *resp; MaRequest *req; MprList *list; MprDirEntry *dp; Dir *dir; cchar *filename; uint nameSize; int next; conn = q->conn; req = conn->request; resp = conn->response; dir = q->stage->stageData; filename = resp->filename; mprAssert(filename); maDontCacheResponse(conn); maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); maPutForService(q, maCreateHeaderPacket(q), 0); parseQuery(conn); list = mprGetPathFiles(conn, filename, 1); if (list == 0) { maWrite(q, "<h2>Can't get file list</h2>\r\n"); outputFooter(q); return; } if (dir->pattern) { filterDirList(conn, list); } sortList(conn, list); /* * Get max filename */ nameSize = 0; for (next = 0; (dp = mprGetNextItem(list, &next)) != 0; ) { nameSize = max((int) strlen(dp->name), nameSize); } nameSize = max(nameSize, 22); outputHeader(q, req->url, nameSize); for (next = 0; (dp = mprGetNextItem(list, &next)) != 0; ) { outputLine(q, dp, filename, nameSize); } outputFooter(q); maPutForService(q, maCreateEndPacket(conn), 1); mprFree(list); }
/* * Join a packet onto the service queue */ void maJoinForService(MaQueue *q, MaPacket *packet, bool serviceQ) { MaPacket *old; if (q->first == 0) { /* * Just use the service queue as a holding queue while we aggregate the post data. */ maPutForService(q, packet, 0); maCheckQueueCount(q); } else { maCheckQueueCount(q); q->count += maGetPacketLength(packet); if (q->first && maGetPacketLength(q->first) == 0) { old = q->first; packet = q->first->next; q->first = packet; maFreePacket(q, old); } else { /* * Aggregate all data into one packet and free the packet. */ maJoinPacket(q->first, packet); maCheckQueueCount(q); maFreePacket(q, packet); } } maCheckQueueCount(q); if (serviceQ && !(q->flags & MA_QUEUE_DISABLED)) { maScheduleQueue(q); } }
/* This routine runs after all incoming data has been received */ static void runCgi(MaQueue *q) { MaResponse *resp; MaConn *conn; MprCmd *cmd; conn = q->conn; resp = conn->response; cmd = (MprCmd*) q->queueData; if (cmd == 0) { startCgi(q); cmd = (MprCmd*) q->queueData; if (q->pair->count > 0) { writeToCGI(q->pair); } } /* Close the CGI program's stdin. This will allow it to exit if it was expecting input data. */ mprCloseCmdFd(cmd, MPR_CMD_STDIN); if (conn->requestFailed) { maPutForService(q, maCreateEndPacket(q), 1); return; } while (mprWaitForCmd(cmd, 1000) < 0) { if (mprGetElapsedTime(cmd, cmd->lastActivity) >= conn->host->timeout) { break; } } if (cmd->pid == 0) { maPutForService(q, maCreateEndPacket(q), 1); } else { mprStopCmd(cmd); mprReapCmd(cmd, MPR_TIMEOUT_STOP_TASK); cmd->status = 255; } }
/* * This runs when all input data has been received. The egi form must write all the data. * It currently does not support forms that return before writing all the data. */ static void runEgi(MaQueue *q) { MaConn *conn; MaRequest *req; MaEgiForm *form; MaEgi *egi; conn = q->conn; req = conn->request; egi = (MaEgi*) q->stage->stageData; maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); maDontCacheResponse(conn); maPutForService(q, maCreateHeaderPacket(conn), 0); form = (MaEgiForm*) mprLookupHash(egi->forms, req->url); if (form == 0) { maFailRequest(conn, MPR_HTTP_CODE_NOT_FOUND, "Egi Form: \"%s\" is not defined", req->url); } else { (*form)(q); } maPutForService(q, maCreateEndPacket(conn), 1); }
/* Accept incoming body data from the client (via the pipeline) destined for the CGI gateway. This is typically POST or PUT data. */ static void incomingCgiData(MaQueue *q, MaPacket *packet) { MaConn *conn; MaResponse *resp; MaRequest *req; MprCmd *cmd; mprAssert(q); mprAssert(packet); conn = q->conn; resp = conn->response; req = conn->request; cmd = (MprCmd*) q->pair->queueData; if (cmd) { cmd->lastActivity = mprGetTime(cmd); } if (maGetPacketLength(packet) == 0) { /* End of input */ if (req->remainingContent > 0) { /* Short incoming body data. Just kill the CGI process. */ mprFree(cmd); q->queueData = 0; maFailRequest(conn, MPR_HTTP_CODE_BAD_REQUEST, "Client supplied insufficient body data"); } if (req->form) { maAddVarsFromQueue(req->formVars, q); } } else { /* No service routine, we just need it to be queued for writeToCGI */ if (req->form) { maJoinForService(q, packet, 0); } else { maPutForService(q, packet, 0); } } if (cmd) { writeToCGI(q); } }
/* * Write a block of data. This is the lowest level write routine for dynamic data. If block is true, this routine will * block until all the block is written. If block is false, then it may return without having written all the data. */ int maWriteBlock(MaQueue *q, cchar *buf, int size, bool block) { MaPacket *packet; MaConn *conn; MaResponse *resp; int bytes, written, packetSize; mprAssert(q->stage->flags & MA_STAGE_HANDLER); conn = q->conn; resp = conn->response; packetSize = (resp->chunkSize > 0) ? resp->chunkSize : q->max; packetSize = min(packetSize, size); if ((q->flags & MA_QUEUE_DISABLED) || (q->count > 0 && (q->count + size) >= q->max)) { if (!drain(q, block)) { return 0; } } for (written = 0; size > 0; ) { if (q->count >= q->max && !drain(q, block)) { maCheckQueueCount(q); break; } if (conn->disconnected) { return MPR_ERR_CANT_WRITE; } if ((packet = maCreateDataPacket(q, packetSize)) == 0) { return MPR_ERR_NO_MEMORY; } if ((bytes = mprPutBlockToBuf(packet->content, buf, size)) == 0) { return MPR_ERR_NO_MEMORY; } buf += bytes; size -= bytes; written += bytes; maPutForService(q, packet, 1); maCheckQueueCount(q); } maCheckQueueCount(q); return written; }
/* * Process request body data (typically post or put content). Packet will be null if the client closed the * connection to signify end of data. */ static bool processContent(MaConn *conn, MaPacket *packet) { MaRequest *req; MaResponse *resp; MaQueue *q; MaHost *host; MprBuf *content; int64 remaining; int nbytes; req = conn->request; resp = conn->response; host = conn->host; q = &resp->queue[MA_QUEUE_RECEIVE]; mprAssert(packet); if (packet == 0) { return 0; } if (conn->connectionFailed) { conn->state = MPR_HTTP_STATE_PROCESSING; maPutForService(resp->queue[MA_QUEUE_SEND].nextQ, maCreateHeaderPacket(resp), 1); return 1; } content = packet->content; if (req->flags & MA_REQ_CHUNKED) { if ((remaining = getChunkPacketSize(conn, content)) == 0) { /* Need more data or bad chunk specification */ if (mprGetBufLength(content) > 0) { conn->input = packet; } return 0; } } else { remaining = req->remainingContent; } nbytes = (int) min(remaining, mprGetBufLength(content)); mprAssert(nbytes >= 0); mprLog(conn, 7, "processContent: packet of %d bytes, remaining %Ld", mprGetBufLength(content), remaining); if (maShouldTrace(conn, MA_TRACE_REQUEST | MA_TRACE_BODY)) { maTraceContent(conn, packet, 0, 0, MA_TRACE_REQUEST | MA_TRACE_BODY); } if (nbytes > 0) { mprAssert(maGetPacketLength(packet) > 0); remaining -= nbytes; req->remainingContent -= nbytes; req->receivedContent += nbytes; if (req->receivedContent >= host->limits->maxBody) { conn->keepAliveCount = 0; maFailConnection(conn, MPR_HTTP_CODE_REQUEST_TOO_LARGE, "Request content body is too big %Ld vs limit %Ld", req->receivedContent, host->limits->maxBody); return 1; } if (packet == req->headerPacket) { /* Preserve headers if more data to come. Otherwise handlers may free the packet and destory the headers */ packet = maSplitPacket(resp, packet, 0); } else { mprStealBlock(resp, packet); } conn->input = 0; if (remaining == 0 && mprGetBufLength(packet->content) > nbytes) { /* * Split excess data belonging to the next pipelined request. */ mprLog(conn, 7, "processContent: Split packet of %d at %d", maGetPacketLength(packet), nbytes); conn->input = maSplitPacket(conn, packet, nbytes); mprAssert(mprGetParent(conn->input) == conn); } if ((q->count + maGetPacketLength(packet)) > q->max) { conn->keepAliveCount = 0; maFailConnection(q->conn, MPR_HTTP_CODE_REQUEST_TOO_LARGE, "Too much body data"); return 1; } maPutNext(q, packet); } else { conn->input = 0; mprStealBlock(resp, packet); } if (req->remainingContent == 0 || conn->requestFailed) { /* * End of input. Send a zero packet EOF signal and enable the handler send queue. */ if (req->remainingContent > 0 && conn->protocol > 0 && !conn->requestFailed) { maFailConnection(conn, MPR_HTTP_CODE_COMMS_ERROR, "Insufficient content data sent with request"); } else { maPutNext(q, maCreateEndPacket(resp)); conn->state = MPR_HTTP_STATE_PROCESSING; maRunPipeline(conn); } return 1; } maServiceQueues(conn); return conn->input ? mprGetBufLength(conn->input->content) : 0; }
static void runPhp(MaQueue *q) { MaConn *conn; MaRequest *req; MaResponse *resp; MaPhp *php; FILE *fp; char shebang[MPR_MAX_STRING]; zend_file_handle file_handle; TSRMLS_FETCH(); conn = q->conn; req = conn->request; resp = conn->response; php = q->queueData; maPutForService(q, maCreateHeaderPacket(q), 0); /* * Set the request context */ zend_first_try { php->var_array = 0; SG(server_context) = conn; if (req->user) { SG(request_info).auth_user = estrdup(req->user); } if (req->password) { SG(request_info).auth_password = estrdup(req->password); } if (req->authType && req->authDetails) { SG(request_info).auth_digest = estrdup(mprAsprintf(req, -1, "%s %s", req->authType, req->authDetails)); } SG(request_info).auth_password = req->password; SG(request_info).content_type = req->mimeType; SG(request_info).content_length = req->length; SG(sapi_headers).http_response_code = MPR_HTTP_CODE_OK; SG(request_info).path_translated = resp->filename; SG(request_info).query_string = req->parsedUri->query; SG(request_info).request_method = req->methodName; SG(request_info).request_uri = req->url; /* * Workaround on MAC OS X where the SIGPROF is given to the wrong thread */ PG(max_input_time) = -1; EG(timeout_seconds) = 0; /* The readPostData callback may be invoked during startup */ php_request_startup(TSRMLS_C); CG(zend_lineno) = 0; } zend_catch { mprError(q, "Can't start PHP request"); zend_try { php_request_shutdown(0); } zend_end_try(); maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "PHP initialization failed"); return; } zend_end_try(); /* * Execute the script file */ file_handle.filename = resp->filename; file_handle.free_filename = 0; file_handle.opened_path = 0; #if LOAD_FROM_FILE file_handle.type = ZEND_HANDLE_FILENAME; #else file_handle.type = ZEND_HANDLE_FP; if ((fp = fopen(resp->filename, "r")) == NULL) { maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "PHP can't open script"); return; } /* Check for shebang and skip */ file_handle.handle.fp = fp; shebang[0] = '\0'; if (fgets(shebang, sizeof(shebang), file_handle.handle.fp) != 0) { if (shebang[0] != '#' || shebang[1] != '!') { fseek(fp, 0L, SEEK_SET); } } #endif zend_try { php_execute_script(&file_handle TSRMLS_CC); if (!SG(headers_sent)) { sapi_send_headers(TSRMLS_C); } } zend_catch { php_request_shutdown(0); maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "PHP script execution failed"); return; } zend_end_try(); zend_try { php_request_shutdown(0); } zend_end_try(); maPutForService(q, maCreateEndPacket(q), 1); }
static void startCgi(MaQueue *q) { MaRequest *req; MaConn *conn; MprCmd *cmd; MprHash *hp; cchar *baseName; char **argv, **envv, *fileName; int index, argc, varCount; argv = 0; argc = 0; conn = q->conn; req = conn->request; if ((req->form || req->flags & MA_REQ_UPLOADING) && conn->state <= MPR_HTTP_STATE_CONTENT) { /* Delay starting the CGI process if uploading files or a form request. This enables env vars to be defined with file upload and form data before starting the CGI gateway. */ return; } cmd = q->queueData = mprCreateCmd(req); if (conn->http->forkCallback) { cmd->forkCallback = conn->http->forkCallback; cmd->forkData = conn->http->forkData; } /* Build the commmand line arguments */ argc = 1; /* argv[0] == programName */ buildArgs(conn, cmd, &argc, &argv); fileName = argv[0]; baseName = mprGetPathBase(q, fileName); if (strncmp(baseName, "nph-", 4) == 0 || (strlen(baseName) > 4 && strcmp(&baseName[strlen(baseName) - 4], "-nph") == 0)) { /* Pretend we've seen the header for Non-parsed Header CGI programs */ cmd->userFlags |= MA_CGI_SEEN_HEADER; } /* Build environment variables */ varCount = mprGetHashCount(req->headers) + mprGetHashCount(req->formVars); envv = (char**) mprAlloc(cmd, (varCount + 1) * (int) sizeof(char*)); index = 0; hp = mprGetFirstHash(req->headers); while (hp) { if (hp->data) { envv[index] = mprStrcat(cmd, -1, hp->key, "=", (char*) hp->data, NULL); index++; } hp = mprGetNextHash(req->headers, hp); } hp = mprGetFirstHash(req->formVars); while (hp) { if (hp->data) { envv[index] = mprStrcat(cmd, -1, hp->key, "=", (char*) hp->data, NULL); index++; } hp = mprGetNextHash(req->formVars, hp); } envv[index] = 0; mprAssert(index <= varCount); cmd->stdoutBuf = mprCreateBuf(cmd, MA_BUFSIZE, -1); cmd->stderrBuf = mprCreateBuf(cmd, MA_BUFSIZE, -1); cmd->lastActivity = mprGetTime(cmd); mprSetCmdDir(cmd, mprGetPathDir(q, fileName)); mprSetCmdCallback(cmd, cgiCallback, conn); maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); maDontCacheResponse(conn); maPutForService(q, maCreateHeaderPacket(q), 0); if (mprStartCmd(cmd, argc, argv, envv, MPR_CMD_IN | MPR_CMD_OUT | MPR_CMD_ERR) < 0) { maFailRequest(conn, MPR_HTTP_CODE_SERVICE_UNAVAILABLE, "Can't run CGI process: %s, URI %s", fileName, req->url); return; } /* This will dedicate this thread to the connection. It will also put the socket into blocking mode. */ maDedicateThreadToConn(conn); }
/* Parse the CGI output headers. Sample CGI program: Content-type: text/html <html..... */ static bool parseHeader(MaConn *conn, MprCmd *cmd) { MaResponse *resp; MaQueue *q; MprBuf *buf; char *endHeaders, *headers, *key, *value, *location; int fd, len; resp = conn->response; location = 0; value = 0; buf = mprGetCmdBuf(cmd, MPR_CMD_STDOUT); mprAddNullToBuf(buf); headers = mprGetBufStart(buf); /* Split the headers from the body. */ len = 0; fd = mprGetCmdFd(cmd, MPR_CMD_STDOUT); if ((endHeaders = strstr(headers, "\r\n\r\n")) == NULL) { if ((endHeaders = strstr(headers, "\n\n")) == NULL) { if (fd >= 0 && strlen(headers) < MA_MAX_HEADERS) { /* Not EOF and less than max headers and have not yet seen an end of headers delimiter */ return 0; } } else len = 2; } else { len = 4; } if (endHeaders) { endHeaders[len - 1] = '\0'; endHeaders += len; } /* Want to be tolerant of CGI programs that omit the status line. */ if (strncmp((char*) buf->start, "HTTP/1.", 7) == 0) { if (!parseFirstCgiResponse(conn, cmd)) { /* maFailConnection already called */ return 0; } } if (endHeaders && strchr(mprGetBufStart(buf), ':')) { mprLog(conn, 4, "CGI: parseHeader: header\n%s", headers); while (mprGetBufLength(buf) > 0 && buf->start[0] && (buf->start[0] != '\r' && buf->start[0] != '\n')) { if ((key = getCgiToken(buf, ":")) == 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad header format"); return 0; } value = getCgiToken(buf, "\n"); while (isspace((int) *value)) { value++; } len = (int) strlen(value); while (len > 0 && (value[len - 1] == '\r' || value[len - 1] == '\n')) { value[len - 1] = '\0'; len--; } mprStrLower(key); if (strcmp(key, "location") == 0) { location = value; } else if (strcmp(key, "status") == 0) { maSetResponseCode(conn, atoi(value)); } else if (strcmp(key, "content-type") == 0) { maSetResponseMimeType(conn, value); } else if (strcmp(key, "content-length") == 0) { maSetEntityLength(conn, (MprOff) mprAtoi(value, 10)); resp->chunkSize = 0; } else { /* Now pass all other headers back to the client */ maSetHeader(conn, 0, key, "%s", value); } } buf->start = endHeaders; } if (location) { maRedirect(conn, resp->code, location); q = resp->queue[MA_QUEUE_SEND].nextQ; maPutForService(q, maCreateEndPacket(q), 1); } cmd->userFlags |= MA_CGI_SEEN_HEADER; return 1; }