diff options
Diffstat (limited to 'backends/networking/sdl_net/reader.cpp')
-rw-r--r-- | backends/networking/sdl_net/reader.cpp | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp new file mode 100644 index 0000000000..8f3199f51c --- /dev/null +++ b/backends/networking/sdl_net/reader.cpp @@ -0,0 +1,462 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "backends/networking/sdl_net/reader.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/memstream.h" +#include "common/stream.h" + +namespace Networking { + +Reader::Reader() { + _state = RS_NONE; + _content = nullptr; + _bytesLeft = 0; + + _window = nullptr; + _windowUsed = 0; + _windowSize = 0; + + _headersStream = nullptr; + _firstBlock = true; + + _contentLength = 0; + _availableBytes = 0; + _isBadRequest = false; + _allContentRead = false; +} + +Reader::~Reader() { + cleanup(); +} + +Reader &Reader::operator=(Reader &r) { + if (this == &r) + return *this; + cleanup(); + + _state = r._state; + _content = r._content; + _bytesLeft = r._bytesLeft; + r._state = RS_NONE; + + _window = r._window; + _windowUsed = r._windowUsed; + _windowSize = r._windowSize; + r._window = nullptr; + + _headersStream = r._headersStream; + r._headersStream = nullptr; + + _headers = r._headers; + _method = r._method; + _path = r._path; + _query = r._query; + _anchor = r._anchor; + _queryParameters = r._queryParameters; + _contentLength = r._contentLength; + _boundary = r._boundary; + _availableBytes = r._availableBytes; + _firstBlock = r._firstBlock; + _isBadRequest = r._isBadRequest; + _allContentRead = r._allContentRead; + + return *this; +} + +void Reader::cleanup() { + //_content is not to be freed, it's not owned by Reader + + if (_headersStream != nullptr) + delete _headersStream; + + if (_window != nullptr) + freeWindow(); +} + +bool Reader::readAndHandleFirstHeaders() { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) { + makeWindow(boundary.size()); + } + if (_headersStream == nullptr) { + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + } + + while (readOneByteInStream(_headersStream, boundary)) { + if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) { + _isBadRequest = true; + return true; + } + if (!bytesLeft()) + return false; + } + handleFirstHeaders(_headersStream); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + if (stream) stream->flush(); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +namespace { +void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + } +} +} + +void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) { + if (!_boundary.empty()) { + warning("Reader: handleFirstHeaders() called when first headers were already handled"); + return; + } + + //parse method, path, query, fragment + _headers = readEverythingFromMemoryStream(headersStream); + parseFirstLine(_headers); + + //find boundary + _boundary = ""; + readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary); + + //find content length + Common::String contentLength = ""; + readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength); + _contentLength = contentLength.asUint64(); + _availableBytes = _contentLength; +} + +void Reader::parseFirstLine(const Common::String &headers) { + uint32 headersSize = headers.size(); + bool bad = false; + + if (headersSize > 0) { + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "\r\n"); + if (position) { //we have at least one line - and we want the first one + //"<METHOD> <path> HTTP/<VERSION>\r\n" + Common::String method, path, http, buf; + uint32 length = position - cstr; + if (headersSize > length) + headersSize = length; + for (uint32 i = 0; i < headersSize; ++i) { + if (headers[i] != ' ') + buf += headers[i]; + if (headers[i] == ' ' || i == headersSize - 1) { + if (method == "") { + method = buf; + } else if (path == "") { + path = buf; + } else if (http == "") { + http = buf; + } else { + bad = true; + break; + } + buf = ""; + } + } + + //check that method is supported + if (method != "GET" && method != "PUT" && method != "POST") + bad = true; + + //check that HTTP/<VERSION> is OK + if (!http.hasPrefix("HTTP/")) + bad = true; + + _method = method; + parsePathQueryAndAnchor(path); + } + } + + if (bad) _isBadRequest = true; +} + +void Reader::parsePathQueryAndAnchor(Common::String path) { + //<path>[?query][#anchor] + bool readingPath = true; + bool readingQuery = false; + _path = ""; + _query = ""; + _anchor = ""; + for (uint32 i = 0; i < path.size(); ++i) { + if (readingPath) { + if (path[i] == '?') { + readingPath = false; + readingQuery = true; + } else { + _path += path[i]; + } + } else if (readingQuery) { + if (path[i] == '#') { + readingQuery = false; + } else { + _query += path[i]; + } + } else { + _anchor += path[i]; + } + } + + parseQueryParameters(); +} + +void Reader::parseQueryParameters() { + Common::String key = ""; + Common::String value = ""; + bool readingKey = true; + for (uint32 i = 0; i < _query.size(); ++i) { + if (readingKey) { + if (_query[i] == '=') { + readingKey = false; + value = ""; + } else { + key += _query[i]; + } + } else { + if (_query[i] == '&') { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + readingKey = true; + key = ""; + } else { + value += _query[i]; + } + } + } + + if (!key.empty()) { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + } +} + +bool Reader::readContentIntoStream(Common::WriteStream *stream) { + Common::String boundary = "--" + _boundary; + if (!_firstBlock) + boundary = "\r\n" + boundary; + if (_boundary.empty()) + boundary = "\r\n"; + if (_window == nullptr) + makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + + _firstBlock = false; + if (stream) + stream->flush(); + + freeWindow(); + _state = RS_READING_HEADERS; + return true; +} + +void Reader::makeWindow(uint32 size) { + freeWindow(); + + _window = new byte[size]; + _windowUsed = 0; + _windowSize = size; +} + +void Reader::freeWindow() { + delete[] _window; + _window = nullptr; + _windowUsed = _windowSize = 0; +} + +namespace { +bool windowEqualsString(const byte *window, uint32 windowSize, const Common::String &boundary) { + if (boundary.size() != windowSize) + return false; + + for (uint32 i = 0; i < windowSize; ++i) { + if (window[i] != boundary[i]) + return false; + } + + return true; +} +} + +bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary) { + byte b = readOne(); + _window[_windowUsed++] = b; + if (_windowUsed < _windowSize) + return true; + + //when window is filled, check whether that's the boundary + if (windowEqualsString(_window, _windowSize, boundary)) + return false; + + //if not, add the first byte of the window to the string + if (stream) + stream->writeByte(_window[0]); + for (uint32 i = 1; i < _windowSize; ++i) + _window[i - 1] = _window[i]; + --_windowUsed; + return true; +} + +byte Reader::readOne() { + byte b; + _content->read(&b, 1); + --_availableBytes; + --_bytesLeft; + return b; +} + +/// public + +bool Reader::readFirstHeaders() { + if (_state == RS_NONE) + _state = RS_READING_HEADERS; + + if (!bytesLeft()) + return false; + + if (_state == RS_READING_HEADERS) + return readAndHandleFirstHeaders(); + + warning("Reader::readFirstHeaders(): bad state"); + return false; +} + +bool Reader::readFirstContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readFirstContent(): bad state"); + return false; + } + + // no difference, actually + return readBlockContent(stream); +} + +bool Reader::readBlockHeaders(Common::WriteStream *stream) { + if (_state != RS_READING_HEADERS) { + warning("Reader::readBlockHeaders(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + return readBlockHeadersIntoStream(stream); +} + +bool Reader::readBlockContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readBlockContent(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + if (!readContentIntoStream(stream)) + return false; + + if (_availableBytes >= 2) { + Common::String bts; + bts += readOne(); + bts += readOne(); + if (bts == "--") + _allContentRead = true; + else if (bts != "\r\n") + warning("Reader: strange bytes: \"%s\"", bts.c_str()); + } else { + warning("Reader: strange ending"); + _allContentRead = true; + } + + return true; +} + +uint32 Reader::bytesLeft() const { return _bytesLeft; } + +void Reader::setContent(Common::MemoryReadWriteStream *stream) { + _content = stream; + _bytesLeft = stream->size() - stream->pos(); +} + +bool Reader::badRequest() const { return _isBadRequest; } + +bool Reader::noMoreContent() const { return _allContentRead; } + +Common::String Reader::headers() const { return _headers; } + +Common::String Reader::method() const { return _method; } + +Common::String Reader::path() const { return _path; } + +Common::String Reader::query() const { return _query; } + +Common::String Reader::queryParameter(Common::String name) const { return _queryParameters[name]; } + +Common::String Reader::anchor() const { return _anchor; } + +Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (true) { + readBytes = stream->read(buf, 1024); + if (readBytes == 0) + break; + result += Common::String(buf, readBytes); + } + return result; +} + +} // End of namespace Networking |