Initial commit
This commit is contained in:
173
backends/networking/basic/android/jni.cpp
Normal file
173
backends/networking/basic/android/jni.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/* 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/networking/basic/android/jni.h"
|
||||
#ifdef USE_HTTP
|
||||
#include "backends/networking/http/android/networkreadstream-android.h"
|
||||
#endif
|
||||
|
||||
namespace Networking {
|
||||
|
||||
jclass NetJNI::_CLS_URL = nullptr;
|
||||
|
||||
jmethodID NetJNI::_MID_url_init = 0;
|
||||
jmethodID NetJNI::_MID_url_getProtocol = 0;
|
||||
jmethodID NetJNI::_MID_url_getHost = 0;
|
||||
jmethodID NetJNI::_MID_url_getPort = 0;
|
||||
jmethodID NetJNI::_MID_url_getDefaultPort = 0;
|
||||
|
||||
jclass NetJNI::_CLS_Socket = nullptr;
|
||||
|
||||
jmethodID NetJNI::_MID_socket_init = 0;
|
||||
jmethodID NetJNI::_MID_socket_ready = 0;
|
||||
jmethodID NetJNI::_MID_socket_send = 0;
|
||||
jmethodID NetJNI::_MID_socket_recv = 0;
|
||||
jmethodID NetJNI::_MID_socket_close = 0;
|
||||
|
||||
#ifdef USE_HTTP
|
||||
jmethodID NetJNI::_MID_manager_init = 0;
|
||||
jmethodID NetJNI::_MID_manager_startRequest = 0;
|
||||
jmethodID NetJNI::_MID_manager_poll = 0;
|
||||
jfieldID NetJNI::_FID_manager__empty = 0;
|
||||
|
||||
jclass NetJNI::_CLS_HTTPRequest = nullptr;
|
||||
|
||||
jmethodID NetJNI::_MID_request_bufinit = 0;
|
||||
jmethodID NetJNI::_MID_request_forminit = 0;
|
||||
jmethodID NetJNI::_MID_request_cancel = 0;
|
||||
jmethodID NetJNI::_MID_request_getURL = 0;
|
||||
|
||||
const JNINativeMethod NetJNI::_natives_request[] = {
|
||||
{ "gotHeaders", "(J[Ljava/lang/String;)V",
|
||||
(void *)NetworkReadStreamAndroid::gotHeaders },
|
||||
{ "gotData", "(J[BII)V",
|
||||
(void *)NetworkReadStreamAndroid::gotData },
|
||||
{ "finished", "(JILjava/lang/String;)V",
|
||||
(void *)NetworkReadStreamAndroid::finished_ },
|
||||
};
|
||||
#endif
|
||||
|
||||
bool NetJNI::_init = false;
|
||||
|
||||
void NetJNI::init(JNIEnv *env) {
|
||||
if (_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't call error here as the backend is not built yet
|
||||
#define FIND_CONSTRUCTOR(prefix, signature) do { \
|
||||
_MID_ ## prefix ## init = env->GetMethodID(cls, "<init>", signature "V");\
|
||||
if (_MID_ ## prefix ## init == 0) { \
|
||||
LOGE("Can't find method ID <init>"); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define FIND_METHOD(prefix, name, signature) do { \
|
||||
_MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \
|
||||
if (_MID_ ## prefix ## name == 0) { \
|
||||
LOGE("Can't find method ID " #name); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define FIND_FIELD(prefix, name, signature) do { \
|
||||
_FID_ ## prefix ## name = env->GetFieldID(cls, #name, signature); \
|
||||
if (_FID_ ## prefix ## name == 0) { \
|
||||
LOGE("Can't find field ID " #name); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
jclass cls = env->FindClass("java/net/URL");
|
||||
_CLS_URL = (jclass)env->NewGlobalRef(cls);
|
||||
|
||||
FIND_CONSTRUCTOR(url_, "(Ljava/lang/String;)");
|
||||
FIND_METHOD(url_, getProtocol, "()Ljava/lang/String;");
|
||||
FIND_METHOD(url_, getHost, "()Ljava/lang/String;");
|
||||
FIND_METHOD(url_, getPort, "()I");
|
||||
FIND_METHOD(url_, getDefaultPort, "()I");
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
|
||||
cls = env->FindClass("org/scummvm/scummvm/net/SSocket");
|
||||
_CLS_Socket = (jclass)env->NewGlobalRef(cls);
|
||||
|
||||
FIND_CONSTRUCTOR(socket_, "(Ljava/lang/String;)");
|
||||
FIND_METHOD(socket_, ready, "()I");
|
||||
FIND_METHOD(socket_, send, "([B)I");
|
||||
FIND_METHOD(socket_, recv, "([B)I");
|
||||
FIND_METHOD(socket_, close, "()V");
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
|
||||
#ifdef USE_HTTP
|
||||
cls = env->FindClass("org/scummvm/scummvm/net/HTTPManager");
|
||||
|
||||
FIND_CONSTRUCTOR(manager_, "()");
|
||||
FIND_METHOD(manager_, startRequest, "(Lorg/scummvm/scummvm/net/HTTPRequest;)V");
|
||||
FIND_METHOD(manager_, poll, "()V");
|
||||
FIND_FIELD(manager_, _empty, "Z");
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
|
||||
cls = env->FindClass("org/scummvm/scummvm/net/HTTPRequest");
|
||||
_CLS_HTTPRequest = (jclass)env->NewGlobalRef(cls);
|
||||
|
||||
if (env->RegisterNatives(cls, _natives_request, ARRAYSIZE(_natives_request)) < 0) {
|
||||
LOGE("Can't register natives for org/scummvm/scummvm/net/HTTPRequest");
|
||||
abort();
|
||||
}
|
||||
|
||||
FIND_CONSTRUCTOR(request_buf, "(JLjava/lang/String;[Ljava/lang/String;[BZZZ)");
|
||||
FIND_CONSTRUCTOR(request_form, "(JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)");
|
||||
FIND_METHOD(request_, cancel, "()V");
|
||||
FIND_METHOD(request_, getURL, "()Ljava/lang/String;");
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
#endif
|
||||
|
||||
#undef FIND_FIELD
|
||||
#undef FIND_METHOD
|
||||
#undef FIND_CONSTRUCTOR
|
||||
|
||||
_init = true;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
79
backends/networking/basic/android/jni.h
Normal file
79
backends/networking/basic/android/jni.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/* 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_BASIC_ANDROID_JNI_H
|
||||
#define BACKENDS_NETWORKING_BASIC_ANDROID_JNI_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class NetJNI {
|
||||
#ifdef USE_HTTP
|
||||
friend class ConnectionManagerAndroid;
|
||||
friend class NetworkReadStreamAndroid;
|
||||
#endif
|
||||
friend class AndroidSocket;
|
||||
friend class AndroidURL;
|
||||
|
||||
public:
|
||||
static void init(JNIEnv *env);
|
||||
|
||||
private:
|
||||
static jclass _CLS_URL;
|
||||
|
||||
static jmethodID _MID_url_init;
|
||||
static jmethodID _MID_url_getProtocol;
|
||||
static jmethodID _MID_url_getHost;
|
||||
static jmethodID _MID_url_getPort;
|
||||
static jmethodID _MID_url_getDefaultPort;
|
||||
|
||||
static jclass _CLS_Socket;
|
||||
|
||||
static jmethodID _MID_socket_init;
|
||||
static jmethodID _MID_socket_ready;
|
||||
static jmethodID _MID_socket_send;
|
||||
static jmethodID _MID_socket_recv;
|
||||
static jmethodID _MID_socket_close;
|
||||
|
||||
#ifdef USE_HTTP
|
||||
static jmethodID _MID_manager_init;
|
||||
static jmethodID _MID_manager_startRequest;
|
||||
static jmethodID _MID_manager_poll;
|
||||
static jfieldID _FID_manager__empty;
|
||||
|
||||
static const JNINativeMethod _natives_request[];
|
||||
|
||||
static jclass _CLS_HTTPRequest;
|
||||
|
||||
static jmethodID _MID_request_bufinit;
|
||||
static jmethodID _MID_request_forminit;
|
||||
static jmethodID _MID_request_cancel;
|
||||
static jmethodID _MID_request_getURL;
|
||||
#endif
|
||||
|
||||
static bool _init;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
|
||||
156
backends/networking/basic/android/socket.cpp
Normal file
156
backends/networking/basic/android/socket.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/* 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/basic/android/socket.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Socket *Socket::connect(const Common::String &url) {
|
||||
return AndroidSocket::connect(url);
|
||||
}
|
||||
|
||||
Socket *AndroidSocket::connect(const Common::String &url) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
NetJNI::init(env);
|
||||
|
||||
jstring url_obj = env->NewStringUTF(url.c_str());
|
||||
jobject socket_obj = env->NewObject(NetJNI::_CLS_Socket, NetJNI::_MID_socket_init, url_obj);
|
||||
|
||||
env->DeleteLocalRef(url_obj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Socket::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Socket *socket = new AndroidSocket(env, socket_obj);
|
||||
env->DeleteLocalRef(socket_obj);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
AndroidSocket::AndroidSocket(JNIEnv *env, jobject socket) {
|
||||
_socket = env->NewGlobalRef(socket);
|
||||
}
|
||||
|
||||
AndroidSocket::~AndroidSocket() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
env->CallVoidMethod(_socket, NetJNI::_MID_socket_close);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Socket::close failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
env->DeleteGlobalRef(_socket);
|
||||
}
|
||||
|
||||
int AndroidSocket::ready() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jint ret = env->CallIntMethod(_socket, NetJNI::_MID_socket_ready);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Socket::ready failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
// In doubt make it ready
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t AndroidSocket::send(const char *data, int len) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jbyteArray buffer_obj = env->NewByteArray(len);
|
||||
env->SetByteArrayRegion(buffer_obj, 0, len, (const jbyte *)data);
|
||||
|
||||
jint sent = env->CallIntMethod(_socket, NetJNI::_MID_socket_send, buffer_obj);
|
||||
|
||||
env->DeleteLocalRef(buffer_obj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Socket::send failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
size_t AndroidSocket::recv(void *data, int maxLen) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jbyteArray buffer_obj = env->NewByteArray(maxLen);
|
||||
|
||||
jint recvd = env->CallIntMethod(_socket, NetJNI::_MID_socket_recv, buffer_obj);
|
||||
assert(recvd <= maxLen);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Socket::send failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
env->DeleteLocalRef(buffer_obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
env->GetByteArrayRegion(buffer_obj, 0, recvd, (jbyte *)data);
|
||||
|
||||
return recvd;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
47
backends/networking/basic/android/socket.h
Normal file
47
backends/networking/basic/android/socket.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* 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_BASIC_CURL_SOCKET_H
|
||||
#define BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "backends/networking/basic/socket.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class AndroidSocket : public Socket {
|
||||
public:
|
||||
static Socket *connect(const Common::String &url);
|
||||
|
||||
AndroidSocket(JNIEnv *env, jobject socket);
|
||||
~AndroidSocket() override;
|
||||
|
||||
int ready() override;
|
||||
|
||||
size_t send(const char *data, int len) override;
|
||||
size_t recv(void *data, int maxLen) override;
|
||||
private:
|
||||
jobject _socket;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
180
backends/networking/basic/android/url.cpp
Normal file
180
backends/networking/basic/android/url.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/* 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/basic/android/url.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
URL *URL::parseURL(const Common::String &url) {
|
||||
return AndroidURL::parseURL(url);
|
||||
}
|
||||
|
||||
URL *AndroidURL::parseURL(const Common::String &url) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
NetJNI::init(env);
|
||||
|
||||
jstring url_sobj = env->NewStringUTF(url.c_str());
|
||||
jobject url_obj = env->NewObject(NetJNI::_CLS_URL, NetJNI::_MID_url_init, url_sobj);
|
||||
|
||||
env->DeleteLocalRef(url_sobj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("URL::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
URL *url_ = new AndroidURL(env, url_obj);
|
||||
env->DeleteLocalRef(url_obj);
|
||||
|
||||
return url_;
|
||||
}
|
||||
|
||||
AndroidURL::AndroidURL(JNIEnv *env, jobject url) {
|
||||
_url = env->NewGlobalRef(url);
|
||||
}
|
||||
|
||||
AndroidURL::~AndroidURL() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
env->DeleteGlobalRef(_url);
|
||||
}
|
||||
|
||||
Common::String AndroidURL::getScheme() const {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring protocol_obj = (jstring)env->CallObjectMethod(_url, NetJNI::_MID_url_getProtocol);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("URL::getProtocol failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
uint length = env->GetStringLength(protocol_obj);
|
||||
if (!length) {
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
const char *protocol_ptr = env->GetStringUTFChars(protocol_obj, 0);
|
||||
if (!protocol_ptr) {
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
Common::String result(protocol_ptr, length);
|
||||
|
||||
env->ReleaseStringUTFChars(protocol_obj, protocol_ptr);
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::String AndroidURL::getHost() const {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring protocol_obj = (jstring)env->CallObjectMethod(_url, NetJNI::_MID_url_getHost);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("URL::getHost failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
uint length = env->GetStringLength(protocol_obj);
|
||||
if (!length) {
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
const char *protocol_ptr = env->GetStringUTFChars(protocol_obj, 0);
|
||||
if (!protocol_ptr) {
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
Common::String result(protocol_ptr, length);
|
||||
|
||||
env->ReleaseStringUTFChars(protocol_obj, protocol_ptr);
|
||||
env->DeleteLocalRef(protocol_obj);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int AndroidURL::getPort(bool defaultPort) const {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jint port = env->CallIntMethod(_url, NetJNI::_MID_url_getPort);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("URL::getPort failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return -1;
|
||||
}
|
||||
if (port == -1 && defaultPort) {
|
||||
port = env->CallIntMethod(_url, NetJNI::_MID_url_getDefaultPort);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("URL::getDefaultPort failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (port == -1) {
|
||||
port = 0;
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
47
backends/networking/basic/android/url.h
Normal file
47
backends/networking/basic/android/url.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* 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_BASIC_ANDROID_URL_H
|
||||
#define BACKENDS_NETWORKING_BASIC_ANDROID_URL_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "backends/networking/basic/url.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class AndroidURL : public URL {
|
||||
public:
|
||||
static URL *parseURL(const Common::String &url);
|
||||
|
||||
AndroidURL(JNIEnv *env, jobject url);
|
||||
|
||||
~AndroidURL() override;
|
||||
|
||||
Common::String getScheme() const override;
|
||||
Common::String getHost() const override;
|
||||
int getPort(bool returnDefault = false) const override;
|
||||
private:
|
||||
jobject _url;
|
||||
};
|
||||
|
||||
} // End of Namespace Networking
|
||||
|
||||
#endif
|
||||
52
backends/networking/basic/curl/cacert.cpp
Normal file
52
backends/networking/basic/curl/cacert.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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/basic/curl/cacert.h"
|
||||
|
||||
#include "common/fs.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Common::String getCaCertPath() {
|
||||
#if defined(DATA_PATH)
|
||||
static enum {
|
||||
kNotInitialized,
|
||||
kFileNotFound,
|
||||
kFileExists
|
||||
} state = kNotInitialized;
|
||||
|
||||
if (state == kNotInitialized) {
|
||||
Common::FSNode node(DATA_PATH "/cacert.pem");
|
||||
state = node.exists() ? kFileExists : kFileNotFound;
|
||||
}
|
||||
|
||||
if (state == kFileExists) {
|
||||
return DATA_PATH "/cacert.pem";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace Networking
|
||||
33
backends/networking/basic/curl/cacert.h
Normal file
33
backends/networking/basic/curl/cacert.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/* 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_BASIC_CURL_CACERT_H
|
||||
#define BACKENDS_NETWORKING_BASIC_CURL_CACERT_H
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
/** Return the path to the CA certificates bundle. */
|
||||
Common::String getCaCertPath();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
177
backends/networking/basic/curl/socket.cpp
Normal file
177
backends/networking/basic/curl/socket.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
/* 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
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "backends/networking/basic/curl/socket.h"
|
||||
#include "backends/networking/basic/curl/cacert.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/system.h"
|
||||
|
||||
// Auxiliary function that waits on the socket.
|
||||
// From https://github.com/curl/curl/blob/master/docs/examples/sendrecv.c
|
||||
static int waitOnSocket(curl_socket_t sockfd, int for_recv, long timeout_ms) {
|
||||
struct timeval tv {};
|
||||
fd_set infd {}, outfd {}, errfd {};
|
||||
int res;
|
||||
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
FD_ZERO(&infd);
|
||||
FD_ZERO(&outfd);
|
||||
FD_ZERO(&errfd);
|
||||
|
||||
FD_SET(sockfd, &errfd); /* always check for error */
|
||||
|
||||
if(for_recv) {
|
||||
FD_SET(sockfd, &infd);
|
||||
} else {
|
||||
FD_SET(sockfd, &outfd);
|
||||
}
|
||||
|
||||
/* select() returns the number of signalled sockets or -1 */
|
||||
res = select((int)sockfd + 1, &infd, &outfd, &errfd, &tv);
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Socket *Socket::connect(const Common::String &url) {
|
||||
CURL *easy = curl_easy_init();
|
||||
if (!easy) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
|
||||
// Just connect to the host, do not do any transfers.
|
||||
curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1L);
|
||||
|
||||
// libcurl won't connect to SSL connections
|
||||
// with VERIFYPEER enabled because we do not ship
|
||||
// with a CA bundle in these platforms.
|
||||
// So let's disable it.
|
||||
#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());
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(easy);
|
||||
if (res != CURLE_OK) {
|
||||
warning("libcurl: Failed to connect: %s", curl_easy_strerror(res));
|
||||
curl_easy_cleanup(easy);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the socket, we'll need it for waiting.
|
||||
// NB: Do not use CURL_AT_LEAST_VERSION(x,y,z) or CURL_VERSION_BITS(x,y,z) for version check
|
||||
// These were only added around v7.45.0 release so break build with earlier libcurl versions
|
||||
#if LIBCURL_VERSION_NUM >= 0x072d00 // 7.45.0
|
||||
// Use new CURLINFO_ACTIVESOCKET API for libcurl v7.45.0 or greater
|
||||
curl_socket_t socket;
|
||||
res = curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket);
|
||||
if (res == CURLE_OK) {
|
||||
return new CurlSocket(easy, socket);
|
||||
}
|
||||
#else
|
||||
// Fallback on old deprecated CURLINFO_LASTSOCKET API for libcurl older than v7.45.0 (October 2015)
|
||||
long socket;
|
||||
res = curl_easy_getinfo(easy, CURLINFO_LASTSOCKET, &socket);
|
||||
if (res == CURLE_OK) {
|
||||
// curl_socket_t is an int or a SOCKET (Win32) which is a UINT_PTR
|
||||
// A cast should be safe enough as long fits in it
|
||||
return new CurlSocket(easy, (curl_socket_t)socket);
|
||||
}
|
||||
#endif
|
||||
|
||||
warning("libcurl: Failed to extract socket: %s", curl_easy_strerror(res));
|
||||
curl_easy_cleanup(easy);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CurlSocket::CurlSocket(CURL *easy, curl_socket_t socket) : _easy(easy), _socket(socket) {
|
||||
}
|
||||
|
||||
CurlSocket::~CurlSocket() {
|
||||
// Always clean up.
|
||||
curl_easy_cleanup(_easy);
|
||||
}
|
||||
|
||||
int CurlSocket::ready() {
|
||||
return waitOnSocket(_socket, 1, 0);
|
||||
}
|
||||
|
||||
size_t CurlSocket::send(const char *data, int len) {
|
||||
size_t nsent_total = 0, left = len;
|
||||
CURLcode res = CURLE_AGAIN;
|
||||
|
||||
// Keep looping until the whole thing is sent, errors,
|
||||
// or times out.
|
||||
while (((left > 0) && (len > 0))) {
|
||||
size_t nsent = 0;
|
||||
uint32 tickCount = g_system->getMillis() + 5000;
|
||||
while (res == CURLE_AGAIN) {
|
||||
res = curl_easy_send(_easy, data + nsent_total, left - nsent_total, &nsent);
|
||||
if (g_system->getMillis() >= tickCount) {
|
||||
warning("libcurl: Took too long attempting to send data to socket");
|
||||
return nsent;
|
||||
}
|
||||
}
|
||||
if (res == CURLE_OK) {
|
||||
nsent_total += nsent;
|
||||
left -= nsent;
|
||||
} else if (res != CURLE_AGAIN) {
|
||||
warning("libcurl: Error when sending to socket: %s", curl_easy_strerror(res));
|
||||
return nsent_total;
|
||||
}
|
||||
}
|
||||
|
||||
return nsent_total;
|
||||
}
|
||||
|
||||
size_t CurlSocket::recv(void *data, int maxLen) {
|
||||
size_t nread = 0;
|
||||
CURLcode res = CURLE_AGAIN;
|
||||
uint32 tickCount = g_system->getMillis() + 5000;
|
||||
while (res == CURLE_AGAIN) {
|
||||
res = curl_easy_recv(_easy, data, maxLen, &nread);
|
||||
if (g_system->getMillis() >= tickCount) {
|
||||
warning("libcurl: Took too long attempting to read data from socket");
|
||||
return nread;
|
||||
}
|
||||
}
|
||||
if(res != CURLE_OK) {
|
||||
warning("libcurl Error on receiving data: %s\n", curl_easy_strerror(res));
|
||||
return nread;
|
||||
}
|
||||
|
||||
debug(3, "libcurl: Received %llu bytes", (unsigned long long)nread);
|
||||
return nread;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
56
backends/networking/basic/curl/socket.h
Normal file
56
backends/networking/basic/curl/socket.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* 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_BASIC_CURL_SOCKET_H
|
||||
#define BACKENDS_NETWORKING_BASIC_CURL_SOCKET_H
|
||||
|
||||
typedef void CURL;
|
||||
#ifdef WIN32
|
||||
// Including winsock2.h will result in errors, we have to define
|
||||
// SOCKET ourselves.
|
||||
#include <basetsd.h>
|
||||
typedef UINT_PTR SOCKET;
|
||||
|
||||
typedef SOCKET curl_socket_t;
|
||||
#else
|
||||
typedef int curl_socket_t;
|
||||
#endif
|
||||
|
||||
#include "backends/networking/basic/socket.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class CurlSocket : public Socket {
|
||||
public:
|
||||
CurlSocket(CURL *easy, curl_socket_t socket);
|
||||
~CurlSocket() override;
|
||||
|
||||
int ready() override;
|
||||
|
||||
size_t send(const char *data, int len) override;
|
||||
size_t recv(void *data, int maxLen) override;
|
||||
private:
|
||||
CURL *_easy;
|
||||
curl_socket_t _socket;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
104
backends/networking/basic/curl/url.cpp
Normal file
104
backends/networking/basic/curl/url.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/* 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
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "backends/networking/basic/curl/url.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
// This requires libcurl version 7.62.0, If we're using
|
||||
// a lower version, stub all of this.
|
||||
#if LIBCURL_VERSION_NUM < 0x073E00
|
||||
|
||||
URL *URL::parseURL(const Common::String &url) {
|
||||
warning("libcurl: curl_url requires curl 7.62.0 or later");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
URL *URL::parseURL(const Common::String &url) {
|
||||
CURLU *curlu = curl_url();
|
||||
if (!curlu) {
|
||||
warning("libcurl: Could not create curl_url handle");
|
||||
return nullptr;
|
||||
}
|
||||
CURLUcode rc = curl_url_set(curlu, CURLUPART_URL, url.c_str(), 0);
|
||||
if (rc) {
|
||||
warning("libcurl: Unable to parse URL: \"%s\"", url.c_str());
|
||||
curl_url_cleanup(curlu);
|
||||
return nullptr;
|
||||
}
|
||||
return new CurlURL(curlu);
|
||||
}
|
||||
|
||||
CurlURL::CurlURL(CURLU *curlu) : _url(curlu) {
|
||||
}
|
||||
|
||||
CurlURL::~CurlURL() {
|
||||
curl_url_cleanup(_url);
|
||||
}
|
||||
|
||||
Common::String CurlURL::getScheme() const {
|
||||
char *scheme;
|
||||
CURLUcode rc = curl_url_get(_url, CURLUPART_SCHEME, &scheme, 0);
|
||||
if (rc) {
|
||||
warning("libcurl: Unable to get scheme");
|
||||
return "";
|
||||
}
|
||||
Common::String schemeString(scheme);
|
||||
curl_free(scheme);
|
||||
return schemeString;
|
||||
}
|
||||
|
||||
Common::String CurlURL::getHost() const {
|
||||
char *host;
|
||||
CURLUcode rc = curl_url_get(_url, CURLUPART_HOST, &host, 0);
|
||||
if (rc) {
|
||||
warning("libcurl: Unable to get host");
|
||||
return "";
|
||||
}
|
||||
Common::String hostString(host);
|
||||
curl_free(host);
|
||||
return hostString;
|
||||
}
|
||||
|
||||
int CurlURL::getPort(bool defaultPort) const {
|
||||
char *portChr;
|
||||
CURLUcode rc = curl_url_get(_url, CURLUPART_PORT, &portChr, (defaultPort) ? CURLU_DEFAULT_PORT : CURLU_NO_DEFAULT_PORT);
|
||||
if (rc) {
|
||||
if (rc == CURLUE_NO_PORT)
|
||||
return 0;
|
||||
warning("libcurl: Unable to get port");
|
||||
return -1;
|
||||
}
|
||||
int port = atoi(portChr);
|
||||
curl_free(portChr);
|
||||
return port;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Networking
|
||||
45
backends/networking/basic/curl/url.h
Normal file
45
backends/networking/basic/curl/url.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* 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_BASIC_CURL_URL_H
|
||||
#define BACKENDS_NETWORKING_BASIC_CURL_URL_H
|
||||
|
||||
typedef struct Curl_URL CURLU;
|
||||
|
||||
#include "backends/networking/basic/url.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class CurlURL : public URL {
|
||||
public:
|
||||
CurlURL(CURLU *curlu);
|
||||
|
||||
~CurlURL() override;
|
||||
|
||||
Common::String getScheme() const override;
|
||||
Common::String getHost() const override;
|
||||
int getPort(bool returnDefault = false) const override;
|
||||
private:
|
||||
CURLU *_url;
|
||||
};
|
||||
|
||||
} // End of Namespace Networking
|
||||
|
||||
#endif
|
||||
42
backends/networking/basic/socket.h
Normal file
42
backends/networking/basic/socket.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* 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_BASIC_SOCKET_H
|
||||
#define BACKENDS_NETWORKING_BASIC_SOCKET_H
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
static Socket *connect(const Common::String &url);
|
||||
|
||||
virtual ~Socket() {}
|
||||
|
||||
virtual int ready() = 0;
|
||||
virtual size_t send(const char *data, int len) = 0;
|
||||
virtual size_t recv(void *data, int maxLen) = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
|
||||
67
backends/networking/basic/url.h
Normal file
67
backends/networking/basic/url.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 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_BASIC_URL_H
|
||||
#define BACKENDS_NETWORKING_BASIC_URL_H
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class URL {
|
||||
public:
|
||||
/**
|
||||
* Parses an URL string and creates a new URL object.
|
||||
* @param url is a string containing the URL. e.g. "https://scummvm.org".
|
||||
* @retval An URL object from the url provided
|
||||
*/
|
||||
static URL *parseURL(const Common::String &url);
|
||||
|
||||
virtual ~URL() {}
|
||||
|
||||
/**
|
||||
* Extracts the scheme of an URL parsed previously by parseURL.
|
||||
* @retval String of the URL's scheme. e.g. "https".
|
||||
* @retval Empty string on failure.
|
||||
*/
|
||||
virtual Common::String getScheme() const = 0;
|
||||
|
||||
/**
|
||||
* Extracts the host name of an URL parsed previously by parseURL.
|
||||
* @retval String of the URL's host name. e.g. "scummvm.org".
|
||||
* @retval Empty string on failure.
|
||||
*/
|
||||
virtual Common::String getHost() const = 0;
|
||||
|
||||
/**
|
||||
* Extracts the port of an URL parsed previously by parseURL.
|
||||
* @param returnDefault tells libcurl to return the default port according to the URL's scheme if not explicitly defined
|
||||
* @retval The URL's port number if one exists.
|
||||
* @retval 0 if no port found.
|
||||
* @retval default port if returnDefault is true.
|
||||
* @retval -1 on failure.
|
||||
*/
|
||||
virtual int getPort(bool returnDefault = false) const = 0;
|
||||
};
|
||||
|
||||
} // End of Namespace Networking
|
||||
|
||||
#endif
|
||||
|
||||
144
backends/networking/enet/enet.cpp
Normal file
144
backends/networking/enet/enet.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/* 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
|
||||
|
||||
#include "backends/networking/enet/source/enet.h"
|
||||
#include "backends/networking/enet/enet.h"
|
||||
#include "backends/networking/enet/host.h"
|
||||
#include "backends/networking/enet/socket.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
ENet::ENet() {
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
ENet::~ENet() {
|
||||
if (_initialized) {
|
||||
// Deinitialize the library.
|
||||
debug(1, "ENet: Deinitializing.");
|
||||
enet_deinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool ENet::initialize() {
|
||||
if (ENet::_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (enet_initialize() != 0) {
|
||||
warning("ENet: ENet library failed to initialize");
|
||||
return false;
|
||||
}
|
||||
debug(1, "ENet: Initialized.");
|
||||
_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Host *ENet::createHost(const Common::String &address, int port, int numClients, int numChannels, int incBand, int outBand) {
|
||||
ENetAddress enetAddress;
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
enetAddress.port = port;
|
||||
|
||||
ENetHost *_host = enet_host_create(&enetAddress, numClients, numChannels, incBand, outBand);
|
||||
if (_host == nullptr) {
|
||||
warning("ENet: An error occurred when trying to create host with address %s:%d", address.c_str(), port);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new Host(_host);
|
||||
}
|
||||
|
||||
Host *ENet::connectToHost(const Common::String &hostAddress, int hostPort, const Common::String &address, int port, int timeout, int numChannels, int incBand, int outBand) {
|
||||
ENetAddress enetHostAddress;
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetHostAddress, hostAddress.c_str());
|
||||
enetHostAddress.port = hostPort;
|
||||
|
||||
// NOTE: Number of channels must match with the server's.
|
||||
ENetHost *enetHost = enet_host_create(&enetHostAddress, 1, numChannels, incBand, outBand);
|
||||
if (enetHost == nullptr) {
|
||||
warning("ENet: An error occurred when trying to create client host");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ENetAddress enetAddress;
|
||||
if (address == "255.255.255.255") {
|
||||
enetAddress.host = ENET_HOST_BROADCAST;
|
||||
} else {
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
}
|
||||
enetAddress.port = port;
|
||||
|
||||
// Connect to server address
|
||||
ENetPeer *enetPeer = enet_host_connect(enetHost, &enetAddress, numChannels, 0);
|
||||
|
||||
ENetEvent event;
|
||||
if (enet_host_service(enetHost, &event, timeout) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
|
||||
debug(1, "ENet: Connection to %s:%d succeeded.", address.c_str(), port);
|
||||
return new Host(enetHost, enetPeer);
|
||||
}
|
||||
warning("ENet: Connection to %s:%d failed", address.c_str(), port);
|
||||
enet_peer_reset(enetPeer);
|
||||
enet_host_destroy(enetHost);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Host *ENet::connectToHost(const Common::String &address, int port, int timeout, int numChannels, int incBand, int outBand) {
|
||||
return connectToHost("0.0.0.0", 0, address, port, timeout, numChannels, incBand, outBand);
|
||||
}
|
||||
|
||||
Socket *ENet::createSocket(const Common::String &address, int port) {
|
||||
ENetAddress enetAddress;
|
||||
if (address == "255.255.255.255") {
|
||||
enetAddress.host = ENET_HOST_BROADCAST;
|
||||
} else {
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
}
|
||||
enetAddress.port = port;
|
||||
|
||||
ENetSocket enetSocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
|
||||
if (enetSocket == ENET_SOCKET_NULL) {
|
||||
warning("ENet: Unable to create socket");
|
||||
return nullptr;
|
||||
}
|
||||
if (enet_socket_bind(enetSocket, &enetAddress) < 0) {
|
||||
warning("ENet: Unable to bind socket to address %s:%d", address.c_str(), port);
|
||||
enet_socket_destroy(enetSocket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enet_socket_set_option (enetSocket, ENET_SOCKOPT_NONBLOCK, 1);
|
||||
enet_socket_set_option (enetSocket, ENET_SOCKOPT_BROADCAST, 1);
|
||||
enet_socket_set_option (enetSocket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE);
|
||||
enet_socket_set_option (enetSocket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE);
|
||||
|
||||
return new Socket(enetSocket);
|
||||
}
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
97
backends/networking/enet/enet.h
Normal file
97
backends/networking/enet/enet.h
Normal 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_ENET_ENET_H
|
||||
#define BACKENDS_NETWORKING_ENET_ENET_H
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
class Host;
|
||||
class Socket;
|
||||
|
||||
class ENet {
|
||||
public:
|
||||
/**
|
||||
* The main object that allows for ENet host and socket creation.
|
||||
*/
|
||||
ENet();
|
||||
~ENet();
|
||||
|
||||
/**
|
||||
* Initializes the ENet library. Must be called first before any other functions.
|
||||
* @return true if successful, false on failure.
|
||||
*/
|
||||
bool initialize();
|
||||
/**
|
||||
* Creates a new ENet Host instance for listening for/connecting to peers.
|
||||
* @param address the address at which other peers may connect to this host. "0.0.0.0" may be to used to use the default host.
|
||||
* @param port a port number this host will use.
|
||||
* @param numClients the maximum number of peers that should be allocated for the host.
|
||||
* @param numChannels the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
|
||||
* @param incBand downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.
|
||||
* @param outBand upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.
|
||||
* @retval Networking::Host object on success
|
||||
* @retval nullptr on failure
|
||||
* @see Networking::Host
|
||||
*/
|
||||
Host *createHost(const Common::String &address, int port, int numClients, int numChannels = 1, int incBand = 0, int outBand = 0);
|
||||
/**
|
||||
* Creates a new ENet Host instance, and attempts to connect to the assigned address and port.
|
||||
* @param hostAddress the address this host will use to connect to this peer. "0.0.0.0" may be to used to use the default host.
|
||||
* @param hostPort a port number this host will use. If not used, the host will use an allocated port given by the operating system.
|
||||
* @param address the address of the peer that will attempt to connect to.
|
||||
* @param port the port number of the peer that will attempt to connect to.
|
||||
* @param timeout specifies the connection timeout in milliseconds, 5 full seconds by default. Will fail if the given time has passed.
|
||||
* @param numChannels the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT. This must match with the connecting peer.
|
||||
* @param incBand downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. This must match with the connecting peer.
|
||||
* @param outBand upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. This must match with the connecting peer.
|
||||
* @retval Networking::Host object on success
|
||||
* @retval nullptr on failure
|
||||
* @see Networking::Host
|
||||
*/
|
||||
Host *connectToHost(const Common::String &hostAddress, int hostPort, const Common::String &address, int port, int timeout = 5000, int numChannels = 1, int incBand = 0, int outBand = 0);
|
||||
Host *connectToHost(const Common::String &address, int port, int timeout = 5000, int numChannels = 1, int incBand = 0, int outBand = 0);
|
||||
/**
|
||||
* Creates a Networking::Socket instance which is a representation of a raw UDP socket.
|
||||
* Useful for and sending and receiving data that is outside the ENet library protocol.
|
||||
* @param address the address this socket will send to and/or receive data from.
|
||||
* @param port the port number this socket will send to and/or receive data from.
|
||||
* @retval Networking::Socket object on success
|
||||
* @retval nullptr on failure
|
||||
* @see Networking::Socket
|
||||
*/
|
||||
Socket *createSocket(const Common::String &address, int port);
|
||||
private:
|
||||
/**
|
||||
* Indicates if the ENet library has successfully initialized or not.
|
||||
* @see initialize()
|
||||
*/
|
||||
bool _initialized;
|
||||
};
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
|
||||
|
||||
#endif
|
||||
192
backends/networking/enet/host.cpp
Normal file
192
backends/networking/enet/host.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/* 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
|
||||
|
||||
#include "backends/networking/enet/source/enet.h"
|
||||
#include "backends/networking/enet/host.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
Host::Host(ENetHost *host) {
|
||||
_host = host;
|
||||
_serverPeer = nullptr;
|
||||
_recentHost = "";
|
||||
_recentPort = 0;
|
||||
_recentPacket = nullptr;
|
||||
}
|
||||
|
||||
Host::Host(ENetHost *host, ENetPeer *serverPeer) {
|
||||
_host = host;
|
||||
_serverPeer = serverPeer;
|
||||
_recentHost = "";
|
||||
_recentPort = 0;
|
||||
_recentPacket = nullptr;
|
||||
}
|
||||
|
||||
Host::~Host() {
|
||||
if (_recentPacket)
|
||||
destroyPacket();
|
||||
enet_host_destroy(_host);
|
||||
}
|
||||
|
||||
uint8 Host::service(int timeout) {
|
||||
ENetEvent event;
|
||||
enet_host_service(_host, &event, timeout);
|
||||
|
||||
if (event.type > ENET_EVENT_TYPE_NONE) {
|
||||
char hostName[50];
|
||||
if (enet_address_get_host_ip(&event.peer->address, hostName, 50) == 0)
|
||||
_recentHost = Common::String(hostName);
|
||||
_recentPort = event.peer->address.port;
|
||||
}
|
||||
|
||||
if (event.type == ENET_EVENT_TYPE_RECEIVE) {
|
||||
if (_recentPacket)
|
||||
destroyPacket();
|
||||
_recentPacket = event.packet;
|
||||
}
|
||||
return event.type;
|
||||
}
|
||||
|
||||
bool Host::connectPeer(const Common::String &address, int port, int timeout, int numChannels) {
|
||||
ENetAddress enetAddress;
|
||||
if (address == "255.255.255.255") {
|
||||
enetAddress.host = ENET_HOST_BROADCAST;
|
||||
} else {
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
}
|
||||
enetAddress.port = port;
|
||||
|
||||
// Connect to server address
|
||||
ENetPeer *enetPeer = enet_host_connect(_host, &enetAddress, numChannels, 0);
|
||||
|
||||
ENetEvent event;
|
||||
if (enet_host_service(_host, &event, timeout) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
|
||||
debug(1, "ENet: Connection to %s:%d succeeded.", address.c_str(), port);
|
||||
return true;
|
||||
}
|
||||
warning("ENet: Connection to %s:%d failed", address.c_str(), port);
|
||||
enet_peer_reset(enetPeer);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Host::disconnectPeer(int peerIndex) {
|
||||
// calling _later will ensure that all the queued packets are sent before disconnecting.
|
||||
enet_peer_disconnect_later(&_host->peers[peerIndex], 0);
|
||||
enet_host_flush(_host);
|
||||
|
||||
if (_serverPeer) {
|
||||
// Allow 3 seconds for disconnection to succeed and drop incoming packets
|
||||
uint tickCount = 0;
|
||||
while(tickCount < 3000) {
|
||||
switch(service(0)) {
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
destroyPacket();
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
// Disconnect succeeded.
|
||||
return;
|
||||
}
|
||||
g_system->delayMillis(5);
|
||||
tickCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::String Host::getHost() {
|
||||
return _recentHost;
|
||||
}
|
||||
|
||||
int Host::getPort() {
|
||||
return _recentPort;
|
||||
}
|
||||
|
||||
int Host::getPeerIndexFromHost(const Common::String &host, int port) {
|
||||
for (int i = 0; i < (int)_host->peerCount; i++) {
|
||||
char hostName[50];
|
||||
if (enet_address_get_host_ip(&_host->peers[i].address, hostName, 50) == 0) {
|
||||
if (host == hostName && port == _host->peers[i].address.port) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Common::String Host::getPacketData() {
|
||||
if (!_recentPacket)
|
||||
return "";
|
||||
return Common::String((const char*)_recentPacket->data, (uint32)_recentPacket->dataLength);
|
||||
}
|
||||
|
||||
void Host::destroyPacket() {
|
||||
if (!_recentPacket)
|
||||
return;
|
||||
enet_packet_destroy(_recentPacket);
|
||||
_recentPacket = nullptr;
|
||||
}
|
||||
|
||||
bool Host::send(const char *data, int peerIndex, int channel, bool reliable) {
|
||||
ENetPeer *peer;
|
||||
if (_serverPeer) {
|
||||
peer = _serverPeer;
|
||||
} else {
|
||||
if (peerIndex > (int)_host->peerCount) {
|
||||
warning("ENet: Peer index (%d) is too high", peerIndex);
|
||||
return false;
|
||||
}
|
||||
peer = &_host->peers[peerIndex];
|
||||
}
|
||||
|
||||
ENetPacket *packet = enet_packet_create(const_cast<char *>(data), strlen(data), (reliable) ? ENET_PACKET_FLAG_RELIABLE : 0);
|
||||
enet_peer_send(peer, channel, packet);
|
||||
|
||||
enet_host_flush(_host);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Host::sendRawData(const Common::String &address, int port, const char *data) {
|
||||
ENetAddress enetAddress;
|
||||
if (address == "255.255.255.255") {
|
||||
enetAddress.host = ENET_HOST_BROADCAST;
|
||||
} else {
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
}
|
||||
enetAddress.port = port;
|
||||
|
||||
ENetBuffer _buffer;
|
||||
_buffer.data = const_cast<char *>(data);
|
||||
_buffer.dataLength = strlen(data);
|
||||
|
||||
int sentLength = enet_socket_send(_host->socket, &enetAddress, &_buffer, 1);
|
||||
if (sentLength < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
177
backends/networking/enet/host.h
Normal file
177
backends/networking/enet/host.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/* 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_ENET_HOST_H
|
||||
#define BACKENDS_NETWORKING_ENET_HOST_H
|
||||
|
||||
typedef struct _ENetHost ENetHost;
|
||||
typedef struct _ENetPeer ENetPeer;
|
||||
|
||||
typedef struct _ENetEvent ENetEvent;
|
||||
typedef struct _ENetPacket ENetPacket;
|
||||
|
||||
|
||||
// Event types
|
||||
#define ENET_EVENT_TYPE_NONE 0
|
||||
#define ENET_EVENT_TYPE_CONNECT 1
|
||||
#define ENET_EVENT_TYPE_DISCONNECT 2
|
||||
#define ENET_EVENT_TYPE_RECEIVE 3
|
||||
|
||||
#include "common/system.h"
|
||||
#include "common/str.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
class Host {
|
||||
public:
|
||||
/**
|
||||
* A representation of ENetHost.
|
||||
* @param host A pointer to ENetHost given by the createHost function.
|
||||
* @see Networking::ENet::createHost
|
||||
*/
|
||||
Host(ENetHost *host);
|
||||
/**
|
||||
* A representation of ENetHost, connected to a server peer.
|
||||
* @param host A pointer to ENetHost given by the connnectToHost function.
|
||||
* @param serverPeer a pointer to a connected peer given by the connnectToHost function.
|
||||
* @see Networking::ENet::connectToHost
|
||||
*/
|
||||
Host(ENetHost *host, ENetPeer *serverPeer);
|
||||
~Host();
|
||||
|
||||
/**
|
||||
* Services the host which receives or sends pending messages,
|
||||
* intended to be called at the start of a game loop.
|
||||
* @param timeout number of milliseconds that ENet should wait for events.
|
||||
* @retval > 0 if an event occurred within the specified time limit.
|
||||
* @retval 0 if no event occurred.
|
||||
* @retval < 0 on failure.
|
||||
*/
|
||||
uint8 service(int timeout = 0);
|
||||
|
||||
/**
|
||||
* Connected to a foreign peer.
|
||||
* @param address the address of the peer that will attempt to connect to.
|
||||
* @param port the port number of the peer that will attempt to connect to.
|
||||
* @param timeout specifies the connection timeout in milliseconds, 5 full seconds by default. Will fail if the given time has passed.
|
||||
* @param numChannels the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT. This must match with the connecting peer.
|
||||
* @retval true on successful.
|
||||
* @retval false on failure.
|
||||
*/
|
||||
bool connectPeer(const Common::String &address, int port, int timeout = 5000, int numChannels = 1);
|
||||
/**
|
||||
* Disconnects a specific peer from the host.
|
||||
* @param peerIndex Index of a peer to disconnect.
|
||||
* @note A peer index can be retrieved from the getPeerIndexFromHost function.
|
||||
*/
|
||||
void disconnectPeer(int peerIndex);
|
||||
|
||||
/**
|
||||
* Creates a packet and sends to a peer.
|
||||
* @param data Contents of the packet's data; the packet's data will remain uninitialized if data is NULL.
|
||||
* @param peerIndex Index of a peer to send the packet to.
|
||||
* @param channel channel on which to send; 0 by default.
|
||||
* @param reliable Indicates that the packet must be received by the target peer and resend attempts will be made until the packet is delivered.
|
||||
* @retval true on successful.
|
||||
* @retval false on failure.
|
||||
* @note Currently, if this object is created by the connectToHost function, data will always get sent to _serverPeer regardless of index given.
|
||||
*/
|
||||
bool send(const char *data, int peerIndex, int channel = 0, bool reliable = true);
|
||||
/**
|
||||
* Sends raw data to an address outside the ENet protocol using its UDP socket directly.
|
||||
* @param address Address to send data to.
|
||||
* @param port Port number to send data to.
|
||||
* @param data The data which will be sent.
|
||||
* @retval true on successful.
|
||||
* @retval false on failure.
|
||||
* @note This sends data as a raw connection-less UDP socket, the same functionaility as a Networking::Socket object, but retains the address and port this host object is using.
|
||||
* @note Useful for hole-punching a peer, so it can connect to it.
|
||||
*/
|
||||
bool sendRawData(const Common::String &address, int port, const char *data);
|
||||
|
||||
/**
|
||||
* Gets the host name of a peer that have recently connected, disconnected or received packet from.
|
||||
* @return String containing the host name.
|
||||
* @note service() must be called and returned > 0 for this function to work.
|
||||
*/
|
||||
Common::String getHost();
|
||||
/**
|
||||
* Gets the port number of a peer that have recently connected, disconnected or received packet from.
|
||||
* @return A port number.
|
||||
* @note service() must be called and returned > 0 for this function to work.
|
||||
*/
|
||||
int getPort();
|
||||
|
||||
/**
|
||||
* Gets an index from a connected peer.
|
||||
* @param host A peer's host name
|
||||
* @param port A peer's port number.
|
||||
* @retval >= 0 containing a peer index if successfully found.
|
||||
* @retval -1 if not found.
|
||||
*/
|
||||
int getPeerIndexFromHost(const Common::String &host, int port);
|
||||
|
||||
/**
|
||||
* Gets the data from the most-recently received packet.
|
||||
* @return String containing the packet's data.
|
||||
* @note service() must be called and returned ENET_EVENT_TYPE_RECEIVE (3) for this function to work.
|
||||
*/
|
||||
Common::String getPacketData();
|
||||
/**
|
||||
* Deallocate the packet, must be called upon receiving and finished using the packet's data.
|
||||
*/
|
||||
void destroyPacket();
|
||||
private:
|
||||
|
||||
/**
|
||||
* Pointer to ENetHost this object represents.
|
||||
* @see Networking::ENet::createHost
|
||||
* @see Networking::ENet::connectToHost
|
||||
*/
|
||||
ENetHost *_host;
|
||||
/**
|
||||
* A representing server peer connected by the connectToHost function.
|
||||
* @see Networking::ENet::connectToHost
|
||||
*/
|
||||
ENetPeer *_serverPeer;
|
||||
/**
|
||||
* String containing the recent host name connected, disconnected or received from.
|
||||
* @see getHost()
|
||||
*/
|
||||
Common::String _recentHost;
|
||||
/**
|
||||
* String containing the recent host name connected, disconnected or received from.
|
||||
* @see getPort()
|
||||
*/
|
||||
int _recentPort;
|
||||
/**
|
||||
* String containing the recent host name connected, disconnected or received from.
|
||||
* @see getPacketData()
|
||||
*/
|
||||
ENetPacket *_recentPacket;
|
||||
};
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
106
backends/networking/enet/socket.cpp
Normal file
106
backends/networking/enet/socket.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/* 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
|
||||
|
||||
#include "backends/networking/enet/source/enet.h"
|
||||
#include "backends/networking/enet/socket.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
Socket::Socket(ENetSocket socket) {
|
||||
_socket = socket;
|
||||
_recentData = Common::String();
|
||||
_recentHost = Common::String();
|
||||
_recentPort = 0;
|
||||
}
|
||||
Socket::~Socket() {
|
||||
enet_socket_destroy(_socket);
|
||||
}
|
||||
|
||||
bool Socket::send(const Common::String &address, int port, const char *data) {
|
||||
ENetAddress enetAddress;
|
||||
if (address == "255.255.255.255") {
|
||||
enetAddress.host = ENET_HOST_BROADCAST;
|
||||
} else {
|
||||
// NOTE: 0.0.0.0 returns ENET_HOST_ANY normally.
|
||||
enet_address_set_host(&enetAddress, address.c_str());
|
||||
}
|
||||
enetAddress.port = port;
|
||||
|
||||
ENetBuffer buffer;
|
||||
buffer.data = const_cast<char *>(data);
|
||||
buffer.dataLength = strlen(data);
|
||||
|
||||
int sentLength = enet_socket_send(_socket, &enetAddress, &buffer, 1);
|
||||
if (sentLength < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Socket::receive() {
|
||||
ENetBuffer buffer;
|
||||
|
||||
char data[4096]; // ENET_PROTOCOL_MAXIMUM_MTU
|
||||
buffer.data = data;
|
||||
buffer.dataLength = sizeof(data);
|
||||
|
||||
ENetAddress _address;
|
||||
|
||||
int receivedLength = enet_socket_receive(_socket, &_address, &buffer, 1);
|
||||
if (receivedLength < 0) {
|
||||
warning("ENet: An error has occurred when receiving data from socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedLength == 0)
|
||||
return false;
|
||||
|
||||
_recentData = Common::String((const char*)data, receivedLength);
|
||||
|
||||
char hostName[50];
|
||||
if (enet_address_get_host_ip(&_address, hostName, sizeof(hostName)) == 0)
|
||||
_recentHost = hostName;
|
||||
else
|
||||
_recentHost = "";
|
||||
_recentPort = _address.port;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::String Socket::getData() {
|
||||
return _recentData;
|
||||
}
|
||||
|
||||
Common::String Socket::getHost() {
|
||||
return _recentHost;
|
||||
}
|
||||
|
||||
int Socket::getPort() {
|
||||
return _recentPort;
|
||||
}
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
110
backends/networking/enet/socket.h
Normal file
110
backends/networking/enet/socket.h
Normal 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_ENET_SOCKET_H
|
||||
#define BACKENDS_NETWORKING_ENET_SOCKET_H
|
||||
|
||||
#ifdef WIN32
|
||||
// Including winsock2.h will result in errors, we have to define
|
||||
// SOCKET ourselves.
|
||||
#include <basetsd.h>
|
||||
typedef UINT_PTR SOCKET;
|
||||
|
||||
typedef SOCKET ENetSocket;
|
||||
#else
|
||||
typedef int ENetSocket;
|
||||
#endif
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
namespace ENet {
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
/**
|
||||
* A representation of a raw UDP socket.
|
||||
* @param socket Contains the socket itself.
|
||||
*/
|
||||
Socket(ENetSocket socket);
|
||||
~Socket();
|
||||
|
||||
/**
|
||||
* Send data to the specified address and port.
|
||||
* @param address Address to send data to.
|
||||
* @param port Port number to send data to.
|
||||
* @param data The data which will be sent.
|
||||
* @retval true on successful.
|
||||
* @retval false on failure.
|
||||
*/
|
||||
bool send(const Common::String &address, int port, const char *data);
|
||||
/**
|
||||
* Checks for received data.
|
||||
* @retval true if received data.
|
||||
* @retval false otherwise.
|
||||
*/
|
||||
bool receive();
|
||||
|
||||
/**
|
||||
* Get the data received from socket.
|
||||
* @return String containing received data.
|
||||
* @note receive() must be called and returned true to get actual data.
|
||||
*/
|
||||
Common::String getData();
|
||||
|
||||
/**
|
||||
* Get the host name of received data.
|
||||
* @return The host name
|
||||
* @note receive() must be called and returned true to get host name.
|
||||
*/
|
||||
Common::String getHost();
|
||||
/**
|
||||
* Get the port number of received data.
|
||||
* @return The port number
|
||||
* @note receive() must be called and returned true to get port.
|
||||
*/
|
||||
int getPort();
|
||||
private:
|
||||
/**
|
||||
* Representation of the UDP socket.
|
||||
*/
|
||||
ENetSocket _socket;
|
||||
/**
|
||||
* String containing the recent data received,
|
||||
* @see getData()
|
||||
*/
|
||||
Common::String _recentData;
|
||||
/**
|
||||
* String containing the last received host.
|
||||
* @see getHost()
|
||||
*/
|
||||
Common::String _recentHost;
|
||||
/**
|
||||
* The last last received port number
|
||||
* @see getPort()
|
||||
*/
|
||||
int _recentPort;
|
||||
};
|
||||
|
||||
} // End of namespace ENet
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
7
backends/networking/enet/source/LICENSE
Normal file
7
backends/networking/enet/source/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2002-2020 Lee Salzman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
15
backends/networking/enet/source/README
Normal file
15
backends/networking/enet/source/README
Normal file
@@ -0,0 +1,15 @@
|
||||
Please visit the ENet homepage at http://enet.bespin.org for installation
|
||||
and usage instructions.
|
||||
|
||||
If you obtained this package from github, the quick description on how to build
|
||||
is:
|
||||
|
||||
# Generate the build system.
|
||||
|
||||
autoreconf -vfi
|
||||
|
||||
# Compile and install the library.
|
||||
|
||||
./configure && make && make install
|
||||
|
||||
|
||||
53
backends/networking/enet/source/callbacks.cpp
Normal file
53
backends/networking/enet/source/callbacks.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
@file callbacks.c
|
||||
@brief ENet callback functions
|
||||
*/
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include "enet.h"
|
||||
|
||||
static ENetCallbacks callbacks = { malloc, free, abort };
|
||||
|
||||
int
|
||||
enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits)
|
||||
{
|
||||
if (version < ENET_VERSION_CREATE (1, 3, 0))
|
||||
return -1;
|
||||
|
||||
if (inits -> malloc != NULL || inits -> free != NULL)
|
||||
{
|
||||
if (inits -> malloc == NULL || inits -> free == NULL)
|
||||
return -1;
|
||||
|
||||
callbacks.malloc = inits -> malloc;
|
||||
callbacks.free = inits -> free;
|
||||
}
|
||||
|
||||
if (inits -> no_memory != NULL)
|
||||
callbacks.no_memory = inits -> no_memory;
|
||||
|
||||
return enet_initialize ();
|
||||
}
|
||||
|
||||
ENetVersion
|
||||
enet_linked_version (void)
|
||||
{
|
||||
return ENET_VERSION;
|
||||
}
|
||||
|
||||
void *
|
||||
enet_malloc (size_t size)
|
||||
{
|
||||
void * memory = callbacks.malloc (size);
|
||||
|
||||
if (memory == NULL)
|
||||
callbacks.no_memory ();
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
void
|
||||
enet_free (void * memory)
|
||||
{
|
||||
callbacks.free (memory);
|
||||
}
|
||||
|
||||
27
backends/networking/enet/source/callbacks.h
Normal file
27
backends/networking/enet/source/callbacks.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
@file callbacks.h
|
||||
@brief ENet callbacks
|
||||
*/
|
||||
#ifndef __ENET_CALLBACKS_H__
|
||||
#define __ENET_CALLBACKS_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct _ENetCallbacks
|
||||
{
|
||||
void * (ENET_CALLBACK * malloc) (size_t size);
|
||||
void (ENET_CALLBACK * free) (void * memory);
|
||||
void (ENET_CALLBACK * no_memory) (void);
|
||||
} ENetCallbacks;
|
||||
|
||||
/** @defgroup callbacks ENet internal callbacks
|
||||
@{
|
||||
@ingroup private
|
||||
*/
|
||||
extern void * enet_malloc (size_t);
|
||||
extern void enet_free (void *);
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* __ENET_CALLBACKS_H__ */
|
||||
|
||||
654
backends/networking/enet/source/compress.cpp
Normal file
654
backends/networking/enet/source/compress.cpp
Normal file
@@ -0,0 +1,654 @@
|
||||
/**
|
||||
@file compress.c
|
||||
@brief An adaptive order-2 PPM range coder
|
||||
*/
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include <string.h>
|
||||
#include "enet.h"
|
||||
|
||||
typedef struct _ENetSymbol
|
||||
{
|
||||
/* binary indexed tree of symbols */
|
||||
enet_uint8 value;
|
||||
enet_uint8 count;
|
||||
enet_uint16 under;
|
||||
enet_uint16 left, right;
|
||||
|
||||
/* context defined by this symbol */
|
||||
enet_uint16 symbols;
|
||||
enet_uint16 escapes;
|
||||
enet_uint16 total;
|
||||
enet_uint16 parent;
|
||||
} ENetSymbol;
|
||||
|
||||
/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */
|
||||
enum
|
||||
{
|
||||
ENET_RANGE_CODER_TOP = 1<<24,
|
||||
ENET_RANGE_CODER_BOTTOM = 1<<16,
|
||||
|
||||
ENET_CONTEXT_SYMBOL_DELTA = 3,
|
||||
ENET_CONTEXT_SYMBOL_MINIMUM = 1,
|
||||
ENET_CONTEXT_ESCAPE_MINIMUM = 1,
|
||||
|
||||
ENET_SUBCONTEXT_ORDER = 2,
|
||||
ENET_SUBCONTEXT_SYMBOL_DELTA = 2,
|
||||
ENET_SUBCONTEXT_ESCAPE_DELTA = 5
|
||||
};
|
||||
|
||||
/* context exclusion roughly halves compression speed, so disable for now */
|
||||
#undef ENET_CONTEXT_EXCLUSION
|
||||
|
||||
typedef struct _ENetRangeCoder
|
||||
{
|
||||
/* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */
|
||||
ENetSymbol symbols[4096];
|
||||
} ENetRangeCoder;
|
||||
|
||||
void *
|
||||
enet_range_coder_create (void)
|
||||
{
|
||||
ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder));
|
||||
if (rangeCoder == NULL)
|
||||
return NULL;
|
||||
|
||||
return rangeCoder;
|
||||
}
|
||||
|
||||
void
|
||||
enet_range_coder_destroy (void * context)
|
||||
{
|
||||
ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
|
||||
if (rangeCoder == NULL)
|
||||
return;
|
||||
|
||||
enet_free (rangeCoder);
|
||||
}
|
||||
|
||||
#define ENET_SYMBOL_CREATE(symbol, value_, count_) \
|
||||
{ \
|
||||
symbol = & rangeCoder -> symbols [nextSymbol ++]; \
|
||||
symbol -> value = value_; \
|
||||
symbol -> count = count_; \
|
||||
symbol -> under = count_; \
|
||||
symbol -> left = 0; \
|
||||
symbol -> right = 0; \
|
||||
symbol -> symbols = 0; \
|
||||
symbol -> escapes = 0; \
|
||||
symbol -> total = 0; \
|
||||
symbol -> parent = 0; \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \
|
||||
{ \
|
||||
ENET_SYMBOL_CREATE (context, 0, 0); \
|
||||
(context) -> escapes = escapes_; \
|
||||
(context) -> total = escapes_ + 256*minimum; \
|
||||
(context) -> symbols = 0; \
|
||||
}
|
||||
|
||||
static enet_uint16
|
||||
enet_symbol_rescale (ENetSymbol * symbol)
|
||||
{
|
||||
enet_uint16 total = 0;
|
||||
for (;;)
|
||||
{
|
||||
symbol -> count -= symbol->count >> 1;
|
||||
symbol -> under = symbol -> count;
|
||||
if (symbol -> left)
|
||||
symbol -> under += enet_symbol_rescale (symbol + symbol -> left);
|
||||
total += symbol -> under;
|
||||
if (! symbol -> right) break;
|
||||
symbol += symbol -> right;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_RESCALE(context, minimum) \
|
||||
{ \
|
||||
(context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \
|
||||
(context) -> escapes -= (context) -> escapes >> 1; \
|
||||
(context) -> total += (context) -> escapes + 256*minimum; \
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_OUTPUT(value) \
|
||||
{ \
|
||||
if (outData >= outEnd) \
|
||||
return 0; \
|
||||
* outData ++ = value; \
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_ENCODE(under, count, total) \
|
||||
{ \
|
||||
encodeRange /= (total); \
|
||||
encodeLow += (under) * encodeRange; \
|
||||
encodeRange *= (count); \
|
||||
for (;;) \
|
||||
{ \
|
||||
if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \
|
||||
{ \
|
||||
if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \
|
||||
encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \
|
||||
} \
|
||||
ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \
|
||||
encodeRange <<= 8; \
|
||||
encodeLow <<= 8; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_FLUSH \
|
||||
{ \
|
||||
while (encodeLow) \
|
||||
{ \
|
||||
ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \
|
||||
encodeLow <<= 8; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_FREE_SYMBOLS \
|
||||
{ \
|
||||
if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \
|
||||
{ \
|
||||
nextSymbol = 0; \
|
||||
ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \
|
||||
predicted = 0; \
|
||||
order = 0; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \
|
||||
{ \
|
||||
under_ = value*minimum; \
|
||||
count_ = minimum; \
|
||||
if (! (context) -> symbols) \
|
||||
{ \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
(context) -> symbols = symbol_ - (context); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
ENetSymbol * node = (context) + (context) -> symbols; \
|
||||
for (;;) \
|
||||
{ \
|
||||
if (value_ < node -> value) \
|
||||
{ \
|
||||
node -> under += update; \
|
||||
if (node -> left) { node += node -> left; continue; } \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
node -> left = symbol_ - node; \
|
||||
} \
|
||||
else \
|
||||
if (value_ > node -> value) \
|
||||
{ \
|
||||
under_ += node -> under; \
|
||||
if (node -> right) { node += node -> right; continue; } \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
node -> right = symbol_ - node; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
count_ += node -> count; \
|
||||
under_ += node -> under - node -> count; \
|
||||
node -> under += update; \
|
||||
node -> count += update; \
|
||||
symbol_ = node; \
|
||||
} \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
#define ENET_CONTEXT_WALK(context, body) \
|
||||
{ \
|
||||
const ENetSymbol * node = (context) + (context) -> symbols; \
|
||||
const ENetSymbol * stack [256]; \
|
||||
size_t stackSize = 0; \
|
||||
while (node -> left) \
|
||||
{ \
|
||||
stack [stackSize ++] = node; \
|
||||
node += node -> left; \
|
||||
} \
|
||||
for (;;) \
|
||||
{ \
|
||||
body; \
|
||||
if (node -> right) \
|
||||
{ \
|
||||
node += node -> right; \
|
||||
while (node -> left) \
|
||||
{ \
|
||||
stack [stackSize ++] = node; \
|
||||
node += node -> left; \
|
||||
} \
|
||||
} \
|
||||
else \
|
||||
if (stackSize <= 0) \
|
||||
break; \
|
||||
else \
|
||||
node = stack [-- stackSize]; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \
|
||||
ENET_CONTEXT_WALK(context, { \
|
||||
if (node -> value != value_) \
|
||||
{ \
|
||||
enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \
|
||||
if (node -> value < value_) \
|
||||
under -= parentCount; \
|
||||
total -= parentCount; \
|
||||
} \
|
||||
})
|
||||
#endif
|
||||
|
||||
size_t
|
||||
enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit)
|
||||
{
|
||||
ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
|
||||
enet_uint8 * outStart = outData, * outEnd = & outData [outLimit];
|
||||
const enet_uint8 * inData, * inEnd;
|
||||
enet_uint32 encodeLow = 0, encodeRange = ~0U;
|
||||
ENetSymbol * root;
|
||||
enet_uint16 predicted = 0;
|
||||
size_t order = 0, nextSymbol = 0;
|
||||
|
||||
if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0)
|
||||
return 0;
|
||||
|
||||
inData = (const enet_uint8 *) inBuffers -> data;
|
||||
inEnd = & inData [inBuffers -> dataLength];
|
||||
inBuffers ++;
|
||||
inBufferCount --;
|
||||
|
||||
ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ENetSymbol * subcontext, * symbol;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
const ENetSymbol * childContext = & emptyContext;
|
||||
#endif
|
||||
enet_uint8 value;
|
||||
enet_uint16 count, under, * parent = & predicted, total;
|
||||
if (inData >= inEnd)
|
||||
{
|
||||
if (inBufferCount <= 0)
|
||||
break;
|
||||
inData = (const enet_uint8 *) inBuffers -> data;
|
||||
inEnd = & inData [inBuffers -> dataLength];
|
||||
inBuffers ++;
|
||||
inBufferCount --;
|
||||
}
|
||||
value = * inData ++;
|
||||
|
||||
for (subcontext = & rangeCoder -> symbols [predicted];
|
||||
subcontext != root;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
childContext = subcontext,
|
||||
#endif
|
||||
subcontext = & rangeCoder -> symbols [subcontext -> parent])
|
||||
{
|
||||
ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0);
|
||||
* parent = symbol - rangeCoder -> symbols;
|
||||
parent = & symbol -> parent;
|
||||
total = subcontext -> total;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA)
|
||||
ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0);
|
||||
#endif
|
||||
if (count > 0)
|
||||
{
|
||||
ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (subcontext -> escapes > 0 && subcontext -> escapes < total)
|
||||
ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total);
|
||||
subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA;
|
||||
subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA;
|
||||
}
|
||||
subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA;
|
||||
if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
|
||||
ENET_CONTEXT_RESCALE (subcontext, 0);
|
||||
if (count > 0) goto nextInput;
|
||||
}
|
||||
|
||||
ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
* parent = symbol - rangeCoder -> symbols;
|
||||
parent = & symbol -> parent;
|
||||
total = root -> total;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA)
|
||||
ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
#endif
|
||||
ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total);
|
||||
root -> total += ENET_CONTEXT_SYMBOL_DELTA;
|
||||
if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
|
||||
ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
|
||||
nextInput:
|
||||
if (order >= ENET_SUBCONTEXT_ORDER)
|
||||
predicted = rangeCoder -> symbols [predicted].parent;
|
||||
else
|
||||
order ++;
|
||||
ENET_RANGE_CODER_FREE_SYMBOLS;
|
||||
}
|
||||
|
||||
ENET_RANGE_CODER_FLUSH;
|
||||
|
||||
return (size_t) (outData - outStart);
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_SEED \
|
||||
{ \
|
||||
if (inData < inEnd) decodeCode |= * inData ++ << 24; \
|
||||
if (inData < inEnd) decodeCode |= * inData ++ << 16; \
|
||||
if (inData < inEnd) decodeCode |= * inData ++ << 8; \
|
||||
if (inData < inEnd) decodeCode |= * inData ++; \
|
||||
}
|
||||
|
||||
#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total)))
|
||||
|
||||
#define ENET_RANGE_CODER_DECODE(under, count, total) \
|
||||
{ \
|
||||
decodeLow += (under) * decodeRange; \
|
||||
decodeRange *= (count); \
|
||||
for (;;) \
|
||||
{ \
|
||||
if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \
|
||||
{ \
|
||||
if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \
|
||||
decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \
|
||||
} \
|
||||
decodeCode <<= 8; \
|
||||
if (inData < inEnd) \
|
||||
decodeCode |= * inData ++; \
|
||||
decodeRange <<= 8; \
|
||||
decodeLow <<= 8; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \
|
||||
{ \
|
||||
under_ = 0; \
|
||||
count_ = minimum; \
|
||||
if (! (context) -> symbols) \
|
||||
{ \
|
||||
createRoot; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
ENetSymbol * node = (context) + (context) -> symbols; \
|
||||
for (;;) \
|
||||
{ \
|
||||
enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \
|
||||
visitNode; \
|
||||
if (code >= after) \
|
||||
{ \
|
||||
under_ += node -> under; \
|
||||
if (node -> right) { node += node -> right; continue; } \
|
||||
createRight; \
|
||||
} \
|
||||
else \
|
||||
if (code < after - before) \
|
||||
{ \
|
||||
node -> under += update; \
|
||||
if (node -> left) { node += node -> left; continue; } \
|
||||
createLeft; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
value_ = node -> value; \
|
||||
count_ += node -> count; \
|
||||
under_ = after - before; \
|
||||
node -> under += update; \
|
||||
node -> count += update; \
|
||||
symbol_ = node; \
|
||||
} \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \
|
||||
ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0)
|
||||
|
||||
#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \
|
||||
ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \
|
||||
{ \
|
||||
value_ = code / minimum; \
|
||||
under_ = code - code%minimum; \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
(context) -> symbols = symbol_ - (context); \
|
||||
}, \
|
||||
exclude (node -> value, after, before), \
|
||||
{ \
|
||||
value_ = node->value + 1 + (code - after)/minimum; \
|
||||
under_ = code - (code - after)%minimum; \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
node -> right = symbol_ - node; \
|
||||
}, \
|
||||
{ \
|
||||
value_ = node->value - 1 - (after - before - code - 1)/minimum; \
|
||||
under_ = code - (after - before - code - 1)%minimum; \
|
||||
ENET_SYMBOL_CREATE (symbol_, value_, update); \
|
||||
node -> left = symbol_ - node; \
|
||||
}) \
|
||||
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
typedef struct _ENetExclude
|
||||
{
|
||||
enet_uint8 value;
|
||||
enet_uint16 under;
|
||||
} ENetExclude;
|
||||
|
||||
#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \
|
||||
{ \
|
||||
enet_uint16 under = 0; \
|
||||
nextExclude = excludes; \
|
||||
ENET_CONTEXT_WALK (context, { \
|
||||
under += rangeCoder -> symbols [node -> parent].count + minimum; \
|
||||
nextExclude -> value = node -> value; \
|
||||
nextExclude -> under = under; \
|
||||
nextExclude ++; \
|
||||
}); \
|
||||
total -= under; \
|
||||
}
|
||||
|
||||
#define ENET_CONTEXT_EXCLUDED(value_, after, before) \
|
||||
{ \
|
||||
size_t low = 0, high = nextExclude - excludes; \
|
||||
for(;;) \
|
||||
{ \
|
||||
size_t mid = (low + high) >> 1; \
|
||||
const ENetExclude * exclude = & excludes [mid]; \
|
||||
if (value_ < exclude -> value) \
|
||||
{ \
|
||||
if (low + 1 < high) \
|
||||
{ \
|
||||
high = mid; \
|
||||
continue; \
|
||||
} \
|
||||
if (exclude > excludes) \
|
||||
after -= exclude [-1].under; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
if (value_ > exclude -> value) \
|
||||
{ \
|
||||
if (low + 1 < high) \
|
||||
{ \
|
||||
low = mid; \
|
||||
continue; \
|
||||
} \
|
||||
} \
|
||||
else \
|
||||
before = 0; \
|
||||
after -= exclude -> under; \
|
||||
} \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before)
|
||||
|
||||
size_t
|
||||
enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit)
|
||||
{
|
||||
ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context;
|
||||
enet_uint8 * outStart = outData, * outEnd = & outData [outLimit];
|
||||
const enet_uint8 * inEnd = & inData [inLimit];
|
||||
enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0U;
|
||||
ENetSymbol * root;
|
||||
enet_uint16 predicted = 0;
|
||||
size_t order = 0, nextSymbol = 0;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
ENetExclude excludes [256];
|
||||
ENetExclude * nextExclude = excludes;
|
||||
#endif
|
||||
|
||||
if (rangeCoder == NULL || inLimit <= 0)
|
||||
return 0;
|
||||
|
||||
ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
|
||||
ENET_RANGE_CODER_SEED;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ENetSymbol * subcontext, * symbol, * patch;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
const ENetSymbol * childContext = & emptyContext;
|
||||
#endif
|
||||
enet_uint8 value = 0;
|
||||
enet_uint16 code, under, count, bottom, * parent = & predicted, total;
|
||||
|
||||
for (subcontext = & rangeCoder -> symbols [predicted];
|
||||
subcontext != root;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
childContext = subcontext,
|
||||
#endif
|
||||
subcontext = & rangeCoder -> symbols [subcontext -> parent])
|
||||
{
|
||||
if (subcontext -> escapes <= 0)
|
||||
continue;
|
||||
total = subcontext -> total;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > 0)
|
||||
ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0);
|
||||
#endif
|
||||
if (subcontext -> escapes >= total)
|
||||
continue;
|
||||
code = ENET_RANGE_CODER_READ (total);
|
||||
if (code < subcontext -> escapes)
|
||||
{
|
||||
ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total);
|
||||
continue;
|
||||
}
|
||||
code -= subcontext -> escapes;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > 0)
|
||||
{
|
||||
ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED);
|
||||
}
|
||||
bottom = symbol - rangeCoder -> symbols;
|
||||
ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total);
|
||||
subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA;
|
||||
if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
|
||||
ENET_CONTEXT_RESCALE (subcontext, 0);
|
||||
goto patchContexts;
|
||||
}
|
||||
|
||||
total = root -> total;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > 0)
|
||||
ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
#endif
|
||||
code = ENET_RANGE_CODER_READ (total);
|
||||
if (code < root -> escapes)
|
||||
{
|
||||
ENET_RANGE_CODER_DECODE (0, root -> escapes, total);
|
||||
break;
|
||||
}
|
||||
code -= root -> escapes;
|
||||
#ifdef ENET_CONTEXT_EXCLUSION
|
||||
if (childContext -> total > 0)
|
||||
{
|
||||
ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED);
|
||||
}
|
||||
bottom = symbol - rangeCoder -> symbols;
|
||||
ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total);
|
||||
root -> total += ENET_CONTEXT_SYMBOL_DELTA;
|
||||
if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
|
||||
ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM);
|
||||
|
||||
patchContexts:
|
||||
for (patch = & rangeCoder -> symbols [predicted];
|
||||
patch != subcontext;
|
||||
patch = & rangeCoder -> symbols [patch -> parent])
|
||||
{
|
||||
ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0);
|
||||
* parent = symbol - rangeCoder -> symbols;
|
||||
parent = & symbol -> parent;
|
||||
if (count <= 0)
|
||||
{
|
||||
patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA;
|
||||
patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA;
|
||||
}
|
||||
patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA;
|
||||
if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100)
|
||||
ENET_CONTEXT_RESCALE (patch, 0);
|
||||
}
|
||||
* parent = bottom;
|
||||
|
||||
ENET_RANGE_CODER_OUTPUT (value);
|
||||
|
||||
if (order >= ENET_SUBCONTEXT_ORDER)
|
||||
predicted = rangeCoder -> symbols [predicted].parent;
|
||||
else
|
||||
order ++;
|
||||
ENET_RANGE_CODER_FREE_SYMBOLS;
|
||||
}
|
||||
|
||||
return (size_t) (outData - outStart);
|
||||
}
|
||||
|
||||
/** @defgroup host ENet host functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/** Sets the packet compressor the host should use to the default range coder.
|
||||
@param host host to enable the range coder for
|
||||
@returns 0 on success, < 0 on failure
|
||||
*/
|
||||
int
|
||||
enet_host_compress_with_range_coder (ENetHost * host)
|
||||
{
|
||||
ENetCompressor compressor;
|
||||
memset (& compressor, 0, sizeof (compressor));
|
||||
compressor.context = enet_range_coder_create();
|
||||
if (compressor.context == NULL)
|
||||
return -1;
|
||||
compressor.compress = enet_range_coder_compress;
|
||||
compressor.decompress = enet_range_coder_decompress;
|
||||
compressor.destroy = enet_range_coder_destroy;
|
||||
enet_host_compress (host, & compressor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
612
backends/networking/enet/source/enet.h
Normal file
612
backends/networking/enet/source/enet.h
Normal file
@@ -0,0 +1,612 @@
|
||||
/**
|
||||
@file enet.h
|
||||
@brief ENet public header file
|
||||
*/
|
||||
#ifndef __ENET_ENET_H__
|
||||
#define __ENET_ENET_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#else
|
||||
#include "unix.h"
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
#include "protocol.h"
|
||||
#include "list.h"
|
||||
#include "callbacks.h"
|
||||
|
||||
#define ENET_VERSION_MAJOR 1
|
||||
#define ENET_VERSION_MINOR 3
|
||||
#define ENET_VERSION_PATCH 17
|
||||
#define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch))
|
||||
#define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF)
|
||||
#define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF)
|
||||
#define ENET_VERSION_GET_PATCH(version) ((version)&0xFF)
|
||||
#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH)
|
||||
|
||||
typedef enet_uint32 ENetVersion;
|
||||
|
||||
struct _ENetHost;
|
||||
struct _ENetEvent;
|
||||
struct _ENetPacket;
|
||||
|
||||
typedef enum _ENetSocketType
|
||||
{
|
||||
ENET_SOCKET_TYPE_STREAM = 1,
|
||||
ENET_SOCKET_TYPE_DATAGRAM = 2
|
||||
} ENetSocketType;
|
||||
|
||||
typedef enum _ENetSocketWait
|
||||
{
|
||||
ENET_SOCKET_WAIT_NONE = 0,
|
||||
ENET_SOCKET_WAIT_SEND = (1 << 0),
|
||||
ENET_SOCKET_WAIT_RECEIVE = (1 << 1),
|
||||
ENET_SOCKET_WAIT_INTERRUPT = (1 << 2)
|
||||
} ENetSocketWait;
|
||||
|
||||
typedef enum _ENetSocketOption
|
||||
{
|
||||
ENET_SOCKOPT_NONBLOCK = 1,
|
||||
ENET_SOCKOPT_BROADCAST = 2,
|
||||
ENET_SOCKOPT_RCVBUF = 3,
|
||||
ENET_SOCKOPT_SNDBUF = 4,
|
||||
ENET_SOCKOPT_REUSEADDR = 5,
|
||||
ENET_SOCKOPT_RCVTIMEO = 6,
|
||||
ENET_SOCKOPT_SNDTIMEO = 7,
|
||||
ENET_SOCKOPT_ERROR = 8,
|
||||
ENET_SOCKOPT_NODELAY = 9
|
||||
} ENetSocketOption;
|
||||
|
||||
typedef enum _ENetSocketShutdown
|
||||
{
|
||||
ENET_SOCKET_SHUTDOWN_READ = 0,
|
||||
ENET_SOCKET_SHUTDOWN_WRITE = 1,
|
||||
ENET_SOCKET_SHUTDOWN_READ_WRITE = 2
|
||||
} ENetSocketShutdown;
|
||||
|
||||
#define ENET_HOST_ANY 0
|
||||
#define ENET_HOST_BROADCAST 0xFFFFFFFFU
|
||||
#define ENET_PORT_ANY 0
|
||||
|
||||
/**
|
||||
* Portable internet address structure.
|
||||
*
|
||||
* The host must be specified in network byte-order, and the port must be in host
|
||||
* byte-order. The constant ENET_HOST_ANY may be used to specify the default
|
||||
* server host. The constant ENET_HOST_BROADCAST may be used to specify the
|
||||
* broadcast address (255.255.255.255). This makes sense for enet_host_connect,
|
||||
* but not for enet_host_create. Once a server responds to a broadcast, the
|
||||
* address is updated from ENET_HOST_BROADCAST to the server's actual IP address.
|
||||
*/
|
||||
typedef struct _ENetAddress
|
||||
{
|
||||
enet_uint32 host;
|
||||
enet_uint16 port;
|
||||
} ENetAddress;
|
||||
|
||||
/**
|
||||
* Packet flag bit constants.
|
||||
*
|
||||
* The host must be specified in network byte-order, and the port must be in
|
||||
* host byte-order. The constant ENET_HOST_ANY may be used to specify the
|
||||
* default server host.
|
||||
|
||||
@sa ENetPacket
|
||||
*/
|
||||
typedef enum _ENetPacketFlag
|
||||
{
|
||||
/** packet must be received by the target peer and resend attempts should be
|
||||
* made until the packet is delivered */
|
||||
ENET_PACKET_FLAG_RELIABLE = (1 << 0),
|
||||
/** packet will not be sequenced with other packets
|
||||
* not supported for reliable packets
|
||||
*/
|
||||
ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1),
|
||||
/** packet will not allocate data, and user must supply it instead */
|
||||
ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2),
|
||||
/** packet will be fragmented using unreliable (instead of reliable) sends
|
||||
* if it exceeds the MTU */
|
||||
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3),
|
||||
|
||||
/** whether the packet has been sent from all queues it has been entered into */
|
||||
ENET_PACKET_FLAG_SENT = (1<<8)
|
||||
} ENetPacketFlag;
|
||||
|
||||
typedef void (ENET_CALLBACK * ENetPacketFreeCallback) (struct _ENetPacket *);
|
||||
|
||||
/**
|
||||
* ENet packet structure.
|
||||
*
|
||||
* An ENet data packet that may be sent to or received from a peer. The shown
|
||||
* fields should only be read and never modified. The data field contains the
|
||||
* allocated data for the packet. The dataLength fields specifies the length
|
||||
* of the allocated data. The flags field is either 0 (specifying no flags),
|
||||
* or a bitwise-or of any combination of the following flags:
|
||||
*
|
||||
* ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer
|
||||
* and resend attempts should be made until the packet is delivered
|
||||
*
|
||||
* ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets
|
||||
* (not supported for reliable packets)
|
||||
*
|
||||
* ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead
|
||||
*
|
||||
* ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - packet will be fragmented using unreliable
|
||||
* (instead of reliable) sends if it exceeds the MTU
|
||||
*
|
||||
* ENET_PACKET_FLAG_SENT - whether the packet has been sent from all queues it has been entered into
|
||||
@sa ENetPacketFlag
|
||||
*/
|
||||
typedef struct _ENetPacket
|
||||
{
|
||||
size_t referenceCount; /**< internal use only */
|
||||
enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */
|
||||
enet_uint8 * data; /**< allocated data for packet */
|
||||
size_t dataLength; /**< length of data */
|
||||
ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */
|
||||
void * userData; /**< application private data, may be freely modified */
|
||||
} ENetPacket;
|
||||
|
||||
typedef struct _ENetAcknowledgement
|
||||
{
|
||||
ENetListNode acknowledgementList;
|
||||
enet_uint32 sentTime;
|
||||
ENetProtocol command;
|
||||
} ENetAcknowledgement;
|
||||
|
||||
typedef struct _ENetOutgoingCommand
|
||||
{
|
||||
ENetListNode outgoingCommandList;
|
||||
enet_uint16 reliableSequenceNumber;
|
||||
enet_uint16 unreliableSequenceNumber;
|
||||
enet_uint32 sentTime;
|
||||
enet_uint32 roundTripTimeout;
|
||||
enet_uint32 roundTripTimeoutLimit;
|
||||
enet_uint32 fragmentOffset;
|
||||
enet_uint16 fragmentLength;
|
||||
enet_uint16 sendAttempts;
|
||||
ENetProtocol command;
|
||||
ENetPacket * packet;
|
||||
} ENetOutgoingCommand;
|
||||
|
||||
typedef struct _ENetIncomingCommand
|
||||
{
|
||||
ENetListNode incomingCommandList;
|
||||
enet_uint16 reliableSequenceNumber;
|
||||
enet_uint16 unreliableSequenceNumber;
|
||||
ENetProtocol command;
|
||||
enet_uint32 fragmentCount;
|
||||
enet_uint32 fragmentsRemaining;
|
||||
enet_uint32 * fragments;
|
||||
ENetPacket * packet;
|
||||
} ENetIncomingCommand;
|
||||
|
||||
typedef enum _ENetPeerState
|
||||
{
|
||||
ENET_PEER_STATE_DISCONNECTED = 0,
|
||||
ENET_PEER_STATE_CONNECTING = 1,
|
||||
ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2,
|
||||
ENET_PEER_STATE_CONNECTION_PENDING = 3,
|
||||
ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4,
|
||||
ENET_PEER_STATE_CONNECTED = 5,
|
||||
ENET_PEER_STATE_DISCONNECT_LATER = 6,
|
||||
ENET_PEER_STATE_DISCONNECTING = 7,
|
||||
ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8,
|
||||
ENET_PEER_STATE_ZOMBIE = 9
|
||||
} ENetPeerState;
|
||||
|
||||
#ifndef ENET_BUFFER_MAXIMUM
|
||||
#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS)
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024,
|
||||
ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024,
|
||||
ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000,
|
||||
ENET_HOST_DEFAULT_MTU = 1400,
|
||||
ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024,
|
||||
ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024,
|
||||
|
||||
ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500,
|
||||
ENET_PEER_DEFAULT_PACKET_THROTTLE = 32,
|
||||
ENET_PEER_PACKET_THROTTLE_SCALE = 32,
|
||||
ENET_PEER_PACKET_THROTTLE_COUNTER = 7,
|
||||
ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2,
|
||||
ENET_PEER_PACKET_THROTTLE_DECELERATION = 2,
|
||||
ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000,
|
||||
ENET_PEER_PACKET_LOSS_SCALE = (1 << 16),
|
||||
ENET_PEER_PACKET_LOSS_INTERVAL = 10000,
|
||||
ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024,
|
||||
ENET_PEER_TIMEOUT_LIMIT = 32,
|
||||
ENET_PEER_TIMEOUT_MINIMUM = 5000,
|
||||
ENET_PEER_TIMEOUT_MAXIMUM = 30000,
|
||||
ENET_PEER_PING_INTERVAL = 500,
|
||||
ENET_PEER_UNSEQUENCED_WINDOWS = 64,
|
||||
ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024,
|
||||
ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32,
|
||||
ENET_PEER_RELIABLE_WINDOWS = 16,
|
||||
ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000,
|
||||
ENET_PEER_FREE_RELIABLE_WINDOWS = 8
|
||||
};
|
||||
|
||||
typedef struct _ENetChannel
|
||||
{
|
||||
enet_uint16 outgoingReliableSequenceNumber;
|
||||
enet_uint16 outgoingUnreliableSequenceNumber;
|
||||
enet_uint16 usedReliableWindows;
|
||||
enet_uint16 reliableWindows [ENET_PEER_RELIABLE_WINDOWS];
|
||||
enet_uint16 incomingReliableSequenceNumber;
|
||||
enet_uint16 incomingUnreliableSequenceNumber;
|
||||
ENetList incomingReliableCommands;
|
||||
ENetList incomingUnreliableCommands;
|
||||
} ENetChannel;
|
||||
|
||||
typedef enum _ENetPeerFlag
|
||||
{
|
||||
ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0)
|
||||
} ENetPeerFlag;
|
||||
|
||||
/**
|
||||
* An ENet peer which data packets may be sent or received from.
|
||||
*
|
||||
* No fields should be modified unless otherwise specified.
|
||||
*/
|
||||
typedef struct _ENetPeer
|
||||
{
|
||||
ENetListNode dispatchList;
|
||||
struct _ENetHost * host;
|
||||
enet_uint16 outgoingPeerID;
|
||||
enet_uint16 incomingPeerID;
|
||||
enet_uint32 connectID;
|
||||
enet_uint8 outgoingSessionID;
|
||||
enet_uint8 incomingSessionID;
|
||||
ENetAddress address; /**< Internet address of the peer */
|
||||
void * data; /**< Application private data, may be freely modified */
|
||||
ENetPeerState state;
|
||||
ENetChannel * channels;
|
||||
size_t channelCount; /**< Number of channels allocated for communication with peer */
|
||||
enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */
|
||||
enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */
|
||||
enet_uint32 incomingBandwidthThrottleEpoch;
|
||||
enet_uint32 outgoingBandwidthThrottleEpoch;
|
||||
enet_uint32 incomingDataTotal;
|
||||
enet_uint32 outgoingDataTotal;
|
||||
enet_uint32 lastSendTime;
|
||||
enet_uint32 lastReceiveTime;
|
||||
enet_uint32 nextTimeout;
|
||||
enet_uint32 earliestTimeout;
|
||||
enet_uint32 packetLossEpoch;
|
||||
enet_uint32 packetsSent;
|
||||
enet_uint32 packetsLost;
|
||||
enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */
|
||||
enet_uint32 packetLossVariance;
|
||||
enet_uint32 packetThrottle;
|
||||
enet_uint32 packetThrottleLimit;
|
||||
enet_uint32 packetThrottleCounter;
|
||||
enet_uint32 packetThrottleEpoch;
|
||||
enet_uint32 packetThrottleAcceleration;
|
||||
enet_uint32 packetThrottleDeceleration;
|
||||
enet_uint32 packetThrottleInterval;
|
||||
enet_uint32 pingInterval;
|
||||
enet_uint32 timeoutLimit;
|
||||
enet_uint32 timeoutMinimum;
|
||||
enet_uint32 timeoutMaximum;
|
||||
enet_uint32 lastRoundTripTime;
|
||||
enet_uint32 lowestRoundTripTime;
|
||||
enet_uint32 lastRoundTripTimeVariance;
|
||||
enet_uint32 highestRoundTripTimeVariance;
|
||||
enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */
|
||||
enet_uint32 roundTripTimeVariance;
|
||||
enet_uint32 mtu;
|
||||
enet_uint32 windowSize;
|
||||
enet_uint32 reliableDataInTransit;
|
||||
enet_uint16 outgoingReliableSequenceNumber;
|
||||
ENetList acknowledgements;
|
||||
ENetList sentReliableCommands;
|
||||
ENetList sentUnreliableCommands;
|
||||
ENetList outgoingCommands;
|
||||
ENetList dispatchedCommands;
|
||||
enet_uint16 flags;
|
||||
enet_uint16 reserved;
|
||||
enet_uint16 incomingUnsequencedGroup;
|
||||
enet_uint16 outgoingUnsequencedGroup;
|
||||
enet_uint32 unsequencedWindow [ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32];
|
||||
enet_uint32 eventData;
|
||||
size_t totalWaitingData;
|
||||
} ENetPeer;
|
||||
|
||||
/** An ENet packet compressor for compressing UDP packets before socket sends or receives.
|
||||
*/
|
||||
typedef struct _ENetCompressor
|
||||
{
|
||||
/** Context data for the compressor. Must be non-NULL. */
|
||||
void * context;
|
||||
/** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */
|
||||
size_t (ENET_CALLBACK * compress) (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit);
|
||||
/** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */
|
||||
size_t (ENET_CALLBACK * decompress) (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit);
|
||||
/** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */
|
||||
void (ENET_CALLBACK * destroy) (void * context);
|
||||
} ENetCompressor;
|
||||
|
||||
/** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */
|
||||
typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback) (const ENetBuffer * buffers, size_t bufferCount);
|
||||
|
||||
/** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */
|
||||
typedef int (ENET_CALLBACK * ENetInterceptCallback) (struct _ENetHost * host, struct _ENetEvent * event);
|
||||
|
||||
/** An ENet host for communicating with peers.
|
||||
*
|
||||
* No fields should be modified unless otherwise stated.
|
||||
|
||||
@sa enet_host_create()
|
||||
@sa enet_host_destroy()
|
||||
@sa enet_host_connect()
|
||||
@sa enet_host_service()
|
||||
@sa enet_host_flush()
|
||||
@sa enet_host_broadcast()
|
||||
@sa enet_host_compress()
|
||||
@sa enet_host_compress_with_range_coder()
|
||||
@sa enet_host_channel_limit()
|
||||
@sa enet_host_bandwidth_limit()
|
||||
@sa enet_host_bandwidth_throttle()
|
||||
*/
|
||||
typedef struct _ENetHost
|
||||
{
|
||||
ENetSocket socket;
|
||||
ENetAddress address; /**< Internet address of the host */
|
||||
enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */
|
||||
enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */
|
||||
enet_uint32 bandwidthThrottleEpoch;
|
||||
enet_uint32 mtu;
|
||||
enet_uint32 randomSeed;
|
||||
int recalculateBandwidthLimits;
|
||||
ENetPeer * peers; /**< array of peers allocated for this host */
|
||||
size_t peerCount; /**< number of peers allocated for this host */
|
||||
size_t channelLimit; /**< maximum number of channels allowed for connected peers */
|
||||
enet_uint32 serviceTime;
|
||||
ENetList dispatchQueue;
|
||||
int continueSending;
|
||||
size_t packetSize;
|
||||
enet_uint16 headerFlags;
|
||||
ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS];
|
||||
size_t commandCount;
|
||||
ENetBuffer buffers [ENET_BUFFER_MAXIMUM];
|
||||
size_t bufferCount;
|
||||
ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */
|
||||
ENetCompressor compressor;
|
||||
enet_uint8 packetData [2][ENET_PROTOCOL_MAXIMUM_MTU];
|
||||
ENetAddress receivedAddress;
|
||||
enet_uint8 * receivedData;
|
||||
size_t receivedDataLength;
|
||||
enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */
|
||||
enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */
|
||||
enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */
|
||||
enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */
|
||||
ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */
|
||||
size_t connectedPeers;
|
||||
size_t bandwidthLimitedPeers;
|
||||
size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */
|
||||
size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */
|
||||
size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */
|
||||
} ENetHost;
|
||||
|
||||
/**
|
||||
* An ENet event type, as specified in @ref ENetEvent.
|
||||
*/
|
||||
typedef enum _ENetEventType
|
||||
{
|
||||
/** no event occurred within the specified time limit */
|
||||
ENET_EVENT_TYPE_NONE = 0,
|
||||
|
||||
/** a connection request initiated by enet_host_connect has completed.
|
||||
* The peer field contains the peer which successfully connected.
|
||||
*/
|
||||
ENET_EVENT_TYPE_CONNECT = 1,
|
||||
|
||||
/** a peer has disconnected. This event is generated on a successful
|
||||
* completion of a disconnect initiated by enet_peer_disconnect, if
|
||||
* a peer has timed out, or if a connection request intialized by
|
||||
* enet_host_connect has timed out. The peer field contains the peer
|
||||
* which disconnected. The data field contains user supplied data
|
||||
* describing the disconnection, or 0, if none is available.
|
||||
*/
|
||||
ENET_EVENT_TYPE_DISCONNECT = 2,
|
||||
|
||||
/** a packet has been received from a peer. The peer field specifies the
|
||||
* peer which sent the packet. The channelID field specifies the channel
|
||||
* number upon which the packet was received. The packet field contains
|
||||
* the packet that was received; this packet must be destroyed with
|
||||
* enet_packet_destroy after use.
|
||||
*/
|
||||
ENET_EVENT_TYPE_RECEIVE = 3
|
||||
} ENetEventType;
|
||||
|
||||
/**
|
||||
* An ENet event as returned by enet_host_service().
|
||||
|
||||
@sa enet_host_service
|
||||
*/
|
||||
typedef struct _ENetEvent
|
||||
{
|
||||
ENetEventType type; /**< type of the event */
|
||||
ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */
|
||||
enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */
|
||||
enet_uint32 data; /**< data associated with the event, if appropriate */
|
||||
ENetPacket * packet; /**< packet associated with the event, if appropriate */
|
||||
} ENetEvent;
|
||||
|
||||
/** @defgroup global ENet global functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/**
|
||||
Initializes ENet globally. Must be called prior to using any functions in
|
||||
ENet.
|
||||
@returns 0 on success, < 0 on failure
|
||||
*/
|
||||
ENET_API int enet_initialize (void);
|
||||
|
||||
/**
|
||||
Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored.
|
||||
|
||||
@param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use
|
||||
@param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults
|
||||
@returns 0 on success, < 0 on failure
|
||||
*/
|
||||
ENET_API int enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits);
|
||||
|
||||
/**
|
||||
Shuts down ENet globally. Should be called when a program that has
|
||||
initialized ENet exits.
|
||||
*/
|
||||
ENET_API void enet_deinitialize (void);
|
||||
|
||||
/**
|
||||
Gives the linked version of the ENet library.
|
||||
@returns the version number
|
||||
*/
|
||||
ENET_API ENetVersion enet_linked_version (void);
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @defgroup private ENet private implementation functions */
|
||||
|
||||
/**
|
||||
Returns the wall-time in milliseconds. Its initial value is unspecified
|
||||
unless otherwise set.
|
||||
*/
|
||||
ENET_API enet_uint32 enet_time_get (void);
|
||||
/**
|
||||
Sets the current wall-time in milliseconds.
|
||||
*/
|
||||
ENET_API void enet_time_set (enet_uint32);
|
||||
|
||||
/** @defgroup socket ENet socket functions
|
||||
@{
|
||||
*/
|
||||
ENET_API ENetSocket enet_socket_create (ENetSocketType);
|
||||
ENET_API int enet_socket_bind (ENetSocket, const ENetAddress *);
|
||||
ENET_API int enet_socket_get_address (ENetSocket, ENetAddress *);
|
||||
ENET_API int enet_socket_listen (ENetSocket, int);
|
||||
ENET_API ENetSocket enet_socket_accept (ENetSocket, ENetAddress *);
|
||||
ENET_API int enet_socket_connect (ENetSocket, const ENetAddress *);
|
||||
ENET_API int enet_socket_send (ENetSocket, const ENetAddress *, const ENetBuffer *, size_t);
|
||||
ENET_API int enet_socket_receive (ENetSocket, ENetAddress *, ENetBuffer *, size_t);
|
||||
ENET_API int enet_socket_wait (ENetSocket, enet_uint32 *, enet_uint32);
|
||||
ENET_API int enet_socket_set_option (ENetSocket, ENetSocketOption, int);
|
||||
ENET_API int enet_socket_get_option (ENetSocket, ENetSocketOption, int *);
|
||||
ENET_API int enet_socket_shutdown (ENetSocket, ENetSocketShutdown);
|
||||
ENET_API void enet_socket_destroy (ENetSocket);
|
||||
ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32);
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @defgroup Address ENet address functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/** Attempts to parse the printable form of the IP address in the parameter hostName
|
||||
and sets the host field in the address parameter if successful.
|
||||
@param address destination to store the parsed IP address
|
||||
@param hostName IP address to parse
|
||||
@retval 0 on success
|
||||
@retval < 0 on failure
|
||||
@returns the address of the given hostName in address on success
|
||||
*/
|
||||
ENET_API int enet_address_set_host_ip (ENetAddress * address, const char * hostName);
|
||||
|
||||
/** Attempts to resolve the host named by the parameter hostName and sets
|
||||
the host field in the address parameter if successful.
|
||||
@param address destination to store resolved address
|
||||
@param hostName host name to lookup
|
||||
@retval 0 on success
|
||||
@retval < 0 on failure
|
||||
@returns the address of the given hostName in address on success
|
||||
*/
|
||||
ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName);
|
||||
|
||||
/** Gives the printable form of the IP address specified in the address parameter.
|
||||
@param address address printed
|
||||
@param hostName destination for name, must not be NULL
|
||||
@param nameLength maximum length of hostName.
|
||||
@returns the null-terminated name of the host in hostName on success
|
||||
@retval 0 on success
|
||||
@retval < 0 on failure
|
||||
*/
|
||||
ENET_API int enet_address_get_host_ip (const ENetAddress * address, char * hostName, size_t nameLength);
|
||||
|
||||
/** Attempts to do a reverse lookup of the host field in the address parameter.
|
||||
@param address address used for reverse lookup
|
||||
@param hostName destination for name, must not be NULL
|
||||
@param nameLength maximum length of hostName.
|
||||
@returns the null-terminated name of the host in hostName on success
|
||||
@retval 0 on success
|
||||
@retval < 0 on failure
|
||||
*/
|
||||
ENET_API int enet_address_get_host (const ENetAddress * address, char * hostName, size_t nameLength);
|
||||
|
||||
/** @} */
|
||||
|
||||
ENET_API ENetPacket * enet_packet_create (const void *, size_t, enet_uint32);
|
||||
ENET_API void enet_packet_destroy (ENetPacket *);
|
||||
ENET_API int enet_packet_resize (ENetPacket *, size_t);
|
||||
ENET_API enet_uint32 enet_crc32 (const ENetBuffer *, size_t);
|
||||
|
||||
ENET_API ENetHost * enet_host_create (const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32);
|
||||
ENET_API void enet_host_destroy (ENetHost *);
|
||||
ENET_API ENetPeer * enet_host_connect (ENetHost *, const ENetAddress *, size_t, enet_uint32);
|
||||
ENET_API int enet_host_check_events (ENetHost *, ENetEvent *);
|
||||
ENET_API int enet_host_service (ENetHost *, ENetEvent *, enet_uint32);
|
||||
ENET_API void enet_host_flush (ENetHost *);
|
||||
ENET_API void enet_host_broadcast (ENetHost *, enet_uint8, ENetPacket *);
|
||||
ENET_API void enet_host_compress (ENetHost *, const ENetCompressor *);
|
||||
ENET_API int enet_host_compress_with_range_coder (ENetHost * host);
|
||||
ENET_API void enet_host_channel_limit (ENetHost *, size_t);
|
||||
ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32);
|
||||
extern void enet_host_bandwidth_throttle (ENetHost *);
|
||||
extern enet_uint32 enet_host_random_seed (void);
|
||||
|
||||
ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *);
|
||||
ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID);
|
||||
ENET_API void enet_peer_ping (ENetPeer *);
|
||||
ENET_API void enet_peer_ping_interval (ENetPeer *, enet_uint32);
|
||||
ENET_API void enet_peer_timeout (ENetPeer *, enet_uint32, enet_uint32, enet_uint32);
|
||||
ENET_API void enet_peer_reset (ENetPeer *);
|
||||
ENET_API void enet_peer_disconnect (ENetPeer *, enet_uint32);
|
||||
ENET_API void enet_peer_disconnect_now (ENetPeer *, enet_uint32);
|
||||
ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32);
|
||||
ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32);
|
||||
extern int enet_peer_throttle (ENetPeer *, enet_uint32);
|
||||
extern void enet_peer_reset_queues (ENetPeer *);
|
||||
extern void enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *);
|
||||
extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16);
|
||||
extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32);
|
||||
extern ENetAcknowledgement * enet_peer_queue_acknowledgement (ENetPeer *, const ENetProtocol *, enet_uint16);
|
||||
extern void enet_peer_dispatch_incoming_unreliable_commands (ENetPeer *, ENetChannel *, ENetIncomingCommand *);
|
||||
extern void enet_peer_dispatch_incoming_reliable_commands (ENetPeer *, ENetChannel *, ENetIncomingCommand *);
|
||||
extern void enet_peer_on_connect (ENetPeer *);
|
||||
extern void enet_peer_on_disconnect (ENetPeer *);
|
||||
|
||||
ENET_API void * enet_range_coder_create (void);
|
||||
ENET_API void enet_range_coder_destroy (void *);
|
||||
ENET_API size_t enet_range_coder_compress (void *, const ENetBuffer *, size_t, size_t, enet_uint8 *, size_t);
|
||||
ENET_API size_t enet_range_coder_decompress (void *, const enet_uint8 *, size_t, enet_uint8 *, size_t);
|
||||
|
||||
extern size_t enet_protocol_command_size (enet_uint8);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __ENET_ENET_H__ */
|
||||
|
||||
491
backends/networking/enet/source/host.cpp
Normal file
491
backends/networking/enet/source/host.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
@file host.c
|
||||
@brief ENet host management functions
|
||||
*/
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include <string.h>
|
||||
#include "enet.h"
|
||||
|
||||
/** @defgroup host ENet host functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/** Creates a host for communicating to peers.
|
||||
|
||||
@param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host.
|
||||
@param peerCount the maximum number of peers that should be allocated for the host.
|
||||
@param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
|
||||
@param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.
|
||||
@param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.
|
||||
|
||||
@returns the host on success and NULL on failure
|
||||
|
||||
@remarks ENet will strategically drop packets on specific sides of a connection between hosts
|
||||
to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine
|
||||
the window size of a connection which limits the amount of reliable packets that may be in transit
|
||||
at any given time.
|
||||
*/
|
||||
ENetHost *
|
||||
enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
|
||||
{
|
||||
ENetHost * host;
|
||||
ENetPeer * currentPeer;
|
||||
|
||||
if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID)
|
||||
return NULL;
|
||||
|
||||
host = (ENetHost *) enet_malloc (sizeof (ENetHost));
|
||||
if (host == NULL)
|
||||
return NULL;
|
||||
memset (host, 0, sizeof (ENetHost));
|
||||
|
||||
host -> peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer));
|
||||
if (host -> peers == NULL)
|
||||
{
|
||||
enet_free (host);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
memset (host -> peers, 0, peerCount * sizeof (ENetPeer));
|
||||
|
||||
host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM);
|
||||
if (host -> socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host -> socket, address) < 0))
|
||||
{
|
||||
if (host -> socket != ENET_SOCKET_NULL)
|
||||
enet_socket_destroy (host -> socket);
|
||||
|
||||
enet_free (host -> peers);
|
||||
enet_free (host);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enet_socket_set_option (host -> socket, ENET_SOCKOPT_NONBLOCK, 1);
|
||||
enet_socket_set_option (host -> socket, ENET_SOCKOPT_BROADCAST, 1);
|
||||
enet_socket_set_option (host -> socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE);
|
||||
enet_socket_set_option (host -> socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE);
|
||||
|
||||
if (address != NULL && enet_socket_get_address (host -> socket, & host -> address) < 0)
|
||||
host -> address = * address;
|
||||
|
||||
if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
|
||||
channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
|
||||
else
|
||||
if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
|
||||
channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
|
||||
|
||||
host -> randomSeed = (enet_uint32) (size_t) host;
|
||||
host -> randomSeed += enet_host_random_seed ();
|
||||
host -> randomSeed = (host -> randomSeed << 16) | (host -> randomSeed >> 16);
|
||||
host -> channelLimit = channelLimit;
|
||||
host -> incomingBandwidth = incomingBandwidth;
|
||||
host -> outgoingBandwidth = outgoingBandwidth;
|
||||
host -> bandwidthThrottleEpoch = 0;
|
||||
host -> recalculateBandwidthLimits = 0;
|
||||
host -> mtu = ENET_HOST_DEFAULT_MTU;
|
||||
host -> peerCount = peerCount;
|
||||
host -> commandCount = 0;
|
||||
host -> bufferCount = 0;
|
||||
host -> checksum = NULL;
|
||||
host -> receivedAddress.host = ENET_HOST_ANY;
|
||||
host -> receivedAddress.port = 0;
|
||||
host -> receivedData = NULL;
|
||||
host -> receivedDataLength = 0;
|
||||
|
||||
host -> totalSentData = 0;
|
||||
host -> totalSentPackets = 0;
|
||||
host -> totalReceivedData = 0;
|
||||
host -> totalReceivedPackets = 0;
|
||||
|
||||
host -> connectedPeers = 0;
|
||||
host -> bandwidthLimitedPeers = 0;
|
||||
host -> duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID;
|
||||
host -> maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE;
|
||||
host -> maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA;
|
||||
|
||||
host -> compressor.context = NULL;
|
||||
host -> compressor.compress = NULL;
|
||||
host -> compressor.decompress = NULL;
|
||||
host -> compressor.destroy = NULL;
|
||||
|
||||
host -> intercept = NULL;
|
||||
|
||||
enet_list_clear (& host -> dispatchQueue);
|
||||
|
||||
for (currentPeer = host -> peers;
|
||||
currentPeer < & host -> peers [host -> peerCount];
|
||||
++ currentPeer)
|
||||
{
|
||||
currentPeer -> host = host;
|
||||
currentPeer -> incomingPeerID = currentPeer - host -> peers;
|
||||
currentPeer -> outgoingSessionID = currentPeer -> incomingSessionID = 0xFF;
|
||||
currentPeer -> data = NULL;
|
||||
|
||||
enet_list_clear (& currentPeer -> acknowledgements);
|
||||
enet_list_clear (& currentPeer -> sentReliableCommands);
|
||||
enet_list_clear (& currentPeer -> sentUnreliableCommands);
|
||||
enet_list_clear (& currentPeer -> outgoingCommands);
|
||||
enet_list_clear (& currentPeer -> dispatchedCommands);
|
||||
|
||||
enet_peer_reset (currentPeer);
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
/** Destroys the host and all resources associated with it.
|
||||
@param host pointer to the host to destroy
|
||||
*/
|
||||
void
|
||||
enet_host_destroy (ENetHost * host)
|
||||
{
|
||||
ENetPeer * currentPeer;
|
||||
|
||||
if (host == NULL)
|
||||
return;
|
||||
|
||||
enet_socket_destroy (host -> socket);
|
||||
|
||||
for (currentPeer = host -> peers;
|
||||
currentPeer < & host -> peers [host -> peerCount];
|
||||
++ currentPeer)
|
||||
{
|
||||
enet_peer_reset (currentPeer);
|
||||
}
|
||||
|
||||
if (host -> compressor.context != NULL && host -> compressor.destroy)
|
||||
(* host -> compressor.destroy) (host -> compressor.context);
|
||||
|
||||
enet_free (host -> peers);
|
||||
enet_free (host);
|
||||
}
|
||||
|
||||
/** Initiates a connection to a foreign host.
|
||||
@param host host seeking the connection
|
||||
@param address destination for the connection
|
||||
@param channelCount number of channels to allocate
|
||||
@param data user data supplied to the receiving host
|
||||
@returns a peer representing the foreign host on success, NULL on failure
|
||||
@remarks The peer returned will have not completed the connection until enet_host_service()
|
||||
notifies of an ENET_EVENT_TYPE_CONNECT event for the peer.
|
||||
*/
|
||||
ENetPeer *
|
||||
enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelCount, enet_uint32 data)
|
||||
{
|
||||
ENetPeer * currentPeer;
|
||||
ENetChannel * channel;
|
||||
ENetProtocol command;
|
||||
|
||||
if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
|
||||
channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
|
||||
else
|
||||
if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
|
||||
channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
|
||||
|
||||
for (currentPeer = host -> peers;
|
||||
currentPeer < & host -> peers [host -> peerCount];
|
||||
++ currentPeer)
|
||||
{
|
||||
if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED)
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentPeer >= & host -> peers [host -> peerCount])
|
||||
return NULL;
|
||||
|
||||
currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel));
|
||||
if (currentPeer -> channels == NULL)
|
||||
return NULL;
|
||||
currentPeer -> channelCount = channelCount;
|
||||
currentPeer -> state = ENET_PEER_STATE_CONNECTING;
|
||||
currentPeer -> address = * address;
|
||||
currentPeer -> connectID = ++ host -> randomSeed;
|
||||
|
||||
if (host -> outgoingBandwidth == 0)
|
||||
currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
|
||||
else
|
||||
currentPeer -> windowSize = (host -> outgoingBandwidth /
|
||||
ENET_PEER_WINDOW_SIZE_SCALE) *
|
||||
ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
|
||||
|
||||
if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
|
||||
currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
|
||||
else
|
||||
if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
|
||||
currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
|
||||
|
||||
for (channel = currentPeer -> channels;
|
||||
channel < & currentPeer -> channels [channelCount];
|
||||
++ channel)
|
||||
{
|
||||
channel -> outgoingReliableSequenceNumber = 0;
|
||||
channel -> outgoingUnreliableSequenceNumber = 0;
|
||||
channel -> incomingReliableSequenceNumber = 0;
|
||||
channel -> incomingUnreliableSequenceNumber = 0;
|
||||
|
||||
enet_list_clear (& channel -> incomingReliableCommands);
|
||||
enet_list_clear (& channel -> incomingUnreliableCommands);
|
||||
|
||||
channel -> usedReliableWindows = 0;
|
||||
memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows));
|
||||
}
|
||||
|
||||
command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
|
||||
command.header.channelID = 0xFF;
|
||||
command.connect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID);
|
||||
command.connect.incomingSessionID = currentPeer -> incomingSessionID;
|
||||
command.connect.outgoingSessionID = currentPeer -> outgoingSessionID;
|
||||
command.connect.mtu = ENET_HOST_TO_NET_32 (currentPeer -> mtu);
|
||||
command.connect.windowSize = ENET_HOST_TO_NET_32 (currentPeer -> windowSize);
|
||||
command.connect.channelCount = ENET_HOST_TO_NET_32 (channelCount);
|
||||
command.connect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth);
|
||||
command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth);
|
||||
command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval);
|
||||
command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration);
|
||||
command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration);
|
||||
command.connect.connectID = currentPeer -> connectID;
|
||||
command.connect.data = ENET_HOST_TO_NET_32 (data);
|
||||
|
||||
enet_peer_queue_outgoing_command (currentPeer, & command, NULL, 0, 0);
|
||||
|
||||
return currentPeer;
|
||||
}
|
||||
|
||||
/** Queues a packet to be sent to all peers associated with the host.
|
||||
@param host host on which to broadcast the packet
|
||||
@param channelID channel on which to broadcast
|
||||
@param packet packet to broadcast
|
||||
*/
|
||||
void
|
||||
enet_host_broadcast (ENetHost * host, enet_uint8 channelID, ENetPacket * packet)
|
||||
{
|
||||
ENetPeer * currentPeer;
|
||||
|
||||
for (currentPeer = host -> peers;
|
||||
currentPeer < & host -> peers [host -> peerCount];
|
||||
++ currentPeer)
|
||||
{
|
||||
if (currentPeer -> state != ENET_PEER_STATE_CONNECTED)
|
||||
continue;
|
||||
|
||||
enet_peer_send (currentPeer, channelID, packet);
|
||||
}
|
||||
|
||||
if (packet -> referenceCount == 0)
|
||||
enet_packet_destroy (packet);
|
||||
}
|
||||
|
||||
/** Sets the packet compressor the host should use to compress and decompress packets.
|
||||
@param host host to enable or disable compression for
|
||||
@param compressor callbacks for for the packet compressor; if NULL, then compression is disabled
|
||||
*/
|
||||
void
|
||||
enet_host_compress (ENetHost * host, const ENetCompressor * compressor)
|
||||
{
|
||||
if (host -> compressor.context != NULL && host -> compressor.destroy)
|
||||
(* host -> compressor.destroy) (host -> compressor.context);
|
||||
|
||||
if (compressor)
|
||||
host -> compressor = * compressor;
|
||||
else
|
||||
host -> compressor.context = NULL;
|
||||
}
|
||||
|
||||
/** Limits the maximum allowed channels of future incoming connections.
|
||||
@param host host to limit
|
||||
@param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
|
||||
*/
|
||||
void
|
||||
enet_host_channel_limit (ENetHost * host, size_t channelLimit)
|
||||
{
|
||||
if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
|
||||
channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
|
||||
else
|
||||
if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
|
||||
channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
|
||||
|
||||
host -> channelLimit = channelLimit;
|
||||
}
|
||||
|
||||
|
||||
/** Adjusts the bandwidth limits of a host.
|
||||
@param host host to adjust
|
||||
@param incomingBandwidth new incoming bandwidth
|
||||
@param outgoingBandwidth new outgoing bandwidth
|
||||
@remarks the incoming and outgoing bandwidth parameters are identical in function to those
|
||||
specified in enet_host_create().
|
||||
*/
|
||||
void
|
||||
enet_host_bandwidth_limit (ENetHost * host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
|
||||
{
|
||||
host -> incomingBandwidth = incomingBandwidth;
|
||||
host -> outgoingBandwidth = outgoingBandwidth;
|
||||
host -> recalculateBandwidthLimits = 1;
|
||||
}
|
||||
|
||||
void
|
||||
enet_host_bandwidth_throttle (ENetHost * host)
|
||||
{
|
||||
enet_uint32 timeCurrent = enet_time_get (),
|
||||
elapsedTime = timeCurrent - host -> bandwidthThrottleEpoch,
|
||||
peersRemaining = (enet_uint32) host -> connectedPeers,
|
||||
dataTotal = ~0U,
|
||||
bandwidth = ~0U,
|
||||
throttle = 0,
|
||||
bandwidthLimit = 0;
|
||||
int needsAdjustment = host -> bandwidthLimitedPeers > 0 ? 1 : 0;
|
||||
ENetPeer * peer;
|
||||
ENetProtocol command;
|
||||
|
||||
if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL)
|
||||
return;
|
||||
|
||||
host -> bandwidthThrottleEpoch = timeCurrent;
|
||||
|
||||
if (peersRemaining == 0)
|
||||
return;
|
||||
|
||||
if (host -> outgoingBandwidth != 0)
|
||||
{
|
||||
dataTotal = 0;
|
||||
bandwidth = (host -> outgoingBandwidth * elapsedTime) / 1000;
|
||||
|
||||
for (peer = host -> peers;
|
||||
peer < & host -> peers [host -> peerCount];
|
||||
++ peer)
|
||||
{
|
||||
if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)
|
||||
continue;
|
||||
|
||||
dataTotal += peer -> outgoingDataTotal;
|
||||
}
|
||||
}
|
||||
|
||||
while (peersRemaining > 0 && needsAdjustment != 0)
|
||||
{
|
||||
needsAdjustment = 0;
|
||||
|
||||
if (dataTotal <= bandwidth)
|
||||
throttle = ENET_PEER_PACKET_THROTTLE_SCALE;
|
||||
else
|
||||
throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal;
|
||||
|
||||
for (peer = host -> peers;
|
||||
peer < & host -> peers [host -> peerCount];
|
||||
++ peer)
|
||||
{
|
||||
enet_uint32 peerBandwidth;
|
||||
|
||||
if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
|
||||
peer -> incomingBandwidth == 0 ||
|
||||
peer -> outgoingBandwidthThrottleEpoch == timeCurrent)
|
||||
continue;
|
||||
|
||||
peerBandwidth = (peer -> incomingBandwidth * elapsedTime) / 1000;
|
||||
if ((throttle * peer -> outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth)
|
||||
continue;
|
||||
|
||||
peer -> packetThrottleLimit = (peerBandwidth *
|
||||
ENET_PEER_PACKET_THROTTLE_SCALE) / peer -> outgoingDataTotal;
|
||||
|
||||
if (peer -> packetThrottleLimit == 0)
|
||||
peer -> packetThrottleLimit = 1;
|
||||
|
||||
if (peer -> packetThrottle > peer -> packetThrottleLimit)
|
||||
peer -> packetThrottle = peer -> packetThrottleLimit;
|
||||
|
||||
peer -> outgoingBandwidthThrottleEpoch = timeCurrent;
|
||||
|
||||
peer -> incomingDataTotal = 0;
|
||||
peer -> outgoingDataTotal = 0;
|
||||
|
||||
needsAdjustment = 1;
|
||||
-- peersRemaining;
|
||||
bandwidth -= peerBandwidth;
|
||||
dataTotal -= peerBandwidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (peersRemaining > 0)
|
||||
{
|
||||
if (dataTotal <= bandwidth)
|
||||
throttle = ENET_PEER_PACKET_THROTTLE_SCALE;
|
||||
else
|
||||
throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal;
|
||||
|
||||
for (peer = host -> peers;
|
||||
peer < & host -> peers [host -> peerCount];
|
||||
++ peer)
|
||||
{
|
||||
if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
|
||||
peer -> outgoingBandwidthThrottleEpoch == timeCurrent)
|
||||
continue;
|
||||
|
||||
peer -> packetThrottleLimit = throttle;
|
||||
|
||||
if (peer -> packetThrottle > peer -> packetThrottleLimit)
|
||||
peer -> packetThrottle = peer -> packetThrottleLimit;
|
||||
|
||||
peer -> incomingDataTotal = 0;
|
||||
peer -> outgoingDataTotal = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (host -> recalculateBandwidthLimits)
|
||||
{
|
||||
host -> recalculateBandwidthLimits = 0;
|
||||
|
||||
peersRemaining = (enet_uint32) host -> connectedPeers;
|
||||
bandwidth = host -> incomingBandwidth;
|
||||
needsAdjustment = 1;
|
||||
|
||||
if (bandwidth == 0)
|
||||
bandwidthLimit = 0;
|
||||
else
|
||||
while (peersRemaining > 0 && needsAdjustment != 0)
|
||||
{
|
||||
needsAdjustment = 0;
|
||||
bandwidthLimit = bandwidth / peersRemaining;
|
||||
|
||||
for (peer = host -> peers;
|
||||
peer < & host -> peers [host -> peerCount];
|
||||
++ peer)
|
||||
{
|
||||
if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) ||
|
||||
peer -> incomingBandwidthThrottleEpoch == timeCurrent)
|
||||
continue;
|
||||
|
||||
if (peer -> outgoingBandwidth > 0 &&
|
||||
peer -> outgoingBandwidth >= bandwidthLimit)
|
||||
continue;
|
||||
|
||||
peer -> incomingBandwidthThrottleEpoch = timeCurrent;
|
||||
|
||||
needsAdjustment = 1;
|
||||
-- peersRemaining;
|
||||
bandwidth -= peer -> outgoingBandwidth;
|
||||
}
|
||||
}
|
||||
|
||||
for (peer = host -> peers;
|
||||
peer < & host -> peers [host -> peerCount];
|
||||
++ peer)
|
||||
{
|
||||
if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)
|
||||
continue;
|
||||
|
||||
command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
|
||||
command.header.channelID = 0xFF;
|
||||
command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth);
|
||||
|
||||
if (peer -> incomingBandwidthThrottleEpoch == timeCurrent)
|
||||
command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (peer -> outgoingBandwidth);
|
||||
else
|
||||
command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (bandwidthLimit);
|
||||
|
||||
enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
75
backends/networking/enet/source/list.cpp
Normal file
75
backends/networking/enet/source/list.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
@file list.c
|
||||
@brief ENet linked list functions
|
||||
*/
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include "enet.h"
|
||||
|
||||
/**
|
||||
@defgroup list ENet linked list utility functions
|
||||
@ingroup private
|
||||
@{
|
||||
*/
|
||||
void
|
||||
enet_list_clear (ENetList * list)
|
||||
{
|
||||
list -> sentinel.next = & list -> sentinel;
|
||||
list -> sentinel.previous = & list -> sentinel;
|
||||
}
|
||||
|
||||
ENetListIterator
|
||||
enet_list_insert (ENetListIterator position, void * data)
|
||||
{
|
||||
ENetListIterator result = (ENetListIterator) data;
|
||||
|
||||
result -> previous = position -> previous;
|
||||
result -> next = position;
|
||||
|
||||
result -> previous -> next = result;
|
||||
position -> previous = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void *
|
||||
enet_list_remove (ENetListIterator position)
|
||||
{
|
||||
position -> previous -> next = position -> next;
|
||||
position -> next -> previous = position -> previous;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
ENetListIterator
|
||||
enet_list_move (ENetListIterator position, void * dataFirst, void * dataLast)
|
||||
{
|
||||
ENetListIterator first = (ENetListIterator) dataFirst,
|
||||
last = (ENetListIterator) dataLast;
|
||||
|
||||
first -> previous -> next = last -> next;
|
||||
last -> next -> previous = first -> previous;
|
||||
|
||||
first -> previous = position -> previous;
|
||||
last -> next = position;
|
||||
|
||||
first -> previous -> next = first;
|
||||
position -> previous = last;
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
size_t
|
||||
enet_list_size (ENetList * list)
|
||||
{
|
||||
size_t size = 0;
|
||||
ENetListIterator position;
|
||||
|
||||
for (position = enet_list_begin (list);
|
||||
position != enet_list_end (list);
|
||||
position = enet_list_next (position))
|
||||
++ size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
43
backends/networking/enet/source/list.h
Normal file
43
backends/networking/enet/source/list.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
@file list.h
|
||||
@brief ENet list management
|
||||
*/
|
||||
#ifndef __ENET_LIST_H__
|
||||
#define __ENET_LIST_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct _ENetListNode
|
||||
{
|
||||
struct _ENetListNode * next;
|
||||
struct _ENetListNode * previous;
|
||||
} ENetListNode;
|
||||
|
||||
typedef ENetListNode * ENetListIterator;
|
||||
|
||||
typedef struct _ENetList
|
||||
{
|
||||
ENetListNode sentinel;
|
||||
} ENetList;
|
||||
|
||||
extern void enet_list_clear (ENetList *);
|
||||
|
||||
extern ENetListIterator enet_list_insert (ENetListIterator, void *);
|
||||
extern void * enet_list_remove (ENetListIterator);
|
||||
extern ENetListIterator enet_list_move (ENetListIterator, void *, void *);
|
||||
|
||||
extern size_t enet_list_size (ENetList *);
|
||||
|
||||
#define enet_list_begin(list) ((list) -> sentinel.next)
|
||||
#define enet_list_end(list) (& (list) -> sentinel)
|
||||
|
||||
#define enet_list_empty(list) (enet_list_begin (list) == enet_list_end (list))
|
||||
|
||||
#define enet_list_next(iterator) ((iterator) -> next)
|
||||
#define enet_list_previous(iterator) ((iterator) -> previous)
|
||||
|
||||
#define enet_list_front(list) ((void *) (list) -> sentinel.next)
|
||||
#define enet_list_back(list) ((void *) (list) -> sentinel.previous)
|
||||
|
||||
#endif /* __ENET_LIST_H__ */
|
||||
|
||||
165
backends/networking/enet/source/packet.cpp
Normal file
165
backends/networking/enet/source/packet.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
@file packet.c
|
||||
@brief ENet packet management functions
|
||||
*/
|
||||
#include <string.h>
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include "enet.h"
|
||||
|
||||
/** @defgroup Packet ENet packet functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/** Creates a packet that may be sent to a peer.
|
||||
@param data initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL.
|
||||
@param dataLength size of the data allocated for this packet
|
||||
@param flags flags for this packet as described for the ENetPacket structure.
|
||||
@returns the packet on success, NULL on failure
|
||||
*/
|
||||
ENetPacket *
|
||||
enet_packet_create (const void * data, size_t dataLength, enet_uint32 flags)
|
||||
{
|
||||
ENetPacket * packet = (ENetPacket *) enet_malloc (sizeof (ENetPacket));
|
||||
if (packet == NULL)
|
||||
return NULL;
|
||||
|
||||
if (flags & ENET_PACKET_FLAG_NO_ALLOCATE)
|
||||
packet -> data = const_cast<enet_uint8 *>(static_cast<const enet_uint8 *>(data));
|
||||
else
|
||||
if (dataLength <= 0)
|
||||
packet -> data = NULL;
|
||||
else
|
||||
{
|
||||
packet -> data = (enet_uint8 *) enet_malloc (dataLength);
|
||||
if (packet -> data == NULL)
|
||||
{
|
||||
enet_free (packet);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (data != NULL)
|
||||
memcpy (packet -> data, data, dataLength);
|
||||
}
|
||||
|
||||
packet -> referenceCount = 0;
|
||||
packet -> flags = flags;
|
||||
packet -> dataLength = dataLength;
|
||||
packet -> freeCallback = NULL;
|
||||
packet -> userData = NULL;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/** Destroys the packet and deallocates its data.
|
||||
@param packet packet to be destroyed
|
||||
*/
|
||||
void
|
||||
enet_packet_destroy (ENetPacket * packet)
|
||||
{
|
||||
if (packet == NULL)
|
||||
return;
|
||||
|
||||
if (packet -> freeCallback != NULL)
|
||||
(* packet -> freeCallback) (packet);
|
||||
if (! (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE) &&
|
||||
packet -> data != NULL)
|
||||
enet_free (packet -> data);
|
||||
enet_free (packet);
|
||||
}
|
||||
|
||||
/** Attempts to resize the data in the packet to length specified in the
|
||||
dataLength parameter
|
||||
@param packet packet to resize
|
||||
@param dataLength new size for the packet data
|
||||
@returns 0 on success, < 0 on failure
|
||||
*/
|
||||
int
|
||||
enet_packet_resize (ENetPacket * packet, size_t dataLength)
|
||||
{
|
||||
enet_uint8 * newData;
|
||||
|
||||
if (dataLength <= packet -> dataLength || (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE))
|
||||
{
|
||||
packet -> dataLength = dataLength;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
newData = (enet_uint8 *) enet_malloc (dataLength);
|
||||
if (newData == NULL)
|
||||
return -1;
|
||||
|
||||
memcpy (newData, packet -> data, packet -> dataLength);
|
||||
enet_free (packet -> data);
|
||||
|
||||
packet -> data = newData;
|
||||
packet -> dataLength = dataLength;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int initializedCRC32 = 0;
|
||||
static enet_uint32 crcTable [256];
|
||||
|
||||
static enet_uint32
|
||||
reflect_crc (int val, int bits)
|
||||
{
|
||||
int result = 0, bit;
|
||||
|
||||
for (bit = 0; bit < bits; bit ++)
|
||||
{
|
||||
if(val & 1) result |= 1 << (bits - 1 - bit);
|
||||
val >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_crc32 (void)
|
||||
{
|
||||
int byte;
|
||||
|
||||
for (byte = 0; byte < 256; ++ byte)
|
||||
{
|
||||
enet_uint32 crc = reflect_crc (byte, 8) << 24;
|
||||
int offset;
|
||||
|
||||
for(offset = 0; offset < 8; ++ offset)
|
||||
{
|
||||
if (crc & 0x80000000)
|
||||
crc = (crc << 1) ^ 0x04c11db7;
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
|
||||
crcTable [byte] = reflect_crc (crc, 32);
|
||||
}
|
||||
|
||||
initializedCRC32 = 1;
|
||||
}
|
||||
|
||||
enet_uint32
|
||||
enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
|
||||
{
|
||||
enet_uint32 crc = 0xFFFFFFFF;
|
||||
|
||||
if (! initializedCRC32) initialize_crc32 ();
|
||||
|
||||
while (bufferCount -- > 0)
|
||||
{
|
||||
const enet_uint8 * data = (const enet_uint8 *) buffers -> data,
|
||||
* dataEnd = & data [buffers -> dataLength];
|
||||
|
||||
while (data < dataEnd)
|
||||
{
|
||||
crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];
|
||||
}
|
||||
|
||||
++ buffers;
|
||||
}
|
||||
|
||||
return ENET_HOST_TO_NET_32 (~ crc);
|
||||
}
|
||||
|
||||
/** @} */
|
||||
1015
backends/networking/enet/source/peer.cpp
Normal file
1015
backends/networking/enet/source/peer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1877
backends/networking/enet/source/protocol.cpp
Normal file
1877
backends/networking/enet/source/protocol.cpp
Normal file
File diff suppressed because it is too large
Load Diff
198
backends/networking/enet/source/protocol.h
Normal file
198
backends/networking/enet/source/protocol.h
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
@file protocol.h
|
||||
@brief ENet protocol
|
||||
*/
|
||||
#ifndef __ENET_PROTOCOL_H__
|
||||
#define __ENET_PROTOCOL_H__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
enum
|
||||
{
|
||||
ENET_PROTOCOL_MINIMUM_MTU = 576,
|
||||
ENET_PROTOCOL_MAXIMUM_MTU = 4096,
|
||||
ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32,
|
||||
ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096,
|
||||
ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536,
|
||||
ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1,
|
||||
ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255,
|
||||
ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF,
|
||||
ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024
|
||||
};
|
||||
|
||||
typedef enum _ENetProtocolCommand
|
||||
{
|
||||
ENET_PROTOCOL_COMMAND_NONE = 0,
|
||||
ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1,
|
||||
ENET_PROTOCOL_COMMAND_CONNECT = 2,
|
||||
ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3,
|
||||
ENET_PROTOCOL_COMMAND_DISCONNECT = 4,
|
||||
ENET_PROTOCOL_COMMAND_PING = 5,
|
||||
ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6,
|
||||
ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7,
|
||||
ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8,
|
||||
ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9,
|
||||
ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10,
|
||||
ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11,
|
||||
ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12,
|
||||
ENET_PROTOCOL_COMMAND_COUNT = 13,
|
||||
|
||||
ENET_PROTOCOL_COMMAND_MASK = 0x0F
|
||||
} ENetProtocolCommand;
|
||||
|
||||
typedef enum _ENetProtocolFlag
|
||||
{
|
||||
ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7),
|
||||
ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6),
|
||||
|
||||
ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14),
|
||||
ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15),
|
||||
ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME,
|
||||
|
||||
ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12),
|
||||
ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12
|
||||
} ENetProtocolFlag;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push, 1)
|
||||
#define ENET_PACKED
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define ENET_PACKED __attribute__ ((packed))
|
||||
#else
|
||||
#define ENET_PACKED
|
||||
#endif
|
||||
|
||||
typedef struct _ENetProtocolHeader
|
||||
{
|
||||
enet_uint16 peerID;
|
||||
enet_uint16 sentTime;
|
||||
} ENET_PACKED ENetProtocolHeader;
|
||||
|
||||
typedef struct _ENetProtocolCommandHeader
|
||||
{
|
||||
enet_uint8 command;
|
||||
enet_uint8 channelID;
|
||||
enet_uint16 reliableSequenceNumber;
|
||||
} ENET_PACKED ENetProtocolCommandHeader;
|
||||
|
||||
typedef struct _ENetProtocolAcknowledge
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 receivedReliableSequenceNumber;
|
||||
enet_uint16 receivedSentTime;
|
||||
} ENET_PACKED ENetProtocolAcknowledge;
|
||||
|
||||
typedef struct _ENetProtocolConnect
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 outgoingPeerID;
|
||||
enet_uint8 incomingSessionID;
|
||||
enet_uint8 outgoingSessionID;
|
||||
enet_uint32 mtu;
|
||||
enet_uint32 windowSize;
|
||||
enet_uint32 channelCount;
|
||||
enet_uint32 incomingBandwidth;
|
||||
enet_uint32 outgoingBandwidth;
|
||||
enet_uint32 packetThrottleInterval;
|
||||
enet_uint32 packetThrottleAcceleration;
|
||||
enet_uint32 packetThrottleDeceleration;
|
||||
enet_uint32 connectID;
|
||||
enet_uint32 data;
|
||||
} ENET_PACKED ENetProtocolConnect;
|
||||
|
||||
typedef struct _ENetProtocolVerifyConnect
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 outgoingPeerID;
|
||||
enet_uint8 incomingSessionID;
|
||||
enet_uint8 outgoingSessionID;
|
||||
enet_uint32 mtu;
|
||||
enet_uint32 windowSize;
|
||||
enet_uint32 channelCount;
|
||||
enet_uint32 incomingBandwidth;
|
||||
enet_uint32 outgoingBandwidth;
|
||||
enet_uint32 packetThrottleInterval;
|
||||
enet_uint32 packetThrottleAcceleration;
|
||||
enet_uint32 packetThrottleDeceleration;
|
||||
enet_uint32 connectID;
|
||||
} ENET_PACKED ENetProtocolVerifyConnect;
|
||||
|
||||
typedef struct _ENetProtocolBandwidthLimit
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint32 incomingBandwidth;
|
||||
enet_uint32 outgoingBandwidth;
|
||||
} ENET_PACKED ENetProtocolBandwidthLimit;
|
||||
|
||||
typedef struct _ENetProtocolThrottleConfigure
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint32 packetThrottleInterval;
|
||||
enet_uint32 packetThrottleAcceleration;
|
||||
enet_uint32 packetThrottleDeceleration;
|
||||
} ENET_PACKED ENetProtocolThrottleConfigure;
|
||||
|
||||
typedef struct _ENetProtocolDisconnect
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint32 data;
|
||||
} ENET_PACKED ENetProtocolDisconnect;
|
||||
|
||||
typedef struct _ENetProtocolPing
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
} ENET_PACKED ENetProtocolPing;
|
||||
|
||||
typedef struct _ENetProtocolSendReliable
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 dataLength;
|
||||
} ENET_PACKED ENetProtocolSendReliable;
|
||||
|
||||
typedef struct _ENetProtocolSendUnreliable
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 unreliableSequenceNumber;
|
||||
enet_uint16 dataLength;
|
||||
} ENET_PACKED ENetProtocolSendUnreliable;
|
||||
|
||||
typedef struct _ENetProtocolSendUnsequenced
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 unsequencedGroup;
|
||||
enet_uint16 dataLength;
|
||||
} ENET_PACKED ENetProtocolSendUnsequenced;
|
||||
|
||||
typedef struct _ENetProtocolSendFragment
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
enet_uint16 startSequenceNumber;
|
||||
enet_uint16 dataLength;
|
||||
enet_uint32 fragmentCount;
|
||||
enet_uint32 fragmentNumber;
|
||||
enet_uint32 totalLength;
|
||||
enet_uint32 fragmentOffset;
|
||||
} ENET_PACKED ENetProtocolSendFragment;
|
||||
|
||||
typedef union _ENetProtocol
|
||||
{
|
||||
ENetProtocolCommandHeader header;
|
||||
ENetProtocolAcknowledge acknowledge;
|
||||
ENetProtocolConnect connect;
|
||||
ENetProtocolVerifyConnect verifyConnect;
|
||||
ENetProtocolDisconnect disconnect;
|
||||
ENetProtocolPing ping;
|
||||
ENetProtocolSendReliable sendReliable;
|
||||
ENetProtocolSendUnreliable sendUnreliable;
|
||||
ENetProtocolSendUnsequenced sendUnsequenced;
|
||||
ENetProtocolSendFragment sendFragment;
|
||||
ENetProtocolBandwidthLimit bandwidthLimit;
|
||||
ENetProtocolThrottleConfigure throttleConfigure;
|
||||
} ENET_PACKED ENetProtocol;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
#endif /* __ENET_PROTOCOL_H__ */
|
||||
|
||||
18
backends/networking/enet/source/time.h
Normal file
18
backends/networking/enet/source/time.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
@file time.h
|
||||
@brief ENet time constants and macros
|
||||
*/
|
||||
#ifndef __ENET_TIME_H__
|
||||
#define __ENET_TIME_H__
|
||||
|
||||
#define ENET_TIME_OVERFLOW 86400000
|
||||
|
||||
#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW)
|
||||
#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW)
|
||||
#define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b))
|
||||
#define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b))
|
||||
|
||||
#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b))
|
||||
|
||||
#endif /* __ENET_TIME_H__ */
|
||||
|
||||
13
backends/networking/enet/source/types.h
Normal file
13
backends/networking/enet/source/types.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
@file types.h
|
||||
@brief type definitions for ENet
|
||||
*/
|
||||
#ifndef __ENET_TYPES_H__
|
||||
#define __ENET_TYPES_H__
|
||||
|
||||
typedef unsigned char enet_uint8; /**< unsigned 8-bit type */
|
||||
typedef unsigned short enet_uint16; /**< unsigned 16-bit type */
|
||||
typedef unsigned int enet_uint32; /**< unsigned 32-bit type */
|
||||
|
||||
#endif /* __ENET_TYPES_H__ */
|
||||
|
||||
629
backends/networking/enet/source/unix.cpp
Normal file
629
backends/networking/enet/source/unix.cpp
Normal file
@@ -0,0 +1,629 @@
|
||||
/**
|
||||
@file unix.c
|
||||
@brief ENet Unix system specific functions
|
||||
*/
|
||||
#ifndef WIN32
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include "enet.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifdef HAS_POLL
|
||||
#undef HAS_POLL
|
||||
#endif
|
||||
#ifndef HAS_FCNTL
|
||||
#define HAS_FCNTL 1
|
||||
#endif
|
||||
#ifndef HAS_INET_PTON
|
||||
#define HAS_INET_PTON 1
|
||||
#endif
|
||||
#ifndef HAS_INET_NTOP
|
||||
#define HAS_INET_NTOP 1
|
||||
#endif
|
||||
#ifndef HAS_MSGHDR_FLAGS
|
||||
#define HAS_MSGHDR_FLAGS 1
|
||||
#endif
|
||||
#ifndef HAS_SOCKLEN_T
|
||||
#define HAS_SOCKLEN_T 1
|
||||
#endif
|
||||
#ifndef HAS_GETADDRINFO
|
||||
#define HAS_GETADDRINFO 1
|
||||
#endif
|
||||
#ifndef HAS_GETNAMEINFO
|
||||
#define HAS_GETNAMEINFO 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// vitasdk do this in their boost package
|
||||
#if defined(__vita__) && !defined(SOMAXCONN)
|
||||
#define SOMAXCONN 4096
|
||||
#endif
|
||||
|
||||
#ifdef HAS_FCNTL
|
||||
#include <fcntl.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAS_POLL
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAS_SOCKLEN_T) && !defined(__socklen_t_defined)
|
||||
typedef int socklen_t;
|
||||
#endif
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static enet_uint32 timeBase = 0;
|
||||
|
||||
int
|
||||
enet_initialize (void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
enet_deinitialize (void)
|
||||
{
|
||||
}
|
||||
|
||||
enet_uint32
|
||||
enet_host_random_seed (void)
|
||||
{
|
||||
return (enet_uint32) time (NULL);
|
||||
}
|
||||
|
||||
enet_uint32
|
||||
enet_time_get (void)
|
||||
{
|
||||
struct timeval timeVal;
|
||||
|
||||
gettimeofday (& timeVal, NULL);
|
||||
|
||||
return timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - timeBase;
|
||||
}
|
||||
|
||||
void
|
||||
enet_time_set (enet_uint32 newTimeBase)
|
||||
{
|
||||
struct timeval timeVal;
|
||||
|
||||
gettimeofday (& timeVal, NULL);
|
||||
|
||||
timeBase = timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - newTimeBase;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_set_host_ip (ENetAddress * address, const char * name)
|
||||
{
|
||||
#ifdef HAS_INET_PTON
|
||||
if (! inet_pton (AF_INET, name, & address -> host))
|
||||
#else
|
||||
if (! inet_aton (name, (struct in_addr *) & address -> host))
|
||||
#endif
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_set_host (ENetAddress * address, const char * name)
|
||||
{
|
||||
#ifdef HAS_GETADDRINFO
|
||||
struct addrinfo hints, * resultList = NULL, * result = NULL;
|
||||
|
||||
memset (& hints, 0, sizeof (hints));
|
||||
hints.ai_family = AF_INET;
|
||||
|
||||
if (getaddrinfo (name, NULL, NULL, & resultList) != 0)
|
||||
return -1;
|
||||
|
||||
for (result = resultList; result != NULL; result = result -> ai_next)
|
||||
{
|
||||
if (result -> ai_family == AF_INET && result -> ai_addr != NULL && result -> ai_addrlen >= sizeof (struct sockaddr_in))
|
||||
{
|
||||
struct sockaddr_in * sin = (struct sockaddr_in *) result -> ai_addr;
|
||||
|
||||
address -> host = sin -> sin_addr.s_addr;
|
||||
|
||||
freeaddrinfo (resultList);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (resultList != NULL)
|
||||
freeaddrinfo (resultList);
|
||||
#else
|
||||
struct hostent * hostEntry = NULL;
|
||||
#ifdef HAS_GETHOSTBYNAME_R
|
||||
struct hostent hostData;
|
||||
char buffer [2048];
|
||||
int errnum;
|
||||
|
||||
#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
|
||||
gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum);
|
||||
#else
|
||||
hostEntry = gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & errnum);
|
||||
#endif
|
||||
#else
|
||||
hostEntry = gethostbyname (name);
|
||||
#endif
|
||||
|
||||
if (hostEntry != NULL && hostEntry -> h_addrtype == AF_INET)
|
||||
{
|
||||
address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return enet_address_set_host_ip (address, name);
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength)
|
||||
{
|
||||
#ifdef HAS_INET_NTOP
|
||||
if (inet_ntop (AF_INET, & address -> host, name, nameLength) == NULL)
|
||||
#else
|
||||
char * addr = inet_ntoa (* (struct in_addr *) const_cast<struct in_addr *>(reinterpret_cast<const struct in_addr *>(& address -> host)));
|
||||
if (addr != NULL)
|
||||
{
|
||||
size_t addrLen = strlen(addr);
|
||||
if (addrLen >= nameLength)
|
||||
return -1;
|
||||
memcpy (name, addr, addrLen + 1);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength)
|
||||
{
|
||||
#ifdef HAS_GETNAMEINFO
|
||||
struct sockaddr_in sin;
|
||||
int err;
|
||||
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
|
||||
err = getnameinfo ((struct sockaddr *) & sin, sizeof (sin), name, nameLength, NULL, 0, NI_NAMEREQD);
|
||||
if (! err)
|
||||
{
|
||||
if (name != NULL && nameLength > 0 && ! memchr (name, '\0', nameLength))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (err != EAI_NONAME)
|
||||
return -1;
|
||||
#else
|
||||
struct in_addr in;
|
||||
struct hostent * hostEntry = NULL;
|
||||
#ifdef HAS_GETHOSTBYADDR_R
|
||||
struct hostent hostData;
|
||||
char buffer [2048];
|
||||
int errnum;
|
||||
|
||||
in.s_addr = address -> host;
|
||||
|
||||
#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
|
||||
gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum);
|
||||
#else
|
||||
hostEntry = gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & errnum);
|
||||
#endif
|
||||
#else
|
||||
in.s_addr = address -> host;
|
||||
|
||||
hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET);
|
||||
#endif
|
||||
|
||||
if (hostEntry != NULL)
|
||||
{
|
||||
size_t hostLen = strlen (hostEntry -> h_name);
|
||||
if (hostLen >= nameLength)
|
||||
return -1;
|
||||
memcpy (name, hostEntry -> h_name, hostLen + 1);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return enet_address_get_host_ip (address, name, nameLength);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_bind (ENetSocket socket, const ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
}
|
||||
else
|
||||
{
|
||||
sin.sin_port = 0;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
}
|
||||
|
||||
return bind (socket,
|
||||
(struct sockaddr *) & sin,
|
||||
sizeof (struct sockaddr_in));
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_get_address (ENetSocket socket, ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
socklen_t sinLength = sizeof (struct sockaddr_in);
|
||||
|
||||
if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1)
|
||||
return -1;
|
||||
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_listen (ENetSocket socket, int backlog)
|
||||
{
|
||||
return listen (socket, backlog < 0 ? SOMAXCONN : backlog);
|
||||
}
|
||||
|
||||
ENetSocket
|
||||
enet_socket_create (ENetSocketType type)
|
||||
{
|
||||
return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value)
|
||||
{
|
||||
int result = -1;
|
||||
switch (option)
|
||||
{
|
||||
case ENET_SOCKOPT_NONBLOCK:
|
||||
#ifdef HAS_FCNTL
|
||||
result = fcntl (socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl (socket, F_GETFL) & ~O_NONBLOCK));
|
||||
#else
|
||||
result = ioctl (socket, FIONBIO, & value);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_BROADCAST:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_REUSEADDR:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_RCVBUF:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_SNDBUF:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_RCVTIMEO:
|
||||
{
|
||||
struct timeval timeVal;
|
||||
timeVal.tv_sec = value / 1000;
|
||||
timeVal.tv_usec = (value % 1000) * 1000;
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *) & timeVal, sizeof (struct timeval));
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_SOCKOPT_SNDTIMEO:
|
||||
{
|
||||
struct timeval timeVal;
|
||||
timeVal.tv_sec = value / 1000;
|
||||
timeVal.tv_usec = (value % 1000) * 1000;
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO, (char *) & timeVal, sizeof (struct timeval));
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_SOCKOPT_NODELAY:
|
||||
result = setsockopt (socket, IPPROTO_TCP, TCP_NODELAY, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result == -1 ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_get_option (ENetSocket socket, ENetSocketOption option, int * value)
|
||||
{
|
||||
int result = -1;
|
||||
socklen_t len;
|
||||
switch (option)
|
||||
{
|
||||
case ENET_SOCKOPT_ERROR:
|
||||
len = sizeof (int);
|
||||
result = getsockopt (socket, SOL_SOCKET, SO_ERROR, value, & len);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result == -1 ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_connect (ENetSocket socket, const ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
int result;
|
||||
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
|
||||
result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in));
|
||||
if (result == -1 && errno == EINPROGRESS)
|
||||
return 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ENetSocket
|
||||
enet_socket_accept (ENetSocket socket, ENetAddress * address)
|
||||
{
|
||||
int result;
|
||||
struct sockaddr_in sin;
|
||||
socklen_t sinLength = sizeof (struct sockaddr_in);
|
||||
|
||||
result = accept (socket,
|
||||
address != NULL ? (struct sockaddr *) & sin : NULL,
|
||||
address != NULL ? & sinLength : NULL);
|
||||
|
||||
if (result == -1)
|
||||
return ENET_SOCKET_NULL;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_shutdown (ENetSocket socket, ENetSocketShutdown how)
|
||||
{
|
||||
return shutdown (socket, (int) how);
|
||||
}
|
||||
|
||||
void
|
||||
enet_socket_destroy (ENetSocket socket)
|
||||
{
|
||||
if (socket != -1)
|
||||
close (socket);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_send (ENetSocket socket,
|
||||
const ENetAddress * address,
|
||||
const ENetBuffer * buffers,
|
||||
size_t bufferCount)
|
||||
{
|
||||
struct msghdr msgHdr;
|
||||
struct sockaddr_in sin;
|
||||
int sentLength;
|
||||
|
||||
memset (& msgHdr, 0, sizeof (struct msghdr));
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
|
||||
#if defined(__amigaos4__)
|
||||
msgHdr.msg_name = (char *)& sin;
|
||||
#else
|
||||
msgHdr.msg_name = & sin;
|
||||
#endif
|
||||
msgHdr.msg_namelen = sizeof (struct sockaddr_in);
|
||||
}
|
||||
|
||||
msgHdr.msg_iov = const_cast<struct iovec *>(reinterpret_cast<const struct iovec *>(buffers));
|
||||
msgHdr.msg_iovlen = bufferCount;
|
||||
|
||||
sentLength = sendmsg (socket, & msgHdr, MSG_NOSIGNAL);
|
||||
|
||||
if (sentLength == -1)
|
||||
{
|
||||
if (errno == EWOULDBLOCK)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sentLength;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_receive (ENetSocket socket,
|
||||
ENetAddress * address,
|
||||
ENetBuffer * buffers,
|
||||
size_t bufferCount)
|
||||
{
|
||||
struct msghdr msgHdr;
|
||||
struct sockaddr_in sin;
|
||||
int recvLength;
|
||||
|
||||
memset (& msgHdr, 0, sizeof (struct msghdr));
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
#if defined(__amigaos4__)
|
||||
msgHdr.msg_name = (char *)& sin;
|
||||
#else
|
||||
msgHdr.msg_name = & sin;
|
||||
#endif
|
||||
msgHdr.msg_namelen = sizeof (struct sockaddr_in);
|
||||
}
|
||||
|
||||
msgHdr.msg_iov = (struct iovec *) buffers;
|
||||
msgHdr.msg_iovlen = bufferCount;
|
||||
|
||||
recvLength = recvmsg (socket, & msgHdr, MSG_NOSIGNAL);
|
||||
|
||||
if (recvLength == -1)
|
||||
{
|
||||
if (errno == EWOULDBLOCK)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAS_MSGHDR_FLAGS
|
||||
if (msgHdr.msg_flags & MSG_TRUNC)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
}
|
||||
|
||||
return recvLength;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout)
|
||||
{
|
||||
struct timeval timeVal;
|
||||
|
||||
timeVal.tv_sec = timeout / 1000;
|
||||
timeVal.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout)
|
||||
{
|
||||
#ifdef HAS_POLL
|
||||
struct pollfd pollSocket;
|
||||
int pollCount;
|
||||
|
||||
pollSocket.fd = socket;
|
||||
pollSocket.events = 0;
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_SEND)
|
||||
pollSocket.events |= POLLOUT;
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_RECEIVE)
|
||||
pollSocket.events |= POLLIN;
|
||||
|
||||
pollCount = poll (& pollSocket, 1, timeout);
|
||||
|
||||
if (pollCount < 0)
|
||||
{
|
||||
if (errno == EINTR && * condition & ENET_SOCKET_WAIT_INTERRUPT)
|
||||
{
|
||||
* condition = ENET_SOCKET_WAIT_INTERRUPT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
* condition = ENET_SOCKET_WAIT_NONE;
|
||||
|
||||
if (pollCount == 0)
|
||||
return 0;
|
||||
|
||||
if (pollSocket.revents & POLLOUT)
|
||||
* condition |= ENET_SOCKET_WAIT_SEND;
|
||||
|
||||
if (pollSocket.revents & POLLIN)
|
||||
* condition |= ENET_SOCKET_WAIT_RECEIVE;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
fd_set readSet, writeSet;
|
||||
struct timeval timeVal;
|
||||
int selectCount;
|
||||
|
||||
timeVal.tv_sec = timeout / 1000;
|
||||
timeVal.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
FD_ZERO (& readSet);
|
||||
FD_ZERO (& writeSet);
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_SEND)
|
||||
FD_SET (socket, & writeSet);
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_RECEIVE)
|
||||
FD_SET (socket, & readSet);
|
||||
|
||||
selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal);
|
||||
|
||||
if (selectCount < 0)
|
||||
{
|
||||
if (errno == EINTR && * condition & ENET_SOCKET_WAIT_INTERRUPT)
|
||||
{
|
||||
* condition = ENET_SOCKET_WAIT_INTERRUPT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
* condition = ENET_SOCKET_WAIT_NONE;
|
||||
|
||||
if (selectCount == 0)
|
||||
return 0;
|
||||
|
||||
if (FD_ISSET (socket, & writeSet))
|
||||
* condition |= ENET_SOCKET_WAIT_SEND;
|
||||
|
||||
if (FD_ISSET (socket, & readSet))
|
||||
* condition |= ENET_SOCKET_WAIT_RECEIVE;
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
48
backends/networking/enet/source/unix.h
Normal file
48
backends/networking/enet/source/unix.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
@file unix.h
|
||||
@brief ENet Unix header
|
||||
*/
|
||||
#ifndef __ENET_UNIX_H__
|
||||
#define __ENET_UNIX_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef MSG_MAXIOVLEN
|
||||
#define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN
|
||||
#endif
|
||||
|
||||
typedef int ENetSocket;
|
||||
|
||||
#define ENET_SOCKET_NULL -1
|
||||
|
||||
#define ENET_HOST_TO_NET_16(value) (htons (value)) /**< macro that converts host to net byte-order of a 16-bit value */
|
||||
#define ENET_HOST_TO_NET_32(value) (htonl (value)) /**< macro that converts host to net byte-order of a 32-bit value */
|
||||
|
||||
#define ENET_NET_TO_HOST_16(value) (ntohs (value)) /**< macro that converts net to host byte-order of a 16-bit value */
|
||||
#define ENET_NET_TO_HOST_32(value) (ntohl (value)) /**< macro that converts net to host byte-order of a 32-bit value */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void * data;
|
||||
size_t dataLength;
|
||||
} ENetBuffer;
|
||||
|
||||
#define ENET_CALLBACK
|
||||
|
||||
#define ENET_API extern
|
||||
|
||||
typedef fd_set ENetSocketSet;
|
||||
|
||||
#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset))
|
||||
#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset))
|
||||
#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset))
|
||||
#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset))
|
||||
|
||||
#endif /* __ENET_UNIX_H__ */
|
||||
|
||||
13
backends/networking/enet/source/utility.h
Normal file
13
backends/networking/enet/source/utility.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
@file utility.h
|
||||
@brief ENet utility header
|
||||
*/
|
||||
#ifndef __ENET_UTILITY_H__
|
||||
#define __ENET_UTILITY_H__
|
||||
|
||||
#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||
#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define ENET_DIFFERENCE(x, y) ((x) < (y) ? (y) - (x) : (x) - (y))
|
||||
|
||||
#endif /* __ENET_UTILITY_H__ */
|
||||
|
||||
442
backends/networking/enet/source/win32.cpp
Normal file
442
backends/networking/enet/source/win32.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
/**
|
||||
@file win32.c
|
||||
@brief ENet Win32 system specific functions
|
||||
*/
|
||||
#ifdef WIN32
|
||||
|
||||
#define ENET_BUILDING_LIB 1
|
||||
#include "enet.h"
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
static enet_uint32 timeBase = 0;
|
||||
|
||||
int
|
||||
enet_initialize (void)
|
||||
{
|
||||
WORD versionRequested = MAKEWORD (1, 1);
|
||||
WSADATA wsaData;
|
||||
|
||||
if (WSAStartup (versionRequested, & wsaData))
|
||||
return -1;
|
||||
|
||||
if (LOBYTE (wsaData.wVersion) != 1||
|
||||
HIBYTE (wsaData.wVersion) != 1)
|
||||
{
|
||||
WSACleanup ();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
timeBeginPeriod (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
enet_deinitialize (void)
|
||||
{
|
||||
timeEndPeriod (1);
|
||||
|
||||
WSACleanup ();
|
||||
}
|
||||
|
||||
enet_uint32
|
||||
enet_host_random_seed (void)
|
||||
{
|
||||
return (enet_uint32) timeGetTime ();
|
||||
}
|
||||
|
||||
enet_uint32
|
||||
enet_time_get (void)
|
||||
{
|
||||
return (enet_uint32) timeGetTime () - timeBase;
|
||||
}
|
||||
|
||||
void
|
||||
enet_time_set (enet_uint32 newTimeBase)
|
||||
{
|
||||
timeBase = (enet_uint32) timeGetTime () - newTimeBase;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_set_host_ip (ENetAddress * address, const char * name)
|
||||
{
|
||||
enet_uint8 vals [4] = { 0, 0, 0, 0 };
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; ++ i)
|
||||
{
|
||||
const char * next = name + 1;
|
||||
if (* name != '0')
|
||||
{
|
||||
long val = strtol(name, const_cast<char **>(&next), 10);
|
||||
if (val < 0 || val > 255 || next == name || next - name > 3)
|
||||
return -1;
|
||||
vals [i] = (enet_uint8) val;
|
||||
}
|
||||
|
||||
if (* next != (i < 3 ? '.' : '\0'))
|
||||
return -1;
|
||||
name = next + 1;
|
||||
}
|
||||
|
||||
memcpy (& address -> host, vals, sizeof (enet_uint32));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_set_host (ENetAddress * address, const char * name)
|
||||
{
|
||||
struct hostent * hostEntry;
|
||||
|
||||
hostEntry = gethostbyname (name);
|
||||
if (hostEntry == NULL ||
|
||||
hostEntry -> h_addrtype != AF_INET)
|
||||
return enet_address_set_host_ip (address, name);
|
||||
|
||||
address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength)
|
||||
{
|
||||
char *addr = inet_ntoa(*(struct in_addr *)const_cast<struct in_addr *>(reinterpret_cast<const struct in_addr *>(&address->host)));
|
||||
if (addr == NULL)
|
||||
return -1;
|
||||
else
|
||||
{
|
||||
size_t addrLen = strlen(addr);
|
||||
if (addrLen >= nameLength)
|
||||
return -1;
|
||||
memcpy (name, addr, addrLen + 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength)
|
||||
{
|
||||
struct in_addr in {};
|
||||
struct hostent * hostEntry;
|
||||
|
||||
in.s_addr = address -> host;
|
||||
|
||||
hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET);
|
||||
if (hostEntry == NULL)
|
||||
return enet_address_get_host_ip (address, name, nameLength);
|
||||
else
|
||||
{
|
||||
size_t hostLen = strlen (hostEntry -> h_name);
|
||||
if (hostLen >= nameLength)
|
||||
return -1;
|
||||
memcpy (name, hostEntry -> h_name, hostLen + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_bind (ENetSocket socket, const ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
}
|
||||
else
|
||||
{
|
||||
sin.sin_port = 0;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
}
|
||||
|
||||
return bind (socket,
|
||||
(struct sockaddr *) & sin,
|
||||
sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_get_address (ENetSocket socket, ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin {};
|
||||
int sinLength = sizeof (struct sockaddr_in);
|
||||
|
||||
if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1)
|
||||
return -1;
|
||||
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_listen (ENetSocket socket, int backlog)
|
||||
{
|
||||
return listen (socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
|
||||
ENetSocket
|
||||
enet_socket_create (ENetSocketType type)
|
||||
{
|
||||
return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value)
|
||||
{
|
||||
int result = SOCKET_ERROR;
|
||||
switch (option)
|
||||
{
|
||||
case ENET_SOCKOPT_NONBLOCK:
|
||||
{
|
||||
u_long nonBlocking = (u_long) value;
|
||||
result = ioctlsocket (socket, FIONBIO, & nonBlocking);
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_SOCKOPT_BROADCAST:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_REUSEADDR:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_RCVBUF:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_SNDBUF:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_RCVTIMEO:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_SNDTIMEO:
|
||||
result = setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
case ENET_SOCKOPT_NODELAY:
|
||||
result = setsockopt (socket, IPPROTO_TCP, TCP_NODELAY, (char *) & value, sizeof (int));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_get_option (ENetSocket socket, ENetSocketOption option, int * value)
|
||||
{
|
||||
int result = SOCKET_ERROR, len;
|
||||
switch (option)
|
||||
{
|
||||
case ENET_SOCKOPT_ERROR:
|
||||
len = sizeof(int);
|
||||
result = getsockopt (socket, SOL_SOCKET, SO_ERROR, (char *) value, & len);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_connect (ENetSocket socket, const ENetAddress * address)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
int result;
|
||||
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
|
||||
result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in));
|
||||
if (result == SOCKET_ERROR && WSAGetLastError () != WSAEWOULDBLOCK)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ENetSocket
|
||||
enet_socket_accept (ENetSocket socket, ENetAddress * address)
|
||||
{
|
||||
SOCKET result;
|
||||
struct sockaddr_in sin {};
|
||||
int sinLength = sizeof (struct sockaddr_in);
|
||||
|
||||
result = accept (socket,
|
||||
address != NULL ? (struct sockaddr *) & sin : NULL,
|
||||
address != NULL ? & sinLength : NULL);
|
||||
|
||||
if (result == INVALID_SOCKET)
|
||||
return ENET_SOCKET_NULL;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_shutdown (ENetSocket socket, ENetSocketShutdown how)
|
||||
{
|
||||
return shutdown (socket, (int) how) == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
|
||||
void
|
||||
enet_socket_destroy (ENetSocket socket)
|
||||
{
|
||||
if (socket != INVALID_SOCKET)
|
||||
closesocket (socket);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_send (ENetSocket socket,
|
||||
const ENetAddress * address,
|
||||
const ENetBuffer * buffers,
|
||||
size_t bufferCount)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
DWORD sentLength = 0;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
memset (& sin, 0, sizeof (struct sockaddr_in));
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
|
||||
sin.sin_addr.s_addr = address -> host;
|
||||
}
|
||||
|
||||
if (WSASendTo (socket,
|
||||
reinterpret_cast<LPWSABUF>(const_cast<ENetBuffer *>(buffers)),
|
||||
(DWORD) bufferCount,
|
||||
& sentLength,
|
||||
0,
|
||||
address != NULL ? (struct sockaddr *) & sin : NULL,
|
||||
address != NULL ? sizeof (struct sockaddr_in) : 0,
|
||||
NULL,
|
||||
NULL) == SOCKET_ERROR)
|
||||
{
|
||||
if (WSAGetLastError () == WSAEWOULDBLOCK)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int) sentLength;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_receive (ENetSocket socket,
|
||||
ENetAddress * address,
|
||||
ENetBuffer * buffers,
|
||||
size_t bufferCount)
|
||||
{
|
||||
INT sinLength = sizeof (struct sockaddr_in);
|
||||
DWORD flags = 0,
|
||||
recvLength = 0;
|
||||
struct sockaddr_in sin {};
|
||||
|
||||
if (WSARecvFrom (socket,
|
||||
(LPWSABUF) buffers,
|
||||
(DWORD) bufferCount,
|
||||
& recvLength,
|
||||
& flags,
|
||||
address != NULL ? (struct sockaddr *) & sin : NULL,
|
||||
address != NULL ? & sinLength : NULL,
|
||||
NULL,
|
||||
NULL) == SOCKET_ERROR)
|
||||
{
|
||||
switch (WSAGetLastError ())
|
||||
{
|
||||
case WSAEWOULDBLOCK:
|
||||
case WSAECONNRESET:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flags & MSG_PARTIAL)
|
||||
return -1;
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
address -> host = (enet_uint32) sin.sin_addr.s_addr;
|
||||
address -> port = ENET_NET_TO_HOST_16 (sin.sin_port);
|
||||
}
|
||||
|
||||
return (int) recvLength;
|
||||
}
|
||||
|
||||
int
|
||||
enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout)
|
||||
{
|
||||
struct timeval timeVal {};
|
||||
|
||||
timeVal.tv_sec = timeout / 1000;
|
||||
timeVal.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal);
|
||||
}
|
||||
|
||||
int
|
||||
enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout)
|
||||
{
|
||||
fd_set readSet {}, writeSet{};
|
||||
struct timeval timeVal {};
|
||||
int selectCount;
|
||||
|
||||
timeVal.tv_sec = timeout / 1000;
|
||||
timeVal.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
FD_ZERO (& readSet);
|
||||
FD_ZERO (& writeSet);
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_SEND)
|
||||
FD_SET (socket, & writeSet);
|
||||
|
||||
if (* condition & ENET_SOCKET_WAIT_RECEIVE)
|
||||
FD_SET (socket, & readSet);
|
||||
|
||||
selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal);
|
||||
|
||||
if (selectCount < 0)
|
||||
return -1;
|
||||
|
||||
* condition = ENET_SOCKET_WAIT_NONE;
|
||||
|
||||
if (selectCount == 0)
|
||||
return 0;
|
||||
|
||||
if (FD_ISSET (socket, & writeSet))
|
||||
* condition |= ENET_SOCKET_WAIT_SEND;
|
||||
|
||||
if (FD_ISSET (socket, & readSet))
|
||||
* condition |= ENET_SOCKET_WAIT_RECEIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
59
backends/networking/enet/source/win32.h
Normal file
59
backends/networking/enet/source/win32.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
@file win32.h
|
||||
@brief ENet Win32 header
|
||||
*/
|
||||
#ifndef __ENET_WIN32_H__
|
||||
#define __ENET_WIN32_H__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef ENET_BUILDING_LIB
|
||||
#pragma warning (disable: 4267) // size_t to int conversion
|
||||
#pragma warning (disable: 4244) // 64bit to 32bit int
|
||||
#pragma warning (disable: 4018) // signed/unsigned mismatch
|
||||
#pragma warning (disable: 4146) // unary minus operator applied to unsigned type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
|
||||
typedef SOCKET ENetSocket;
|
||||
|
||||
#define ENET_SOCKET_NULL INVALID_SOCKET
|
||||
|
||||
#define ENET_HOST_TO_NET_16(value) (htons (value))
|
||||
#define ENET_HOST_TO_NET_32(value) (htonl (value))
|
||||
|
||||
#define ENET_NET_TO_HOST_16(value) (ntohs (value))
|
||||
#define ENET_NET_TO_HOST_32(value) (ntohl (value))
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t dataLength;
|
||||
void * data;
|
||||
} ENetBuffer;
|
||||
|
||||
#define ENET_CALLBACK __cdecl
|
||||
|
||||
#ifdef ENET_DLL
|
||||
#ifdef ENET_BUILDING_LIB
|
||||
#define ENET_API __declspec( dllexport )
|
||||
#else
|
||||
#define ENET_API __declspec( dllimport )
|
||||
#endif /* ENET_BUILDING_LIB */
|
||||
#else /* !ENET_DLL */
|
||||
#define ENET_API extern
|
||||
#endif /* ENET_DLL */
|
||||
|
||||
typedef fd_set ENetSocketSet;
|
||||
|
||||
#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset))
|
||||
#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset))
|
||||
#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset))
|
||||
#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset))
|
||||
|
||||
#endif /* __ENET_WIN32_H__ */
|
||||
|
||||
|
||||
113
backends/networking/http/android/connectionmanager-android.cpp
Normal file
113
backends/networking/http/android/connectionmanager-android.cpp
Normal 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
|
||||
46
backends/networking/http/android/connectionmanager-android.h
Normal file
46
backends/networking/http/android/connectionmanager-android.h
Normal 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
|
||||
408
backends/networking/http/android/networkreadstream-android.cpp
Normal file
408
backends/networking/http/android/networkreadstream-android.cpp
Normal 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
|
||||
82
backends/networking/http/android/networkreadstream-android.h
Normal file
82
backends/networking/http/android/networkreadstream-android.h
Normal 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
|
||||
167
backends/networking/http/connectionmanager.cpp
Normal file
167
backends/networking/http/connectionmanager.cpp
Normal 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
|
||||
115
backends/networking/http/connectionmanager.h
Normal file
115
backends/networking/http/connectionmanager.h
Normal 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
|
||||
92
backends/networking/http/curl/connectionmanager-curl.cpp
Normal file
92
backends/networking/http/curl/connectionmanager-curl.cpp
Normal 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
|
||||
54
backends/networking/http/curl/connectionmanager-curl.h
Normal file
54
backends/networking/http/curl/connectionmanager-curl.h
Normal 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
|
||||
394
backends/networking/http/curl/networkreadstream-curl.cpp
Normal file
394
backends/networking/http/curl/networkreadstream-curl.cpp
Normal 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
|
||||
97
backends/networking/http/curl/networkreadstream-curl.h
Normal file
97
backends/networking/http/curl/networkreadstream-curl.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
190
backends/networking/http/httpjsonrequest.cpp
Normal file
190
backends/networking/http/httpjsonrequest.cpp
Normal 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
|
||||
64
backends/networking/http/httpjsonrequest.h
Normal file
64
backends/networking/http/httpjsonrequest.h
Normal 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
|
||||
160
backends/networking/http/httprequest.cpp
Normal file
160
backends/networking/http/httprequest.cpp
Normal 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
|
||||
110
backends/networking/http/httprequest.h
Normal file
110
backends/networking/http/httprequest.h
Normal 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
|
||||
70
backends/networking/http/networkreadstream.cpp
Normal file
70
backends/networking/http/networkreadstream.cpp
Normal 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
|
||||
162
backends/networking/http/networkreadstream.h
Normal file
162
backends/networking/http/networkreadstream.h
Normal 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
|
||||
128
backends/networking/http/postrequest.cpp
Normal file
128
backends/networking/http/postrequest.cpp
Normal 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
|
||||
63
backends/networking/http/postrequest.h
Normal file
63
backends/networking/http/postrequest.h
Normal 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
|
||||
73
backends/networking/http/request.cpp
Normal file
73
backends/networking/http/request.cpp
Normal 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
|
||||
202
backends/networking/http/request.h
Normal file
202
backends/networking/http/request.h
Normal 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
|
||||
88
backends/networking/http/session.cpp
Normal file
88
backends/networking/http/session.cpp
Normal 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
|
||||
54
backends/networking/http/session.h
Normal file
54
backends/networking/http/session.h
Normal 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
|
||||
260
backends/networking/http/sessionrequest.cpp
Normal file
260
backends/networking/http/sessionrequest.cpp
Normal 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
|
||||
103
backends/networking/http/sessionrequest.h
Normal file
103
backends/networking/http/sessionrequest.h
Normal 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
|
||||
198
backends/networking/sdl_net/client.cpp
Normal file
198
backends/networking/sdl_net/client.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/* 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
|
||||
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/memstream.h"
|
||||
#include <SDL_net.h>
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Client::Client():
|
||||
_state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
|
||||
_previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {}
|
||||
|
||||
Client::Client(SDLNet_SocketSet set, TCPsocket socket):
|
||||
_state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
|
||||
_previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {
|
||||
open(set, socket);
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
close();
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
void Client::open(SDLNet_SocketSet set, TCPsocket socket) {
|
||||
if (_state != INVALID)
|
||||
close();
|
||||
_state = READING_HEADERS;
|
||||
_socket = socket;
|
||||
_set = set;
|
||||
Reader cleanReader;
|
||||
_reader = cleanReader;
|
||||
if (_handler)
|
||||
delete _handler;
|
||||
_handler = nullptr;
|
||||
if (_previousHandler)
|
||||
delete _previousHandler;
|
||||
_previousHandler = nullptr;
|
||||
_stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
if (set) {
|
||||
int numused = SDLNet_TCP_AddSocket(set, socket);
|
||||
if (numused == -1) {
|
||||
error("Client: SDLNet_AddSocket: %s\n", SDLNet_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::readMoreIfNeeded() {
|
||||
if (_stream == nullptr)
|
||||
return false; //nothing to read into
|
||||
if (_stream->size() - _stream->pos() > 0)
|
||||
return true; //not needed, some data left in the stream
|
||||
if (!_socket)
|
||||
return false;
|
||||
if (!SDLNet_SocketReady(_socket))
|
||||
return false;
|
||||
|
||||
int bytes = SDLNet_TCP_Recv(_socket, _buffer, CLIENT_BUFFER_SIZE);
|
||||
if (bytes <= 0) {
|
||||
warning("Client::readMoreIfNeeded: recv fail");
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_stream->write(_buffer, bytes) != (uint32)bytes) {
|
||||
warning("Client::readMoreIfNeeded: failed to write() into MemoryReadWriteStream");
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Client::readHeaders() {
|
||||
if (!readMoreIfNeeded())
|
||||
return;
|
||||
_reader.setContent(_stream);
|
||||
if (_reader.readFirstHeaders())
|
||||
_state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS);
|
||||
}
|
||||
|
||||
bool Client::readContent(Common::WriteStream *stream) {
|
||||
if (!readMoreIfNeeded())
|
||||
return false;
|
||||
_reader.setMode(RM_HTTP_GENERIC);
|
||||
_reader.setContent(_stream);
|
||||
return _reader.readContent(stream);
|
||||
}
|
||||
|
||||
bool Client::readFirstContent(Common::WriteStream *stream) {
|
||||
if (!readMoreIfNeeded())
|
||||
return false;
|
||||
_reader.setMode(RM_POST_FORM_MULTIPART);
|
||||
_reader.setContent(_stream);
|
||||
return _reader.readFirstContent(stream);
|
||||
}
|
||||
|
||||
bool Client::readBlockHeaders(Common::WriteStream *stream) {
|
||||
if (!readMoreIfNeeded())
|
||||
return false;
|
||||
_reader.setContent(_stream);
|
||||
return _reader.readBlockHeaders(stream);
|
||||
}
|
||||
|
||||
bool Client::readBlockContent(Common::WriteStream *stream) {
|
||||
if (!readMoreIfNeeded())
|
||||
return false;
|
||||
_reader.setContent(_stream);
|
||||
return _reader.readBlockContent(stream);
|
||||
}
|
||||
|
||||
void Client::setHandler(ClientHandler *handler) {
|
||||
if (_handler) {
|
||||
if (_previousHandler)
|
||||
delete _previousHandler;
|
||||
_previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself
|
||||
}
|
||||
_state = BEING_HANDLED;
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
void Client::handle() {
|
||||
if (_state != BEING_HANDLED)
|
||||
warning("handle() called in a wrong Client's state");
|
||||
if (!_handler)
|
||||
warning("Client doesn't have handler to be handled by");
|
||||
if (_handler)
|
||||
_handler->handle(this);
|
||||
}
|
||||
|
||||
void Client::close() {
|
||||
if (_set) {
|
||||
if (_socket) {
|
||||
int numused = SDLNet_TCP_DelSocket(_set, _socket);
|
||||
if (numused == -1)
|
||||
error("Client: SDLNet_DelSocket: %s\n", SDLNet_GetError());
|
||||
}
|
||||
_set = nullptr;
|
||||
}
|
||||
|
||||
if (_socket) {
|
||||
SDLNet_TCP_Close(_socket);
|
||||
_socket = nullptr;
|
||||
}
|
||||
|
||||
if (_stream) {
|
||||
delete _stream;
|
||||
_stream = nullptr;
|
||||
}
|
||||
|
||||
_state = INVALID;
|
||||
}
|
||||
|
||||
|
||||
ClientState Client::state() const { return _state; }
|
||||
|
||||
Common::String Client::headers() const { return _reader.headers(); }
|
||||
|
||||
Common::String Client::method() const { return _reader.method(); }
|
||||
|
||||
Common::String Client::path() const { return _reader.path(); }
|
||||
|
||||
Common::String Client::query() const { return _reader.query(); }
|
||||
|
||||
Common::String Client::queryParameter(const Common::String &name) const { return _reader.queryParameter(name); }
|
||||
|
||||
Common::String Client::anchor() const { return _reader.anchor(); }
|
||||
|
||||
bool Client::noMoreContent() const { return _reader.noMoreContent(); }
|
||||
|
||||
bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); }
|
||||
|
||||
int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); }
|
||||
|
||||
int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); }
|
||||
|
||||
} // End of namespace Networking
|
||||
125
backends/networking/sdl_net/client.h
Normal file
125
backends/networking/sdl_net/client.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/* 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_SDL_NET_CLIENT_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_CLIENT_H
|
||||
|
||||
#include "backends/networking/sdl_net/reader.h"
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Common {
|
||||
class MemoryReadWriteStream;
|
||||
}
|
||||
|
||||
typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
|
||||
typedef struct _TCPsocket *TCPsocket;
|
||||
|
||||
namespace Networking {
|
||||
|
||||
enum ClientState {
|
||||
INVALID,
|
||||
READING_HEADERS,
|
||||
READ_HEADERS,
|
||||
BAD_REQUEST,
|
||||
BEING_HANDLED
|
||||
};
|
||||
|
||||
class Client;
|
||||
|
||||
#define CLIENT_BUFFER_SIZE 1 * 1024 * 1024
|
||||
|
||||
class ClientHandler {
|
||||
public:
|
||||
virtual ~ClientHandler() {};
|
||||
virtual void handle(Client *client) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client class represents one client's HTTP request
|
||||
* to the LocalWebserver.
|
||||
*
|
||||
* While in READING_HEADERS state, it's kept in LocalWebserver.
|
||||
* Client must read the headers and decide whether it's
|
||||
* READ_HEADERS (could be handled) or BAD_REQUEST (failed).
|
||||
*
|
||||
* If it's READ_HEADERS, LocalWebserver searches for a corresponding
|
||||
* BaseHandler. These classes use the information from headers -
|
||||
* like method, path, GET parameters - to build the response
|
||||
* for this client's request. When they do, they call setHandler()
|
||||
* and pass a special ClientHandler. Client becomes BEING_HANDLED.
|
||||
*
|
||||
* While in that state, LocalWebserver calls Client's handle() and
|
||||
* it's passed to ClientHandler. The latter does the job: it commands
|
||||
* Client to read or write bytes with its socket or calls
|
||||
* readContent() methods, so Client reads the request through Reader.
|
||||
*/
|
||||
|
||||
class Client {
|
||||
ClientState _state;
|
||||
SDLNet_SocketSet _set;
|
||||
TCPsocket _socket;
|
||||
Reader _reader;
|
||||
ClientHandler *_handler, *_previousHandler;
|
||||
Common::MemoryReadWriteStream *_stream;
|
||||
byte *_buffer;
|
||||
|
||||
bool readMoreIfNeeded();
|
||||
|
||||
public:
|
||||
Client();
|
||||
Client(SDLNet_SocketSet set, TCPsocket socket);
|
||||
virtual ~Client();
|
||||
|
||||
void open(SDLNet_SocketSet set, TCPsocket socket);
|
||||
void readHeaders();
|
||||
bool readContent(Common::WriteStream *stream);
|
||||
bool readFirstContent(Common::WriteStream *stream);
|
||||
bool readBlockHeaders(Common::WriteStream *stream);
|
||||
bool readBlockContent(Common::WriteStream *stream);
|
||||
void setHandler(ClientHandler *handler);
|
||||
void handle();
|
||||
void close();
|
||||
|
||||
ClientState state() const;
|
||||
Common::String headers() const;
|
||||
Common::String method() const;
|
||||
Common::String path() const;
|
||||
Common::String query() const;
|
||||
Common::String queryParameter(const Common::String &name) const;
|
||||
Common::String anchor() const;
|
||||
|
||||
bool noMoreContent() const;
|
||||
|
||||
/**
|
||||
* Return SDLNet_SocketReady(_socket).
|
||||
*
|
||||
* It's "ready" when it has something
|
||||
* to read (recv()). You can send()
|
||||
* when this is false.
|
||||
*/
|
||||
bool socketIsReady();
|
||||
int recv(void *data, int maxlen);
|
||||
int send(void *data, int len);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
161
backends/networking/sdl_net/getclienthandler.cpp
Normal file
161
backends/networking/sdl_net/getclienthandler.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
/* 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/sdl_net/getclienthandler.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream):
|
||||
_responseCode(200), _headersPrepared(false),
|
||||
_stream(stream), _buffer(new byte[CLIENT_HANDLER_BUFFER_SIZE]) {}
|
||||
|
||||
GetClientHandler::~GetClientHandler() {
|
||||
delete _stream;
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
const char *GetClientHandler::responseMessage(long responseCode) {
|
||||
switch (responseCode) {
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 226: return "IM Used";
|
||||
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Moved Temporarily"; //case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "RESERVED";
|
||||
case 307: return "Temporary Redirect";
|
||||
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-URI Too Large";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 425: return "Unordered Collection";
|
||||
case 426: return "Upgrade Required";
|
||||
case 428: return "Precondition Required";
|
||||
case 429: return "Too Many Requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 434: return "Requested Host Unavailable";
|
||||
case 449: return "Retry With";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 506: return "Variant Also Negotiates";
|
||||
case 507: return "Insufficient Storage";
|
||||
case 508: return "Loop Detected";
|
||||
case 509: return "Bandwidth Limit Exceeded";
|
||||
case 510: return "Not Extended";
|
||||
case 511: return "Network Authentication Required";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
void GetClientHandler::prepareHeaders() {
|
||||
if (!_specialHeaders.contains("Content-Type"))
|
||||
setHeader("Content-Type", "text/html; charset=UTF-8");
|
||||
|
||||
if (!_specialHeaders.contains("Content-Length") && _stream)
|
||||
setHeader("Content-Length", Common::String::format("%u", unsigned(_stream->size())));
|
||||
|
||||
_headers = Common::String::format("HTTP/1.1 %ld %s\r\n", _responseCode, responseMessage(_responseCode));
|
||||
for (auto &specialHeader : _specialHeaders)
|
||||
_headers += specialHeader._key + ": " + specialHeader._value + "\r\n";
|
||||
_headers += "\r\n";
|
||||
|
||||
_headersPrepared = true;
|
||||
}
|
||||
|
||||
void GetClientHandler::handle(Client *client) {
|
||||
if (!client)
|
||||
return;
|
||||
if (!_headersPrepared)
|
||||
prepareHeaders();
|
||||
|
||||
uint32 readBytes;
|
||||
|
||||
// send headers first
|
||||
if (_headers.size() > 0) {
|
||||
readBytes = _headers.size();
|
||||
if (readBytes > CLIENT_HANDLER_BUFFER_SIZE)
|
||||
readBytes = CLIENT_HANDLER_BUFFER_SIZE;
|
||||
memcpy(_buffer, _headers.c_str(), readBytes);
|
||||
_headers.erase(0, readBytes);
|
||||
} else {
|
||||
if (!_stream) {
|
||||
client->close();
|
||||
return;
|
||||
}
|
||||
|
||||
readBytes = _stream->read(_buffer, CLIENT_HANDLER_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (readBytes != 0)
|
||||
if (client->send(_buffer, readBytes) != (int)readBytes) {
|
||||
warning("GetClientHandler: unable to send all bytes to the client");
|
||||
client->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// we're done here!
|
||||
if (_stream->eos())
|
||||
client->close();
|
||||
}
|
||||
|
||||
void GetClientHandler::setHeader(const Common::String &name, const Common::String &value) { _specialHeaders[name] = value; }
|
||||
void GetClientHandler::setResponseCode(long code) { _responseCode = code; }
|
||||
|
||||
} // End of namespace Networking
|
||||
56
backends/networking/sdl_net/getclienthandler.h
Normal file
56
backends/networking/sdl_net/getclienthandler.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* 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_SDL_NET_GETCLIENTHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/hash-str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define CLIENT_HANDLER_BUFFER_SIZE 1 * 1024 * 1024
|
||||
|
||||
class GetClientHandler: public ClientHandler {
|
||||
Common::HashMap<Common::String, Common::String> _specialHeaders;
|
||||
long _responseCode;
|
||||
bool _headersPrepared;
|
||||
Common::String _headers;
|
||||
Common::SeekableReadStream *_stream;
|
||||
byte *_buffer;
|
||||
|
||||
static const char *responseMessage(long responseCode);
|
||||
void prepareHeaders();
|
||||
|
||||
public:
|
||||
GetClientHandler(Common::SeekableReadStream *stream);
|
||||
~GetClientHandler() override;
|
||||
|
||||
void handle(Client *client) override;
|
||||
void setHeader(const Common::String &name, const Common::String &value);
|
||||
void setResponseCode(long code);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
40
backends/networking/sdl_net/handlers/basehandler.h
Normal file
40
backends/networking/sdl_net/handlers/basehandler.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/* 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_SDL_NET_BASEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class BaseHandler {
|
||||
public:
|
||||
BaseHandler() {}
|
||||
virtual ~BaseHandler() {}
|
||||
|
||||
virtual void handle(Client &) = 0;
|
||||
virtual bool minimalModeSupported() { return false; }
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
144
backends/networking/sdl_net/handlers/connectcloudhandler.cpp
Normal file
144
backends/networking/sdl_net/handlers/connectcloudhandler.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/* 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/sdl_net/handlers/connectcloudhandler.h"
|
||||
#include "backends/fs/fs-factory.h"
|
||||
#include "backends/cloud/cloudmanager.h"
|
||||
#include "backends/networking/http/httpjsonrequest.h"
|
||||
#include "backends/networking/sdl_net/getclienthandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "backends/networking/sdl_net/reader.h"
|
||||
#include "common/formats/json.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/callback.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
ConnectCloudHandler::ConnectCloudHandler() : _storageConnectionCallback(nullptr) {}
|
||||
|
||||
ConnectCloudHandler::~ConnectCloudHandler() {}
|
||||
|
||||
void ConnectCloudHandler::handle(Client &client) {
|
||||
client.setHandler(new ConnectCloudClientHandler(this));
|
||||
}
|
||||
|
||||
void ConnectCloudHandler::storageConnected(const Networking::ErrorResponse &response) const {
|
||||
if (_storageConnectionCallback)
|
||||
(*_storageConnectionCallback)(response);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
ConnectCloudClientHandler::ConnectCloudClientHandler(const ConnectCloudHandler *cloudHandler):
|
||||
_cloudHandler(cloudHandler), _clientContent(DisposeAfterUse::YES), _client(nullptr) {}
|
||||
|
||||
ConnectCloudClientHandler::~ConnectCloudClientHandler() {}
|
||||
|
||||
void ConnectCloudClientHandler::respond(Client &client, const Common::String &response, long responseCode) const {
|
||||
Common::SeekableReadStream *responseStream = HandlerUtils::makeResponseStreamFromString(response);
|
||||
GetClientHandler *responseHandler = new GetClientHandler(responseStream);
|
||||
responseHandler->setResponseCode(responseCode);
|
||||
responseHandler->setHeader("Access-Control-Allow-Origin", "https://cloud.scummvm.org");
|
||||
responseHandler->setHeader("Access-Control-Allow-Methods", "POST");
|
||||
responseHandler->setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
client.setHandler(responseHandler);
|
||||
}
|
||||
|
||||
void ConnectCloudClientHandler::respondWithJson(Client &client, bool error, const Common::String &message, long responseCode) const {
|
||||
Common::JSONObject response;
|
||||
response.setVal("error", new Common::JSONValue(error));
|
||||
response.setVal("message", new Common::JSONValue(message));
|
||||
|
||||
Common::JSONValue json = response;
|
||||
respond(client, json.stringify(true), responseCode);
|
||||
}
|
||||
|
||||
void ConnectCloudClientHandler::handleError(Client &client, const Common::String &message, long responseCode) const {
|
||||
respondWithJson(client, true, message, responseCode);
|
||||
}
|
||||
|
||||
void ConnectCloudClientHandler::handleSuccess(Client &client, const Common::String &message) const {
|
||||
respondWithJson(client, false, message);
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void ConnectCloudClientHandler::handle(Client *client) {
|
||||
if (client == nullptr) {
|
||||
warning("ConnectCloudClientHandler::handle(): empty client pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
_client = client;
|
||||
|
||||
if (client->method() == "OPTIONS") {
|
||||
respond(*client, "", 200);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client->method() != "POST") {
|
||||
handleError(*client, "Method Not Allowed", 405);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_clientContent.size() > SUSPICIOUS_CONTENT_SIZE) {
|
||||
handleError(*client, "Bad Request", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!client->readContent(&_clientContent))
|
||||
return;
|
||||
|
||||
char *contents = Common::JSON::zeroTerminateContents(_clientContent);
|
||||
Common::JSONValue *json = Common::JSON::parse(contents);
|
||||
if (json == nullptr) {
|
||||
handleError(*client, "Not Acceptable", 406);
|
||||
return;
|
||||
}
|
||||
|
||||
Networking::ErrorCallback callback = new Common::Callback<ConnectCloudClientHandler, const Networking::ErrorResponse &>(this, &ConnectCloudClientHandler::storageConnectionCallback);
|
||||
Networking::JsonResponse jsonResponse(nullptr, json);
|
||||
if (!CloudMan.connectStorage(jsonResponse, callback)) { // JSON doesn't have "storage" in it or it was invalid
|
||||
delete json;
|
||||
delete callback;
|
||||
handleError(*client, "Not Acceptable", 406);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectCloudClientHandler::storageConnectionCallback(const Networking::ErrorResponse &response) {
|
||||
if (response.failed || response.interrupted) {
|
||||
Common::String message = "Failed to connect storage.";
|
||||
if (response.failed) {
|
||||
message += " Context: ";
|
||||
message += response.response.c_str();
|
||||
}
|
||||
|
||||
handleError(*_client, message, 200);
|
||||
} else {
|
||||
handleSuccess(*_client, "Storage connected.");
|
||||
}
|
||||
|
||||
_cloudHandler->storageConnected(response);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
70
backends/networking/sdl_net/handlers/connectcloudhandler.h
Normal file
70
backends/networking/sdl_net/handlers/connectcloudhandler.h
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_SDL_NET_CONNECTCLOUDHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_CONNECTCLOUDHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/basehandler.h"
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
#include "backends/networking/http/request.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ConnectCloudHandler: public BaseHandler {
|
||||
void handleError(Client &client, const Common::String &message) const;
|
||||
void setJsonResponseHandler(Client &client, const Common::String &type, const Common::String &message) const;
|
||||
|
||||
Networking::ErrorCallback _storageConnectionCallback;
|
||||
|
||||
public:
|
||||
ConnectCloudHandler();
|
||||
~ConnectCloudHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
bool minimalModeSupported() override { return true; }
|
||||
|
||||
void setStorageConnectionCallback(Networking::ErrorCallback cb) { _storageConnectionCallback = cb; }
|
||||
void storageConnected(const Networking::ErrorResponse& response) const;
|
||||
};
|
||||
|
||||
class ConnectCloudClientHandler : public ClientHandler {
|
||||
const ConnectCloudHandler *_cloudHandler;
|
||||
Common::MemoryWriteStreamDynamic _clientContent;
|
||||
Client *_client;
|
||||
static const int32 SUSPICIOUS_CONTENT_SIZE = 640 * 1024; // 640K ought to be enough for anybody
|
||||
|
||||
void respond(Client &client, const Common::String &response, long responseCode = 200) const;
|
||||
void respondWithJson(Client &client, bool error, const Common::String &message, long responseCode = 200) const;
|
||||
void handleError(Client &client, const Common::String &message, long responseCode) const;
|
||||
void handleSuccess(Client &client, const Common::String &message) const;
|
||||
void storageConnectionCallback(const Networking::ErrorResponse &response);
|
||||
|
||||
public:
|
||||
ConnectCloudClientHandler(const ConnectCloudHandler* cloudHandler);
|
||||
~ConnectCloudClientHandler() override;
|
||||
|
||||
void handle(Client *client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
126
backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
Normal file
126
backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/* 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/sdl_net/handlers/createdirectoryhandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/callback.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
CreateDirectoryHandler::CreateDirectoryHandler() {}
|
||||
|
||||
CreateDirectoryHandler::~CreateDirectoryHandler() {}
|
||||
|
||||
void CreateDirectoryHandler::handleError(Client &client, const Common::String &message) const {
|
||||
if (client.queryParameter("answer_json") == "true")
|
||||
setJsonResponseHandler(client, "error", message);
|
||||
else
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
|
||||
}
|
||||
|
||||
void CreateDirectoryHandler::setJsonResponseHandler(Client &client, const Common::String &type, const Common::String &message) const {
|
||||
Common::JSONObject response;
|
||||
response.setVal("type", new Common::JSONValue(type));
|
||||
response.setVal("message", new Common::JSONValue(message));
|
||||
|
||||
Common::JSONValue json = response;
|
||||
LocalWebserver::setClientGetHandler(client, json.stringify(true));
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void CreateDirectoryHandler::handle(Client &client) {
|
||||
Common::String path = client.queryParameter("path");
|
||||
Common::String name = client.queryParameter("directory_name");
|
||||
|
||||
// check that <path> is not an absolute root
|
||||
if (path == "" || path == "/") {
|
||||
handleError(client, Common::convertFromU32String(_("Can't create directory here!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> contains no '../'
|
||||
if (HandlerUtils::hasForbiddenCombinations(path)) {
|
||||
handleError(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// transform virtual path to actual file system one
|
||||
Common::String basePath;
|
||||
Common::Path baseFSPath, fsPath;
|
||||
if (!urlToPath(path, fsPath, basePath, baseFSPath)) {
|
||||
handleError(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> exists, is directory and isn't forbidden
|
||||
Common::FSNode node(fsPath);
|
||||
if (!HandlerUtils::permittedPath(node.getPath())) {
|
||||
handleError(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
if (!node.exists()) {
|
||||
handleError(client, Common::convertFromU32String(_("Parent directory doesn't exists!")));
|
||||
return;
|
||||
}
|
||||
if (!node.isDirectory()) {
|
||||
handleError(client, Common::convertFromU32String(_("Can't create a directory within a file!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <directory_name> doesn't exist or is directory
|
||||
node = Common::FSNode(fsPath.appendComponent(name));
|
||||
if (node.exists()) {
|
||||
if (!node.isDirectory()) {
|
||||
handleError(client, Common::convertFromU32String(_("There is a file with that name in the parent directory!")));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// create the <directory_name> in <path>
|
||||
if (!node.createDirectory()) {
|
||||
handleError(client, Common::convertFromU32String(_("Failed to create the directory!")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if json requested, respond with it
|
||||
if (client.queryParameter("answer_json") == "true") {
|
||||
setJsonResponseHandler(client, "success", Common::convertFromU32String(_("Directory created successfully!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// set redirect on success
|
||||
HandlerUtils::setMessageHandler(
|
||||
client,
|
||||
Common::String::format(
|
||||
"%s<br/><a href=\"files?path=%s\">%s</a>",
|
||||
Common::convertFromU32String(_("Directory created successfully!")).c_str(),
|
||||
client.queryParameter("path").c_str(),
|
||||
Common::convertFromU32String(_("Back to parent directory")).c_str()
|
||||
),
|
||||
(client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
|
||||
LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
|
||||
);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
@@ -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_SDL_NET_CREATEDIRECTORYHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class CreateDirectoryHandler: public FilesBaseHandler {
|
||||
void handleError(Client &client, const Common::String &message) const;
|
||||
void setJsonResponseHandler(Client &client, const Common::String &type, const Common::String &message) const;
|
||||
public:
|
||||
CreateDirectoryHandler();
|
||||
~CreateDirectoryHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
87
backends/networking/sdl_net/handlers/downloadfilehandler.cpp
Normal file
87
backends/networking/sdl_net/handlers/downloadfilehandler.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/* 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/sdl_net/handlers/downloadfilehandler.h"
|
||||
#include "backends/networking/sdl_net/getclienthandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
DownloadFileHandler::DownloadFileHandler() {}
|
||||
|
||||
DownloadFileHandler::~DownloadFileHandler() {}
|
||||
|
||||
/// public
|
||||
|
||||
void DownloadFileHandler::handle(Client &client) {
|
||||
Common::String path = client.queryParameter("path");
|
||||
|
||||
// check that <path> is not an absolute root
|
||||
if (path == "" || path == "/") {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> contains no '../'
|
||||
if (HandlerUtils::hasForbiddenCombinations(path)) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// transform virtual path to actual file system one
|
||||
Common::String basePath;
|
||||
Common::Path baseFSPath, fsPath;
|
||||
if (!urlToPath(path, fsPath, basePath, baseFSPath, false) || path.empty()) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> exists, is directory and isn't forbidden
|
||||
Common::FSNode node(fsPath);
|
||||
if (!HandlerUtils::permittedPath(node.getPath())) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
if (!node.exists()) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("The file doesn't exist!")));
|
||||
return;
|
||||
}
|
||||
if (node.isDirectory()) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Can't download a directory!")));
|
||||
return;
|
||||
}
|
||||
Common::SeekableReadStream *stream = node.createReadStream();
|
||||
if (stream == nullptr) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Failed to read the file!")));
|
||||
return;
|
||||
}
|
||||
|
||||
GetClientHandler *handler = new GetClientHandler(stream);
|
||||
handler->setResponseCode(200);
|
||||
handler->setHeader("Content-Type", "application/force-download");
|
||||
handler->setHeader("Content-Disposition", "attachment; filename=\"" + node.getName() + "\"");
|
||||
handler->setHeader("Content-Transfer-Encoding", "binary");
|
||||
client.setHandler(handler);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
39
backends/networking/sdl_net/handlers/downloadfilehandler.h
Normal file
39
backends/networking/sdl_net/handlers/downloadfilehandler.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* 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_SDL_NET_DOWNLOADFILEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class DownloadFileHandler: public FilesBaseHandler {
|
||||
public:
|
||||
DownloadFileHandler();
|
||||
~DownloadFileHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
/* 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/sdl_net/handlers/filesajaxpagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define FILES_PAGE_NAME ".filesAJAX.html"
|
||||
|
||||
FilesAjaxPageHandler::FilesAjaxPageHandler() {}
|
||||
|
||||
FilesAjaxPageHandler::~FilesAjaxPageHandler() {}
|
||||
|
||||
namespace {
|
||||
|
||||
Common::String encodeDoubleQuotesAndSlashes(const Common::String &s) {
|
||||
Common::String result = "";
|
||||
for (uint32 i = 0; i < s.size(); ++i)
|
||||
if (s[i] == '"') {
|
||||
result += "\\\"";
|
||||
} else if (s[i] == '\\') {
|
||||
result += "\\\\";
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void FilesAjaxPageHandler::handle(Client &client) {
|
||||
// load stylish response page from the archive
|
||||
Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
|
||||
if (stream == nullptr) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("The page is not available without the resources. Make sure file wwwroot.zip from ScummVM distribution is available in 'themepath'.")));
|
||||
return;
|
||||
}
|
||||
|
||||
Common::String response = HandlerUtils::readEverythingFromStream(stream);
|
||||
Common::String path = client.queryParameter("path");
|
||||
|
||||
//these occur twice:
|
||||
replace(response, "{create_directory_button}", _("Create directory").encode());
|
||||
replace(response, "{create_directory_button}", _("Create directory").encode());
|
||||
replace(response, "{upload_files_button}", _("Upload files").encode()); //tab
|
||||
replace(response, "{upload_file_button}", _("Upload files").encode()); //button in the tab
|
||||
replace(response, "{create_directory_desc}", _("Type new directory name:").encode());
|
||||
replace(response, "{upload_file_desc}", _("Select a file to upload:").encode());
|
||||
replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):").encode());
|
||||
replace(response, "{index_of}", _("Index of ").encode());
|
||||
replace(response, "{loading}", ("Loading..."));
|
||||
replace(response, "{error}", _("Error occurred").encode());
|
||||
replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path));
|
||||
LocalWebserver::setClientGetHandler(client, response);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
39
backends/networking/sdl_net/handlers/filesajaxpagehandler.h
Normal file
39
backends/networking/sdl_net/handlers/filesajaxpagehandler.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* 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_SDL_NET_FILESAJAXPAGEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class FilesAjaxPageHandler: public FilesBaseHandler {
|
||||
public:
|
||||
FilesAjaxPageHandler();
|
||||
~FilesAjaxPageHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
83
backends/networking/sdl_net/handlers/filesbasehandler.cpp
Normal file
83
backends/networking/sdl_net/handlers/filesbasehandler.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/* 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/sdl_net/handlers/filesbasehandler.h"
|
||||
#include "backends/saves/default/default-saves.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
FilesBaseHandler::FilesBaseHandler() {}
|
||||
|
||||
FilesBaseHandler::~FilesBaseHandler() {}
|
||||
|
||||
Common::String FilesBaseHandler::parentPath(const Common::String &path) {
|
||||
Common::String result = path;
|
||||
if (result.size() && (result.lastChar() == '/' || result.lastChar() == '\\')) result.deleteLastChar();
|
||||
if (!result.empty()) {
|
||||
for (int i = result.size() - 1; i >= 0; --i)
|
||||
if (i == 0 || result[i] == '/' || result[i] == '\\') {
|
||||
result.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.size() && result.lastChar() != '/' && result.lastChar() != '\\')
|
||||
result += '/';
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FilesBaseHandler::urlToPath(Common::String &url, Common::Path &path, Common::String &baseUrl, Common::Path &basePath, bool isDirectory) {
|
||||
// <url> is not empty, but could lack the trailing slash
|
||||
if (isDirectory && url.lastChar() != '/')
|
||||
url += '/';
|
||||
|
||||
if (url.hasPrefix("/root") && ConfMan.hasKey("rootpath", "cloud")) {
|
||||
baseUrl = "/root/";
|
||||
basePath = ConfMan.getPath("rootpath", "cloud");
|
||||
url.erase(0, 5);
|
||||
if (url.size() && url[0] == '/')
|
||||
url.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c"
|
||||
path = basePath.join(url, '/');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.hasPrefix("/saves")) {
|
||||
baseUrl = "/saves/";
|
||||
|
||||
// determine savepath (prefix to remove)
|
||||
#ifdef USE_CLOUD
|
||||
DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
|
||||
basePath = (manager ? manager->concatWithSavesPath("") : ConfMan.getPath("savepath"));
|
||||
#else
|
||||
basePath = ConfMan.getPath("savepath");
|
||||
#endif
|
||||
url.erase(0, 6);
|
||||
if (url.size() && url[0] == '/')
|
||||
url.deleteChar(0);
|
||||
path = basePath.join(url, '/');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
53
backends/networking/sdl_net/handlers/filesbasehandler.h
Normal file
53
backends/networking/sdl_net/handlers/filesbasehandler.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* 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_SDL_NET_FILESBASEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/basehandler.h"
|
||||
|
||||
namespace Common {
|
||||
class Path;
|
||||
}
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class FilesBaseHandler: public BaseHandler {
|
||||
protected:
|
||||
Common::String parentPath(const Common::String &path);
|
||||
|
||||
/**
|
||||
* Transforms virtual <url> into actual file system path.
|
||||
*
|
||||
* Fills base path with actual file system prefix
|
||||
* and base URL with virtual prefix
|
||||
*
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool urlToPath(Common::String &url, Common::Path &path, Common::String &baseUrl, Common::Path &basePath, bool isDirectory = true);
|
||||
public:
|
||||
FilesBaseHandler();
|
||||
~FilesBaseHandler() override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
233
backends/networking/sdl_net/handlers/filespagehandler.cpp
Normal file
233
backends/networking/sdl_net/handlers/filespagehandler.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
/* 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/sdl_net/handlers/filespagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define INDEX_PAGE_NAME ".index.html"
|
||||
#define FILES_PAGE_NAME ".files.html"
|
||||
|
||||
FilesPageHandler::FilesPageHandler() {}
|
||||
|
||||
FilesPageHandler::~FilesPageHandler() {}
|
||||
|
||||
namespace {
|
||||
Common::String encodeDoubleQuotes(const Common::String &s) {
|
||||
Common::String result = "";
|
||||
for (uint32 i = 0; i < s.size(); ++i)
|
||||
if (s[i] == '"') {
|
||||
result += "\\\"";
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::String encodeHtmlEntities(const Common::String &s) {
|
||||
Common::String result = "";
|
||||
for (uint32 i = 0; i < s.size(); ++i)
|
||||
if (s[i] == '<')
|
||||
result += "<";
|
||||
else if (s[i] == '>')
|
||||
result += ">";
|
||||
else if (s[i] == '&')
|
||||
result += "&";
|
||||
else if ((byte)s[i] > (byte)0x7F)
|
||||
result += Common::String::format("&#%d;", (int)s[i]);
|
||||
else result += s[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::String getDisplayPath(const Common::String &s) {
|
||||
Common::String result = "";
|
||||
for (uint32 i = 0; i < s.size(); ++i)
|
||||
if (s[i] == '\\')
|
||||
result += '/';
|
||||
else
|
||||
result += s[i];
|
||||
if (result == "")
|
||||
return "/";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool FilesPageHandler::listDirectory(const Common::String &path_, Common::String &content, const Common::String &itemTemplate) {
|
||||
if (path_ == "" || path_ == "/") {
|
||||
if (ConfMan.hasKey("rootpath", "cloud"))
|
||||
addItem(content, itemTemplate, IT_DIRECTORY, "/root/", Common::convertFromU32String(_("File system root")));
|
||||
addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", Common::convertFromU32String(_("Saved games")));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HandlerUtils::hasForbiddenCombinations(path_))
|
||||
return false;
|
||||
|
||||
Common::String path = path_;
|
||||
Common::String basePath;
|
||||
Common::Path baseFSPath, fsPath;
|
||||
if (!urlToPath(path, fsPath, basePath, baseFSPath))
|
||||
return false;
|
||||
|
||||
Common::FSNode node = Common::FSNode(fsPath);
|
||||
|
||||
if (!HandlerUtils::permittedPath(node.getPath()))
|
||||
return false;
|
||||
|
||||
if (!node.isDirectory())
|
||||
return false;
|
||||
|
||||
// list directory
|
||||
Common::FSList _nodeContent;
|
||||
if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
|
||||
_nodeContent.clear();
|
||||
else
|
||||
Common::sort(_nodeContent.begin(), _nodeContent.end());
|
||||
|
||||
// add parent directory link
|
||||
{
|
||||
Common::Path relPath = fsPath.relativeTo(baseFSPath);
|
||||
relPath = relPath.getParent();
|
||||
Common::String filePath("/");
|
||||
if (!relPath.empty())
|
||||
filePath = basePath + relPath.toString('/');
|
||||
addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, Common::convertFromU32String(_("Parent directory")));
|
||||
}
|
||||
|
||||
// fill the content
|
||||
for (auto &i : _nodeContent) {
|
||||
Common::String name = i.getName();
|
||||
if (i.isDirectory())
|
||||
name += "/";
|
||||
|
||||
Common::Path relPath = i.getPath().relativeTo(baseFSPath);
|
||||
Common::String filePath(basePath);
|
||||
filePath += relPath.toString('/');
|
||||
|
||||
addItem(content, itemTemplate, detectType(i.isDirectory(), name), filePath, name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FilesPageHandler::ItemType FilesPageHandler::detectType(bool isDirectory, const Common::String &name) {
|
||||
if (isDirectory)
|
||||
return IT_DIRECTORY;
|
||||
if (name.hasSuffix(".txt"))
|
||||
return IT_TXT;
|
||||
if (name.hasSuffix(".zip"))
|
||||
return IT_ZIP;
|
||||
if (name.hasSuffix(".7z"))
|
||||
return IT_7Z;
|
||||
return IT_UNKNOWN;
|
||||
}
|
||||
|
||||
void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, const Common::String &path, const Common::String &name, const Common::String &size) const {
|
||||
Common::String item = itemTemplate, icon;
|
||||
bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
|
||||
switch (itemType) {
|
||||
case IT_DIRECTORY:
|
||||
icon = "dir.png";
|
||||
break;
|
||||
case IT_PARENT_DIRECTORY:
|
||||
icon = "up.png";
|
||||
break;
|
||||
case IT_TXT:
|
||||
icon = "txt.png";
|
||||
break;
|
||||
case IT_ZIP:
|
||||
icon = "zip.png";
|
||||
break;
|
||||
case IT_7Z:
|
||||
icon = "7z.png";
|
||||
break;
|
||||
default:
|
||||
icon = "unk.png";
|
||||
}
|
||||
replace(item, "{icon}", icon);
|
||||
replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + LocalWebserver::urlEncodeQueryParameterValue(path));
|
||||
replace(item, "{name}", encodeHtmlEntities(name));
|
||||
replace(item, "{size}", size);
|
||||
content += item;
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void FilesPageHandler::handle(Client &client) {
|
||||
Common::String response =
|
||||
"<html>" \
|
||||
"<head><title>ScummVM</title><meta charset=\"utf-8\"/></head>" \
|
||||
"<body>" \
|
||||
"<p>{create_directory_desc}</p>" \
|
||||
"<form action=\"create\">" \
|
||||
"<input type=\"hidden\" name=\"path\" value=\"{path}\"/>" \
|
||||
"<input type=\"text\" name=\"directory_name\" value=\"\"/>" \
|
||||
"<input type=\"submit\" value=\"{create_directory_button}\"/>" \
|
||||
"</form>" \
|
||||
"<hr/>" \
|
||||
"<p>{upload_file_desc}</p>" \
|
||||
"<form action=\"upload?path={path}\" method=\"post\" enctype=\"multipart/form-data\">" \
|
||||
"<input type=\"file\" name=\"upload_file-f\" allowdirs multiple/>" \
|
||||
"<span>{or_upload_directory_desc}</span>" \
|
||||
"<input type=\"file\" name=\"upload_file-d\" directory webkitdirectory multiple/>" \
|
||||
"<input type=\"submit\" value=\"{upload_file_button}\"/>" \
|
||||
"</form>"
|
||||
"<hr/>" \
|
||||
"<h1>{index_of_directory}</h1>" \
|
||||
"<table>{content}</table>" \
|
||||
"</body>" \
|
||||
"</html>";
|
||||
Common::String itemTemplate = "<tr><td><img src=\"icons/{icon}\"/></td><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too?
|
||||
|
||||
// load stylish response page from the archive
|
||||
Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
|
||||
if (stream)
|
||||
response = HandlerUtils::readEverythingFromStream(stream);
|
||||
|
||||
Common::String path = client.queryParameter("path");
|
||||
Common::String content = "";
|
||||
|
||||
// show an error message if failed to list directory
|
||||
if (!listDirectory(path, content, itemTemplate)) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("ScummVM couldn't list the directory you specified.")).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
//some of these occur twice:
|
||||
replace(response, "{create_directory_button}", _("Create directory").encode());
|
||||
replace(response, "{create_directory_button}", _("Create directory").encode());
|
||||
replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
|
||||
replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
|
||||
replace(response, "{upload_files_button}", _("Upload files").encode()); //tab
|
||||
replace(response, "{upload_file_button}", _("Upload files").encode()); //button in the tab
|
||||
replace(response, "{create_directory_desc}", _("Type new directory name:").encode());
|
||||
replace(response, "{upload_file_desc}", _("Select a file to upload:").encode());
|
||||
replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):").encode());
|
||||
replace(response, "{index_of_directory}", Common::String::format("%s %s", _("Index of").encode().c_str(), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str()));
|
||||
replace(response, "{content}", content);
|
||||
LocalWebserver::setClientGetHandler(client, response);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
61
backends/networking/sdl_net/handlers/filespagehandler.h
Normal file
61
backends/networking/sdl_net/handlers/filespagehandler.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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_SDL_NET_FILESPAGEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class FilesPageHandler: public FilesBaseHandler {
|
||||
enum ItemType {
|
||||
IT_DIRECTORY,
|
||||
IT_PARENT_DIRECTORY,
|
||||
IT_TXT,
|
||||
IT_ZIP,
|
||||
IT_7Z,
|
||||
IT_UNKNOWN
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists the directory <path>.
|
||||
*
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool listDirectory(const Common::String &path, Common::String &content, const Common::String &itemTemplate);
|
||||
|
||||
/** Helper method for detecting items' type. */
|
||||
static ItemType detectType(bool isDirectory, const Common::String &name);
|
||||
|
||||
/** Helper method for adding items into the files list. */
|
||||
void addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, const Common::String &path, const Common::String &name, const Common::String &size = Common::String()) const;
|
||||
|
||||
public:
|
||||
FilesPageHandler();
|
||||
~FilesPageHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
52
backends/networking/sdl_net/handlers/indexpagehandler.cpp
Normal file
52
backends/networking/sdl_net/handlers/indexpagehandler.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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/sdl_net/handlers/indexpagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {}
|
||||
|
||||
IndexPageHandler::~IndexPageHandler() {}
|
||||
|
||||
/// public
|
||||
|
||||
void IndexPageHandler::handle(Client &client) {
|
||||
// redirect to "/filesAJAX"
|
||||
HandlerUtils::setMessageHandler(
|
||||
client,
|
||||
Common::String::format(
|
||||
"%s<br/><a href=\"files\">%s</a>",
|
||||
Common::convertFromU32String(_("This is a local webserver index page.")).c_str(),
|
||||
Common::convertFromU32String(_("Open Files manager")).c_str()
|
||||
),
|
||||
"/filesAJAX"
|
||||
);
|
||||
}
|
||||
|
||||
bool IndexPageHandler::minimalModeSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
42
backends/networking/sdl_net/handlers/indexpagehandler.h
Normal file
42
backends/networking/sdl_net/handlers/indexpagehandler.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* 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_SDL_NET_INDEXPAGEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/basehandler.h"
|
||||
#include "gui/object.h"
|
||||
|
||||
namespace Networking {
|
||||
class LocalWebserver;
|
||||
|
||||
class IndexPageHandler: public BaseHandler, public GUI::CommandSender {
|
||||
public:
|
||||
IndexPageHandler();
|
||||
~IndexPageHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
bool minimalModeSupported() override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
155
backends/networking/sdl_net/handlers/listajaxhandler.cpp
Normal file
155
backends/networking/sdl_net/handlers/listajaxhandler.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/* 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/sdl_net/handlers/listajaxhandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/formats/json.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
ListAjaxHandler::ListAjaxHandler() {}
|
||||
|
||||
ListAjaxHandler::~ListAjaxHandler() {}
|
||||
|
||||
Common::JSONObject ListAjaxHandler::listDirectory(const Common::String &path_) {
|
||||
Common::JSONArray itemsList;
|
||||
Common::JSONObject errorResult;
|
||||
Common::JSONObject successResult;
|
||||
successResult.setVal("type", new Common::JSONValue("success"));
|
||||
errorResult.setVal("type", new Common::JSONValue("error"));
|
||||
|
||||
if (path_ == "" || path_ == "/") {
|
||||
if (ConfMan.hasKey("rootpath", "cloud"))
|
||||
addItem(itemsList, IT_DIRECTORY, "/root/", Common::convertFromU32String(_("File system root")));
|
||||
addItem(itemsList, IT_DIRECTORY, "/saves/", Common::convertFromU32String(_("Saved games")));
|
||||
successResult.setVal("items", new Common::JSONValue(itemsList));
|
||||
return successResult;
|
||||
}
|
||||
|
||||
if (HandlerUtils::hasForbiddenCombinations(path_))
|
||||
return errorResult;
|
||||
|
||||
Common::String path = path_;
|
||||
Common::String basePath;
|
||||
Common::Path baseFSPath, fsPath;
|
||||
if (!urlToPath(path, fsPath, basePath, baseFSPath))
|
||||
return errorResult;
|
||||
|
||||
Common::FSNode node = Common::FSNode(fsPath);
|
||||
|
||||
if (!HandlerUtils::permittedPath(node.getPath()))
|
||||
return errorResult;
|
||||
|
||||
if (!node.isDirectory())
|
||||
return errorResult;
|
||||
|
||||
// list directory
|
||||
Common::FSList _nodeContent;
|
||||
if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
|
||||
_nodeContent.clear();
|
||||
else
|
||||
Common::sort(_nodeContent.begin(), _nodeContent.end());
|
||||
|
||||
// add parent directory link
|
||||
{
|
||||
Common::Path relPath = fsPath.relativeTo(baseFSPath);
|
||||
relPath = relPath.getParent();
|
||||
Common::String filePath("/");
|
||||
if (!relPath.empty())
|
||||
filePath = basePath + relPath.toString('/');
|
||||
|
||||
addItem(itemsList, IT_PARENT_DIRECTORY, filePath, Common::convertFromU32String(_("Parent directory")));
|
||||
}
|
||||
|
||||
// fill the content
|
||||
for (auto &curNode : _nodeContent) {
|
||||
Common::String name = curNode.getName();
|
||||
if (curNode.isDirectory())
|
||||
name += "/";
|
||||
|
||||
Common::Path relPath = curNode.getPath().relativeTo(baseFSPath);
|
||||
Common::String filePath(basePath);
|
||||
filePath += relPath.toString('/');
|
||||
|
||||
addItem(itemsList, detectType(curNode.isDirectory(), name), filePath, name);
|
||||
}
|
||||
|
||||
successResult.setVal("items", new Common::JSONValue(itemsList));
|
||||
return successResult;
|
||||
}
|
||||
|
||||
ListAjaxHandler::ItemType ListAjaxHandler::detectType(bool isDirectory, const Common::String &name) {
|
||||
if (isDirectory)
|
||||
return IT_DIRECTORY;
|
||||
if (name.hasSuffix(".txt"))
|
||||
return IT_TXT;
|
||||
if (name.hasSuffix(".zip"))
|
||||
return IT_ZIP;
|
||||
if (name.hasSuffix(".7z"))
|
||||
return IT_7Z;
|
||||
return IT_UNKNOWN;
|
||||
}
|
||||
|
||||
void ListAjaxHandler::addItem(Common::JSONArray &responseItemsList, ItemType itemType, const Common::String &path, const Common::String &name, const Common::String &size) {
|
||||
Common::String icon;
|
||||
bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
|
||||
switch (itemType) {
|
||||
case IT_DIRECTORY:
|
||||
icon = "dir.png";
|
||||
break;
|
||||
case IT_PARENT_DIRECTORY:
|
||||
icon = "up.png";
|
||||
break;
|
||||
case IT_TXT:
|
||||
icon = "txt.png";
|
||||
break;
|
||||
case IT_ZIP:
|
||||
icon = "zip.png";
|
||||
break;
|
||||
case IT_7Z:
|
||||
icon = "7z.png";
|
||||
break;
|
||||
default:
|
||||
icon = "unk.png";
|
||||
}
|
||||
|
||||
Common::JSONObject item;
|
||||
item.setVal("name", new Common::JSONValue(name));
|
||||
item.setVal("path", new Common::JSONValue(path));
|
||||
item.setVal("isDirectory", new Common::JSONValue(isDirectory));
|
||||
item.setVal("size", new Common::JSONValue(size));
|
||||
item.setVal("icon", new Common::JSONValue(icon));
|
||||
responseItemsList.push_back(new Common::JSONValue(item));
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void ListAjaxHandler::handle(Client &client) {
|
||||
Common::String path = client.queryParameter("path");
|
||||
Common::JSONValue jsonResponse = listDirectory(path);
|
||||
Common::String response = jsonResponse.stringify(true);
|
||||
LocalWebserver::setClientGetHandler(client, response);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
62
backends/networking/sdl_net/handlers/listajaxhandler.h
Normal file
62
backends/networking/sdl_net/handlers/listajaxhandler.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* 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_SDL_NET_LISTAJAXHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
#include "common/formats/json.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ListAjaxHandler: public FilesBaseHandler {
|
||||
enum ItemType {
|
||||
IT_DIRECTORY,
|
||||
IT_PARENT_DIRECTORY,
|
||||
IT_TXT,
|
||||
IT_ZIP,
|
||||
IT_7Z,
|
||||
IT_UNKNOWN
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists the directory <path>.
|
||||
*
|
||||
* Returns JSON with either listed directory or error response.
|
||||
*/
|
||||
Common::JSONObject listDirectory(const Common::String &path);
|
||||
|
||||
/** Helper method for detecting items' type. */
|
||||
static ItemType detectType(bool isDirectory, const Common::String &name);
|
||||
|
||||
/** Helper method for adding items into the files list. */
|
||||
static void addItem(Common::JSONArray &responseItemsList, ItemType itemType, const Common::String &path, const Common::String &name, const Common::String &size = "");
|
||||
|
||||
public:
|
||||
ListAjaxHandler();
|
||||
~ListAjaxHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
77
backends/networking/sdl_net/handlers/resourcehandler.cpp
Normal file
77
backends/networking/sdl_net/handlers/resourcehandler.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 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/sdl_net/handlers/resourcehandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
ResourceHandler::ResourceHandler() {}
|
||||
|
||||
ResourceHandler::~ResourceHandler() {}
|
||||
|
||||
const char *ResourceHandler::determineMimeType(const Common::String &filename) {
|
||||
// text
|
||||
if (filename.hasSuffix(".html")) return "text/html";
|
||||
if (filename.hasSuffix(".css")) return "text/css";
|
||||
if (filename.hasSuffix(".txt")) return "text/plain";
|
||||
if (filename.hasSuffix(".js")) return "application/javascript";
|
||||
|
||||
// images
|
||||
if (filename.hasSuffix(".jpeg") || filename.hasSuffix(".jpg") || filename.hasSuffix(".jpe")) return "image/jpeg";
|
||||
if (filename.hasSuffix(".gif")) return "image/gif";
|
||||
if (filename.hasSuffix(".png")) return "image/png";
|
||||
if (filename.hasSuffix(".svg")) return "image/svg+xml";
|
||||
if (filename.hasSuffix(".tiff")) return "image/tiff";
|
||||
if (filename.hasSuffix(".ico")) return "image/vnd.microsoft.icon";
|
||||
if (filename.hasSuffix(".wbmp")) return "image/vnd.wap.wbmp";
|
||||
|
||||
if (filename.hasSuffix(".zip")) return "application/zip";
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
void ResourceHandler::handle(Client &client) {
|
||||
Common::String filename = client.path();
|
||||
|
||||
// remove leading slash
|
||||
if (filename.size() > 0 && filename[0] == '/')
|
||||
filename.deleteChar(0);
|
||||
|
||||
// if archive hidden file is requested, ignore
|
||||
if (filename.size() > 0 && filename[0] == '.')
|
||||
return;
|
||||
|
||||
// if file not found, don't set handler either
|
||||
Common::SeekableReadStream *file = HandlerUtils::getArchiveFile(filename);
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
LocalWebserver::setClientGetHandler(client, file, 200, determineMimeType(filename));
|
||||
}
|
||||
|
||||
bool ResourceHandler::minimalModeSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
41
backends/networking/sdl_net/handlers/resourcehandler.h
Normal file
41
backends/networking/sdl_net/handlers/resourcehandler.h
Normal 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_SDL_NET_RESOURCEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/basehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ResourceHandler: public BaseHandler {
|
||||
static const char *determineMimeType(const Common::String &filename);
|
||||
public:
|
||||
ResourceHandler();
|
||||
~ResourceHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
bool minimalModeSupported() override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
78
backends/networking/sdl_net/handlers/uploadfilehandler.cpp
Normal file
78
backends/networking/sdl_net/handlers/uploadfilehandler.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/* 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/sdl_net/handlers/uploadfilehandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/uploadfileclienthandler.h"
|
||||
#include "common/system.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
UploadFileHandler::UploadFileHandler() {}
|
||||
|
||||
UploadFileHandler::~UploadFileHandler() {}
|
||||
|
||||
/// public
|
||||
|
||||
void UploadFileHandler::handle(Client &client) {
|
||||
Common::String path = client.queryParameter("path");
|
||||
|
||||
// check that <path> is not an absolute root
|
||||
if (path == "" || path == "/" || path == "\\") {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> contains no '../'
|
||||
if (HandlerUtils::hasForbiddenCombinations(path)) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// transform virtual path to actual file system one
|
||||
Common::String basePath;
|
||||
Common::Path baseFSPath, fsPath;
|
||||
if (!urlToPath(path, fsPath, basePath, baseFSPath)) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <path> exists, is directory and isn't forbidden
|
||||
Common::FSNode node(fsPath);
|
||||
if (!HandlerUtils::permittedPath(node.getPath())) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
if (!node.exists()) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("The parent directory doesn't exist!")));
|
||||
return;
|
||||
}
|
||||
if (!node.isDirectory()) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, Common::convertFromU32String(_("Can't upload into a file!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// if all OK, set special handler
|
||||
client.setHandler(new UploadFileClientHandler(fsPath));
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
39
backends/networking/sdl_net/handlers/uploadfilehandler.h
Normal file
39
backends/networking/sdl_net/handlers/uploadfilehandler.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* 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_SDL_NET_UPLOADFILEHANDLER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H
|
||||
|
||||
#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class UploadFileHandler: public FilesBaseHandler {
|
||||
public:
|
||||
UploadFileHandler();
|
||||
~UploadFileHandler() override;
|
||||
|
||||
void handle(Client &client) override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
210
backends/networking/sdl_net/handlerutils.cpp
Normal file
210
backends/networking/sdl_net/handlerutils.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/* 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/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "backends/saves/default/default-saves.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/compression/unzip.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define ARCHIVE_NAME "wwwroot.zip"
|
||||
|
||||
#define INDEX_PAGE_NAME ".index.html"
|
||||
|
||||
Common::Archive *HandlerUtils::getZipArchive() {
|
||||
// first search in themepath
|
||||
if (ConfMan.hasKey("themepath")) {
|
||||
const Common::FSNode &node = Common::FSNode(ConfMan.getPath("themepath"));
|
||||
if (node.exists() && node.isReadable() && node.isDirectory()) {
|
||||
Common::FSNode fileNode = node.getChild(ARCHIVE_NAME);
|
||||
if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) {
|
||||
Common::SeekableReadStream *const stream = fileNode.createReadStream();
|
||||
Common::Archive *zipArchive = Common::makeZipArchive(stream);
|
||||
if (zipArchive)
|
||||
return zipArchive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then use SearchMan to find it
|
||||
Common::ArchiveMemberList fileList;
|
||||
SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME);
|
||||
for (auto &m : fileList) {
|
||||
Common::SeekableReadStream *const stream = m->createReadStream();
|
||||
Common::Archive *zipArchive = Common::makeZipArchive(stream);
|
||||
if (zipArchive)
|
||||
return zipArchive;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Common::ArchiveMemberList HandlerUtils::listArchive() {
|
||||
Common::ArchiveMemberList resultList;
|
||||
Common::Archive *zipArchive = getZipArchive();
|
||||
if (zipArchive) {
|
||||
zipArchive->listMembers(resultList);
|
||||
delete zipArchive;
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *HandlerUtils::getArchiveFile(const Common::String &name) {
|
||||
Common::SeekableReadStream *result = nullptr;
|
||||
Common::Archive *zipArchive = getZipArchive();
|
||||
if (zipArchive) {
|
||||
const Common::ArchiveMemberPtr ptr = zipArchive->getMember(Common::Path(name, '/'));
|
||||
if (ptr.get() == nullptr)
|
||||
return nullptr;
|
||||
result = ptr->createReadStream();
|
||||
delete zipArchive;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) {
|
||||
Common::String result;
|
||||
char buf[1024];
|
||||
uint32 readBytes;
|
||||
while (!stream->eos()) {
|
||||
readBytes = stream->read(buf, 1024);
|
||||
result += Common::String(buf, readBytes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *HandlerUtils::makeResponseStreamFromString(const Common::String &response) {
|
||||
byte *data = (byte *)malloc(response.size());
|
||||
assert(data);
|
||||
memcpy(data, response.c_str(), response.size());
|
||||
return new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
Common::String HandlerUtils::normalizePath(const Common::String &path) {
|
||||
Common::String normalized;
|
||||
bool slash = false;
|
||||
for (uint32 i = 0; i < path.size(); ++i) {
|
||||
char c = path[i];
|
||||
if (c == '\\' || c == '/') {
|
||||
slash = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slash) {
|
||||
normalized += '/';
|
||||
slash = false;
|
||||
}
|
||||
|
||||
if ('A' <= c && c <= 'Z') {
|
||||
normalized += c - 'A' + 'a';
|
||||
} else {
|
||||
normalized += c;
|
||||
}
|
||||
}
|
||||
if (slash) normalized += '/';
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) {
|
||||
return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\"));
|
||||
}
|
||||
|
||||
bool HandlerUtils::isBlacklisted(const Common::Path &path) {
|
||||
const char *blacklist[] = {
|
||||
"/etc",
|
||||
"/bin",
|
||||
"c:/windows" // just saying: I know guys who install windows on another drives
|
||||
};
|
||||
|
||||
// normalize path
|
||||
Common::Path normalized = path.normalize();
|
||||
|
||||
uint32 size = sizeof(blacklist) / sizeof(const char *);
|
||||
for (uint32 i = 0; i < size; ++i)
|
||||
if (normalized.isRelativeTo(Common::Path(blacklist[i], '/')))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandlerUtils::hasPermittedPrefix(const Common::Path &path) {
|
||||
// normalize path
|
||||
Common::Path normalized = path.normalize();
|
||||
|
||||
// prefix for /root/
|
||||
Common::Path prefix;
|
||||
if (ConfMan.hasKey("rootpath", "cloud")) {
|
||||
prefix = ConfMan.getPath("rootpath", "cloud").normalize();
|
||||
if (normalized.isRelativeTo(prefix))
|
||||
return true;
|
||||
}
|
||||
|
||||
// prefix for /saves/
|
||||
#ifdef USE_CLOUD
|
||||
DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
|
||||
prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.getPath("savepath"));
|
||||
#else
|
||||
prefix = ConfMan.getPath("savepath");
|
||||
#endif
|
||||
prefix = prefix.normalize();
|
||||
return normalized.isRelativeTo(prefix);
|
||||
}
|
||||
|
||||
bool HandlerUtils::permittedPath(const Common::Path &path) {
|
||||
return hasPermittedPrefix(path) && !isBlacklisted(path);
|
||||
}
|
||||
|
||||
void HandlerUtils::setMessageHandler(Client &client, const Common::String &message, const Common::String &redirectTo) {
|
||||
Common::String response = "<html><head><title>ScummVM</title><meta charset=\"utf-8\"/></head><body>{message}</body></html>";
|
||||
|
||||
// load stylish response page from the archive
|
||||
Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
|
||||
if (stream)
|
||||
response = readEverythingFromStream(stream);
|
||||
|
||||
replace(response, "{message}", message);
|
||||
if (redirectTo.empty())
|
||||
LocalWebserver::setClientGetHandler(client, response);
|
||||
else
|
||||
LocalWebserver::setClientRedirectHandler(client, response, redirectTo);
|
||||
}
|
||||
|
||||
void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, const Common::String &message, const Common::String &redirectTo) {
|
||||
setMessageHandler(
|
||||
client,
|
||||
Common::String::format(
|
||||
"%s<br/><a href=\"files%s?path=%s\">%s</a>",
|
||||
message.c_str(),
|
||||
client.queryParameter("ajax") == "true" ? "AJAX" : "",
|
||||
"%2F", //that's encoded "/"
|
||||
Common::convertFromU32String(_("Back to the files manager")).c_str()
|
||||
),
|
||||
redirectTo
|
||||
);
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
50
backends/networking/sdl_net/handlerutils.h
Normal file
50
backends/networking/sdl_net/handlerutils.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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_SDL_NET_HANDLERUTILS_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H
|
||||
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
#include "common/archive.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class HandlerUtils {
|
||||
public:
|
||||
static Common::Archive *getZipArchive();
|
||||
static Common::ArchiveMemberList listArchive();
|
||||
static Common::SeekableReadStream *getArchiveFile(const Common::String &name);
|
||||
static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream);
|
||||
static Common::SeekableReadStream *makeResponseStreamFromString(const Common::String &response);
|
||||
|
||||
static Common::String normalizePath(const Common::String &path);
|
||||
static bool hasForbiddenCombinations(const Common::String &path);
|
||||
static bool isBlacklisted(const Common::Path &path);
|
||||
static bool hasPermittedPrefix(const Common::Path &path);
|
||||
static bool permittedPath(const Common::Path &path);
|
||||
|
||||
static void setMessageHandler(Client &client, const Common::String &message, const Common::String &redirectTo = "");
|
||||
static void setFilesManagerErrorMessageHandler(Client &client, const Common::String &message, const Common::String &redirectTo = "");
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
502
backends/networking/sdl_net/localwebserver.cpp
Normal file
502
backends/networking/sdl_net/localwebserver.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
/* 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
|
||||
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "backends/networking/sdl_net/getclienthandler.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/str.h"
|
||||
#include "common/system.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/translation.h"
|
||||
#include <SDL_net.h>
|
||||
#include "common/config-manager.h"
|
||||
|
||||
#ifdef POSIX
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef SIOCGIFCONF
|
||||
#include <sys/sockio.h>
|
||||
#endif
|
||||
|
||||
#ifndef _SIZEOF_ADDR_IFREQ
|
||||
#define _SIZEOF_ADDR_IFREQ sizeof
|
||||
#endif
|
||||
|
||||
#define LSSDP_BUFFER_LEN 2048
|
||||
#endif // POSIX
|
||||
|
||||
#ifdef PLAYSTATION3
|
||||
#include <net/netctl.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
class MemoryReadWriteStream;
|
||||
|
||||
DECLARE_SINGLETON(Networking::LocalWebserver);
|
||||
|
||||
}
|
||||
|
||||
namespace Networking {
|
||||
|
||||
LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
|
||||
_stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) {
|
||||
addPathHandler("/", &_indexPageHandler);
|
||||
addPathHandler("/files", &_filesPageHandler);
|
||||
addPathHandler("/create", &_createDirectoryHandler);
|
||||
addPathHandler("/download", &_downloadFileHandler);
|
||||
addPathHandler("/upload", &_uploadFileHandler);
|
||||
addPathHandler("/list", &_listAjaxHandler);
|
||||
addPathHandler("/filesAJAX", &_filesAjaxPageHandler);
|
||||
#ifdef USE_CLOUD
|
||||
addPathHandler("/connect_cloud", &_connectCloudHandler);
|
||||
#endif // USE_CLOUD
|
||||
_defaultHandler = &_resourceHandler;
|
||||
}
|
||||
|
||||
LocalWebserver::~LocalWebserver() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void localWebserverTimer(void *ignored) {
|
||||
LocalServer.handle();
|
||||
}
|
||||
|
||||
void LocalWebserver::startTimer(int interval) {
|
||||
Common::TimerManager *manager = g_system->getTimerManager();
|
||||
if (manager->installTimerProc(localWebserverTimer, interval, nullptr, "Networking::LocalWebserver's Timer")) {
|
||||
_timerStarted = true;
|
||||
} else {
|
||||
warning("Failed to install Networking::LocalWebserver's timer");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalWebserver::stopTimer() {
|
||||
Common::TimerManager *manager = g_system->getTimerManager();
|
||||
manager->removeTimerProc(localWebserverTimer);
|
||||
_timerStarted = false;
|
||||
}
|
||||
|
||||
void LocalWebserver::start(bool useMinimalMode) {
|
||||
_handleMutex.lock();
|
||||
_serverPort = getPort();
|
||||
_stopOnIdle = false;
|
||||
if (_timerStarted) {
|
||||
_handleMutex.unlock();
|
||||
return;
|
||||
}
|
||||
_minimalMode = useMinimalMode;
|
||||
startTimer();
|
||||
|
||||
// Create a listening TCP socket
|
||||
IPaddress ip;
|
||||
if (SDLNet_ResolveHost(&ip, nullptr, _serverPort) == -1) {
|
||||
error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
|
||||
}
|
||||
|
||||
resolveAddress(&ip);
|
||||
|
||||
_serverSocket = SDLNet_TCP_Open(&ip);
|
||||
if (!_serverSocket) {
|
||||
warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError());
|
||||
stopTimer();
|
||||
g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again."));
|
||||
_handleMutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a socket set
|
||||
_set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket
|
||||
if (!_set) {
|
||||
error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
|
||||
}
|
||||
|
||||
int numused = SDLNet_TCP_AddSocket(_set, _serverSocket);
|
||||
if (numused == -1) {
|
||||
error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError());
|
||||
}
|
||||
|
||||
if (_timerStarted)
|
||||
g_system->taskStarted(OSystem::kLocalServer);
|
||||
|
||||
_handleMutex.unlock();
|
||||
}
|
||||
|
||||
void LocalWebserver::stop() {
|
||||
_handleMutex.lock();
|
||||
if (_timerStarted) {
|
||||
stopTimer();
|
||||
g_system->taskFinished(OSystem::kLocalServer);
|
||||
}
|
||||
|
||||
if (_serverSocket) {
|
||||
SDLNet_TCP_Close(_serverSocket);
|
||||
_serverSocket = nullptr;
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
|
||||
_client[i].close();
|
||||
|
||||
_clients = 0;
|
||||
|
||||
if (_set) {
|
||||
SDLNet_FreeSocketSet(_set);
|
||||
_set = nullptr;
|
||||
}
|
||||
_handleMutex.unlock();
|
||||
}
|
||||
|
||||
void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
|
||||
|
||||
void LocalWebserver::addPathHandler(const Common::String &path, BaseHandler *handler) {
|
||||
if (_pathHandlers.contains(path))
|
||||
warning("LocalWebserver::addPathHandler: path already had a handler");
|
||||
_pathHandlers[path] = handler;
|
||||
}
|
||||
|
||||
Common::String LocalWebserver::getAddress() { return _address; }
|
||||
|
||||
IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; }
|
||||
|
||||
bool LocalWebserver::isRunning() {
|
||||
bool result = false;
|
||||
_handleMutex.lock();
|
||||
result = _timerStarted;
|
||||
_handleMutex.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32 LocalWebserver::getPort() {
|
||||
#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
|
||||
if (ConfMan.hasKey("local_server_port"))
|
||||
return ConfMan.getInt("local_server_port");
|
||||
#endif
|
||||
return DEFAULT_SERVER_PORT;
|
||||
}
|
||||
|
||||
void LocalWebserver::handle() {
|
||||
_handleMutex.lock();
|
||||
int numready = SDLNet_CheckSockets(_set, 0);
|
||||
if (numready == -1) {
|
||||
error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError());
|
||||
} else if (numready) {
|
||||
acceptClient();
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
|
||||
handleClient(i);
|
||||
|
||||
_clients = 0;
|
||||
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
|
||||
if (_client[i].state() != INVALID)
|
||||
++_clients;
|
||||
|
||||
if (_clients == 0)
|
||||
++_idlingFrames;
|
||||
else
|
||||
_idlingFrames = 0;
|
||||
|
||||
if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) {
|
||||
_handleMutex.unlock();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_handleMutex.unlock();
|
||||
}
|
||||
|
||||
void LocalWebserver::handleClient(uint32 i) {
|
||||
switch (_client[i].state()) {
|
||||
case INVALID:
|
||||
return;
|
||||
case READING_HEADERS:
|
||||
_client[i].readHeaders();
|
||||
break;
|
||||
case READ_HEADERS: {
|
||||
// decide what to do next with that client
|
||||
// check whether we know a handler for such URL
|
||||
BaseHandler *handler = nullptr;
|
||||
if (_pathHandlers.contains(_client[i].path())) {
|
||||
handler = _pathHandlers[_client[i].path()];
|
||||
} else {
|
||||
// try default handler
|
||||
handler = _defaultHandler;
|
||||
}
|
||||
|
||||
// if server's in "minimal mode", only handlers which support it are used
|
||||
if (handler && (!_minimalMode || handler->minimalModeSupported()))
|
||||
handler->handle(_client[i]);
|
||||
|
||||
if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID)
|
||||
break;
|
||||
|
||||
// if no handler, answer with default BAD REQUEST
|
||||
}
|
||||
// fall through
|
||||
|
||||
case BAD_REQUEST:
|
||||
setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
|
||||
break;
|
||||
case BEING_HANDLED:
|
||||
_client[i].handle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LocalWebserver::acceptClient() {
|
||||
if (!SDLNet_SocketReady(_serverSocket))
|
||||
return;
|
||||
|
||||
TCPsocket client = SDLNet_TCP_Accept(_serverSocket);
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
if (_clients == MAX_CONNECTIONS) { //drop the connection
|
||||
SDLNet_TCP_Close(client);
|
||||
return;
|
||||
}
|
||||
|
||||
++_clients;
|
||||
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
|
||||
if (_client[i].state() == INVALID) {
|
||||
_client[i].open(_set, client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LocalWebserver::resolveAddress(void *ipAddress) {
|
||||
IPaddress *ip = (IPaddress *)ipAddress;
|
||||
|
||||
// not resolved
|
||||
_address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort);
|
||||
|
||||
// default way (might work everywhere, surely works on Windows)
|
||||
const char *name = SDLNet_ResolveIP(ip);
|
||||
if (name == nullptr) {
|
||||
warning("LocalWebserver: SDLNet_ResolveIP: %s", SDLNet_GetError());
|
||||
} else {
|
||||
IPaddress localIp;
|
||||
if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) {
|
||||
warning("LocalWebserver: SDLNet_ResolveHost: %s", SDLNet_GetError());
|
||||
} else {
|
||||
_address = Common::String::format(
|
||||
"http://%u.%u.%u.%u:%u/",
|
||||
localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF,
|
||||
_serverPort
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check that our trick worked
|
||||
if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:"))
|
||||
warning("LocalWebserver: Failed to resolve IP with the default way");
|
||||
else
|
||||
return;
|
||||
|
||||
// if not - try platform-specific
|
||||
#ifdef POSIX
|
||||
void *tmpAddrPtr = NULL;
|
||||
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0) {
|
||||
warning("LocalWebserver: failed to create socket: %s (%d)", strerror(errno), errno);
|
||||
} else {
|
||||
// get ifconfig
|
||||
char buffer[LSSDP_BUFFER_LEN] = {};
|
||||
struct ifconf ifc;
|
||||
ifc.ifc_len = sizeof(buffer);
|
||||
ifc.ifc_buf = (caddr_t) buffer;
|
||||
|
||||
if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
|
||||
warning("LocalWebserver: ioctl SIOCGIFCONF failed: %s (%d)", strerror(errno), errno);
|
||||
} else {
|
||||
struct ifreq *i;
|
||||
for (size_t index = 0; index < (size_t)ifc.ifc_len; index += _SIZEOF_ADDR_IFREQ(*i)) {
|
||||
i = (struct ifreq *)(buffer + index);
|
||||
|
||||
Common::String addr;
|
||||
|
||||
// IPv4
|
||||
if (i->ifr_addr.sa_family == AF_INET) {
|
||||
tmpAddrPtr = &((struct sockaddr_in *)&i->ifr_addr)->sin_addr;
|
||||
char addressBuffer[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
|
||||
debug(9, "%s IP Address %s", i->ifr_name, addressBuffer);
|
||||
addr = addressBuffer;
|
||||
}
|
||||
|
||||
// IPv6
|
||||
/*
|
||||
if (i->ifr_addr.sa_family == AF_INET6) {
|
||||
tmpAddrPtr = &((struct sockaddr_in6 *)&i->ifr_addr)->sin6_addr;
|
||||
char addressBuffer[INET6_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
|
||||
debug(9, "%s IP Address %s", i->ifr_name, addressBuffer);
|
||||
addr = addressBuffer;
|
||||
}
|
||||
*/
|
||||
|
||||
if (addr.empty())
|
||||
continue;
|
||||
|
||||
// ignored IPv4 addresses
|
||||
if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost"))
|
||||
continue;
|
||||
|
||||
// ignored IPv6 addresses
|
||||
/*
|
||||
if (addr.equals("::1"))
|
||||
continue;
|
||||
*/
|
||||
|
||||
// use the address found
|
||||
_address = "http://" + addr + Common::String::format(":%u/", _serverPort);
|
||||
}
|
||||
}
|
||||
|
||||
// close socket
|
||||
if (close(fd) != 0) {
|
||||
warning("LocalWebserver: failed to close socket [fd %d]: %s (%d)", fd, strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
#elif defined(PLAYSTATION3)
|
||||
netCtlInit();
|
||||
s32 connectionState;
|
||||
netCtlGetState(&connectionState);
|
||||
if (connectionState == NET_CTL_STATE_IPObtained) {
|
||||
union net_ctl_info info;
|
||||
if (netCtlGetInfo(NET_CTL_INFO_IP_ADDRESS, &info) == 0) {
|
||||
debug(9, "LocalWebserver: IP Address %s", info.ip_address);
|
||||
_address = "http://" + Common::String(info.ip_address) + Common::String::format(":%u/", _serverPort);
|
||||
} else {
|
||||
warning("LocalWebserver: failed to get IP address for network connection");
|
||||
}
|
||||
} else {
|
||||
warning("LocalWebserver: no active network connection was detected");
|
||||
}
|
||||
netCtlTerm();
|
||||
#endif
|
||||
}
|
||||
|
||||
void LocalWebserver::setClientGetHandler(Client &client, const Common::String &response, long code, const char *mimeType) {
|
||||
byte *data = new byte[response.size()];
|
||||
memcpy(data, response.c_str(), response.size());
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
|
||||
setClientGetHandler(client, stream, code, mimeType);
|
||||
}
|
||||
|
||||
void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) {
|
||||
GetClientHandler *handler = new GetClientHandler(responseStream);
|
||||
handler->setResponseCode(code);
|
||||
if (mimeType)
|
||||
handler->setHeader("Content-Type", mimeType);
|
||||
client.setHandler(handler);
|
||||
}
|
||||
|
||||
void LocalWebserver::setClientRedirectHandler(Client &client, const Common::String &response, const Common::String &location, const char *mimeType) {
|
||||
byte *data = new byte[response.size()];
|
||||
memcpy(data, response.c_str(), response.size());
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
|
||||
setClientRedirectHandler(client, stream, location, mimeType);
|
||||
}
|
||||
|
||||
void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, const Common::String &location, const char *mimeType) {
|
||||
GetClientHandler *handler = new GetClientHandler(responseStream);
|
||||
handler->setResponseCode(302); //redirect
|
||||
handler->setHeader("Location", location);
|
||||
if (mimeType)
|
||||
handler->setHeader("Content-Type", mimeType);
|
||||
client.setHandler(handler);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int hexDigit(char c) {
|
||||
if ('0' <= c && c <= '9') return c - '0';
|
||||
if ('A' <= c && c <= 'F') return c - 'A' + 10;
|
||||
if ('a' <= c && c <= 'f') return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Common::String LocalWebserver::urlDecode(const Common::String &value) {
|
||||
Common::String result = "";
|
||||
uint32 size = value.size();
|
||||
for (uint32 i = 0; i < size; ++i) {
|
||||
if (value[i] == '+') {
|
||||
result += ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value[i] == '%' && i + 2 < size) {
|
||||
int d1 = hexDigit(value[i + 1]);
|
||||
int d2 = hexDigit(value[i + 2]);
|
||||
if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) {
|
||||
result += (char)(d1 * 16 + d2);
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result += value[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool isQueryUnreserved(char c) {
|
||||
return (
|
||||
('0' <= c && c <= '9') ||
|
||||
('A' <= c && c <= 'Z') ||
|
||||
('a' <= c && c <= 'z') ||
|
||||
c == '-' || c == '_' || c == '.' || c == '!' ||
|
||||
c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Common::String LocalWebserver::urlEncodeQueryParameterValue(const Common::String &value) {
|
||||
//OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
|
||||
//reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", ","
|
||||
//that means these must be encoded too or otherwise they could malform the query
|
||||
Common::String result = "";
|
||||
char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
for (uint32 i = 0; i < value.size(); ++i) {
|
||||
char c = value[i];
|
||||
if (isQueryUnreserved(c))
|
||||
result += c;
|
||||
else {
|
||||
result += '%';
|
||||
result += hexChar[(c >> 4) & 0xF];
|
||||
result += hexChar[c & 0xF];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
125
backends/networking/sdl_net/localwebserver.h
Normal file
125
backends/networking/sdl_net/localwebserver.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/* 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_SDL_NET_LOCALWEBSERVER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H
|
||||
|
||||
#include "backends/networking/sdl_net/client.h"
|
||||
#include "backends/networking/sdl_net/handlers/basehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/createdirectoryhandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/downloadfilehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/filesajaxpagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/filespagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/indexpagehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/listajaxhandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/resourcehandler.h"
|
||||
#include "backends/networking/sdl_net/handlers/uploadfilehandler.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#ifdef USE_CLOUD
|
||||
#include "backends/networking/sdl_net/handlers/connectcloudhandler.h"
|
||||
#endif // USE_CLOUD
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
|
||||
typedef struct _TCPsocket *TCPsocket;
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
|
||||
|
||||
class LocalWebserver : public Common::Singleton<LocalWebserver> {
|
||||
static const uint32 FRAMES_PER_SECOND = 20;
|
||||
static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
|
||||
static const uint32 MAX_CONNECTIONS = 10;
|
||||
|
||||
friend void localWebserverTimer(void *); //calls handle()
|
||||
|
||||
SDLNet_SocketSet _set;
|
||||
TCPsocket _serverSocket;
|
||||
Client _client[MAX_CONNECTIONS];
|
||||
uint32 _clients;
|
||||
bool _timerStarted, _stopOnIdle, _minimalMode;
|
||||
Common::HashMap<Common::String, BaseHandler*> _pathHandlers;
|
||||
BaseHandler *_defaultHandler;
|
||||
IndexPageHandler _indexPageHandler;
|
||||
FilesPageHandler _filesPageHandler;
|
||||
CreateDirectoryHandler _createDirectoryHandler;
|
||||
DownloadFileHandler _downloadFileHandler;
|
||||
UploadFileHandler _uploadFileHandler;
|
||||
ListAjaxHandler _listAjaxHandler;
|
||||
FilesAjaxPageHandler _filesAjaxPageHandler;
|
||||
#ifdef USE_CLOUD
|
||||
ConnectCloudHandler _connectCloudHandler;
|
||||
#endif // USE_CLOUD
|
||||
ResourceHandler _resourceHandler;
|
||||
uint32 _idlingFrames;
|
||||
Common::Mutex _handleMutex;
|
||||
Common::String _address;
|
||||
uint32 _serverPort;
|
||||
|
||||
void startTimer(int interval = TIMER_INTERVAL);
|
||||
void stopTimer();
|
||||
void handle();
|
||||
void handleClient(uint32 i);
|
||||
void acceptClient();
|
||||
void resolveAddress(void *ipAddress);
|
||||
void addPathHandler(const Common::String &path, BaseHandler *handler);
|
||||
|
||||
public:
|
||||
static const uint32 DEFAULT_SERVER_PORT = 12345;
|
||||
|
||||
LocalWebserver();
|
||||
~LocalWebserver() override;
|
||||
|
||||
void start(bool useMinimalMode = false);
|
||||
void stop();
|
||||
void stopOnIdle();
|
||||
|
||||
Common::String getAddress();
|
||||
IndexPageHandler &indexPageHandler();
|
||||
bool isRunning();
|
||||
static uint32 getPort();
|
||||
|
||||
#ifdef USE_CLOUD
|
||||
void setStorageConnectionCallback(Networking::ErrorCallback cb) { _connectCloudHandler.setStorageConnectionCallback(cb); }
|
||||
#endif // USE_CLOUD
|
||||
|
||||
static void setClientGetHandler(Client &client, const Common::String &response, long code = 200, const char *mimeType = nullptr);
|
||||
static void setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code = 200, const char *mimeType = nullptr);
|
||||
static void setClientRedirectHandler(Client &client, const Common::String &response, const Common::String &location, const char *mimeType = nullptr);
|
||||
static void setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, const Common::String &location, const char *mimeType = nullptr);
|
||||
static Common::String urlDecode(const Common::String &value);
|
||||
static Common::String urlEncodeQueryParameterValue(const Common::String &value);
|
||||
};
|
||||
|
||||
/** Shortcut for accessing the local webserver. */
|
||||
#define LocalServer Networking::LocalWebserver::instance()
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
544
backends/networking/sdl_net/reader.cpp
Normal file
544
backends/networking/sdl_net/reader.cpp
Normal file
@@ -0,0 +1,544 @@
|
||||
/* 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/sdl_net/reader.h"
|
||||
#include "backends/fs/fs-factory.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Reader::Reader() {
|
||||
_state = RS_NONE;
|
||||
_mode = RM_HTTP_GENERIC;
|
||||
_content = nullptr;
|
||||
_bytesLeft = 0;
|
||||
|
||||
_window = nullptr;
|
||||
_windowUsed = 0;
|
||||
_windowSize = 0;
|
||||
_windowReadPosition = 0;
|
||||
_windowWritePosition = 0;
|
||||
_windowHash = 0;
|
||||
|
||||
_headersStream = nullptr;
|
||||
_firstBlock = true;
|
||||
|
||||
_contentLength = 0;
|
||||
_availableBytes = 0;
|
||||
_isBadRequest = false;
|
||||
_allContentRead = false;
|
||||
}
|
||||
|
||||
Reader::~Reader() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
Reader &Reader::operator=(Reader &r) {
|
||||
if (this == &r)
|
||||
return *this;
|
||||
cleanup();
|
||||
|
||||
_state = r._state;
|
||||
_content = r._content;
|
||||
_bytesLeft = r._bytesLeft;
|
||||
r._state = RS_NONE;
|
||||
|
||||
_window = r._window;
|
||||
_windowUsed = r._windowUsed;
|
||||
_windowSize = r._windowSize;
|
||||
_windowReadPosition = r._windowReadPosition;
|
||||
_windowWritePosition = r._windowWritePosition;
|
||||
_windowHash = r._windowHash;
|
||||
r._window = nullptr;
|
||||
|
||||
_headersStream = r._headersStream;
|
||||
r._headersStream = nullptr;
|
||||
|
||||
_headers = r._headers;
|
||||
_method = r._method;
|
||||
_path = r._path;
|
||||
_query = r._query;
|
||||
_anchor = r._anchor;
|
||||
_queryParameters = r._queryParameters;
|
||||
_contentLength = r._contentLength;
|
||||
_boundary = r._boundary;
|
||||
_availableBytes = r._availableBytes;
|
||||
_firstBlock = r._firstBlock;
|
||||
_isBadRequest = r._isBadRequest;
|
||||
_allContentRead = r._allContentRead;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Reader::cleanup() {
|
||||
//_content is not to be freed, it's not owned by Reader
|
||||
|
||||
if (_headersStream != nullptr)
|
||||
delete _headersStream;
|
||||
|
||||
if (_window != nullptr)
|
||||
freeWindow();
|
||||
}
|
||||
|
||||
namespace {
|
||||
uint32 calculateHash(const Common::String& boundary) {
|
||||
uint32 result = 0;
|
||||
for (uint32 i = 0; i < boundary.size(); ++i)
|
||||
result ^= boundary[i];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::readAndHandleFirstHeaders() {
|
||||
Common::String boundary = "\r\n\r\n";
|
||||
uint32 boundaryHash = calculateHash(boundary);
|
||||
if (_window == nullptr) {
|
||||
makeWindow(boundary.size());
|
||||
}
|
||||
if (_headersStream == nullptr) {
|
||||
_headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
while (readOneByteInStream(_headersStream, boundary, boundaryHash)) {
|
||||
if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) {
|
||||
_isBadRequest = true;
|
||||
return true;
|
||||
}
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
}
|
||||
handleFirstHeaders(_headersStream);
|
||||
|
||||
freeWindow();
|
||||
_state = RS_READING_CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) {
|
||||
Common::String boundary = "\r\n\r\n";
|
||||
uint32 boundaryHash = calculateHash(boundary);
|
||||
if (_window == nullptr) makeWindow(boundary.size());
|
||||
|
||||
while (readOneByteInStream(stream, boundary, boundaryHash)) {
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
}
|
||||
if (stream) stream->flush();
|
||||
|
||||
freeWindow();
|
||||
_state = RS_READING_CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void readFromThatUntilLineEnd(const char *cstr, const Common::String &needle, Common::String &result) {
|
||||
const char *position = strstr(cstr, needle.c_str());
|
||||
|
||||
if (position) {
|
||||
char c;
|
||||
for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
|
||||
if (c == '\n' || c == '\r')
|
||||
break;
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) {
|
||||
if (!_boundary.empty()) {
|
||||
warning("Reader: handleFirstHeaders() called when first headers were already handled");
|
||||
return;
|
||||
}
|
||||
|
||||
//parse method, path, query, fragment
|
||||
_headers = readEverythingFromMemoryStream(headersStream);
|
||||
parseFirstLine(_headers);
|
||||
|
||||
//find boundary
|
||||
_boundary = "";
|
||||
readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary);
|
||||
|
||||
//find content length
|
||||
Common::String contentLength = "";
|
||||
readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength);
|
||||
_contentLength = contentLength.asUint64();
|
||||
_availableBytes = _contentLength;
|
||||
}
|
||||
|
||||
void Reader::parseFirstLine(const Common::String &headersToParse) {
|
||||
uint32 headersSize = headersToParse.size();
|
||||
bool bad = false;
|
||||
|
||||
if (headersSize > 0) {
|
||||
const char *cstr = headersToParse.c_str();
|
||||
const char *position = strstr(cstr, "\r\n");
|
||||
if (position) { //we have at least one line - and we want the first one
|
||||
//"<METHOD> <path> HTTP/<VERSION>\r\n"
|
||||
Common::String methodParsed, pathParsed, http, buf;
|
||||
uint32 length = position - cstr;
|
||||
if (headersSize > length)
|
||||
headersSize = length;
|
||||
for (uint32 i = 0; i < headersSize; ++i) {
|
||||
if (headersToParse[i] != ' ')
|
||||
buf += headersToParse[i];
|
||||
if (headersToParse[i] == ' ' || i == headersSize - 1) {
|
||||
if (methodParsed == "") {
|
||||
methodParsed = buf;
|
||||
} else if (pathParsed == "") {
|
||||
pathParsed = buf;
|
||||
} else if (http == "") {
|
||||
http = buf;
|
||||
} else {
|
||||
bad = true;
|
||||
break;
|
||||
}
|
||||
buf = "";
|
||||
}
|
||||
}
|
||||
|
||||
//check that method is supported
|
||||
if (methodParsed != "GET" && methodParsed != "PUT" && methodParsed != "POST" && methodParsed != "OPTIONS")
|
||||
bad = true;
|
||||
|
||||
//check that HTTP/<VERSION> is OK
|
||||
if (!http.hasPrefix("HTTP/"))
|
||||
bad = true;
|
||||
|
||||
_method = methodParsed;
|
||||
parsePathQueryAndAnchor(pathParsed);
|
||||
}
|
||||
}
|
||||
|
||||
if (bad) _isBadRequest = true;
|
||||
}
|
||||
|
||||
void Reader::parsePathQueryAndAnchor(const Common::String &pathToParse) {
|
||||
//<path>[?query][#anchor]
|
||||
bool readingPath = true;
|
||||
bool readingQuery = false;
|
||||
_path = "";
|
||||
_query = "";
|
||||
_anchor = "";
|
||||
for (uint32 i = 0; i < pathToParse.size(); ++i) {
|
||||
if (readingPath) {
|
||||
if (pathToParse[i] == '?') {
|
||||
readingPath = false;
|
||||
readingQuery = true;
|
||||
} else {
|
||||
_path += pathToParse[i];
|
||||
}
|
||||
} else if (readingQuery) {
|
||||
if (pathToParse[i] == '#') {
|
||||
readingQuery = false;
|
||||
} else {
|
||||
_query += pathToParse[i];
|
||||
}
|
||||
} else {
|
||||
_anchor += pathToParse[i];
|
||||
}
|
||||
}
|
||||
|
||||
parseQueryParameters();
|
||||
}
|
||||
|
||||
void Reader::parseQueryParameters() {
|
||||
Common::String key = "";
|
||||
Common::String value = "";
|
||||
bool readingKey = true;
|
||||
for (uint32 i = 0; i < _query.size(); ++i) {
|
||||
if (readingKey) {
|
||||
if (_query[i] == '=') {
|
||||
readingKey = false;
|
||||
value = "";
|
||||
} else {
|
||||
key += _query[i];
|
||||
}
|
||||
} else {
|
||||
if (_query[i] == '&') {
|
||||
if (_queryParameters.contains(key))
|
||||
warning("Reader: query parameter \"%s\" is already set!", key.c_str());
|
||||
else
|
||||
_queryParameters[key] = LocalWebserver::urlDecode(value);
|
||||
readingKey = true;
|
||||
key = "";
|
||||
} else {
|
||||
value += _query[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!key.empty()) {
|
||||
if (_queryParameters.contains(key))
|
||||
warning("Reader: query parameter \"%s\" is already set!", key.c_str());
|
||||
else
|
||||
_queryParameters[key] = LocalWebserver::urlDecode(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::readContentIntoStream(Common::WriteStream *stream) {
|
||||
Common::String boundary = "--" + _boundary;
|
||||
if (!_firstBlock)
|
||||
boundary = "\r\n" + boundary;
|
||||
if (_boundary.empty())
|
||||
boundary = "\r\n";
|
||||
if (_window == nullptr)
|
||||
makeWindow(boundary.size());
|
||||
|
||||
uint32 boundaryHash = calculateHash(boundary);
|
||||
while (readOneByteInStream(stream, boundary, boundaryHash)) {
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
}
|
||||
|
||||
_firstBlock = false;
|
||||
if (stream)
|
||||
stream->flush();
|
||||
|
||||
freeWindow();
|
||||
_state = RS_READING_HEADERS;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reader::makeWindow(uint32 size) {
|
||||
freeWindow();
|
||||
|
||||
_window = new byte[size];
|
||||
_windowUsed = 0;
|
||||
_windowSize = size;
|
||||
_windowReadPosition = 0;
|
||||
_windowWritePosition = 0;
|
||||
_windowHash = 0;
|
||||
}
|
||||
|
||||
void Reader::freeWindow() {
|
||||
delete[] _window;
|
||||
_window = nullptr;
|
||||
_windowUsed = _windowSize = 0;
|
||||
_windowReadPosition = _windowWritePosition = 0;
|
||||
_windowHash = 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool windowEqualsString(const byte *window, uint32 windowStart, uint32 windowSize, const Common::String &boundary) {
|
||||
if (boundary.size() != windowSize)
|
||||
return false;
|
||||
|
||||
for (uint32 i = 0; i < windowSize; ++i) {
|
||||
if (window[(windowStart + i) % windowSize] != boundary[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary, const uint32 boundaryHash) {
|
||||
byte b = readOne();
|
||||
++_windowUsed;
|
||||
_window[_windowWritePosition] = b;
|
||||
_windowWritePosition = (_windowWritePosition + 1) % _windowSize;
|
||||
_windowHash ^= b;
|
||||
if (_windowUsed < _windowSize)
|
||||
return true;
|
||||
|
||||
//when window is filled, check whether that's the boundary
|
||||
if (_windowHash == boundaryHash && windowEqualsString(_window, _windowReadPosition, _windowSize, boundary))
|
||||
return false;
|
||||
|
||||
//if not, add the first byte of the window to the string
|
||||
if (stream)
|
||||
stream->writeByte(_window[_windowReadPosition]);
|
||||
_windowHash ^= _window[_windowReadPosition];
|
||||
_windowReadPosition = (_windowReadPosition + 1) % _windowSize;
|
||||
--_windowUsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
byte Reader::readOne() {
|
||||
byte b = 0;
|
||||
_content->read(&b, 1);
|
||||
--_availableBytes;
|
||||
--_bytesLeft;
|
||||
return b;
|
||||
}
|
||||
|
||||
/// public
|
||||
|
||||
bool Reader::readFirstHeaders() {
|
||||
if (_state == RS_NONE)
|
||||
_state = RS_READING_HEADERS;
|
||||
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
|
||||
if (_state == RS_READING_HEADERS)
|
||||
return readAndHandleFirstHeaders();
|
||||
|
||||
warning("Reader::readFirstHeaders(): bad state");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Reader::readContent(Common::WriteStream* stream) {
|
||||
if (_mode != RM_HTTP_GENERIC) {
|
||||
warning("Reader::readContent(): bad mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state != RS_READING_CONTENT) {
|
||||
warning("Reader::readContent(): bad state");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
|
||||
byte buffer[1024];
|
||||
while (_availableBytes > 0) {
|
||||
uint32 bytesRead = _content->read(&buffer, 1024);
|
||||
_availableBytes -= bytesRead;
|
||||
_bytesLeft -= bytesRead;
|
||||
|
||||
if (stream)
|
||||
if (stream->write(buffer, bytesRead) != bytesRead) {
|
||||
warning("Reader::readContent(): failed to write buffer to stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_availableBytes == 0)
|
||||
_allContentRead = true;
|
||||
|
||||
if (!bytesLeft())
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream)
|
||||
stream->flush();
|
||||
|
||||
return _allContentRead;
|
||||
}
|
||||
|
||||
bool Reader::readFirstContent(Common::WriteStream *stream) {
|
||||
if (_mode != RM_POST_FORM_MULTIPART) {
|
||||
warning("Reader::readFirstContent(): bad mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state != RS_READING_CONTENT) {
|
||||
warning("Reader::readFirstContent(): bad state");
|
||||
return false;
|
||||
}
|
||||
|
||||
// no difference, actually
|
||||
return readBlockContent(stream);
|
||||
}
|
||||
|
||||
bool Reader::readBlockHeaders(Common::WriteStream *stream) {
|
||||
if (_mode != RM_POST_FORM_MULTIPART) {
|
||||
warning("Reader::readBlockHeaders(): bad mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state != RS_READING_HEADERS) {
|
||||
warning("Reader::readBlockHeaders(): bad state");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
|
||||
return readBlockHeadersIntoStream(stream);
|
||||
}
|
||||
|
||||
bool Reader::readBlockContent(Common::WriteStream *stream) {
|
||||
if (_mode != RM_POST_FORM_MULTIPART) {
|
||||
warning("Reader::readBlockContent(): bad mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state != RS_READING_CONTENT) {
|
||||
warning("Reader::readBlockContent(): bad state");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bytesLeft())
|
||||
return false;
|
||||
|
||||
if (!readContentIntoStream(stream))
|
||||
return false;
|
||||
|
||||
if (_availableBytes >= 2) {
|
||||
Common::String bts;
|
||||
bts += readOne();
|
||||
bts += readOne();
|
||||
if (bts == "--")
|
||||
_allContentRead = true;
|
||||
else if (bts != "\r\n")
|
||||
warning("Reader: strange bytes: \"%s\"", bts.c_str());
|
||||
} else {
|
||||
warning("Reader: strange ending");
|
||||
_allContentRead = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 Reader::bytesLeft() const { return _bytesLeft; }
|
||||
|
||||
void Reader::setMode(ReaderMode mode) { _mode = mode; }
|
||||
|
||||
void Reader::setContent(Common::MemoryReadWriteStream *stream) {
|
||||
_content = stream;
|
||||
_bytesLeft = stream->size() - stream->pos();
|
||||
}
|
||||
|
||||
bool Reader::badRequest() const { return _isBadRequest; }
|
||||
|
||||
bool Reader::noMoreContent() const { return _allContentRead; }
|
||||
|
||||
Common::String Reader::headers() const { return _headers; }
|
||||
|
||||
Common::String Reader::method() const { return _method; }
|
||||
|
||||
Common::String Reader::path() const { return _path; }
|
||||
|
||||
Common::String Reader::query() const { return _query; }
|
||||
|
||||
Common::String Reader::queryParameter(const Common::String &name) const { return _queryParameters.getValOrDefault(name); }
|
||||
|
||||
Common::String Reader::anchor() const { return _anchor; }
|
||||
|
||||
Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) {
|
||||
Common::String result;
|
||||
char buf[1024];
|
||||
uint32 readBytes;
|
||||
while (true) {
|
||||
readBytes = stream->read(buf, 1024);
|
||||
if (readBytes == 0)
|
||||
break;
|
||||
result += Common::String(buf, readBytes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
151
backends/networking/sdl_net/reader.h
Normal file
151
backends/networking/sdl_net/reader.h
Normal file
@@ -0,0 +1,151 @@
|
||||
/* 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_SDL_NET_READER_H
|
||||
#define BACKENDS_NETWORKING_SDL_NET_READER_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/hash-str.h"
|
||||
|
||||
namespace Common {
|
||||
class MemoryReadWriteStream;
|
||||
class WriteStream;
|
||||
}
|
||||
|
||||
namespace Networking {
|
||||
|
||||
enum ReaderState {
|
||||
RS_NONE,
|
||||
RS_READING_HEADERS,
|
||||
RS_READING_CONTENT
|
||||
};
|
||||
|
||||
enum ReaderMode {
|
||||
RM_HTTP_GENERIC,
|
||||
RM_POST_FORM_MULTIPART
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a helper class for Client.
|
||||
*
|
||||
* It parses HTTP request and finds headers
|
||||
* and content. It also supports POST form/multipart.
|
||||
*
|
||||
* One might pass the request even byte by byte,
|
||||
* Reader will always be able to continue from the
|
||||
* state it stopped on.
|
||||
*
|
||||
* Main headers/content must be read with
|
||||
* readFirstHeaders() and readFirstContent() methods.
|
||||
* Further headers/content blocks (POST form/multipart)
|
||||
* must be read with readBlockHeaders() and readBlockContent().
|
||||
*
|
||||
* Main headers and parsed URL components could be accessed
|
||||
* with special methods after reading.
|
||||
*
|
||||
* To use the object, call setContent() and then one of those
|
||||
* reading methods. It would return whether reading is over
|
||||
* or not. If reading is over, content stream still could
|
||||
* contain bytes to read with other methods.
|
||||
*
|
||||
* If reading is not over, Reader awaits you to call the
|
||||
* same reading method when you'd get more content.
|
||||
*
|
||||
* If it's over, you should check whether Reader awaits
|
||||
* more content with noMoreContent() and call the other
|
||||
* reading method, if it is. When headers are read, one
|
||||
* must read contents, and vice versa.
|
||||
*/
|
||||
|
||||
class Reader {
|
||||
ReaderState _state;
|
||||
ReaderMode _mode;
|
||||
Common::MemoryReadWriteStream *_content;
|
||||
uint32 _bytesLeft;
|
||||
|
||||
byte *_window;
|
||||
uint32 _windowUsed, _windowSize, _windowReadPosition, _windowWritePosition;
|
||||
uint32 _windowHash;
|
||||
|
||||
Common::MemoryReadWriteStream *_headersStream;
|
||||
|
||||
Common::String _headers;
|
||||
Common::String _method, _path, _query, _anchor;
|
||||
Common::HashMap<Common::String, Common::String> _queryParameters;
|
||||
uint32 _contentLength;
|
||||
Common::String _boundary;
|
||||
uint32 _availableBytes;
|
||||
bool _firstBlock;
|
||||
bool _isBadRequest;
|
||||
bool _allContentRead;
|
||||
|
||||
void cleanup();
|
||||
|
||||
bool readAndHandleFirstHeaders(); //true when ended reading
|
||||
bool readBlockHeadersIntoStream(Common::WriteStream *stream); //true when ended reading
|
||||
bool readContentIntoStream(Common::WriteStream *stream); //true when ended reading
|
||||
|
||||
void handleFirstHeaders(Common::MemoryReadWriteStream *headers);
|
||||
void parseFirstLine(const Common::String &headers);
|
||||
void parsePathQueryAndAnchor(const Common::String &pathToParse);
|
||||
void parseQueryParameters();
|
||||
|
||||
void makeWindow(uint32 size);
|
||||
void freeWindow();
|
||||
bool readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary, const uint32 boundaryHash);
|
||||
|
||||
byte readOne();
|
||||
uint32 bytesLeft() const;
|
||||
|
||||
public:
|
||||
static const int32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot
|
||||
|
||||
Reader();
|
||||
~Reader();
|
||||
|
||||
Reader &operator=(Reader &r);
|
||||
|
||||
bool readFirstHeaders(); //true when ended reading
|
||||
bool readContent(Common::WriteStream *stream); //true when ended reading
|
||||
bool readFirstContent(Common::WriteStream *stream); //true when ended reading
|
||||
bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading
|
||||
bool readBlockContent(Common::WriteStream *stream); //true when ended reading
|
||||
|
||||
void setMode(ReaderMode mode);
|
||||
void setContent(Common::MemoryReadWriteStream *stream);
|
||||
|
||||
bool badRequest() const;
|
||||
bool noMoreContent() const;
|
||||
|
||||
Common::String headers() const;
|
||||
Common::String method() const;
|
||||
Common::String path() const;
|
||||
Common::String query() const;
|
||||
Common::String queryParameter(const Common::String &name) const;
|
||||
Common::String anchor() const;
|
||||
|
||||
static Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
208
backends/networking/sdl_net/uploadfileclienthandler.cpp
Normal file
208
backends/networking/sdl_net/uploadfileclienthandler.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
/* 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/sdl_net/uploadfileclienthandler.h"
|
||||
#include "backends/networking/sdl_net/handlerutils.h"
|
||||
#include "backends/networking/sdl_net/localwebserver.h"
|
||||
#include "backends/networking/sdl_net/reader.h"
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
UploadFileClientHandler::UploadFileClientHandler(const Common::Path &parentDirectoryPath):
|
||||
_state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr),
|
||||
_parentDirectoryPath(parentDirectoryPath), _uploadedFiles(0) {}
|
||||
|
||||
UploadFileClientHandler::~UploadFileClientHandler() {
|
||||
delete _headersStream;
|
||||
delete _contentStream;
|
||||
}
|
||||
|
||||
void UploadFileClientHandler::handle(Client *client) {
|
||||
if (client == nullptr) {
|
||||
warning("UploadFileClientHandler::handle(): empty client pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
switch (_state) {
|
||||
case UFH_READING_CONTENT:
|
||||
if (client->readFirstContent(nullptr)) {
|
||||
_state = UFH_READING_BLOCK_HEADERS;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case UFH_READING_BLOCK_HEADERS:
|
||||
if (_headersStream == nullptr)
|
||||
_headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
|
||||
if (client->readBlockHeaders(_headersStream)) {
|
||||
handleBlockHeaders(client);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fail on suspicious headers
|
||||
if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("Invalid request: headers are too long!")));
|
||||
}
|
||||
break;
|
||||
|
||||
case UFH_READING_BLOCK_CONTENT:
|
||||
// _contentStream is created by handleBlockHeaders() if needed
|
||||
|
||||
if (client->readBlockContent(_contentStream)) {
|
||||
handleBlockContent(client);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case UFH_ERROR:
|
||||
case UFH_STOP:
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void readFromThatUntilDoubleQuote(const char *cstr, const Common::String &needle, Common::String &result) {
|
||||
const char *position = strstr(cstr, needle.c_str());
|
||||
|
||||
if (position) {
|
||||
char c;
|
||||
for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
|
||||
if (c == '"')
|
||||
break;
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UploadFileClientHandler::handleBlockHeaders(Client *client) {
|
||||
_state = UFH_READING_BLOCK_CONTENT;
|
||||
|
||||
// fail on suspicious headers
|
||||
if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("Invalid request: headers are too long!")));
|
||||
}
|
||||
|
||||
// search for "upload_file" field
|
||||
Common::String headers = Reader::readEverythingFromMemoryStream(_headersStream);
|
||||
Common::String fieldName = "";
|
||||
readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName);
|
||||
if (!fieldName.hasPrefix("upload_file"))
|
||||
return;
|
||||
|
||||
Common::String filename = "";
|
||||
readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename);
|
||||
|
||||
// skip block if <filename> is empty
|
||||
if (filename.empty())
|
||||
return;
|
||||
|
||||
if (HandlerUtils::hasForbiddenCombinations(filename))
|
||||
return;
|
||||
|
||||
// check that <path>/<filename> doesn't exist
|
||||
Common::Path path = _parentDirectoryPath.appendComponent(filename);
|
||||
Common::FSNode originalNode(path);
|
||||
if (!HandlerUtils::permittedPath(originalNode.getPath())) {
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("Invalid path!")));
|
||||
return;
|
||||
}
|
||||
if (originalNode.exists()) {
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("There is a file with that name in the parent directory!")));
|
||||
return;
|
||||
}
|
||||
|
||||
// remove previous stream (if there is one)
|
||||
if (_contentStream) {
|
||||
delete _contentStream;
|
||||
_contentStream = nullptr;
|
||||
}
|
||||
|
||||
// create file stream (and necessary subdirectories)
|
||||
Common::DumpFile *f = new Common::DumpFile();
|
||||
if (!f->open(originalNode.getPath(), true)) {
|
||||
delete f;
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("Failed to upload the file!")));
|
||||
return;
|
||||
}
|
||||
|
||||
_contentStream = f;
|
||||
}
|
||||
|
||||
void UploadFileClientHandler::handleBlockContent(Client *client) {
|
||||
_state = UFH_READING_BLOCK_HEADERS;
|
||||
|
||||
// if previous block headers were file-related and created a stream
|
||||
if (_contentStream) {
|
||||
_contentStream->flush();
|
||||
++_uploadedFiles;
|
||||
|
||||
delete _contentStream;
|
||||
_contentStream = nullptr;
|
||||
|
||||
if (client->noMoreContent()) {
|
||||
// success - redirect back to directory listing
|
||||
setSuccessHandler(*client);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no more content avaiable
|
||||
if (client->noMoreContent()) {
|
||||
// if no file field was found - failure
|
||||
if (_uploadedFiles == 0) {
|
||||
setErrorMessageHandler(*client, Common::convertFromU32String(_("No file was passed!")));
|
||||
} else {
|
||||
setSuccessHandler(*client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UploadFileClientHandler::setErrorMessageHandler(Client &client, const Common::String &message) {
|
||||
HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
|
||||
_state = UFH_ERROR;
|
||||
}
|
||||
|
||||
void UploadFileClientHandler::setSuccessHandler(Client &client) {
|
||||
// success - redirect back to directory listing
|
||||
HandlerUtils::setMessageHandler(
|
||||
client,
|
||||
Common::String::format(
|
||||
"%s<br/><a href=\"files?path=%s\">%s</a>",
|
||||
Common::convertFromU32String(_("Uploaded successfully!")).c_str(),
|
||||
client.queryParameter("path").c_str(),
|
||||
Common::convertFromU32String(_("Back to parent directory")).c_str()
|
||||
),
|
||||
(client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
|
||||
LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
|
||||
);
|
||||
_state = UFH_STOP;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user