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-2014 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 <webview/request_dispatcher.h>
23 #include <webview/request_processor.h>
24 #include <webview/url_manager.h>
25 #include <webview/page_reply.h>
26 #include <webview/error_reply.h>
27 #include <webview/user_verifier.h>
28 #include <webview/access_log.h>
29 
30 #include <core/threading/mutex.h>
31 #include <core/threading/mutex_locker.h>
32 #include <core/exception.h>
33 #include <utils/misc/string_urlescape.h>
34 #include <utils/time/time.h>
35 
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <cstdarg>
39 #include <microhttpd.h>
40 #include <cstring>
41 #include <cstdlib>
42 
43 #include <microhttpd.h>
44 
45 #define UNAUTHORIZED_REPLY \
46  "<html>\n" \
47  " <head><title>Access denied</title></head>\n" \
48  " <body>\n" \
49  " <h1>Access denied</h1>\n" \
50  " <p>Authentication is required to access Fawkes Webview</p>\n" \
51  " </body>\n" \
52  "</html>"
53 
54 namespace fawkes {
55 #if 0 /* just to make Emacs auto-indent happy */
56 }
57 #endif
58 
59 /** @class WebRequestDispatcher "request_dispatcher.h"
60  * Web request dispatcher.
61  * Takes web request received via a webserver run by libmicrohttpd and dispatches
62  * pages to registered WebRequestProcessor instances or gives a 404 error if no
63  * processor was registered for the given base url.
64  * @author Tim Niemueller
65  */
66 
67 /** Constructor.
68  * @param url_manager URL manager to use for URL to processor mapping
69  * @param headergen page header generator
70  * @param footergen page footer generator
71  */
73  WebPageHeaderGenerator *headergen,
74  WebPageFooterGenerator *footergen)
75 {
76  __realm = NULL;
77  __access_log = NULL;
78  __url_manager = url_manager;
79  __page_header_generator = headergen;
80  __page_footer_generator = footergen;
81  __active_requests = 0;
82  __active_requests_mutex = new Mutex();
83  __last_request_completion_time = new Time();
84 }
85 
86 
87 /** Destructor. */
89 {
90  if (__realm) free(__realm);
91  delete __active_requests_mutex;
92  delete __last_request_completion_time;
93  delete __access_log;
94 }
95 
96 
97 /** Setup basic authentication.
98  * @param realm authentication realm to display to the user.
99  * If NULL basic authentication will be disabled.
100  * @param verifier verifier to use for checking credentials.
101  * If NULL basic authentication will be disabled.
102  */
103 void
105  WebUserVerifier *verifier)
106 {
107 #if MHD_VERSION >= 0x00090400
108  if (__realm) free(__realm);
109  __realm = NULL;
110  __user_verifier = NULL;
111  if (realm && verifier) {
112  __realm = strdup(realm);
113  __user_verifier = verifier;
114  }
115 #else
116  throw Exception("libmicrohttpd >= 0.9.4 is required for basic authentication, "
117  "which was not available at compile time.");
118 #endif
119 }
120 
121 
122 /** Setup access log.
123  * @param filename access log file name
124  */
125 void
127 {
128  delete __access_log;
129  __access_log = NULL;
130  __access_log = new WebviewAccessLog(filename);
131 }
132 
133 /** Callback for new requests.
134  * @param cls closure, must be WebRequestDispatcher
135  * @param uri requested URI
136  * @return returns output of WebRequestDispatcher::log_uri()
137  */
138 void *
139 WebRequestDispatcher::uri_log_cb(void *cls, const char * uri)
140 {
141  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(cls);
142  return rd->log_uri(uri);
143 }
144 
145 /** Process request callback for libmicrohttpd.
146  * @param callback_data instance of WebRequestDispatcher to call
147  * @param connection libmicrohttpd connection instance
148  * @param url URL, may contain escape sequences
149  * @param method HTTP method
150  * @param version HTTP version
151  * @param upload_data uploaded data
152  * @param upload_data_size size of upload_data parameter
153  * @param session_data session data pointer
154  * @return appropriate return code for libmicrohttpd
155  */
156 int
158  struct MHD_Connection * connection,
159  const char *url,
160  const char *method,
161  const char *version,
162  const char *upload_data,
163  size_t *upload_data_size,
164  void **session_data)
165 {
166  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(callback_data);
167  return rd->process_request(connection, url, method, version,
168  upload_data, upload_data_size, session_data);
169 }
170 
171 
172 /** Process request completion.
173  * @param cls closure which is a pointer to the request dispatcher
174  * @param connection connection on which the request completed
175  * @param con_cls connection specific data, for us the request
176  * @param toe termination code
177  */
178 void
180  struct MHD_Connection *connection, void **con_cls,
181  enum MHD_RequestTerminationCode toe)
182 {
183  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(cls);
184  WebRequest *request = static_cast<WebRequest *>(*con_cls);
185  rd->request_completed(request, toe);
186  delete request;
187 }
188 
189 
190 /** Callback based chunk-wise data.
191  * Supplies data chunk based.
192  * @param reply instance of DynamicWebReply
193  * @param pos position in stream
194  * @param buf buffer to put data in
195  * @param max maximum number of bytes that can be put in buf
196  * @return suitable libmicrohttpd return code
197  */
198 static ssize_t
199 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
200 {
201  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
202  ssize_t bytes = dreply->next_chunk(pos, buf, max);
203  WebRequest *request = dreply->get_request();
204  if (bytes > 0 && request) request->increment_reply_size(bytes);
205  return bytes;
206 }
207 
208 
209 /** Callback to free dynamic web reply.
210  * @param reply Instance of DynamicWebReply to free.
211  */
212 static void
214 {
215  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
216  delete dreply;
217 }
218 
219 
220 /** Prepare response from static reply.
221  * @param sreply static reply
222  * @return response struct ready to be enqueued
223  */
224 struct MHD_Response *
225 WebRequestDispatcher::prepare_static_response(StaticWebReply *sreply)
226 {
227  struct MHD_Response *response;
228  WebPageReply *wpreply = dynamic_cast<WebPageReply *>(sreply);
229  if (wpreply) {
230  wpreply->pack(__active_baseurl,
231  __page_header_generator, __page_footer_generator);
232  } else {
233  sreply->pack();
234  }
235  if (sreply->body_length() > 0) {
236  response = MHD_create_response_from_buffer(sreply->body_length(),
237  (void*) sreply->body().c_str(),
238  MHD_RESPMEM_MUST_COPY);
239  } else {
240  response = MHD_create_response_from_buffer(0, (void*) "",
241  MHD_RESPMEM_PERSISTENT);
242  }
243 
244  WebRequest *request = sreply->get_request();
245  if (request) {
246  request->set_reply_code(sreply->code());
247  request->increment_reply_size(sreply->body_length());
248  }
249 
250  const WebReply::HeaderMap &headers = sreply->headers();
251  WebReply::HeaderMap::const_iterator i;
252  for (i = headers.begin(); i != headers.end(); ++i) {
253  MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
254  }
255 
256  return response;
257 }
258 
259 /** Prepare response from static reply.
260  * @param request request this reply is associated to
261  * @param sreply static reply
262  * @return response struct ready to be enqueued
263  */
264 int
265 WebRequestDispatcher::queue_dynamic_reply(struct MHD_Connection * connection,
266  WebRequest *request,
267  DynamicWebReply *dreply)
268 {
269  dreply->set_request(request);
270  request->set_reply_code(dreply->code());
271 
272  struct MHD_Response *response;
273  response = MHD_create_response_from_callback(dreply->size(),
274  dreply->chunk_size(),
276  dreply,
278 
279  const WebReply::HeaderMap &headers = dreply->headers();
280  WebReply::HeaderMap::const_iterator i;
281  for (i = headers.begin(); i != headers.end(); ++i) {
282  MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
283  }
284 
285  int ret = MHD_queue_response (connection, dreply->code(), response);
286  MHD_destroy_response (response);
287 
288  return ret;
289 }
290 
291 /** Queue a static web reply.
292  * @param connection libmicrohttpd connection to queue response to
293  * @param request request this reply is associated to
294  * @param sreply static web reply to queue
295  * @return suitable libmicrohttpd return code
296  */
297 int
298 WebRequestDispatcher::queue_static_reply(struct MHD_Connection * connection,
299  WebRequest *request,
300  StaticWebReply *sreply)
301 {
302  sreply->set_request(request);
303 
304  struct MHD_Response *response = prepare_static_response(sreply);
305 
306  int rv = MHD_queue_response(connection, sreply->code(), response);
307  MHD_destroy_response(response);
308  return rv;
309 }
310 
311 
312 /** Queue a static web reply after basic authentication failure.
313  * @param connection libmicrohttpd connection to queue response to
314  * @return suitable libmicrohttpd return code
315  */
316 int
317 WebRequestDispatcher::queue_basic_auth_fail(struct MHD_Connection * connection,
318  WebRequest *request)
319 {
320  StaticWebReply sreply(WebReply::HTTP_UNAUTHORIZED, UNAUTHORIZED_REPLY);
321 #if MHD_VERSION >= 0x00090400
322  sreply.set_request(request);
323  struct MHD_Response *response = prepare_static_response(&sreply);
324 
325  int rv = MHD_queue_basic_auth_fail_response(connection, __realm, response);
326  MHD_destroy_response(response);
327 #else
328  sreply.add_header(MHD_HTTP_HEADER_WWW_AUTHENTICATE,
329  (std::string("Basic realm=") + __realm).c_str());
330 
331  int rv = queue_static_reply(connection, request, &sreply);
332 #endif
333  return rv;
334 }
335 
336 
337 /// @cond INTERNALS
338 /** Iterator over key-value pairs where the value
339  * maybe made available in increments and/or may
340  * not be zero-terminated. Used for processing
341  * POST data.
342  *
343  * @param cls user-specified closure
344  * @param kind type of the value
345  * @param key 0-terminated key for the value
346  * @param filename name of the uploaded file, NULL if not known
347  * @param content_type mime-type of the data, NULL if not known
348  * @param transfer_encoding encoding of the data, NULL if not known
349  * @param data pointer to size bytes of data at the
350  * specified offset
351  * @param off offset of data in the overall value
352  * @param size number of bytes in data available
353  * @return MHD_YES to continue iterating,
354  * MHD_NO to abort the iteration
355  */
356 static int
357 post_iterator(void *cls, enum MHD_ValueKind kind, const char *key,
358  const char *filename, const char *content_type,
359  const char *transfer_encoding, const char *data, uint64_t off,
360  size_t size)
361 {
362  WebRequest *request = static_cast<WebRequest *>(cls);
363 
364  // Cannot handle files, yet
365  if (filename) return MHD_NO;
366 
367  request->set_post_value(key, data+off, size);
368 
369  return MHD_YES;
370 }
371 /// @endcond
372 
373 /** URI logging callback.
374  * @param uri requested URI
375  */
376 void *
377 WebRequestDispatcher::log_uri(const char *uri)
378 {
379  return new WebRequest(uri);
380 }
381 
382 /** Process request callback for libmicrohttpd.
383  * @param connection libmicrohttpd connection instance
384  * @param url URL, may contain escape sequences
385  * @param method HTTP method
386  * @param version HTTP version
387  * @param upload_data uploaded data
388  * @param upload_data_size size of upload_data parameter
389  * @param session_data session data pointer
390  * @return appropriate return code for libmicrohttpd
391  */
392 int
393 WebRequestDispatcher::process_request(struct MHD_Connection * connection,
394  const char *url,
395  const char *method,
396  const char *version,
397  const char *upload_data,
398  size_t *upload_data_size,
399  void **session_data)
400 {
401  WebRequest *request = static_cast<WebRequest *>(*session_data);
402 
403  if ( ! request->is_setup() ) {
404  // The first time only the headers are valid,
405  // do not respond in the first round...
406  request->setup(url, method, version, connection);
407 
408  __active_requests_mutex->lock();
409  __active_requests += 1;
410  __active_requests_mutex->unlock();
411 
412  if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
413  request->pp_ =
414  MHD_create_post_processor(connection, 1024, &post_iterator, request);
415  }
416 
417  return MHD_YES;
418  }
419 
420 #if MHD_VERSION >= 0x00090400
421  if (__realm) {
422  char *user, *pass = NULL;
423  user = MHD_basic_auth_get_username_password(connection, &pass);
424  if ( (user == NULL) || (pass == NULL) ||
425  ! __user_verifier->verify_user(user, pass))
426  {
427  return queue_basic_auth_fail(connection, request);
428  }
429  request->user_ = user;
430  }
431 #endif
432 
433  std::string surl = url;
434  int ret;
435 
436  MutexLocker lock(__url_manager->mutex());
437  WebRequestProcessor *proc = __url_manager->find_processor(surl);
438 
439  if (proc) {
440  if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
441  if (MHD_post_process(request->pp_, upload_data, *upload_data_size) == MHD_NO) {
442  request->set_raw_post_data(upload_data, *upload_data_size);
443  }
444  if (0 != *upload_data_size) {
445  *upload_data_size = 0;
446  return MHD_YES;
447  }
448  MHD_destroy_post_processor(request->pp_);
449  request->pp_ = NULL;
450  }
451 
452  WebReply *reply = proc->process_request(request);
453  if ( reply ) {
454  StaticWebReply *sreply = dynamic_cast<StaticWebReply *>(reply);
455  DynamicWebReply *dreply = dynamic_cast<DynamicWebReply *>(reply);
456  if (sreply) {
457  ret = queue_static_reply(connection, request, sreply);
458  delete reply;
459  } else if (dreply) {
460  ret = queue_dynamic_reply(connection, request, dreply);
461  } else {
463  ret = queue_static_reply(connection, request, &ereply);
464  delete reply;
465  }
466  } else {
468  ret = queue_static_reply(connection, request, &ereply);
469  }
470  } else {
471  if (surl == "/") {
472  WebPageReply preply("Fawkes", "<h1>Welcome to Fawkes.</h1><hr />");
473  ret = queue_static_reply(connection, request, &preply);
474  } else {
476  ret = queue_static_reply(connection, request, &ereply);
477  }
478  }
479  return ret;
480 }
481 
482 
483 void
484 WebRequestDispatcher::request_completed(WebRequest *request, MHD_RequestTerminationCode term_code)
485 {
486  __active_requests_mutex->lock();
487  if (__active_requests > 0) __active_requests -= 1;
488  __last_request_completion_time->stamp();
489  __active_requests_mutex->unlock();
490  if (__access_log) __access_log->log(request);
491 }
492 
493 /** Get number of active requests.
494  * @return number of ongoing requests.
495  */
496 unsigned int
498 {
499  MutexLocker lock(__active_requests_mutex);
500  return __active_requests;
501 }
502 
503 /** Get time when last request was completed.
504  * @return Time when last request was completed
505  */
506 Time
508 {
509  MutexLocker lock(__active_requests_mutex);
510  return *__last_request_completion_time;
511 }
512 
513 } // end namespace fawkes
const HeaderMap & headers() const
get headers.
Definition: reply.cpp:129
Web request dispatcher.
void set_request(WebRequest *request)
Set associated request.
Definition: reply.cpp:150
virtual size_t next_chunk(size_t pos, char *buffer, size_t buf_max_size)=0
Get data of next chunk.
virtual size_t chunk_size()
Chunksize.
Definition: reply.cpp:194
unsigned int active_requests() const
Get number of active requests.
void set_reply_code(WebReply::Code code)
Set HTTP code of the final reply.
Definition: request.cpp:249
WebRequest * get_request() const
Get associated request.
Definition: reply.cpp:140
virtual size_t size()=0
Total size of the web reply.
Fawkes library namespace.
void unlock()
Unlock the mutex.
Definition: mutex.cpp:135
Mutex locking helper.
Definition: mutex_locker.h:33
static ssize_t dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
Callback based chunk-wise data.
void add_header(std::string header, std::string content)
Add a HTTP header.
Definition: reply.cpp:97
static void dynamic_reply_free_cb(void *reply)
Callback to free dynamic web reply.
Abstract web request processor.
static void request_completed_cb(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe)
Process request completion.
A class for handling time.
Definition: time.h:91
virtual void pack()
Pack the data.
Definition: page_reply.h:43
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.
WebRequestDispatcher(WebUrlManager *url_manager, WebPageHeaderGenerator *headergen=0, WebPageFooterGenerator *footergen=0)
Constructor.
Mutex * mutex()
Get internal mutex.
virtual const std::string & body()
Get body.
Definition: reply.cpp:253
void setup_basic_auth(const char *realm, WebUserVerifier *verifier)
Setup basic authentication.
Interface for user verification.
Definition: user_verifier.h:31
Interface for HTML header generator.
Code code() const
Get response code.
Definition: reply.cpp:86
Webview access_log writer.
Definition: access_log.h:35
void set_post_value(const char *key, const char *data, size_t size)
Set a POST value.
Definition: request.cpp:169
Manage URL mappings.
Definition: url_manager.h:37
Dynamic web reply.
Definition: reply.h:123
Base class for exceptions in Fawkes.
Definition: exception.h:36
std::map< std::string, std::string > HeaderMap
Map of headers.
Definition: reply.h:101
Web request meta data carrier.
Definition: request.h:42
void increment_reply_size(size_t increment_by)
Increment reply bytes counter.
Definition: request.cpp:196
Basic page reply.
Definition: page_reply.h:36
Time last_request_completion_time() const
Get time when last request was completed.
virtual bool verify_user(const char *user, const char *password)=0
Verify a user.
WebRequestProcessor * find_processor(std::string &url) const
Lock mutex and find processor.
Basic web reply.
Definition: reply.h:36
Interface for HTML footer generator.
static void * uri_log_cb(void *cls, const char *uri)
Callback for new requests.
void set_raw_post_data(const char *data, size_t data_size)
Set raw post data.
Definition: request.cpp:187
void setup_access_log(const char *filename)
Setup access log.
void lock()
Lock this mutex.
Definition: mutex.cpp:89
Time & stamp()
Set this time to the current time.
Definition: time.cpp:783
Mutex mutual exclusion lock.
Definition: mutex.h:32
Static error page reply.
Definition: error_reply.h:33
void log(const WebRequest *request)
Log a request.
Definition: access_log.cpp:70
virtual void pack()
Pack the data.
Definition: reply.cpp:275
virtual std::string::size_type body_length()
Get length of body.
Definition: reply.cpp:263
Static web reply.
Definition: reply.h:133