static int decodeNumber(EcInput *input, int radix, int length) { char buf[16]; int i, c, lowerc; for (i = 0; i < length; i++) { c = getNextChar(input->stream); if (c == 0) { break; } if (radix <= 10) { if (!isdigit(c)) { break; } } else if (radix == 16) { lowerc = tolower(c); if (!isdigit(c) && !('a' <= c && c <= 'f')) { break; } } buf[i] = c; } if (i < length) { putBackChar(input->stream, c); } buf[i] = '\0'; return mprAtoi(buf, radix); }
/* * 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; }
/* * 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; }
/* * Format is: Range: bytes=n1-n2,n3-n4,... * Where n1 is first byte pos and n2 is last byte pos * * Examples: * Range: 0-49 first 50 bytes * Range: 50-99,200-249 Two 50 byte ranges from 50 and 200 * Range: -50 Last 50 bytes * Range: 1- Skip first byte then emit the rest * * Return 1 if more ranges, 0 if end of ranges, -1 if bad range. */ static bool parseRange(MaConn *conn, char *value) { MaRequest *req; MaResponse *resp; MaRange *range, *last, *next; char *tok, *ep; req = conn->request; resp = conn->response; value = mprStrdup(conn, value); if (value == 0) { return 0; } /* * Step over the "bytes=" */ tok = mprStrTok(value, "=", &value); for (last = 0; value && *value; ) { range = mprAllocObjZeroed(req, MaRange); if (range == 0) { return 0; } /* * A range "-7" will set the start to -1 and end to 8 */ tok = mprStrTok(value, ",", &value); if (*tok != '-') { range->start = (int) mprAtoi(tok, 10); } else { range->start = -1; } range->end = -1; if ((ep = strchr(tok, '-')) != 0) { if (*++ep != '\0') { /* * End is one beyond the range. Makes the math easier. */ range->end = (int) mprAtoi(ep, 10) + 1; } } if (range->start >= 0 && range->end >= 0) { range->len = range->end - range->start; } if (last == 0) { req->ranges = range; } else { last->next = range; } last = range; } /* * Validate ranges */ for (range = req->ranges; range; range = range->next) { if (range->end != -1 && range->start >= range->end) { return 0; } if (range->start < 0 && range->end < 0) { return 0; } next = range->next; if (range->start < 0 && next) { /* This range goes to the end, so can't have another range afterwards */ return 0; } if (next) { if (next->start >= 0 && range->end > next->start) { return 0; } } } resp->currentRange = req->ranges; return (last) ? 1: 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); } }
/* 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; }
int MaClient::processResponseData() { char *line, *cp; int nbytes; mprLog(6, tMod, "READ DATA: %s\n", inBuf->getStart()); timestamp = mprGetTime(0); line = 0; while (state != MPR_HTTP_CLIENT_DONE && inBuf->getLength() > 0) { line = inBuf->getStart(); if (state != MPR_HTTP_CLIENT_CONTENT) { if ((cp = strchr(line, '\n')) == 0) { // Wait for more data return 0; } *cp = '\0'; if (cp[-1] == '\r') { nbytes = cp - line; *--cp = '\0'; } else { nbytes = cp - line; } inBuf->adjustStart(nbytes + 1); } else { if (contentLength <= 0) { nbytes = inBuf->getLength(); } else { nbytes = min(contentRemaining, inBuf->getLength()); } inBuf->adjustStart(nbytes); } switch(state) { case MPR_HTTP_CLIENT_START: mprLog(3, tMod, "processResponseData: %s\n", line); if (line[0] == '\0') { return 0; } responseHeader->put(line); responseHeader->put('\n'); if (parseFirst(line) < 0) { return MPR_ERR_BAD_STATE; } state = MPR_HTTP_CLIENT_HEADER; break; case MPR_HTTP_CLIENT_HEADER: if (nbytes > 1) { mprLog(3, tMod, "processResponseData: %s\n", line); responseHeader->put(line); responseHeader->put('\n'); if (parseHeader(line) < 0) { return MPR_ERR_BAD_STATE; } } else { // // Blank line means end of headers // if (flags & MPR_HTTP_INPUT_CHUNKED) { if (flags & MPR_HTTP_END_CHUNK_DATA) { finishRequest(0); } else { state = MPR_HTTP_CLIENT_CHUNK; } } else { state = MPR_HTTP_CLIENT_CONTENT; // // This means there was an explicit zero content length // if (contentRemaining == 0) { finishRequest(0); } else if (mprStrCmpAnyCase(method, "HEAD") == 0) { finishRequest(0); } } } break; case MPR_HTTP_CLIENT_CHUNK: mprLog(3, tMod, "processResponseData: %s\n", line); contentRemaining = contentLength = mprAtoi(line, 16); if (contentLength <= 0) { flags |= MPR_HTTP_END_CHUNK_DATA; state = MPR_HTTP_CLIENT_HEADER; } else { state = MPR_HTTP_CLIENT_CONTENT; } if (contentLength > MPR_HTTP_CLIENT_BUFSIZE) { delete responseContent; responseContent = new MprBuf(contentLength + 1, -1); } break; case MPR_HTTP_CLIENT_CONTENT: responseContent->put((uchar*) line, nbytes); responseContent->addNull(); mprLog(3, tMod, "processResponseData: %d bytes, %d remaining, %d sofar\n", nbytes, contentRemaining, responseContent->getLength()); if (contentRemaining > 0 || nbytes <= 0) { contentRemaining -= nbytes; if (contentRemaining <= 0) { /* if (!(flags & MPR_HTTP_INPUT_CHUNKED)) */ finishRequest(0); } } break; default: formatError("Bad state"); responseCode = MPR_HTTP_CLIENT_ERROR; finishRequest(1); return MPR_ERR_BAD_STATE; } } return 0; }