Fawkes API  Fawkes Development Version
request_dispatcher.cpp
1 
2 /***************************************************************************
3  * request_dispatcher.cpp - Web request dispatcher
4  *
5  * Created: Mon Oct 13 22:48:04 2008
6  * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include <core/exception.h>
23 #include <core/threading/mutex.h>
24 #include <core/threading/mutex_locker.h>
25 #include <sys/socket.h>
26 #include <sys/types.h>
27 #include <utils/misc/string_urlescape.h>
28 #include <utils/time/time.h>
29 #include <webview/access_log.h>
30 #include <webview/error_reply.h>
31 #include <webview/page_reply.h>
32 #include <webview/request_dispatcher.h>
33 #include <webview/url_manager.h>
34 #include <webview/user_verifier.h>
35 
36 #include <cstdarg>
37 #include <cstdlib>
38 #include <cstring>
39 #include <microhttpd.h>
40 
41 #define UNAUTHORIZED_REPLY \
42  "<html>\n" \
43  " <head><title>Access denied</title></head>\n" \
44  " <body>\n" \
45  " <h1>Access denied</h1>\n" \
46  " <p>Authentication is required to access Fawkes Webview</p>\n" \
47  " </body>\n" \
48  "</html>"
49 
50 namespace fawkes {
51 
52 /** @class WebRequestDispatcher "request_dispatcher.h"
53  * Web request dispatcher.
54  * Takes web request received via a webserver run by libmicrohttpd and dispatches
55  * pages to registered URL handlers or gives a 404 error if no
56  * handler was registered for the given url.
57  * @author Tim Niemueller
58  */
59 
60 /** Constructor.
61  * @param url_manager URL manager to use for URL to processor mapping
62  * @param headergen page header generator
63  * @param footergen page footer generator
64  */
66  WebPageHeaderGenerator *headergen,
67  WebPageFooterGenerator *footergen)
68 {
69  realm_ = NULL;
70  access_log_ = NULL;
71  url_manager_ = url_manager;
72  page_header_generator_ = headergen;
73  page_footer_generator_ = footergen;
74  active_requests_ = 0;
75  active_requests_mutex_ = new Mutex();
76  last_request_completion_time_ = new Time();
77 
78  cors_allow_all_ = false;
79  cors_max_age_ = 0;
80 }
81 
82 /** Destructor. */
84 {
85  if (realm_)
86  free(realm_);
87  delete active_requests_mutex_;
88  delete last_request_completion_time_;
89  delete access_log_;
90 }
91 
92 /** Setup basic authentication.
93  * @param realm authentication realm to display to the user.
94  * If NULL basic authentication will be disabled.
95  * @param verifier verifier to use for checking credentials.
96  * If NULL basic authentication will be disabled.
97  */
98 void
100 {
101 #if MHD_VERSION >= 0x00090400
102  if (realm_)
103  free(realm_);
104  realm_ = NULL;
105  user_verifier_ = NULL;
106  if (realm && verifier) {
107  realm_ = strdup(realm);
108  user_verifier_ = verifier;
109  }
110 #else
111  throw Exception("libmicrohttpd >= 0.9.4 is required for basic authentication, "
112  "which was not available at compile time.");
113 #endif
114 }
115 
116 /** Setup access log.
117  * @param filename access log file name
118  */
119 void
121 {
122  delete access_log_;
123  access_log_ = NULL;
124  access_log_ = new WebviewAccessLog(filename);
125 }
126 
127 /** Setup cross-origin resource sharing
128  * @param allow_all allow access to all hosts
129  * @param origins allow access from these specific origins
130  * @param max_age maximum cache time to send to the client, zero to disable
131  */
132 void
134  std::vector<std::string> &&origins,
135  unsigned int max_age)
136 {
137  cors_allow_all_ = allow_all;
138  cors_origins_ = std::move(origins);
139  cors_max_age_ = max_age;
140 }
141 
142 /** Callback for new requests.
143  * @param cls closure, must be WebRequestDispatcher
144  * @param uri requested URI
145  * @return returns output of WebRequestDispatcher::log_uri()
146  */
147 void *
148 WebRequestDispatcher::uri_log_cb(void *cls, const char *uri)
149 {
150  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(cls);
151  return rd->log_uri(uri);
152 }
153 
154 /** Process request callback for libmicrohttpd.
155  * @param callback_data instance of WebRequestDispatcher to call
156  * @param connection libmicrohttpd connection instance
157  * @param url URL, may contain escape sequences
158  * @param method HTTP method
159  * @param version HTTP version
160  * @param upload_data uploaded data
161  * @param upload_data_size size of upload_data parameter
162  * @param session_data session data pointer
163  * @return appropriate return code for libmicrohttpd
164  */
165 int
167  struct MHD_Connection *connection,
168  const char * url,
169  const char * method,
170  const char * version,
171  const char * upload_data,
172  size_t * upload_data_size,
173  void ** session_data)
174 {
175  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(callback_data);
176  return rd->process_request(
177  connection, url, method, version, upload_data, upload_data_size, session_data);
178 }
179 
180 /** Process request completion.
181  * @param cls closure which is a pointer to the request dispatcher
182  * @param connection connection on which the request completed
183  * @param con_cls connection specific data, for us the request
184  * @param toe termination code
185  */
186 void
188  struct MHD_Connection * connection,
189  void ** con_cls,
190  enum MHD_RequestTerminationCode toe)
191 {
192  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(cls);
193  WebRequest * request = static_cast<WebRequest *>(*con_cls);
194  rd->request_completed(request, toe);
195  delete request;
196 }
197 
198 /** Callback based chunk-wise data.
199  * Supplies data chunk based.
200  * @param reply instance of DynamicWebReply
201  * @param pos position in stream
202  * @param buf buffer to put data in
203  * @param max maximum number of bytes that can be put in buf
204  * @return suitable libmicrohttpd return code
205  */
206 static ssize_t
207 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
208 {
209  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
210  ssize_t bytes = dreply->next_chunk(pos, buf, max);
211  WebRequest * request = dreply->get_request();
212  if (bytes > 0 && request)
213  request->increment_reply_size(bytes);
214  return bytes;
215 }
216 
217 /** Callback to free dynamic web reply.
218  * @param reply Instance of DynamicWebReply to free.
219  */
220 static void
222 {
223  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
224  delete dreply;
225 }
226 
227 /** Prepare response from static reply.
228  * @param sreply static reply
229  * @return response struct ready to be enqueued
230  */
231 struct MHD_Response *
232 WebRequestDispatcher::prepare_static_response(StaticWebReply *sreply)
233 {
234  struct MHD_Response *response;
235  WebPageReply * wpreply = dynamic_cast<WebPageReply *>(sreply);
236  if (wpreply) {
237  wpreply->pack(active_baseurl_, page_header_generator_, page_footer_generator_);
238  } else {
239  sreply->pack_caching();
240  sreply->pack();
241  }
242  if (sreply->body_length() > 0) {
243  response = MHD_create_response_from_buffer(sreply->body_length(),
244  (void *)sreply->body().c_str(),
245  MHD_RESPMEM_MUST_COPY);
246  } else {
247  response = MHD_create_response_from_buffer(0, (void *)"", MHD_RESPMEM_PERSISTENT);
248  }
249 
250  WebRequest *request = sreply->get_request();
251  if (request) {
252  request->set_reply_code(sreply->code());
253  request->increment_reply_size(sreply->body_length());
254  }
255 
256  const WebReply::HeaderMap & headers = sreply->headers();
257  WebReply::HeaderMap::const_iterator i;
258  for (i = headers.begin(); i != headers.end(); ++i) {
259  MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
260  }
261 
262  return response;
263 }
264 
265 /** Prepare response from static reply.
266  * @param request request this reply is associated to
267  * @param sreply static reply
268  * @return response struct ready to be enqueued
269  */
270 int
271 WebRequestDispatcher::queue_dynamic_reply(struct MHD_Connection *connection,
272  WebRequest * request,
273  DynamicWebReply * dreply)
274 {
275  dreply->set_request(request);
276  dreply->pack_caching();
277  request->set_reply_code(dreply->code());
278 
279  struct MHD_Response *response;
280  response = MHD_create_response_from_callback(
281  dreply->size(), dreply->chunk_size(), dynamic_reply_data_cb, dreply, dynamic_reply_free_cb);
282 
283  const WebReply::HeaderMap & headers = dreply->headers();
284  WebReply::HeaderMap::const_iterator i;
285  for (i = headers.begin(); i != headers.end(); ++i) {
286  MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
287  }
288 
289  int ret = MHD_queue_response(connection, dreply->code(), response);
290  MHD_destroy_response(response);
291 
292  return ret;
293 }
294 
295 /** Queue a static web reply.
296  * @param connection libmicrohttpd connection to queue response to
297  * @param request request this reply is associated to
298  * @param sreply static web reply to queue
299  * @return suitable libmicrohttpd return code
300  */
301 int
302 WebRequestDispatcher::queue_static_reply(struct MHD_Connection *connection,
303  WebRequest * request,
304  StaticWebReply * sreply)
305 {
306  sreply->set_request(request);
307 
308  struct MHD_Response *response = prepare_static_response(sreply);
309 
310  int rv = MHD_queue_response(connection, sreply->code(), response);
311  MHD_destroy_response(response);
312  return rv;
313 }
314 
315 /** Queue a static web reply after basic authentication failure.
316  * @param connection libmicrohttpd connection to queue response to
317  * @return suitable libmicrohttpd return code
318  */
319 int
320 WebRequestDispatcher::queue_basic_auth_fail(struct MHD_Connection *connection, WebRequest *request)
321 {
322  StaticWebReply sreply(WebReply::HTTP_UNAUTHORIZED, UNAUTHORIZED_REPLY);
323 #if MHD_VERSION >= 0x00090400
324  sreply.set_request(request);
325  sreply.pack_caching();
326  sreply.pack();
327  struct MHD_Response *response = prepare_static_response(&sreply);
328 
329  int rv = MHD_queue_basic_auth_fail_response(connection, realm_, response);
330  MHD_destroy_response(response);
331 #else
332  sreply.add_header(MHD_HTTP_HEADER_WWW_AUTHENTICATE,
333  (std::string("Basic realm=") + realm_).c_str());
334 
335  int rv = queue_static_reply(connection, request, &sreply);
336 #endif
337  return rv;
338 }
339 
340 /// @cond INTERNALS
341 /** Iterator over key-value pairs where the value
342  * maybe made available in increments and/or may
343  * not be zero-terminated. Used for processing
344  * POST data.
345  *
346  * @param cls user-specified closure
347  * @param kind type of the value
348  * @param key 0-terminated key for the value
349  * @param filename name of the uploaded file, NULL if not known
350  * @param content_type mime-type of the data, NULL if not known
351  * @param transfer_encoding encoding of the data, NULL if not known
352  * @param data pointer to size bytes of data at the
353  * specified offset
354  * @param off offset of data in the overall value
355  * @param size number of bytes in data available
356  * @return MHD_YES to continue iterating,
357  * MHD_NO to abort the iteration
358  */
359 static int
360 post_iterator(void * cls,
361  enum MHD_ValueKind kind,
362  const char * key,
363  const char * filename,
364  const char * content_type,
365  const char * transfer_encoding,
366  const char * data,
367  uint64_t off,
368  size_t size)
369 {
370  WebRequest *request = static_cast<WebRequest *>(cls);
371 
372  // Cannot handle files, yet
373  if (filename)
374  return MHD_NO;
375 
376  request->set_post_value(key, data + off, size);
377 
378  return MHD_YES;
379 }
380 /// @endcond
381 
382 /** URI logging callback.
383  * @param uri requested URI
384  */
385 void *
386 WebRequestDispatcher::log_uri(const char *uri)
387 {
388  return new WebRequest(uri);
389 }
390 
391 /** Process request callback for libmicrohttpd.
392  * @param connection libmicrohttpd connection instance
393  * @param url URL, may contain escape sequences
394  * @param method HTTP method
395  * @param version HTTP version
396  * @param upload_data uploaded data
397  * @param upload_data_size size of upload_data parameter
398  * @param session_data session data pointer
399  * @return appropriate return code for libmicrohttpd
400  */
401 int
402 WebRequestDispatcher::process_request(struct MHD_Connection *connection,
403  const char * url,
404  const char * method,
405  const char * version,
406  const char * upload_data,
407  size_t * upload_data_size,
408  void ** session_data)
409 {
410  WebRequest *request = static_cast<WebRequest *>(*session_data);
411 
412  if (!request->is_setup()) {
413  // The first time only the headers are valid,
414  // do not respond in the first round...
415  request->setup(url, method, version, connection);
416 
417  active_requests_mutex_->lock();
418  active_requests_ += 1;
419  active_requests_mutex_->unlock();
420 
421  if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
422  request->pp_ = MHD_create_post_processor(connection, 1024, &post_iterator, request);
423  }
424 
425  return MHD_YES;
426  }
427 
428 #if MHD_VERSION >= 0x00090400
429  if (realm_) {
430  char *user, *pass = NULL;
431  user = MHD_basic_auth_get_username_password(connection, &pass);
432  if ((user == NULL) || (pass == NULL) || !user_verifier_->verify_user(user, pass)) {
433  return queue_basic_auth_fail(connection, request);
434  }
435  request->user_ = user;
436  }
437 #endif
438 
439  if (0 == strcmp(method, MHD_HTTP_METHOD_OPTIONS)) {
440  StaticWebReply *reply = new StaticWebReply(WebReply::HTTP_OK);
441  reply->set_caching(true); // handled via Max-Age header anyway
442  const std::map<std::string, std::string> &headers{request->headers()};
443  const auto &request_method = headers.find("Access-Control-Request-Method");
444  const auto &request_headers = headers.find("Access-Control-Request-Headers");
445  if (cors_allow_all_) {
446  reply->add_header("Access-Control-Allow-Origin", "*");
447  if (cors_max_age_ > 0) {
448  reply->add_header("Access-Control-Max-Age", std::to_string(cors_max_age_));
449  }
450  if (request_method != headers.end()) {
451  reply->add_header("Access-Control-Allow-Methods", request_method->second);
452  }
453  if (request_headers != headers.end()) {
454  reply->add_header("Access-Control-Allow-Headers", request_headers->second);
455  }
456  } else if (!cors_origins_.empty()) {
457  const auto &origin = headers.find("Origin");
458  if (origin != headers.end()) {
459  if (std::find(cors_origins_.begin(), cors_origins_.end(), origin->second)
460  != cors_origins_.end()) {
461  reply->add_header("Access-Control-Allow-Origin", origin->second);
462  if (cors_max_age_ > 0) {
463  reply->add_header("Access-Control-Max-Age", std::to_string(cors_max_age_));
464  }
465  if (request_method != headers.end()) {
466  reply->add_header("Access-Control-Allow-Methods", request_method->second);
467  }
468  if (request_headers != headers.end()) {
469  reply->add_header("Access-Control-Allow-Headers", request_headers->second);
470  }
471  } else {
472  reply->set_code(WebReply::HTTP_FORBIDDEN);
473  }
474  } else {
475  reply->set_code(WebReply::HTTP_FORBIDDEN);
476  }
477  }
478  return queue_static_reply(connection, request, reply);
479  delete reply;
480  }
481 
482  if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
483  if (MHD_post_process(request->pp_, upload_data, *upload_data_size) == MHD_NO) {
484  request->addto_body(upload_data, *upload_data_size);
485  }
486  if (0 != *upload_data_size) {
487  *upload_data_size = 0;
488  return MHD_YES;
489  }
490  MHD_destroy_post_processor(request->pp_);
491  request->pp_ = NULL;
492  } else if (0 != *upload_data_size) {
493  request->addto_body(upload_data, *upload_data_size);
494  *upload_data_size = 0;
495  return MHD_YES;
496  } else {
497  request->finish_body();
498  }
499 
500  try {
501  WebReply *reply = url_manager_->process_request(request);
502  int ret;
503 
504  if (reply) {
505  if (cors_allow_all_) {
506  reply->add_header("Access-Control-Allow-Origin", "*");
507  }
508 
509  StaticWebReply * sreply = dynamic_cast<StaticWebReply *>(reply);
510  DynamicWebReply *dreply = dynamic_cast<DynamicWebReply *>(reply);
511  if (sreply) {
512  ret = queue_static_reply(connection, request, sreply);
513  delete reply;
514  } else if (dreply) {
515  ret = queue_dynamic_reply(connection, request, dreply);
516  } else {
517  WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR, "Unknown reply type");
518  ret = queue_static_reply(connection, request, &ereply);
519  delete reply;
520  }
521  } else {
522  WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
523  ret = queue_static_reply(connection, request, &ereply);
524  }
525  return ret;
526  } catch (Exception &e) {
527  WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR, "%s", e.what_no_backtrace());
528  return queue_static_reply(connection, request, &ereply);
529  } catch (std::exception &e) {
530  WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR, "%s", e.what());
531  return queue_static_reply(connection, request, &ereply);
532  }
533 }
534 
535 void
536 WebRequestDispatcher::request_completed(WebRequest *request, MHD_RequestTerminationCode term_code)
537 {
538  active_requests_mutex_->lock();
539  if (active_requests_ > 0)
540  active_requests_ -= 1;
541  last_request_completion_time_->stamp();
542  active_requests_mutex_->unlock();
543  if (access_log_)
544  access_log_->log(request);
545 }
546 
547 /** Get number of active requests.
548  * @return number of ongoing requests.
549  */
550 unsigned int
552 {
553  MutexLocker lock(active_requests_mutex_);
554  return active_requests_;
555 }
556 
557 /** Get time when last request was completed.
558  * @return Time when last request was completed
559  */
560 Time
562 {
563  MutexLocker lock(active_requests_mutex_);
564  return *last_request_completion_time_;
565 }
566 
567 } // end namespace fawkes
fawkes::Mutex::lock
void lock()
Lock this mutex.
Definition: mutex.cpp:93
fawkes::WebPageHeaderGenerator
Definition: page_header_generator.h:36
fawkes::WebRequest
Definition: request.h:41
fawkes::dynamic_reply_free_cb
static void dynamic_reply_free_cb(void *reply)
Callback to free dynamic web reply.
Definition: request_dispatcher.cpp:221
fawkes::Mutex
Definition: mutex.h:38
fawkes::DynamicWebReply::next_chunk
virtual size_t next_chunk(size_t pos, char *buffer, size_t buf_max_size)=0
fawkes::WebReply::HTTP_UNAUTHORIZED
@ HTTP_UNAUTHORIZED
UNAUTHORIZED.
Definition: reply.h:67
fawkes::WebRequest::increment_reply_size
void increment_reply_size(size_t increment_by)
Increment reply bytes counter.
Definition: request.cpp:236
fawkes::WebRequestDispatcher::WebRequestDispatcher
WebRequestDispatcher(WebUrlManager *url_manager, WebPageHeaderGenerator *headergen=0, WebPageFooterGenerator *footergen=0)
Constructor.
Definition: request_dispatcher.cpp:65
fawkes::WebRequestDispatcher::~WebRequestDispatcher
~WebRequestDispatcher()
Destructor.
Definition: request_dispatcher.cpp:83
fawkes::WebRequestDispatcher::process_request_cb
static int process_request_cb(void *callback_data, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **session_data)
Process request callback for libmicrohttpd.
Definition: request_dispatcher.cpp:166
fawkes::WebRequestDispatcher::setup_cors
void setup_cors(bool allow_all, std::vector< std::string > &&origins, unsigned int max_age)
Setup cross-origin resource sharing.
Definition: request_dispatcher.cpp:133
fawkes::WebReply::HTTP_NOT_FOUND
@ HTTP_NOT_FOUND
NOT_FOUND.
Definition: reply.h:70
fawkes::MutexLocker
Definition: mutex_locker.h:39
fawkes::Mutex::unlock
void unlock()
Unlock the mutex.
Definition: mutex.cpp:137
fawkes::WebUrlManager
Definition: url_manager.h:45
fawkes::WebRequestDispatcher
Definition: request_dispatcher.h:51
fawkes::WebReply::pack_caching
void pack_caching()
Called just before the reply is sent.
Definition: reply.cpp:187
fawkes::WebReply::HTTP_OK
@ HTTP_OK
OK.
Definition: reply.h:48
fawkes::dynamic_reply_data_cb
static ssize_t dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
Callback based chunk-wise data.
Definition: request_dispatcher.cpp:207
fawkes::WebRequestDispatcher::active_requests
unsigned int active_requests() const
Get number of active requests.
Definition: request_dispatcher.cpp:551
fawkes
fawkes::WebReply::HTTP_FORBIDDEN
@ HTTP_FORBIDDEN
FORBIDDEN.
Definition: reply.h:69
fawkes::WebPageFooterGenerator
Definition: page_footer_generator.h:36
fawkes::WebReply::add_header
void add_header(const std::string &header, const std::string &content)
Add a HTTP header.
Definition: reply.cpp:129
fawkes::WebRequestDispatcher::request_completed_cb
static void request_completed_cb(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe)
Process request completion.
Definition: request_dispatcher.cpp:187
fawkes::WebReply::HeaderMap
std::map< std::string, std::string > HeaderMap
Map of headers.
Definition: reply.h:104
fawkes::Time
Definition: time.h:98
fawkes::WebRequestDispatcher::uri_log_cb
static void * uri_log_cb(void *cls, const char *uri)
Callback for new requests.
Definition: request_dispatcher.cpp:148
fawkes::WebRequestDispatcher::last_request_completion_time
Time last_request_completion_time() const
Get time when last request was completed.
Definition: request_dispatcher.cpp:561
fawkes::Time::stamp
Time & stamp()
Set this time to the current time.
Definition: time.cpp:711
fawkes::WebUserVerifier
Definition: user_verifier.h:34
fawkes::WebviewAccessLog
Definition: access_log.h:37
fawkes::WebRequestDispatcher::setup_basic_auth
void setup_basic_auth(const char *realm, WebUserVerifier *verifier)
Setup basic authentication.
Definition: request_dispatcher.cpp:99
fawkes::WebviewAccessLog::log
void log(const WebRequest *request)
Log a request.
Definition: access_log.cpp:70
fawkes::WebRequestDispatcher::setup_access_log
void setup_access_log(const char *filename)
Setup access log.
Definition: request_dispatcher.cpp:120
fawkes::WebReply::get_request
WebRequest * get_request() const
Get associated request.
Definition: reply.cpp:169
fawkes::WebReply::HTTP_INTERNAL_SERVER_ERROR
@ HTTP_INTERNAL_SERVER_ERROR
INTERNAL_SERVER_ERROR.
Definition: reply.h:91
fawkes::DynamicWebReply
Definition: reply.h:131
fawkes::WebUserVerifier::verify_user
virtual bool verify_user(const char *user, const char *password)=0
fawkes::Exception
Definition: exception.h:41