/* * 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; }
int mprPutBlockToBuf(MprBuf *bp, cchar *str, int size) { int thisLen, bytes, space; mprAssert(str); mprAssert(size >= 0); /* Add the max we can in one copy */ bytes = 0; while (size > 0) { space = mprGetBufSpace(bp); thisLen = min(space, size); if (thisLen <= 0) { if (mprGrowBuf(bp, size) < 0) { break; } space = mprGetBufSpace(bp); thisLen = min(space, size); } memcpy(bp->end, str, thisLen); str += thisLen; bp->end += thisLen; size -= thisLen; bytes += thisLen; } if (bp->end < bp->endbuf) { *((char*) bp->end) = (char) '\0'; } return bytes; }
/* Read the required number of bytes into the response content buffer. Count < 0 means transfer the entire content. Returns the number of bytes read. Returns null on EOF. */ static ssize readHttpData(Ejs *ejs, EjsHttp *hp, ssize count) { MprBuf *buf; HttpConn *conn; ssize len, space, nbytes; conn = hp->conn; buf = hp->responseContent; mprResetBufIfEmpty(buf); while (count < 0 || mprGetBufLength(buf) < count) { len = (count < 0) ? BIT_MAX_BUFFER : (count - mprGetBufLength(buf)); space = mprGetBufSpace(buf); if (space < len) { mprGrowBuf(buf, len - space); } if ((nbytes = httpRead(conn, mprGetBufEnd(buf), len)) < 0) { ejsThrowIOError(ejs, "Cannot read required data"); return MPR_ERR_CANT_READ; } mprAdjustBufEnd(buf, nbytes); if (hp->conn->async || (nbytes == 0 && conn->state > HTTP_STATE_CONTENT)) { break; } } if (count < 0) { return mprGetBufLength(buf); } return min(count, mprGetBufLength(buf)); }
/* Get the packet into which to read data. Return in *size the length of data to attempt to read. */ static HttpPacket *getPacket(HttpConn *conn, ssize *size) { HttpPacket *packet; MprBuf *content; ssize psize; if ((packet = conn->input) == NULL) { /* Boost the size of the packet if we have already read a largish amount of data */ psize = (conn->rx && conn->rx->bytesRead > ME_MAX_BUFFER) ? ME_MAX_BUFFER * 8 : ME_MAX_BUFFER; conn->input = packet = httpCreateDataPacket(psize); } else { content = packet->content; mprResetBufIfEmpty(content); if (mprGetBufSpace(content) < ME_MAX_BUFFER && mprGrowBuf(content, ME_MAX_BUFFER) < 0) { conn->keepAliveCount = 0; conn->state = HTTP_STATE_BEGIN; return 0; } } *size = mprGetBufSpace(packet->content); assert(*size > 0); return packet; }
/* * Default callback routine for the mprRunCmd routines. Uses may supply their own callback instead of this routine. * The callback is run whenever there is I/O to read/write to the CGI gateway. */ static int cmdCallback(MprCmd *cmd, int channel, void *data) { MprBuf *buf; int len, space; /* * Note: stdin, stdout and stderr are named from the client's perspective */ buf = 0; switch (channel) { case MPR_CMD_STDIN: return 0; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; } /* * Read and aggregate the result into a single string */ space = mprGetBufSpace(buf); if (space < (MPR_BUFSIZE / 4)) { if (mprGrowBuf(buf, MPR_BUFSIZE) < 0) { mprCloseCmdFd(cmd, channel); return 0; } space = mprGetBufSpace(buf); } len = mprReadCmdPipe(cmd, channel, mprGetBufEnd(buf), space); if (len <= 0) { if (len == 0 || (len < 0 && !(errno == EAGAIN || errno == EWOULDBLOCK))) { if (channel == MPR_CMD_STDOUT && cmd->flags & MPR_CMD_ERR) { /* * Now that stdout is complete, enable stderr to receive an EOF or any error output. * This is serialized to eliminate both stdin and stdout events on different threads at the same time. * Do before closing as the stderr event may come on another thread and we want to ensure avoid locking. */ mprCloseCmdFd(cmd, channel); } else { mprCloseCmdFd(cmd, channel); } return 0; } } else { mprAdjustBufEnd(buf, len); } return 0; }
/* * This appends a silent null. It does not count as one of the actual bytes in the buffer */ void mprAddNullToBuf(MprBuf *bp) { int space; space = (int) (bp->endbuf - bp->end); if (space < (int) sizeof(char)) { if (mprGrowBuf(bp, 1) < 0) { return; } } mprAssert(bp->end < bp->endbuf); if (bp->end < bp->endbuf) { *((char*) bp->end) = (char) '\0'; } }
/* Default callback routine for the mprRunCmd routines. Uses may supply their own callback instead of this routine. The callback is run whenever there is I/O to read/write to the CGI gateway. */ static void defaultCmdCallback(MprCmd *cmd, int channel, void *data) { MprBuf *buf; ssize len, space; int errCode; /* Note: stdin, stdout and stderr are named from the client's perspective */ buf = 0; switch (channel) { case MPR_CMD_STDIN: return; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; default: /* Child death notification */ return; } /* Read and aggregate the result into a single string */ space = mprGetBufSpace(buf); if (space < (ME_BUFSIZE / 4)) { if (mprGrowBuf(buf, ME_BUFSIZE) < 0) { mprCloseCmdFd(cmd, channel); return; } space = mprGetBufSpace(buf); } len = mprReadCmd(cmd, channel, mprGetBufEnd(buf), space); errCode = mprGetError(); if (len <= 0) { if (len == 0 || (len < 0 && !(errCode == EAGAIN || errCode == EWOULDBLOCK))) { mprCloseCmdFd(cmd, channel); return; } } else { mprAdjustBufEnd(buf, len); } mprAddNullToBuf(buf); mprEnableCmdEvents(cmd, channel); }
/* * Set the current buffer size and maximum size limit. */ int mprSetBufSize(MprBuf *bp, int initialSize, int maxSize) { mprAssert(bp); if (initialSize <= 0) { if (maxSize > 0) { bp->maxsize = maxSize; } return 0; } if (maxSize > 0 && initialSize > maxSize) { initialSize = maxSize; } mprAssert(initialSize > 0); if (bp->data) { /* * Buffer already exists */ if (bp->buflen < initialSize) { if (mprGrowBuf(bp, initialSize - bp->buflen) < 0) { return MPR_ERR_NO_MEMORY; } } bp->maxsize = maxSize; return 0; } /* * New buffer - create storage for the data */ if ((bp->data = mprAlloc(bp, initialSize)) == 0) { mprAssert(!MPR_ERR_NO_MEMORY); return MPR_ERR_NO_MEMORY; } bp->growBy = initialSize; bp->maxsize = maxSize; bp->buflen = initialSize; bp->endbuf = &bp->data[bp->buflen]; bp->start = bp->data; bp->end = bp->data; *bp->start = '\0'; return 0; }
int mprPutCharToBuf(MprBuf *bp, int c) { char *cp; int space; mprAssert(bp->buflen == (bp->endbuf - bp->data)); space = bp->buflen - mprGetBufLength(bp); if (space < (int) sizeof(char)) { if (mprGrowBuf(bp, 1) < 0) { return MPR_ERR_NO_MEMORY; } } cp = (char*) bp->end; *cp++ = (char) c; bp->end = (char*) cp; if (bp->end < bp->endbuf) { *((char*) bp->end) = (char) '\0'; } return 1; }
static int getPostData(MprCtx ctx, MprBuf *buf) { char *contentLength; int bytes, len, space; if ((contentLength = getenv("CONTENT_LENGTH")) != 0) { len = atoi(contentLength); } else { len = MAXINT; } while (len > 0) { space = mprGetBufSpace(buf); if (space < MPR_BUFSIZE) { if (mprGrowBuf(buf, MPR_BUFSIZE) < 0) { error(mpr, "Couldn't allocate memory to read post data"); return -1; } } space = mprGetBufSpace(buf); bytes = (int) read(0, mprGetBufEnd(buf), space); if (bytes < 0) { error(mpr, "Couldn't read CGI input %d", errno); return -1; } else if (bytes == 0) { /* EOF */ if (len > 0) { error(mpr, "Missing content (length %s)", contentLength); } break; } mprAdjustBufEnd(buf, bytes); len -= bytes; } mprAddNullToBuf(buf); return 0; }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprCloseCmdFd(cmd, channel); break; } else { traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { mprLog("error cgi", 0, "CGI failed uri=\"%s\", details: %s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }
static void cgiEvent(MaQueue *q, MprCmd *cmd, int channel) { MaConn *conn; MaResponse *resp; MprBuf *buf; int space, nbytes, err; mprLog(cmd, 6, "CGI callback channel %d", channel); buf = 0; conn = q->conn; resp = conn->response; mprAssert(resp); cmd->lastActivity = mprGetTime(cmd); switch (channel) { case MPR_CMD_STDIN: writeToCGI(q->pair); return; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; } mprAssert(buf); mprResetBufIfEmpty(buf); /* Come here for CGI stdout, stderr events. ie. reading data from the CGI program. */ while (mprGetCmdFd(cmd, channel) >= 0) { /* Read as much data from the CGI as possible */ do { if ((space = mprGetBufSpace(buf)) == 0) { mprGrowBuf(buf, MA_BUFSIZE); if ((space = mprGetBufSpace(buf)) == 0) { break; } } nbytes = mprReadCmdPipe(cmd, channel, mprGetBufEnd(buf), space); mprLog(q, 5, "CGI: read from gateway %d on channel %d. errno %d", nbytes, channel, nbytes >= 0 ? 0 : mprGetOsError()); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprLog(cmd, 5, "CGI read error %d for %", mprGetError(), (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); } else if (nbytes == 0) { /* This may reap the terminated child and thus clear cmd->process if both stderr and stdout are closed. */ mprLog(cmd, 5, "CGI EOF for %s", (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); break; } else { mprLog(cmd, 5, "CGI read %d bytes from %s", nbytes, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprAdjustBufEnd(buf, nbytes); traceData(cmd, mprGetBufStart(buf), nbytes); } } while ((space = mprGetBufSpace(buf)) > 0); if (mprGetBufLength(buf) == 0) { return; } if (channel == MPR_CMD_STDERR) { /* If we have an error message, send that to the client */ if (mprGetBufLength(buf) > 0) { mprAddNullToBuf(buf); mprLog(conn, 4, mprGetBufStart(buf)); if (writeToClient(q, cmd, buf, channel) < 0) { return; } maSetResponseCode(conn, MPR_HTTP_CODE_SERVICE_UNAVAILABLE); cmd->userFlags |= MA_CGI_SEEN_HEADER; cmd->status = 0; } } else { if (!(cmd->userFlags & MA_CGI_SEEN_HEADER) && !parseHeader(conn, cmd)) { return; } if (cmd->userFlags & MA_CGI_SEEN_HEADER) { if (writeToClient(q, cmd, buf, channel) < 0) { return; } } } } }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprTrace(6, "CGI: Gateway read error %d for %s", err, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprTrace(6, "CGI: Gateway EOF for %s, pid %d", (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr", cmd->pid); mprCloseCmdFd(cmd, channel); break; } else { mprTrace(6, "CGI: Gateway read %d bytes from %s", nbytes, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { // FUTURE - should be an option to keep going despite stderr output mprError("CGI: Error for \"%s\"\n\n%s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }