Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
// Allow use of stuff in <time.h> and abort()
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(__printf__, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni-android.h"
#include "backends/networking/basic/android/jni.h"
#include "backends/networking/http/android/connectionmanager-android.h"
#include "backends/networking/http/android/networkreadstream-android.h"
#include "common/debug.h"
namespace Common {
template<>
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
return new Networking::ConnectionManagerAndroid();
}
} // namespace Common
namespace Networking {
ConnectionManagerAndroid::ConnectionManagerAndroid() : ConnectionManager(), _manager(0) {
JNIEnv *env = JNI::getEnv();
NetJNI::init(env);
// As we are called once, don't bother storing a global reference in JNI init
jclass cls = env->FindClass("org/scummvm/scummvm/net/HTTPManager");
jobject obj = env->NewObject(cls, NetJNI::_MID_manager_init);
if (env->ExceptionCheck()) {
LOGE("HTTPManager::<init> failed");
env->ExceptionDescribe();
env->ExceptionClear();
abort();
}
_manager = env->NewGlobalRef(obj);
env->DeleteLocalRef(obj);
}
ConnectionManagerAndroid::~ConnectionManagerAndroid() {
JNIEnv *env = JNI::getEnv();
env->DeleteGlobalRef(_manager);
}
void ConnectionManagerAndroid::registerRequest(JNIEnv *env, jobject request) const {
env->CallVoidMethod(_manager, NetJNI::_MID_manager_startRequest, request);
if (env->ExceptionCheck()) {
LOGE("HTTPManager::startRequest failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
// private goes here:
void ConnectionManagerAndroid::processTransfers() {
JNIEnv *env = JNI::getEnv();
// Don't call Java is there is no need
// This is not perfect as the worker threads can update it and we are not
// synchronized but that should do the job
if (env->GetBooleanField(_manager, NetJNI::_FID_manager__empty)) {
return;
}
env->CallVoidMethod(_manager, NetJNI::_MID_manager_poll);
if (env->ExceptionCheck()) {
LOGE("HTTPManager::poll failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
} // End of namespace Networking

View File

@@ -0,0 +1,46 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
#define BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
#include <jni.h>
#include "backends/networking/http/connectionmanager.h"
namespace Networking {
class ConnectionManagerAndroid : public ConnectionManager {
private:
jobject _manager;
void processTransfers() override;
public:
ConnectionManagerAndroid();
~ConnectionManagerAndroid() override;
void registerRequest(JNIEnv *env, jobject request) const;
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,408 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
// Allow use of stuff in <time.h> and abort()
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(__printf__, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni-android.h"
#include "backends/networking/basic/android/jni.h"
#include "backends/networking/http/android/networkreadstream-android.h"
#include "backends/networking/http/android/connectionmanager-android.h"
#include "common/debug.h"
namespace Networking {
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamAndroid(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamAndroid(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamAndroid(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
}
void NetworkReadStreamAndroid::gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers) {
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
if (!stream) {
return;
}
jsize size = env->GetArrayLength(headers);
assert((size & 1) == 0);
stream->_responseHeadersMap.clear();
for (jsize i = 0; i < size; i += 2) {
jstring key_obj = (jstring)env->GetObjectArrayElement(headers, i);
jstring value_obj = (jstring)env->GetObjectArrayElement(headers, i + 1);
const char *key = env->GetStringUTFChars(key_obj, 0);
const char *value = env->GetStringUTFChars(value_obj, 0);
if (key != nullptr && value != nullptr) {
stream->_responseHeadersMap[key] = value;
}
env->ReleaseStringUTFChars(key_obj, key);
env->ReleaseStringUTFChars(value_obj, value);
env->DeleteLocalRef(key_obj);
env->DeleteLocalRef(value_obj);
}
}
void NetworkReadStreamAndroid::gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize) {
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
if (!stream) {
return;
}
jsize arrSize = env->GetArrayLength(data);
assert(size >= 0 && (jsize)size <= arrSize);
if (size > 0) {
jbyte *dataP = (jbyte *)env->GetPrimitiveArrayCritical(data, 0);
assert(dataP);
stream->_backingStream.write(dataP, size);
stream->_downloaded += size;
env->ReleasePrimitiveArrayCritical(data, dataP, JNI_ABORT);
}
stream->setProgress(stream->_downloaded, totalSize);
}
void NetworkReadStreamAndroid::finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg) {
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
if (!stream) {
return;
}
Common::String errorMsgStr;
if (errorMsg) {
const char *errorMsgP = env->GetStringUTFChars(errorMsg, 0);
if (errorMsgP != 0) {
errorMsgStr = Common::String(errorMsgP);
env->ReleaseStringUTFChars(errorMsg, errorMsgP);
}
}
stream->finished(errorCode, errorMsgStr);
}
static jobjectArray getHeaders(JNIEnv *env, RequestHeaders *headersList) {
if (!headersList) {
return nullptr;
}
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray array = env->NewObjectArray(headersList->size() * 2, stringClass, nullptr);
env->DeleteLocalRef(stringClass);
int i = 0;
for (const Common::String &header : *headersList) {
// Find the colon separator
uint colonPos = header.findFirstOf(':');
if (colonPos == Common::String::npos) {
warning("NetworkReadStreamAndroid: Malformed header (no colon): %s", header.c_str());
continue;
}
// Split into key and value parts
Common::String key = header.substr(0, colonPos);
Common::String value = header.substr(colonPos + 1);
// Trim whitespace from key and value
key.trim();
value.trim();
jobject key_obj = env->NewStringUTF(key.c_str());
jobject value_obj = env->NewStringUTF(value.c_str());
// Store key and value as separate strings
env->SetObjectArrayElement(array, i, key_obj);
env->SetObjectArrayElement(array, i + 1, value_obj);
env->DeleteLocalRef(key_obj);
env->DeleteLocalRef(value_obj);
i += 2;
}
return array;
}
static jobjectArray getFormFields(JNIEnv *env, const Common::HashMap<Common::String, Common::String> &map) {
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
env->DeleteLocalRef(stringClass);
int i = 0;
for (const Common::HashMap<Common::String, Common::String>::Node &entry : map) {
jobject key_obj = env->NewStringUTF(entry._key.c_str());
jobject value_obj = env->NewStringUTF(entry._value.c_str());
// Store key and value as separate strings
env->SetObjectArrayElement(array, i, key_obj);
env->SetObjectArrayElement(array, i + 1, value_obj);
env->DeleteLocalRef(key_obj);
env->DeleteLocalRef(value_obj);
}
return array;
}
static jobjectArray getFormFiles(JNIEnv *env, const Common::HashMap<Common::String, Common::Path> &map) {
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
env->DeleteLocalRef(stringClass);
int i = 0;
for (const Common::HashMap<Common::String, Common::Path>::Node &entry : map) {
jobject key_obj = env->NewStringUTF(entry._key.c_str());
jobject value_obj = env->NewStringUTF(entry._value.toString('/').c_str());
// Store key and value as separate strings
env->SetObjectArrayElement(array, i, key_obj);
env->SetObjectArrayElement(array, i + 1, value_obj);
env->DeleteLocalRef(key_obj);
env->DeleteLocalRef(value_obj);
}
return array;
}
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields,
bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
if (!reuse(url, headersList, postFields, uploading, usingPatch)) {
abort();
}
}
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields,
const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
if (!reuse(url, headersList, formFields, formFiles)) {
abort();
}
}
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
if (!reuse(url, headersList, buffer, bufferSize, uploading, usingPatch, post)) {
abort();
}
}
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch) {
JNIEnv *env = JNI::getEnv();
resetStream(env);
jstring url_obj = env->NewStringUTF(url);
jobjectArray headers_obj = getHeaders(env, headersList);
jbyteArray uploadBuffer_obj = env->NewByteArray(postFields.size());
env->SetByteArrayRegion(uploadBuffer_obj, 0, postFields.size(), (const jbyte *)postFields.c_str());
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
env->DeleteLocalRef(uploadBuffer_obj);
env->DeleteLocalRef(headers_obj);
env->DeleteLocalRef(url_obj);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::<init> failed");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
_request = env->NewGlobalRef(obj);
env->DeleteLocalRef(obj);
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
return true;
}
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
JNIEnv *env = JNI::getEnv();
resetStream(env);
jstring url_obj = env->NewStringUTF(url);
jobjectArray headers_obj = getHeaders(env, headersList);
jobjectArray formFields_obj = getFormFields(env, formFields);
jobjectArray formFiles_obj = getFormFiles(env, formFiles);
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_forminit, (jlong)this, url_obj, headers_obj, formFields_obj, formFiles_obj);
env->DeleteLocalRef(formFiles_obj);
env->DeleteLocalRef(formFields_obj);
env->DeleteLocalRef(headers_obj);
env->DeleteLocalRef(url_obj);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::<init> failed");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
_request = env->NewGlobalRef(obj);
env->DeleteLocalRef(obj);
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
return true;
}
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
JNIEnv *env = JNI::getEnv();
resetStream(env);
jstring url_obj = env->NewStringUTF(url);
jobjectArray headers_obj = getHeaders(env, headersList);
jbyteArray uploadBuffer_obj = env->NewByteArray(bufferSize);
env->SetByteArrayRegion(uploadBuffer_obj, 0, bufferSize, (const jbyte *)buffer);
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
env->DeleteLocalRef(uploadBuffer_obj);
env->DeleteLocalRef(headers_obj);
env->DeleteLocalRef(url_obj);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::<init> failed");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
_request = env->NewGlobalRef(obj);
env->DeleteLocalRef(obj);
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
return true;
}
NetworkReadStreamAndroid::~NetworkReadStreamAndroid() {
JNIEnv *env = JNI::getEnv();
env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::cancel failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
env->DeleteGlobalRef(_request);
}
void NetworkReadStreamAndroid::finished(int errorCode, const Common::String &errorMsg) {
_requestComplete = true;
_errorCode = errorCode;
_errorMsg = errorMsg;
if (_errorCode >= 200 && _errorCode < 300) {
debug(9, "NetworkReadStreamAndroid: %s - Request succeeded", currentLocation().c_str());
} else {
warning("NetworkReadStreamAndroid: %s - Request failed (%d - %s)", currentLocation().c_str(), _errorCode, _errorMsg.c_str());
}
}
void NetworkReadStreamAndroid::resetStream(JNIEnv *env) {
_eos = _requestComplete = false;
_progressDownloaded = _progressTotal = 0;
if (_request) {
env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::cancel failed");
env->ExceptionDescribe();
env->ExceptionClear();
}
env->DeleteGlobalRef(_request);
_request = nullptr;
}
_responseHeadersMap.clear();
_downloaded = 0;
_errorCode = 0;
_errorMsg.clear();
}
Common::String NetworkReadStreamAndroid::currentLocation() const {
if (!_request) {
return Common::String();
}
JNIEnv *env = JNI::getEnv();
jstring location_obj = (jstring)env->CallObjectMethod(_request, NetJNI::_MID_request_getURL);
if (env->ExceptionCheck()) {
LOGE("HTTPRequest::getURL failed");
env->ExceptionDescribe();
env->ExceptionClear();
return Common::String();
}
uint length = env->GetStringLength(location_obj);
if (!length) {
env->DeleteLocalRef(location_obj);
return Common::String();
}
const char *location_ptr = env->GetStringUTFChars(location_obj, 0);
if (!location_ptr) {
env->DeleteLocalRef(location_obj);
return Common::String();
}
Common::String result(location_ptr, length);
env->ReleaseStringUTFChars(location_obj, location_ptr);
env->DeleteLocalRef(location_obj);
return result;
}
} // End of namespace Networking

