// Copyright (c) 2012- 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 "Core/HLE/HLE.h" #include "Core/HLE/sceParseUri.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/Debugger/MemBlockInfo.h" #include "Common/Net/URL.h" #include "Common/StringUtils.h" int workAreaAddString(u32 workAreaAddr, int workAreaSize, int offset, const char* s) { const std::string str = (s? s:""); int length = (int)str.length() + 1; // added space for null termination if (offset + length > workAreaSize) { length = workAreaSize - offset; if (length <= 0) { return offset; } } Memory::MemcpyUnchecked(workAreaAddr + offset, str.c_str(), length); return offset + length; } // FIXME: parsedUriArea, workArea can be 0/null? LittelBigPlanet seems to set workAreaSizeAddr to 0 static int sceUriParse(u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr, u32 workAreaSizeAddr, int workAreaSize) { WARN_LOG(Log::sceNet, "UNTESTED sceUriParse(%x, %s, %x, %x, %d) at %08x", parsedUriAreaAddr, safe_string(url), workAreaAddr, workAreaSizeAddr, workAreaSize, currentMIPS->pc); if (url == nullptr) return hleLogError(Log::sceNet, -1, "invalid arg"); auto workAreaSz = PSPPointer::Create(workAreaSizeAddr); // Size returner if (parsedUriAreaAddr == 0 || workAreaAddr == 0) { // Based on JPCSP: The required workArea size is maximum the size of the URL + 7 times the null-byte for string termination. int sz = (int)strlen(url) + 7; if (workAreaSz.IsValid()) { *workAreaSz = sz; workAreaSz.NotifyWrite("UriParse"); } return hleLogDebug(Log::sceNet, -1, "workAreaSize: %d, %d", sz, workAreaSize); } auto parsedUri = PSPPointer::Create(parsedUriAreaAddr); // Parse the URL into URI components // Full URI = scheme ":" ["//" [username [":" password] "@"] host [":" port]] path ["?" query] ["#" fragment] Url uri(url); // FIXME: URI without "://" should be valid too, due to PSPParsedUri.noSlash property if (!uri.Valid()) { return hleLogError(Log::sceNet, -1, "invalid arg"); } // Parse Host() into userInfo (in the format ":") and host std::string host = uri.Host(); std::string userInfoUserName = ""; std::string userInfoPassword = ""; // Extract Host size_t pos = host.find('@'); if (pos <= host.size()) { userInfoUserName = host.substr(0, pos); host.erase(0, pos + 1LL); // removing the "@" } // Extract UserName and Password pos = userInfoUserName.find(':'); if (pos <= userInfoUserName.size()) { userInfoPassword = userInfoUserName.substr(pos + 1LL); // removing the ":" userInfoUserName.erase(pos); } // Parse Resource() into path(/), query(?), and fragment(#) std::string path = uri.Resource(); std::string query = ""; std::string fragment = ""; // Extract Path pos = path.find('?'); if (pos <= path.size()) { query = path.substr(pos); // Not removing the leading "?". Query must have the leading "?" if it's not empty (according to JPCSP) if (query.size() == 1) query.clear(); path.erase(pos); } // Extract Query and Fragment pos = query.find('#'); if (pos <= query.size()) { fragment = query.substr(pos); // FIXME: Should we remove the leading "#" ? probably not, just like query query.erase(pos); } // FIXME: Related to "scheme-specific-part" (ie. parts after the scheme + ":") ? 0 = start with "//" pos = std::string(url).find("://"); if (parsedUri.IsValid()) parsedUri->noSlash = (pos != std::string::npos) ? 0 : 1; // Store the URI components in sequence into workAreanand store the respective addresses into the parsedUri structure. int offset = 0; if (parsedUri.IsValid()) parsedUri->schemeAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, uri.Protocol().c_str()); // FiXME: scheme in lowercase, while protocol in uppercase? if (parsedUri.IsValid()) parsedUri->userInfoUserNameAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoUserName.c_str()); if (parsedUri.IsValid()) parsedUri->userInfoPasswordAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, userInfoPassword.c_str()); if (parsedUri.IsValid()) parsedUri->hostAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, host.c_str()); if (parsedUri.IsValid()) parsedUri->pathAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, path.c_str()); if (parsedUri.IsValid()) parsedUri->queryAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, query.c_str()); if (parsedUri.IsValid()) parsedUri->fragmentAddr = workAreaAddr + offset; offset = workAreaAddString(workAreaAddr, workAreaSize, offset, fragment.c_str()); if (parsedUri.IsValid()) { parsedUri->port = uri.Port(); memset(parsedUri->unknown, 0, sizeof(parsedUri->unknown)); parsedUri.NotifyWrite("UriParse"); } // Update the size if (workAreaSz.IsValid()) { *workAreaSz = offset; workAreaSz.NotifyWrite("UriParse"); } return 0; } static int sceUriBuild(u32 workAreaAddr, u32 workAreaSizeAddr, int workAreaSize, u32 parsedUriAddr, int flags) { WARN_LOG(Log::sceNet, "UNTESTED sceUriBuild(%x, %x, %d, %x, %d) at %08x", workAreaAddr, workAreaSizeAddr, workAreaSize, parsedUriAddr, flags, currentMIPS->pc); auto workAreaSz = PSPPointer::Create(workAreaSizeAddr); if (parsedUriAddr == 0) { return hleLogError(Log::sceNet, -1, "invalid parsedUriAddr"); } auto parsedUri = PSPPointer::Create(parsedUriAddr); if (!parsedUri.IsValid()) { return hleLogError(Log::sceNet, -1, "invalid parsedUri structure"); } auto getUriComponent = [](u32 addr, int flags, int flagMask) -> std::string { if (addr == 0 || (flags & flagMask) == 0) return ""; const char* str = Memory::GetCharPointer(addr); return str ? std::string(str) : ""; }; std::string scheme = getUriComponent(parsedUri->schemeAddr, flags, 0x1); std::string userInfoUserName = getUriComponent(parsedUri->userInfoUserNameAddr, flags, 0x10); std::string userInfoPassword = getUriComponent(parsedUri->userInfoPasswordAddr, flags, 0x20); std::string host = getUriComponent(parsedUri->hostAddr, flags, 0x2); std::string path = getUriComponent(parsedUri->pathAddr, flags, 0x8); std::string query = getUriComponent(parsedUri->queryAddr, flags, 0x40); std::string fragment = getUriComponent(parsedUri->fragmentAddr, flags, 0x80); int port = (flags & 0x4) != 0 ? parsedUri->port : -1; // Build the complete URI std::string uri = ""; if (!scheme.empty()) { std::string finalScheme = scheme; if (finalScheme == "https") { // https is unsupported finalScheme = "http"; } uri += finalScheme + ":"; } if (parsedUri->noSlash == 0) { uri += "//"; } if (!userInfoUserName.empty()) { uri += userInfoUserName; if (!userInfoPassword.empty()) { uri += ":" + userInfoPassword; } uri += "@"; } if (!host.empty()) { uri += host; } if (port > 0) { int defaultPort = -1; if (parsedUri->schemeAddr != 0) { const char* protocol = Memory::GetCharPointer(parsedUri->schemeAddr); if (protocol) { std::string protocolStr(protocol); if (protocolStr == "http") defaultPort = 80; //else if (protocolStr == "https") defaultPort = 443; else if (protocolStr == "ftp") defaultPort = 21; else if (protocolStr == "ssh") defaultPort = 22; } } if (port != defaultPort) { uri += ":" + std::to_string(port); } } if (!path.empty()) { uri += path; } if (!query.empty()) { uri += query; } if (!fragment.empty()) { uri += fragment; } int requiredSize = (int)uri.length() + 1; if (workAreaSz.IsValid()) { *workAreaSz = requiredSize; workAreaSz.NotifyWrite("UriBuild"); } if (workAreaAddr == 0) { return hleLogDebug(Log::sceNet, 0, "size query, required size: %d", requiredSize); } if (requiredSize > workAreaSize) { return hleLogError(Log::sceNet, -1, "work area too small: need %d, have %d", requiredSize, workAreaSize); } Memory::MemcpyUnchecked(workAreaAddr, uri.c_str(), requiredSize); return hleLogDebug(Log::sceNet, 0, "built URI: %s", uri.c_str()); } static int sceUriEscape(u32 escapedAddr, u32 escapedLengthAddr, int escapedBufferLength, const char* source) { WARN_LOG(Log::sceNet, "UNTESTED sceUriEscape(%x, %x, %d, %s) at %08x", escapedAddr, escapedLengthAddr, escapedBufferLength, safe_string(source), currentMIPS->pc); if (source == nullptr) { return hleLogError(Log::sceNet, -1, "invalid source"); } auto escapedLength = PSPPointer::Create(escapedLengthAddr); // Helper function to check if a character needs to be escaped auto needsEscaping = [](unsigned char c) -> bool { // Characters that should NOT be escaped (unreserved characters in RFC 3986) // ALPHA / DIGIT / "-" / "." / "_" / "~" if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '~') { return false; } // Everything else needs to be escaped return true; }; // Helper function to convert a hex digit to character auto toHexChar = [](int digit) -> char { if (digit < 10) return '0' + digit; return 'A' + (digit - 10); }; std::string sourceStr(source); int requiredSize = 0; for (unsigned char c : sourceStr) { if (needsEscaping(c)) { requiredSize += 3; // %XX format } else { requiredSize += 1; // unchanged character } } requiredSize += 1; // null terminator if (escapedLength.IsValid()) { *escapedLength = requiredSize; escapedLength.NotifyWrite("UriEscape"); } if (escapedAddr == 0) { return hleLogDebug(Log::sceNet, 0, "size query, required size: %d", requiredSize); } if (requiredSize > escapedBufferLength) { return hleLogError(Log::sceNet, -1, "buffer too small: need %d, have %d", requiredSize, escapedBufferLength); } std::string escaped = ""; for (unsigned char c : sourceStr) { if (needsEscaping(c)) { // Escape as %XX escaped += '%'; escaped += toHexChar((c >> 4) & 0xF); escaped += toHexChar(c & 0xF); } else { // Keep unchanged escaped += c; } } Memory::MemcpyUnchecked(escapedAddr, escaped.c_str(), (u32)escaped.length() + 1); return hleLogDebug(Log::sceNet, 0, "escaped '%s' to '%s'", source, escaped.c_str()); } const HLEFunction sceParseUri[] = { {0X49E950EC, &WrapI_UUIC, "sceUriEscape", 'i', "xxis"}, {0X062BB07E, nullptr, "sceUriUnescape", '?', ""}, {0X568518C9, &WrapI_UCUUI, "sceUriParse", 'i', "xsxxi" }, {0X7EE318AF, &WrapI_UUIUI, "sceUriBuild", 'i', "xxxxi" }, }; void Register_sceParseUri() { RegisterHLEModule("sceParseUri", ARRAY_SIZE(sceParseUri), sceParseUri); }