/* * Split a packet at a given offset and return a new packet containing the data after the offset. * The suffix data migrates to the new packet. */ MaPacket *maSplitPacket(MprCtx ctx, MaPacket *orig, int offset) { MaPacket *packet; int count, size; if (orig->esize) { if ((packet = maCreateEntityPacket(ctx, orig->epos + offset, orig->esize - offset, orig->fill)) == 0) { return 0; } orig->esize = offset; } else { if (offset >= maGetPacketLength(orig)) { mprAssert(offset < maGetPacketLength(orig)); return 0; } count = maGetPacketLength(orig) - offset; size = max(count, MA_BUFSIZE); size = MA_PACKET_ALIGN(size); if ((packet = maCreateDataPacket(ctx, size)) == 0) { return 0; } mprAdjustBufEnd(orig->content, -count); if (mprPutBlockToBuf(packet->content, mprGetBufEnd(orig->content), count) != count) { return 0; } #if BLD_DEBUG mprAddNullToBuf(orig->content); #endif } packet->flags = orig->flags; return packet; }
/* * 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); } }
/* * Return true if the next queue will accept this packet. If not, then disable the queue's service procedure. * This may split the packet if it exceeds the downstreams maximum packet size. */ bool maWillNextQueueAccept(MaQueue *q, MaPacket *packet) { MaQueue *next; int64 size; next = q->nextQ; size = maGetPacketLength(packet); if (size <= next->packetSize && (size + next->count) <= next->max) { return 1; } if (maResizePacket(q, packet, 0) < 0) { return 0; } size = maGetPacketLength(packet); if (size <= next->packetSize && (size + next->count) <= next->max) { return 1; } /* * The downstream queue is full, so disable the queue and mark the downstream queue as full and service immediately. */ maDisableQueue(q); next->flags |= MA_QUEUE_FULL; maScheduleQueue(next); return 0; }
/* * Clear entries from the IO vector that have actually been transmitted. This supports partial writes due to the socket * being full. Don't come here if we've seen all the packets and all the data has been completely written. ie. small files * don't come here. */ static void freeSentPackets(MaQueue *q, int64 bytes) { MaPacket *packet; int64 len; mprAssert(q->first); mprAssert(q->count >= 0); mprAssert(bytes >= 0); while ((packet = q->first) != 0) { if (packet->prefix) { len = mprGetBufLength(packet->prefix); len = min(len, bytes); mprAdjustBufStart(packet->prefix, (int) len); bytes -= len; /* Prefixes don't count in the q->count. No need to adjust */ if (mprGetBufLength(packet->prefix) == 0) { mprFree(packet->prefix); packet->prefix = 0; } } if (packet->esize) { len = min(packet->esize, bytes); packet->esize -= len; packet->epos += len; bytes -= len; mprAssert(packet->esize >= 0); mprAssert(bytes == 0); if (packet->esize > 0) { break; } } else if ((len = maGetPacketLength(packet)) > 0) { len = min(len, bytes); mprAdjustBufStart(packet->content, (int) len); bytes -= len; q->count -= (int) len; mprAssert(q->count >= 0); } if (maGetPacketLength(packet) == 0) { if ((packet = maGet(q)) != 0) { maFreePacket(q, packet); } } mprAssert(bytes >= 0); if (bytes == 0) { break; } } }
static void setChunkPrefix(MaQueue *q, MaPacket *packet) { if (packet->prefix) { return; } packet->prefix = mprCreateBuf(packet, 32, 32); /* * NOTE: prefixes don't count in the queue length. No need to adjust q->count */ if (maGetPacketLength(packet)) { mprPutFmtToBuf(packet->prefix, "\r\n%x\r\n", maGetPacketLength(packet)); } else { mprPutStringToBuf(packet->prefix, "\r\n0\r\n\r\n"); } }
/* * Add a packet to the io vector. Return the number of bytes added to the vector. */ static void addPacketForSend(MaQueue *q, MaPacket *packet) { MaResponse *resp; MaConn *conn; MprIOVec *iovec; int mask; conn = q->conn; resp = conn->response; iovec = q->iovec; mprAssert(q->count >= 0); mprAssert(q->ioIndex < (MA_MAX_IOVEC - 2)); if (packet->prefix) { addToSendVector(q, mprGetBufStart(packet->prefix), mprGetBufLength(packet->prefix)); } if (packet->esize > 0) { mprAssert(q->ioFile == 0); q->ioFile = 1; q->ioCount += packet->esize; } else if (maGetPacketLength(packet) > 0) { /* * Header packets have actual content. File data packets are virtual and only have a count. */ addToSendVector(q, mprGetBufStart(packet->content), mprGetBufLength(packet->content)); mask = (packet->flags & MA_PACKET_HEADER) ? MA_TRACE_HEADERS : MA_TRACE_BODY; if (maShouldTrace(conn, mask)) { maTraceContent(conn, packet, 0, resp->bytesWritten, mask); } } }
/* * Remove all data from non-header packets in the queue. Don't worry about freeing. Will happen automatically at * the request end. See also maCleanQueue above. */ void maDiscardData(MaQueue *q, bool removePackets) { MaPacket *packet; int len; if (q->first) { /* * Skip the header packet */ if (q->first->flags & MA_PACKET_HEADER) { packet = q->first->next; } else { packet = q->first; } /* * Just flush each packet. Don't remove so the EOF packet is preserved */ for (; packet; packet = packet->next) { if (packet->content) { len = maGetPacketLength(packet); q->conn->response->length -= len; q->count -= len; mprFlushBuf(packet->content); } } maCheckQueueCount(q); } }
/* * Split a packet if required so it fits in the downstream queue. Put back the 2nd portion of the split packet on the queue. * Ensure that the packet is not larger than "size" if it is greater than zero. */ int maResizePacket(MaQueue *q, MaPacket *packet, int size) { MaPacket *tail; MaConn *conn; MprCtx ctx; int len; conn = q->conn; if (size <= 0) { size = MAXINT; } ctx = conn->request ? (MprCtx) conn->request : (MprCtx) conn; if (packet->esize > size) { if ((tail = maSplitPacket(ctx, packet, size)) == 0) { return MPR_ERR_NO_MEMORY; } } else { /* * Calculate the size that will fit */ len = packet->content ? maGetPacketLength(packet) : 0; size = min(size, len); size = min(size, q->nextQ->max); size = min(size, q->nextQ->packetSize); if (size == 0 || size == len) { return 0; } if ((tail = maSplitPacket(ctx, packet, size)) == 0) { return MPR_ERR_NO_MEMORY; } } maPutBack(q, tail); return 0; }
/* * Add a packet to the io vector. Return the number of bytes added to the vector. */ static void addPacketForNet(MaQueue *q, MaPacket *packet) { MaResponse *resp; MaConn *conn; MprIOVec *iovec; int index, mask; conn = q->conn; resp = conn->response; iovec = q->iovec; index = q->ioIndex; mprAssert(q->count >= 0); mprAssert(q->ioIndex < (MA_MAX_IOVEC - 2)); if (packet->prefix) { addToNetVector(q, mprGetBufStart(packet->prefix), mprGetBufLength(packet->prefix)); } if (maGetPacketLength(packet) > 0) { addToNetVector(q, mprGetBufStart(packet->content), mprGetBufLength(packet->content)); } mask = MA_TRACE_RESPONSE | ((packet->flags & MA_PACKET_HEADER) ? MA_TRACE_HEADERS : MA_TRACE_BODY); if (maShouldTrace(conn, mask)) { maTraceContent(conn, packet, 0, resp->bytesWritten, mask); } }
/* * Remove packets from a queue which do not need to be processed. * Remove data packets if no body is required (HEAD|TRACE|OPTIONS|PUT|DELETE method, not modifed content, or error) * This actually removes and frees the data packets whereas maDiscardData will just flush the data packets. */ void maCleanQueue(MaQueue *q) { MaConn *conn; MaResponse *resp; MaPacket *packet, *next, *prev; conn = q->conn; resp = conn->response; if (!(resp->flags & MA_RESP_NO_BODY)) { return; } for (prev = 0, packet = q->first; packet; packet = next) { next = packet->next; if (packet->flags & (MA_PACKET_RANGE | MA_PACKET_DATA)) { if (prev) { prev->next = next; } else { q->first = next; } q->count -= maGetPacketLength(packet); maFreePacket(q, packet); continue; } prev = packet; } maCheckQueueCount(q); }
/* * Put the packet back at the front of the queue */ void maPutBack(MaQueue *q, MaPacket *packet) { mprAssert(packet); mprAssert(packet->next == 0); packet->next = q->first; if (q->first == 0) { q->last = packet; } q->first = packet; mprAssert(maGetPacketLength(packet) >= 0); q->count += maGetPacketLength(packet); mprAssert(q->count >= 0); maCheckQueueCount(q); }
/* * Get the next packet from the queue */ MaPacket *maGet(MaQueue *q) { MaConn *conn; MaQueue *prev; MaPacket *packet; maCheckQueueCount(q); conn = q->conn; while (q->first) { if ((packet = q->first) != 0) { if (packet->flags & MA_PACKET_DATA && conn->requestFailed) { q->first = packet->next; q->count -= maGetPacketLength(packet); maFreePacket(q, packet); continue; } q->first = packet->next; packet->next = 0; q->count -= maGetPacketLength(packet); mprAssert(q->count >= 0); if (packet == q->last) { q->last = 0; mprAssert(q->first == 0); } } maCheckQueueCount(q); if (q->flags & MA_QUEUE_FULL && q->count < q->low) { /* * This queue was full and now is below the low water mark. Back-enable the previous queue. */ q->flags &= ~MA_QUEUE_FULL; prev = findPreviousQueue(q); if (prev && prev->flags & MA_QUEUE_DISABLED) { maEnableQueue(prev); } } maCheckQueueCount(q); return packet; } return 0; }
void maCheckQueueCount(MaQueue *q) { MaPacket *packet; int64 count; count = 0; for (packet = q->first; packet; packet = packet->next) { count += maGetPacketLength(packet); } mprAssert(count == q->count); }
/* * Build the IO vector. This connector uses the send file API which permits multiple IO blocks to be written with * file data. This is used to write transfer the headers and chunk encoding boundaries. Return the count of bytes to * be written. */ static int64 buildSendVec(MaQueue *q) { MaConn *conn; MaResponse *resp; MaPacket *packet; conn = q->conn; resp = conn->response; mprAssert(q->ioIndex == 0); q->ioCount = 0; q->ioFile = 0; /* * Examine each packet and accumulate as many packets into the I/O vector as possible. Can only have one data packet at * a time due to the limitations of the sendfile API (on Linux). And the data packet must be after all the * vector entries. Leave the packets on the queue for now, they are removed after the IO is complete for the * entire packet. */ for (packet = q->first; packet; packet = packet->next) { if (packet->flags & MA_PACKET_HEADER) { maFillHeaders(conn, packet); q->count += maGetPacketLength(packet); } else if (maGetPacketLength(packet) == 0 && packet->esize == 0) { q->flags |= MA_QUEUE_EOF; if (packet->prefix == NULL) { break; } } else if (resp->flags & MA_RESP_NO_BODY) { maDiscardData(q, 0); continue; } if (q->ioFile || q->ioIndex >= (MA_MAX_IOVEC - 2)) { break; } addPacketForSend(q, packet); } return q->ioCount; }
/* * Join two packets by pulling the content from the second into the first. */ int maJoinPacket(MaPacket *packet, MaPacket *p) { int len; mprAssert(packet->esize == 0); mprAssert(p->esize == 0); len = maGetPacketLength(p); if (mprPutBlockToBuf(packet->content, mprGetBufStart(p->content), len) != len) { return MPR_ERR_NO_MEMORY; } return 0; }
/* * Build the IO vector. Return the count of bytes to be written. Return -1 for EOF. */ static int64 buildNetVec(MaQueue *q) { MaConn *conn; MaResponse *resp; MaPacket *packet; conn = q->conn; resp = conn->response; /* * Examine each packet and accumulate as many packets into the I/O vector as possible. Leave the packets on the queue * for now, they are removed after the IO is complete for the entire packet. */ for (packet = q->first; packet; packet = packet->next) { if (packet->flags & MA_PACKET_HEADER) { if (resp->chunkSize <= 0 && q->count > 0 && resp->length < 0) { /* Incase no chunking filter and we've not seen all the data yet */ conn->keepAliveCount = 0; } maFillHeaders(conn, packet); q->count += maGetPacketLength(packet); } else if (maGetPacketLength(packet) == 0) { q->flags |= MA_QUEUE_EOF; if (packet->prefix == NULL) { break; } } else if (resp->flags & MA_RESP_NO_BODY) { maDiscardData(q, 0); continue; } if (q->ioIndex >= (MA_MAX_IOVEC - 2)) { break; } addPacketForNet(q, packet); } return q->ioCount; }
/* * Apply chunks to dynamic outgoing data. */ static void outgoingChunkService(MaQueue *q) { MaConn *conn; MaPacket *packet; MaResponse *resp; conn = q->conn; resp = conn->response; if (!(q->flags & MA_QUEUE_SERVICED)) { /* * If the last packet is the end packet, we have all the data. Thus we know the actual content length * and can bypass the chunk handler. */ if (q->last->flags & MA_PACKET_END) { if (resp->chunkSize < 0 && resp->length <= 0) { /* * Set the response content length and thus disable chunking -- not needed as we know the entity length. */ resp->length = q->count; } } else { if (resp->chunkSize < 0 && resp->entityLength < 0) { resp->chunkSize = (int) min(conn->http->limits.maxChunkSize, q->max); } } } if (resp->chunkSize <= 0) { maDefaultOutgoingServiceStage(q); } else { for (packet = maGet(q); packet; packet = maGet(q)) { if (!(packet->flags & MA_PACKET_HEADER)) { if (maGetPacketLength(packet) > resp->chunkSize) { maResizePacket(q, packet, resp->chunkSize); } } if (!maWillNextQueueAccept(q, packet)) { maPutBack(q, packet); return; } if (!(packet->flags & MA_PACKET_HEADER)) { setChunkPrefix(q, packet); } maPutNext(q, packet); } } }
/* 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); } }
/* * Put a packet on the service queue. */ void maPutForService(MaQueue *q, MaPacket *packet, bool serviceQ) { mprAssert(packet); maCheckQueueCount(q); q->count += maGetPacketLength(packet); packet->next = 0; if (q->first) { q->last->next = packet; q->last = packet; } else { q->first = packet; q->last = packet; } maCheckQueueCount(q); if (serviceQ && !(q->flags & MA_QUEUE_DISABLED)) { maScheduleQueue(q); } }
/* * 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); } }
/* * 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; }