Merge feature branch 'feature-auto-update'

Change-Id: I8949b3bb24d853e486b00a82b0e3a63d0d5cf1e2
diff --git a/gui/chronosharegui.cpp b/gui/chronosharegui.cpp
index 1bd0c1c..8d811e8 100644
--- a/gui/chronosharegui.cpp
+++ b/gui/chronosharegui.cpp
@@ -26,18 +26,26 @@
 #include "logging.h"
 #include "ccnx-wrapper.h"
 #include <QValidator>
+#include <QDir>
+#include <QDesktopServices>
 
 #include <boost/make_shared.hpp>
 
 using namespace boost;
 using namespace Ccnx;
 
+static const string HTTP_SERVER_ADDRESS = "localhost";
+static const string HTTP_SERVER_PORT = "9001";
+static const string DOC_ROOT = ":/html";
+static const QString ICON_PICTURE_QSTRING(":/images/friends-group-icon.png");
+
 INIT_LOGGER ("Gui");
 
 ChronoShareGui::ChronoShareGui(QWidget *parent)
   : QDialog(parent)
   , m_watcher(0)
   , m_dispatcher(0)
+  , m_httpServer(0)
 #ifdef ADHOC_SUPPORTED
   , m_executor (1)
 #endif
@@ -133,6 +141,17 @@
   m_watcher = new FsWatcher (m_dirPath,
                              bind (&Dispatcher::Did_LocalFile_AddOrModify, m_dispatcher, _1),
                              bind (&Dispatcher::Did_LocalFile_Delete,      m_dispatcher, _1));
+  QFileInfo indexHtmlInfo(":/html/index.html");
+  if (indexHtmlInfo.exists())
+  {
+    m_httpServer = new http::server::server(HTTP_SERVER_ADDRESS, HTTP_SERVER_PORT, DOC_ROOT);
+    m_httpServerThread = boost::thread(&http::server::server::run, m_httpServer);
+  }
+  else
+  {
+    _LOG_ERROR ("Http server doc root dir does not exist!");
+    // shall we bail here?
+  }
 }
 
 ChronoShareGui::~ChronoShareGui()
@@ -143,6 +162,7 @@
 
   delete m_watcher; // stop filewatching ASAP
   delete m_dispatcher; // stop dispatcher ASAP, but after watcher (to prevent triggering callbacks on deleted object)
+  delete m_httpServer;
 
   // cleanup
   delete m_trayIcon;
@@ -174,7 +194,7 @@
   messageBox.setWindowTitle(title);
   messageBox.setText(text);
 
-  messageBox.setIconPixmap(QPixmap(":/images/friends-group-icon.png"));
+  messageBox.setIconPixmap(QPixmap(ICON_PICTURE_QSTRING));
 
   messageBox.exec();
 }
@@ -186,7 +206,7 @@
   messageBox.setText(text);
   messageBox.setInformativeText(infotext);
 
-  messageBox.setIconPixmap(QPixmap(":/images/friends-group-icon.png"));
+  messageBox.setIconPixmap(QPixmap(ICON_PICTURE_QSTRING));
 
   messageBox.exec();
 }
@@ -302,31 +322,13 @@
 void ChronoShareGui::setIcon()
 {
   // set the icon image
-  m_trayIcon->setIcon(QIcon(":/images/friends-group-icon.png"));
+  m_trayIcon->setIcon(QIcon(ICON_PICTURE_QSTRING));
 }
 
 void ChronoShareGui::openSharedFolder()
 {
-  // Alex: isn't there an OS-independent way in QT for this?
-
-  // tell Finder to open the shared folder
-  QStringList scriptArgs;
-  scriptArgs << QLatin1String("-e")
-             << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
-    .arg(m_dirPath);
-
-  // execute the commands to make it happen
-  QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
-
-  // clear command arguments
-  scriptArgs.clear();
-
-  // tell Finder to appear
-  scriptArgs << QLatin1String("-e")
-             << QLatin1String("tell application \"Finder\" to activate");
-
-  // execute the commands to make it happen
-  QProcess::execute("/usr/bin/osascript", scriptArgs);
+  QString path = QDir::toNativeSeparators(m_dirPath);
+  QDesktopServices::openUrl(QUrl("file:///" + path));
 }
 
 void ChronoShareGui::changeSettings()
diff --git a/gui/chronosharegui.h b/gui/chronosharegui.h
index e0db4ba..c094f3e 100644
--- a/gui/chronosharegui.h
+++ b/gui/chronosharegui.h
@@ -45,6 +45,8 @@
 
 #include "fs-watcher.h"
 #include "dispatcher.h"