View File

@@ -0,0 +1,82 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
#define BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
#include <jni.h>
#include "backends/networking/http/networkreadstream.h"
namespace Networking {
class NetworkReadStreamAndroid : public NetworkReadStream {
friend class NetJNI;
private:
static void gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers);
static void gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize);
static void finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg);
void resetStream(JNIEnv *env);
void finished(int errorCode, const Common::String &errorMsg);
jobject _request;
Common::HashMap<Common::String, Common::String> _responseHeadersMap;
uint64 _downloaded;
int _errorCode;
Common::String _errorMsg;
public:
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
~NetworkReadStreamAndroid() override;
/** Send <postFields>, using POST by default. */
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override;
/** Send <formFields>, <formFiles>, using POST multipart/form. */
bool reuse(
const char *url, RequestHeaders *headersList,
const Common::HashMap<Common::String, Common::String> &formFields,
const Common::HashMap<Common::String, Common::Path> &formFiles) override;
/** Send <buffer>, using POST by default. */
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
long httpResponseCode() const override { return _errorCode; }
Common::String currentLocation() const override;
/**
* Return response headers as HashMap. All header names in
* it are lowercase.
*
* @note This method should be called when eos() == true.
*/
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override { return _responseHeadersMap; }
bool hasError() const override { return _errorCode < 200 || _errorCode >= 300; }
const char *getError() const override { return _errorMsg.c_str(); }
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,167 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/timer.h"
namespace Common {
DECLARE_SINGLETON(Networking::ConnectionManager);
/* The makeInstance function is defined in the platform specific source file */
} // namespace Common
namespace Networking {
ConnectionManager::ConnectionManager() : _timerStarted(false), _frame(0) {}
ConnectionManager::~ConnectionManager() {
stopTimer();
// terminate all added requests which haven't been processed yet
_addedRequestsMutex.lock();
for (auto &curRequest : _addedRequests) {
Request *request = curRequest.request;
RequestCallback callback = curRequest.onDeleteCallback;
if (request)
request->finish();
delete request;
if (callback) {
(*callback)(request);
delete callback;
}
}
_addedRequests.clear();
_addedRequestsMutex.unlock();
// terminate all requests
_handleMutex.lock();
for (auto &curRequest : _requests) {
Request *request = curRequest.request;
RequestCallback callback = curRequest.onDeleteCallback;
if (request)
request->finish();
delete request;
if (callback) {
(*callback)(request);
delete callback;
}
}
_requests.clear();
_handleMutex.unlock();
}
Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) {
_addedRequestsMutex.lock();
_addedRequests.push_back(RequestWithCallback(request, callback));
if (!_timerStarted)
startTimer();
_addedRequestsMutex.unlock();
return request;
}
uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
return TIMER_INTERVAL * ITERATION_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, nullptr, "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 % ITERATION_PERIOD == 0)
iterateRequests();
if (_frame % PROCESSING_PERIOD == 0)
processTransfers();
if (_requests.empty() && !hasAddedRequests())
stopTimer();
_handleMutex.unlock();
}
void ConnectionManager::iterateRequests() {
// add new requests
_addedRequestsMutex.lock();
for (auto &addedRequest : _addedRequests) {
_requests.push_back(addedRequest);
}
_addedRequests.clear();
_addedRequestsMutex.unlock();
// call handle() of all running requests (so they can do their work)
if (_frame % DEBUG_PRINT_PERIOD == 0)
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)
delete i->onDeleteCallback;
}
_requests.erase(i);
continue;
}
++i;
}
}
} // End of namespace Networking

View File

@@ -0,0 +1,115 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_CONNECTIONMANAGER_H
#define BACKENDS_NETWORKING_HTTP_CONNECTIONMANAGER_H
#include "backends/networking/http/request.h"
#include "common/hashmap.h"
#include "common/mutex.h"
#include "common/singleton.h"
#include "common/str.h"
namespace Networking {
class ConnectionManager : public Common::Singleton<Networking::ConnectionManager> {
static const uint32 FRAMES_PER_SECOND = 100;
static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
static const uint32 ITERATION_PERIOD = 1; // every frame
static const uint32 PROCESSING_PERIOD = 1; // every frame
static const uint32 DEBUG_PRINT_PERIOD = FRAMES_PER_SECOND; // once per second
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) {}
};
bool _timerStarted;
Common::Array<RequestWithCallback> _requests, _addedRequests;
Common::Mutex _handleMutex, _addedRequestsMutex;
uint32 _frame;
void startTimer(int interval = TIMER_INTERVAL);
void stopTimer();
void handle();
void iterateRequests();
virtual void processTransfers() = 0;
bool hasAddedRequests();
public:
ConnectionManager();
~ConnectionManager();
/**
* 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);
static uint32 getCloudRequestsPeriodInMicroseconds();
};
/** Shortcut for accessing the connection manager. */
#define ConnMan Networking::ConnectionManager::instance()
} // End of namespace Networking
namespace Common {
template<>
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance();
} // End of namespace Common
#endif

View File

