From 3a8a2ff9330bedb025716c76d91efbb6ac955daa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 13:51:18 -0700 Subject: [PATCH] http: Move web server to core and use flags. This way we can independently control the debugger and disc sharing, since debugger could have security impact. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/WebServer.cpp | 278 ++++++++++++++++++++++++++++++++++++ Core/WebServer.h | 27 ++++ UI/NativeApp.cpp | 4 +- UI/RemoteISOScreen.cpp | 209 ++------------------------- UI/RemoteISOScreen.h | 2 - UWP/CoreUWP/CoreUWP.vcxproj | 2 + android/jni/Android.mk | 1 + 10 files changed, 329 insertions(+), 204 deletions(-) create mode 100644 Core/WebServer.cpp create mode 100644 Core/WebServer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ea354847c9..1e0db28925 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1393,6 +1393,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HDRemaster.cpp Core/HDRemaster.h Core/ThreadEventQueue.h + Core/WebServer.cpp + Core/WebServer.h Core/Debugger/Breakpoints.cpp Core/Debugger/Breakpoints.h Core/Debugger/DebugInterface.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 209890e9f1..b068e321e9 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -511,6 +511,7 @@ AnySuitable + @@ -746,6 +747,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2efbdbd29a..fe637033f8 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -689,6 +689,9 @@ Core + + Core + @@ -1268,6 +1271,9 @@ Core + + Core + diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp new file mode 100644 index 0000000000..6b20646f2e --- /dev/null +++ b/Core/WebServer.cpp @@ -0,0 +1,278 @@ +// Copyright (c) 2014- PPSSPP Project. + +// 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, version 2.0 or later versions. + +// 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include +#include +#include "base/stringutil.h" +#include "base/timeutil.h" +#include "file/fd_util.h" +#include "net/http_client.h" +#include "net/http_server.h" +#include "net/sinks.h" +#include "thread/threadutil.h" +#include "Common/FileUtil.h" +#include "Common/Log.h" +#include "Core/Config.h" +#include "Core/WebServer.h" + +enum class ServerStatus { + STOPPED, + STARTING, + RUNNING, + STOPPING, + RESTARTING, +}; + +static const char *REPORT_HOSTNAME = "report.ppsspp.org"; +static const int REPORT_PORT = 80; + +static std::thread serverThread; +static ServerStatus serverStatus; +static std::mutex serverStatusLock; +static int serverFlags; + +static void UpdateStatus(ServerStatus s) { + std::lock_guard guard(serverStatusLock); + serverStatus = s; +} + +static bool UpdateStatus(ServerStatus s, ServerStatus old) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == old) { + serverStatus = s; + return true; + } + return false; +} + +static ServerStatus RetrieveStatus() { + std::lock_guard guard(serverStatusLock); + return serverStatus; +} + +// This reports the local IP address to report.ppsspp.org, which can then +// relay that address to a mobile device searching for the server. +static void RegisterServer(int port) { + http::Client http; + Buffer theVoid; + + char resource4[1024] = {}; + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) { + if (http.Connect()) { + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + } + + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV6)) { + // We register both IPv4 and IPv6 in case the other client is using a different one. + if (resource4[0] != 0 && http.Connect()) { + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + + // Currently, we're not using keepalive, so gotta reconnect... + if (http.Connect()) { + char resource6[1024] = {}; + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource6, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + } +} + +static void RegisterDiscHandlers(http::Server *http, std::unordered_map *paths) { + for (std::string filename : g_Config.recentIsos) { +#ifdef _WIN32 + static const std::string sep = "\\/"; +#else + static const std::string sep = "/"; +#endif + size_t basepos = filename.find_last_of(sep); + std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1)); + + // Let's not serve directories, since they won't work. Only single files. + // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. + if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { + (*paths)[ReplaceAll(basename, " ", "%20")] = filename; + } + } + + auto handler = [paths](const http::Request &request) { + std::string filename = (*paths)[request.resource()]; + s64 sz = File::GetFileSize(filename); + + std::string range; + if (request.Method() == http::RequestHeader::HEAD) { + request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n"); + } else if (request.GetHeader("range", &range)) { + s64 begin = 0, last = 0; + if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) { + request.WriteHttpResponseHeader(400, -1, "text/plain"); + request.Out()->Push("Could not understand range request."); + return; + } + + if (begin < 0 || begin > last || last >= sz) { + request.WriteHttpResponseHeader(416, -1, "text/plain"); + request.Out()->Push("Range goes outside of file."); + return; + } + + FILE *fp = File::OpenCFile(filename, "rb"); + if (!fp || fseek(fp, begin, SEEK_SET) != 0) { + request.WriteHttpResponseHeader(500, -1, "text/plain"); + request.Out()->Push("File access failed."); + if (fp) { + fclose(fp); + } + return; + } + + s64 len = last - begin + 1; + char contentRange[1024]; + sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz); + request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange); + + const size_t CHUNK_SIZE = 16 * 1024; + char *buf = new char[CHUNK_SIZE]; + for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) { + s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE); + fread(buf, chunklen, 1, fp); + request.Out()->Push(buf, chunklen); + } + fclose(fp); + delete [] buf; + request.Out()->Flush(); + } else { + request.WriteHttpResponseHeader(418, -1, "text/plain"); + request.Out()->Push("This server only supports range requests."); + } + }; + + for (auto pair : *paths) { + http->RegisterHandler(pair.first.c_str(), handler); + } +} + +static void ExecuteWebServer() { + setCurrentThreadName("HTTPServer"); + + auto http = new http::Server(new threading::NewThreadExecutor()); + std::unordered_map discPaths; + + if (serverFlags & (int)WebServerFlags::DISCS) { + RegisterDiscHandlers(http, &discPaths); + } + + if (!http->Listen(g_Config.iRemoteISOPort)) { + if (!http->Listen(0)) { + ERROR_LOG(FILESYS, "Unable to listen on any port"); + UpdateStatus(ServerStatus::STOPPED); + return; + } + } + UpdateStatus(ServerStatus::RUNNING); + + g_Config.iRemoteISOPort = http->Port(); + RegisterServer(http->Port()); + double lastRegister = real_time_now(); + while (RetrieveStatus() == ServerStatus::RUNNING) { + http->RunSlice(1.0); + + double now = real_time_now(); + if (now > lastRegister + 540.0) { + RegisterServer(http->Port()); + lastRegister = now; + } + } + + http->Stop(); + + // Move to STARTING to lock flags/STOPPING. + if (UpdateStatus(ServerStatus::STARTING, ServerStatus::RESTARTING)) { + ExecuteWebServer(); + } else { + UpdateStatus(ServerStatus::STOPPED); + } +} + +bool StartWebServer(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + switch (serverStatus) { + case ServerStatus::RUNNING: + case ServerStatus::RESTARTING: + if ((serverFlags & (int)flags) == (int)flags) { + // Already running those flags. + return false; + } + serverStatus = ServerStatus::RESTARTING; + serverFlags |= (int)flags; + return true; + + case ServerStatus::STOPPED: + serverStatus = ServerStatus::STARTING; + serverFlags = (int)flags; + serverThread = std::thread(&ExecuteWebServer); + serverThread.detach(); + return true; + + default: + return false; + } +} + +bool StopWebServer(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus != ServerStatus::RUNNING && serverStatus != ServerStatus::RESTARTING) { + return false; + } + + serverFlags &= ~(int)flags; + if (serverFlags == 0) { + serverStatus = ServerStatus::STOPPING; + } else { + serverStatus = ServerStatus::RESTARTING; + } + return true; +} + +bool WebServerStopping(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == ServerStatus::RESTARTING) { + return (serverFlags & (int)flags) == 0; + } + return serverStatus == ServerStatus::STOPPING; +} + +bool WebServerStopped(WebServerFlags flags) { + std::lock_guard guard(serverStatusLock); + if (serverStatus == ServerStatus::RUNNING) { + return (serverFlags & (int)flags) == 0; + } + return serverStatus == ServerStatus::STOPPED; +} diff --git a/Core/WebServer.h b/Core/WebServer.h new file mode 100644 index 0000000000..db0967cbf2 --- /dev/null +++ b/Core/WebServer.h @@ -0,0 +1,27 @@ +// Copyright (c) 2014- PPSSPP Project. + +// 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, version 2.0 or later versions. + +// 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +enum class WebServerFlags { + DISCS = 1, + + ALL = 1, +}; + +bool StartWebServer(WebServerFlags flags); +bool StopWebServer(WebServerFlags flags); +bool WebServerStopping(WebServerFlags flags); +bool WebServerStopped(WebServerFlags flags); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 7130c7d181..8ae0bedb41 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -84,6 +84,7 @@ #include "Core/HLE/sceUsbGps.h" #include "Core/Util/GameManager.h" #include "Core/Util/AudioFormat.h" +#include "Core/WebServer.h" #include "GPU/GPUInterface.h" #include "ui_atlas.h" @@ -587,7 +588,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } if (g_Config.bRemoteShareOnStartup) { - StartRemoteISOSharing(); + // TODO: Separate config options. + StartWebServer(WebServerFlags::ALL); } std::string sysName = System_GetProperty(SYSPROP_NAME); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 9831701e17..31d8f9f966 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -18,20 +18,15 @@ #include #include #include -#include #include "base/timeutil.h" -#include "file/fd_util.h" #include "i18n/i18n.h" #include "json/json_reader.h" #include "net/http_client.h" -#include "net/http_server.h" #include "net/resolve.h" -#include "net/sinks.h" -#include "thread/threadutil.h" #include "Common/Common.h" -#include "Common/FileUtil.h" #include "Core/Config.h" +#include "Core/WebServer.h" #include "UI/RemoteISOScreen.h" using namespace UI; @@ -39,190 +34,9 @@ using namespace UI; static const char *REPORT_HOSTNAME = "report.ppsspp.org"; static const int REPORT_PORT = 80; -enum class ServerStatus { - STOPPED, - STARTING, - RUNNING, - STOPPING, -}; - -static std::thread *serverThread = nullptr; -static ServerStatus serverStatus; -static std::mutex serverStatusLock; -static std::condition_variable serverStatusCond; - static bool scanCancelled = false; static bool scanAborted = false; -static void UpdateStatus(ServerStatus s) { - std::lock_guard guard(serverStatusLock); - serverStatus = s; - serverStatusCond.notify_one(); -} - -static ServerStatus RetrieveStatus() { - std::lock_guard guard(serverStatusLock); - return serverStatus; -} - -// This reports the local IP address to report.ppsspp.org, which can then -// relay that address to a mobile device searching for the server. -static void RegisterServer(int port) { - http::Client http; - Buffer theVoid; - - char resource4[1024] = {}; - if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) { - if (http.Connect(2, 20.0, &scanCancelled)) { - std::string ip = fd_util::GetLocalIP(http.sock()); - snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - - http.GET(resource4, &theVoid); - theVoid.Skip(theVoid.size()); - http.Disconnect(); - } - } - - if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV6)) { - // We register both IPv4 and IPv6 in case the other client is using a different one. - if (resource4[0] != 0 && http.Connect()) { - http.GET(resource4, &theVoid); - theVoid.Skip(theVoid.size()); - http.Disconnect(); - } - - // Currently, we're not using keepalive, so gotta reconnect... - if (http.Connect()) { - char resource6[1024] = {}; - std::string ip = fd_util::GetLocalIP(http.sock()); - snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - - http.GET(resource6, &theVoid); - theVoid.Skip(theVoid.size()); - http.Disconnect(); - } - } -} - -static void ExecuteServer() { - setCurrentThreadName("HTTPServer"); - - auto http = new http::Server(new threading::SameThreadExecutor()); - - std::map paths; - for (std::string filename : g_Config.recentIsos) { -#ifdef _WIN32 - static const std::string sep = "\\/"; -#else - static const std::string sep = "/"; -#endif - size_t basepos = filename.find_last_of(sep); - std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1)); - - // Let's not serve directories, since they won't work. Only single files. - // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. - if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { - paths[ReplaceAll(basename, " ", "%20")] = filename; - } - } - - auto handler = [&](const http::Request &request) { - std::string filename = paths[request.resource()]; - s64 sz = File::GetFileSize(filename); - - std::string range; - if (request.Method() == http::RequestHeader::HEAD) { - request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n"); - } else if (request.GetHeader("range", &range)) { - s64 begin = 0, last = 0; - if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) { - request.WriteHttpResponseHeader(400, -1, "text/plain"); - request.Out()->Push("Could not understand range request."); - return; - } - - if (begin < 0 || begin > last || last >= sz) { - request.WriteHttpResponseHeader(416, -1, "text/plain"); - request.Out()->Push("Range goes outside of file."); - return; - } - - FILE *fp = File::OpenCFile(filename, "rb"); - if (!fp || fseek(fp, begin, SEEK_SET) != 0) { - request.WriteHttpResponseHeader(500, -1, "text/plain"); - request.Out()->Push("File access failed."); - if (fp) { - fclose(fp); - } - return; - } - - s64 len = last - begin + 1; - char contentRange[1024]; - sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz); - request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange); - - const size_t CHUNK_SIZE = 16 * 1024; - char *buf = new char[CHUNK_SIZE]; - for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) { - s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE); - fread(buf, chunklen, 1, fp); - request.Out()->Push(buf, chunklen); - } - fclose(fp); - delete [] buf; - request.Out()->Flush(); - } else { - request.WriteHttpResponseHeader(418, -1, "text/plain"); - request.Out()->Push("This server only supports range requests."); - } - }; - - for (auto pair : paths) { - http->RegisterHandler(pair.first.c_str(), handler); - } - - if (!http->Listen(g_Config.iRemoteISOPort)) { - if (!http->Listen(0)) { - ERROR_LOG(FILESYS, "Unable to listen on any port"); - UpdateStatus(ServerStatus::STOPPED); - return; - } - } - UpdateStatus(ServerStatus::RUNNING); - - g_Config.iRemoteISOPort = http->Port(); - RegisterServer(http->Port()); - double lastRegister = real_time_now(); - while (RetrieveStatus() == ServerStatus::RUNNING) { - http->RunSlice(5.0); - - double now = real_time_now(); - if (now > lastRegister + 540.0) { - RegisterServer(http->Port()); - lastRegister = now; - } - } - - http->Stop(); - - UpdateStatus(ServerStatus::STOPPED); -} - -bool StartRemoteISOSharing() { - std::lock_guard guard(serverStatusLock); - - if (serverStatus != ServerStatus::STOPPED) { - return false; - } - - serverStatus = ServerStatus::STARTING; - serverThread = new std::thread(&ExecuteServer); - serverThread->detach(); - - return true; -} - static bool FindServer(std::string &resultHost, int &resultPort) { http::Client http; Buffer result; @@ -393,11 +207,8 @@ RemoteISOScreen::RemoteISOScreen() : serverRunning_(false), serverStopping_(fals void RemoteISOScreen::update() { UIScreenWithBackground::update(); - bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; + bool nowRunning = !WebServerStopped(WebServerFlags::DISCS); if (serverStopping_ && !nowRunning) { - // Server stopped, delete the thread. - delete serverThread; - serverThread = nullptr; serverStopping_ = false; } @@ -424,11 +235,10 @@ void RemoteISOScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); Choice *browseChoice = new Choice(ri->T("Browse Games")); rightColumnItems->Add(browseChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); - ServerStatus status = RetrieveStatus(); - if (status == ServerStatus::STOPPING) { + if (WebServerStopping(WebServerFlags::DISCS)) { rightColumnItems->Add(new Choice(ri->T("Stopping..")))->SetDisabledPtr(&serverStopping_); browseChoice->SetEnabled(false); - } else if (status != ServerStatus::STOPPED) { + } else if (!WebServerStopped(WebServerFlags::DISCS)) { rightColumnItems->Add(new Choice(ri->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); browseChoice->SetEnabled(false); } else { @@ -450,7 +260,7 @@ void RemoteISOScreen::CreateViews() { } UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { - if (!StartRemoteISOSharing()) { + if (!StartWebServer(WebServerFlags::DISCS)) { return EVENT_SKIPPED; } @@ -458,13 +268,10 @@ UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { } UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { - std::lock_guard guard(serverStatusLock); - - if (serverStatus != ServerStatus::RUNNING) { + if (!StopWebServer(WebServerFlags::DISCS)) { return EVENT_SKIPPED; } - serverStatus = ServerStatus::STOPPING; serverStopping_ = true; RecreateViews(); @@ -683,13 +490,13 @@ void RemoteISOBrowseScreen::CreateViews() { } RemoteISOSettingsScreen::RemoteISOSettingsScreen() { - serverRunning_ = RetrieveStatus() != ServerStatus::STOPPED; + serverRunning_ = !WebServerStopped(WebServerFlags::DISCS); } void RemoteISOSettingsScreen::update() { UIDialogScreenWithBackground::update(); - bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; + bool nowRunning = !WebServerStopped(WebServerFlags::DISCS); if (serverRunning_ != nowRunning) { RecreateViews(); } diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index a4384d793e..b7f7ff0f5c 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -25,8 +25,6 @@ #include "UI/MiscScreens.h" #include "UI/MainScreen.h" -bool StartRemoteISOSharing(); - class RemoteISOScreen : public UIScreenWithBackground { public: RemoteISOScreen(); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 04eba6a66f..7cd70cfa70 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -463,6 +463,7 @@ + @@ -673,6 +674,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2c081f2ca7..428134fbcb 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -297,6 +297,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Screenshot.cpp \ $(SRC)/Core/System.cpp \ $(SRC)/Core/TextureReplacer.cpp \ + $(SRC)/Core/WebServer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \