Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
websocket.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "websocket.hh"
3#include "platform.hh"
4#include "strings.hh"
5#include "formatter.hh"
6#include "path.hh"
7#include "blob.hh"
8#include "utils.hh"
9#include "regex.hh"
10#include "randomhash.hh"
11#include "internal.hh"
12#include <regex>
13#include <fstream>
14
15#include <websocketpp/config/asio_no_tls.hpp>
16#include <websocketpp/server.hpp>
17
18namespace Ase {
19
20struct WebSocketServerImpl;
21using WebSocketServerImplP = std::shared_ptr<WebSocketServerImpl>;
22
23struct CustomServerConfig : public websocketpp::config::asio {
24 static const size_t connection_read_buffer_size = 16384;
25};
26using WppServer = websocketpp::server<CustomServerConfig>;
27using WppConnectionP = WppServer::connection_ptr;
28using WppConnection = WppConnectionP::element_type;
29using WppHdl = websocketpp::connection_hdl;
30
31static inline bool
32operator!= (const WppHdl &a, const WppHdl &b)
33{
34 return a.owner_before (b) || b.owner_before (a);
35}
36static inline bool operator== (const WppHdl &a, const WppHdl &b) { return !(a != b); }
37
38// == WebSocketServerImpl ==
42 std::thread *initialized_thread_ = nullptr;
43 WppServer wppserver_;
44 String server_url_, dir_, token_, see_other_;
46 ConVec opencons_;
47 RegexVector ignores_;
48 MakeConnection make_con_;
49 int logflags_ = 0;
50 int listen_port_ = 0;
51 void setup (const String &host, int port);
52 void run ();
53 WebSocketConnectionP make_connection (WppHdl hdl);
54 WebSocketConnection* force_con (WppHdl);
55 friend class WebSocketServer;
56public:
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); }
60 void
61 http_dir (const String &path) override
62 {
63 assert_return (!initialized_thread_);
64 dir_ = Path::normalize (path);
65 aliases_.clear();
66 ignores_.clear();
67
68 // compile .aseignore
69 String cxxline;
70 std::ifstream aseignore;
71 aseignore.open (Path::join (dir_, ".aseignore"));
72 while (!aseignore.fail() && std::getline (aseignore, cxxline))
73 if (!cxxline.empty())
74 ignores_.push_back (std::regex (cxxline));
75 }
76 void
77 http_alias (const String &webdir, const String &path) override
78 {
79 assert_return (!dir_.empty());
80 String aliaspath = Path::normalize (Path::abspath (path, dir_));
81 aliases_.push_back (std::make_pair (webdir, aliaspath));
82 // sort by URL length, longest URLs come first
83 std::stable_sort (aliases_.begin(), aliases_.end(), [] (const auto &a, const auto &b) {
84 return a.first.size() > b.first.size();
85 });
86 }
87 String
88 map_url (const String &urlpath) override
89 {
90 if (dir_.empty())
91 return "";
92
93 // decode URL, also uncovers '.' and '/'
94 String absurl = string_url_decode (urlpath);
95
96 // normalize '.' and '..' dirs
97 absurl = Path::simplify_abspath (absurl);
98
99 // ignore urls
100 for (const auto &ignorepat : ignores_)
101 if (std::regex_search (absurl, ignorepat))
102 return "";
103
104 // map URL to sorted aliases, prefers longest match
105 for (const auto &pair : aliases_)
106 if (absurl == pair.first)
107 return pair.second;
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());
112
113 // fallback to root
114 return Path::join (dir_, absurl);
115 }
116 void
117 listen (const String &host, int port, const UnlistenCB &ulcb) override
118 {
119 assert_return (!initialized_thread_);
120 setup (host, port);
121 initialized_thread_ = new std::thread ([this, ulcb] () {
122 this_thread_set_name ("AsioWebSocket");
123 this->run();
124 if (ulcb)
125 ulcb();
126 });
127 }
128 void
129 see_other (const String &uri) override
130 {
131 // TODO: not thread-safe
132 see_other_ = uri;
133 }
134 void
135 shutdown () override
136 {
137 if (initialized_thread_)
138 {
139 wppserver_.stop();
140 initialized_thread_->join();
141 initialized_thread_ = nullptr;
142 }
143 reset();
144 }
145 void ws_opened (WebSocketConnectionP);
146 void ws_closed (WebSocketConnectionP);
147 void reset () override;
148 WebSocketServerImpl (const MakeConnection &make_con, int logflags, const std::string &token) :
149 token_ (token), make_con_ (make_con), logflags_ (logflags)
150 {}
151};
152
153void
154WebSocketServerImpl::setup (const String &host, int port)
155{
156 // setup websocket and run asio loop
157 wppserver_.set_user_agent (user_agent());
158 wppserver_.set_validate_handler ([this] (WppHdl hdl) {
159 WebSocketConnectionP conp = make_connection (hdl);
160 WppConnectionP cp = this->wppconp (hdl);
161 return_unless (conp && cp, false);
162 const bool is_authenticated = conp->authenticated (token_);
163 if (is_authenticated) {
164 const int index = conp->validate();
165 if (index >= 0)
166 {
167 const std::vector<std::string> &subprotocols = cp->get_requested_subprotocols();
168 if (size_t (index) < subprotocols.size())
169 {
170 cp->select_subprotocol (subprotocols[index]);
171 return true;
172 }
173 else if (subprotocols.size() == 0 && index == 0)
174 return true;
175 }
176 }
177 cp->set_status (websocketpp::http::status_code::forbidden);
178 conp->log_status (cp->get_response_code());
179 return false;
180 });
181 wppserver_.set_http_handler ([this] (WppHdl hdl) {
182 WebSocketConnectionP conp = make_connection (hdl);
183 if (conp)
184 conp->http_request();
185 });
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);
190
191 // listen on localhost
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);
196 if (ec)
197 fatal_error ("failed to listen on socket: %s:%d: %s", host, port, ec.message());
198 if (port)
199 listen_port_ = port;
200 else { // port == 0
201 websocketpp::lib::asio::error_code ac;
202 listen_port_ = wppserver_.get_local_endpoint (ac).port();
203 }
204 String fullurl = string_format ("http://%s:%d/", host, listen_port_);
205 server_url_ = fullurl;
206}
207
208void
209WebSocketServerImpl::run ()
210{
211 wppserver_.start_accept();
212 wppserver_.run();
213}
214
215// == WebSocketConnection ==
218 WppServer &wppserver;
219 WppHdl hdl;
220 String nickname_;
221 bool opened = false;
222 WppConnectionP wppconp() { return server->wppconp (hdl); }
223 friend struct WebSocketServerImpl;
224};
225
226void
227WebSocketConnection::log_status (int intstatus, const String &filepath)
228{
229 const auto status = websocketpp::http::status_code::value (intstatus);
230 WppConnectionP cp = internals_.wppconp();
231 if (!cp || !(logflags_ & 16))
232 return;
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);
239 log (string_format ("%s%d %s %s%s%s",
240 C1, status,
241 cp->get_request().get_method(), resource, C0,
242 filepath.empty() ? "" : " [" + filepath + "]"));
243}
244
245WebSocketConnection::WebSocketConnection (Internals &internals, int logflags) :
246 internals_ (*new (internals_mem_) Internals (internals)), logflags_ (logflags)
247{
248 static_assert (sizeof (Internals) <= sizeof (internals_mem_));
249 assert_return (internals_.server != nullptr);
250}
251
252WebSocketConnection::~WebSocketConnection()
253{
254 internals_.~Internals();
255}
256
257bool
258WebSocketConnection::is_open () const
259{
260 return internals_.opened;
261}
262
263bool
265{
266 assert_return (!message.empty(), false);
267 WppConnectionP cp = internals_.wppconp();
268 return_unless (cp, false);
269 websocketpp::lib::error_code ec;
270 internals_.wppserver.send (internals_.hdl, message, websocketpp::frame::opcode::text, ec);
271 if (ec)
272 {
273 if (logflags_ > 0)
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);
277 return false;
278 }
279 return true;
280}
281
282bool
284{
285 WppConnectionP cp = internals_.wppconp();
286 return_unless (cp, false);
287 websocketpp::lib::error_code ec;
288 // See "Sending Messages" about `endpoint::send` in utility_client.md
289 internals_.wppserver.send (internals_.hdl, blob, websocketpp::frame::opcode::binary, ec); // MT-Safe, locks mutex
290 if (ec)
291 {
292 if (logflags_ > 0)
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);
296 return false; // invalid connection or send failed
297 }
298 if (ASE_UNLIKELY (logflags_ & 256))
299 {
300 String hex;
301 for (size_t i = 0; i < blob.size(); i++)
302 {
303 if (i && 0 == i % 16)
304 hex += "\n ";
305 else if (0 == i % 8)
306 hex += " ";
307 hex += string_format (" %02x", blob[i]);
308 }
309 log (string_format ("⇜ Blob: len=%d hash=%016x\n%s", blob.size(), fnv1a_consthash64 (blob.data(), blob.size()), hex));
310 }
311 return true; // connection alive and message queued
312}
313
315WebSocketConnection::get_info ()
316{
317 WppConnectionP cp = internals_.wppconp();
318 const websocketpp::http::parser::request &rq = cp->get_request();
319 auto headers = std::make_shared<websocketpp::http::parser::header_list> (rq.get_headers());
320 Info info;
321 info.header = [headers] (const String &header) {
322 const auto it = headers->find (header);
323 return it == headers->end() ? "" : it->second;
324 };
325 if (0)
326 for (auto it : *headers)
327 printerr ("%s: %s\n", it.first, it.second);
328 // https://github.com/zaphoyd/websocketpp/issues/694#issuecomment-454623641
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();
338 return info;
339}
340
341String
342WebSocketConnection::nickname ()
343{
344 if (internals_.nickname_.empty())
345 {
346 Info info = get_info();
347 const String ua = info.header ("User-Agent");
348 String s = info.local + ":" + string_from_int (info.lport) + "\n" +
349 info.remote + ":" + string_from_int (info.rport * 0) + "\n" +
350 ua + "\n" +
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";
356 String hh;
357 uint64_t hash = string_hash64 (s);
358 if (Re::search (R"(\bFirefox/)", ua) >= 0)
359 hh = "FF";
360 else if (Re::search (R"(\bElectron/)", ua) >= 0)
361 hh = "El";
362 else if (Re::search (R"(\bChrome-Lighthouse\b)", ua) >= 0)
363 hh = "Lh";
364 else if (Re::search (R"(\bChrome/)", ua) >= 0)
365 hh = "CR";
366 else if (Re::search (R"(\bSafari/)", ua) >= 0)
367 hh = "Sa";
368 else if (Re::search (R"(\bWget/)", ua) >= 0)
369 hh = "Wg";
370 else if (Re::search (R"(\bw3m/)", ua) >= 0)
371 hh = "W3";
372 else if (Re::search (R"(\bLynx/)", ua) >= 0)
373 hh = "Ly";
374 else if (Re::search (R"(\bPython-urllib/)", ua) >= 0)
375 hh = "Py";
376 else
377 hh = "NA";
378 internals_.nickname_ = string_format ("%s-%08x:%x", hh, uint32_t (hash ^ (hash >> 32)), info.rport);
379 }
380 return internals_.nickname_;
381}
382
383int WebSocketConnection::validate () { return -1; }
384void WebSocketConnection::failed () { /*if (logflags_ & 2) log (__func__);*/ }
385void WebSocketConnection::opened () { if (logflags_ & 4) log (__func__); }
386void WebSocketConnection::message (const String &message) { if (logflags_ & 8) log (__func__); }
387void WebSocketConnection::closed () { if (logflags_ & 4) log (__func__); }
388void WebSocketConnection::log (const String &message) { printerr ("%s\n", message);}
389
390bool
392{
393 WppConnectionP cp = internals_.wppconp();
394 if (!cp)
395 return false;
396 if (token.empty())
397 return true;
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;
401}
402
403enum CacheType {
404 CACHE_NEVER,
405 CACHE_AUTO,
406 CACHE_BRIEFLY,
407 CACHE_FOREVER,
408};
409
410void
412{
413 if (internals_.server->dir_.empty())
414 return;
415 WppConnectionP cp = internals_.wppconp();
416 const auto &parts = string_split (cp->get_resource(), "?");
417 const String query = cp->get_uri()->get_query();
418 String filepath;
419
420 // Helpers
421 auto set_response = [] (WppConnectionP cp, websocketpp::http::status_code::value status, const String &title, const String &msg) {
422 const String body =
423 string_format ("<!DOCTYPE html>\n"
424 "<html><head><title>%u %s</title></head>\n<body>\n"
425 "<h1>%s</h1>\n\n"
426 "%s\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());
431 cp->set_body (body);
432 cp->replace_header ("Content-Type", "text/html; charset=utf-8");
433 cp->set_status (status);
434 };
435
436 // GET ~auth
437 if (cp->get_request().get_method() == "GET" && parts[0] == "/~auth" &&
438 (authenticated (internals_.server->token_) ||
439 Re::search ("\\btoken=" + internals_.server->token_ + "\\b", query) >= 0)) {
440 const std::string target = string_format ("http://localhost:%u/", cp->get_port());
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, // 302 Found
444 "Found", "Redirecting to: <tt>" + target + "</tt>");
445 log_status (cp->get_response_code());
446 return;
447 }
448
449 // Validate Cookie session_auth
450 if (!authenticated (internals_.server->token_)) {
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, // 405 Method Not Allowed
454 "Method Not Allowed", "Authentication required.");
455 else {
456 if (string_startswith (see_other, "http"))
457 cp->replace_header ("Location", see_other);
458 set_response (cp, websocketpp::http::status_code::see_other, // 303 See Other
459 "See Other", "<a href=\"" + see_other + "\">" + see_other + "</a>");
460 }
461 log_status (cp->get_response_code());
462 return;
463 }
464
465 // find file for URL
466 filepath = internals_.server->map_url (parts[0]);
467
468 // map directories to index.html
469 if (!filepath.empty() && Path::check (filepath, "dx"))
470 filepath = Path::join (filepath, "index.html");
471
472 // GET
473 if (cp->get_request().get_method() == "GET") {
474 // serve existing files
475 const String accept_encoding = cp->get_request().get_header ("Accept-Encoding");
476 const bool canzip = nullptr != string_find_word (accept_encoding.c_str(), "gzip");
477 bool fp = false, fz = false;
478 if (!filepath.empty() && ((fp = Path::check (filepath, "fr")) || (fz = canzip && Path::check (filepath + ".gz", "fr"))))
479 {
480 const char *ext = strrchr (filepath.c_str(), '.');
481 const String mimetype = WebSocketServer::mime_type (ext ? ext + 1 : "", true);
482 cp->append_header ("Content-Type", mimetype);
483 // Use CACHE_FOREVER for text/css to reduce FOUC (flashing of unstyled content)
484 // with shadowRoot stylesheet links in Chrome-115. CACHE_AUTO worsens FOUC.
485 const CacheType caching = mimetype == "text/css" ? CACHE_FOREVER : CACHE_AUTO;
486 switch (caching) {
487 case CACHE_NEVER: // response must not be stored in cache
488 cp->append_header ("Cache-Control", "no-store");
489 break;
490 case CACHE_AUTO: // force response revalidation if content stored in cache
491 cp->append_header ("Cache-Control", "no-cache"); // max-age=0, must-revalidate
492 break;
493 case CACHE_BRIEFLY: // needs validation support: https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests
494 cp->append_header ("Cache-Control", "max-age=7, stale-while-revalidate=31536000");
495 break;
496 case CACHE_FOREVER: // keep content forever in cache
497 cp->append_header ("Cache-Control", "max-age=31536000, public, immutable");
498 break;
499 }
500 if (fz) {
501 filepath = filepath + ".gz";
502 cp->append_header ("Content-Encoding", "gzip");
503 }
504 Blob blob = Blob::from_file (filepath);
505 cp->set_body (blob.string());
506 cp->set_status (websocketpp::http::status_code::ok); // 200 OK
507 }
508 else // 404
509 set_response (cp, websocketpp::http::status_code::not_found, // 404 Not Found
510 "Not Found",
511 "The requested URL was not found: <br /> <tt>" + cp->get_uri()->str() + "</tt>");
512 }
513 else // 405
514 set_response (cp, websocketpp::http::status_code::method_not_allowed, // 405 Method Not Allowed
515 "Method Not Allowed", "");
516
517 log_status (cp->get_response_code());
518}
519
520// == WebSocketServerImpl ==
522WebSocketServerImpl::make_connection (WppHdl hdl)
523{
524 WppConnectionP cp = wppconp (hdl);
525 assert_return (cp, nullptr);
528 wppserver_, hdl,
529 };
530 WebSocketConnectionP conp = make_con_ (internals, logflags_);
531 // capturing conp in handler contexts keeps it alive long enough for calls
532 cp->set_http_handler ([conp] (WppHdl hdl) {
533 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
534 assert_return (hdl == internals_.hdl);
535 conp->http_request();
536 });
537 // cp->set_validate_handler (...); - too late, validate_handler calls make_connection()
538 cp->set_fail_handler ([conp] (WppHdl hdl) {
539 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
540 assert_return (hdl == internals_.hdl);
541 conp->failed();
542 });
543 cp->set_open_handler ([conp] (WppHdl hdl) {
544 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
545 assert_return (hdl == internals_.hdl);
546 internals_.server->ws_opened (conp);
547 });
548 cp->set_message_handler ([conp] (WppHdl hdl, WppServer::message_ptr msg) {
549 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
550 assert_return (hdl == internals_.hdl);
551 conp->message (msg->get_payload());
552 });
553 cp->set_close_handler ([conp] (WppHdl hdl) {
554 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
555 assert_return (hdl == internals_.hdl);
556 internals_.server->ws_closed (conp);
557 });
558 return conp;
559}
560
561void
562WebSocketServerImpl::ws_opened (WebSocketConnectionP conp)
563{
564 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
565 internals_.opened = true;
566 opencons_.push_back (conp);
567 conp->opened ();
568}
569
570void
571WebSocketServerImpl::ws_closed (WebSocketConnectionP conp)
572{
573 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
574 if (internals_.opened)
575 {
576 internals_.opened = false;
577 auto it = std::find (opencons_.begin(), opencons_.end(), conp);
578 if (it != opencons_.end())
579 opencons_.erase (it);
580 conp->closed ();
581 }
582}
583
584void
585WebSocketServerImpl::reset()
586{
587 // stop open asio connections
588 for (ssize_t i = ssize_t (opencons_.size()) - 1; i >= 0; i--)
589 {
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); // omit_handshake
595 (void) ec;
596 }
597}
598
599// == WebSocketServer ==
600WebSocketServer::~WebSocketServer()
601{}
602
603WebSocketServerP
604WebSocketServer::create (const MakeConnection &make, int logflags, const std::string &session_token)
605{
606 return std::make_shared<WebSocketServerImpl> (make, logflags, session_token);
607}
608
609String
610WebSocketServer::user_agent ()
611{
612 return String ("AnklangSynthEngine/") + ase_version();
613}
614
616bool
618{
619 return !!websocketpp::utf8_validator::validate (utf8string);
620}
621
622#include "mime-types.hh" // static const char mime_types[];
623
624String
625WebSocketServer::mime_type (const String &ext, bool utf8)
626{
628 static MimeMap mime_map = [] () {
629 MimeMap mime_map;
630 // mime_types, list of "mime/type ext ext2\n" lines
631 for (const String &line : string_split (mime_types, "\n"))
632 {
633 const StringS w = string_split (line, " ");
634 for (size_t i = 1; i < w.size(); i++)
635 if (!w[i].empty())
636 {
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];
640 }
641 }
642 return mime_map;
643 } ();
644 auto it = mime_map.find (ext);
645 String mimetype = it != mime_map.end() ? it->second : "application/octet-stream";
646 if (utf8)
647 {
648 if (mimetype == "text/html" || mimetype == "text/markdown" || mimetype == "text/plain")
649 mimetype += "; charset=utf-8";
650 }
651 return mimetype;
652}
653
654} // Ase
T c_str(T... args)
Binary large object storage container.
Definition blob.hh:12
static Blob from_file(const String &filename)
Create Blob by loading from filename.
Definition blob.cc:196
String string()
Copy Blob data into a zero terminated string.
Definition blob.cc:117
static String sub(const String &regex, const String &subst, const String &input, Flags=DEFAULT)
Substitute regex in input by sbref with backreferences $00…$99 or $&.
Definition regex.cc:249
static String grep(const String &regex, const String &input, int group=0, Flags=DEFAULT)
Find regex in input and return matching string.
Definition regex.cc:225
static ssize_t search(const String &regex, const String &input, Flags=DEFAULT)
Find regex in input and return match position >= 0 or return < 0 otherwise.
Definition regex.cc:217
virtual void closed()
Pairs with opened().
Definition websocket.cc:387
virtual int validate()
Return true to allow opened().
Definition websocket.cc:383
virtual void http_request()
Independent of socket ops.
Definition websocket.cc:411
virtual void opened()
Pairs with closed().
Definition websocket.cc:385
bool send_binary(const String &blob)
Returns true if binary blob was sent.
Definition websocket.cc:283
virtual void message(const String &message)
Only if opened.
Definition websocket.cc:386
virtual void failed()
Never folloed by opened().
Definition websocket.cc:384
bool send_text(const String &message)
Returns true if text message was sent.
Definition websocket.cc:264
virtual bool authenticated(const std::string &token)
Return true to allow validate().
Definition websocket.cc:391
static bool utf8_validate(const std::string &utf8string)
Validate UTF-8 string with websocketpp::utf8_validator.
Definition websocket.cc:617
T clear(T... args)
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
Definition cxxaux.hh:46
T data(T... args)
T empty(T... args)
T end(T... args)
T fail(T... args)
T find(T... args)
T getline(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
T join(T... args)
T make_pair(T... args)
bool check(const String &file, const String &mode)
Definition path.cc:625
String simplify_abspath(const std::string &abspath_expression)
Remove extra slashes, './' and '../' from abspath_expression.
Definition path.cc:886
String abspath(const String &path, const String &incwd)
Definition path.cc:134
String normalize(const String &path)
Convert path to normal form.
Definition path.cc:86
The Anklang C++ API namespace.
Definition api.hh:9
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)
Definition strings.cc:343
String string_from_int(int64 value)
Convert a 64bit signed integer into a string.
Definition strings.cc:604
const char * string_find_word(const char *haystack, const char *word)
Find occurance of word in haystack.
Definition strings.cc:520
String string_url_decode(const String &urlstr, const bool form_url_encoded)
Decode URL %-sequences in a string, decode '+' if form_url_encoded.
Definition strings.cc:965
const char * ase_version()
Provide a string containing the package version.
Definition platform.cc:835
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
T open(T... args)
T push_back(T... args)
T regex_search(T... args)
T size(T... args)
socket
T stable_sort(T... args)
typedef uint64_t
strncmp
strrchr