Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote ISO: Allow sharing a full folder instead of Recent #18632

Merged
merged 2 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Common/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,15 @@ void GetQuotedStrings(const std::string& str, std::vector<std::string> &output)
}
}

std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) {
std::string ReplaceAll(std::string_view input, std::string_view src, std::string_view dest) {
size_t pos = 0;

std::string result(input);

if (src == dest)
return result;

// TODO: Don't mutate the input, just append stuff to the output instead.
while (true) {
pos = result.find(src, pos);
if (pos == result.npos)
Expand Down
2 changes: 1 addition & 1 deletion Common/StringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void SplitString(std::string_view str, const char delim, std::vector<std::string

void GetQuotedStrings(const std::string& str, std::vector<std::string>& output);

std::string ReplaceAll(std::string input, const std::string& src, const std::string& dest);
std::string ReplaceAll(std::string_view input, std::string_view src, std::string_view dest);

// Takes something like R&eplace and returns Replace, plus writes 'e' to *shortcutChar
// if not nullptr. Useful for Windows menu strings.
Expand Down
2 changes: 2 additions & 0 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("RemoteISOSubdir", &g_Config.sRemoteISOSubdir, "/", CfgFlag::DEFAULT),
ConfigSetting("RemoteDebuggerOnStartup", &g_Config.bRemoteDebuggerOnStartup, false, CfgFlag::DEFAULT),
ConfigSetting("RemoteTab", &g_Config.bRemoteTab, false, CfgFlag::DEFAULT),
ConfigSetting("RemoteISOSharedDir", &g_Config.sRemoteISOSharedDir, "", CfgFlag::DEFAULT),
ConfigSetting("RemoteISOShareType", &g_Config.iRemoteISOShareType, (int)RemoteISOShareType::RECENT, CfgFlag::DEFAULT),

#ifdef __ANDROID__
ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, ROTATION_AUTO_HORIZONTAL),
Expand Down
2 changes: 2 additions & 0 deletions Core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ struct Config {
bool bRemoteISOManual;
bool bRemoteShareOnStartup;
std::string sRemoteISOSubdir;
std::string sRemoteISOSharedDir;
int iRemoteISOShareType;
bool bRemoteDebuggerOnStartup;
bool bRemoteTab;
bool bMemStickInserted;
Expand Down
5 changes: 5 additions & 0 deletions Core/ConfigValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,8 @@ enum class SkipGPUReadbackMode : int {
SKIP,
COPY_TO_TEXTURE,
};

enum class RemoteISOShareType : int {
RECENT,
LOCAL_FOLDER,
};
71 changes: 59 additions & 12 deletions Core/WebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
#include "Common/Net/HTTPClient.h"
#include "Common/Net/HTTPServer.h"
#include "Common/Net/Sinks.h"
#include "Common/Net/URL.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Log.h"
#include "Common/File/FileUtil.h"
#include "Common/File/FileDescriptor.h"
#include "Common/File/DirListing.h"
#include "Common/File/VFS/VFS.h"
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
Expand All @@ -49,6 +51,16 @@ static ServerStatus serverStatus;
static std::mutex serverStatusLock;
static int serverFlags;

// NOTE: These *only* encode spaces, which is really enough.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you also want to ecnode at least %. A filename with "%20" in it (such as a downloaded file) will not work.

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, true. Just doing what the old Recent code does.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, and I did first use UriDecode, but some logic broke because it encoded slashes. Should maybe fix the root cause of that instead maybe, but could also simply not encode slashes to keep compatibilty with older versions.


std::string ServerUriEncode(std::string_view plain) {
return ReplaceAll(plain, " ", "%20");
}

std::string ServerUriDecode(std::string_view encoded) {
return ReplaceAll(encoded, "%20", " ");
}

static void UpdateStatus(ServerStatus s) {
std::lock_guard<std::mutex> guard(serverStatusLock);
serverStatus = s;
Expand Down Expand Up @@ -130,6 +142,12 @@ bool RemoteISOFileSupported(const std::string &filename) {
}

static std::string RemotePathForRecent(const std::string &filename) {
Path path(filename);
if (path.Type() == PathType::HTTP) {
// Don't re-share HTTP files from some other device.
return std::string();
}

#ifdef _WIN32
static const std::string sep = "\\/";
#else
Expand All @@ -147,19 +165,30 @@ static std::string RemotePathForRecent(const std::string &filename) {
// 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 (RemoteISOFileSupported(basename)) {
return ReplaceAll(basename, " ", "%20");
return ServerUriEncode(basename);
}
return "";

return std::string();
}

static Path LocalFromRemotePath(const std::string &path) {
for (const std::string &filename : g_Config.RecentIsos()) {
std::string basename = RemotePathForRecent(filename);
if (basename == path) {
return Path(filename);
switch ((RemoteISOShareType)g_Config.iRemoteISOShareType) {
case RemoteISOShareType::RECENT:
for (const std::string &filename : g_Config.RecentIsos()) {
std::string basename = RemotePathForRecent(filename);
if (basename == path) {
return Path(filename);
}
}
return Path();
case RemoteISOShareType::LOCAL_FOLDER:
{
std::string decoded = ServerUriDecode(path);
return Path(g_Config.sRemoteISOSharedDir) / decoded;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably technically a security gap. The Recent path is intentionally a bit careful to only take the filename-part, and not take for example "../../../passwd". I guess this is probably fine as hopefully you're only on a network with devices you trust...

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... Probably worth adding checks against though indeed.

}
default:
return Path();
}
return Path();
}

static void DiscHandler(const http::ServerRequest &request, const Path &filename) {
Expand Down Expand Up @@ -226,12 +255,30 @@ static void HandleListing(const http::ServerRequest &request) {
request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain");
request.Out()->Printf("/\n");
if (serverFlags & (int)WebServerFlags::DISCS) {
// List the current discs in their recent order.
for (const std::string &filename : g_Config.RecentIsos()) {
std::string basename = RemotePathForRecent(filename);
if (!basename.empty()) {
request.Out()->Printf("%s\n", basename.c_str());
switch ((RemoteISOShareType)g_Config.iRemoteISOShareType) {
case RemoteISOShareType::RECENT:
// List the current discs in their recent order.
for (const std::string &filename : g_Config.RecentIsos()) {
std::string basename = RemotePathForRecent(filename);
if (!basename.empty()) {
request.Out()->Printf("%s\n", basename.c_str());
}
}
break;
case RemoteISOShareType::LOCAL_FOLDER:
{
std::vector<File::FileInfo> entries;
File::GetFilesInDir(Path(g_Config.sRemoteISOSharedDir), &entries);
for (const auto &entry : entries) {
// TODO: Support browsing into subdirs. How are folders marked?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folders have a trailing slash. But I can't remember if browsing supports them... I kinda think it does? It wouldn't be hard to anyway.

People will probably expect this to be recursive or else to support folders (my personal preference would be folders to work.)

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I will add folder support later. I didn't think about the trailing slash - that'll be enough, and I'll make sure it works if it doesn't already.

Copy link
Owner Author

@hrydgard hrydgard Dec 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, let's hope the user doesn't have a subfolder called "debugger". Since /debugger is handled separately :)

We're gonna have to use FallbackHandler to handle subfolder requests.

if (entry.isDirectory || !RemoteISOFileSupported(entry.name)) {
continue;
}
std::string encoded = ServerUriEncode(entry.name);
request.Out()->Printf("%s\n", encoded.c_str());
}
break;
}
}
}
if (serverFlags & (int)WebServerFlags::DEBUGGER) {
Expand Down
21 changes: 18 additions & 3 deletions UI/RemoteISOScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,11 @@ void RemoteISOScreen::CreateViews() {
ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL);

leftColumnItems->Add(new TextView(ri->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
if ((RemoteISOShareType)g_Config.iRemoteISOShareType == RemoteISOShareType::RECENT) {
leftColumnItems->Add(new TextView(ri->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
} else {
leftColumnItems->Add(new TextView(std::string(ri->T("Share Games(Server)")) + " " + Path(g_Config.sRemoteISOSharedDir).ToVisualString(), new LinearLayoutParams(Margins(12, 5, 0, 5))));
}
leftColumnItems->Add(new TextView(ri->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
firewallWarning_ = leftColumnItems->Add(new TextView(ri->T("RemoteISOWinFirewall", "WARNING: Windows Firewall is blocking sharing"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
firewallWarning_->SetTextColor(0xFF0000FF);
Expand Down Expand Up @@ -589,8 +593,19 @@ void RemoteISOSettingsScreen::CreateViews() {
remoteisoSettings->Add(new CheckBox(&g_Config.bRemoteISOManual, ri->T("Manual Mode Client", "Manually configure client")));
remoteisoSettings->Add(new CheckBox(&g_Config.bRemoteTab, ri->T("Show Remote tab on main screen")));

UI::Choice *remoteServer;
remoteServer = new PopupTextInputChoice(&g_Config.sLastRemoteISOServer, ri->T("Remote Server"), "", 255, screenManager());
if (System_GetPropertyBool(SYSPROP_HAS_FOLDER_BROWSER)) {
static const char *shareTypes[] = { "Recent files", "Choose directory" };
remoteisoSettings->Add(new PopupMultiChoice(&g_Config.iRemoteISOShareType, ri->T("Files to share"), shareTypes, 0, ARRAY_SIZE(shareTypes), I18NCat::REMOTEISO, screenManager()));
FolderChooserChoice *folderChooser = remoteisoSettings->Add(new FolderChooserChoice(&g_Config.sRemoteISOSharedDir, ri->T("Files to share")));
folderChooser->SetEnabledFunc([=]() {
return g_Config.iRemoteISOShareType == (int)RemoteISOShareType::LOCAL_FOLDER;
});
} else {
// Can't pick a folder, only allow sharing recent stuff.
g_Config.iRemoteISOShareType = (int)RemoteISOShareType::RECENT;
}

UI::Choice *remoteServer = new PopupTextInputChoice(&g_Config.sLastRemoteISOServer, ri->T("Remote Server"), "", 255, screenManager());
remoteisoSettings->Add(remoteServer);
remoteServer->SetEnabledPtr(&g_Config.bRemoteISOManual);

Expand Down
Loading