static bool mapToFile(MaConn *conn, bool *rescan) { MaRequest *req; MaResponse *resp; req = conn->request; resp = conn->response; if (resp->filename == 0) { resp->filename = makeFilename(conn, req->alias, req->url, 1); } req->dir = maLookupBestDir(req->host, resp->filename); if (req->dir == 0) { maFailRequest(conn, MPR_HTTP_CODE_NOT_FOUND, "Missing directory block for %s", resp->filename); return 0; } mprAssert(req->dir); req->auth = req->dir->auth; if (!resp->fileInfo.valid && mprGetFileInfo(conn, resp->filename, &resp->fileInfo) < 0) { #if UNUSED if (req->method & (MA_REQ_GET | MA_REQ_POST)) { maFailRequest(conn, MPR_HTTP_CODE_NOT_FOUND, "Can't open document: %s", resp->filename); return 0; } #endif } if (resp->fileInfo.isDir) { processDirectory(conn, rescan); } return 1; }
/* Parse the CGI output first line */ static bool parseFirstCgiResponse(MaConn *conn, MprCmd *cmd) { MprBuf *buf; char *protocol, *code, *message; buf = mprGetCmdBuf(cmd, MPR_CMD_STDOUT); protocol = getCgiToken(buf, " "); if (protocol == 0 || protocol[0] == '\0') { maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, "Bad CGI HTTP protocol response"); return 0; } if (strncmp(protocol, "HTTP/1.", 7) != 0) { maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, "Unsupported CGI protocol"); return 0; } code = getCgiToken(buf, " "); if (code == 0 || *code == '\0') { maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, "Bad CGI header response"); return 0; } message = getCgiToken(buf, "\n"); mprLog(conn, 4, "CGI status line: %s %s %s", protocol, code, message); return 1; }
static void openPhp(MaQueue *q) { MaRequest *req; MaResponse *resp; MaConn *conn; conn = q->conn; if (!q->stage->stageData) { if (initializePhp(conn->http) < 0) { maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "PHP initialization failed"); } q->stage->stageData = (void*) 1; } resp = conn->response; req = conn->request; switch (req->method) { case MA_REQ_GET: case MA_REQ_HEAD: case MA_REQ_POST: case MA_REQ_PUT: q->queueData = mprAllocObjZeroed(resp, MaPhp); maDontCacheResponse(conn); maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); break; case MA_REQ_DELETE: default: maFailRequest(q->conn, MPR_HTTP_CODE_BAD_METHOD, "Method not supported by file handler: %s", req->methodName); break; } }
/* * When we come here, we've already matched either a location block or an extension */ static bool matchEjs(MaConn *conn, MaStage *handler, cchar *url) { MaRequest *req; MaLocation *loc; EjsWeb *web; cchar *ext; char urlbuf[MPR_MAX_FNAME]; loc = conn->request->location; #if UNUSED if (!(loc->flags & (MA_LOC_APP | MA_LOC_APP_DIR))) { return 1; } #endif /* * Send non-ejs content under web to another handler, typically the file handler. */ req = conn->request; ext = conn->response->extension; if (ext && strcmp(ext, "mod") == 0) { maFormatBody(conn, "Bad Request", "Can't serve *.mod files"); maFailRequest(conn, MPR_HTTP_CODE_BAD_REQUEST, "Can't server *.mod files"); return 1; } if (parseUrl(conn) < 0) { return 1; } web = (EjsWeb*) conn->response->handlerData; /* * TODO - need a more general way of handling these routes. Push back into the Ejscript framework */ url = web->url; if (*url == '\0' || strcmp(url, "/") == 0) { mprSprintf(urlbuf, sizeof(urlbuf), "%s/web/index.ejs", web->appUrl); maSetRequestUri(conn, urlbuf); return 0; } else if (strcmp(url, "/favicon.ico") == 0) { mprSprintf(urlbuf, sizeof(urlbuf), "%s/web/favicon.ico", web->appUrl); maSetRequestUri(conn, urlbuf); return 0; } else if (strncmp(url, "/web/", 5) == 0 || *url == '\0') { if (!(ext && strcmp(ext, "ejs") == 0)) { return 0; } } else { if (loc->flags & (MA_LOC_APP | MA_LOC_APP_DIR) && ext && strcmp(ext, "ejs") == 0) { maFormatBody(conn, "Bad Request", "Can't serve *.ejs files outside web directory"); maFailRequest(conn, MPR_HTTP_CODE_BAD_REQUEST, "Can't server *.ejs files outside web directory"); } } return 1; }
/* * 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 upload(MaQueue *q) { MaConn *conn; char *sw; char *newLocation; int responseStatus; conn = q->conn; newLocation = 0; responseStatus = 0; sw = (char*) strstr(maGetFormVar(conn, "QUERY_STRING", ""), "SWITCHES="); if (sw) { sw = mprStrdup(q, sw + 9); mprUrlDecode(sw, (int) strlen(sw) + 1, sw); if (*sw == '-') { if (sw[1] == 'l') { newLocation = sw + 3; } else if (sw[1] == 's') { responseStatus = atoi(sw + 3); } } } maSetResponseCode(conn, 200); maSetResponseMimeType(conn, "text/html"); maDontCacheResponse(conn); /* * Test writing headers. The Server header overwrote the "Server" header * * maSetHeader(conn, "MyCustomHeader: true"); * maSetHeader(conn, "Server: private"); */ if (maGetCookies(conn) == 0) { maSetCookie(conn, "appwebTest", "Testing can be fun", 43200, "/", 0); } if (newLocation) { maRedirect(conn, 302, newLocation); } else if (responseStatus) { maFailRequest(conn, responseStatus, "Custom Status"); } else { maWrite(q, "<HTML><TITLE>egiProgram: EGI Output</TITLE><BODY>\r\n"); printRequestHeaders(q); printQueryData(q); printBodyData(q); maWrite(q, "</BODY></HTML>\r\n"); } if (sw) { mprFree(sw); } }
static void error(void *handle, int code, cchar *fmt, ...) { va_list args; char *msg; va_start(args, fmt); mprAllocVsprintf(handle, &msg, -1, fmt, args); maFailRequest(handle, code, "%s", msg); }
/* * Format an authentication response. This is typically a 401 response code. */ static void formatAuthResponse(MaConn *conn, MaAuth *auth, int code, char *msg, char *logMsg) { MaRequest *req; #if BLD_FEATURE_AUTH_DIGEST char *qopClass, *nonceStr, *etag; #endif req = conn->request; if (logMsg == 0) { logMsg = msg; } mprLog(conn, 3, "formatAuthResponse: code %d, %s\n", code, logMsg); if (auth->type == MA_AUTH_BASIC) { maSetHeader(conn, 0, "WWW-Authenticate", "Basic realm=\"%s\"", auth->requiredRealm); #if BLD_FEATURE_AUTH_DIGEST } else if (auth->type == MA_AUTH_DIGEST) { qopClass = auth->qop; /* * Use the etag as our opaque string */ etag = conn->response->etag; if (etag == 0) { etag = ""; } if (etag[0] == '"') { etag = mprStrdup(req, etag); etag = mprStrTrim(etag, "\""); } mprCalcDigestNonce(req, &nonceStr, conn->host->secret, etag, auth->requiredRealm); if (strcmp(qopClass, "auth") == 0) { maSetHeader(conn, 0, "WWW-Authenticate", "Digest realm=\"%s\", domain=\"%s\", " "qop=\"auth\", nonce=\"%s\", opaque=\"%s\", algorithm=\"MD5\", stale=\"FALSE\"", auth->requiredRealm, conn->host->name, nonceStr, etag); } else if (strcmp(qopClass, "auth-int") == 0) { maSetHeader(conn, 0, "WWW-Authenticate", "Digest realm=\"%s\", domain=\"%s\", " "qop=\"auth\", nonce=\"%s\", opaque=\"%s\", algorithm=\"MD5\", stale=\"FALSE\"", auth->requiredRealm, conn->host->name, nonceStr, etag); } else { maSetHeader(conn, 0, "WWW-Authenticate", "Digest realm=\"%s\", nonce=\"%s\"", auth->requiredRealm, nonceStr); } mprFree(nonceStr); #endif } maFailRequest(conn, code, "Authentication Error: %s", msg); }
/* * Invoked to initialize the send connector for a request */ static void sendOpen(MaQueue *q) { MaConn *conn; MaResponse *resp; conn = q->conn; resp = conn->response; if (!conn->requestFailed && !(resp->flags & MA_RESP_NO_BODY)) { resp->file = mprOpen(q, resp->filename, O_RDONLY | O_BINARY, 0); if (resp->file == 0) { maFailRequest(conn, MPR_HTTP_CODE_NOT_FOUND, "Can't open document: %s", resp->filename); } } }
/* 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); } }
static void writeToCGI(MaQueue *q) { MaConn *conn; MaPacket *packet; MprCmd *cmd; MprBuf *buf; int len, rc, err; cmd = (MprCmd*) q->pair->queueData; mprAssert(cmd); conn = q->conn; for (packet = maGet(q); packet && !conn->requestFailed; packet = maGet(q)) { buf = packet->content; len = mprGetBufLength(buf); mprAssert(len > 0); rc = mprWriteCmdPipe(cmd, MPR_CMD_STDIN, mprGetBufStart(buf), len); mprLog(q, 5, "CGI: write %d bytes to gateway. Rc rc %d, errno %d", len, rc, mprGetOsError()); if (rc < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprLog(q, 2, "CGI: write to gateway failed for %d bytes, rc %d, errno %d", len, rc, mprGetOsError()); mprCloseCmdFd(cmd, MPR_CMD_STDIN); maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, "Can't write body data to CGI gateway"); break; } else { mprLog(q, 5, "CGI: write to gateway %d bytes asked to write %d", rc, len); mprAdjustBufStart(buf, rc); if (mprGetBufLength(buf) > 0) { maPutBack(q, packet); } else { maFreePacket(q, packet); } } } }
static void myEgi(MaQueue *q) { MaConn *conn; conn = q->conn; maSetResponseCode(conn, 200); maWrite(q, "<HTML><TITLE>simpleEgi</TITLE><BODY>\r\n"); maWrite(q, "<p>Name: %s</p>\n", maGetFormVar(conn, "name", "-")); maWrite(q, "<p>Address: %s</p>\n", maGetFormVar(conn, "address", "-")); maWrite(q, "</BODY></HTML>\r\n"); #if POSSIBLE /* * Useful things to do in egi forms */ maSetResponseCode(conn, 200); maSetResponseMimeType(conn, "text/plain"); maDontCacheResponse(conn); maRedirect(conn, 302, "/myURl"); maFailRequest(conn, 409, "My message : %d", 5); #endif }
/* * 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); }
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); }
/* * When we come here, we've already matched either a location block or an extension */ static int run(request_rec *r) { EjsDirConfig *dir; EjsServerConfig *server; cchar *ext; MaRequest *req; MaResponse *resp; MaConn *conn; MaAlias *alias; MaLocation *location; EjsWeb *web; cchar *sep, *prefix; char *urlBase, *dir, *app, *url, *cp; int flags, locFlags; if (!r->handler || strcasecmp(r->handler, "ejs") != 0) { return DECLINED; } dir = getDir(r) server = getServer(r->XXX); // EjsAlias should probably be creating a directory block. These flags should probably be in a directory if (loc->flags & (MA_LOC_APP | MA_LOC_APP_DIR)) { /* * Send non-ejs content under web to another handler, typically the file handler. */ if (strncmp(&req->url[loc->prefixLen], "web/", 4) == 0) { if (!(ext && strcmp(ext, "ejs") == 0)) { return DECLINED; } } else { if (ext && strcmp(ext, "ejs") == 0) { maFormatBody(conn, "Bad Request", "Can't serve *.ejs files outside web directory"); maFailRequest(conn, MPR_HTTP_CODE_BAD_REQUEST, "Can't server *.ejs files outside web directory"); return HTTP_XXX; } } } flags = 0; url = 0; locFlags = location->flags; if (locFlags & MA_LOC_APP) { app = mprStrTrim(mprStrdup(q, prefix), "/"); url = &req->url[alias->prefixLen]; dir = mprStrdup(resp, alias->filename); if (*url != '/') { url--; } urlBase = mprStrdup(resp, prefix); } else if (locFlags & MA_LOC_APP_DIR) { url = &req->url[alias->prefixLen]; app = mprStrdup(resp, url); if ((cp = strchr(app, '/')) != 0) { url = mprStrdup(resp, cp); *cp = '\0'; } sep = prefix[strlen(prefix) - 1] == '/' ? "" : "/"; dir = mprStrcat(resp, &dir, alias->filename, sep, app, NULL); urlBase = mprStrcat(resp, prefix, sep, app, NULL); } else { app = 0; dir = mprStrdup(resp, alias->filename); url = &req->url[alias->prefixLen]; flags |= EJS_WEB_FLAG_SOLO; if (*url != '/') { url--; } urlBase = mprStrdup(resp, prefix); } mprStrTrim(urlBase, "/"); mprStrTrim(dir, "/"); if (location->flags & MA_LOC_BROWSER) { flags |= EJS_WEB_FLAG_BROWSER_ERRORS; } if (location->flags & MA_LOC_AUTO_SESSION) { flags |= EJS_WEB_FLAG_SESSION; } /* * Var Stand-Alone App AppDir * app 0 carmen carmen * dir /Users/mob/.... /Users/mob/hg/carmen /Users/mob/hg/carmen * urlBase /xg/carmen /carmen * url stock stock */ web = ejsCreateWebRequest(req, q->stage->stageData, conn, app, dir, urlBase, url, req->cookie, flags); if (web == 0) { maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create Ejs web object for %s", req->url); return; } q->queueData = web; maSetHeader(conn, 0, "Last-Modified", req->host->currentDate); maDontCacheResponse(conn); if (r->method_number != M_GET) { return HTTP_METHOD_NOT_ALLOWED; } if (ejsProcessWebRequest((EjsWeb*) r, &msg) < 0) { if (web->flags & EJS_WEB_FLAG_BROWSER_ERRORS) { maFormatBody(conn, "Request Failed", "%s", msg); } maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, msg); mprFree(msg); } ap_set_content_type(r, "text/html"); ap_rputs("<html><title>Hello World!</title><body><p>Hello World</p></body></html>\r\n", r); #if 0 if ((err = set_content_length(r, r->finfo.st_stize)) || (err = set_last_modified(r, r->finfo.st_mtime))) return err; if (r->finof.st_mode == 0) return NOT_FOUND; fopen(r->filename, "r"); if (!r->main) { /* Not internal redirect */ apr_table_set(r->headers_out, "X-ejs", "Under construction"); } register_timeout("send", r); send_http_header(r); if (!r->header_only) send_fd(f, r); pfclose(r->pool, f); #endif return OK; }
/* * Parse the request headers. Return true if the header parsed. */ static bool parseHeaders(MaConn *conn, MaPacket *packet) { MaHostAddress *address; MaRequest *req; MaHost *host, *hp; MaLimits *limits; MprBuf *content; char keyBuf[MPR_MAX_STRING]; char *key, *value, *cp, *tok; int count, keepAlive; req = conn->request; host = req->host; content = packet->content; conn->request->headerPacket = packet; limits = &conn->http->limits; keepAlive = 0; strcpy(keyBuf, "HTTP_"); mprAssert(strstr((char*) content->start, "\r\n")); for (count = 0; content->start[0] != '\r' && !conn->connectionFailed; count++) { if (count >= limits->maxNumHeaders) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Too many headers"); return 0; } if ((key = getToken(conn, ":")) == 0 || *key == '\0') { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad header format"); return 0; } value = getToken(conn, "\r\n"); while (isspace((int) *value)) { value++; } if (conn->requestFailed) { continue; } mprStrUpper(key); for (cp = key; *cp; cp++) { if (*cp == '-') { *cp = '_'; } } mprLog(req, 8, "Key %s, value %s", key, value); if (strspn(key, "%<>/\\") > 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad header key value"); continue; } /* * Define the header with a "HTTP_" prefix */ mprStrcpy(&keyBuf[5], sizeof(keyBuf) - 5, key); mprAddDuplicateHash(req->headers, keyBuf, value); switch (key[0]) { case 'A': if (strcmp(key, "AUTHORIZATION") == 0) { value = mprStrdup(req, value); req->authType = mprStrTok(value, " \t", &tok); req->authDetails = tok; } else if (strcmp(key, "ACCEPT_CHARSET") == 0) { req->acceptCharset = value; } else if (strcmp(key, "ACCEPT") == 0) { req->accept = value; } else if (strcmp(key, "ACCEPT_ENCODING") == 0) { req->acceptEncoding = value; } break; case 'C': if (strcmp(key, "CONTENT_LENGTH") == 0) { if (req->length >= 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Mulitple content length headers"); continue; } req->length = mprAtoi(value, 10); if (req->length < 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad content length"); continue; } if (req->length >= host->limits->maxBody) { maFailConnection(conn, MPR_HTTP_CODE_REQUEST_TOO_LARGE, "Request content length %Ld is too big. Limit %Ld", req->length, host->limits->maxBody); continue; } mprAssert(req->length >= 0); req->remainingContent = req->length; req->contentLengthStr = value; } else if (strcmp(key, "CONTENT_RANGE") == 0) { /* * This headers specifies the range of any posted body data * Format is: Content-Range: bytes n1-n2/length * Where n1 is first byte pos and n2 is last byte pos */ char *sp; int start, end, size; start = end = size = -1; sp = value; while (*sp && !isdigit((int) *sp)) { sp++; } if (*sp) { start = (int) mprAtoi(sp, 10); if ((sp = strchr(sp, '-')) != 0) { end = (int) mprAtoi(++sp, 10); } if ((sp = strchr(sp, '/')) != 0) { /* * Note this is not the content length transmitted, but the original size of the input of which * the client is transmitting only a portion. */ size = (int) mprAtoi(++sp, 10); } } if (start < 0 || end < 0 || size < 0 || end <= start) { maFailRequest(conn, MPR_HTTP_CODE_RANGE_NOT_SATISFIABLE, "Bad content range"); continue; } req->inputRange = maCreateRange(conn, start, end); } else if (strcmp(key, "CONTENT_TYPE") == 0) { req->mimeType = value; req->form = strstr(value, "application/x-www-form-urlencoded") != 0; } else if (strcmp(key, "COOKIE") == 0) { if (req->cookie && *req->cookie) { req->cookie = mprStrcat(req, -1, req->cookie, "; ", value, NULL); } else { req->cookie = value; } } else if (strcmp(key, "CONNECTION") == 0) { req->connection = value; if (mprStrcmpAnyCase(value, "KEEP-ALIVE") == 0) { keepAlive++; } else if (mprStrcmpAnyCase(value, "CLOSE") == 0) { conn->keepAliveCount = 0; } if (!host->keepAlive) { conn->keepAliveCount = 0; } } break; case 'F': req->forwarded = value; break; case 'H': if (strcmp(key, "HOST") == 0) { req->hostName = value; address = conn->address; if (maIsNamedVirtualHostAddress(address)) { hp = maLookupVirtualHost(address, value); if (hp == 0) { maFailRequest(conn, 404, "No host to serve request. Searching for %s", value); mprLog(conn, 1, "Can't find virtual host %s", value); continue; } req->host = hp; /* * Reassign this request to a new host */ maRemoveConn(host, conn); host = hp; conn->host = hp; maAddConn(hp, conn); } } break; case 'I': if ((strcmp(key, "IF_MODIFIED_SINCE") == 0) || (strcmp(key, "IF_UNMODIFIED_SINCE") == 0)) { MprTime newDate = 0; char *cp; bool ifModified = (key[3] == 'M'); if ((cp = strchr(value, ';')) != 0) { *cp = '\0'; } if (mprParseTime(conn, &newDate, value, MPR_UTC_TIMEZONE, NULL) < 0) { mprAssert(0); break; } if (newDate) { setIfModifiedDate(conn, newDate, ifModified); req->flags |= MA_REQ_IF_MODIFIED; } } else if ((strcmp(key, "IF_MATCH") == 0) || (strcmp(key, "IF_NONE_MATCH") == 0)) { char *word, *tok; bool ifMatch = key[3] == 'M'; if ((tok = strchr(value, ';')) != 0) { *tok = '\0'; } req->ifMatch = ifMatch; req->flags |= MA_REQ_IF_MODIFIED; value = mprStrdup(conn, value); word = mprStrTok(value, " ,", &tok); while (word) { addMatchEtag(conn, word); word = mprStrTok(0, " ,", &tok); } } else if (strcmp(key, "IF_RANGE") == 0) { char *word, *tok; if ((tok = strchr(value, ';')) != 0) { *tok = '\0'; } req->ifMatch = 1; req->flags |= MA_REQ_IF_MODIFIED; value = mprStrdup(conn, value); word = mprStrTok(value, " ,", &tok); while (word) { addMatchEtag(conn, word); word = mprStrTok(0, " ,", &tok); } } break; case 'P': if (strcmp(key, "PRAGMA") == 0) { req->pragma = value; } break; case 'R': if (strcmp(key, "RANGE") == 0) { if (!parseRange(conn, value)) { maFailRequest(conn, MPR_HTTP_CODE_RANGE_NOT_SATISFIABLE, "Bad range"); } } else if (strcmp(key, "REFERER") == 0) { /* NOTE: yes the header is misspelt in the spec */ req->referer = value; } break; case 'T': if (strcmp(key, "TRANSFER_ENCODING") == 0) { mprStrLower(value); if (strcmp(value, "chunked") == 0) { req->flags |= MA_REQ_CHUNKED; /* * This will be revised by the chunk filter as chunks are processed and will be set to zero when the * last chunk has been received. */ req->remainingContent = MAXINT; } } break; #if BLD_DEBUG case 'X': if (strcmp(key, "X_APPWEB_CHUNK_SIZE") == 0) { mprStrUpper(value); conn->response->chunkSize = atoi(value); if (conn->response->chunkSize <= 0) { conn->response->chunkSize = 0; } else if (conn->response->chunkSize > conn->http->limits.maxChunkSize) { conn->response->chunkSize = conn->http->limits.maxChunkSize; } } break; #endif case 'U': if (strcmp(key, "USER_AGENT") == 0) { req->userAgent = value; } break; } } if (conn->protocol == 0 && !keepAlive) { conn->keepAliveCount = 0; } if (!(req->flags & MA_REQ_CHUNKED)) { /* * Step over "\r\n" after headers. As an optimization, don't do this if chunked so chunking can parse a single * chunk delimiter of "\r\nSIZE ...\r\n" */ mprAdjustBufStart(content, 2); } mprLog(conn, 3, "Select host \"%s\"", conn->host->name); if (maSetRequestUri(conn, req->url, "") < 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad URI format"); return 0; } if (conn->host->secure) { req->parsedUri->scheme = mprStrdup(req, "https"); } req->parsedUri->port = conn->sock->port; req->parsedUri->host = req->hostName ? req->hostName : conn->host->name; return 1; }
/* * Parse the first line of a http request. Return true if the first line parsed. This is only called once all the headers * have been read and buffered. */ static bool parseFirstLine(MaConn *conn, MaPacket *packet) { MaRequest *req; MaResponse *resp; MaHost *host; MprBuf *content; cchar *endp; char *methodName, *uri, *httpProtocol; int method, len; req = conn->request = maCreateRequest(conn); resp = conn->response = maCreateResponse(conn); host = conn->host; #if BLD_DEBUG req->startTime = mprGetTime(conn); req->startTicks = mprGetTicks(); #endif methodName = getToken(conn, " "); if (*methodName == '\0') { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad request method name"); return 0; } method = 0; switch (methodName[0]) { case 'D': if (strcmp(methodName, "DELETE") == 0) { method = MA_REQ_DELETE; } break; case 'G': if (strcmp(methodName, "GET") == 0) { method = MA_REQ_GET; } break; case 'P': if (strcmp(methodName, "POST") == 0) { method = MA_REQ_POST; } else if (strcmp(methodName, "PUT") == 0) { method = MA_REQ_PUT; } break; case 'H': if (strcmp(methodName, "HEAD") == 0) { method = MA_REQ_HEAD; resp->flags |= MA_RESP_NO_BODY; } break; case 'O': if (strcmp(methodName, "OPTIONS") == 0) { method = MA_REQ_OPTIONS; resp->flags |= MA_RESP_NO_BODY; } break; case 'T': if (strcmp(methodName, "TRACE") == 0) { method = MA_REQ_TRACE; resp->flags |= MA_RESP_NO_BODY; } break; } if (method == 0) { maFailConnection(conn, MPR_HTTP_CODE_BAD_METHOD, "Bad method"); return 0; } uri = getToken(conn, " "); if (*uri == '\0') { maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad HTTP request. Bad URI."); return 0; } if ((int) strlen(uri) >= conn->http->limits.maxUrl) { maFailRequest(conn, MPR_HTTP_CODE_REQUEST_URL_TOO_LARGE, "Bad request. URI too long."); return 0; } httpProtocol = getToken(conn, "\r\n"); if (strcmp(httpProtocol, "HTTP/1.1") == 0) { conn->protocol = 1; } else if (strcmp(httpProtocol, "HTTP/1.0") == 0) { conn->protocol = 0; if (method == MA_REQ_POST || method == MA_REQ_PUT) { req->remainingContent = MAXINT; } } else { maFailConnection(conn, MPR_HTTP_CODE_NOT_ACCEPTABLE, "Unsupported HTTP protocol"); return 0; } req->method = method; req->methodName = methodName; req->httpProtocol = httpProtocol; req->url = uri; if ((conn->trace = maSetupTrace(host, conn->response->extension)) != 0) { if (maShouldTrace(conn, MA_TRACE_REQUEST | MA_TRACE_HEADERS)) { mprLog(req, host->traceLevel, "\n@@@ New request from %s:%d to %s:%d\n%s %s %s", conn->remoteIpAddr, conn->remotePort, conn->sock->ipAddr, conn->sock->port, methodName, uri, httpProtocol); content = packet->content; endp = strstr((char*) content->start, "\r\n\r\n"); len = (endp) ? (int) (endp - mprGetBufStart(content) + 4) : 0; maTraceContent(conn, packet, len, 0, MA_TRACE_REQUEST | MA_TRACE_HEADERS); } } else { mprLog(conn, 2, "%s %s %s", methodName, uri, httpProtocol); } return 1; }
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 int parseUrl(MaConn *conn) { EjsWeb *web; EjsWebControl *control; MaRequest *req; MaResponse *resp; MaAlias *alias; MaLocation *location; char *baseDir, *cp, *url, *baseUrl; int flags, locFlags; resp = conn->response; req = conn->request; alias = req->alias; location = req->location; locFlags = location->flags; url = req->url; flags = 0; if (locFlags & MA_LOC_APP_DIR) { flags |= EJS_WEB_FLAG_APP; mprAllocSprintf(resp, &baseDir, -1, "%s%s", alias->filename, &req->url[alias->prefixLen]); if ((cp = strchr(&baseDir[strlen(alias->filename) + 1], '/')) != 0) { *cp = '\0'; } mprAllocSprintf(resp, &baseUrl, -1, "%s%s", alias->prefix, &req->url[alias->prefixLen]); if ((cp = strchr(&baseUrl[alias->prefixLen + 1], '/')) != 0) { *cp = '\0'; } if (*url) { /* Step over the directory and app name */ while (*++url != '/') ; if (*url) { while (*++url != '/') ; } } } else { if (locFlags & MA_LOC_APP) { flags |= EJS_WEB_FLAG_APP; } baseDir = alias->filename; if (alias->prefixLen > 0) { /* Step over the application name (same as alias prefix) */ url = &url[alias->prefixLen]; if (*url != '/' && url[-1] == '/') { url--; } } baseUrl = alias->prefix; } if (location->flags & MA_LOC_BROWSER) { flags |= EJS_WEB_FLAG_BROWSER_ERRORS; } if (location->flags & MA_LOC_AUTO_SESSION) { flags |= EJS_WEB_FLAG_SESSION; } control = conn->http->ejsHandler->stageData; web = ejsCreateWebRequest(req, control, conn, baseUrl, url, baseDir, flags); if (web == 0) { maFailRequest(conn, MPR_HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create Ejs web object for %s", req->url); return EJS_ERR; } resp->handlerData = web; return 0; }
/* * Match the request for authorization. This implements basic and digest authentication. */ static bool matchAuth(MaConn *conn, MaStage *stage, cchar *url) { MaRequest *req; MaAuth *auth; AuthData *ad; cchar *requiredPassword; char *msg; int actualAuthType; req = conn->request; auth = req->auth; if (auth == 0) { maFailRequest(conn, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Authorization enabled."); return 1; } if ((ad = mprAllocObjZeroed(req, AuthData)) == 0) { maFailRequest(conn, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Server Error."); return 1; } if (auth->type == 0) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Authorization required.", 0); return 1; } if (req->authDetails == 0) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Missing authorization details.", 0); return 1; } if (mprStrcmpAnyCase(req->authType, "basic") == 0) { decodeBasicAuth(conn, ad); actualAuthType = MA_AUTH_BASIC; #if BLD_FEATURE_AUTH_DIGEST } else if (mprStrcmpAnyCase(req->authType, "digest") == 0) { if (decodeDigestDetails(conn, ad) < 0) { maFailRequest(conn, 400, "Bad authorization header"); return 1; } actualAuthType = MA_AUTH_DIGEST; #endif } else { actualAuthType = MA_AUTH_UNKNOWN; } mprLog(conn, 4, "openAuth: type %d, url %s\nDetails %s\n", auth->type, req->url, req->authDetails); if (ad->userName == 0) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Missing user name.", 0); return 1; } if (auth->type != actualAuthType) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Wrong authentication protocol.", 0); return 1; } /* * Some backend methods can't return the password and will simply do everything in validateUserCredentials. * In this case, they and will return "". That is okay. */ if ((requiredPassword = getPassword(conn, auth->requiredRealm, ad->userName)) == 0) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, authentication error.", "User not defined"); return 1; } #if BLD_FEATURE_AUTH_DIGEST if (auth->type == MA_AUTH_DIGEST) { char *requiredDigest; if (strcmp(ad->qop, auth->qop) != 0) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access Denied, Quality of protection does not match.", 0); return 1; } mprCalcDigest(req, &requiredDigest, 0, requiredPassword, ad->realm, req->url, ad->nonce, ad->qop, ad->nc, ad->cnonce, req->methodName); requiredPassword = requiredDigest; } #endif if (!validateUserCredentials(conn, auth->requiredRealm, ad->userName, ad->password, requiredPassword, &msg)) { formatAuthResponse(conn, auth, MPR_HTTP_CODE_UNAUTHORIZED, "Access denied, authentication error", msg); return 1; } return 0; }
/* * Create stages for the request pipeline. */ void maCreatePipeline(MaConn *conn) { MaHttp *http; MaHost *host; MaResponse *resp; MaRequest *req; MaStage *handler; MaLocation *location; MaStage *stage, *connector; MaFilter *filter; MaQueue *q, *qhead, *rq, *rqhead; int next; req = conn->request; resp = conn->response; host = req->host; location = req->location; handler = resp->handler; http = conn->http; mprAssert(req); mprAssert(location->outputStages); /* * Create the output pipeline for this request. Handler first, then filters, connector last. */ resp->outputPipeline = mprCreateList(resp); /* * Add the handler and filters. Switch to the pass handler if any errors have occurred so far. Only add the * filters if the request has not failed. */ if (conn->requestFailed) { resp->handler = http->passHandler; mprAddItem(resp->outputPipeline, resp->handler); } else { mprAddItem(resp->outputPipeline, resp->handler); for (next = 0; (filter = mprGetNextItem(location->outputStages, &next)) != 0; ) { if (filter->stage == http->authFilter) { if (req->auth->type == 0 && req->auth->type == 0) { continue; } } if (filter->stage == http->rangeFilter && (req->ranges == 0 || handler == http->fileHandler)) { continue; } if ((filter->stage->flags & MA_STAGE_ALL & req->method) == 0) { continue; } /* * Remove the chunk filter chunking if it is explicitly turned off vi a the X_APPWEB_CHUNK_SIZE header setting * the chunk size to zero. Also remove if using the fileHandler which always knows the entity length and an * explicit chunk size has not been requested. */ if (filter->stage == http->chunkFilter) { if ((handler == http->fileHandler && resp->chunkSize < 0) || resp->chunkSize == 0) { continue; } } if (matchFilter(conn, filter)) { mprAddItem(resp->outputPipeline, filter->stage); } } } connector = location->connector; #if BLD_FEATURE_SEND if (resp->handler == http->fileHandler && connector == http->netConnector && http->sendConnector && !req->ranges && !host->secure) { /* * Switch (transparently) to the send connector if serving whole static file content via the net connector */ connector = http->sendConnector; } #endif resp->connector = connector; if ((connector->flags & MA_STAGE_ALL & req->method) == 0) { maFailRequest(conn, MPR_HTTP_CODE_BAD_REQUEST, "Connector \"%s\" does not support the \"%s\" method \"%s\"", connector->name, req->methodName); return; } mprAddItem(resp->outputPipeline, connector); /* * Create the outgoing queue heads and open the queues */ q = &resp->queue[MA_QUEUE_SEND]; for (next = 0; (stage = mprGetNextItem(resp->outputPipeline, &next)) != 0; ) { q = maCreateQueue(conn, stage, MA_QUEUE_SEND, q); } /* * Create the receive pipeline for this request. Connector first, handler last */ if (req->remainingContent > 0) { req->inputPipeline = mprCreateList(resp); mprAddItem(req->inputPipeline, connector); if (!conn->requestFailed) { for (next = 0; (filter = mprGetNextItem(location->inputStages, &next)) != 0; ) { if (filter->stage == http->authFilter || !matchFilter(conn, filter)) { continue; } if ((filter->stage->flags & MA_STAGE_ALL & req->method) == 0) { continue; } mprAddItem(req->inputPipeline, filter->stage); } } mprAddItem(req->inputPipeline, handler); /* * Create the incoming queue heads and open the queues. */ q = &resp->queue[MA_QUEUE_RECEIVE]; for (next = 0; (stage = mprGetNextItem(req->inputPipeline, &next)) != 0; ) { q = maCreateQueue(conn, stage, MA_QUEUE_RECEIVE, q); } } /* * Pair up the send and receive queues. NOTE: can't use a stage multiple times. */ qhead = &resp->queue[MA_QUEUE_SEND]; rqhead = &resp->queue[MA_QUEUE_RECEIVE]; for (q = qhead->nextQ; q != qhead; q = q->nextQ) { for (rq = rqhead->nextQ; rq != rqhead; rq = rq->nextQ) { if (q->stage == rq->stage) { q->pair = rq; rq->pair = q; } } } /* * Open the queues (keep going on errors) */ qhead = &resp->queue[MA_QUEUE_SEND]; for (q = qhead->nextQ; q != qhead; q = q->nextQ) { if (q->open && !(q->flags & MA_QUEUE_OPEN)) { q->flags |= MA_QUEUE_OPEN; openQ(q); } } if (req->remainingContent > 0) { qhead = &resp->queue[MA_QUEUE_RECEIVE]; for (q = qhead->nextQ; q != qhead; q = q->nextQ) { if (q->open && !(q->flags & MA_QUEUE_OPEN)) { if (q->pair == 0 || !(q->pair->flags & MA_QUEUE_OPEN)) { q->flags |= MA_QUEUE_OPEN; openQ(q); } } } } }
static void error(void *handle, int code, cchar *msg) { maFailRequest(handle, code, "%s", msg); }
/* * Find the matching handler for a request. If any errors occur, the pass handler is used to pass errors onto the * net/sendfile connectors to send to the client. This routine may rewrite the request URI and may redirect the request. */ void maMatchHandler(MaConn *conn) { MaRequest *req; MaResponse *resp; MaHost *host; MaAlias *alias; MaStage *handler; bool rescan; int loopCount; req = conn->request; resp = conn->response; host = req->host; /* * Find the alias that applies for this url. There is always a catch-all alias for the document root. */ alias = req->alias = maGetAlias(host, req->url); mprAssert(alias); if (alias->redirectCode) { // TODO - what about internal redirects? maRedirect(conn, alias->redirectCode, alias->uri); return; } if (conn->requestFailed || conn->request->method & (MA_REQ_OPTIONS | MA_REQ_TRACE)) { handler = conn->http->passHandler; return; } /* * Get the best (innermost) location block and see if a handler is explicitly set for that location block. * Possibly rewrite the url and retry. */ loopCount = MA_MAX_REWRITE; do { rescan = 0; if ((handler = findLocationHandler(conn)) == 0) { /* * Didn't find a location block handler, so try to match by extension and by handler match() routines. * This may invoke processDirectory which may redirect and thus require reprocessing -- hence the loop. */ handler = findHandlerByExtension(conn); } if (handler && !(handler->flags & MA_STAGE_VIRTUAL)) { if (!mapToFile(conn, &rescan)) { return; } } } while (handler && rescan && loopCount-- > 0); if (handler == 0) { maFailRequest(conn, MPR_HTTP_CODE_BAD_METHOD, "Requested method %s not supported for URL: %s", req->methodName, req->url); handler = conn->http->passHandler; } resp->handler = handler; mprLog(resp, 4, "Select handler: \"%s\" for \"%s\"", handler->name, req->url); setEnv(conn); }