+#include "server.hpp"
+#include <boost/thread/thread.hpp>
 
 class ChronoShareGui : public QDialog
 {
@@ -126,6 +128,8 @@
 
   FsWatcher  *m_watcher;
   Dispatcher *m_dispatcher;
+  http::server::server *m_httpServer;
+  boost::thread m_httpServerThread;
 
   QLabel* labelUsername;
   QPushButton* button;
diff --git a/gui/html.qrc b/gui/html.qrc
new file mode 100644
index 0000000..630a137
--- /dev/null
+++ b/gui/html.qrc
@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file>html/index.html</file>
+    </qresource>
+</RCC>
diff --git a/gui/html/index.html b/gui/html/index.html
new file mode 100644
index 0000000..f372d2e
--- /dev/null
+++ b/gui/html/index.html
@@ -0,0 +1,3 @@
+<html>
+<h1>It works!</h1>
+</html>
diff --git a/gui/images.qrc b/gui/images.qrc
index 15d4acf..29a8104 100644
--- a/gui/images.qrc
+++ b/gui/images.qrc
@@ -1,5 +1,5 @@
 <RCC>
-    <qresource prefix="/images">
-        <file>friends-group-icon.png</file>
+    <qresource prefix="/">
+        <file>images/friends-group-icon.png</file>
     </qresource>
 </RCC>
diff --git a/gui/friends-group-icon.png b/gui/images/friends-group-icon.png
similarity index 100%
rename from gui/friends-group-icon.png
rename to gui/images/friends-group-icon.png
Binary files differ
diff --git a/server/connection.cpp b/server/connection.cpp
new file mode 100644
index 0000000..e379043
--- /dev/null
+++ b/server/connection.cpp
@@ -0,0 +1,99 @@
+//
+// connection.cpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "connection.hpp"
+#include <vector>
+#include <boost/bind.hpp>
+#include "connection_manager.hpp"
+#include "request_handler.hpp"
+
+namespace http {
+namespace server {
+
+connection::connection(boost::asio::io_service& io_service,
+    connection_manager& manager, request_handler& handler)
+  : socket_(io_service),
+    connection_manager_(manager),
+    request_handler_(handler)
+{
+}
+
+boost::asio::ip::tcp::socket& connection::socket()
+{
+  return socket_;
+}
+
+void connection::start()
+{
+  socket_.async_read_some(boost::asio::buffer(buffer_),
+      boost::bind(&connection::handle_read, shared_from_this(),
+        boost::asio::placeholders::error,
+        boost::asio::placeholders::bytes_transferred));
+}
+
+void connection::stop()
+{
+  socket_.close();
+}
+
+void connection::handle_read(const boost::system::error_code& e,
+    std::size_t bytes_transferred)
+{
+  if (!e)
+  {
+    boost::tribool result;
+    boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
+        request_, buffer_.data(), buffer_.data() + bytes_transferred);
+
+    if (result)
+    {
+      request_handler_.handle_request(request_, reply_);
+      boost::asio::async_write(socket_, reply_.to_buffers(),
+          boost::bind(&connection::handle_write, shared_from_this(),
+            boost::asio::placeholders::error));
+    }
+    else if (!result)
+    {
+      reply_ = reply::stock_reply(reply::bad_request);
+      boost::asio::async_write(socket_, reply_.to_buffers(),
+          boost::bind(&connection::handle_write, shared_from_this(),
+            boost::asio::placeholders::error));
+    }
+    else
+    {
+      socket_.async_read_some(boost::asio::buffer(buffer_),
+          boost::bind(&connection::handle_read, shared_from_this(),
+            boost::asio::placeholders::error,
+            boost::asio::placeholders::bytes_transferred));
+    }
+  }
+  else if (e != boost::asio::error::operation_aborted)
+  {
+    connection_manager_.stop(shared_from_this());
+  }
+}
+
+void connection::handle_write(const boost::system::error_code& e)
+{
+  if (!e)
+  {
+    // Initiate graceful connection closure.
+    boost::system::error_code ignored_ec;
+    socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
+  }
+
+  if (e != boost::asio::error::operation_aborted)
+  {
+    connection_manager_.stop(shared_from_this());
+  }
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/connection.hpp b/server/connection.hpp
new file mode 100644
index 0000000..35d3a1c
--- /dev/null
+++ b/server/connection.hpp
@@ -0,0 +1,83 @@
+//
+// connection.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_CONNECTION_HPP
+#define HTTP_CONNECTION_HPP
+
+#include <boost/asio.hpp>
+#include <boost/array.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "reply.hpp"
+#include "request.hpp"
+#include "request_handler.hpp"
+#include "request_parser.hpp"
+
+namespace http {
+namespace server {
+
+class connection_manager;
+
+/// Represents a single connection from a client.
+class connection
+  : public boost::enable_shared_from_this<connection>,
+    private boost::noncopyable
+{
+public:
+  /// Construct a connection with the given io_service.
+  explicit connection(boost::asio::io_service& io_service,
+      connection_manager& manager, request_handler& handler);
+
+  /// Get the socket associated with the connection.
+  boost::asio::ip::tcp::socket& socket();
+
+  /// Start the first asynchronous operation for the connection.
+  void start();
+
+  /// Stop all asynchronous operations associated with the connection.
+  void stop();
+
+private:
+  /// Handle completion of a read operation.
+  void handle_read(const boost::system::error_code& e,
+      std::size_t bytes_transferred);
+
+  /// Handle completion of a write operation.
+  void handle_write(const boost::system::error_code& e);
+
+  /// Socket for the connection.
+  boost::asio::ip::tcp::socket socket_;
+
+  /// The manager for this connection.
+  connection_manager& connection_manager_;
+
+  /// The handler used to process the incoming request.
+  request_handler& request_handler_;
+
+  /// Buffer for incoming data.
+  boost::array<char, 8192> buffer_;
+
+  /// The incoming request.
+  request request_;
+
+  /// The parser for the incoming request.
+  request_parser request_parser_;
+
+  /// The reply to be sent back to the client.
+  reply reply_;
+};
+
+typedef boost::shared_ptr<connection> connection_ptr;
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_CONNECTION_HPP
diff --git a/server/connection_manager.cpp b/server/connection_manager.cpp
new file mode 100644
index 0000000..664d444
--- /dev/null
+++ b/server/connection_manager.cpp
@@ -0,0 +1,38 @@
+//
+// connection_manager.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "connection_manager.hpp"
+#include <algorithm>
+#include <boost/bind.hpp>
+
+namespace http {
+namespace server {
+
+void connection_manager::start(connection_ptr c)
+{
+  connections_.insert(c);
+  c->start();
+}
+
+void connection_manager::stop(connection_ptr c)
+{
+  connections_.erase(c);
+  c->stop();
+}
+
+void connection_manager::stop_all()
+{
+  std::for_each(connections_.begin(), connections_.end(),
+      boost::bind(&connection::stop, _1));
+  connections_.clear();
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/connection_manager.hpp b/server/connection_manager.hpp
new file mode 100644
index 0000000..f49ab64
--- /dev/null
+++ b/server/connection_manager.hpp
@@ -0,0 +1,44 @@
+//
+// connection_manager.hpp
+// ~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_CONNECTION_MANAGER_HPP
+#define HTTP_CONNECTION_MANAGER_HPP
+
+#include <set>
+#include <boost/noncopyable.hpp>
+#include "connection.hpp"
+
+namespace http {
+namespace server {
+
+/// Manages open connections so that they may be cleanly stopped when the server
+/// needs to shut down.
+class connection_manager
+  : private boost::noncopyable
+{
+public:
+  /// Add the specified connection to the manager and start it.
+  void start(connection_ptr c);
+
+  /// Stop the specified connection.
+  void stop(connection_ptr c);
+
+  /// Stop all connections.
+  void stop_all();
+
+private:
+  /// The managed connections.
+  std::set<connection_ptr> connections_;
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_CONNECTION_MANAGER_HPP
diff --git a/server/header.hpp b/server/header.hpp
new file mode 100644
index 0000000..85bf301
--- /dev/null
+++ b/server/header.hpp
@@ -0,0 +1,28 @@
+//
+// header.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_HEADER_HPP
+#define HTTP_HEADER_HPP
+
+#include <string>
+
+namespace http {
+namespace server {
+
+struct header
+{
+  std::string name;
+  std::string value;
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_HEADER_HPP
diff --git a/server/mime_types.cpp b/server/mime_types.cpp
new file mode 100644
index 0000000..d2898b2
--- /dev/null
+++ b/server/mime_types.cpp
@@ -0,0 +1,46 @@
+//
+// mime_types.cpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "mime_types.hpp"
+
+namespace http {
+namespace server {
+namespace mime_types {
+
+struct mapping
+{
+  const char* extension;
+  const char* mime_type;
+} mappings[] =
+{
+  { "gif", "image/gif" },
+  { "htm", "text/html" },
+  { "html", "text/html" },
+  { "jpg", "image/jpeg" },
+  { "png", "image/png" },
+  { 0, 0 } // Marks end of list.
+};
+
+std::string extension_to_type(const std::string& extension)
+{
+  for (mapping* m = mappings; m->extension; ++m)
+  {
+    if (m->extension == extension)
+    {
+      return m->mime_type;
+    }
+  }
+
+  return "text/plain";
+}
+
+} // namespace mime_types
+} // namespace server
+} // namespace http
diff --git a/server/mime_types.hpp b/server/mime_types.hpp
new file mode 100644
index 0000000..ab55291
--- /dev/null
+++ b/server/mime_types.hpp
@@ -0,0 +1,27 @@
+//
+// mime_types.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_MIME_TYPES_HPP
+#define HTTP_MIME_TYPES_HPP
+
+#include <string>
+
+namespace http {
+namespace server {
+namespace mime_types {
+
+/// Convert a file extension into a MIME type.
+std::string extension_to_type(const std::string& extension);
+
+} // namespace mime_types
+} // namespace server
+} // namespace http
+
+#endif // HTTP_MIME_TYPES_HPP
diff --git a/server/reply.cpp b/server/reply.cpp
new file mode 100644
index 0000000..044951d
--- /dev/null
+++ b/server/reply.cpp
@@ -0,0 +1,256 @@
+//
+// reply.cpp
+// ~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "reply.hpp"
+#include <string>
+#include <boost/lexical_cast.hpp>
+
+namespace http {
+namespace server {
+
+namespace status_strings {
+
+const std::string ok =
+  "HTTP/1.0 200 OK\r\n";
+const std::string created =
+  "HTTP/1.0 201 Created\r\n";
+const std::string accepted =
+  "HTTP/1.0 202 Accepted\r\n";
+const std::string no_content =
+  "HTTP/1.0 204 No Content\r\n";
+const std::string multiple_choices =
+  "HTTP/1.0 300 Multiple Choices\r\n";
+const std::string moved_permanently =
+  "HTTP/1.0 301 Moved Permanently\r\n";
+const std::string moved_temporarily =
+  "HTTP/1.0 302 Moved Temporarily\r\n";
+const std::string not_modified =
+  "HTTP/1.0 304 Not Modified\r\n";
+const std::string bad_request =
+  "HTTP/1.0 400 Bad Request\r\n";
+const std::string unauthorized =
+  "HTTP/1.0 401 Unauthorized\r\n";
+const std::string forbidden =
+  "HTTP/1.0 403 Forbidden\r\n";
+const std::string not_found =
+  "HTTP/1.0 404 Not Found\r\n";
+const std::string internal_server_error =
+  "HTTP/1.0 500 Internal Server Error\r\n";
+const std::string not_implemented =
+  "HTTP/1.0 501 Not Implemented\r\n";
+const std::string bad_gateway =
+  "HTTP/1.0 502 Bad Gateway\r\n";
+const std::string service_unavailable =
+  "HTTP/1.0 503 Service Unavailable\r\n";
+
+boost::asio::const_buffer to_buffer(reply::status_type status)
+{
+  switch (status)
+  {
+  case reply::ok:
+    return boost::asio::buffer(ok);
+  case reply::created:
+    return boost::asio::buffer(created);
+  case reply::accepted:
+    return boost::asio::buffer(accepted);
+  case reply::no_content:
+    return boost::asio::buffer(no_content);
+  case reply::multiple_choices:
+    return boost::asio::buffer(multiple_choices);
+  case reply::moved_permanently:
+    return boost::asio::buffer(moved_permanently);
+  case reply::moved_temporarily:
+    return boost::asio::buffer(moved_temporarily);
+  case reply::not_modified:
+    return boost::asio::buffer(not_modified);
+  case reply::bad_request:
+    return boost::asio::buffer(bad_request);
+  case reply::unauthorized:
+    return boost::asio::buffer(unauthorized);
+  case reply::forbidden:
+    return boost::asio::buffer(forbidden);
+  case reply::not_found:
+    return boost::asio::buffer(not_found);
+  case reply::internal_server_error:
+    return boost::asio::buffer(internal_server_error);
+  case reply::not_implemented:
+    return boost::asio::buffer(not_implemented);
+  case reply::bad_gateway:
+    return boost::asio::buffer(bad_gateway);
+  case reply::service_unavailable:
+    return boost::asio::buffer(service_unavailable);
+  default:
+    return boost::asio::buffer(internal_server_error);
+  }
+}
+
+} // namespace status_strings
+
+namespace misc_strings {
+
+const char name_value_separator[] = { ':', ' ' };
+const char crlf[] = { '\r', '\n' };
+
+} // namespace misc_strings
+
+std::vector<boost::asio::const_buffer> reply::to_buffers()
+{
+  std::vector<boost::asio::const_buffer> buffers;
+  buffers.push_back(status_strings::to_buffer(status));
+  for (std::size_t i = 0; i < headers.size(); ++i)
+  {
+    header& h = headers[i];
+    buffers.push_back(boost::asio::buffer(h.name));
+    buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
+    buffers.push_back(boost::asio::buffer(h.value));
+    buffers.push_back(boost::asio::buffer(misc_strings::crlf));
+  }
+  buffers.push_back(boost::asio::buffer(misc_strings::crlf));
+  buffers.push_back(boost::asio::buffer(content));
+  return buffers;
+}
+
+namespace stock_replies {
+
+const char ok[] = "";
+const char created[] =
+  "<html>"
+  "<head><title>Created</title></head>"
+  "<body><h1>201 Created</h1></body>"
+  "</html>";
+const char accepted[] =
+  "<html>"
+  "<head><title>Accepted</title></head>"
+  "<body><h1>202 Accepted</h1></body>"
+  "</html>";
+const char no_content[] =
+  "<html>"
+  "<head><title>No Content</title></head>"
+  "<body><h1>204 Content</h1></body>"
+  "</html>";
+const char multiple_choices[] =
+  "<html>"
+  "<head><title>Multiple Choices</title></head>"
+  "<body><h1>300 Multiple Choices</h1></body>"
+  "</html>";
+const char moved_permanently[] =
+  "<html>"
+  "<head><title>Moved Permanently</title></head>"
+  "<body><h1>301 Moved Permanently</h1></body>"
+  "</html>";
+const char moved_temporarily[] =
+  "<html>"
+  "<head><title>Moved Temporarily</title></head>"
+  "<body><h1>302 Moved Temporarily</h1></body>"
+  "</html>";
+const char not_modified[] =
+  "<html>"
+  "<head><title>Not Modified</title></head>"
+  "<body><h1>304 Not Modified</h1></body>"
+  "</html>";
+const char bad_request[] =
+  "<html>"
+  "<head><title>Bad Request</title></head>"
+  "<body><h1>400 Bad Request</h1></body>"
+  "</html>";
+const char unauthorized[] =
+  "<html>"
+  "<head><title>Unauthorized</title></head>"
+  "<body><h1>401 Unauthorized</h1></body>"
+  "</html>";
+const char forbidden[] =
+  "<html>"
+  "<head><title>Forbidden</title></head>"
+  "<body><h1>403 Forbidden</h1></body>"
+  "</html>";
+const char not_found[] =
+  "<html>"
+  "<head><title>Not Found</title></head>"
+  "<body><h1>404 Not Found</h1></body>"
+  "</html>";
+const char internal_server_error[] =
+  "<html>"
+  "<head><title>Internal Server Error</title></head>"
+  "<body><h1>500 Internal Server Error</h1></body>"
+  "</html>";
+const char not_implemented[] =
+  "<html>"
+  "<head><title>Not Implemented</title></head>"
+  "<body><h1>501 Not Implemented</h1></body>"
+  "</html>";
+const char bad_gateway[] =
+  "<html>"
+  "<head><title>Bad Gateway</title></head>"
+  "<body><h1>502 Bad Gateway</h1></body>"
+  "</html>";
+const char service_unavailable[] =
+  "<html>"
+  "<head><title>Service Unavailable</title></head>"
+  "<body><h1>503 Service Unavailable</h1></body>"
+  "</html>";
+
+std::string to_string(reply::status_type status)
+{
+  switch (status)
+  {
+  case reply::ok:
+    return ok;
+  case reply::created:
+    return created;
+  case reply::accepted:
+    return accepted;
+  case reply::no_content:
+    return no_content;
+  case reply::multiple_choices:
+    return multiple_choices;
+  case reply::moved_permanently:
+    return moved_permanently;
+  case reply::moved_temporarily:
+    return moved_temporarily;
+  case reply::not_modified:
+    return not_modified;
+  case reply::bad_request:
+    return bad_request;
+  case reply::unauthorized:
+    return unauthorized;
+  case reply::forbidden:
+    return forbidden;
+  case reply::not_found:
+    return not_found;
+  case reply::internal_server_error:
+    return internal_server_error;
+  case reply::not_implemented:
+    return not_implemented;
+  case reply::bad_gateway:
+    return bad_gateway;
+  case reply::service_unavailable:
+    return service_unavailable;
+  default:
+    return internal_server_error;
+  }
+}
+
+} // namespace stock_replies
+
+reply reply::stock_reply(reply::status_type status)
+{
+  reply rep;
+  rep.status = status;
+  rep.content = stock_replies::to_string(status);
+  rep.headers.resize(2);
+  rep.headers[0].name = "Content-Length";
+  rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
+  rep.headers[1].name = "Content-Type";
+  rep.headers[1].value = "text/html";
+  return rep;
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/reply.hpp b/server/reply.hpp
new file mode 100644
index 0000000..a0dab62
--- /dev/null
+++ b/server/reply.hpp
@@ -0,0 +1,64 @@
+//
+// reply.hpp
+// ~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_REPLY_HPP
+#define HTTP_REPLY_HPP
+
+#include <string>
+#include <vector>
+#include <boost/asio.hpp>
+#include "header.hpp"
+
+namespace http {
+namespace server {
+
+/// A reply to be sent to a client.
+struct reply
+{
+  /// The status of the reply.
+  enum status_type
+  {
+    ok = 200,
+    created = 201,
+    accepted = 202,
+    no_content = 204,
+    multiple_choices = 300,
+    moved_permanently = 301,
+    moved_temporarily = 302,
+    not_modified = 304,
+    bad_request = 400,
+    unauthorized = 401,
+    forbidden = 403,
+    not_found = 404,
+    internal_server_error = 500,
+    not_implemented = 501,
+    bad_gateway = 502,
+    service_unavailable = 503
+  } status;
+
+  /// The headers to be included in the reply.
+  std::vector<header> headers;
+
+  /// The content to be sent in the reply.
+  std::string content;
+
+  /// Convert the reply into a vector of buffers. The buffers do not own the
+  /// underlying memory blocks, therefore the reply object must remain valid and
+  /// not be changed until the write operation has completed.
+  std::vector<boost::asio::const_buffer> to_buffers();
+
+  /// Get a stock reply.
+  static reply stock_reply(status_type status);
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_REPLY_HPP
diff --git a/server/request.hpp b/server/request.hpp
new file mode 100644
index 0000000..3be1021
--- /dev/null
+++ b/server/request.hpp
@@ -0,0 +1,34 @@
+//
+// request.hpp
+// ~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_REQUEST_HPP
+#define HTTP_REQUEST_HPP
+
+#include <string>
+#include <vector>
+#include "header.hpp"
+
+namespace http {
+namespace server {
+
+/// A request received from a client.
+struct request
+{
+  std::string method;
+  std::string uri;
+  int http_version_major;
+  int http_version_minor;
+  std::vector<header> headers;
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_REQUEST_HPP
diff --git a/server/request_handler.cpp b/server/request_handler.cpp
new file mode 100644
index 0000000..d1d5061
--- /dev/null
+++ b/server/request_handler.cpp
@@ -0,0 +1,146 @@
+//
+// request_handler.cpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "request_handler.hpp"
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include "mime_types.hpp"
+#include "reply.hpp"
+#include "request.hpp"
+#include <QIODevice>
+#include <QFile>
+#include <QDataStream>
+#include <QString>
+#include "logging.h"
+
+INIT_LOGGER("HttpServer")
+
+namespace http {
+namespace server {
+
+request_handler::request_handler(const std::string& doc_root)
+  : doc_root_(doc_root.c_str())
+{
+}
+
+void request_handler::handle_request(const request& req, reply& rep)
+{
+  // Decode url to path.
+  std::string request_path;
+  if (!url_decode(req.uri, request_path))
+  {
+    rep = reply::stock_reply(reply::bad_request);
+    return;
+  }
+
+  // Request path must be absolute and not contain "..".
+  if (request_path.empty() || request_path[0] != '/'
+      || request_path.find("..") != std::string::npos)
+  {
+    rep = reply::stock_reply(reply::bad_request);
+    return;
+  }
+
+  // If path ends in slash (i.e. is a directory) then add "index.html".
+  if (request_path[request_path.size() - 1] == '/')
+  {
+    request_path += "index.html";
+  }
+
+  // Determine the file extension.
+  std::size_t last_slash_pos = request_path.find_last_of("/");
+  std::size_t last_dot_pos = request_path.find_last_of(".");
+  std::string extension;
+  if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
+  {
+    extension = request_path.substr(last_dot_pos + 1);
+  }
+
+  // Open the file to send back.
+  // The following is a hack to make the server understands Qt's
+  // resource system, so that the html resources can be managed using Qt's
+  // resource system (e.g. no need to worry about the location of html)
+  // in Mac OS, it will be inside the bundle, in Linux, perhaps somewhere
+  // in /usr/local/share
+  QString full_path = doc_root_.absolutePath() + QString(request_path.c_str());
+  QFile file(full_path);
+  if (!file.exists() || !file.open(QIODevice::ReadOnly))
+  {
+    rep = reply::stock_reply(reply::not_found);
+    return;
+  }
+
+  _LOG_DEBUG("Serving file: " << request_path);
+  // Fill out the reply to be sent to the client.
+  rep.status = reply::ok;
+  char buf[512];
+  QDataStream in(&file);
+  while (true)
+  {
+    int bytes = in.readRawData(buf, sizeof(buf));
+    if (bytes > 0)
+    {
+      rep.content.append(buf, bytes);
+    }
+    else
+    {
+      break;
+    }
+  }
+  rep.headers.resize(2);
+  rep.headers[0].name = "Content-Length";
+  rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
+  rep.headers[1].name = "Content-Type";
+  rep.headers[1].value = mime_types::extension_to_type(extension);
+}
+
+bool request_handler::url_decode(const std::string& in, std::string& out)
+{
+  out.clear();
+  out.reserve(in.size());
+  for (std::size_t i = 0; i < in.size(); ++i)
+  {
+    if (in[i] == '%')
+    {
+      if (i + 3 <= in.size())
+      {
+        int value = 0;
+        std::istringstream is(in.substr(i + 1, 2));
+        if (is >> std::hex >> value)
+        {
+          out += static_cast<char>(value);
+          i += 2;
+        }
+        else
+        {
+          return false;
+        }
+      }
+      else
+      {
+        return false;
+      }
+    }
+    else if (in[i] == '+')
+    {
+      out += ' ';
+    }
+    else
+    {
+      out += in[i];
+    }
+  }
+  return true;
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/request_handler.hpp b/server/request_handler.hpp
new file mode 100644
index 0000000..384907c
--- /dev/null
+++ b/server/request_handler.hpp
@@ -0,0 +1,47 @@
+//
+// request_handler.hpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_REQUEST_HANDLER_HPP
+#define HTTP_REQUEST_HANDLER_HPP
+
+#include <string>
+#include <boost/noncopyable.hpp>
+#include <QDir>
+
+namespace http {
+namespace server {
+
+struct reply;
+struct request;
+
+/// The common handler for all incoming requests.
+class request_handler
+  : private boost::noncopyable
+{
+public:
+  /// Construct with a directory containing files to be served.
+  explicit request_handler(const std::string& doc_root);
+
+  /// Handle a request and produce a reply.
+  void handle_request(const request& req, reply& rep);
+
+private:
+  /// The directory containing the files to be served.
+  QDir doc_root_;
+
+  /// Perform URL-decoding on a string. Returns false if the encoding was
+  /// invalid.
+  static bool url_decode(const std::string& in, std::string& out);
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_REQUEST_HANDLER_HPP
diff --git a/server/request_parser.cpp b/server/request_parser.cpp
new file mode 100644
index 0000000..b5bb9c2
--- /dev/null
+++ b/server/request_parser.cpp
@@ -0,0 +1,315 @@
+//
+// request_parser.cpp
+// ~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "request_parser.hpp"
+#include "request.hpp"
+
+namespace http {
+namespace server {
+
+request_parser::request_parser()
+  : state_(method_start)
+{
+}
+
+void request_parser::reset()
+{
+  state_ = method_start;
+}
+
+boost::tribool request_parser::consume(request& req, char input)
+{
+  switch (state_)
+  {
+  case method_start:
+    if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+    {
+      return false;
+    }
+    else
+    {
+      state_ = method;
+      req.method.push_back(input);
+      return boost::indeterminate;
+    }
+  case method:
+    if (input == ' ')
+    {
+      state_ = uri;
+      return boost::indeterminate;
+    }
+    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+    {
+      return false;
+    }
+    else
+    {
+      req.method.push_back(input);
+      return boost::indeterminate;
+    }
+  case uri:
+    if (input == ' ')
+    {
+      state_ = http_version_h;
+      return boost::indeterminate;
+    }
+    else if (is_ctl(input))
+    {
+      return false;
+    }
+    else
+    {
+      req.uri.push_back(input);
+      return boost::indeterminate;
+    }
+  case http_version_h:
+    if (input == 'H')
+    {
+      state_ = http_version_t_1;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_t_1:
+    if (input == 'T')
+    {
+      state_ = http_version_t_2;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_t_2:
+    if (input == 'T')
+    {
+      state_ = http_version_p;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_p:
+    if (input == 'P')
+    {
+      state_ = http_version_slash;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_slash:
+    if (input == '/')
+    {
+      req.http_version_major = 0;
+      req.http_version_minor = 0;
+      state_ = http_version_major_start;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_major_start:
+    if (is_digit(input))
+    {
+      req.http_version_major = req.http_version_major * 10 + input - '0';
+      state_ = http_version_major;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_major:
+    if (input == '.')
+    {
+      state_ = http_version_minor_start;
+      return boost::indeterminate;
+    }
+    else if (is_digit(input))
+    {
+      req.http_version_major = req.http_version_major * 10 + input - '0';
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_minor_start:
+    if (is_digit(input))
+    {
+      req.http_version_minor = req.http_version_minor * 10 + input - '0';
+      state_ = http_version_minor;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case http_version_minor:
+    if (input == '\r')
+    {
+      state_ = expecting_newline_1;
+      return boost::indeterminate;
+    }
+    else if (is_digit(input))
+    {
+      req.http_version_minor = req.http_version_minor * 10 + input - '0';
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case expecting_newline_1:
+    if (input == '\n')
+    {
+      state_ = header_line_start;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case header_line_start:
+    if (input == '\r')
+    {
+      state_ = expecting_newline_3;
+      return boost::indeterminate;
+    }
+    else if (!req.headers.empty() && (input == ' ' || input == '\t'))
+    {
+      state_ = header_lws;
+      return boost::indeterminate;
+    }
+    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+    {
+      return false;
+    }
+    else
+    {
+      req.headers.push_back(header());
+      req.headers.back().name.push_back(input);
+      state_ = header_name;
+      return boost::indeterminate;
+    }
+  case header_lws:
+    if (input == '\r')
+    {
+      state_ = expecting_newline_2;
+      return boost::indeterminate;
+    }
+    else if (input == ' ' || input == '\t')
+    {
+      return boost::indeterminate;
+    }
+    else if (is_ctl(input))
+    {
+      return false;
+    }
+    else
+    {
+      state_ = header_value;
+      req.headers.back().value.push_back(input);
+      return boost::indeterminate;
+    }
+  case header_name:
+    if (input == ':')
+    {
+      state_ = space_before_header_value;
+      return boost::indeterminate;
+    }
+    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+    {
+      return false;
+    }
+    else
+    {
+      req.headers.back().name.push_back(input);
+      return boost::indeterminate;
+    }
+  case space_before_header_value:
+    if (input == ' ')
+    {
+      state_ = header_value;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case header_value:
+    if (input == '\r')
+    {
+      state_ = expecting_newline_2;
+      return boost::indeterminate;
+    }
+    else if (is_ctl(input))
+    {
+      return false;
+    }
+    else
+    {
+      req.headers.back().value.push_back(input);
+      return boost::indeterminate;
+    }
+  case expecting_newline_2:
+    if (input == '\n')
+    {
+      state_ = header_line_start;
+      return boost::indeterminate;
+    }
+    else
+    {
+      return false;
+    }
+  case expecting_newline_3:
+    return (input == '\n');
+  default:
+    return false;
+  }
+}
+
+bool request_parser::is_char(int c)
+{
+  return c >= 0 && c <= 127;
+}
+
+bool request_parser::is_ctl(int c)
+{
+  return (c >= 0 && c <= 31) || (c == 127);
+}
+
+bool request_parser::is_tspecial(int c)
+{
+  switch (c)
+  {
+  case '(': case ')': case '<': case '>': case '@':
+  case ',': case ';': case ':': case '\\': case '"':
+  case '/': case '[': case ']': case '?': case '=':
+  case '{': case '}': case ' ': case '\t':
+    return true;
+  default:
+    return false;
+  }
+}
+
+bool request_parser::is_digit(int c)
+{
+  return c >= '0' && c <= '9';
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/request_parser.hpp b/server/request_parser.hpp
new file mode 100644
index 0000000..506fda1
--- /dev/null
+++ b/server/request_parser.hpp
@@ -0,0 +1,95 @@
+//
+// request_parser.hpp
+// ~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_REQUEST_PARSER_HPP
+#define HTTP_REQUEST_PARSER_HPP
+
+#include <boost/logic/tribool.hpp>
+#include <boost/tuple/tuple.hpp>
+
+namespace http {
+namespace server {
+
+struct request;
+
+/// Parser for incoming requests.
+class request_parser
+{
+public:
+  /// Construct ready to parse the request method.
+  request_parser();
+
+  /// Reset to initial parser state.
+  void reset();
+
+  /// Parse some data. The tribool return value is true when a complete request
+  /// has been parsed, false if the data is invalid, indeterminate when more
+  /// data is required. The InputIterator return value indicates how much of the
+  /// input has been consumed.
+  template <typename InputIterator>
+  boost::tuple<boost::tribool, InputIterator> parse(request& req,
+      InputIterator begin, InputIterator end)
+  {
+    while (begin != end)
+    {
+      boost::tribool result = consume(req, *begin++);
+      if (result || !result)
+        return boost::make_tuple(result, begin);
+    }
+    boost::tribool result = boost::indeterminate;
+    return boost::make_tuple(result, begin);
+  }
+
+private:
+  /// Handle the next character of input.
+  boost::tribool consume(request& req, char input);
+
+  /// Check if a byte is an HTTP character.
+  static bool is_char(int c);
+
+  /// Check if a byte is an HTTP control character.
+  static bool is_ctl(int c);
+
+  /// Check if a byte is defined as an HTTP tspecial character.
+  static bool is_tspecial(int c);
+
+  /// Check if a byte is a digit.
+  static bool is_digit(int c);
+
+  /// The current state of the parser.
+  enum state
+  {
+    method_start,
+    method,
+    uri,
+    http_version_h,
+    http_version_t_1,
+    http_version_t_2,
+    http_version_p,
+    http_version_slash,
+    http_version_major_start,
+    http_version_major,
+    http_version_minor_start,
+    http_version_minor,
+    expecting_newline_1,
+    header_line_start,
+    header_lws,
+    header_name,
+    space_before_header_value,
+    header_value,
+    expecting_newline_2,
+    expecting_newline_3
+  } state_;
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_REQUEST_PARSER_HPP
diff --git a/server/server.cpp b/server/server.cpp
new file mode 100644
index 0000000..fa20be7
--- /dev/null
+++ b/server/server.cpp
@@ -0,0 +1,98 @@
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "server.hpp"
+#include <boost/bind.hpp>
+#include <signal.h>
+#include "logging.h"
+
+INIT_LOGGER ("HttpServer");
+namespace http {
+namespace server {
+
+server::server(const std::string& address, const std::string& port,
+    const std::string& doc_root)
+  : io_service_(),
+    signals_(io_service_),
+    acceptor_(io_service_),
+    connection_manager_(),
+    new_connection_(),
+    request_handler_(doc_root)
+{
+  // Register to handle the signals that indicate when the server should exit.
+  // It is safe to register for the same signal multiple times in a program,
+  // provided all registration for the specified signal is made through Asio.
+  signals_.add(SIGINT);
+  signals_.add(SIGTERM);
+#if defined(SIGQUIT)
+  signals_.add(SIGQUIT);
+#endif // defined(SIGQUIT)
+  signals_.async_wait(boost::bind(&server::handle_stop, this));
+
+  // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
+  boost::asio::ip::tcp::resolver resolver(io_service_);
+  boost::asio::ip::tcp::resolver::query query(address, port);
+  boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
+  acceptor_.open(endpoint.protocol());
+  acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+  acceptor_.bind(endpoint);
+  acceptor_.listen();
+
+  start_accept();
+
+  _LOG_DEBUG("Listen on [" << address << ": " << port << "] with doc_root = " << doc_root);
+}
+
+void server::run()
+{
+  // The io_service::run() call will block until all asynchronous operations
+  // have finished. While the server is running, there is always at least one
+  // asynchronous operation outstanding: the asynchronous accept call waiting
+  // for new incoming connections.
+  io_service_.run();
+}
+
+void server::start_accept()
+{
+  new_connection_.reset(new connection(io_service_,
+        connection_manager_, request_handler_));
+  acceptor_.async_accept(new_connection_->socket(),
+      boost::bind(&server::handle_accept, this,
+        boost::asio::placeholders::error));
+}
+
+void server::handle_accept(const boost::system::error_code& e)
+{
+  // Check whether the server was stopped by a signal before this completion
+  // handler had a chance to run.
+  if (!acceptor_.is_open())
+  {
+    return;
+  }
+
+  if (!e)
+  {
+    connection_manager_.start(new_connection_);
+  }
+
+  start_accept();
+}
+
+void server::handle_stop()
+{
+  // The server is stopped by cancelling all outstanding asynchronous
+  // operations. Once all operations have finished the io_service::run() call
+  // will exit.
+  acceptor_.close();
+  connection_manager_.stop_all();
+}
+
+} // namespace server
+} // namespace http
diff --git a/server/server.hpp b/server/server.hpp
new file mode 100644
index 0000000..0cb3476
--- /dev/null
+++ b/server/server.hpp
@@ -0,0 +1,69 @@
+//
+// server.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_SERVER_HPP
+#define HTTP_SERVER_HPP
+
+#include <boost/asio.hpp>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include "connection.hpp"
+#include "connection_manager.hpp"
+#include "request_handler.hpp"
+
+namespace http {
+namespace server {
+
+/// The top-level class of the HTTP server.
+class server
+  : private boost::noncopyable
+{
+public:
+  /// Construct the server to listen on the specified TCP address and port, and
+  /// serve up files from the given directory.
+  explicit server(const std::string& address, const std::string& port,
+      const std::string& doc_root);
+
+  /// Run the server's io_service loop.
+  void run();
+
+private:
+  /// Initiate an asynchronous accept operation.
+  void start_accept();
+
+  /// Handle completion of an asynchronous accept operation.
+  void handle_accept(const boost::system::error_code& e);
+
+  /// Handle a request to stop the server.
+  void handle_stop();
+
+  /// The io_service used to perform asynchronous operations.
+  boost::asio::io_service io_service_;
+
+  /// The signal_set is used to register for process termination notifications.
+  boost::asio::signal_set signals_;
+
+  /// Acceptor used to listen for incoming connections.
+  boost::asio::ip::tcp::acceptor acceptor_;
+
+  /// The connection manager which owns all live connections.
+  connection_manager connection_manager_;
+
+  /// The next connection to be accepted.
+  connection_ptr new_connection_;
+
+  /// The handler for all incoming requests.
+  request_handler request_handler_;
+};
+
+} // namespace server
+} // namespace http
+
+#endif // HTTP_SERVER_HPP
diff --git a/wscript b/wscript
index 11c7b34..ff61844 100644
--- a/wscript
+++ b/wscript
@@ -144,7 +144,7 @@
     if Utils.unversioned_sys_platform () == "darwin":
         adhoc.mac_app = True
         adhoc.source = 'adhoc/adhoc-osx.mm'
-        adhoc.use = "OSX_FOUNDATION OSX_COREWLAN"
+        adhoc.use = "LOG4CXX OSX_FOUNDATION OSX_COREWLAN"
 
     chornoshare = bld (
         target="chronoshare",
@@ -175,13 +175,24 @@
           install_prefix = None,
           )
 
+    http_server = bld (
+          target = "http_server",
+          features = "qt4 cxx",
+          source = bld.path.ant_glob(['server/*.cpp']),
+          includes = "server src .",
+          use = "BOOST QTCORE"
+          )
+
     qt = bld (
         target = "ChronoShare",
         features = "qt4 cxx cxxprogram",
         defines = "WAF",
-        source = bld.path.ant_glob(['gui/*.cpp', 'gui/*.cc', 'gui/*.qrc']),
-        includes = "ccnx scheduler executor fs-watcher gui src adhoc . ",
-        use = "BOOST BOOST_FILESYSTEM SQLITE3 QTCORE QTGUI LOG4CXX fs_watcher ccnx database chronoshare"
+        # do not include html.qrc as we don't want it to be compiled into binary
+        # qt seems to adopt a pattern of compiling every resource file into the
+        # executable; if things don't work, we can use that as last resort
+        source = bld.path.ant_glob(['gui/*.cpp', 'gui/*.cc', 'gui/images.qrc']),
+        includes = "ccnx scheduler executor fs-watcher gui src adhoc server . ",
+        use = "BOOST BOOST_FILESYSTEM SQLITE3 QTCORE QTGUI LOG4CXX fs_watcher ccnx database chronoshare http_server"
         )
 
     if Utils.unversioned_sys_platform () == "darwin":