// SPDX-FileCopyrightText: 2023 Yury Gubich // SPDX-License-Identifier: GPL-3.0-or-later #include "request.h" #include "response/response.h" constexpr static const char* REQUEST_METHOD("REQUEST_METHOD"); constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); constexpr static const char* SERVER_NAME("SERVER_NAME"); constexpr static const char* CONTENT_TYPE("CONTENT_TYPE"); constexpr static const char* CONTENT_LENGTH("CONTENT_LENGTH"); constexpr static const char* urlEncoded("application/x-www-form-urlencoded"); // constexpr static const char* REQUEST_URI("REQUEST_URI"); // // constexpr static const char* DOCUMENT_URI("DOCUMENT_URI"); // constexpr static const char* DOCUMENT_ROOT("DOCUMENT_ROOT"); // // constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); constexpr std::array< std::pair, static_cast(Request::Method::unknown) > methods = {{ {"GET", Request::Method::get}, {"POST", Request::Method::post} }}; Request::Request (): state(State::initial), raw(), response(nullptr) {} Request::~Request() { terminate(); } void Request::terminate() { switch (state) { case State::accepted: case State::responded: FCGX_Finish_r(&raw); break; default: break; } } std::string_view Request::methodName() const { if (state == State::initial) throw std::runtime_error("An attempt to read request method on not accepted request"); return FCGX_GetParam(REQUEST_METHOD, raw.envp); } Request::Method Request::method() const { std::string_view method = methodName(); for (const auto& pair : methods) { if (pair.first == method) return pair.second; } return Request::Method::unknown; } bool Request::wait(int socketDescriptor) { if (state != State::initial) throw std::runtime_error("An attempt to wait for new incomming request on a wrong request state"); FCGX_Request* request = &raw; FCGX_InitRequest(request, socketDescriptor, 0); int rc = FCGX_Accept_r(request); bool result = rc == 0; if (result) state = State::accepted; return result; } OStream Request::getOutputStream() { return OStream(raw.out); } OStream Request::getErrorStream() { return OStream(raw.err); } Response& Request::createResponse() { if (state != State::accepted) throw std::runtime_error("An attempt create response to the request in the wrong state"); response = std::unique_ptr(new Response(*this)); state = State::responding; return *response.get(); } Response& Request::createResponse(Response::Status status) { if (state != State::accepted) throw std::runtime_error("An attempt create response to the request in the wrong state"); response = std::unique_ptr(new Response(*this, status)); state = State::responding; return *response.get(); } uint16_t Request::responseCode() const { if (state != State::responded) throw std::runtime_error("An attempt create read response code on the wrong state"); return response->statusCode(); } void Request::responseIsComplete() { switch (state) { case State::initial: throw std::runtime_error("An attempt to mark the request as complete, but it wasn't even accepted yet"); break; case State::accepted: throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); break; case State::responding: state = State::responded; break; case State::responded: throw std::runtime_error("An attempt to mark the request as a complete for the second time"); break; } } Request::State Request::currentState() const { return state; } std::string Request::getPath(const std::string& serverName) const { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); std::string path; std::string_view scriptFileName(FCGX_GetParam(SCRIPT_FILENAME, raw.envp)); std::string::size_type snLocation = scriptFileName.find(serverName); if (snLocation != std::string::npos) { if (snLocation + serverName.size() < scriptFileName.size()) path = scriptFileName.substr(snLocation + serverName.size() + 1); } if (!path.empty()) { while (path.back() == '/') path.erase(path.end() - 1); } return path; } std::string Request::getServerName() const { if (state != State::accepted) throw std::runtime_error("An attempt to request server name on a wrong request state"); return FCGX_GetParam(SERVER_NAME, raw.envp);; } void Request::printEnvironment(std::ostream& out) { char **envp = raw.envp; for (int i = 0; envp[i] != nullptr; ++i) { out << envp[i] << "\n"; } } void Request::printEnvironment(nlohmann::json& out) { if (!out.is_object()) return; char **envp = raw.envp; for (int i = 0; envp[i] != nullptr; ++i) { std::string_view value(envp[i]); std::string::size_type pos = value.find('='); if (pos != std::string::npos) out[std::string(value.substr(0, pos))] = std::string(value.substr(pos + 1, value.size())); } } bool Request::isFormUrlEncoded() const { if (state == State::initial) throw std::runtime_error("An attempt to read request content type on not accepted request"); std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); if (!contentType.empty() && contentType.find(urlEncoded) != std::string_view::npos) { return true; } return false; } unsigned int Request::contentLength() const { if (state == State::initial) throw std::runtime_error("An attempt to read request content length on not accepted request"); return atoi(FCGX_GetParam(CONTENT_LENGTH, raw.envp)); } std::map Request::getForm() const { if (state == State::initial) throw std::runtime_error("An attempt to read form on not accepted request"); std::map result; std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); if (contentType.empty()) return result; unsigned int length = contentLength(); if (contentType.find(urlEncoded) != std::string_view::npos) { std::string postData(length, '\0'); FCGX_GetStr(&postData[0], length, raw.in); result = urlDecodeAndParse(postData); } return result; }