Fawkes API  Fawkes Development Version
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
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-2010 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL file in the doc directory.
21  */
22 
23 #include <webview/request_dispatcher.h>
24 #include <webview/request_processor.h>
25 #include <webview/url_manager.h>
26 #include <webview/page_reply.h>
27 #include <webview/error_reply.h>
28 #include <webview/user_verifier.h>
29 
30 #include <core/threading/mutex_locker.h>
31 #include <core/exception.h>
32 #include <utils/misc/string_urlescape.h>
33 
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <cstdarg>
37 #include <microhttpd.h>
38 #include <cstring>
39 #include <cstdlib>
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 #if 0 /* just to make Emacs auto-indent happy */
52 }
53 #endif
54 
55 /** @class WebRequestDispatcher "request_dispatcher.h"
56  * Web request dispatcher.
57  * Takes web request received via a webserver run by libmicrohttpd and dispatches
58  * pages to registered WebRequestProcessor instances or gives a 404 error if no
59  * processor was registered for the given base url.
60  * @author Tim Niemueller
61  */
62 
63 /** Constructor.
64  * @param url_manager URL manager to use for URL to processor mapping
65  * @param headergen page header generator
66  * @param footergen page footer generator
67  */
68 WebRequestDispatcher::WebRequestDispatcher(WebUrlManager *url_manager,
69  WebPageHeaderGenerator *headergen,
70  WebPageFooterGenerator *footergen)
71 {
72  __realm = NULL;
73  __url_manager = url_manager;
74  __page_header_generator = headergen;
75  __page_footer_generator = footergen;
76 }
77 
78 
79 /** Destructor. */
80 WebRequestDispatcher::~WebRequestDispatcher()
81 {
82  if (__realm) free(__realm);
83 }
84 
85 
86 /** Setup basic authentication.
87  * @param realm authentication realm to display to the user.
88  * If NULL basic authentication will be disabled.
89  * @param verifier verifier to use for checking credentials.
90  * If NULL basic authentication will be disabled.
91  */
92 void
93 WebRequestDispatcher::setup_basic_auth(const char *realm,
94  WebUserVerifier *verifier)
95 {
96 #if MHD_VERSION >= 0x00090400
97  if (__realm) free(__realm);
98  __realm = NULL;
99  __user_verifier = NULL;
100  if (realm && verifier) {
101  __realm = strdup(realm);
102  __user_verifier = verifier;
103  }
104 #else
105  throw Exception("libmicrohttpd >= 0.9.4 is required for basic authentication, "
106  "which was not available at compile time.");
107 #endif
108 }
109 
110 /** Process request callback for libmicrohttpd.
111  * @param callback_data instance of WebRequestDispatcher to call
112  * @param connection libmicrohttpd connection instance
113  * @param url URL, may contain escape sequences
114  * @param method HTTP method
115  * @param version HTTP version
116  * @param upload_data uploaded data
117  * @param upload_data_size size of upload_data parameter
118  * @param session_data session data pointer
119  * @return appropriate return code for libmicrohttpd
120  */
121 int
122 WebRequestDispatcher::process_request_cb(void *callback_data,
123  struct MHD_Connection * connection,
124  const char *url,
125  const char *method,
126  const char *version,
127  const char *upload_data,
128  size_t *upload_data_size,
129  void **session_data)
130 {
131  WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(callback_data);
132  return rd->process_request(connection, url, method, version,
133  upload_data, upload_data_size, session_data);
134 }
135 
136 
137 /** Callback based chunk-wise data.
138  * Supplies data chunk based.
139  * @param reply instance of DynamicWebReply
140  * @param pos position in stream
141  * @param buf buffer to put data in
142  * @param max maximum number of bytes that can be put in buf
143  * @return suitable libmicrohttpd return code
144  */
145 #if MHD_VERSION >= 0x00090200
146 static ssize_t
147 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
148 #else
149 static int
150 # if MHD_VERSION <= 0x00040000
151 dynamic_reply_data_cb(void *reply, size_t pos, char *buf, int max)
152 # else
153 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, int max)
154 # endif
155 #endif
156 {
157  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
158  return dreply->next_chunk(pos, buf, max);
159 }
160 
161 
162 /** Callback to free dynamic web reply.
163  * @param reply Instance of DynamicWebReply to free.
164  */
165 static void
167 {
168  DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
169  delete dreply;
170 }
171 
172 
173 /** Prepare response from static reply.
174  * @param sreply static reply
175  * @return response struct ready to be enqueued
176  */
177 struct MHD_Response *
178 WebRequestDispatcher::prepare_static_response(StaticWebReply *sreply)
179 {
180  struct MHD_Response *response;
181  WebPageReply *wpreply = dynamic_cast<WebPageReply *>(sreply);
182  if (wpreply) {
183  wpreply->pack(__active_baseurl,
184  __page_header_generator, __page_footer_generator);
185  } else {
186  sreply->pack();
187  }
188  if (sreply->body_length() > 0) {
189  response = MHD_create_response_from_data(sreply->body_length(),
190  (void*) sreply->body().c_str(),
191  /* free */ MHD_YES,
192  /* copy */ MHD_YES);
193  } else {
194  response = MHD_create_response_from_data(0, (void*) "",
195  /* free */ MHD_NO,
196  /* copy */ MHD_NO);
197  }
198 
199  const WebReply::HeaderMap &headers = sreply->headers();
200  WebReply::HeaderMap::const_iterator i;
201  for (i = headers.begin(); i != headers.end(); ++i) {
202  MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
203  }
204 
205  return response;
206 }
207 
208 /** Queue a static web reply.
209  * @param connection libmicrohttpd connection to queue response to
210  * @param sreply static web reply to queue
211  * @return suitable libmicrohttpd return code
212  */
213 int
214 WebRequestDispatcher::queue_static_reply(struct MHD_Connection * connection,
215  StaticWebReply *sreply)
216 {
217  struct MHD_Response *response = prepare_static_response(sreply);
218 
219  int rv = MHD_queue_response(connection, sreply->code(), response);
220  MHD_destroy_response(response);
221  return rv;
222 }
223 
224 
225 /** Queue a static web reply after basic authentication failure.
226  * @param connection libmicrohttpd connection to queue response to
227  * @return suitable libmicrohttpd return code
228  */
229 int
230 WebRequestDispatcher::queue_basic_auth_fail(struct MHD_Connection * connection)
231 {
232  StaticWebReply sreply(WebReply::HTTP_UNAUTHORIZED, UNAUTHORIZED_REPLY);
233 #if MHD_VERSION >= 0x00090400
234  struct MHD_Response *response = prepare_static_response(&sreply);
235 
236  int rv = MHD_queue_basic_auth_fail_response(connection, __realm, response);
237  MHD_destroy_response(response);
238 #else
239  sreply.add_header(MHD_HTTP_HEADER_WWW_AUTHENTICATE,
240  (std::string("Basic realm=") + __realm).c_str());
241 
242  int rv = queue_static_reply(connection, &sreply);
243 #endif
244  return rv;
245 }
246 
247 
248 /** Process request callback for libmicrohttpd.
249  * @param connection libmicrohttpd connection instance
250  * @param url URL, may contain escape sequences
251  * @param method HTTP method
252  * @param version HTTP version
253  * @param upload_data uploaded data
254  * @param upload_data_size size of upload_data parameter
255  * @param session_data session data pointer
256  * @return appropriate return code for libmicrohttpd
257  */
258 int
259 WebRequestDispatcher::process_request(struct MHD_Connection * connection,
260  const char *url,
261  const char *method,
262  const char *version,
263  const char *upload_data,
264  size_t *upload_data_size,
265  void **session_data)
266 {
267  std::string surl = url;
268  static int dummy;
269  int ret;
270 
271  if ((0 != strcmp(method, "GET")) && (0 != strcmp(method, "POST")))
272  return MHD_NO; /* unexpected method */
273 
274  MutexLocker lock(__url_manager->mutex());
275  WebRequestProcessor *proc = __url_manager->find_processor(surl);
276 
277  if (proc) {
278  char *urlc = strdup(url);
279  fawkes::hex_unescape(urlc);
280  std::string urls = urlc;
281  free(urlc);
282 
283  if (! proc->handles_session_data()) {
284  if ( *session_data == NULL) {
285  // The first time only the headers are valid,
286  // do not respond in the first round...
287  *session_data = &dummy;
288  return MHD_YES;
289  }
290  *session_data = NULL; /* clear context pointer */
291  } else {
292  if ( *session_data == NULL) {
293  WebReply *reply = proc->process_request(urls.c_str(), method, version,
294  upload_data, upload_data_size,
295  session_data);
296  if ((reply != NULL) || (*session_data == NULL)) {
297  return MHD_NO;
298  } else {
299  return MHD_YES;
300  }
301  }
302  }
303 
304 #if MHD_VERSION >= 0x00090400
305  if (__realm) {
306  char *user, *pass = NULL;
307  user = MHD_basic_auth_get_username_password(connection, &pass);
308  if ( (user == NULL) || (pass == NULL) ||
309  ! __user_verifier->verify_user(user, pass))
310  {
311  return queue_basic_auth_fail(connection);
312  }
313  }
314 #endif
315 
316  WebReply *reply = proc->process_request(urls.c_str(), method, version,
317  upload_data, upload_data_size,
318  session_data);
319  if ( reply ) {
320  StaticWebReply *sreply = dynamic_cast<StaticWebReply *>(reply);
321  DynamicWebReply *dreply = dynamic_cast<DynamicWebReply *>(reply);
322  if (sreply) {
323  ret = queue_static_reply(connection, sreply);
324  delete reply;
325  } else if (dreply) {
326  struct MHD_Response *response;
327  response = MHD_create_response_from_callback(dreply->size(),
328  dreply->chunk_size(),
329  dynamic_reply_data_cb,
330  dreply,
332  ret = MHD_queue_response (connection, dreply->code(), response);
333  MHD_destroy_response (response);
334  } else {
335  WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR);
336  ret = queue_static_reply(connection, &ereply);
337  delete reply;
338  }
339  } else {
340  if (proc->handles_session_data()) {
341  return MHD_YES;
342  } else {
343  WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
344  ret = queue_static_reply(connection, &ereply);
345  }
346  }
347  } else {
348  if (surl == "/") {
349  WebPageReply preply("Fawkes", "<h1>Welcome to Fawkes.</h1><hr />");
350  ret = queue_static_reply(connection, &preply);
351  } else {
352  WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
353  ret = queue_static_reply(connection, &ereply);
354  }
355  }
356  return ret;
357 }
358 
359 } // end namespace fawkes
Web request dispatcher.
virtual size_t next_chunk(size_t pos, char *buffer, size_t buf_max_size)=0
Get data of next chunk.
static int DynamicWebReply * dreply
Callback based chunk-wise data.
static void dynamic_reply_free_cb(void *reply)
Callback to free dynamic web reply.
Interface for user verification.
Definition: user_verifier.h:31
Interface for HTML header generator.
Manage URL mappings.
Definition: url_manager.h:37
Dynamic web reply.
Definition: reply.h:115
Base class for exceptions in Fawkes.
Definition: exception.h:36
Interface for HTML footer generator.
void hex_unescape(char *s)
Remove URL hex escapes from s in place.