/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_IOTOOLS_BUFFERED_IO_H_ #define _PASSENGER_IOTOOLS_BUFFERED_IO_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Passenger { using namespace std; using namespace oxt; /** * Provides buffered I/O for arbitrary file descriptors. Supports features not * found in C++'s iostream or libc's stdio: * - All functions have timeout support. * - The readLine() method returns a C++ string, so no need to worry about * buffer management. A size limit can be imposed. * - Read buffer is infinite in size. * - Unreading (pushing back) arbitrary amount of data. */ class BufferedIO { private: FileDescriptor fd; string buffer; static pair nReadOrEofReached(const char *data, unsigned int size, void *output, unsigned int goalSize, unsigned int *alreadyRead) { char *current = (char *) output + *alreadyRead; unsigned int remaining = goalSize - *alreadyRead; unsigned int consumed = min(remaining, size); memcpy(current, data, consumed); *alreadyRead += consumed; return make_pair(consumed, *alreadyRead == goalSize); } static pair eofReached(const char *data, unsigned int size, string *output) { output->append(data, size); return make_pair(size, false); } static pair newlineFound(const char *data, unsigned int size, string *output, unsigned int max) { const char *newline = (const char *) memchr(data, '\n', size); if (newline != NULL) { unsigned int accepted = newline - data + 1; if (output->size() + accepted > max) { throw SecurityException("Line too long"); } output->append(data, accepted); return make_pair(accepted, true); } else { if (output->size() + size > max) { throw SecurityException("Line too long"); } output->append(data, size); return make_pair(size, false); } } public: typedef boost::function< pair(const char *data, unsigned int size) > AcceptFunction; BufferedIO() { } BufferedIO(const FileDescriptor &_fd) : fd(_fd) { } FileDescriptor getFd() const { return fd; } const string &getBuffer() const { return buffer; } /** * This method keeps reading data in a loop, feeding each chunk to the given * acceptor function, until the function says that it has consumed all data * that it needs. Leftover data that has been read from the file descriptor * but not consumed by the acceptor function will be put in the buffer, making * it available for future read operations. * * The acceptor function accepts (data, size) as arguments and returns a * (consumed, done) pair, where 'consumed' indicates the number of bytes * from 'data' that it has consumed. 'done' indicates whether the acceptor * function is done consuming (true), or whether it expects more data (false). * * readUntil() can be used for e.g. reading data until a newline is encountered. * * If the acceptor function throws an exception then the BufferedIO instance * will be left in an undefined state, making it unusable. * * @throws RuntimeException The acceptor function returned an invalid result. * @throws SystemException * @throws TimeoutException * @throws boost::thread_interrupted */ unsigned int readUntil(const AcceptFunction &acceptor, unsigned long long *timeout = NULL) { pair acceptResult; unsigned int totalRead = 0; if (!buffer.empty()) { acceptResult = acceptor(buffer.c_str(), buffer.size()); if (OXT_UNLIKELY(!acceptResult.second && acceptResult.first < buffer.size())) { throw RuntimeException("Acceptor function cannot return (x,false) where x is smaller than the input size"); } else if (OXT_UNLIKELY(acceptResult.first > buffer.size())) { throw RuntimeException("Acceptor function cannot return a larger accept count than the input size"); } buffer.erase(0, acceptResult.first); totalRead = acceptResult.first; if (acceptResult.second) { return totalRead; } } while (true) { if (OXT_UNLIKELY(timeout != NULL && !waitUntilReadable(fd, timeout))) { throw TimeoutException("Read timeout"); } char tmp[1024 * 8]; ssize_t ret = syscalls::read(fd, tmp, sizeof(tmp)); if (ret == 0) { return totalRead; } else if (OXT_UNLIKELY(ret == -1)) { if (errno != EAGAIN) { int e = errno; throw SystemException("read() failed", e); } } else { acceptResult = acceptor(tmp, ret); totalRead += acceptResult.first; if (OXT_UNLIKELY(!acceptResult.second && acceptResult.first < (unsigned int) ret)) { throw RuntimeException("Acceptor function cannot return (x,false) where x is smaller than the input size"); } else if (OXT_UNLIKELY(acceptResult.first > (unsigned int) ret)) { throw RuntimeException("Acceptor function cannot return a larger accept count than the input size"); } if (acceptResult.second) { buffer.assign(tmp + acceptResult.first, ret - acceptResult.first); return totalRead; } } } } unsigned int read(void *buf, unsigned int size, unsigned long long *timeout = NULL) { unsigned int counter = 0; return readUntil( boost::bind(nReadOrEofReached, boost::placeholders::_1, boost::placeholders::_2, buf, size, &counter), timeout); } string readAll(unsigned long long *timeout = NULL) { string output; readUntil( boost::bind(eofReached, boost::placeholders::_1, boost::placeholders::_2, &output), timeout); return output; } /** * Reads a line and returns the line including the newline character. Upon * encountering EOF, the empty string is returned. * * The `max` parameter dictates the maximum length of the returned line. * If the line is longer than this number of characters, then a SecurityException * is thrown, and the BufferedIO becomes unusable (enters an undefined state). * * @throws SystemException * @throws TimeoutException * @throws SecurityException * @throws boost::thread_interrupted */ string readLine(unsigned int max = 1024 * 8, unsigned long long *timeout = NULL) { string output; readUntil( boost::bind(newlineFound, boost::placeholders::_1, boost::placeholders::_2, &output, max), timeout); return output; } void unread(const void *buf, unsigned int size) { string newBuffer; newBuffer.reserve(size + buffer.size()); newBuffer.append((const char *) buf, (string::size_type) size); newBuffer.append(buffer); buffer = newBuffer; } void unread(const StaticString &str) { unread(str.c_str(), str.size()); } }; } // namespace Passenger #endif /* _PASSENGER_IOTOOLS_BUFFERED_IO_H_ */