diff --git a/core/libs/prx/libSceHttp/src/Curl.cpp b/core/libs/prx/libSceHttp/src/Curl.cpp index 532b55d76d4c..06a6d0e198d0 100644 --- a/core/libs/prx/libSceHttp/src/Curl.cpp +++ b/core/libs/prx/libSceHttp/src/Curl.cpp @@ -128,7 +128,7 @@ class CurlTransport final : public ITransport { headers.append(line); std::string name(line.substr(0, line.find(':'))); std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; }); - if (name == "location") redirectLocation = true; + if (name == "location" && line.substr(line.find(':') + 1).find_first_not_of(" \t\r\n") != std::string_view::npos) redirectLocation = true; if (name == "content-length") { auto text = line.substr(line.find(':') + 1); while (!text.empty() && (text.front() == ' ' || text.front() == '\t')) text.remove_prefix(1); diff --git a/core/libs/tests/GuestHttpNetwork.cpp b/core/libs/tests/GuestHttpNetwork.cpp index 63d08f552a1a..3197ce3eaff5 100644 --- a/core/libs/tests/GuestHttpNetwork.cpp +++ b/core/libs/tests/GuestHttpNetwork.cpp @@ -169,6 +169,14 @@ int main(int argc, char** argv) { const auto redirect = Get(connection, "/redirect"); Require(sceHttpGetStatusCode(redirect, &status) == 0 && status == 200 && ReadBody(redirect) == "final", "redirect following"); Require(sceHttpDeleteRequest(redirect) == 0, "redirect cleanup"); +#ifdef __linux__ + for (const auto* path : {"/emptyredirect", "/blankredirect"}) { + const auto emptyRedirect = Get(connection, path); + Require(sceHttpGetStatusCode(emptyRedirect, &status) == 0 && status == 302, "empty redirect changed status"); + Require(ReadBody(emptyRedirect) == "no destination", "empty redirect discarded response body"); + Require(sceHttpDeleteRequest(emptyRedirect) == 0, "empty redirect cleanup"); + } +#endif const auto noRedirect = sceHttpCreateRequest(connection, 0, "/redirect", 0); Require(sceHttpSetAutoRedirect(noRedirect, 0) == 0 && sceHttpSendRequest(noRedirect, nullptr, 0) == 0, "redirect-disabled send"); Require(sceHttpGetStatusCode(noRedirect, &status) == 0 && status == 302 && ReadBody(noRedirect).empty(), "redirect-disabled response"); diff --git a/core/libs/tests/http/network.py b/core/libs/tests/http/network.py index f4606fa8441b..19e3b189d6ec 100644 --- a/core/libs/tests/http/network.py +++ b/core/libs/tests/http/network.py @@ -79,6 +79,8 @@ def do_GET(self): self.close_connection = True elif self.path == "/redirect": self.response(302, b"", [("Location", "/final")]) + elif self.path in ("/emptyredirect", "/blankredirect"): + self.response(302, b"no destination", [("Location", "" if self.path == "/emptyredirect" else " \t ")]) elif self.path == "/headers": body = "|".join([self.headers.get("X-Inherited", ""), ",".join(self.headers.get_all("X-Value", [])), self.headers.get("User-Agent", "")]) self.response(200, body.encode()) diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index 632c933297fb..cd15c3b27ca3 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -25,4 +25,4 @@ - [sceHttpAbortWaitRequest, sceHttpWaitRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the abort takes only the epoll handle, as in [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); behaviour and error codes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), not confirmed on a PS5 title or hardware. An abort stays set: every later wait on the handle, a poll included, returns `SCE_HTTP_ERROR_ABORTED` (0x80431080) until the handle is destroyed. A wait woken by `sceHttpDestroyEpoll`, or by the `sceHttpTerm` that ends the last context, returns 0 events. The epoll calls return `BEFORE_INIT` (0x80431001) while no `sceHttpInit` context is alive and `INVALID_ID` (0x80431100) for an unknown context or handle, and `sceHttpTerm` returns the same errors instead of succeeding for any id. Handles are pointers, so a stale handle whose address a newer epoll reuses is taken as that epoll -- [Linux HTTP transport](../../../core/libs/prx/libSceHttp/src/Curl.cpp) - the response body is streamed through a 64 KiB host buffer. DNS, connect, upload and receive deadlines use host timing with 25 ms polling, not console measurements. DNS cancellation depends on the host resolver returning. The host CA bundle and curl/mbedTLS diagnostics replace the console trust store and errno details. Certificate callbacks apply the inherited TLS flag assumptions; certificate-loading APIs, nonblocking requests and guest callback dispatch remain unsupported. No title progression is established by loopback tests. +- [Linux HTTP transport](../../../core/libs/prx/libSceHttp/src/Curl.cpp) - the response body is streamed through a 64 KiB host buffer. Empty or whitespace-only Location headers do not suppress the response body, matching curl's header handling; console redirect behaviour is unmeasured. DNS, connect, upload and receive deadlines use host timing with 25 ms polling, not console measurements. DNS cancellation depends on the host resolver returning. The host CA bundle and curl/mbedTLS diagnostics replace the console trust store and errno details. Certificate callbacks apply the inherited TLS flag assumptions; certificate-loading APIs, nonblocking requests and guest callback dispatch remain unsupported. No title progression is established by loopback tests.