diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 3453660a45..c4a8f12b39 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -57,12 +57,85 @@ static void ExecuteServer() { net::Init(); 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[basename] = 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); + } + http->Listen(0); // TODO: Report local IP and port. UpdateStatus(ServerStatus::RUNNING); while (RetrieveStatus() == ServerStatus::RUNNING) { - http->RunSlice(5.0); + http->RunSlice(5.0); } net::Shutdown(); diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 1001df80f9..1658e45d18 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -60,48 +60,50 @@ private: // Register handlers on this class to serve stuff. class Server { - public: - Server(threading::Executor *executor); +public: + Server(threading::Executor *executor); - typedef std::function UrlHandlerFunc; - typedef std::map UrlHandlerMap; + typedef std::function UrlHandlerFunc; + typedef std::map UrlHandlerMap; - // Runs forever, serving request. If you want to do something else than serve pages, - // better put this on a thread. Returns false if failed to start serving, never - // returns if successful. - bool Run(int port); - // May run for (significantly) longer than timeout, but won't wait longer than that - // for a new connection to handle. - bool RunSlice(double timeout); - bool Listen(int port); + // Runs forever, serving request. If you want to do something else than serve pages, + // better put this on a thread. Returns false if failed to start serving, never + // returns if successful. + bool Run(int port); + // May run for (significantly) longer than timeout, but won't wait longer than that + // for a new connection to handle. + bool RunSlice(double timeout); + bool Listen(int port); - void RegisterHandler(const char *url_path, UrlHandlerFunc handler); - void SetFallbackHandler(UrlHandlerFunc handler); + void RegisterHandler(const char *url_path, UrlHandlerFunc handler); + void SetFallbackHandler(UrlHandlerFunc handler); - // If you want to customize things at a lower level than just a simple path handler, - // then inherit and override this. Implementations should forward to HandleRequestDefault - // if they don't recognize the url. - virtual void HandleRequest(const Request &request); + // If you want to customize things at a lower level than just a simple path handler, + // then inherit and override this. Implementations should forward to HandleRequestDefault + // if they don't recognize the url. + virtual void HandleRequest(const Request &request); - private: - void HandleConnection(int conn_fd); + int Port() { + return port_; + } - void GetRequest(Request *request); +private: + void HandleConnection(int conn_fd); - // Things like default 404, etc. - void HandleRequestDefault(const Request &request); + // Things like default 404, etc. + void HandleRequestDefault(const Request &request); - // Neat built-in handlers that are tied to the server. - void HandleListing(const Request &request); - void Handle404(const Request &request); + // Neat built-in handlers that are tied to the server. + void HandleListing(const Request &request); + void Handle404(const Request &request); - int listener_; - int port_; + int listener_; + int port_; - UrlHandlerMap handlers_; - UrlHandlerFunc fallback_; + UrlHandlerMap handlers_; + UrlHandlerFunc fallback_; - threading::Executor *executor_; + threading::Executor *executor_; }; } // namespace http