@@ -0,0 +1,92 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/curl/connectionmanager-curl.h"
#include "backends/networking/http/curl/networkreadstream-curl.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/timer.h"
namespace Common {
template<>
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
return new Networking::ConnectionManagerCurl();
}
} // namespace Common
namespace Networking {
/* Workaround a MSVC bug from MSVC 2015
* The compiler considers this template specialization as inline.
* If this TU doesn't use the function, it is then discarded.
*/
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
void dummyFunction() {
ConnMan;
}
#endif
ConnectionManagerCurl::ConnectionManagerCurl() : ConnectionManager(), _multi(nullptr) {
curl_global_init(CURL_GLOBAL_ALL);
_multi = curl_multi_init();
}
ConnectionManagerCurl::~ConnectionManagerCurl() {
// cleanup
curl_multi_cleanup(_multi);
curl_global_cleanup();
_multi = nullptr;
}
void ConnectionManagerCurl::registerEasyHandle(CURL *easy) const {
curl_multi_add_handle(_multi, easy);
}
// private goes here:
void ConnectionManagerCurl::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))) {
if (curlMsg->msg == CURLMSG_DONE) {
CURL *easyHandle = curlMsg->easy_handle;
NetworkReadStreamCurl *stream = nullptr;
curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
if (stream)
stream->finished(curlMsg->data.result);
curl_multi_remove_handle(_multi, easyHandle);
} else {
warning("Unknown libcurl message type %d", curlMsg->msg);
}
}
}
} // End of namespace Networking

View File

