string FileServer::parseResourcePath(const string& aResource, const websocketpp::http::parser::request& aRequest, StringPairList& headers_) const { // Serve files only from the resource directory if (aResource.empty() || aResource.find("..") != std::string::npos) { throw RequestException(websocketpp::http::status_code::bad_request, "Invalid resource path"); } auto request = aResource; auto extension = getExtension(request); if (!extension.empty()) { dcassert(extension[0] != '.'); // We have compressed versions only for JS files if (extension == "js" && aRequest.get_header("Accept-Encoding").find("gzip") != string::npos) { request += ".gz"; // The Content-Encoding header will be set only after the file has been read successfully // as gzip encoding shouldn't be used in case of errors... } if (extension != "html" && aResource != "/sw.js") { // File versioning is done with hashes in filenames (except for the index file and service worker) addCacheControlHeader(headers_, 365); } } else { // Forward all requests for non-static files to index // (but try to report API requests or other downloads with an invalid path) if (aRequest.get_header("Accept").find("text/html") == string::npos) { if (aRequest.get_header("Content-Type") == "application/json") { throw RequestException(websocketpp::http::status_code::not_acceptable, "File server won't serve JSON files. Did you mean \"/api" + aResource + "\" instead?"); } throw RequestException(websocketpp::http::status_code::not_found, "Invalid file path (hint: use \"Accept: text/html\" if you want index.html)"); } request = "index.html"; // The main chunk name may change and it's stored in the HTML file addCacheControlHeader(headers_, 0); } // Avoid double separators because of assertions if (!request.empty() && request.front() == '/') { request = request.substr(1); } // For windows Util::replace(request, "/", PATH_SEPARATOR_STR); return resourcePath + request; }
websocketpp::http::status_code::value ApiRouter::handleHttpRequest(const string& aRequestPath, const websocketpp::http::parser::request& aRequest, json& output_, json& error_, bool aIsSecure, const string& aIp) noexcept { SessionPtr session = nullptr; auto token = aRequest.get_header("Authorization"); if (token != websocketpp::http::empty_header) { session = WebServerManager::getInstance()->getUserManager().getSession(token); } auto& requestBody = aRequest.get_body(); dcdebug("Received HTTP request: %s\n", aRequest.get_body().c_str()); try { ApiRequest apiRequest(aRequestPath, aRequest.get_method(), output_, error_); apiRequest.validate(); apiRequest.parseHttpRequestJson(requestBody); apiRequest.setSession(session); return handleRequest(apiRequest, aIsSecure, nullptr, aIp); } catch (const std::exception& e) { error_ = { "message", "Parsing failed: " + string(e.what()) }; return websocketpp::http::status_code::bad_request; } return websocketpp::http::status_code::ok; }
string FileServer::parseResourcePath(const string& aResource, const websocketpp::http::parser::request& aRequest, StringPairList& headers_) const noexcept { auto request = aResource; auto extension = getExtension(request); if (!extension.empty()) { // Strip the dot extension = extension.substr(1); // We have compressed versions only for JS files if (extension == "js" && aRequest.get_header("Accept-Encoding").find("gzip") != string::npos) { request += ".gz"; headers_.emplace_back("Content-Encoding", "gzip"); } } else if (request.find("/build") != 0 && request != "/favicon.ico") { // Forward all requests for non-static files to index request = "/index.html"; } // For windows Util::replace(request, "/", PATH_SEPARATOR_STR); return resourcePath + request; }
websocketpp::http::status_code::value FileServer::handleGetRequest(const websocketpp::http::parser::request& aRequest, string& output_, StringPairList& headers_, const SessionPtr& aSession) noexcept { const auto& requestUrl = aRequest.get_uri(); dcdebug("Requesting file %s\n", requestUrl.c_str()); // Get the disk path string filePath; try { if (requestUrl.length() >= 6 && requestUrl.compare(0, 6, "/view/") == 0) { filePath = parseViewFilePath(requestUrl.substr(6), headers_, aSession); } else { filePath = parseResourcePath(requestUrl, aRequest, headers_); } } catch (const RequestException& e) { output_ = e.what(); return e.getCode(); } auto fileSize = File::getSize(filePath); int64_t startPos = 0, endPos = fileSize - 1; auto partialContent = parsePartialRange(aRequest.get_header("Range"), startPos, endPos); // Read file try { File f(filePath, File::READ, File::OPEN); f.setPos(startPos); output_ = f.read(static_cast<size_t>(endPos) + 1); } catch (const FileException& e) { dcdebug("Failed to serve the file %s: %s\n", filePath.c_str(), e.getError().c_str()); output_ = e.getError(); return websocketpp::http::status_code::not_found; } catch (const std::bad_alloc&) { output_ = "Not enough memory on the server to serve this request"; return websocketpp::http::status_code::internal_server_error; } { const auto ext = Util::getFileExt(filePath); if (ext == ".nfo") { string encoding; // Platform-independent encoding conversion function could be added if there is more use for it #ifdef _WIN32 encoding = "CP.437"; #else encoding = "cp437"; #endif output_ = Text::toUtf8(output_, encoding); } else if (ext == ".gz" && aRequest.get_header("Accept-Encoding").find("gzip") != string::npos) { headers_.emplace_back("Content-Encoding", "gzip"); } } { // Get the mime type (but get it from the original request with gzipped content) auto usingEncoding = find_if(headers_.begin(), headers_.end(), CompareFirst<string, string>("Content-Encoding")) != headers_.end(); auto type = getMimeType(usingEncoding ? requestUrl : filePath); if (type) { headers_.emplace_back("Content-Type", type); } } if (partialContent) { headers_.emplace_back("Content-Range", formatPartialRange(startPos, endPos, fileSize)); headers_.emplace_back("Accept-Ranges", "bytes"); return websocketpp::http::status_code::partial_content; } return websocketpp::http::status_code::ok; }