aboutsummaryrefslogtreecommitdiff
path: root/backends/networking/curl
diff options
context:
space:
mode:
Diffstat (limited to 'backends/networking/curl')
-rw-r--r--backends/networking/curl/connectionmanager.cpp199
-rw-r--r--backends/networking/curl/connectionmanager.h127
-rw-r--r--backends/networking/curl/curljsonrequest.cpp215
-rw-r--r--backends/networking/curl/curljsonrequest.h67
-rw-r--r--backends/networking/curl/curlrequest.cpp162
-rw-r--r--backends/networking/curl/curlrequest.h100
-rw-r--r--backends/networking/curl/networkreadstream.cpp257
-rw-r--r--backends/networking/curl/networkreadstream.h142
-rw-r--r--backends/networking/curl/request.cpp74
-rw-r--r--backends/networking/curl/request.h203
10 files changed, 1546 insertions, 0 deletions
diff --git a/backends/networking/curl/connectionmanager.cpp b/backends/networking/curl/connectionmanager.cpp
new file mode 100644
index 0000000000..e1761bddc6
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.cpp
@@ -0,0 +1,199 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include <curl/curl.h>
+
+namespace Common {
+
+DECLARE_SINGLETON(Networking::ConnectionManager);
+
+}
+
+namespace Networking {
+
+ConnectionManager::ConnectionManager(): _multi(0), _timerStarted(false), _frame(0) {
+ curl_global_init(CURL_GLOBAL_ALL);
+ _multi = curl_multi_init();
+}
+
+ConnectionManager::~ConnectionManager() {
+ stopTimer();
+
+ //terminate all requests
+ _handleMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end(); ++i) {
+ Request *request = i->request;
+ RequestCallback callback = i->onDeleteCallback;
+ if (request)
+ request->finish();
+ delete request;
+ if (callback)
+ (*callback)(request);
+ }
+ _requests.clear();
+
+ //cleanup
+ curl_multi_cleanup(_multi);
+ curl_global_cleanup();
+ _multi = nullptr;
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::registerEasyHandle(CURL *easy) const {
+ curl_multi_add_handle(_multi, easy);
+}
+
+Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) {
+ _addedRequestsMutex.lock();
+ _addedRequests.push_back(RequestWithCallback(request, callback));
+ if (!_timerStarted)
+ startTimer();
+ _addedRequestsMutex.unlock();
+ return request;
+}
+
+Common::String ConnectionManager::urlEncode(Common::String s) const {
+ if (!_multi)
+ return "";
+ char *output = curl_easy_escape(_multi, s.c_str(), s.size());
+ if (output) {
+ Common::String result = output;
+ curl_free(output);
+ return result;
+ }
+ return "";
+}
+
+uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
+ return TIMER_INTERVAL * CLOUD_PERIOD;
+}
+
+//private goes here:
+
+void connectionsThread(void *ignored) {
+ ConnMan.handle();
+}
+
+void ConnectionManager::startTimer(int interval) {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ if (manager->installTimerProc(connectionsThread, interval, 0, "Networking::ConnectionManager's Timer")) {
+ _timerStarted = true;
+ } else {
+ warning("Failed to install Networking::ConnectionManager's timer");
+ }
+}
+
+void ConnectionManager::stopTimer() {
+ debug(9, "timer stopped");
+ Common::TimerManager *manager = g_system->getTimerManager();
+ manager->removeTimerProc(connectionsThread);
+ _timerStarted = false;
+}
+
+bool ConnectionManager::hasAddedRequests() {
+ _addedRequestsMutex.lock();
+ bool hasNewRequests = !_addedRequests.empty();
+ _addedRequestsMutex.unlock();
+ return hasNewRequests;
+}
+
+void ConnectionManager::handle() {
+ //lock mutex here (in case another handle() would be called before this one ends)
+ _handleMutex.lock();
+ ++_frame;
+ if (_frame % CLOUD_PERIOD == 0)
+ interateRequests();
+ if (_frame % CURL_PERIOD == 0)
+ processTransfers();
+
+ if (_requests.empty() && !hasAddedRequests())
+ stopTimer();
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::interateRequests() {
+ //add new requests
+ _addedRequestsMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _addedRequests.begin(); i != _addedRequests.end(); ++i) {
+ _requests.push_back(*i);
+ }
+ _addedRequests.clear();
+ _addedRequestsMutex.unlock();
+
+ //call handle() of all running requests (so they can do their work)
+ debug(9, "handling %d request(s)", _requests.size());
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) {
+ Request *request = i->request;
+ if (request) {
+ if (request->state() == PROCESSING)
+ request->handle();
+ else if (request->state() == RETRY)
+ request->handleRetry();
+ }
+
+ if (!request || request->state() == FINISHED) {
+ delete (i->request);
+ if (i->onDeleteCallback)
+ (*i->onDeleteCallback)(i->request); //that's not a mistake (we're passing an address and that method knows there is no object anymore)
+ _requests.erase(i);
+ continue;
+ }
+
+ ++i;
+ }
+}
+
+void ConnectionManager::processTransfers() {
+ if (!_multi) return;
+
+ //check libcurl's transfers and notify requests of messages from queue (transfer completion or failure)
+ int transfersRunning;
+ curl_multi_perform(_multi, &transfersRunning);
+
+ int messagesInQueue;
+ CURLMsg *curlMsg;
+ while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) {
+ CURL *easyHandle = curlMsg->easy_handle;
+
+ NetworkReadStream *stream;
+ curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
+ if (stream)
+ stream->finished();
+
+ if (curlMsg->msg == CURLMSG_DONE) {
+ debug(9, "ConnectionManager: SUCCESS (%d - %s)", curlMsg->data.result, curl_easy_strerror(curlMsg->data.result));
+ } else {
+ warning("ConnectionManager: FAILURE (CURLMsg (%d))", curlMsg->msg);
+ }
+
+ curl_multi_remove_handle(_multi, easyHandle);
+ }
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/connectionmanager.h b/backends/networking/curl/connectionmanager.h
new file mode 100644
index 0000000000..f6a9fcb36f
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.h
@@ -0,0 +1,127 @@
+/* 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.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H
+#define BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H
+
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/singleton.h"
+#include "common/hashmap.h"
+#include "common/mutex.h"
+
+typedef void CURL;
+typedef void CURLM;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+class ConnectionManager : public Common::Singleton<ConnectionManager> {
+ static const uint32 FRAMES_PER_SECOND = 20;
+ static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
+ static const uint32 CLOUD_PERIOD = 20; //every 20th frame
+ static const uint32 CURL_PERIOD = 1; //every frame
+
+ friend void connectionsThread(void *); //calls handle()
+
+ typedef Common::BaseCallback<Request *> *RequestCallback;
+
+ /**
+ * RequestWithCallback is used by ConnectionManager to
+ * storage the Request and a callback which should be
+ * called on Request delete.
+ *
+ * Usually one won't need to pass such callback, but
+ * in some cases you'd like to know whether Request is
+ * still running.
+ *
+ * For example, Cloud::Storage is keeping track of how
+ * many Requests are running, and thus it needs to know
+ * that Request was destroyed to decrease its counter.
+ *
+ * onDeleteCallback is called with *invalid* pointer.
+ * ConnectionManager deletes Request first and then passes
+ * the pointer to the callback. One may use the address
+ * to find it in own HashMap or Array and remove it.
+ * So, again, this pointer is for information only. One
+ * cannot use it.
+ */
+ struct RequestWithCallback {
+ Request *request;
+ RequestCallback onDeleteCallback;
+
+ RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr): request(rq), onDeleteCallback(cb) {}
+ };
+
+ CURLM *_multi;
+ bool _timerStarted;
+ Common::Array<RequestWithCallback> _requests, _addedRequests;
+ Common::Mutex _handleMutex, _addedRequestsMutex;
+ uint32 _frame;
+
+ void startTimer(int interval = TIMER_INTERVAL);
+ void stopTimer();
+ void handle();
+ void interateRequests();
+ void processTransfers();
+ bool hasAddedRequests();
+
+public:
+ ConnectionManager();
+ virtual ~ConnectionManager();
+
+ /**
+ * All libcurl transfers are going through this ConnectionManager.
+ * So, if you want to start any libcurl transfer, you must create
+ * an easy handle and register it using this method.
+ */
+ void registerEasyHandle(CURL *easy) const;
+
+ /**
+ * Use this method to add new Request into manager's queue.
+ * Manager will periodically call handle() method of these
+ * Requests until they set their state to FINISHED.
+ *
+ * If Request's state is RETRY, handleRetry() is called instead.
+ *
+ * The passed callback would be called after Request is deleted.
+ *
+ * @note This method starts the timer if it's not started yet.
+ *
+ * @return the same Request pointer, just as a shortcut
+ */
+ Request *addRequest(Request *request, RequestCallback callback = nullptr);
+
+ /** Return URL-encoded version of given string. */
+ Common::String urlEncode(Common::String s) const;
+
+ static uint32 getCloudRequestsPeriodInMicroseconds();
+};
+
+/** Shortcut for accessing the connection manager. */
+#define ConnMan Networking::ConnectionManager::instance()
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp
new file mode 100644
index 0000000000..7764e11950
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.cpp
@@ -0,0 +1,215 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlJsonRequest::CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url) :
+ CurlRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES),
+ _buffer(new byte[CURL_JSON_REQUEST_BUFFER_SIZE]) {}
+
+CurlJsonRequest::~CurlJsonRequest() {
+ delete _jsonCallback;
+ delete[] _buffer;
+}
+
+char *CurlJsonRequest::getPreparedContents() {
+ //write one more byte in the end
+ byte zero[1] = {0};
+ _contentsStream.write(zero, 1);
+
+ //replace all "bad" bytes with '.' character
+ byte *result = _contentsStream.getData();
+ uint32 size = _contentsStream.size();
+ for (uint32 i = 0; i < size; ++i) {
+ if (result[i] == '\n')
+ result[i] = ' '; //yeah, kinda stupid
+ else if (result[i] < 0x20 || result[i] > 0x7f)
+ result[i] = '.';
+ }
+
+ //make it zero-terminated string
+ result[size - 1] = '\0';
+
+ return (char *)result;
+}
+
+void CurlJsonRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream) {
+ uint32 readBytes = _stream->read(_buffer, CURL_JSON_REQUEST_BUFFER_SIZE);
+ if (readBytes != 0)
+ if (_contentsStream.write(_buffer, readBytes) != readBytes)
+ warning("CurlJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
+
+ if (_stream->eos()) {
+ char *contents = getPreparedContents();
+ Common::JSONValue *json = Common::JSON::parse(contents);
+ if (json) {
+ finishJson(json); //it's JSON even if's not 200 OK? That's fine!..
+ } else {
+ if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!..
+ finishJson(nullptr);
+ else
+ finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode()));
+ }
+ }
+ }
+}
+
+void CurlJsonRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ _contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
+ //with no stream available next handle() will create another one
+}
+
+void CurlJsonRequest::finishJson(Common::JSONValue *json) {
+ Request::finishSuccess();
+ if (_jsonCallback)
+ (*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks!
+ else
+ delete json;
+}
+
+bool CurlJsonRequest::jsonIsObject(Common::JSONValue *item, const char *warningPrefix) {
+ if (item == nullptr) {
+ warning("%s: passed item is NULL", warningPrefix);
+ return false;
+ }
+
+ if (item->isObject()) return true;
+
+ warning("%s: passed item is not an object", warningPrefix);
+ debug(9, "%s", item->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isObject()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isArray()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curljsonrequest.h b/backends/networking/curl/curljsonrequest.h
new file mode 100644
index 0000000000..11c6bc81ea
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.h
@@ -0,0 +1,67 @@
+/* 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.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H
+
+#include "backends/networking/curl/curlrequest.h"
+#include "common/memstream.h"
+#include "common/json.h"
+
+namespace Networking {
+
+typedef Response<Common::JSONValue *> JsonResponse;
+typedef Common::BaseCallback<JsonResponse> *JsonCallback;
+
+#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024
+
+class CurlJsonRequest: public CurlRequest {
+protected:
+ JsonCallback _jsonCallback;
+ Common::MemoryWriteStreamDynamic _contentsStream;
+ byte *_buffer;
+
+ /** Prepares raw bytes from _contentsStream to be parsed with Common::JSON::parse(). */
+ char *getPreparedContents();
+
+ /** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */
+ virtual void finishJson(Common::JSONValue *json);
+
+public:
+ CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlJsonRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ static bool jsonIsObject(Common::JSONValue *item, const char *warningPrefix);
+ static bool jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curlrequest.cpp b/backends/networking/curl/curlrequest.cpp
new file mode 100644
index 0000000000..7ee8d66c66
--- /dev/null
+++ b/backends/networking/curl/curlrequest.cpp
@@ -0,0 +1,162 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/curlrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/textconsole.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url):
+ Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr),
+ _bytesBufferSize(0), _uploading(false), _usingPatch(false) {}
+
+CurlRequest::~CurlRequest() {
+ delete _stream;
+ delete _bytesBuffer;
+}
+
+NetworkReadStream *CurlRequest::makeStream() {
+ if (_bytesBuffer)
+ return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
+ if (!_formFields.empty() || !_formFiles.empty())
+ return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles);
+ return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch);
+}
+
+void CurlRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream && _stream->eos()) {
+ if (_stream->httpResponseCode() != 200) {
+ warning("CurlRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
+ ErrorResponse error(this, false, true, "", _stream->httpResponseCode());
+ finishError(error);
+ return;
+ }
+
+ finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return)
+ }
+}
+
+void CurlRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ //with no stream available next handle() will create another one
+}
+
+Common::String CurlRequest::date() const {
+ if (_stream) {
+ Common::String headers = _stream->responseHeaders();
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "Date: ");
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + 6; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ return result;
+ }
+ }
+ return "";
+}
+
+void CurlRequest::setHeaders(Common::Array<Common::String> &headers) {
+ curl_slist_free_all(_headersList);
+ _headersList = nullptr;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ addHeader(headers[i]);
+}
+
+void CurlRequest::addHeader(Common::String header) {
+ _headersList = curl_slist_append(_headersList, header.c_str());
+}
+
+void CurlRequest::addPostField(Common::String keyValuePair) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST fields would be ignored, because there is buffer present");
+
+ if (!_formFields.empty() || !_formFiles.empty())
+ warning("CurlRequest: added POST fields would be ignored, because there are form fields/files present");
+
+ if (_postFields == "")
+ _postFields = keyValuePair;
+ else
+ _postFields += "&" + keyValuePair;
+}
+
+void CurlRequest::addFormField(Common::String name, Common::String value) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form fields would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form field '%s' already had a value", name.c_str());
+
+ _formFields[name] = value;
+}
+
+void CurlRequest::addFormFile(Common::String name, Common::String filename) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form files would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form file field '%s' already had a value", name.c_str());
+
+ _formFiles[name] = filename;
+}
+
+void CurlRequest::setBuffer(byte *buffer, uint32 size) {
+ if (_postFields != "")
+ warning("CurlRequest: added POST fields would be ignored, because buffer added");
+
+ if (_bytesBuffer)
+ delete _bytesBuffer;
+
+ _bytesBuffer = buffer;
+ _bytesBufferSize = size;
+}
+
+void CurlRequest::usePut() { _uploading = true; }
+
+void CurlRequest::usePatch() { _usingPatch = true; }
+
+NetworkReadStreamResponse CurlRequest::execute() {
+ if (!_stream) {
+ _stream = makeStream();
+ ConnMan.addRequest(this);
+ }
+
+ return NetworkReadStreamResponse(this, _stream);
+}
+
+const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curlrequest.h b/backends/networking/curl/curlrequest.h
new file mode 100644
index 0000000000..75fb787f86
--- /dev/null
+++ b/backends/networking/curl/curlrequest.h
@@ -0,0 +1,100 @@
+/* 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.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_CURL_CURLREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+typedef Response<NetworkReadStream *> NetworkReadStreamResponse;
+typedef Common::BaseCallback<NetworkReadStreamResponse> *NetworkReadStreamCallback;
+
+class CurlRequest: public Request {
+protected:
+ Common::String _url;
+ NetworkReadStream *_stream;
+ curl_slist *_headersList;
+ Common::String _postFields;
+ Common::HashMap<Common::String, Common::String> _formFields;
+ Common::HashMap<Common::String, Common::String> _formFiles;
+ byte *_bytesBuffer;
+ uint32 _bytesBufferSize;
+ bool _uploading; //using PUT method
+ bool _usingPatch; //using PATCH method
+
+ virtual NetworkReadStream *makeStream();
+
+public:
+ CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+
+ /** Replaces all headers with the passed array of headers. */
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+
+ /** Adds a header into headers list. */
+ virtual void addHeader(Common::String header);
+
+ /** Adds a post field (key=value pair). */
+ virtual void addPostField(Common::String field);
+
+ /** Adds a form/multipart field (name, value). */
+ virtual void addFormField(Common::String name, Common::String value);
+
+ /** Adds a form/multipart file (field name, file name). */
+ virtual void addFormFile(Common::String name, Common::String filename);
+
+ /** Sets bytes buffer. */
+ virtual void setBuffer(byte *buffer, uint32 size);
+
+ /** Remembers to use PUT method when it would create NetworkReadStream. */
+ virtual void usePut();
+
+ /** Remembers to use PATCH method when it would create NetworkReadStream. */
+ virtual void usePatch();
+
+ /**
+ * Starts this Request with ConnMan.
+ * @return its NetworkReadStream in NetworkReadStreamResponse.
+ */
+ virtual NetworkReadStreamResponse execute();
+
+ /** Returns Request's NetworkReadStream. */
+ const NetworkReadStream *getNetworkReadStream() const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/networkreadstream.cpp b/backends/networking/curl/networkreadstream.cpp
new file mode 100644
index 0000000000..b4516701a7
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.cpp
@@ -0,0 +1,257 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/networkreadstream.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "base/version.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+static size_t curlDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->write(d, n * l);
+ return 0;
+}
+
+static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->fillWithSendingContents(d, n * l);
+ return 0;
+}
+
+static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->addResponseHeaders(d, n * l);
+ return 0;
+}
+
+static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ stream->setProgress(dlnow, dltotal);
+ return 0;
+}
+
+static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) {
+ // for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION)
+ return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+ if (uploading) {
+ curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(_easy, CURLOPT_READDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback);
+ _sendingContentsBuffer = buffer;
+ _sendingContentsSize = bufferSize;
+ } else if (usingPatch) {
+ curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH");
+ } else {
+ if (post || bufferSize != 0) {
+ curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize);
+ curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer);
+ }
+ }
+ ConnMan.registerEasyHandle(_easy);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+
+ // set POST multipart upload form fields/files
+ struct curl_httppost *formpost = nullptr;
+ struct curl_httppost *lastptr = nullptr;
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_COPYCONTENTS, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: field curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_FILE, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: file curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
+
+ ConnMan.registerEasyHandle(_easy);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) {
+ init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ init(url, headersList, formFields, formFiles);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ init(url, headersList, buffer, bufferSize, uploading, usingPatch, post);
+}
+
+NetworkReadStream::~NetworkReadStream() {
+ if (_easy)
+ curl_easy_cleanup(_easy);
+}
+
+bool NetworkReadStream::eos() const {
+ return _eos;
+}
+
+uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 actuallyRead = MemoryReadWriteStream::read(dataPtr, dataSize);
+
+ if (actuallyRead == 0) {
+ if (_requestComplete)
+ _eos = true;
+ return 0;
+ }
+
+ return actuallyRead;
+}
+
+void NetworkReadStream::finished() {
+ _requestComplete = true;
+}
+
+long NetworkReadStream::httpResponseCode() const {
+ long responseCode = -1;
+ if (_easy)
+ curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode);
+ return responseCode;
+}
+
+Common::String NetworkReadStream::currentLocation() const {
+ Common::String result = "";
+ if (_easy) {
+ char *pointer;
+ curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer);
+ result = Common::String(pointer);
+ }
+ return result;
+}
+
+Common::String NetworkReadStream::responseHeaders() const {
+ return _responseHeaders;
+}
+
+uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
+ uint32 size = _sendingContentsSize - _sendingContentsPos;
+ if (size > maxSize)
+ size = maxSize;
+ for (uint32 i = 0; i < size; ++i) {
+ bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i];
+ }
+ _sendingContentsPos += size;
+ return size;
+}
+
+uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 size) {
+ _responseHeaders += Common::String(buffer, size);
+ return size;
+}
+
+double NetworkReadStream::getProgress() const {
+ if (_progressTotal < 1)
+ return 0;
+ return (double)_progressDownloaded / (double)_progressTotal;
+}
+
+void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) {
+ _progressDownloaded = downloaded;
+ _progressTotal = total;
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/networkreadstream.h b/backends/networking/curl/networkreadstream.h
new file mode 100644
index 0000000000..62a3e41d9b
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.h
@@ -0,0 +1,142 @@
+/* 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.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H
+#define BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H
+
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+typedef void CURL;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream: public Common::MemoryReadWriteStream {
+ CURL *_easy;
+ bool _eos, _requestComplete;
+ const byte *_sendingContentsBuffer;
+ uint32 _sendingContentsSize;
+ uint32 _sendingContentsPos;
+ Common::String _responseHeaders;
+ uint64 _progressDownloaded, _progressTotal;
+ void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
+ void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles);
+
+public:
+ /** Send <postFields>, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false);
+ /** Send <formFields>, <formFiles>, using POST multipart/form. */
+ NetworkReadStream(
+ const char *url, curl_slist *headersList,
+ Common::HashMap<Common::String, Common::String> formFields,
+ Common::HashMap<Common::String, Common::String> formFiles);
+ /** Send <buffer, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true);
+ virtual ~NetworkReadStream();
+
+ /**
+ * Returns true if a read failed because the stream end has been reached.
+ * This flag is cleared by clearErr().
+ * For a SeekableReadStream, it is also cleared by a successful seek.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C feof(). In particular, in a stream
+ * with N bytes, reading exactly N bytes from the start should *not*
+ * set eos; only reading *beyond* the available data should set it.
+ */
+ virtual bool eos() const;
+
+ /**
+ * Read data from the stream. Subclasses must implement this
+ * method; all other read methods are implemented using it.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C fread(), in particular where
+ * it concerns setting error and end of file/stream flags.
+ *
+ * @param dataPtr pointer to a buffer into which the data is read
+ * @param dataSize number of bytes to be read
+ * @return the number of bytes which were actually read.
+ */
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+
+ /**
+ * This method is called by ConnectionManager to indicate
+ * that transfer is finished.
+ *
+ * @note It's called on failure too.
+ */
+ void finished();
+
+ /**
+ * Returns HTTP response code from inner CURL handle.
+ * It returns -1 to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ long httpResponseCode() const;
+
+ /**
+ * Return current location URL from inner CURL handle.
+ * "" is returned to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String currentLocation() const;
+
+ /**
+ * Return response headers.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String responseHeaders() const;
+
+ /**
+ * Fills the passed buffer with _sendingContentsBuffer contents.
+ * It works similarly to read(), expect it's not for reading
+ * Stream's contents, but for sending our own data to the server.
+ *
+ * @returns how many bytes were actually read (filled in)
+ */
+ uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize);
+
+ /**
+ * Remembers headers returned to CURL in server's response.
+ *
+ * @returns how many bytes were actually read
+ */
+ uint32 addResponseHeaders(char *buffer, uint32 size);
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Used in curl progress callback to pass current downloaded/total values. */
+ void setProgress(uint64 downloaded, uint64 total);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/request.cpp b/backends/networking/curl/request.cpp
new file mode 100644
index 0000000000..398af04f4b
--- /dev/null
+++ b/backends/networking/curl/request.cpp
@@ -0,0 +1,74 @@
+/* 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/curl/request.h"
+
+namespace Networking {
+
+ErrorResponse::ErrorResponse(Request *rq):
+ request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {}
+
+ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode):
+ request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {}
+
+Request::Request(DataCallback cb, ErrorCallback ecb):
+ _callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {}
+
+Request::~Request() {
+ delete _callback;
+ delete _errorCallback;
+}
+
+void Request::handleRetry() {
+ if (_retryInSeconds > 0) {
+ --_retryInSeconds;
+ } else {
+ _state = PROCESSING;
+ restart();
+ }
+}
+
+void Request::pause() { _state = PAUSED; }
+
+void Request::finish() {
+ ErrorResponse error(this, true, false, "", -1);
+ finishError(error);
+}
+
+void Request::retry(uint32 seconds) {
+ _state = RETRY;
+ _retryInSeconds = seconds;
+}
+
+RequestState Request::state() const { return _state; }
+
+Common::String Request::date() const { return ""; }
+
+void Request::finishError(ErrorResponse error) {
+ _state = FINISHED;
+ if (_errorCallback)
+ (*_errorCallback)(error);
+}
+
+void Request::finishSuccess() { _state = FINISHED; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/request.h b/backends/networking/curl/request.h
new file mode 100644
index 0000000000..b1261f23ee
--- /dev/null
+++ b/backends/networking/curl/request.h
@@ -0,0 +1,203 @@
+/* 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.
+ *
+ */
+
+#ifndef BACKENDS_NETWORKING_CURL_REQUEST_H
+#define BACKENDS_NETWORKING_CURL_REQUEST_H
+
+#include "common/callback.h"
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace Networking {
+
+class Request;
+
+/**
+ * Response<T> is a struct to be returned from Request
+ * to user's callbacks. It's a type safe way to indicate
+ * which "return value" Request has and user awaits.
+ *
+ * It just keeps a Request pointer together with
+ * some T value (which might be a pointer, a reference
+ * or a plain type (copied by value)).
+ *
+ * To make it more convenient, typedefs are used.
+ * For example, Response<void *> is called DataResponse
+ * and corresponding callback pointer is DataCallback.
+ */
+
+template<typename T> struct Response {
+ Request *request;
+ T value;
+
+ Response(Request *rq, T v) : request(rq), value(v) {}
+};
+
+/**
+ * ErrorResponse is a struct to be returned from Request
+ * to user's failure callbacks.
+ *
+ * It keeps a Request pointer together with some useful
+ * information fields, which would explain why failure
+ * callback was called.
+ *
+ * <interrupted> flag is set when Request was interrupted,
+ * i.e. finished by user with finish() call.
+ *
+ * <failed> flag is set when Request has failed because of
+ * some error (bad server response, for example).
+ *
+ * <response> contains server's original response.
+ *
+ * <httpResponseCode> contains server's HTTP response code.
+ */
+
+struct ErrorResponse {
+ Request *request;
+ bool interrupted;
+ bool failed;
+ Common::String response;
+ long httpResponseCode;
+
+ ErrorResponse(Request *rq);
+ ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode);
+};
+
+typedef Response<void *> DataReponse;
+typedef Common::BaseCallback<DataReponse> *DataCallback;
+typedef Common::BaseCallback<ErrorResponse> *ErrorCallback;
+
+/**
+ * RequestState is used to indicate current Request state.
+ * ConnectionManager uses it to decide what to do with the Request.
+ *
+ * PROCESSING state indicates that Request is working.
+ * ConnectionManager calls handle() method of Requests in that state.
+ *
+ * PAUSED state indicates that Request is not working.
+ * ConnectionManager keeps Requests in that state and doesn't call any methods of those.
+ *
+ * RETRY state indicates that Request must restart after a few seconds.
+ * ConnectionManager calls handleRetry() method of Requests in that state.
+ * Default handleRetry() implementation decreases _retryInSeconds value
+ * until it reaches zero. When it does, Request's restart() method is called.
+ *
+ * FINISHED state indicates that Request did the work and might be deleted.
+ * ConnectionManager deletes Requests in that state.
+ * After this state is set, but before ConnectionManager deletes the Request,
+ * Request calls user's callback. User can ask Request to change its state
+ * by calling retry() or pause() methods and Request won't be deleted.
+ *
+ * Request get a success and failure callbacks. Request must call one
+ * (and only one!) of these callbacks when it sets FINISHED state.
+ */
+enum RequestState {
+ PROCESSING,
+ PAUSED,
+ RETRY,
+ FINISHED
+};
+
+class Request {
+protected:
+ /**
+ * Callback, which should be called when Request is finished.
+ * That's the way Requests pass the result to the code which asked to create this request.
+ *
+ * @note some Requests use their own callbacks to return something but void *.
+ * @note callback must be called in finish() or similar method.
+ */
+ DataCallback _callback;
+
+ /**
+ * Callback, which should be called when Request is failed/interrupted.
+ * That's the way Requests pass error information to the code which asked to create this request.
+ * @note callback must be called in finish() or similar method.
+ */
+ ErrorCallback _errorCallback;
+
+ /**
+ * Request state, which is used by ConnectionManager to determine
+ * whether request might be deleted or it's still working.
+ *
+ * State might be changed from outside with finish(), pause() or
+ * retry() methods. Override these if you want to react to these
+ * changes correctly.
+ */
+ RequestState _state;
+
+ /** In RETRY state this indicates whether it's time to call restart(). */
+ uint32 _retryInSeconds;
+
+ /** Sets FINISHED state and calls the _errorCallback with given error. */
+ virtual void finishError(ErrorResponse error);
+
+ /** Sets FINISHED state. Implementations might extend it if needed. */
+ virtual void finishSuccess();
+
+public:
+ Request(DataCallback cb, ErrorCallback ecb);
+ virtual ~Request();
+
+ /** Method, which does actual work. Depends on what this Request is doing. */
+ virtual void handle() = 0;
+
+ /** Method, which is called by ConnectionManager when Request's state is RETRY. */
+ virtual void handleRetry();
+
+ /** Method, which is used to restart the Request. */
+ virtual void restart() = 0;
+
+ /** Method, which is called to pause the Request. */
+ virtual void pause();
+
+ /**
+ * Method, which is called to *interrupt* the Request.
+ * When it's called, Request must stop its work and
+ * call the failure callback to notify user.
+ */
+ virtual void finish();
+
+ /** Method, which is called to retry the Request. */
+ virtual void retry(uint32 seconds);
+
+ /** Returns Request's current state. */
+ RequestState state() const;
+
+ /**
+ * Return date this Request received from server.
+ * It could be extracted from "Date" header,
+ * which is kept in NetworkReadStream.
+ *
+ * @note not all Requests do that, so "" is returned
+ * to indicate the date is unknown. That's also true
+ * if no server response available or no "Date" header
+ * was passed.
+ *
+ * @returns date from "Date" response header.
+ */
+ virtual Common::String date() const;
+};
+
+} // End of namespace Networking
+
+#endif