//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* AUTHORIZATION("HTTP_AUTHORIZATION"); 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), path() {} Request::~Request() { terminate(); } void Request::terminate() { switch (state) { case State::terminated: case State::initial: break; case State::accepted: std::cout << "A termination of accepted request that was not responded to, it's probably an error" << std::endl; FCGX_Finish_r(&raw); break; case State::responding: std::cout << "A termination of responding request that was not actually sent, it's probably an error" << std::endl; FCGX_Finish_r(&raw); break; case State::responded: FCGX_Finish_r(&raw); break; } state = State::terminated; } std::string_view Request::methodName() const { if (state == State::initial || state == State::terminated) 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); } bool Request::active() const { return state != State::initial && state != State::terminated; } 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; std::cout << responseCode() << '\t' << methodName() << '\t' << path << std::endl; break; case State::responded: throw std::runtime_error("An attempt to mark the request as a complete for the second time"); break; case State::terminated: throw std::runtime_error("An attempt to mark the request as a complete on a terminated request"); break; } } Request::State Request::currentState() const { return state; } void Request::readPath(const std::string& serverName) { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); if (!path.empty()) std::cout << "Request already has path \"" + path + "\", but it's being read again, probably an error"; 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); } } std::string Request::getPath() const { 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) { if (!active()) throw std::runtime_error("An attempt to print environment of a request in a wrong state"); 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 (!active()) throw std::runtime_error("An attempt to read content type of a request in a wrong state"); 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 (!active()) throw std::runtime_error("An attempt to read content length of a request in a wrong state"); return atoi(FCGX_GetParam(CONTENT_LENGTH, raw.envp)); } std::map Request::getForm() const { if (!active()) throw std::runtime_error("An attempt to read form of a request in a wrong state"); 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; } std::string Request::getAuthorizationToken() const { if (!active()) throw std::runtime_error("An attempt to read authorization token of a request in a wrong state"); const char* auth = FCGX_GetParam(AUTHORIZATION, raw.envp); if (auth == nullptr) return std::string(); std::string result(auth); if (result.find("Bearer") != 0) return std::string(); result.erase(0, 6); trim(result); return result; }