#include "Common/TimeUtil.h" #include "ppsspp_config.h" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #else #include /* socket definitions */ #include /* socket types */ #include /* for waitpid() */ #include /* struct sockaddr_in */ #include /* inet (3) funtions */ #include /* misc. UNIX functions */ #define closesocket close #endif #if PPSSPP_PLATFORM(UWP) #define in6addr_any IN6ADDR_ANY_INIT #endif #include #include #include #include #include "Common/Net/HTTPServer.h" #include "Common/Net/NetBuffer.h" #include "Common/Net/Sinks.h" #include "Common/File/FileDescriptor.h" #include "Common/Log.h" void NewThreadExecutor::Run(std::function &&func) { threads_.push_back(std::thread(func)); } NewThreadExecutor::~NewThreadExecutor() { // If Run was ever called... for (auto &thread : threads_) thread.join(); threads_.clear(); } namespace http { // Note: charset here helps prevent XSS. const char *const DEFAULT_MIME_TYPE = "text/html; charset=utf-8"; Request::Request(int fd) : fd_(fd) { in_ = new net::InputSink(fd); out_ = new net::OutputSink(fd); header_.ParseHeaders(in_); if (header_.ok) { VERBOSE_LOG(IO, "The request carried with it %i bytes", (int)header_.content_length); } else { Close(); } } Request::~Request() { Close(); if (!in_->Empty()) { ERROR_LOG(IO, "Input not empty - invalid request?"); } delete in_; if (!out_->Empty()) { ERROR_LOG(IO, "Output not empty - connection abort?"); } delete out_; } void Request::WriteHttpResponseHeader(const char *ver, int status, int64_t size, const char *mimeType, const char *otherHeaders) const { const char *statusStr; switch (status) { case 200: statusStr = "OK"; break; case 206: statusStr = "Partial Content"; break; case 301: statusStr = "Moved Permanently"; break; case 302: statusStr = "Found"; break; case 304: statusStr = "Not Modified"; break; case 400: statusStr = "Bad Request"; break; case 403: statusStr = "Forbidden"; break; case 404: statusStr = "Not Found"; break; case 405: statusStr = "Method Not Allowed"; break; case 406: statusStr = "Not Acceptable"; break; case 410: statusStr = "Gone"; break; case 416: statusStr = "Range Not Satisfiable"; break; case 418: statusStr = "I'm a teapot"; break; case 500: statusStr = "Internal Server Error"; break; case 503: statusStr = "Service Unavailable"; break; default: statusStr = "OK"; break; } net::OutputSink *buffer = Out(); buffer->Printf("HTTP/%s %03d %s\r\n", ver, status, statusStr); buffer->Push("Server: PPSSPPServer v0.1\r\n"); if (!mimeType || strcmp(mimeType, "websocket") != 0) { buffer->Printf("Content-Type: %s\r\n", mimeType ? mimeType : DEFAULT_MIME_TYPE); buffer->Push("Connection: close\r\n"); } if (size >= 0) { buffer->Printf("Content-Length: %llu\r\n", size); } if (otherHeaders) { buffer->Push(otherHeaders, (int)strlen(otherHeaders)); } buffer->Push("\r\n"); } void Request::WritePartial() const { _assert_(fd_); out_->Flush(); } void Request::Write() { _assert_(fd_); WritePartial(); Close(); } void Request::Close() { if (fd_) { closesocket(fd_); fd_ = 0; } } Server::Server(NewThreadExecutor *executor) : executor_(executor) { RegisterHandler("/", std::bind(&Server::HandleListing, this, std::placeholders::_1)); SetFallbackHandler(std::bind(&Server::Handle404, this, std::placeholders::_1)); } Server::~Server() { delete executor_; } void Server::RegisterHandler(const char *url_path, UrlHandlerFunc handler) { handlers_[std::string(url_path)] = handler; } void Server::SetFallbackHandler(UrlHandlerFunc handler) { fallback_ = handler; } bool Server::Listen(int port, net::DNSType type) { bool success = false; if (type == net::DNSType::ANY || type == net::DNSType::IPV6) { success = Listen6(port, type == net::DNSType::IPV6); } if (!success && (type == net::DNSType::ANY || type == net::DNSType::IPV4)) { success = Listen4(port); } return success; } bool Server::Listen4(int port) { listener_ = socket(AF_INET, SOCK_STREAM, 0); if (listener_ < 0) return false; struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(port); int opt = 1; // Enable re-binding to avoid the pain when restarting the server quickly. setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { #if PPSSPP_PLATFORM(WINDOWS) int err = WSAGetLastError(); #else int err = errno; #endif ERROR_LOG(IO, "Failed to bind to port %d, error=%d - Bailing (ipv4)", port, err); closesocket(listener_); return false; } fd_util::SetNonBlocking(listener_, true); // 1024 is the max number of queued requests. if (listen(listener_, 1024) < 0) { closesocket(listener_); return false; } socklen_t len = sizeof(server_addr); if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { port = ntohs(server_addr.sin_port); } INFO_LOG(IO, "HTTP server started on port %d", port); port_ = port; return true; } bool Server::Listen6(int port, bool ipv6_only) { #if !PPSSPP_PLATFORM(SWITCH) listener_ = socket(AF_INET6, SOCK_STREAM, 0); if (listener_ < 0) return false; struct sockaddr_in6 server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin6_family = AF_INET6; server_addr.sin6_addr = in6addr_any; server_addr.sin6_port = htons(port); int opt = 1; // Enable re-binding to avoid the pain when restarting the server quickly. setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); // Enable listening on IPv6 and IPv4? opt = ipv6_only ? 1 : 0; setsockopt(listener_, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt)); if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { #if PPSSPP_PLATFORM(WINDOWS) int err = WSAGetLastError(); #else int err = errno; #endif ERROR_LOG(IO, "Failed to bind to port %d, error=%d - Bailing (ipv6)", port, err); closesocket(listener_); return false; } fd_util::SetNonBlocking(listener_, true); // 1024 is the max number of queued requests. if (listen(listener_, 1024) < 0) { closesocket(listener_); return false; } socklen_t len = sizeof(server_addr); if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { port = ntohs(server_addr.sin6_port); } INFO_LOG(IO, "HTTP server started on port %d", port); port_ = port; return true; #else return false; #endif } bool Server::RunSlice(double timeout) { if (listener_ < 0 || port_ == 0) { return false; } if (timeout <= 0.0) { timeout = 86400.0; } if (!fd_util::WaitUntilReady(listener_, timeout, false)) { return false; } union { struct sockaddr sa; struct sockaddr_in ipv4; #if !PPSSPP_PLATFORM(SWITCH) struct sockaddr_in6 ipv6; #endif } client_addr; socklen_t client_addr_size = sizeof(client_addr); int conn_fd = accept(listener_, &client_addr.sa, &client_addr_size); if (conn_fd >= 0) { executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd)); return true; } else { ERROR_LOG(IO, "socket accept failed: %i", conn_fd); return false; } } bool Server::Run(int port) { if (!Listen(port)) { return false; } while (true) { RunSlice(0.0); } // We'll never get here. Ever. return true; } void Server::Stop() { closesocket(listener_); } void Server::HandleConnection(int conn_fd) { Request request(conn_fd); if (!request.IsOK()) { WARN_LOG(IO, "Bad request, ignoring."); return; } HandleRequest(request); // TODO: Way to mark the content body as read, read it here if never read. // This allows the handler to stream if need be. // TODO: Could handle keep alive here. request.Write(); } void Server::HandleRequest(const Request &request) { HandleRequestDefault(request); } void Server::HandleRequestDefault(const Request &request) { if (request.resource() == nullptr) { fallback_(request); return; } // First, look through all handlers. If we got one, use it. auto handler = handlers_.find(request.resource()); if (handler != handlers_.end()) { (handler->second)(request); } else { // Let's hit the 404 handler instead. fallback_(request); } } void Server::Handle404(const Request &request) { INFO_LOG(IO, "No handler for '%s', falling back to 404.", request.resource()); const char *payload = "404 not found\r\n"; request.WriteHttpResponseHeader("1.0", 404, (int)strlen(payload)); request.Out()->Push(payload); } void Server::HandleListing(const Request &request) { request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain"); for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) { request.Out()->Printf("%s\n", iter->first.c_str()); } } } // namespace http