43 unordered_map<unsigned int, string>
const httpStatusCodes = {
47 {203,
"Non-Authoritative Information"},
49 {205,
"Reset Content"},
50 {206,
"Partial Content"},
51 {207,
"Multi-Status"},
52 {208,
"Already Reported"},
54 {300,
"Multiple Choices"},
55 {301,
"Moved Permanently"},
58 {304,
"Not Modified"},
60 {307,
"Temporary Redirect"},
61 {308,
"Permanent Redirect"},
63 {401,
"Unauthorized"},
64 {402,
"Payment Required"},
67 {405,
"Method Not Allowed"},
68 {406,
"Not Acceptable"},
69 {407,
"Proxy Authentication Required"},
70 {408,
"Request Timeout"},
73 {411,
"Length Required"},
74 {412,
"Precondition Failed"},
75 {413,
"Payload Too Large"},
76 {414,
"URI Too Long"},
77 {415,
"Unsupported Media Type"},
78 {416,
"Range Not Satisfiable"},
79 {417,
"Expectation Failed"},
80 {418,
"I'm a teapot"},
81 {421,
"Misdirected Request"},
82 {422,
"Unprocessable Entity"},
84 {424,
"Failed Dependency"},
85 {426,
"Upgrade Required"},
86 {428,
"Precondition Required"},
87 {429,
"Too Many Requests"},
88 {431,
"Request Header Fields Too Large"},
89 {451,
"Unavailable For Legal Reasons"},
90 {500,
"Internal Server Error"},
91 {501,
"Not Implemented"},
93 {503,
"Service Unavailable"},
94 {504,
"Gateway Timeout"},
95 {505,
"HTTP Version Not Supported"},
96 {506,
"Variant Also Negotiates"},
97 {507,
"Insufficient Storage"},
98 {508,
"Loop Detected"},
99 {510,
"Not Extended"},
100 {511,
"Network Authentication Required"}};
103struct Connection::Data {
105 unsigned int responseStatus = 200;
106 unordered_map<string, vector<string>> headers;
107 unordered_map<string, Cookie> cookies;
109 bool isFlushed =
false;
115 stringstream responseStream;
118 responseStream.str(
string());
119 responseStream.clear();
123 bodyString += responseStream.str();
128 config(connectionInit.config),
135 data->bodyString = std::move(content);
139void Connection::sendFile(std::string
const& path, std::string
const& contentType,
bool forceDownload,
140 std::string
const& downloadFilename,
bool checkIfModifiedSince) {
143 ifstream f(path, ifstream::binary);
147 throw Exception(__PRETTY_FUNCTION__, 1,
"Cannot open file for reading");
151 struct stat fileStat {};
152 time_t lastModified = 0;
153 if (stat(path.c_str(), &fileStat) == 0) {
158 time_t ifModifiedSince = 0;
160 ifModifiedSince = stol(data->request.env()[
"if-modified-since"]);
161 }
catch (invalid_argument
const&) {
162 }
catch (out_of_range
const&) {}
163 if (checkIfModifiedSince && ifModifiedSince >= lastModified) {
170 if (!contentType.empty()) {
179 if (!downloadFilename.empty()) {
181 hval <<
"attachment; filename=\"" << downloadFilename <<
'"';
182 setHeader(
"content-disposition", hval.str());
184 setHeader(
"content-disposition",
"attachment");
186 }
else if (!downloadFilename.empty()) {
188 hval <<
"inline; filename=\"" << downloadFilename <<
'"';
189 setHeader(
"content-disposition", hval.str());
194 f.seekg(0, ios::end);
197 setHeader(
"content-length", to_string(fs));
200 if (lastModified > 0) {
210 data->bodyString.resize(
static_cast<unsigned long>(fs) + 1,
'\0');
211 data->bodyString[fs] =
'\0';
212 f.read(&data->bodyString[0], fs);
220 transform(key.begin(), key.end(), key.begin(), ::tolower);
221 data->headers[key] = {std::move(value)};
226 transform(key.begin(), key.end(), key.begin(), ::tolower);
227 data->headers[key].push_back(std::move(value));
232 transform(key.begin(), key.end(), key.begin(), ::tolower);
233 data->headers.erase(key);
237 unordered_multimap<string, string> ret;
238 for (
auto const& [key, values] : data->headers) {
239 for (
auto const& value : values) {
240 ret.insert({key, value});
246 for (
auto const& e : data->cookies) {
247 stringstream headerVal;
248 headerVal << e.first <<
"=" << e.second.content();
250 optional<string> domain = e.second.domain() ? e.second.domain() : data->cookiePolicy.domain();
251 if (domain && !domain->empty()) {
252 headerVal <<
"; Domain=" << *domain;
255 optional<string> path = e.second.path() ? e.second.path() : data->cookiePolicy.path();
256 if (path && !path->empty()) {
257 headerVal <<
"; Path=" << *path;
260 optional<time_t> expiry = e.second.expires() ? e.second.expires() : data->cookiePolicy.expires();
270 optional<unsigned long> maxAge = e.second.maxAge() ? e.second.maxAge()
271 : data->cookiePolicy.maxAge();
273 headerVal <<
"; Max-Age=" << *maxAge;
276 if (e.second.secure() || data->cookiePolicy.secure()) {
277 headerVal <<
"; Secure";
280 if (e.second.httpOnly() || data->cookiePolicy.httpOnly()) {
281 headerVal <<
"; HttpOnly";
285 : data->cookiePolicy.sameSite();
287 headerVal <<
"; SameSite=lax";
289 headerVal <<
"; SameSite=strict";
291 ret.insert({
"set-cookie", headerVal.str()});
299 return data->bodyString;
303 data = make_unique<Data>(
this, connectionInit);
306 data->headers[
"content-type"] = {
"text/html; charset=utf-8"};
309 if (data->config[{
"session",
"autostart"}] ==
"on") {
310 data->session.start();
316 regex matchKey(R
"([A-Za-z0-9!#$%&'*+\-.^_`|~]*)");
317 regex matchContent(R"([A-Za-z0-9!#$%&'()*+\-.\/:<=>?@[\]^_`{|}~]*)");
318 if (!regex_match(key, matchKey) || !regex_match(cookie.
content(), matchContent)) {
319 throw Exception(__PRETTY_FUNCTION__, 1,
"Invalid characters in key or value");
321 data->cookies[key] = std::move(cookie);
329 data->cookies.erase(key);
335 .
status = data->responseStatus,
338 .flushedBefore = data->isFlushed});
340 data->isFlushed =
true;
346 data->responseStatus = status;
350 data->cookiePolicy = std::move(policy);
354 return data->responseStatus;
358 return data->request;
362 return data->session;
366 return data->session;
378 return data->responseStream;
386 auto requestPath = data->request.env().getRequestPath();
391 bool matches = flt.matches(requestPath);
392 if ((!matches && !flt.invert()) || (matches && flt.invert())) {
398 if (!flt.response().empty()) {
409 int authFilterID = -1;
410 for (
auto const& flt : accessFilters.
authFilters()) {
413 bool matches = flt.matches(requestPath);
414 if ((!matches && !flt.invert()) || (matches && flt.invert())) {
418 bool isAuthenticated =
false;
419 string sessionVarKey;
422 if (flt.useSessions()) {
423 data->session.start();
424 sessionVarKey =
"_nawa_authfilter" + to_string(authFilterID);
425 if (data->session.isSet(sessionVarKey)) {
426 isAuthenticated =
true;
431 if (!isAuthenticated) {
433 if (data->request.env()[
"authorization"].empty()) {
437 if (!flt.authName().empty()) {
438 hval <<
" realm=\"" << flt.authName() <<
'"';
440 setHeader(
"www-authenticate", hval.str());
448 auto authResponse =
utils::splitString(data->request.env()[
"authorization"],
' ',
true);
450 if (authResponse.size() == 2 || authResponse.at(0) ==
"Basic") {
454 if (credentials.size() == 2 && flt.authFunction()) {
456 if (flt.authFunction()(credentials.at(0), credentials.at(1))) {
457 isAuthenticated =
true;
459 if (flt.useSessions()) {
460 data->session.set(sessionVarKey, any(credentials.at(0)));
469 if (!isAuthenticated) {
471 if (!flt.response().empty()) {
487 bool matches = flt.matches(requestPath);
488 if ((!matches && !flt.invert()) || (matches && flt.invert())) {
492 stringstream filePath;
493 filePath << flt.basePath();
495 for (
auto const& e : requestPath) {
496 filePath <<
'/' << e;
499 filePath <<
'/' << requestPath.back();
503 auto filePathStr = filePath.str();
505 sendFile(filePathStr,
"",
false,
"",
true);
509 if (!flt.response().empty()) {
528 if (httpStatusCodes.count(
status) == 1) {
529 hval <<
" " << httpStatusCodes.at(
status);
539 for (
auto const& e :
headers) {
540 raw << e.first <<
": " << e.second <<
"\r\n";
Options to check the path and invoke certain actions before forwarding the request to the app.
Container used by request handlers to initiate the nawa::Connection object.
Response object to be passed back to NAWA and accessor to the request.
Exception class that can be used by apps to catch errors resulting from nawa function calls.
Simple class for (not (yet) thread-safe) logging to stderr or to any other output stream.
#define NLOG_DEBUG(Logger, Message)
#define NLOG_ERROR(Logger, Message)
std::vector< BlockFilter > & blockFilters() noexcept
bool & filtersEnabled() noexcept
std::vector< AuthFilter > & authFilters() noexcept
std::vector< ForwardFilter > & forwardFilters() noexcept
nawa::Session & session() noexcept
void addHeader(std::string key, std::string value)
void setCookiePolicy(Cookie policy)
void unsetHeader(std::string key)
nawa::Config & config() noexcept
void setStatus(unsigned int status)
unsigned int getStatus() const
void setCookie(std::string const &key, Cookie cookie)
void setHeader(std::string key, std::string value)
std::ostream & responseStream() noexcept
Connection(ConnectionInitContainer const &connectionInit)
std::unordered_multimap< std::string, std::string > getHeaders(bool includeCookies=true) const
bool applyFilters(AccessFilterList const &accessFilters)
std::string getResponseBody()
nawa::Request const & request() const noexcept
void sendFile(std::string const &path, std::string const &contentType="", bool forceDownload=false, std::string const &downloadFilename="", bool checkIfModifiedSince=false)
void unsetCookie(const std::string &key)
void setResponseBody(std::string content)
std::string & content() noexcept
virtual std::string getMessage() const noexcept
virtual std::string getDebugMessage() const noexcept
Namespace containing functions for text encoding and decoding.
#define NAWA_DEFAULT_DESTRUCTOR_IMPL(Class)
std::string base64Decode(std::string const &input)
time_t getLastModifiedTimeOfFile(struct stat const &fileStat)
std::string getFileExtension(std::string const &filename)
std::string generateErrorPage(unsigned int httpStatus)
std::string makeHttpTime(time_t time)
std::string contentTypeByExtension(std::string extension)
std::vector< std::string > splitString(std::string str, char delimiter, bool ignoreEmpty=false)
std::function< void(FlushCallbackContainer)> FlushCallbackFunction
FlushCallbackFunction flushCallback
This file contains helpers for operating-system specific stuff.
std::string getFullHttp() const
std::unordered_multimap< std::string, std::string > headers
std::string getStatusString() const
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.