Пример #1
0
/*
 *  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);
}
Пример #2
0
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);
}
Пример #3
0
/*
 *  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);
    }
}
Пример #4
0
/*
    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;
    }
}
Пример #5
0
/*
 *  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);
}
Пример #6
0
/*
    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);
    }
}
Пример #7
0
/*
 *  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;
}
Пример #8
0
/*
 *  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;
}
Пример #9
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);
}
Пример #10
0
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);
}
Пример #11
0
/*
    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;
}