@@ -0,0 +1,54 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_CURL_CONNECTIONMANAGERCURL_H
#define BACKENDS_NETWORKING_HTTP_CURL_CONNECTIONMANAGERCURL_H
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/networking/http/connectionmanager.h"
#include <curl/curl.h>
namespace Networking {
class ConnectionManagerCurl : public ConnectionManager {
private:
CURLM *_multi;
void processTransfers() override;
public:
ConnectionManagerCurl();
~ConnectionManagerCurl() override;
/**
* 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;
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,394 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#define CURL_DISABLE_DEPRECATION
#include "backends/networking/basic/curl/cacert.h"
#include "backends/networking/http/curl/networkreadstream-curl.h"
#include "backends/networking/http/curl/connectionmanager-curl.h"
#include "base/version.h"
#include "common/debug.h"
namespace Networking {
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamCurl(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamCurl(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamCurl(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
}
size_t NetworkReadStreamCurl::curlDataCallback(char *d, size_t n, size_t l, void *p) {
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
if (stream)
return stream->_backingStream.write(d, n * l);
return 0;
}
size_t NetworkReadStreamCurl::curlReadDataCallback(char *d, size_t n, size_t l, void *p) {
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
if (stream)
return stream->fillWithSendingContents(d, n * l);
return 0;
}
size_t NetworkReadStreamCurl::curlHeadersCallback(char *d, size_t n, size_t l, void *p) {
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)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) {
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
if (stream)
stream->setProgress(dlnow, dltotal);
return 0;
}
int NetworkReadStreamCurl::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 NetworkReadStreamCurl::resetStream() {
_eos = _requestComplete = false;
if (!_errorBuffer)
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
_sendingContentsBuffer = nullptr;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
_bufferCopy = nullptr;
if (_headersSlist) {
curl_slist_free_all(_headersSlist);
_headersSlist = nullptr;
}
}
void NetworkReadStreamCurl::initCurl(const char *url, RequestHeaders *headersList) {
resetStream();
_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_ERRORBUFFER, _errorBuffer);
curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); // probably it's OK to have it always on
// Convert headers to curl_slist format
_headersSlist = requestHeadersToSlist(headersList);
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, _headersSlist);
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 defined NINTENDO_SWITCH || defined PSP2
curl_easy_setopt(_easy, CURLOPT_SSL_VERIFYPEER, 0);
#endif
Common::String caCertPath = getCaCertPath();
if (!caCertPath.empty()) {
curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath.c_str());
}
#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 LIBCURL_VERSION_NUM >= 0x071900
// Added in libcurl 7.25.0
if (_keepAlive) {
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPIDLE, _keepAliveIdle);
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPINTVL, _keepAliveInterval);
}
#endif
}
bool NetworkReadStreamCurl::reuseCurl(const char *url, RequestHeaders *headersList) {
if (!_keepAlive) {
warning("NetworkReadStream: Can't reuse curl handle (was not setup as keep-alive)");
return false;
}
resetStream();
_headersSlist = requestHeadersToSlist(headersList);
curl_easy_setopt(_easy, CURLOPT_URL, url);
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, _headersSlist);
curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); // in case headersList rewrites it
return true;
}
void NetworkReadStreamCurl::setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
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);
#if LIBCURL_VERSION_NUM >= 0x071101
// CURLOPT_COPYPOSTFIELDS available since curl 7.17.1
curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer);
#else
_bufferCopy = (byte *)malloc(bufferSize);
memcpy(_bufferCopy, buffer, bufferSize);
curl_easy_setopt(_easy, CURLOPT_POSTFIELDS, _bufferCopy);
#endif
}
}
dynamic_cast<ConnectionManagerCurl &>(ConnMan).registerEasyHandle(_easy);
}
void NetworkReadStreamCurl::setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
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("NetworkReadStreamCurl: field curl_formadd('%s') failed", i->_key.c_str());
}
for (Common::HashMap<Common::String, Common::Path>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) {
CURLFORMcode code = curl_formadd(
&formpost,
&lastptr,
CURLFORM_COPYNAME, i->_key.c_str(),
CURLFORM_FILE, i->_value.toString(Common::Path::kNativeSeparator).c_str(),
CURLFORM_END);
if (code != CURL_FORMADD_OK)
warning("NetworkReadStreamCurl: file curl_formadd('%s') failed", i->_key.c_str());
}
curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
dynamic_cast<ConnectionManagerCurl &>(ConnMan).registerEasyHandle(_easy);
}
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
_errorBuffer(nullptr), _headersSlist(nullptr) {
initCurl(url, headersList);
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
}
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
_errorBuffer(nullptr), _headersSlist(nullptr) {
initCurl(url, headersList);
setupFormMultipart(formFields, formFiles);
}
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
_errorBuffer(nullptr), _headersSlist(nullptr) {
initCurl(url, headersList);
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
}
curl_slist *NetworkReadStreamCurl::requestHeadersToSlist(const RequestHeaders *headersList) {
curl_slist *slist = nullptr;
if (headersList) {
for (const Common::String &header : *headersList) {
slist = curl_slist_append(slist, header.c_str());
}
}
return slist;
}
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
return true;
}
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupFormMultipart(formFields, formFiles);
return true;
}
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
return true;
}
NetworkReadStreamCurl::~NetworkReadStreamCurl() {
if (_easy)
curl_easy_cleanup(_easy);
free(_bufferCopy);
free(_errorBuffer);
if (_headersSlist) {
curl_slist_free_all(_headersSlist);
}
}
void NetworkReadStreamCurl::finished(CURLcode errorCode) {
_requestComplete = true;
char *url = nullptr;
curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &url);
_errorCode = errorCode;
if (_errorCode == CURLE_OK) {
debug(9, "NetworkReadStreamCurl: %s - Request succeeded", url);
} else {
warning("NetworkReadStreamCurl: %s - Request failed (%d - %s)", url, _errorCode, getError());
}
}
bool NetworkReadStreamCurl::hasError() const {
return _errorCode != CURLE_OK;
}
const char *NetworkReadStreamCurl::getError() const {
return strlen(_errorBuffer) ? _errorBuffer : curl_easy_strerror(_errorCode);
}
long NetworkReadStreamCurl::httpResponseCode() const {
long responseCode = -1;
if (_easy)
curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode);
return responseCode;
}
Common::String NetworkReadStreamCurl::currentLocation() const {
Common::String result = "";
if (_easy) {
char *pointer;
curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer);
result = Common::String(pointer);
}
return result;
}
Common::HashMap<Common::String, Common::String> NetworkReadStreamCurl::responseHeadersMap() const {
// HTTP headers are described at RFC 2616: https://datatracker.ietf.org/doc/html/rfc2616#section-4.2
// this implementation tries to follow it, but for simplicity it does not support multi-line header values
Common::HashMap<Common::String, Common::String> headers;
Common::String headerName, headerValue, trailingWhitespace;
char c;
bool readingName = true;
for (uint i = 0; i < _responseHeaders.size(); ++i) {
c = _responseHeaders[i];
if (readingName) {
if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// header names should not contain any whitespace, this is invalid
// ignore what's been before
headerName = "";
continue;
}
if (c == ':') {
if (!headerName.empty()) {
readingName = false;
}
continue;
}
headerName += c;
continue;
}
// reading value:
if (c == ' ' || c == '\t') {
if (headerValue.empty()) {
// skip leading whitespace
continue;
} else {
// accumulate trailing whitespace
trailingWhitespace += c;
continue;
}
}
if (c == '\r' || c == '\n') {
// not sure if RFC allows empty values, we'll ignore such
if (!headerName.empty() && !headerValue.empty()) {
// add header value
// RFC allows header with the same name to be sent multiple times
// and requires it to be equivalent of just listing all header values separated with comma
// so if header already was met, we'll add new value to the old one
headerName.toLowercase();
if (headers.contains(headerName)) {
headers[headerName] += "," + headerValue;
} else {
headers[headerName] = headerValue;
}
}
headerName = "";
headerValue = "";
trailingWhitespace = "";
readingName = true;
continue;
}
// if we meet non-whitespace character, turns out those "trailing" whitespace characters were not so trailing
headerValue += trailingWhitespace;
trailingWhitespace = "";
headerValue += c;
}
return headers;
}
} // End of namespace Networking

View File

@@ -0,0 +1,97 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_CURL_NETWORKREADSTREAMCURL_H
#define BACKENDS_NETWORKING_HTTP_CURL_NETWORKREADSTREAMCURL_H
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/networking/http/networkreadstream.h"
#include "common/hash-str.h"
#include "common/hashmap.h"
#include "common/memstream.h"
#include "common/path.h"
#include "common/str.h"
#include "common/stream.h"
#include <curl/curl.h>
namespace Networking {
class NetworkReadStreamCurl : public NetworkReadStream {
private:
CURL *_easy;
struct curl_slist *_headersSlist;
char *_errorBuffer;
CURLcode _errorCode;
byte *_bufferCopy; // To use with old curl version where CURLOPT_COPYPOSTFIELDS is not available
void initCurl(const char *url, RequestHeaders *headersList);
bool reuseCurl(const char *url, RequestHeaders *headersList);
static struct curl_slist *requestHeadersToSlist(const RequestHeaders *headersList);
static size_t curlDataCallback(char *d, size_t n, size_t l, void *p);
static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p);
static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p);
static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow);
// CURL-specific methods
CURL *getEasyHandle() const { return _easy; }
void resetStream();
void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
public:
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
~NetworkReadStreamCurl();
void finished(CURLcode errorCode);
/** Send <postFields>, using POST by default. */
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override;
/** Send <formFields>, <formFiles>, using POST multipart/form. */
bool reuse(
const char *url, RequestHeaders *headersList,
const Common::HashMap<Common::String, Common::String> &formFields,
const Common::HashMap<Common::String, Common::Path> &formFiles) override;
/** Send <buffer>, using POST by default. */
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
long httpResponseCode() const override;
Common::String currentLocation() const override;
/**
* Return response headers as HashMap. All header names in
* it are lowercase.
*
* @note This method should be called when eos() == true.
*/
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override;
bool hasError() const override;
const char *getError() const override;
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,58 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef EMSCRIPTEN
#define FORBIDDEN_SYMBOL_EXCEPTION_asctime
#define FORBIDDEN_SYMBOL_EXCEPTION_clock
#define FORBIDDEN_SYMBOL_EXCEPTION_ctime
#define FORBIDDEN_SYMBOL_EXCEPTION_difftime
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#define FORBIDDEN_SYMBOL_EXCEPTION_getdate
#define FORBIDDEN_SYMBOL_EXCEPTION_gmtime
#define FORBIDDEN_SYMBOL_EXCEPTION_localtime
#define FORBIDDEN_SYMBOL_EXCEPTION_mktime
#define FORBIDDEN_SYMBOL_EXCEPTION_strcpy
#define FORBIDDEN_SYMBOL_EXCEPTION_strdup
#define FORBIDDEN_SYMBOL_EXCEPTION_time
#include "backends/networking/http/emscripten/connectionmanager-emscripten.h"
#include "common/debug.h"
#include <emscripten.h>
namespace Common {
template<>
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
return new Networking::ConnectionManagerEmscripten();
}
} // namespace Common
namespace Networking {
void ConnectionManagerEmscripten::processTransfers() {
// Emscripten handles transfers asynchronously via callbacks
// No action needed here
}
} // End of namespace Networking
#endif // EMSCRIPTEN

View File

@@ -0,0 +1,41 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_CONNECTIONMANAGEREMSCRIPTEN_H
#define BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_CONNECTIONMANAGEREMSCRIPTEN_H
#ifdef EMSCRIPTEN
#include "backends/networking/http/connectionmanager.h"
namespace Networking {
class ConnectionManagerEmscripten : public ConnectionManager {
public:
void processTransfers() override;
};
} // End of namespace Networking
#endif // EMSCRIPTEN
#endif

View File

@@ -0,0 +1,307 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_asctime
#define FORBIDDEN_SYMBOL_EXCEPTION_clock
#define FORBIDDEN_SYMBOL_EXCEPTION_ctime
#define FORBIDDEN_SYMBOL_EXCEPTION_difftime
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#define FORBIDDEN_SYMBOL_EXCEPTION_getdate
#define FORBIDDEN_SYMBOL_EXCEPTION_gmtime
#define FORBIDDEN_SYMBOL_EXCEPTION_localtime
#define FORBIDDEN_SYMBOL_EXCEPTION_mktime
#define FORBIDDEN_SYMBOL_EXCEPTION_strcpy
#define FORBIDDEN_SYMBOL_EXCEPTION_strdup
#define FORBIDDEN_SYMBOL_EXCEPTION_time
#include <emscripten.h>
#include <emscripten/fetch.h>
#include "backends/networking/http/emscripten/networkreadstream-emscripten.h"
#include "backends/networking/http/networkreadstream.h"
#include "base/version.h"
#include "common/debug.h"
namespace Networking {
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamEmscripten(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamEmscripten(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
}
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
return new NetworkReadStreamEmscripten(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
}
void NetworkReadStreamEmscripten::emscriptenOnReadyStateChange(emscripten_fetch_t *fetch) {
if (fetch->readyState != 2)
return;
size_t headersLengthBytes = emscripten_fetch_get_response_headers_length(fetch) + 1;
char *headerString = (char *)malloc(headersLengthBytes);
assert(headerString);
emscripten_fetch_get_response_headers(fetch, headerString, headersLengthBytes);
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
stream->addResponseHeaders(headerString, headersLengthBytes);
free(headerString);
}
void NetworkReadStreamEmscripten::emscriptenOnProgress(emscripten_fetch_t *fetch) {
/*
if (fetch->totalBytes) {
debug(5,"Downloading %s.. %.2f percent complete.", fetch->url, fetch->dataOffset * 100.0 / fetch->totalBytes);
} else {
debug(5,"Downloading %s.. %lld bytes complete.", fetch->url, fetch->dataOffset + fetch->numBytes);
}
debug(5,"Downloading %s.. %.2f %s complete. HTTP readyState: %hu. HTTP status: %hu - "
"HTTP statusText: %s. Received chunk [%llu, %llu]",
fetch->url,
fetch->totalBytes > 0 ? (fetch->dataOffset + fetch->numBytes) * 100.0 / fetch->totalBytes : (fetch->dataOffset + fetch->numBytes),
fetch->totalBytes > 0 ? "percent" : " bytes",
fetch->readyState,
fetch->status,
fetch->statusText,
fetch->dataOffset,
fetch->dataOffset + fetch->numBytes);
*/
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
if (stream) {
stream->setProgress(fetch->dataOffset, fetch->totalBytes);
}
}
void NetworkReadStreamEmscripten::emscriptenOnSuccess(emscripten_fetch_t *fetch) {
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
stream->emscriptenDownloadFinished(true);
}
void NetworkReadStreamEmscripten::emscriptenOnError(emscripten_fetch_t *fetch) {
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
stream->emscriptenDownloadFinished(false);
}
void NetworkReadStreamEmscripten::emscriptenDownloadFinished(bool success) {
_requestComplete = true;
if (_emscripten_fetch->numBytes > 0) {
// TODO: This could be done continuously during emscriptenOnProgress?
this->_backingStream.write(_emscripten_fetch->data, _emscripten_fetch->numBytes);
}
this->setProgress(_emscripten_fetch->numBytes, _emscripten_fetch->numBytes);
if (success) {
debug(5, "NetworkReadStreamEmscripten::emscriptenHandleDownload Finished downloading %llu bytes from URL %s. HTTP status code: %d", _emscripten_fetch->numBytes, _emscripten_fetch->url, _emscripten_fetch->status);
_success = true; // TODO: actually pass the result code from emscripten_fetch
} else {
debug(5, "NetworkReadStreamEmscripten::emscriptenHandleDownload Downloading %s failed, HTTP failure status code: %d, status text: %s", _emscripten_fetch->url, _emscripten_fetch->status, _emscripten_fetch->statusText);
// Make a copy of the error message since _emscripten_fetch might be cleaned up
if (_emscripten_fetch && _emscripten_fetch->statusText) {
_errorBuffer = strdup(_emscripten_fetch->statusText);
} else {
_errorBuffer = strdup("Unknown error");
}
warning("NetworkReadStreamEmscripten::finished %s - Request failed (%s)", _emscripten_fetch_url, getError());
}
}
void NetworkReadStreamEmscripten::resetStream() {
_eos = _requestComplete = false;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
_emscripten_fetch = nullptr;
_emscripten_request_headers = nullptr;
free(_errorBuffer);
_errorBuffer = nullptr;
}
void NetworkReadStreamEmscripten::initEmscripten(const char *url, RequestHeaders *headersList) {
resetStream();
emscripten_fetch_attr_init(_emscripten_fetch_attr);
// convert header list
// first get size of list
int size = 0;
if (headersList) {
size = headersList->size();
debug(5, "_emscripten_request_headers count: %d", size);
}
_emscripten_request_headers = new char *[size * 2 + 1];
_emscripten_request_headers[size * 2] = 0; // header array needs to be null-terminated.
int i = 0;
if (headersList) {
for (const Common::String &header : *headersList) {
// Find the colon separator
uint colonPos = header.findFirstOf(':');
if (colonPos == Common::String::npos) {
warning("NetworkReadStreamEmscripten: Malformed header (no colon): %s", header.c_str());
continue;
}
// Split into key and value parts
Common::String key = header.substr(0, colonPos);
Common::String value = header.substr(colonPos + 1);
// Trim whitespace from key and value
key.trim();
value.trim();
// Store key and value as separate strings
_emscripten_request_headers[i++] = strdup(key.c_str());
_emscripten_request_headers[i++] = strdup(value.c_str());
debug(9, "_emscripten_request_headers key='%s' value='%s'", key.c_str(), value.c_str());
}
}
_emscripten_fetch_attr->requestHeaders = _emscripten_request_headers;
strcpy(_emscripten_fetch_attr->requestMethod, "GET"); // todo: move down to setup buffer contents
_emscripten_fetch_attr->attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
_emscripten_fetch_attr->onerror = emscriptenOnError;
_emscripten_fetch_attr->onprogress = emscriptenOnProgress;
_emscripten_fetch_attr->onreadystatechange = emscriptenOnReadyStateChange;
_emscripten_fetch_attr->onsuccess = emscriptenOnSuccess;
_emscripten_fetch_attr->userData = this;
}
void NetworkReadStreamEmscripten::setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
if (uploading) {
strcpy(_emscripten_fetch_attr->requestMethod, "PUT");
_emscripten_fetch_attr->requestDataSize = bufferSize;
_emscripten_fetch_attr->requestData = (const char *)buffer;
} else if (usingPatch) {
strcpy(_emscripten_fetch_attr->requestMethod, "PATCH");
} else {
if (post || bufferSize != 0) {
strcpy(_emscripten_fetch_attr->requestMethod, "POST");
_emscripten_fetch_attr->requestDataSize = bufferSize;
_emscripten_fetch_attr->requestData = (const char *)buffer;
}
}
debug(5, "NetworkReadStreamEmscripten::setupBufferContents uploading %s usingPatch %s post %s ->method %s", uploading ? "true" : "false", usingPatch ? "true" : "false", post ? "true" : "false", _emscripten_fetch_attr->requestMethod);
_emscripten_fetch = emscripten_fetch(_emscripten_fetch_attr, _emscripten_fetch_url);
}
void NetworkReadStreamEmscripten::setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
// set POST multipart upload form fields/files
error("NetworkReadStreamEmscripten::setupFormMultipart not implemented");
}
/** Send <postFields>, using POST by default. */
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::String &postFields,
bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
_emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
initEmscripten(url, headersList);
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
}
/** Send <formFields>, <formFiles>, using POST multipart/form. */
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String,
Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle,
long keepAliveInterval) : _emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
initEmscripten(url, headersList);
setupFormMultipart(formFields, formFiles);
}
/** Send <buffer>, using POST by default. */
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
_emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
initEmscripten(url, headersList);
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
}
NetworkReadStreamEmscripten::~NetworkReadStreamEmscripten() {
if (_emscripten_fetch) {
debug(5, "~NetworkReadStreamEmscripten: emscripten_fetch_close");
emscripten_fetch_close(_emscripten_fetch);
}
// Free the headers array and its contents
if (_emscripten_request_headers) {
for (int i = 0; _emscripten_request_headers[i] != nullptr; ++i) {
free(_emscripten_request_headers[i]); // Free each strdup'd string
}
delete[] _emscripten_request_headers;
}
}
uint32 NetworkReadStreamEmscripten::read(void *dataPtr, uint32 dataSize) {
uint32 actuallyRead = _backingStream.read(dataPtr, dataSize);
// Only access _emscripten_fetch->url if _emscripten_fetch is valid
// debug(5,"NetworkReadStreamEmscripten::read %u %s %s %s", actuallyRead, _eos ? "_eos" : "not _eos", _requestComplete ? "_requestComplete" : "_request not Complete", _emscripten_fetch ? _emscripten_fetch->url : "no-url");
if (actuallyRead == 0) {
if (_requestComplete)
_eos = true;
return 0;
}
return actuallyRead;
}
bool NetworkReadStreamEmscripten::hasError() const {
return !_success;
}
const char *NetworkReadStreamEmscripten::getError() const {
return _errorBuffer;
}
long NetworkReadStreamEmscripten::httpResponseCode() const {
// return 200;
unsigned short responseCode = 0;
if (_emscripten_fetch)
responseCode = _emscripten_fetch->status;
debug(5, "NetworkReadStreamEmscripten::httpResponseCode %hu", responseCode);
return responseCode;
}
Common::String NetworkReadStreamEmscripten::currentLocation() const {
debug(5, "NetworkReadStreamEmscripten::currentLocation %s", _emscripten_fetch_url);
return Common::String(_emscripten_fetch_url);
}
Common::HashMap<Common::String, Common::String> NetworkReadStreamEmscripten::responseHeadersMap() const {
Common::HashMap<Common::String, Common::String> headers;
const char *headerString = _responseHeaders.c_str();
char **responseHeaders = emscripten_fetch_unpack_response_headers(headerString);
assert(responseHeaders);
int numHeaders = 0;
for (; responseHeaders[numHeaders * 2]; ++numHeaders) {
// Check both the header and its value are present.
assert(responseHeaders[(numHeaders * 2) + 1]);
headers[responseHeaders[numHeaders * 2]] = responseHeaders[(numHeaders * 2) + 1];
}
emscripten_fetch_free_unpacked_response_headers(responseHeaders);
return headers;
}
} // namespace Networking

