]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/webserver.cc
Merge pull request #14020 from omoerbeek/rec-compiling-rust-dcos
[thirdparty/pdns.git] / pdns / webserver.cc
CommitLineData
12c86877 1/*
6edbf68a
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
731f58b8 25#include "utility.hh"
12c86877 26#include "webserver.hh"
12c86877 27#include "misc.hh"
d4c53d8c 28#include <thread>
519f5484 29#include "threadname.hh"
4de01aaa 30#include <utility>
12c86877
BH
31#include <vector>
32#include "logger.hh"
33#include <stdio.h>
34#include "dns.hh"
2db9c30e 35#include "base64.hh"
33196945 36#include "json.hh"
3f9a8002 37#include "uuid-utils.hh"
583ea80d 38#include <yahttp/router.hpp>
35eb2fcf
OM
39#include <algorithm>
40#include <unordered_set>
12c86877 41
5938c49f
CH
42json11::Json HttpRequest::json()
43{
44 string err;
45 if(this->body.empty()) {
6c88989a
OM
46 SLOG(g_log<<Logger::Debug<<logprefix<<"JSON document expected in request body, but body was empty" << endl,
47 d_slog->info(Logr::Debug, "JSON document expected in request body, but body was empty"));
5938c49f
CH
48 throw HttpBadRequestException();
49 }
50 json11::Json doc = json11::Json::parse(this->body, err);
51 if (doc.is_null()) {
6c88989a
OM
52 SLOG(g_log<<Logger::Debug<<logprefix<<"parsing of JSON document failed:" << err << endl,
53 d_slog->error(Logr::Debug, err, "parsing of JSON document failed"));
5938c49f
CH
54 throw HttpBadRequestException();
55 }
56 return doc;
57}
58
40422f78 59bool HttpRequest::compareAuthorization(const CredentialsHolder& credentials) const
bbef8f04
CH
60{
61 // validate password
40422f78 62 auto header = headers.find("authorization");
bbef8f04
CH
63 bool auth_ok = false;
64 if (header != headers.end() && toLower(header->second).find("basic ") == 0) {
65 string cookie = header->second.substr(6);
66
67 string plain;
68 B64Decode(cookie, plain);
69
70 vector<string> cparts;
71 stringtok(cparts, plain, ":");
72
40422f78 73 auth_ok = (cparts.size() == 2 && credentials.matches(cparts[1].c_str()));
bbef8f04
CH
74 }
75 return auth_ok;
76}
77
40422f78 78bool HttpRequest::compareHeader(const string &header_name, const string &expected_value) const
bbef8f04 79{
40422f78
RG
80 auto header = headers.find(header_name);
81 if (header == headers.end()) {
bbef8f04 82 return false;
40422f78 83 }
bbef8f04
CH
84
85 // this gets rid of terminating zeros
86 return (0==strcmp(header->second.c_str(), expected_value.c_str()));
87}
88
40422f78
RG
89bool HttpRequest::compareHeader(const string &header_name, const CredentialsHolder& credentials) const
90{
91 auto header = headers.find(header_name);
92 if (header == headers.end()) {
93 return false;
94 }
95
96 return credentials.matches(header->second);
97}
98
917f686a
KF
99void HttpResponse::setPlainBody(const string& document)
100{
101 this->headers["Content-Type"] = "text/plain; charset=utf-8";
102
103 this->body = document;
104}
bbef8f04 105
917f686a 106void HttpResponse::setYamlBody(const string& document)
5938c49f 107{
917f686a
KF
108 this->headers["Content-Type"] = "application/x-yaml";
109
110 this->body = document;
111}
112
113void HttpResponse::setJsonBody(const string& document)
114{
115 this->headers["Content-Type"] = "application/json";
116
117 this->body = document;
118}
119
120void HttpResponse::setJsonBody(const json11::Json& document)
121{
122 this->headers["Content-Type"] = "application/json";
123
5938c49f
CH
124 document.dump(this->body);
125}
126
dd079764 127void HttpResponse::setErrorResult(const std::string& message, const int status_)
692829aa 128{
917f686a 129 setJsonBody(json11::Json::object { { "error", message } });
dd079764 130 this->status = status_;
692829aa
CH
131}
132
dd079764 133void HttpResponse::setSuccessResult(const std::string& message, const int status_)
692829aa 134{
917f686a 135 setJsonBody(json11::Json::object { { "result", message } });
dd079764 136 this->status = status_;
692829aa
CH
137}
138
4de01aaa 139static void bareHandlerWrapper(const WebServer::HandlerFunction& handler, YaHTTP::Request* req, YaHTTP::Response* resp)
12c86877 140{
583ea80d
CH
141 // wrapper to convert from YaHTTP::* to our subclasses
142 handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
143}
232f0877 144
478e1699 145void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method)
583ea80d 146{
a88c05b2 147 YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);};
478e1699 148 YaHTTP::Router::Map(method, url, std::move(f));
232f0877
CH
149}
150
4de01aaa 151void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) {
7f7481be
AT
152 resp->headers["access-control-allow-origin"] = "*";
153
40422f78 154 if (!d_apikey) {
6c88989a 155 SLOG(g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl,
7ccb828a 156 d_slog->info(Logr::Error, "Authentication failed, API Key missing in config", "urlpath", Logging::Loggable(req->url.path)));
53255086 157 throw HttpUnauthorizedException("X-API-Key");
bbef8f04 158 }
7579a7b9 159
40422f78 160 bool auth_ok = req->compareHeader("x-api-key", *d_apikey) || d_apikey->matches(req->getvars["api-key"]);
c563cbe5
CH
161
162 if (!auth_ok && allowPassword) {
40422f78
RG
163 if (d_webserverPassword) {
164 auth_ok = req->compareAuthorization(*d_webserverPassword);
c563cbe5
CH
165 } else {
166 auth_ok = true;
167 }
168 }
169
bbef8f04 170 if (!auth_ok) {
6c88989a 171 SLOG(g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl,
7ccb828a 172 d_slog->info(Logr::Error, "Authentication by API Key failed", "urlpath", Logging::Loggable(req->url.path)));
53255086 173 throw HttpUnauthorizedException("X-API-Key");
bbef8f04
CH
174 }
175
7fe2a2dc
CH
176 // security headers
177 resp->headers["X-Content-Type-Options"] = "nosniff";
178 resp->headers["X-Frame-Options"] = "deny";
179 resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
180 resp->headers["X-XSS-Protection"] = "1; mode=block";
181 resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
3ae143b0 182
583ea80d 183 req->getvars.erase("_"); // jQuery cache buster
3ae143b0
CH
184
185 try {
583ea80d 186 resp->status = 200;
3ae143b0
CH
187 handler(req, resp);
188 } catch (ApiException &e) {
692829aa 189 resp->setErrorResult(e.what(), 422);
6ec5e728
CH
190 return;
191 } catch (JsonException &e) {
692829aa 192 resp->setErrorResult(e.what(), 422);
3ae143b0
CH
193 return;
194 }
195
37663c3b
CH
196 if (resp->status == 204) {
197 // No Content -> no Content-Type.
198 resp->headers.erase("Content-Type");
199 }
3ae143b0
CH
200}
201
478e1699 202void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method, bool allowPassword) {
969e4459 203 auto f = [=](HttpRequest *req, HttpResponse* resp){apiWrapper(handler, req, resp, allowPassword);};
478e1699 204 registerBareHandler(url, f, method);
bbef8f04
CH
205}
206
4de01aaa 207void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) {
40422f78
RG
208 if (d_webserverPassword) {
209 bool auth_ok = req->compareAuthorization(*d_webserverPassword);
bbef8f04 210 if (!auth_ok) {
6c88989a
OM
211 SLOG(g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl,
212 d_slog->info(Logr::Debug, "HTTP Request: Web Authentication failed", "urlpath", Logging::Loggable(req->url.path)));
53255086 213 throw HttpUnauthorizedException("Basic");
bbef8f04
CH
214 }
215 }
216
217 handler(req, resp);
218}
219
478e1699 220void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method) {
969e4459 221 auto f = [=](HttpRequest *req, HttpResponse *resp){webWrapper(handler, req, resp);};
478e1699 222 registerBareHandler(url, f, method);
3ae143b0
CH
223}
224
224085cc
RP
225static void* WebServerConnectionThreadStart(const WebServer* webServer, const std::shared_ptr<Socket>& client)
226{
9548a664 227 setThreadName("rec/webhndlr");
6c88989a 228 const std::string msg = "Exception while serving a connection in main webserver thread";
89a5aecc 229 try {
beceefde 230 webServer->serveConnection(client);
89a5aecc
PD
231 }
232 catch(PDNSException &e) {
6c88989a
OM
233 SLOG(g_log<<Logger::Error<<"PDNSException while serving a connection in main webserver thread: "<<e.reason<<endl,
234 webServer->d_slog->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
89a5aecc
PD
235 }
236 catch(std::exception &e) {
6c88989a
OM
237 SLOG(g_log<<Logger::Error<<"STL Exception while serving a connection in main webserver thread: "<<e.what()<<endl,
238 webServer->d_slog->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
89a5aecc
PD
239 }
240 catch(...) {
6c88989a
OM
241 SLOG(g_log<<Logger::Error<<"Unknown exception while serving a connection in main webserver thread"<<endl,
242 webServer->d_slog->info(Logr::Error, msg));
89a5aecc 243 }
d4c53d8c 244 return nullptr;
232f0877
CH
245}
246
b184a9dc 247void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
80d59cd1 248{
80d59cd1
CH
249 // set default headers
250 resp.headers["Content-Type"] = "text/html; charset=utf-8";
33196945 251
3e285251
OM
252#ifdef RECURSOR
253 auto log = req.d_slog->withValues("urlpath", Logging::Loggable(req.url.path));
254#endif
255
12c86877 256 try {
825fa717 257 if (!req.complete) {
6c88989a
OM
258 SLOG(g_log<<Logger::Debug<<req.logprefix<<"Incomplete request" << endl,
259 d_slog->info(Logr::Debug, "Incomplete request"));
825fa717
CH
260 throw HttpBadRequestException();
261 }
6c88989a
OM
262 SLOG(g_log<<Logger::Debug<<req.logprefix<<"Handling request \"" << req.url.path << "\"" << endl,
263 log->info(Logr::Debug, "Handling request"));
825fa717 264
80d59cd1
CH
265 YaHTTP::strstr_map_t::iterator header;
266
267 if ((header = req.headers.find("accept")) != req.headers.end()) {
917f686a
KF
268 // yaml wins over json, json wins over html
269 if (header->second.find("application/x-yaml") != std::string::npos) {
270 req.accept_yaml = true;
271 } else if (header->second.find("text/x-yaml") != std::string::npos) {
272 req.accept_yaml = true;
273 } else if (header->second.find("application/json") != std::string::npos) {
80d59cd1
CH
274 req.accept_json = true;
275 } else if (header->second.find("text/html") != std::string::npos) {
276 req.accept_html = true;
277 }
12c86877
BH
278 }
279
583ea80d 280 YaHTTP::THandlerFunction handler;
478e1699
AT
281 YaHTTP::RoutingResult res = YaHTTP::Router::Route(&req, handler);
282
283 if (res == YaHTTP::RouteNotFound) {
6c88989a
OM
284 SLOG(g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl,
285 log->info(Logr::Debug, "No route found"));
33196945 286 throw HttpNotFoundException();
12c86877 287 }
478e1699
AT
288 if (res == YaHTTP::RouteNoMethod) {
289 throw HttpMethodNotAllowedException();
290 }
12c86877 291
6c88989a 292 const string msg = "HTTP ISE Exception";
0f67eeda 293 try {
583ea80d 294 handler(&req, &resp);
6c88989a
OM
295 SLOG(g_log<<Logger::Debug<<req.logprefix<<"Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl,
296 log->info(Logr::Debug, "Result", "status", Logging::Loggable(resp.status), "bodyLength", Logging::Loggable(resp.body.size())));
0f67eeda 297 }
4d706054 298 catch(HttpException&) {
583ea80d
CH
299 throw;
300 }
0f67eeda 301 catch(PDNSException &e) {
6c88989a
OM
302 SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl,
303 log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
0f67eeda
CH
304 throw HttpInternalServerErrorException();
305 }
306 catch(std::exception &e) {
6c88989a
OM
307 SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl,
308 log->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
0f67eeda
CH
309 throw HttpInternalServerErrorException();
310 }
311 catch(...) {
6c88989a
OM
312 SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl,
313 log->info(Logr::Error, msg));
0f67eeda
CH
314 throw HttpInternalServerErrorException();
315 }
12c86877 316 }
33196945 317 catch(HttpException &e) {
80d59cd1 318 resp = e.response();
3e285251
OM
319#ifdef RECURSOR
320 // An HttpException does not initialize d_slog
321 if (!resp.d_slog) {
322 resp.setSLog(log);
323 }
324#endif
a53cd863 325 // TODO rm this logline?
6c88989a
OM
326 SLOG(g_log<<Logger::Debug<<req.logprefix<<"Error result for \"" << req.url.path << "\": " << resp.status << endl,
327 d_slog->error(Logr::Debug, resp.status, "Error result", "urlpath", Logging::Loggable(req.url.path)));
80d59cd1 328 string what = YaHTTP::Utility::status2text(resp.status);
917f686a 329 if (req.accept_json) {
80d59cd1 330 resp.headers["Content-Type"] = "application/json";
8204102e
PL
331 if (resp.body.empty()) {
332 resp.setErrorResult(what, resp.status);
333 }
917f686a
KF
334 } else if (req.accept_html) {
335 resp.headers["Content-Type"] = "text/html; charset=utf-8";
336 resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
33196945 337 } else {
80d59cd1 338 resp.headers["Content-Type"] = "text/plain; charset=utf-8";
3b45a434 339 resp.body = std::move(what);
33196945 340 }
12c86877 341 }
33196945 342
80d59cd1 343 // always set these headers
80d59cd1
CH
344 resp.headers["Connection"] = "close";
345
ac9908c2
CH
346 if (req.method == "HEAD") {
347 resp.body = "";
348 } else {
335da0ba 349 resp.headers["Content-Length"] = std::to_string(resp.body.size());
ac9908c2 350 }
80d59cd1
CH
351}
352
6c88989a
OM
353#ifdef RECURSOR
354// Helper to log key-value maps used by YaHTTP
355template<>
356std::string Logging::IterLoggable<YaHTTP::strstr_map_t::const_iterator>::to_string() const
357{
358 std::ostringstream oss;
359 bool first = true;
360 for (auto i = _t1; i != _t2; i++) {
361 if (!first) {
362 oss << '\n';
363 }
364 else {
365 first = false;
366 }
367 oss << i->first << ": " << i->second;
368 }
369 return oss.str();
370}
371#endif
372
aa87b287 373void WebServer::logRequest(const HttpRequest& req, [[maybe_unused]] const ComboAddress& remote) const {
612ad9ec 374 if (d_loglevel >= WebServer::LogLevel::Detailed) {
6c88989a
OM
375#ifdef RECURSOR
376 if (!g_slogStructured) {
377#endif
3b45a434 378 const auto& logprefix = req.logprefix;
6c88989a
OM
379 g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
380
381 bool first = true;
382 for (const auto& r : req.getvars) {
383 if (first) {
384 first = false;
385 g_log<<Logger::Notice<<logprefix<<" GET params:"<<endl;
386 }
387 g_log<<Logger::Notice<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
612ad9ec 388 }
612ad9ec 389
6c88989a
OM
390 first = true;
391 for (const auto& r : req.postvars) {
392 if (first) {
393 first = false;
394 g_log<<Logger::Notice<<logprefix<<" POST params:"<<endl;
395 }
396 g_log<<Logger::Notice<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
612ad9ec 397 }
612ad9ec 398
6c88989a
OM
399 first = true;
400 for (const auto& h : req.headers) {
401 if (first) {
402 first = false;
403 g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
404 }
405 g_log<<Logger::Notice<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
612ad9ec 406 }
612ad9ec 407
6c88989a
OM
408 if (req.body.empty()) {
409 g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
410 } else {
411 g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
412 g_log<<Logger::Notice<<logprefix<<" "<<req.body<<endl;
413 }
414#ifdef RECURSOR
612ad9ec 415 }
6c88989a
OM
416 else {
417 req.d_slog->info(Logr::Info, "Request details", "getParams", Logging::IterLoggable(req.getvars.cbegin(), req.getvars.cend()),
418 "postParams", Logging::IterLoggable(req.postvars.cbegin(), req.postvars.cend()),
419 "body", Logging::Loggable(req.body),
420 "address", Logging::Loggable(remote));
421 }
422#endif
612ad9ec
PL
423 }
424}
425
d73de874 426void WebServer::logResponse(const HttpResponse& resp, const ComboAddress& /* remote */, const string& logprefix) const {
612ad9ec 427 if (d_loglevel >= WebServer::LogLevel::Detailed) {
6c88989a
OM
428#ifdef RECURSOR
429 if (!g_slogStructured) {
430#endif
431 g_log<<Logger::Notice<<logprefix<<"Response details:"<<endl;
432 bool first = true;
433 for (const auto& h : resp.headers) {
434 if (first) {
435 first = false;
436 g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
437 }
438 g_log<<Logger::Notice<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
439 }
440 if (resp.body.empty()) {
441 g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
442 } else {
443 g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
444 g_log<<Logger::Notice<<logprefix<<" "<<resp.body<<endl;
612ad9ec 445 }
6c88989a 446#ifdef RECURSOR
612ad9ec 447 }
6c88989a
OM
448 else {
449 resp.d_slog->info(Logr::Info, "Response details", "headers", Logging::IterLoggable(resp.headers.cbegin(), resp.headers.cend()),
450 "body", Logging::Loggable(resp.body));
612ad9ec 451 }
6c88989a 452#endif
612ad9ec
PL
453 }
454}
455
35eb2fcf
OM
456
457struct ValidChars {
458 ValidChars()
459 {
460 // letter may be signed, but we only pass positive values
461 for (auto letter : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=") {
462 set.set(letter);
463 }
464 }
465 std::bitset<127> set;
466};
467
468static const ValidChars validChars;
469
470static bool validURLChars(const string& str)
471{
472 for (auto iter = str.begin(); iter != str.end(); ++iter) {
473 if (*iter == '%') {
474 ++iter;
475 if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
476 return false;
477 }
478 ++iter;
479 if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
480 return false;
481 }
482 }
483 else if (static_cast<size_t>(*iter) >= validChars.set.size() || !validChars.set[*iter]) {
484 return false;
485 }
486 }
487 return true;
488}
489
490bool WebServer::validURL(const YaHTTP::URL& url)
491{
492 bool isOK = true;
493 isOK = isOK && validURLChars(url.protocol);
494 isOK = isOK && validURLChars(url.host);
495 isOK = isOK && validURLChars(url.username);
496 isOK = isOK && validURLChars(url.password);
497 isOK = isOK && validURLChars(url.path);
498 isOK = isOK && validURLChars(url.parameters);
499 isOK = isOK && validURLChars(url.anchor);
500 return isOK;
501}
502
4de01aaa 503void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
6c88989a
OM
504 const auto unique = getUniqueID();
505 const string logprefix = d_logprefix + to_string(unique) + " ";
80d59cd1 506
9b960272 507 HttpRequest req(logprefix);
6c88989a 508
9b960272 509 HttpResponse resp;
6c88989a
OM
510#ifdef RECURSOR
511 auto log = d_slog->withValues("uniqueid", Logging::Loggable(to_string(unique)));
512 req.setSLog(log);
513 resp.setSLog(log);
514#endif
214b034e 515 resp.max_response_size=d_maxbodysize;
9b960272
PL
516 ComboAddress remote;
517 string reply;
518
80d59cd1 519 try {
a53cd863
PL
520 YaHTTP::AsyncRequestLoader yarl;
521 yarl.initialize(&req);
214b034e 522 req.max_request_size=d_maxbodysize;
a53cd863
PL
523 int timeout = 5;
524 client->setNonBlocking();
525
526 try {
527 while(!req.complete) {
528 int bytes;
bfde2d10 529 char buf[16000];
a53cd863
PL
530 bytes = client->readWithTimeout(buf, sizeof(buf), timeout);
531 if (bytes > 0) {
532 string data = string(buf, bytes);
533 req.complete = yarl.feed(data);
534 } else {
535 // read error OR EOF
536 break;
537 }
538 }
539 yarl.finalize();
540 } catch (YaHTTP::ParseError &e) {
541 // request stays incomplete
6c88989a
OM
542 SLOG(g_log<<Logger::Warning<<logprefix<<"Unable to parse request: "<<e.what()<<endl,
543 d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
a53cd863
PL
544 }
545
35eb2fcf
OM
546 if (!validURL(req.url)) {
547 throw PDNSException("Received request with invalid URL");
548 }
7ccb828a 549 // Uses of `remote` below guarded by d_loglevel
6c88989a 550 if (d_loglevel > WebServer::LogLevel::None) {
a53cd863
PL
551 client->getRemote(remote);
552 }
553
612ad9ec 554 logRequest(req, remote);
80d59cd1 555
a53cd863
PL
556 WebServer::handleRequest(req, resp);
557 ostringstream ss;
558 resp.write(ss);
9b960272 559 reply = ss.str();
a53cd863 560
612ad9ec 561 logResponse(resp, remote, logprefix);
a53cd863 562
a53cd863 563 client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
b2cb8982 564 }
a53cd863 565 catch(PDNSException &e) {
6c88989a
OM
566 SLOG(g_log<<Logger::Error<<logprefix<<"HTTP Exception: "<<e.reason<<endl,
567 d_slog->error(Logr::Error, e.reason, "HTTP Exception", "exception", Logging::Loggable("PDNSException")));
b2cb8982 568 }
a53cd863 569 catch(std::exception &e) {
4646277d 570 if(strstr(e.what(), "timeout")==nullptr)
6c88989a
OM
571 SLOG(g_log<<Logger::Error<<logprefix<<"HTTP STL Exception: "<<e.what()<<endl,
572 d_slog->error(Logr::Error, e.what(), "HTTP Exception", "exception", Logging::Loggable("std::exception")));
a53cd863
PL
573 }
574 catch(...) {
6c88989a
OM
575 SLOG(g_log<<Logger::Error<<logprefix<<"Unknown exception"<<endl,
576 d_slog->info(Logr::Error, "HTTP Exception"));
b2cb8982 577 }
9b960272 578
a35306f9 579 if (d_loglevel >= WebServer::LogLevel::Normal) {
26f5d605 580 SLOG(g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<req.url.path<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl,
6c88989a
OM
581 d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
582 "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
583 "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
9b960272 584 }
33196945
CH
585}
586
1290c1d2
RP
587WebServer::WebServer(string listenaddress, int port) :
588 d_listenaddress(std::move(listenaddress)),
8a70e507 589 d_port(port),
214b034e
PD
590 d_server(nullptr),
591 d_maxbodysize(2*1024*1024)
12c86877 592{
fe921322
AT
593 YaHTTP::Router::Map("OPTIONS", "/<*url>", [](YaHTTP::Request *req, YaHTTP::Response *resp) {
594 // look for url in routes
595 bool seen = false;
596 std::vector<std::string> methods;
597 for(const auto& route: YaHTTP::Router::GetRoutes()) {
598 const auto& method = std::get<0>(route);
599 const auto& url = std::get<1>(route);
600 if (method == "OPTIONS") {
601 continue;
602 }
603 std::map<std::string, YaHTTP::TDelim> params;
604 if (YaHTTP::Router::Match(url, req->url, params)) {
605 methods.push_back(method);
606 seen = true;
607 }
608 }
609 if (!seen) {
610 resp->status = 404;
611 resp->body = "";
612 return;
613 }
614 methods.emplace_back("OPTIONS");
615 resp->headers["access-control-allow-origin"] = "*";
616 resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
617 resp->headers["access-control-allow-methods"] = boost::algorithm::join(methods, ", ");
618 resp->headers["access-control-max-age"] = "3600";
619 resp->status = 200;
620 resp->headers["content-type"]= "text/plain";
621 resp->body = "";
622 }, "OptionsHandlerRoute");
825fa717
CH
623}
624
625void WebServer::bind()
626{
96d299db 627 try {
825fa717 628 d_server = createServer();
6c88989a 629 SLOG(g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl,
68dbf30c 630 d_slog->info(Logr::Info, "Listening for HTTP requests", "address", Logging::Loggable(d_server->d_local)));
96d299db 631 }
825fa717 632 catch(NetworkError &e) {
6c88989a
OM
633 SLOG(g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl,
634 d_slog->error(Logr::Error, e.what(), "Listening on HTTP socket failed", "exception", Logging::Loggable("NetworkError")));
690984d4 635 d_server = nullptr;
96d299db 636 }
12c86877
BH
637}
638
639void WebServer::go()
640{
96d299db
BH
641 if(!d_server)
642 return;
6c88989a 643 const string msg = "Exception in main webserver thread";
12c86877 644 try {
3ae143b0 645 while(true) {
6c88989a 646 const string acceptmsg = "Exception while accepting a connection in main webserver thread";
17d60ab0 647 try {
b184a9dc 648 auto client = d_server->accept();
8a781bb5
RG
649 if (!client) {
650 continue;
651 }
0010aefa 652 if (client->acl(d_acl)) {
b184a9dc 653 std::thread webHandler(WebServerConnectionThreadStart, this, client);
d4c53d8c 654 webHandler.detach();
17d60ab0
RG
655 } else {
656 ComboAddress remote;
b184a9dc 657 if (client->getRemote(remote))
a53cd863 658 g_log<<Logger::Error<<d_logprefix<<"Webserver closing socket: remote ("<< remote.toString() <<") does not match the set ACL("<<d_acl.toString()<<")"<<endl;
17d60ab0
RG
659 }
660 }
661 catch(PDNSException &e) {
6c88989a
OM
662 SLOG(g_log<<Logger::Error<<d_logprefix<<"PDNSException while accepting a connection in main webserver thread: "<<e.reason<<endl,
663 d_slog->error(Logr::Error, e.reason, acceptmsg, Logging::Loggable("PDNSException")));
17d60ab0
RG
664 }
665 catch(std::exception &e) {
6c88989a
OM
666 SLOG(g_log<<Logger::Error<<d_logprefix<<"STL Exception while accepting a connection in main webserver thread: "<<e.what()<<endl,
667 d_slog->error(Logr::Error, e.what(), acceptmsg, Logging::Loggable("std::exception")));
17d60ab0
RG
668 }
669 catch(...) {
6c88989a
OM
670 SLOG(g_log<<Logger::Error<<d_logprefix<<"Unknown exception while accepting a connection in main webserver thread"<<endl,
671 d_slog->info(Logr::Error, msg));
69e7f117 672 }
12c86877
BH
673 }
674 }
69e7f117 675 catch(PDNSException &e) {
6c88989a
OM
676 SLOG(g_log<<Logger::Error<<d_logprefix<<"PDNSException in main webserver thread: "<<e.reason<<endl,
677 d_slog->error(Logr::Error, e.reason, msg, Logging::Loggable("PDNSException")));
69e7f117 678 }
adc10f99 679 catch(std::exception &e) {
6c88989a
OM
680 SLOG(g_log<<Logger::Error<<d_logprefix<<"STL Exception in main webserver thread: "<<e.what()<<endl,
681 d_slog->error(Logr::Error, e.what(), msg, Logging::Loggable("std::exception")));
12c86877
BH
682 }
683 catch(...) {
6c88989a
OM
684 SLOG(g_log<<Logger::Error<<d_logprefix<<"Unknown exception in main webserver thread"<<endl,
685 d_slog->info(Logr::Error, msg));
12c86877 686 }
5bd2ea7b 687 _exit(1);
3ae143b0 688}