15#include <websocketpp/config/asio_no_tls.hpp> 
   16#include <websocketpp/server.hpp> 
   20struct WebSocketServerImpl;
 
   24  static const size_t connection_read_buffer_size = 16384;
 
 
   26using WppServer = websocketpp::server<CustomServerConfig>;
 
   27using WppConnectionP = WppServer::connection_ptr;
 
   28using WppConnection = WppConnectionP::element_type;
 
   29using WppHdl = websocketpp::connection_hdl;
 
   32operator!= (
const WppHdl &a, 
const WppHdl &b)
 
   34  return a.owner_before (b) || b.owner_before (a);
 
   36static inline bool operator== (
const WppHdl &a, 
const WppHdl &b) { 
return !(a != b); }
 
   44  String         server_url_, dir_, token_, see_other_;
 
   51  void   setup        (
const String &host, 
int port);
 
   57  int            listen_port ()
 const override   { 
return listen_port_; }
 
   58  String         url      ()
 const override      { 
return server_url_; }
 
   59  WppConnectionP wppconp  (WppHdl hdl)           { 
return wppserver_.get_con_from_hdl (hdl); }
 
   61  http_dir (
const String &path)
 override 
   71    aseignore.
open (Path::join (dir_, 
".aseignore"));
 
   77  http_alias (
const String &webdir, 
const String &path)
 override 
   83    std::stable_sort (aliases_.begin(), aliases_.end(), [] (
const auto &a, 
const auto &b) {
 
   84      return a.first.size() > b.first.size();
 
   88  map_url (
const String &urlpath)
 override 
  100    for (
const auto &ignorepat : ignores_)
 
  105    for (
const auto &pair : aliases_)
 
  106      if (absurl == pair.first)
 
  108      else if (absurl.
size() > pair.first.size() &&
 
  109               absurl[pair.first.size()] == 
'/' &&
 
  110               strncmp (absurl.
c_str(), pair.first.c_str(), pair.first.size()) == 0)
 
  111        return Path::join (pair.second, absurl.
c_str() + pair.first.size());
 
  114    return Path::join (dir_, absurl);
 
  121    initialized_thread_ = 
new std::thread ([
this, ulcb] () {
 
  122      this_thread_set_name (
"AsioWebSocket");
 
  129  see_other (
const String &uri)
 override 
  137    if (initialized_thread_)
 
  140        initialized_thread_->
join();
 
  141        initialized_thread_ = 
nullptr;
 
  147  void reset         () 
override;
 
  149    token_ (token), make_con_ (make_con), logflags_ (logflags)
 
 
  154WebSocketServerImpl::setup (
const String &host, 
int port)
 
  157  wppserver_.set_user_agent (user_agent());
 
  158  wppserver_.set_validate_handler ([
this] (WppHdl hdl) {
 
  160    WppConnectionP cp = this->wppconp (hdl);
 
  162    const bool is_authenticated = conp->authenticated (token_);
 
  163    if (is_authenticated) {
 
  164      const int index = conp->validate();
 
  168          if (
size_t (index) < subprotocols.size())
 
  170              cp->select_subprotocol (subprotocols[index]);
 
  173          else if (subprotocols.size() == 0 && index == 0)
 
  177    cp->set_status (websocketpp::http::status_code::forbidden);
 
  178    conp->log_status (cp->get_response_code());
 
  181  wppserver_.set_http_handler ([
this] (WppHdl hdl) {
 
  182    WebSocketConnectionP conp = make_connection (hdl);
 
  184      conp->http_request();
 
  186  wppserver_.init_asio();
 
  187  wppserver_.clear_access_channels (websocketpp::log::alevel::all);
 
  188  wppserver_.clear_error_channels (websocketpp::log::elevel::all);
 
  189  wppserver_.set_reuse_addr (
true);
 
  192  namespace IP = boost::asio::ip;
 
  193  IP::tcp::endpoint endpoint_local = IP::tcp::endpoint (IP::address::from_string (host), port);
 
  194  websocketpp::lib::error_code ec;
 
  195  wppserver_.listen (endpoint_local, ec);
 
  197    fatal_error (
"failed to listen on socket: %s:%d: %s", host, port, ec.message());
 
  201    websocketpp::lib::asio::error_code ac;
 
  202    listen_port_ = wppserver_.get_local_endpoint (ac).port();
 
  205  server_url_ = fullurl;
 
  209WebSocketServerImpl::run ()
 
  211  wppserver_.start_accept();
 
  218  WppServer           &wppserver;
 
  222  WppConnectionP       wppconp()        { 
return server->wppconp (hdl); }
 
 
  227WebSocketConnection::log_status (
int intstatus, 
const String &filepath)
 
  229  const auto status = websocketpp::http::status_code::value (intstatus);
 
  230  WppConnectionP cp = internals_.wppconp();
 
  231  if (!cp || !(logflags_ & 16))
 
  233  using namespace AnsiColors;
 
  234  const bool rejected = (status >= 400 && status <= 499) || status == 303;
 
  235  const String C1 = rejected ? color (FG_RED) : 
"";
 
  236  const String C0 = rejected ? color (RESET) : 
"";
 
  237  String resource = cp->get_resource();
 
  238  resource = 
Re::sub (
"\\btoken=[^;?]+", 
"token=<………>", resource);
 
  241                      cp->get_request().get_method(), resource, C0,
 
  242                      filepath.
empty() ? 
"" : 
" [" + filepath + 
"]"));
 
  245WebSocketConnection::WebSocketConnection (Internals &internals, 
int logflags) :
 
  246  internals_ (*new (internals_mem_) Internals (internals)), logflags_ (logflags)
 
  248  static_assert (
sizeof (Internals) <= 
sizeof (internals_mem_));
 
  252WebSocketConnection::~WebSocketConnection()
 
  254  internals_.~Internals();
 
  258WebSocketConnection::is_open ()
 const 
  260  return internals_.opened;
 
  267  WppConnectionP cp = internals_.wppconp();
 
  269  websocketpp::lib::error_code ec;
 
  270  internals_.wppserver.send (internals_.hdl, 
message, websocketpp::frame::opcode::text, ec);
 
  274        log (
string_format (
"Error: %s: %s", __func__, ec.message()));
 
  275      websocketpp::lib::error_code ec2;
 
  276      cp->close (websocketpp::close::status::going_away, 
"", ec2);
 
 
  285  WppConnectionP cp = internals_.wppconp();
 
  287  websocketpp::lib::error_code ec;
 
  289  internals_.wppserver.send (internals_.hdl, blob, websocketpp::frame::opcode::binary, ec); 
 
  293        log (
string_format (
"Error: %s: %s", __func__, ec.message()));
 
  294      websocketpp::lib::error_code ec2;
 
  295      cp->close (websocketpp::close::status::going_away, 
"", ec2);
 
  301      for (
size_t i = 0; i < blob.
size(); i++)
 
  303          if (i && 0 == i % 16)
 
 
  315WebSocketConnection::get_info ()
 
  317  WppConnectionP cp = internals_.wppconp();
 
  318  const websocketpp::http::parser::request &rq = cp->get_request();
 
  321  info.header = [headers] (
const String &header) {
 
  322    const auto it = headers->find (header);
 
  323    return it == headers->end() ? 
"" : it->second;
 
  326    for (
auto it : *headers)
 
  327      printerr (
"%s: %s\n", it.first, it.second);
 
  329  const auto &
socket = cp->get_raw_socket();
 
  330  boost::system::error_code ec;
 
  331  const auto &laddress = 
socket.local_endpoint (ec).address();
 
  332  info.local = laddress.to_string();
 
  333  info.lport = 
socket.local_endpoint (ec).port();
 
  334  const auto &raddress = 
socket.remote_endpoint (ec).address();
 
  335  info.remote = raddress.to_string();
 
  336  info.rport = 
socket.remote_endpoint (ec).port();
 
  337  info.subs = cp->get_requested_subprotocols();
 
  342WebSocketConnection::nickname ()
 
  344  if (internals_.nickname_.
empty())
 
  346      Info info = get_info();
 
  347      const String ua = info.header (
"User-Agent");
 
  351                 info.header (
"Accept-Encoding") + 
"\n" +
 
  352                 info.header (
"Accept-Language") + 
"\n" +
 
  353                 info.header (
"sec-ch-ua") + 
"\n" +
 
  354                 info.header (
"sec-ch-ua-mobile") + 
"\n" +
 
  355                 info.header (
"sec-gpc") + 
"\n";
 
  360      else if (
Re::search (R
"(\bElectron/)", ua) >= 0) 
  362      else if (
Re::search (R
"(\bChrome-Lighthouse\b)", ua) >= 0) 
  364      else if (
Re::search (R
"(\bChrome/)", ua) >= 0) 
  366      else if (
Re::search (R
"(\bSafari/)", ua) >= 0) 
  374      else if (
Re::search (R
"(\bPython-urllib/)", ua) >= 0) 
  378      internals_.nickname_ = 
string_format (
"%s-%08x:%x", hh, uint32_t (hash ^ (hash >> 32)), info.rport);
 
  380  return internals_.nickname_;
 
  393  WppConnectionP cp = internals_.wppconp();
 
  398  const String cookie_header = cp->get_request().get_header (
"Cookie");
 
  399  const String cookie_token = 
Re::grep (
"\\bsession_auth=([^;?]+)", cookie_header, 1);
 
  400  return cookie_token == token;
 
 
  413  if (internals_.server->dir_.empty())
 
  415  WppConnectionP cp = internals_.wppconp();
 
  416  const auto &parts = 
string_split (cp->get_resource(), 
"?");
 
  417  const String query = cp->get_uri()->get_query();
 
  421  auto set_response = [] (WppConnectionP cp, websocketpp::http::status_code::value status, 
const String &title, 
const String &msg) {
 
  424                     "<html><head><title>%u %s</title></head>\n<body>\n" 
  427                     "<hr><address>%s</address>\n" 
  428                     "<hr></body></html>\n", status, title, title,
 
  429                     msg.size() ? 
"<p>" + msg + 
"</p>" : 
"",
 
  430                     WebSocketServer::user_agent());
 
  432    cp->replace_header (
"Content-Type", 
"text/html; charset=utf-8");
 
  433    cp->set_status (status);
 
  437  if (cp->get_request().get_method() == 
"GET" && parts[0] == 
"/~auth" &&
 
  439       Re::search (
"\\btoken=" + internals_.server->token_ + 
"\\b", query) >= 0)) {
 
  441    cp->replace_header (
"Set-Cookie", 
"session_auth=" + internals_.server->token_ + 
"; Path=/; HttpOnly");
 
  442    cp->replace_header (
"Location", target);
 
  443    set_response (cp, websocketpp::http::status_code::found, 
 
  444                  "Found", 
"Redirecting to: <tt>" + target + 
"</tt>");
 
  445    log_status (cp->get_response_code());
 
  451    const std::string see_other = internals_.server->see_other_;
 
  452    if (see_other.
empty())
 
  453      set_response (cp, websocketpp::http::status_code::method_not_allowed, 
 
  454                    "Method Not Allowed", 
"Authentication required.");
 
  457        cp->replace_header (
"Location", see_other);
 
  458      set_response (cp, websocketpp::http::status_code::see_other, 
 
  459                    "See Other", 
"<a href=\"" + see_other + 
"\">" + see_other + 
"</a>");
 
  461    log_status (cp->get_response_code());
 
  466  filepath = internals_.server->map_url (parts[0]);
 
  470    filepath = Path::join (filepath, 
"index.html");
 
  473  if (cp->get_request().get_method() == 
"GET") {
 
  475    const String accept_encoding = cp->get_request().get_header (
"Accept-Encoding");
 
  477    bool fp = 
false, fz = 
false;
 
  481        const String mimetype = WebSocketServer::mime_type (ext ? ext + 1 : 
"", 
true);
 
  482        cp->append_header (
"Content-Type", mimetype);
 
  485        const CacheType caching = mimetype == 
"text/css" ? CACHE_FOREVER : CACHE_AUTO;
 
  488          cp->append_header (
"Cache-Control", 
"no-store");
 
  491          cp->append_header (
"Cache-Control", 
"no-cache"); 
 
  494          cp->append_header (
"Cache-Control", 
"max-age=7, stale-while-revalidate=31536000");
 
  497          cp->append_header (
"Cache-Control", 
"max-age=31536000, public, immutable");
 
  501          filepath = filepath + 
".gz";
 
  502          cp->append_header (
"Content-Encoding", 
"gzip");
 
  505        cp->set_body (blob.
string());
 
  506        cp->set_status (websocketpp::http::status_code::ok); 
 
  509      set_response (cp, websocketpp::http::status_code::not_found, 
 
  511                    "The requested URL was not found: <br /> <tt>" + cp->get_uri()->str() + 
"</tt>");
 
  514    set_response (cp, websocketpp::http::status_code::method_not_allowed, 
 
  515                  "Method Not Allowed", 
"");
 
  517  log_status (cp->get_response_code());
 
 
  522WebSocketServerImpl::make_connection (WppHdl hdl)
 
  524  WppConnectionP cp = wppconp (hdl);
 
  530  WebSocketConnectionP conp = make_con_ (internals, logflags_);
 
  532  cp->set_http_handler ([conp] (WppHdl hdl) {
 
  533    WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  535    conp->http_request();
 
  538  cp->set_fail_handler ([conp] (WppHdl hdl) {
 
  539    WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  543  cp->set_open_handler  ([conp] (WppHdl hdl) {
 
  544    WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  546    internals_.server->ws_opened (conp);
 
  548  cp->set_message_handler ([conp] (WppHdl hdl, WppServer::message_ptr msg) {
 
  549    WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  551    conp->message (msg->get_payload());
 
  553  cp->set_close_handler ([conp] (WppHdl hdl) {
 
  554    WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  556    internals_.server->ws_closed (conp);
 
  562WebSocketServerImpl::ws_opened (WebSocketConnectionP conp)
 
  564  WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  565  internals_.opened = 
true;
 
  566  opencons_.push_back (conp);
 
  571WebSocketServerImpl::ws_closed (WebSocketConnectionP conp)
 
  573  WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  574  if (internals_.opened)
 
  576      internals_.opened = 
false;
 
  577      auto it = 
std::find (opencons_.begin(), opencons_.end(), conp);
 
  578      if (it != opencons_.end())
 
  579        opencons_.erase (it);
 
  585WebSocketServerImpl::reset()
 
  588  for (ssize_t i = ssize_t (opencons_.size()) - 1; i >= 0; i--)
 
  590      WebSocketConnectionP conp = opencons_[i];
 
  591      WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
 
  592      WppConnectionP cp = internals_.wppconp();
 
  593      websocketpp::lib::error_code ec;
 
  594      cp->close (websocketpp::close::status::going_away, 
"", ec); 
 
  600WebSocketServer::~WebSocketServer()
 
  604WebSocketServer::create (
const MakeConnection &make, 
int logflags, 
const std::string &session_token)
 
  610WebSocketServer::user_agent ()
 
  619  return !!websocketpp::utf8_validator::validate (utf8string);
 
 
  625WebSocketServer::mime_type (
const String &ext, 
bool utf8)
 
  628  static MimeMap mime_map = [] () {
 
  634        for (
size_t i = 1; i < w.
size(); i++)
 
  637              if (mime_map.end() != mime_map.find (w[i]))
 
  638                warning (
"mime-types.hh: duplicate extension: %s", w[i]);
 
  639              mime_map[w[i]] = w[0];
 
  644  auto it = mime_map.find (ext);
 
  645  String mimetype = it != mime_map.
end() ? it->second : 
"application/octet-stream";
 
  648      if (mimetype == 
"text/html" || mimetype == 
"text/markdown" || mimetype == 
"text/plain")
 
  649        mimetype += 
"; charset=utf-8";
 
Binary large object storage container.
 
static Blob from_file(const String &filename)
Create Blob by loading from filename.
 
String string()
Copy Blob data into a zero terminated string.
 
static String sub(const String ®ex, const String &subst, const String &input, Flags=DEFAULT)
Substitute regex in input by sbref with backreferences $00…$99 or $&.
 
static String grep(const String ®ex, const String &input, int group=0, Flags=DEFAULT)
Find regex in input and return matching string.
 
static ssize_t search(const String ®ex, const String &input, Flags=DEFAULT)
Find regex in input and return match position >= 0 or return < 0 otherwise.
 
virtual void closed()
Pairs with opened().
 
virtual int validate()
Return true to allow opened().
 
virtual void http_request()
Independent of socket ops.
 
virtual void opened()
Pairs with closed().
 
bool send_binary(const String &blob)
Returns true if binary blob was sent.
 
virtual void message(const String &message)
Only if opened.
 
virtual void failed()
Never folloed by opened().
 
bool send_text(const String &message)
Returns true if text message was sent.
 
virtual bool authenticated(const std::string &token)
Return true to allow validate().
 
static bool utf8_validate(const std::string &utf8string)
Validate UTF-8 string with websocketpp::utf8_validator.
 
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
 
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
 
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
 
bool check(const String &file, const String &mode)
 
String simplify_abspath(const std::string &abspath_expression)
Remove extra slashes, './' and '../' from abspath_expression.
 
String abspath(const String &path, const String &incwd)
 
String normalize(const String &path)
Convert path to normal form.
 
The Anklang C++ API namespace.
 
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
 
StringS string_split(const String &string, const String &splitter, size_t maxn)
 
String string_from_int(int64 value)
Convert a 64bit signed integer into a string.
 
const char * string_find_word(const char *haystack, const char *word)
Find occurance of word in haystack.
 
String string_url_decode(const String &urlstr, const bool form_url_encoded)
Decode URL %-sequences in a string, decode '+' if form_url_encoded.
 
const char * ase_version()
Provide a string containing the package version.
 
std::string String
Convenience alias for std::string.
 
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
 
T regex_search(T... args)
 
T shared_from_this(T... args)