View File

@@ -0,0 +1,82 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_NETWORKREADSTREAMEMSCRIPTEN_H
#define BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_NETWORKREADSTREAMEMSCRIPTEN_H
#ifdef EMSCRIPTEN
#include "backends/networking/http/networkreadstream.h"
#include <emscripten/fetch.h>
namespace Networking {
class NetworkReadStream; // Forward declaration
class NetworkReadStreamEmscripten : public NetworkReadStream {
private:
emscripten_fetch_attr_t *_emscripten_fetch_attr;
emscripten_fetch_t *_emscripten_fetch;
const char *_emscripten_fetch_url = nullptr;
char **_emscripten_request_headers;
bool _success;
char *_errorBuffer;
void resetStream();
void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
public:
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
~NetworkReadStreamEmscripten() override;
void initEmscripten(const char *url, RequestHeaders *headersList);
// NetworkReadStream interface
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override { return false; } // no reuse for Emscripten
bool reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) override { return false; } // no reuse for Emscripten
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = false) override { return false; } // no reuse for Emscripten
bool hasError() const override;
const char *getError() const override;
long httpResponseCode() const override;
Common::String currentLocation() const override;
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override;
uint32 read(void *dataPtr, uint32 dataSize) override;
// Static callback functions
static void emscriptenOnSuccess(emscripten_fetch_t *fetch);
static void emscriptenOnError(emscripten_fetch_t *fetch);
static void emscriptenOnProgress(emscripten_fetch_t *fetch);
static void emscriptenOnReadyStateChange(emscripten_fetch_t *fetch);
void emscriptenDownloadFinished(bool success);
};
} // End of namespace Networking
#endif // EMSCRIPTEN
#endif

View File

