Fawkes API  Fawkes Development Version
request_dispatcher.cpp
00001 
00002 /***************************************************************************
00003  *  request_dispatcher.cpp - Web request dispatcher
00004  *
00005  *  Created: Mon Oct 13 22:48:04 2008
00006  *  Copyright  2006-2010  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include <webview/request_dispatcher.h>
00024 #include <webview/request_processor.h>
00025 #include <webview/url_manager.h>
00026 #include <webview/page_reply.h>
00027 #include <webview/error_reply.h>
00028 #include <webview/user_verifier.h>
00029 
00030 #include <core/threading/mutex_locker.h>
00031 #include <core/exception.h>
00032 #include <utils/misc/string_urlescape.h>
00033 
00034 #include <sys/types.h>
00035 #include <sys/socket.h>
00036 #include <cstdarg>
00037 #include <microhttpd.h>
00038 #include <cstring>
00039 #include <cstdlib>
00040 
00041 #define UNAUTHORIZED_REPLY                                              \
00042   "<html>\n"                                                            \
00043   " <head><title>Access denied</title></head>\n"                        \
00044   " <body>\n"                                                           \
00045   "  <h1>Access denied</h1>\n"                                  \
00046   "  <p>Authentication is required to access Fawkes Webview</p>\n"      \
00047   " </body>\n"                                                          \
00048   "</html>"
00049 
00050 namespace fawkes {
00051 #if 0 /* just to make Emacs auto-indent happy */
00052 }
00053 #endif
00054 
00055 /** @class WebRequestDispatcher "request_dispatcher.h"
00056  * Web request dispatcher.
00057  * Takes web request received via a webserver run by libmicrohttpd and dispatches
00058  * pages to registered WebRequestProcessor instances or gives a 404 error if no
00059  * processor was registered for the given base url.
00060  * @author Tim Niemueller
00061  */
00062 
00063 /** Constructor.
00064  * @param url_manager URL manager to use for URL to processor mapping
00065  * @param headergen page header generator
00066  * @param footergen page footer generator
00067  */
00068 WebRequestDispatcher::WebRequestDispatcher(WebUrlManager *url_manager,
00069                                            WebPageHeaderGenerator *headergen,
00070                                            WebPageFooterGenerator *footergen)
00071 {
00072   __realm                 = NULL;
00073   __url_manager           = url_manager;
00074   __page_header_generator = headergen;
00075   __page_footer_generator = footergen;
00076 }
00077 
00078 
00079 /** Destructor. */
00080 WebRequestDispatcher::~WebRequestDispatcher()
00081 {
00082   if (__realm)  free(__realm);
00083 }
00084 
00085 
00086 /** Setup basic authentication.
00087  * @param realm authentication realm to display to the user.
00088  * If NULL basic authentication will be disabled.
00089  * @param verifier verifier to use for checking credentials.
00090  * If NULL basic authentication will be disabled.
00091  */
00092 void
00093 WebRequestDispatcher::setup_basic_auth(const char *realm,
00094                                        WebUserVerifier *verifier)
00095 {
00096 #if MHD_VERSION >= 0x00090400
00097   if (__realm)  free(__realm);
00098   __realm = NULL;
00099   __user_verifier = NULL;
00100   if (realm && verifier) {
00101     __realm = strdup(realm);
00102     __user_verifier = verifier;
00103   }
00104 #else
00105   throw Exception("libmicrohttpd >= 0.9.4 is required for basic authentication, "
00106                   "which was not available at compile time.");
00107 #endif
00108 }
00109 
00110 /** Process request callback for libmicrohttpd.
00111  * @param callback_data instance of WebRequestDispatcher to call
00112  * @param connection libmicrohttpd connection instance
00113  * @param url URL, may contain escape sequences
00114  * @param method HTTP method
00115  * @param version HTTP version
00116  * @param upload_data uploaded data
00117  * @param upload_data_size size of upload_data parameter
00118  * @param session_data session data pointer
00119  * @return appropriate return code for libmicrohttpd
00120  */
00121 int
00122 WebRequestDispatcher::process_request_cb(void *callback_data,
00123                                          struct MHD_Connection * connection,
00124                                          const char *url,
00125                                          const char *method,
00126                                          const char *version,
00127                                          const char *upload_data,
00128                                          size_t *upload_data_size,
00129                                          void **session_data)
00130 {
00131   WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(callback_data);
00132   return rd->process_request(connection, url, method, version,
00133                              upload_data, upload_data_size, session_data);
00134 }
00135 
00136 
00137 /** Callback based chunk-wise data.
00138  * Supplies data chunk based.
00139  * @param reply instance of DynamicWebReply
00140  * @param pos position in stream
00141  * @param buf buffer to put data in
00142  * @param max maximum number of bytes that can be put in buf
00143  * @return suitable libmicrohttpd return code
00144  */
00145 #if MHD_VERSION >= 0x00090200
00146 static ssize_t
00147 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, size_t max)
00148 #else
00149 static int
00150 #  if MHD_VERSION <= 0x00040000
00151 dynamic_reply_data_cb(void *reply, size_t pos, char *buf, int max)
00152 #  else
00153 dynamic_reply_data_cb(void *reply, uint64_t pos, char *buf, int max)
00154 #  endif
00155 #endif
00156 {
00157   DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
00158   return dreply->next_chunk(pos, buf, max);
00159 }
00160 
00161 
00162 /** Callback to free dynamic web reply.
00163  * @param reply Instance of DynamicWebReply to free.
00164  */
00165 static void
00166 dynamic_reply_free_cb(void *reply)
00167 {
00168   DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
00169   delete dreply;
00170 }
00171 
00172 
00173 /** Prepare response from static reply.
00174  * @param sreply static reply
00175  * @return response struct ready to be enqueued
00176  */
00177 struct MHD_Response *
00178 WebRequestDispatcher::prepare_static_response(StaticWebReply *sreply)
00179 {
00180   struct MHD_Response *response;
00181   WebPageReply *wpreply = dynamic_cast<WebPageReply *>(sreply);
00182   if (wpreply) {
00183     wpreply->pack(__active_baseurl,
00184                   __page_header_generator, __page_footer_generator);
00185   } else {
00186     sreply->pack();
00187   }
00188   if (sreply->body_length() > 0) {
00189     response = MHD_create_response_from_data(sreply->body_length(),
00190                                              (void*) sreply->body().c_str(),
00191                                              /* free */ MHD_YES,
00192                                              /* copy */ MHD_YES);
00193   } else {
00194     response = MHD_create_response_from_data(0, (void*) "",
00195                                              /* free */ MHD_NO,
00196                                              /* copy */ MHD_NO);
00197   }
00198 
00199   const WebReply::HeaderMap &headers = sreply->headers();
00200   WebReply::HeaderMap::const_iterator i;
00201   for (i = headers.begin(); i != headers.end(); ++i) {
00202     MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
00203   }
00204 
00205   return response;
00206 }
00207 
00208 /** Queue a static web reply.
00209  * @param connection libmicrohttpd connection to queue response to
00210  * @param sreply static web reply to queue
00211  * @return suitable libmicrohttpd return code
00212  */
00213 int
00214 WebRequestDispatcher::queue_static_reply(struct MHD_Connection * connection,
00215                                          StaticWebReply *sreply)
00216 {
00217   struct MHD_Response *response = prepare_static_response(sreply);
00218 
00219   int rv = MHD_queue_response(connection, sreply->code(), response);
00220   MHD_destroy_response(response);
00221   return rv;
00222 }
00223 
00224 
00225 /** Queue a static web reply after basic authentication failure.
00226  * @param connection libmicrohttpd connection to queue response to
00227  * @return suitable libmicrohttpd return code
00228  */
00229 int
00230 WebRequestDispatcher::queue_basic_auth_fail(struct MHD_Connection * connection)
00231 {
00232   StaticWebReply sreply(WebReply::HTTP_UNAUTHORIZED, UNAUTHORIZED_REPLY);
00233 #if MHD_VERSION >= 0x00090400
00234   struct MHD_Response *response = prepare_static_response(&sreply);
00235 
00236   int rv = MHD_queue_basic_auth_fail_response(connection, __realm, response);
00237   MHD_destroy_response(response);
00238 #else
00239   sreply.add_header(MHD_HTTP_HEADER_WWW_AUTHENTICATE,
00240                     (std::string("Basic realm=") + __realm).c_str());
00241   
00242   int rv = queue_static_reply(connection, &sreply);
00243 #endif
00244   return rv;
00245 }
00246 
00247 
00248 /** Process request callback for libmicrohttpd.
00249  * @param connection libmicrohttpd connection instance
00250  * @param url URL, may contain escape sequences
00251  * @param method HTTP method
00252  * @param version HTTP version
00253  * @param upload_data uploaded data
00254  * @param upload_data_size size of upload_data parameter
00255  * @param session_data session data pointer
00256  * @return appropriate return code for libmicrohttpd
00257  */
00258 int
00259 WebRequestDispatcher::process_request(struct MHD_Connection * connection,
00260                                       const char *url,
00261                                       const char *method,
00262                                       const char *version,
00263                                       const char *upload_data,
00264                                       size_t *upload_data_size,
00265                                       void **session_data)
00266 {
00267   std::string surl = url;
00268   static int dummy;
00269   int ret;
00270 
00271   if ((0 != strcmp(method, "GET")) && (0 != strcmp(method, "POST")))
00272     return MHD_NO; /* unexpected method */
00273 
00274   MutexLocker lock(__url_manager->mutex());
00275   WebRequestProcessor *proc = __url_manager->find_processor(surl);
00276 
00277   if (proc) {
00278     char *urlc = strdup(url);
00279     fawkes::hex_unescape(urlc);
00280     std::string urls = urlc;
00281     free(urlc);
00282 
00283     if (! proc->handles_session_data()) {
00284       if ( *session_data == NULL) {
00285         // The first time only the headers are valid,
00286         // do not respond in the first round...
00287         *session_data = &dummy;
00288         return MHD_YES;
00289       }
00290       *session_data = NULL; /* clear context pointer */
00291     } else {
00292       if ( *session_data == NULL) {
00293         WebReply *reply = proc->process_request(urls.c_str(), method, version,
00294                                                 upload_data, upload_data_size,
00295                                                 session_data);
00296         if ((reply != NULL) || (*session_data == NULL)) {
00297           return MHD_NO;
00298         } else {
00299           return MHD_YES;
00300         }
00301       }
00302     }
00303 
00304 #if MHD_VERSION >= 0x00090400
00305     if (__realm) {
00306       char *user, *pass = NULL;
00307       user = MHD_basic_auth_get_username_password(connection, &pass);
00308       if ( (user == NULL) || (pass == NULL) ||
00309            ! __user_verifier->verify_user(user, pass))
00310       {
00311         return queue_basic_auth_fail(connection);
00312       }
00313     }
00314 #endif
00315 
00316     WebReply *reply = proc->process_request(urls.c_str(), method, version,
00317                                             upload_data, upload_data_size,
00318                                             session_data);
00319     if ( reply ) {
00320       StaticWebReply  *sreply = dynamic_cast<StaticWebReply *>(reply);
00321       DynamicWebReply *dreply = dynamic_cast<DynamicWebReply *>(reply);
00322       if (sreply) {
00323         ret = queue_static_reply(connection, sreply);
00324         delete reply;
00325       } else if (dreply) {
00326         struct MHD_Response *response;
00327         response = MHD_create_response_from_callback(dreply->size(),
00328                                                      dreply->chunk_size(),
00329                                                      dynamic_reply_data_cb,
00330                                                      dreply,
00331                                                      dynamic_reply_free_cb);
00332         ret = MHD_queue_response (connection, dreply->code(), response);
00333         MHD_destroy_response (response);
00334       } else {
00335         WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR);
00336         ret = queue_static_reply(connection, &ereply);
00337         delete reply;
00338       }
00339     } else {
00340       if (proc->handles_session_data()) {
00341         return MHD_YES;
00342       } else {
00343         WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
00344         ret = queue_static_reply(connection, &ereply);
00345       }
00346     }
00347   } else {
00348     if (surl == "/") {
00349       WebPageReply preply("Fawkes", "<h1>Welcome to Fawkes.</h1><hr />");
00350       ret = queue_static_reply(connection, &preply);
00351     } else {
00352       WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
00353       ret = queue_static_reply(connection, &ereply);
00354     }
00355   }
00356   return ret;
00357 }
00358 
00359 } // end namespace fawkes