webserv/webserv/handler/URI.cpp
2025-11-04 17:10:25 +01:00

260 lines
7.6 KiB
C++

#include <webserv/config/AConfig.hpp> // for AConfig
#include <webserv/config/LocationConfig.hpp> // for LocationConfig
#include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/handler/URI.hpp>
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/utils/FileUtils.hpp> // for joinPath, isDirectory, isFile, getExtension, isValidPath
#include <webserv/utils/utils.hpp> // for trim, split
#include <cstddef> // for size_t
#include <map> // for map
#include <optional> // for optional, operator!=
#include <vector> // for vector
URI::URI(const HttpRequest &request, const ServerConfig &serverConfig)
: uriTrimmed_(utils::uriDecode(utils::trim(request.getTarget(), "/"))),
config_(matchConfig(uriTrimmed_, serverConfig))
{
Log::trace(LOCATION);
parseUri();
Log::debug("Parsed URI: " + uriTrimmed_, {{"ConfigType", config_->getType()}});
parseFullpath();
authority_ = request.getHeaders().getHost().value();
}
const AConfig *URI::matchConfig(const std::string &uri, const ServerConfig &serverConfig)
{
const auto &locations = serverConfig.getLocationPaths();
const AConfig *bestMatch = &serverConfig;
size_t maxMatchLength = 0;
for (const auto &locationPath : locations)
{
if (uri.empty() && locationPath == "/")
{
return serverConfig.getLocation(locationPath);
}
// TODO this matches only prefix, need to handle exact match
if (uri.starts_with(utils::trim(locationPath, "/")))
{
if (locationPath.length() > maxMatchLength)
{
maxMatchLength = locationPath.length();
bestMatch = serverConfig.getLocation(locationPath);
}
}
}
return bestMatch;
}
void URI::parseUri()
{
if (config_->getType() == "server")
{
fullPath_ = FileUtils::joinPath(config_->get<std::string>("root").value_or(""), uriTrimmed_);
}
else
{
auto const *locConfig = dynamic_cast<LocationConfig const *>(config_);
std::string locTrimmed = utils::trim(locConfig->getPath(), "/");
std::string uriSub = uriTrimmed_.substr(locTrimmed.length());
fullPath_ = FileUtils::joinPath(locConfig->get<std::string>("root").value_or(""), uriSub);
}
size_t fragmentPos = fullPath_.find_first_of('#');
if (fragmentPos != std::string::npos)
{
fragment_ = fullPath_.substr(fragmentPos + 1);
fullPath_ = fullPath_.substr(0, fragmentPos);
}
size_t queryPos = fullPath_.find_first_of('?');
if (queryPos != std::string::npos)
{
query_ = fullPath_.substr(queryPos + 1);
fullPath_ = fullPath_.substr(0, queryPos);
}
}
void URI::parseFullpath()
{
auto uriSegments = utils::split(fullPath_, '/');
for (const auto &segment : uriSegments)
{
std::string currentPath = FileUtils::joinPath(dir_, segment);
if (segment.empty())
{
continue;
}
if (FileUtils::isFile(currentPath) && baseName_.empty())
{
baseName_ = segment;
}
else if (FileUtils::isDirectory(currentPath))
{
dir_ = FileUtils::joinPath(dir_, segment);
}
else if (!baseName_.empty()) // not file or dir, but we have a baseName already
{
if (pathInfo_.empty())
{
pathInfo_ = "/";
}
pathInfo_ = FileUtils::joinPath(pathInfo_, segment);
}
else // not file or dir, and no baseName yet
{
valid_ = false;
Log::warning("Invalid path segment encountered: " + currentPath);
return;
}
}
if (baseName_.empty() && FileUtils::isDirectory(fullPath_))
{
for (const auto &index : config_->get<std::vector<std::string>>("index").value_or(std::vector<std::string>()))
{
std::string indexPath = FileUtils::joinPath(fullPath_, index);
if (FileUtils::isFile(indexPath))
{
baseName_ = index;
break;
}
}
}
Log::debug("URI parseFullpath results", {{"dir", dir_}, {"baseName", baseName_}, {"pathInfo", pathInfo_}});
fullPath_ = FileUtils::joinPath(dir_, baseName_);
}
const AConfig *URI::getConfig() const noexcept
{
return config_;
}
bool URI::isFile() const noexcept
{
return !baseName_.empty();
}
bool URI::isDirectory() const noexcept
{
return baseName_.empty();
}
bool URI::isValid() const noexcept
{
return valid_ && FileUtils::isValidPath(fullPath_);
}
bool URI::isCgi() const noexcept
{
return config_->isCGI(getExtension());
}
bool URI::isRedirect() const noexcept
{
auto redirectOpt = config_->get<std::pair<int, std::string>>("redirect");
return redirectOpt.has_value();
}
std::pair<int, std::string> URI::getRedirect() const
{
auto redirectOpt = config_->get<std::pair<int, std::string>>("redirect");
if (redirectOpt.has_value())
{
return redirectOpt.value();
}
return {0, ""};
}
std::string URI::getCgiPath() const
{
// Log::debug("BaseName: " + baseName_ + ", FullPath: " + fullPath_ + ", Dir: " + dir_ + ", PathInfo: " + pathInfo_
// +
// ", Extension: " + getExtension());
if (!isFile() || getExtension().empty() || !config_->get<bool>("cgi_enabled").has_value()
|| !config_->get<bool>("cgi_enabled").value())
{
// Log::debug("CGI not enabled or not a file or no extension",
// {{"isFile", isFile() ? "true" : "false"},
// {"extension", getExtension()},
// {"cgi_enabled", config_->get<bool>("cgi_enabled").has_value() ? "true" : "false"},
// {"cgi_enabled_value", config_->get<bool>("cgi_enabled").value() ? "true" : "false"}});
return "";
}
auto cgiPath = config_->getCGIPath(getExtension());
return cgiPath;
}
std::string URI::getUriForPath(const std::string &path) const
{
// TOPD not good yet zo even naar kijken
std::string trimmedPath = utils::trim(path, "/");
std::string trimmedLocation;
const auto *locConfig = dynamic_cast<const LocationConfig *>(config_);
if (locConfig != nullptr)
{
trimmedLocation = utils::trim(locConfig->getPath(), "/");
}
std::string trimmedRoot = utils::trim(config_->get<std::string>("root").value_or(""), "/");
if (trimmedPath.starts_with(trimmedRoot))
{
trimmedPath = trimmedPath.substr(trimmedRoot.length());
trimmedPath = utils::trim(trimmedPath, "/");
}
Log::debug(
"Generating URI for path",
{{"path", path}, {"trimmedDir", trimmedLocation}, {"trimmedPath", trimmedPath}, {"Authority", authority_}});
std::string result = "http://" + authority_; // TODO this should not be hardcoded...
result = FileUtils::joinPath(result, trimmedLocation);
return FileUtils::joinPath(result, trimmedPath);
}
const std::string &URI::getBaseName() const noexcept
{
return baseName_;
}
std::string URI::getExtension() const noexcept
{
return FileUtils::getExtension(baseName_);
}
const std::string &URI::getFullPath() const noexcept
{
return fullPath_;
}
const std::string &URI::getDir() const noexcept
{
return dir_;
}
const std::string &URI::getPathInfo() const noexcept
{
return pathInfo_;
}
const std::string &URI::getQuery() const noexcept
{
return query_;
}
const std::string &URI::getFragment() const noexcept
{
return fragment_;
}
const std::string &URI::getAuthority() const noexcept
{
return authority_;
}