@@ -0,0 +1,190 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Networking {
HttpJsonRequest::HttpJsonRequest(JsonCallback cb, ErrorCallback ecb, const Common::String &url) :
HttpRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES),
_buffer(new byte[HTTP_JSON_REQUEST_BUFFER_SIZE]) {}
HttpJsonRequest::~HttpJsonRequest() {
delete _jsonCallback;
delete[] _buffer;
}
void HttpJsonRequest::handle() {
if (!_stream) _stream = makeStream();
if (_stream) {
uint32 readBytes = _stream->read(_buffer, HTTP_JSON_REQUEST_BUFFER_SIZE);
if (readBytes != 0)
if (_contentsStream.write(_buffer, readBytes) != readBytes)
warning("HttpJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
if (_stream->eos()) {
char *contents = Common::JSON::zeroTerminateContents(_contentsStream);
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 HttpJsonRequest::restart() {
if (_stream)
delete _stream;
_stream = nullptr;
_contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
//with no stream available next handle() will create another one
}
void HttpJsonRequest::finishJson(const Common::JSONValue *json) {
Request::finishSuccess();
if (_jsonCallback)
(*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks!
else
delete json;
}
bool HttpJsonRequest::jsonIsObject(const 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 HttpJsonRequest::jsonContainsObject(const 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 HttpJsonRequest::jsonContainsString(const 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 HttpJsonRequest::jsonContainsIntegerNumber(const 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 HttpJsonRequest::jsonContainsArray(const 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 HttpJsonRequest::jsonContainsStringOrIntegerNumber(const 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 HttpJsonRequest::jsonContainsAttribute(const 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

View File

@@ -0,0 +1,64 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_HTTPJSONREQUEST_H
#define BACKENDS_NETWORKING_HTTP_HTTPJSONREQUEST_H
#include "backends/networking/http/httprequest.h"
#include "common/memstream.h"
#include "common/formats/json.h"
namespace Networking {
typedef Response<const Common::JSONValue *> JsonResponse;
typedef Common::BaseCallback<const JsonResponse &> *JsonCallback;
typedef Common::BaseCallback<const Common::JSONValue *> *JSONValueCallback;
#define HTTP_JSON_REQUEST_BUFFER_SIZE 512 * 1024
class HttpJsonRequest: public HttpRequest {
protected:
JsonCallback _jsonCallback;
Common::MemoryWriteStreamDynamic _contentsStream;
byte *_buffer;
/** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */
virtual void finishJson(const Common::JSONValue *json);
public:
HttpJsonRequest(JsonCallback cb, ErrorCallback ecb, const Common::String &url);
~HttpJsonRequest() override;
void handle() override;
void restart() override;
static bool jsonIsObject(const Common::JSONValue *item, const char *warningPrefix);
static bool jsonContainsObject(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
static bool jsonContainsString(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
static bool jsonContainsIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
static bool jsonContainsArray(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
static bool jsonContainsStringOrIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
static bool jsonContainsAttribute(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,160 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/httprequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/textconsole.h"
namespace Networking {
HttpRequest::HttpRequest(DataCallback cb, ErrorCallback ecb, const Common::String &url):
Request(cb, ecb), _url(url), _stream(nullptr), _bytesBuffer(nullptr),
_bytesBufferSize(0), _uploading(false), _usingPatch(false), _keepAlive(false), _keepAliveIdle(120), _keepAliveInterval(60) {}
HttpRequest::~HttpRequest() {
delete _stream;
delete[] _bytesBuffer;
}
NetworkReadStream *HttpRequest::makeStream() {
if (_bytesBuffer)
return NetworkReadStream::make(_url.c_str(), &_headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true, _keepAlive, _keepAliveIdle, _keepAliveInterval);
if (!_formFields.empty() || !_formFiles.empty())
return NetworkReadStream::make(_url.c_str(), &_headersList, _formFields, _formFiles, _keepAlive, _keepAliveIdle, _keepAliveInterval);
return NetworkReadStream::make(_url.c_str(), &_headersList, _postFields, _uploading, _usingPatch, _keepAlive, _keepAliveIdle, _keepAliveInterval);
}
void HttpRequest::handle() {
if (!_stream) _stream = makeStream();
if (_stream && _stream->eos()) {
if (_stream->httpResponseCode() != 200) {
warning("HttpRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
ErrorResponse error(this, false, true, "HTTP response code is not 200 OK", _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 HttpRequest::restart() {
if (_stream)
delete _stream;
_stream = nullptr;
//with no stream available next handle() will create another one
}
Common::String HttpRequest::date() const {
if (_stream) {
Common::HashMap<Common::String, Common::String> headers = _stream->responseHeadersMap();
if (headers.contains("date"))
return headers["date"];
}
return "";
}
void HttpRequest::setHeaders(const Common::Array<Common::String> &headers) {
_headersList = headers;
}
void HttpRequest::addHeader(const Common::String &header) {
_headersList.push_back(header);
}
void HttpRequest::addPostField(const Common::String &keyValuePair) {
if (_bytesBuffer)
warning("HttpRequest: added POST fields would be ignored, because there is buffer present");
if (!_formFields.empty() || !_formFiles.empty())
warning("HttpRequest: added POST fields would be ignored, because there are form fields/files present");
if (_postFields == "")
_postFields = keyValuePair;
else
_postFields += "&" + keyValuePair;
}
void HttpRequest::addFormField(const Common::String &name, const Common::String &value) {
if (_bytesBuffer)
warning("HttpRequest: added POST form fields would be ignored, because there is buffer present");
if (_formFields.contains(name))
warning("HttpRequest: form field '%s' already had a value", name.c_str());
_formFields[name] = value;
}
void HttpRequest::addFormFile(const Common::String &name, const Common::Path &filename) {
if (_bytesBuffer)
warning("HttpRequest: added POST form files would be ignored, because there is buffer present");
if (_formFields.contains(name))
warning("HttpRequest: form file field '%s' already had a value", name.c_str());
_formFiles[name] = filename;
}
void HttpRequest::setBuffer(byte *buffer, uint32 size) {
if (_postFields != "")
warning("HttpRequest: added POST fields would be ignored, because buffer added");
if (_bytesBuffer)
delete[] _bytesBuffer;
_bytesBuffer = buffer;
_bytesBufferSize = size;
}
void HttpRequest::usePut() { _uploading = true; }
void HttpRequest::usePatch() { _usingPatch = true; }
void HttpRequest::connectionKeepAlive(long idle, long interval) {
_keepAlive = true;
_keepAliveIdle = idle;
_keepAliveInterval = interval;
}
void HttpRequest::connectionClose() {
_keepAlive = false;
}
NetworkReadStreamResponse HttpRequest::execute() {
if (!_stream) {
_stream = makeStream();
ConnMan.addRequest(this);
}
return NetworkReadStreamResponse(this, _stream);
}
const NetworkReadStream *HttpRequest::getNetworkReadStream() const { return _stream; }
void HttpRequest::wait(int spinlockDelay) {
while (state() == Networking::PROCESSING) {
g_system->delayMillis(spinlockDelay);
}
}
} // End of namespace Networking

View File

@@ -0,0 +1,110 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_HTTPREQUEST_H
#define BACKENDS_NETWORKING_HTTP_HTTPREQUEST_H
#include "backends/networking/http/request.h"
#include "common/path.h"
#include "common/str.h"
#include "common/array.h"
#include "common/list.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
namespace Networking {
typedef Common::Array<Common::String> RequestHeaders;
class NetworkReadStream;
typedef Response<NetworkReadStream *> NetworkReadStreamResponse;
typedef Common::BaseCallback<const NetworkReadStreamResponse &> *NetworkReadStreamCallback;
class HttpRequest: public Request {
protected:
Common::String _url;
NetworkReadStream *_stream;
RequestHeaders _headersList;
Common::String _postFields;
Common::HashMap<Common::String, Common::String> _formFields;
Common::HashMap<Common::String, Common::Path> _formFiles;
byte *_bytesBuffer;
uint32 _bytesBufferSize;
bool _uploading; //using PUT method
bool _usingPatch; //using PATCH method
bool _keepAlive;
long _keepAliveIdle, _keepAliveInterval;
NetworkReadStream *makeStream();
public:
HttpRequest(DataCallback cb, ErrorCallback ecb, const Common::String &url);
~HttpRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
/** Replaces all headers with the passed array of headers. */
virtual void setHeaders(const Common::Array<Common::String> &headers);
/** Adds a header into headers list. */
virtual void addHeader(const Common::String &header);
/** Adds a post field (key=value pair). */
virtual void addPostField(const Common::String &field);
/** Adds a form/multipart field (name, value). */
virtual void addFormField(const Common::String &name, const Common::String &value);
/** Adds a form/multipart file (field name, file name). */
virtual void addFormFile(const Common::String &name, const Common::Path &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();
/** Remembers to use Connection: keep-alive or close. */
virtual void connectionKeepAlive(long idle = 120, long interval = 60);
virtual void connectionClose();
/**
* Starts this Request with ConnMan.
* @return its NetworkReadStream in NetworkReadStreamResponse.
*/
virtual NetworkReadStreamResponse execute();
/** Returns Request's NetworkReadStream. */
const NetworkReadStream *getNetworkReadStream() const;
/** Waits for Request to be processed. Should be called after Request is put into ConnMan. */
void wait(int spinlockDelay = 5);
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,70 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "networkreadstream.h"
#include "common/tokenizer.h"
namespace Networking {
/*
* The make static functions are defined in the implementation-specific subclass
*/
uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
uint32 sendSize = _sendingContentsSize - _sendingContentsPos;
if (sendSize > maxSize)
sendSize = maxSize;
for (uint32 i = 0; i < sendSize; ++i) {
bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i];
}
_sendingContentsPos += sendSize;
return sendSize;
}
uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 bufferSize) {
_responseHeaders += Common::String(buffer, bufferSize);
return bufferSize;
}
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;
}
uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
uint32 actuallyRead = _backingStream.read(dataPtr, dataSize);
if (actuallyRead == 0) {
if (_requestComplete)
_eos = true;
return 0;
}
return actuallyRead;
}
} // End of namespace Networking

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_NETWORKREADSTREAM_H
#define BACKENDS_NETWORKING_HTTP_NETWORKREADSTREAM_H
#include "common/array.h"
#include "common/hash-str.h"
#include "common/hashmap.h"
#include "common/memstream.h"
#include "common/path.h"
#include "common/str.h"
#include "common/stream.h"
namespace Networking {
typedef Common::Array<Common::String> RequestHeaders;
// Simple interface for platform-specific NetworkReadStream implementations
class NetworkReadStream : public Common::ReadStream {
protected:
Common::MemoryReadWriteStream _backingStream;
bool _keepAlive;
long _keepAliveIdle, _keepAliveInterval;
bool _eos, _requestComplete;
const byte *_sendingContentsBuffer;
uint32 _sendingContentsSize;
uint32 _sendingContentsPos;
Common::String _responseHeaders;
uint64 _progressDownloaded, _progressTotal;
/**
* 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 bufferSize);
NetworkReadStream(bool keepAlive, long keepAliveIdle, long keepAliveInterval)
: _backingStream(DisposeAfterUse::YES), _eos(false), _requestComplete(false), _sendingContentsBuffer(nullptr),
_sendingContentsSize(0), _sendingContentsPos(0), _progressDownloaded(0), _progressTotal(0),
_keepAlive(keepAlive), _keepAliveIdle(keepAliveIdle), _keepAliveInterval(keepAliveInterval) {
}
public:
/* Implementation-defined Constructors */
/** Send <postFields>, using POST by default. */
static NetworkReadStream *make(const char *url, RequestHeaders *headersList, const Common::String &postFields,
bool uploading = false, bool usingPatch = false,
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
/** Send <formFields>, <formFiles>, using POST multipart/form. */
static NetworkReadStream *make(const char *url, RequestHeaders *headersList,
const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles,
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
/** Send <buffer>, using POST by default. */
static NetworkReadStream *make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
bool uploading = false, bool usingPatch = false, bool post = true,
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
/** Send <postFields>, using POST by default. */
virtual bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) = 0;
/** Send <formFields>, <formFiles>, using POST multipart/form. */
virtual bool reuse(
const char *url, RequestHeaders *headersList,
const Common::HashMap<Common::String, Common::String> &formFields,
const Common::HashMap<Common::String, Common::Path> &formFiles) = 0;
/** Send <buffer>, using POST by default. */
virtual bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) = 0;
/**
* 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.
*/
bool eos() const override { return _eos; }
/**
* 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.
*/
uint32 read(void *dataPtr, uint32 dataSize) override;
/**
* 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.
*/
virtual long httpResponseCode() const = 0;
/**
* 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.
*/
virtual Common::String currentLocation() const = 0;
/**
* Return response headers as HashMap. All header names in
* it are lowercase.
*
* @note This method should be called when eos() == true.
*/
virtual Common::HashMap<Common::String, Common::String> responseHeadersMap() const = 0;
/** 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);
bool keepAlive() const { return _keepAlive; }
virtual bool hasError() const = 0;
virtual const char *getError() const = 0;
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,128 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/postrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Networking {
PostRequest::PostRequest(const Common::String &url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _url(url), _jsonCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false), _postData(nullptr), _postLen(0), _jsonData(nullptr) {
_contentType = "application/octet-stream";
}
PostRequest::~PostRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _jsonCallback;
}
void PostRequest::setPostData(byte *postData, int postLen) {
_postData = postData;
_postLen = postLen;
_contentType = "application/octet-stream";
}
void PostRequest::setJSONData(Common::JSONValue *jsonData) {
_jsonData = jsonData;
_contentType = "application/json";
}
void PostRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_ignoreCallback = false;
Networking::JsonCallback innerCallback = new Common::Callback<PostRequest, const Networking::JsonResponse &>(this, &PostRequest::responseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<PostRequest, const Networking::ErrorResponse &>(this, &PostRequest::errorCallback);
Networking::HttpJsonRequest *request = new Networking::HttpJsonRequest(innerCallback, errorResponseCallback, _url);
if (_postData && _jsonData) {
warning("Error, both data and JSON present while calling %s", _url.c_str());
_jsonData = nullptr;
}
request->addHeader(Common::String::format("Content-Type: %s", _contentType.c_str()));
if (_postData)
request->setBuffer(_postData, _postLen);
if (_jsonData)
request->addPostField(Common::JSON::stringify(_jsonData));
_workingRequest = ConnMan.addRequest(request);
}
void PostRequest::responseCallback(const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
if (response.request) _date = response.request->date();
Networking::ErrorResponse error(this, "PostRequest::responseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
finishSuccess();
if (_jsonCallback)
(*_jsonCallback)(json);
delete json;
}
void PostRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void PostRequest::handle() {}
void PostRequest::restart() { start(); }
Common::String PostRequest::date() const { return _date; }
} // End of namespace Networking

View File

@@ -0,0 +1,63 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_POSTREQUEST_H
#define BACKENDS_NETWORKING_HTTP_POSTREQUEST_H
#include "backends/networking/http/request.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Networking {
class PostRequest : public Networking::Request {
Common::String _url;
Networking::JSONValueCallback _jsonCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
byte *_postData;
int _postLen;
Common::JSONValue *_jsonData;
Common::String _contentType;
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
public:
PostRequest(const Common::String &url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb);
~PostRequest() override;
void start();
void setPostData(byte *postData, int postLen);
void setJSONData(Common::JSONValue *jsonData);
void setContentType(const Common::String &type) { _contentType = type; }
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,73 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/request.h"
namespace Networking {
ErrorResponse::ErrorResponse(Request *rq, const Common::String &resp):
request(rq), interrupted(false), failed(true), response(resp), httpResponseCode(-1) {}
ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, const 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, "Request::finish() was called (i.e. interrupted)", -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(const ErrorResponse &error, RequestState state) {
_state = state;
if (_errorCallback)
(*_errorCallback)(error);
}
void Request::finishSuccess() { _state = FINISHED; }
} // End of namespace Networking

View File

@@ -0,0 +1,202 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_REQUEST_H
#define BACKENDS_NETWORKING_HTTP_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 {
const Request *request;
T value;
Response(const 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, const Common::String &resp);
ErrorResponse(Request *rq, bool interrupt, bool failure, const Common::String &resp, long httpCode);
};
typedef Response<void *> DataResponse;
typedef Common::BaseCallback<const DataResponse &> *DataCallback;
typedef Common::BaseCallback<const 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(const ErrorResponse &error, RequestState state = FINISHED);
/** 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

View File

@@ -0,0 +1,88 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/session.h"
namespace Networking {
Session::Session(const Common::String &prefix):
_prefix(prefix), _request(nullptr) {}
Session::~Session() {
close();
}
static Common::String constructUrl(const Common::String &prefix, const Common::String &url) {
// check url prefix
if (!prefix.empty()) {
if (url.contains("://")) {
if (!url.hasPrefix(prefix)) {
warning("Session: given URL does not match the prefix!\n\t%s\n\t%s", url.c_str(), prefix.c_str());
return Common::String();
}
} else {
// if no schema given, just append <url> to <_prefix>
Common::String newUrl = prefix;
if (newUrl.lastChar() != '/' && (url.size() > 0 && url.firstChar() != '/'))
newUrl += "/";
newUrl += url;
return newUrl;
}
}
return url;
}
SessionRequest *Session::get(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary) {
Common::String builtUrl = constructUrl(_prefix, url);
if (builtUrl.empty())
return nullptr;
// check if request has finished (ready to be replaced)
if (_request) {
if (!_request->complete()) {
warning("Session: can't reuse Request that is being processed");
return nullptr;
}
}
if (!_request) {
_request = new SessionRequest(builtUrl, localFile, cb, ecb, binary); // automatically added to ConnMan
_request->connectionKeepAlive();
} else {
_request->reuse(builtUrl, localFile, cb, ecb, binary);
}
return _request;
}
void Session::close() {
if (_request)
_request->close();
}
void Session::abortRequest() {
if (_request)
_request->abortRequest();
}
} // End of namespace Networking

View File

@@ -0,0 +1,54 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_SESSION_H
#define BACKENDS_NETWORKING_HTTP_SESSION_H
#include "backends/networking/http/sessionrequest.h"
namespace Networking {
class Session {
protected:
Common::String _prefix;
SessionRequest *_request;
public:
Session(const Common::String &prefix = Common::String());
~Session();
SessionRequest *get(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
/**
* @brief Gracefully close the session
*
*/
void close();
/**
* @brief Abort session and remove unfinished downloads if they go to local file
*
*/
void abortRequest();
};
} // End of namespace Networking
#endif

View File

@@ -0,0 +1,260 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/networkreadstream.h"
#include "backends/networking/http/sessionrequest.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/formats/json.h"
namespace Networking {
SessionRequest::SessionRequest(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary):
HttpRequest(cb, ecb, url), _contentsStream(DisposeAfterUse::YES),
_buffer(new byte[HTTP_SESSION_REQUEST_BUFFER_SIZE]), _text(nullptr), _localFile(nullptr),
_started(false), _complete(false), _success(false), _binary(binary) {
openLocalFile(localFile);
// automatically go under ConnMan control so nobody would be able to leak the memory
// but, we don't need it to be working just yet
_state = PAUSED;
ConnMan.addRequest(this);
}
SessionRequest::~SessionRequest() {
delete[] _buffer;
}
void SessionRequest::openLocalFile(const Common::Path &localFile) {
if (localFile.empty())
return;
_localFile = new Common::DumpFile();
if (!_localFile->open(localFile, true)) {
warning("SessionRequestFile: unable to open file to download into");
ErrorResponse error(this, false, true, "SessionRequestFile: unable to open file to download into", -1);
finishError(error);
delete _localFile;
_localFile = nullptr;
return;
}
debug(5, "SessionRequest: opened localfile %s", localFile.toString(Common::Path::kNativeSeparator).c_str());
_binary = true; // Enforce binary
}
bool SessionRequest::reuseStream() {
if (!_stream) {
return false;
}
if (_bytesBuffer)
return _stream->reuse(_url.c_str(), &_headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
if (!_formFields.empty() || !_formFiles.empty())
return _stream->reuse(_url.c_str(), &_headersList, _formFields, _formFiles);
return _stream->reuse(_url.c_str(), &_headersList, _postFields, _uploading, _usingPatch);
}
char *SessionRequest::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();
//make it zero-terminated string
result[size - 1] = '\0';
return (char *)result;
}
void SessionRequest::finishError(const ErrorResponse &error, RequestState state) {
_complete = true;
_success = false;
HttpRequest::finishError(error, PAUSED);
}
void SessionRequest::finishSuccess() {
_state = PAUSED;
_complete = true;
_success = true;
if (_localFile) {
_localFile->close();
delete _localFile;
_localFile = nullptr;
}
if (_callback) { // If localfile is present, contentStream is empty, so it is fine
_response.buffer = _contentsStream.getData();
_response.len = _contentsStream.size();
_response.eos = true;
(*_callback)(DataResponse(this, &_response));
}
}
void SessionRequest::start() {
if (_state != PAUSED || _started) {
warning("Can't start() SessionRequest as it is already started");
return;
}
_state = PROCESSING;
_started = true;
}
void SessionRequest::startAndWait() {
start();
wait();
}
void SessionRequest::reuse(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary) {
_url = url;
delete _callback;
delete _errorCallback;
_callback = cb;
_errorCallback = ecb;
_binary = binary;
openLocalFile(localFile);
restart();
}
void SessionRequest::handle() {
// This is called by ConnMan when state is PROCESSING
if (!_stream) _stream = makeStream();
if (_stream) {
if (_stream->httpResponseCode() != 200 && _stream->httpResponseCode() != 0) {
warning("SessionRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
ErrorResponse error(this, false, true, "HTTP response code is not 200 OK", _stream->httpResponseCode());
finishError(error);
return;
}
uint32 readBytes = _stream->read(_buffer, HTTP_SESSION_REQUEST_BUFFER_SIZE);
if (readBytes != 0) {
if (!_localFile) {
if (_contentsStream.write(_buffer, readBytes) != readBytes)
warning("SessionRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
} else {
_response.buffer = _buffer;
_response.len = readBytes;
_response.eos = _stream->eos();
if (_localFile->write(_buffer, readBytes) != readBytes) {
warning("DownloadRequest: unable to write all received bytes into output file");
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to write all bytes into a file", -1));
return;
}
if (_callback)
(*_callback)(DataResponse(this, &_response));
}
}
if (_stream->eos()) {
if (_stream->hasError()) {
ErrorResponse error(this, false, true, Common::String::format("Stream is in error: %s", _stream->getError()), -1);
finishError(error);
return;
}
finishSuccess();
}
}
}
void SessionRequest::restart() {
if (_stream) {
bool deleteStream = true;
if (_keepAlive && reuseStream()) {
deleteStream = false;
}
if (deleteStream) {
delete _stream;
_stream = nullptr;
}
}
_contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
_text = nullptr;
_complete = false;
_success = false;
_started = false;
//with no stream available next handle() will create another one
}
void SessionRequest::close() {
_state = FINISHED;
}
void SessionRequest::abortRequest() {
ErrorResponse error(this, false, true, Common::String::format("Aborted"), -1);
finishError(error);
if (_localFile) {
_localFile->close();
delete _localFile;
_localFile = nullptr;
// TODO we need to remove file, but there is no API
}
}
bool SessionRequest::complete() {
return _complete;
}
bool SessionRequest::success() {
return _success;
}
char *SessionRequest::text() {
if (_binary || _localFile)
return nullptr;
if (_text == nullptr)
_text = getPreparedContents();
return _text;
}
Common::JSONValue *SessionRequest::json() {
if (_binary)
error("SessionRequest::json() is called for binary stream");
if (_localFile)
error("SessionRequest::json() is called for localFile stream");
return Common::JSON::parse(text());
}
} // End of namespace Networking

View File

@@ -0,0 +1,103 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_NETWORKING_HTTP_SESSIONREQUEST_H
#define BACKENDS_NETWORKING_HTTP_SESSIONREQUEST_H
#include "backends/networking/http/httprequest.h"
#include "common/memstream.h"
namespace Common {
class DumpFile;
class JSONValue;
class Path;
}
namespace Networking {
#define HTTP_SESSION_REQUEST_BUFFER_SIZE 512 * 1024
struct SessionFileResponse {
byte *buffer;
uint32 len;
bool eos;
};
/**
* @brief Class for reading file and storing locally
*
* @return Returns SessionFileResponse in the callback
*/
class SessionRequest: public HttpRequest {
protected:
Common::MemoryWriteStreamDynamic _contentsStream;
byte *_buffer;
char *_text;
bool _started, _complete, _success;
bool _binary;
Common::DumpFile *_localFile;
SessionFileResponse _response;
bool reuseStream();
/** Prepares raw bytes from _contentsStream. */
char *getPreparedContents();
void finishError(const ErrorResponse &error, RequestState state = PAUSED) override;
void finishSuccess() override;
void openLocalFile(const Common::Path &localFile);
public:
SessionRequest(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
~SessionRequest() override;
void start();
void startAndWait();
void reuse(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
void handle() override;
void restart() override;
/** This request DOES NOT delete automatically after calling callbacks. It gets PAUSED, and in order to make it FINISHED (i.e. delete), this method MUST be called. */
void close();
/**
* @brief Closes the current request and removes any unfinished files
*
*/
void abortRequest();
bool complete();
bool success();
char *text();
Common::JSONValue *json();
byte *getData() { return _contentsStream.getData(); }
uint32 getSize() { return _contentsStream.size(); }
};
} // End of namespace Networking
#endif