/* Populate a packet with file data. Return the number of bytes read or a negative error code. Will not return with a short read. */ static ssize readFileData(HttpQueue *q, HttpPacket *packet, MprOff pos, ssize size) { HttpConn *conn; HttpTx *tx; ssize nbytes; conn = q->conn; tx = conn->tx; if (packet->content == 0 && (packet->content = mprCreateBuf(size, -1)) == 0) { return MPR_ERR_MEMORY; } assert(size <= mprGetBufSpace(packet->content)); if (pos >= 0) { mprSeekFile(tx->file, SEEK_SET, pos); } if ((nbytes = mprReadFile(tx->file, mprGetBufStart(packet->content), size)) != size) { /* As we may have sent some data already to the client, the only thing we can do is abort and hope the client notices the short data. */ httpError(conn, HTTP_CODE_SERVICE_UNAVAILABLE, "Cannot read file %s", tx->filename); return MPR_ERR_CANT_READ; } mprAdjustBufEnd(packet->content, nbytes); packet->esize -= nbytes; assert(packet->esize == 0); return nbytes; }
/* Index into a file and extract a byte. This is random access reading. */ static EjsNumber *getFileProperty(Ejs *ejs, EjsFile *fp, int slotNum) { MprOff offset; int c; if (!(fp->mode & EJS_FILE_OPEN)) { ejsThrowIOError(ejs, "File is not open"); return 0; } #if KEEP if (fp->mode & EJS_FILE_READ) { if (slotNum >= fp->info.size) { ejsThrowOutOfBoundsError(ejs, "Bad file index"); return 0; } } if (slotNum < 0) { ejsThrowOutOfBoundsError(ejs, "Bad file index"); return 0; } #endif #if ME_CC_MMU && FUTURE // must check against mapped size here. c = fp->mapped[slotNum]; #else offset = mprSeekFile(fp->file, SEEK_CUR, 0); if (offset != slotNum) { if (mprSeekFile(fp->file, SEEK_SET, slotNum) != slotNum) { ejsThrowIOError(ejs, "Cannot seek to file offset"); return 0; } } c = mprPeekFileChar(fp->file); if (c < 0) { ejsThrowIOError(ejs, "Cannot read file"); return 0; } #endif return ejsCreateNumber(ejs, c); }
/* Event callback. Invoked for incoming web socket messages and other events of interest. We're interested in the WRITABLE event. */ static void output_callback(HttpConn *conn, int event, int arg) { Output *output; ssize len, wrote; int flags, type; char buf[MPR_BUFSIZE]; /* Get a writable event when the socket can absorb more data */ if (event == HTTP_EVENT_WRITABLE) { output = getData(); do { if ((len = mprReadFile(output->file, buf, sizeof(buf))) > 0) { /* Set the HTTP_MORE flag on every write except the last. This means each write is sent as a separate frame. The first frame has the type of WS_MSG_TEXT, all others must be continuation frames. */ flags = HTTP_NON_BLOCK; if ((output->written + len) < output->info.size) { flags |= HTTP_MORE; } type = output->written == 0 ? WS_MSG_TEXT : WS_MSG_CONT; /* Send the next chunk as a WebSockets frame using a non-blocking write. This may return having written only a portion of the requested data. */ if ((wrote = httpSendBlock(conn, type, buf, len, flags)) < 0) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot send message of %d bytes", len); return; } output->written += wrote; if (wrote < len) { /* Reposition if the send returned having written less than requested */ mprSeekFile(output->file, SEEK_CUR, wrote - len); break; } } else { httpSendClose(conn, WS_STATUS_OK, "OK"); break; } } while (len > 0); } else if (event == HTTP_EVENT_APP_CLOSE) { mprLog(0, "output.c: close event. Status status %d, orderly closed %d, reason %s", arg, httpWebSocketOrderlyClosed(conn), httpGetWebSocketCloseReason(conn)); } else if (event == HTTP_EVENT_ERROR) { mprLog(0, "output.c: error event"); } }
/* Seek to a new location in the file and set the File marker to a new read/write position. function set position(value: Number): void */ static EjsObj *setFilePosition(Ejs *ejs, EjsFile *fp, int argc, EjsObj **argv) { MprOff pos; assert(argc == 1 && ejsIs(ejs, argv[0], Number)); pos = ejsGetInt(ejs, argv[0]); if (fp->file == 0) { ejsThrowStateError(ejs, "File not opened"); return 0; } pos = ejsGetInt(ejs, argv[0]); if (mprSeekFile(fp->file, SEEK_SET, pos) != pos) { ejsThrowIOError(ejs, "Cannot seek to %Ld", pos); } return 0; }
/* The incoming callback is invoked to receive body data */ static void incomingFile(HttpQueue *q, HttpPacket *packet) { HttpConn *conn; HttpTx *tx; HttpRx *rx; HttpRange *range; MprBuf *buf; MprFile *file; ssize len; conn = q->conn; tx = conn->tx; rx = conn->rx; file = (MprFile*) q->queueData; if (file == 0) { /* Not a PUT so just ignore the incoming data. */ return; } if (httpGetPacketLength(packet) == 0) { /* End of input */ if (file) { mprCloseFile(file); } q->queueData = 0; if (!tx->etag) { /* Set the etag for caching in the client */ mprGetPathInfo(tx->filename, &tx->fileInfo); tx->etag = sfmt("\"%llx-%llx-%llx\"", tx->fileInfo.inode, tx->fileInfo.size, tx->fileInfo.mtime); } return; } buf = packet->content; len = mprGetBufLength(buf); assert(len > 0); range = rx->inputRange; if (range && mprSeekFile(file, SEEK_SET, range->start) != range->start) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot seek to range start to %lld", range->start); } else if (mprWriteFile(file, mprGetBufStart(buf), len) != len) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot PUT to %s", tx->filename); } }
/* This is called to setup for a HTTP PUT request. It is called before receiving the post data via incomingFileData */ static void handlePutRequest(HttpQueue *q) { HttpConn *conn; HttpTx *tx; MprFile *file; char *path; assure(q->pair->queueData == 0); conn = q->conn; tx = conn->tx; assure(tx->filename); assure(tx->fileInfo.checked); path = tx->filename; if (tx->outputRanges) { /* Open an existing file with fall-back to create */ if ((file = mprOpenFile(path, O_BINARY | O_WRONLY, 0644)) == 0) { if ((file = mprOpenFile(path, O_CREAT | O_TRUNC | O_BINARY | O_WRONLY, 0644)) == 0) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create the put URI"); return; } } else { mprSeekFile(file, SEEK_SET, 0); } } else { if ((file = mprOpenFile(path, O_CREAT | O_TRUNC | O_BINARY | O_WRONLY, 0644)) == 0) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create the put URI"); return; } } if (!tx->fileInfo.isReg) { httpSetHeader(conn, "Location", conn->rx->uri); } httpSetStatus(conn, tx->fileInfo.isReg ? HTTP_CODE_NO_CONTENT : HTTP_CODE_CREATED); q->pair->queueData = (void*) file; }