Ejemplo n.º 1
0
/*
 *  Optimization to correctly size the packets to the chunk filter.
 */
static int getChunkPacketSize(MaConn *conn, MprBuf *buf)
{
    MaRequest   *req;
    char        *start, *cp;
    int         need, size;

    req = conn->request;
    need = 0;

    switch (req->chunkState) {
    case MA_CHUNK_START:
        start = mprGetBufStart(buf);
        if (mprGetBufLength(buf) < 3) {
            return 0;
        }
        if (start[0] != '\r' || start[1] != '\n') {
            maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
            return 0;
        }
        for (cp = &start[2]; cp < (char*) buf->end && *cp != '\n'; cp++) {}
        if ((cp - start) < 2 || (cp[-1] != '\r' || cp[0] != '\n')) {
            /* Insufficient data */
            if ((cp - start) > 80) {
                maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
                return 0;
            }
            return 0;
        }
        need = (int) (cp - start + 1);
        size = (int) mprAtoi(&start[2], 16);
        if (size == 0 && &cp[2] < buf->end && cp[1] == '\r' && cp[2] == '\n') {
            /* 
             *  This is the last chunk (size == 0). Now need to consume the trailing "\r\n". 
             *  We are lenient if the request does not have the trailing "\r\n" as required by the spec.
             */
            need += 2;
        }
        break;
    case MA_CHUNK_DATA:
        need = (int) min(MAXINT, req->remainingContent);
        break;

    default:
        mprAssert(0);
    }
    req->remainingContent = need;
    return need;
}
Ejemplo n.º 2
0
/*
 *  Process a socket readable event
 */
static void readEvent(MaConn *conn)
{
    MaPacket    *packet;
    MprBuf      *content;
    int         nbytes, len;

    do {
        if ((packet = getPacket(conn, &len)) == 0) {
            break;
        }
        mprAssert(len > 0);
        content = packet->content;
        nbytes = mprReadSocket(conn->sock, mprGetBufEnd(content), len);
        showRequest(content, nbytes, len);
       
        if (nbytes > 0) {
            mprAdjustBufEnd(content, nbytes);
            maProcessReadEvent(conn, packet);
        } else {
            if (mprIsSocketEof(conn->sock)) {
                conn->dedicated = 0;
                if (conn->request) {
                    maProcessReadEvent(conn, packet);
                }
            } else if (nbytes < 0) {
                maFailConnection(conn, MPR_HTTP_CODE_COMMS_ERROR, "Communications read error");
            }
        }
    } while (!conn->disconnected && conn->dedicated);
}
Ejemplo n.º 3
0
/*
 *  Get the packet into which to read data. This may be owned by the connection or if mid-request, may be owned by the
 *  request. Also return in *bytesToRead the length of data to attempt to read.
 */
static inline MaPacket *getPacket(MaConn *conn, int *bytesToRead)
{
    MaPacket    *packet;
    MaRequest   *req;
    MprBuf      *content;
    MprOff      remaining;
    int         len;

    req = conn->request;
    len = MA_BUFSIZE;

    /*
     *  The input packet may have pipelined headers and data left from the prior request. It may also have incomplete
     *  chunk boundary data.
     */
    if ((packet = conn->input) == NULL) {
        conn->input = packet = maCreateConnPacket(conn, len);

    } else {
        content = packet->content;
        mprResetBufIfEmpty(content);
        if (req) {
            /*
             *  Don't read more than the remainingContent unless chunked. We do this to minimize requests split 
             *  across packets.
             */
            if (req->remainingContent) {
                remaining = req->remainingContent;
                if (req->flags & MA_REQ_CHUNKED) {
                    remaining = max(remaining, MA_BUFSIZE);
                }
                len = (int) min(remaining, MAXINT);
            }
            len = min(len, MA_BUFSIZE);
            mprAssert(len > 0);
            if (mprGetBufSpace(content) < len) {
                mprGrowBuf(content, len);
            }

        } else {
            /*
             *  Still reading the request headers
             */
            if (mprGetBufLength(content) >= conn->http->limits.maxHeader) {
                maFailConnection(conn, MPR_HTTP_CODE_REQUEST_TOO_LARGE, "Header too big");
                return 0;
            }
            if (mprGetBufSpace(content) < MA_BUFSIZE) {
                mprGrowBuf(content, MA_BUFSIZE);
            }
            len = mprGetBufSpace(content);
        }
    }
    mprAssert(len > 0);
    *bytesToRead = len;
    return packet;
}
Ejemplo n.º 4
0
/*
 *  Parse a new request. Return true to keep going with this or subsequent request, zero means insufficient data to proceed.
 */
static bool parseRequest(MaConn *conn, MaPacket *packet) 
{
    MaRequest   *req;
    char        *start, *end;
    int         len;

    /*
     *  Must wait until we have the complete set of headers.
     */
    if ((len = mprGetBufLength(packet->content)) == 0) {
        return 0;
    }
    start = mprGetBufStart(packet->content);
    if ((end = mprStrnstr(start, "\r\n\r\n", len)) == 0) {
        return 0;
    }
    len = (int) (end - start);
    if (len >= conn->host->limits->maxHeader) {
        maFailConnection(conn, MPR_HTTP_CODE_REQUEST_TOO_LARGE, "Header too big");
        return 0;
    }
    if (parseFirstLine(conn, packet)) {
        parseHeaders(conn, packet);
    } else {
        return 0;
    }
    maMatchHandler(conn);
    
    /*
     *  Have read the headers. Create the request pipeline. This calls the open() stage entry routines.
     */
    maCreatePipeline(conn);

    req = conn->request;
    if (conn->connectionFailed) {
        /* Discard input data */
        mprAssert(conn->keepAliveCount <= 0);
        conn->state = MPR_HTTP_STATE_PROCESSING;
        maRunPipeline(conn);
    } else if (req->remainingContent > 0) {
        conn->state = MPR_HTTP_STATE_CONTENT;
    } else if (req->length == -1 && (req->method == MA_REQ_POST || req->method == MA_REQ_PUT)) {
        conn->state = MPR_HTTP_STATE_CONTENT;
    } else {
        /*
         *  Can run the request now if there is no incoming data.
         */
        conn->state = MPR_HTTP_STATE_PROCESSING;
        maRunPipeline(conn);
    }
    return !conn->disconnected;
}
Ejemplo n.º 5
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;
}
Ejemplo n.º 6
0
/*
 *  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;
}
Ejemplo n.º 7
0
/*
 *  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;
}
Ejemplo n.º 8
0
/*
 *  Get the next chunk size. Chunked data format is:
 *      Chunk spec <CRLF>
 *      Data <CRLF>
 *      Chunk spec (size == 0) <CRLF>
 *      <CRLF>
 *  Chunk spec is: "HEX_COUNT; chunk length DECIMAL_COUNT\r\n". The "; chunk length DECIMAL_COUNT is optional.
 *  As an optimization, use "\r\nSIZE ...\r\n" as the delimiter so that the CRLF after data does not special consideration.
 *  Achive this by parseHeaders reversing the input start by 2.
 */
static void incomingChunkData(MaQueue *q, MaPacket *packet)
{
    MaConn      *conn;
    MaRequest   *req;
    MprBuf      *buf;
    char        *start, *cp;
    int         bad;

    conn = q->conn;
    req = conn->request;
    buf = packet->content;

    mprAssert(req->flags & MA_REQ_CHUNKED);

    if (packet->content == 0) {
        if (req->chunkState == MA_CHUNK_DATA) {
            maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad chunk state");
            return;
        }
        req->chunkState = MA_CHUNK_EOF;
    }
    
    /*
     *  NOTE: the request head ensures that packets are correctly sized by packet inspection. The packet will never
     *  have more data than the chunk state expects.
     */
    switch (req->chunkState) {
    case MA_CHUNK_START:
        /*
         *  Validate:  "\r\nSIZE.*\r\n"
         */
        if (mprGetBufLength(buf) < 5) {
            break;
        }
        start = mprGetBufStart(buf);
        bad = (start[0] != '\r' || start[1] != '\n');
        for (cp = &start[2]; cp < buf->end && *cp != '\n'; cp++) {}
        if (*cp != '\n' && (cp - start) < 80) {
            break;
        }
        bad += (cp[-1] != '\r' || cp[0] != '\n');
        if (bad) {
            maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
            return;
        }
        req->chunkSize = (int) mprAtoi(&start[2], 16);
        if (!isxdigit((int) start[2]) || req->chunkSize < 0) {
            maFailConnection(conn, MPR_HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
            return;
        }
        mprAdjustBufStart(buf, (int) (cp - start + 1));
        req->remainingContent = req->chunkSize;
        if (req->chunkSize == 0) {
            req->chunkState = MA_CHUNK_EOF;
            /*
             *  We are lenient if the request does not have a trailing "\r\n" after the last chunk
             */
            cp = mprGetBufStart(buf);
            if (mprGetBufLength(buf) == 2 && *cp == '\r' && cp[1] == '\n') {
                mprAdjustBufStart(buf, 2);
            }
        } else {
            req->chunkState = MA_CHUNK_DATA;
        }
        mprAssert(mprGetBufLength(buf) == 0);
        maFreePacket(q, packet);
        mprLog(q, 5, "chunkFilter: start incoming chunk of %d bytes", req->chunkSize);
        break;

    case MA_CHUNK_DATA:
        mprAssert(maGetPacketLength(packet) <= req->chunkSize);
        mprLog(q, 5, "chunkFilter: data %d bytes, req->remainingContent %d", maGetPacketLength(packet), 
            req->remainingContent);
        maPutNext(q, packet);
        if (req->remainingContent == 0) {
            req->chunkState = MA_CHUNK_START;
            req->remainingContent = MA_BUFSIZE;
        }
        break;

    case MA_CHUNK_EOF:
        mprAssert(maGetPacketLength(packet) == 0);
        maPutNext(q, packet);
        mprLog(q, 5, "chunkFilter: last chunk");
        break;    

    default:
        mprAssert(0);
    }
}
Ejemplo n.º 9
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;
}