Index: chrome/browser/chromeos/web_socket_proxy.cc |
diff --git a/chrome/browser/chromeos/web_socket_proxy.cc b/chrome/browser/chromeos/web_socket_proxy.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8a52746c7d3fcaa0cb9c173f599039735c7ca6b5 |
--- /dev/null |
+++ b/chrome/browser/chromeos/web_socket_proxy.cc |
@@ -0,0 +1,1290 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "web_socket_proxy.h" |
+ |
+#include <stdio.h> |
+#include <stdlib.h> |
+#include <string.h> |
+ |
+#include <algorithm> |
+#include <limits> |
+#include <list> |
+#include <map> |
+#include <vector> |
+ |
+#include <arpa/inet.h> |
+#include <errno.h> |
+#include <fcntl.h> |
+#include <netinet/in.h> |
+#include <signal.h> |
+#include <sys/socket.h> |
+#include <sys/types.h> |
+#include <sys/wait.h> |
+ |
+#include "base/base64.h" |
+#include "base/basictypes.h" |
+#include "base/logging.h" |
+#include "base/md5.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/string_number_conversions.h" |
+#include "base/string_util.h" |
+#include "content/browser/browser_thread.h" |
+#include "content/common/notification_service.h" |
+#include "content/common/notification_type.h" |
+// TODO(dilmah): enable this once webSocketProxyPrivate.getToken is wired. |
+#if 0 |
+#include "chrome/browser/internal_auth.h" |
+#endif |
+#include "third_party/libevent/evdns.h" |
+#include "third_party/libevent/event.h" |
+ |
+namespace chromeos { |
+ |
+namespace { |
+ |
+const uint8 kCRLF[] = "\r\n"; |
+const uint8 kCRLFCRLF[] = "\r\n\r\n"; |
+ |
+// Not a constant but preprocessor definition for easy concatenation. |
+#define kProxyPath "/tcpproxy" |
+ |
+// Returns true on success. |
+bool SetNonBlock(int fd) { |
+ int flags = fcntl(fd, F_GETFL, 0); |
+ return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; |
+} |
+ |
+// Returns true on success. |
+bool IgnoreSigPipe() { |
+ struct sigaction sa; |
+ sa.sa_handler = SIG_IGN; |
+ sa.sa_flags = 0; |
+ if (sigemptyset(&sa.sa_mask) || sigaction(SIGPIPE, &sa, 0)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to disable sigpipe"; |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+int CountSpaces(const std::string& s) { |
+ static const uint8 kSpaceOctet = 0x20; |
+ int rv = 0; |
+ for (size_t i = 0; i < s.size(); ++i) |
+ rv += (s[i] == kSpaceOctet); |
+ return rv; |
+} |
+ |
+std::string FetchLowerCasedASCIISnippet(uint8* begin, uint8* end) { |
+ std::string rv; |
+ for (; begin < end; ++begin) { |
+ if (!isascii(*begin)) |
+ return rv; |
+ rv += base::ToLowerASCII(*begin); |
+ } |
+ return rv; |
+} |
+ |
+// Returns true on success. |
+bool FetchDecimalDigits(const std::string& s, uint32* result) { |
+ *result = 0; |
+ bool got_something = false; |
+ for (size_t i = 0; i < s.size(); ++i) { |
+ if (IsAsciiDigit(s[i])) { |
+ got_something = true; |
+ if (*result > std::numeric_limits<uint32>::max() / 10) |
+ return false; |
+ *result *= 10; |
+ int digit = s[i] - '0'; |
+ if (*result > std::numeric_limits<uint32>::max() - digit) |
+ return false; |
+ *result += digit; |
+ } |
+ } |
+ return got_something; |
+} |
+ |
+// Parses "token:hostname:port:" string. Returns true on success. |
+bool FetchTokenNamePort( |
+ uint8* begin, uint8* end, |
+ std::string* token, std::string* name, uint32* port) { |
+ std::string input(begin, end); |
+ if (input[input.size() - 1] != ':') |
+ return false; |
+ input.resize(input.size() - 1); |
+ |
+ size_t pos = input.find_last_of(':'); |
+ if (pos == std::string::npos) |
+ return false; |
+ std::string port_str(input, pos + 1); |
+ if (port_str.empty()) |
+ return false; |
+ const char kAsciiDigits[] = "0123456789"; |
+ COMPILE_ASSERT(sizeof(kAsciiDigits) == 10 + 1, mess_with_digits); |
+ if (port_str.find_first_not_of(kAsciiDigits) != std::string::npos) |
+ return false; |
+ if (!FetchDecimalDigits(port_str, port) || |
+ *port <= 0 || |
+ *port >= (1 << 16)) { |
+ return false; |
+ } |
+ input.resize(pos); |
+ |
+ pos = input.find_first_of(':'); |
+ if (pos == std::string::npos) |
+ return false; |
+ token->assign(input, 0, pos); |
+ name->assign(input, pos + 1, std::string::npos); |
+ return !name->empty(); |
+} |
+ |
+std::string FetchExtensionIdFromOrigin(const std::string origin) { |
+ // Origin of extension looks like "chrome-extension://EXTENSION_ID". |
+ return origin.substr(origin.find_last_of('/')); |
+} |
+ |
+inline size_t strlen(const uint8* s) { |
+ return ::strlen(reinterpret_cast<const char*>(s)); |
+} |
+ |
+void SendNotification() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ NotificationService::current()->Notify( |
+ NotificationType::WEB_SOCKET_PROXY_STARTED, |
+ NotificationService::AllSources(), NotificationService::NoDetails()); |
+} |
+ |
+class Conn; |
+ |
+// Websocket to TCP proxy server. |
+class Serv { |
+ public: |
+ Serv(const std::vector<std::string>& allowed_origins, |
+ struct sockaddr* addr, int addr_len); |
+ ~Serv(); |
+ |
+ // Do not call it twice. |
+ void Run(); |
+ |
+ // Terminates running server (should be called on a different thread). |
+ void Shutdown(); |
+ |
+ void ZapConn(Conn*); |
+ void MarkConnImportance(Conn*, bool important); |
+ Conn* GetFreshConn(); |
+ bool IsConnSane(Conn*); |
+ bool IsOriginAllowed(const std::string& origin); |
+ void CloseAll(); |
+ |
+ static void OnConnect(int listening_sock, short event, void*); |
+ static void OnShutdownRequest(int fd, short event, void*); |
+ |
+ struct event_base* evbase() { return evbase_; } |
+ |
+ // Checked against value of Origin field specified |
+ // in a client websocket handshake. |
+ std::vector<std::string> allowed_origins_; |
+ |
+ // Address to listen incoming websocket connections. |
+ struct sockaddr* addr_; |
+ int addr_len_; |
+ |
+ // Libevent base. |
+ struct event_base* evbase_; |
+ |
+ // Socket to listen incoming websocket connections. |
+ int listening_sock_; |
+ |
+ // Event on this descriptor triggers server shutdown. |
+ int shutdown_descriptor_[2]; |
+ |
+ // Flag whether shutdown has been requested. |
+ bool shutdown_requested_; |
+ |
+ // List of pending connections; We are trying to keep size of this list |
+ // below kConnPoolLimit in LRU fashion. |
+ typedef std::list<Conn*> ConnPool; |
+ ConnPool conn_pool_; |
+ |
+ // Reverse map to look up a connection in a conn_pool. |
+ typedef std::map<Conn*, ConnPool::iterator> RevMap; |
+ RevMap rev_map_; |
+ |
+ scoped_ptr<struct event> connection_event_; |
+ scoped_ptr<struct event> shutdown_event_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(Serv); |
+}; |
+ |
+// Connection (amalgamates both channels between proxy and javascript and |
+// between proxy and destination). |
+class Conn { |
+ public: |
+ enum Phase { |
+ // Initial stage of connection. |
+ PHASE_WAIT_HANDSHAKE, |
+ PHASE_WAIT_DESTFRAME, |
+ PHASE_WAIT_DESTCONNECT, |
+ |
+ // Operational stage of connection. |
+ PHASE_OUTSIDE_FRAME, |
+ PHASE_INSIDE_FRAME_BASE64, |
+ PHASE_INSIDE_FRAME_SKIP, |
+ |
+ // Terminal stage of connection. |
+ PHASE_SHUT, // Closing handshake was emitted, buffers may be pending. |
+ PHASE_DEFUNCT // Connection was nuked. |
+ }; |
+ |
+ // Channel structure (either proxy<->javascript or proxy<->destination). |
+ class Chan { |
+ public: |
+ explicit Chan(Conn* master) |
+ : master_(master), sock_(-1), bev_(NULL), write_pending_(false) { |
+ } |
+ |
+ ~Chan() { |
+ Zap(); |
+ } |
+ |
+ // Returns true on success. |
+ bool Write(const void* data, size_t size) { |
+ if (bev_ == NULL || sock_ < 0) |
+ return false; |
+ write_pending_ = true; |
+ return (0 == bufferevent_write(bev_, data, size)); |
+ } |
+ |
+ void Zap() { |
+ if (bev_) { |
+ bufferevent_disable(bev_, EV_READ | EV_WRITE); |
+ bufferevent_free(bev_); |
+ bev_ = NULL; |
+ } |
+ if (sock_ >= 0) { |
+ shutdown(sock_, SHUT_RDWR); |
+ close(sock_); |
+ sock_ = -1; |
+ } |
+ write_pending_ = false; |
+ master_->ConsiderSuicide(); |
+ } |
+ |
+ void Shut() { |
+ if (!write_pending_) |
+ Zap(); |
+ } |
+ |
+ int& sock() { return sock_; } |
+ bool& write_pending() { return write_pending_; } |
+ struct bufferevent*& bev() { return bev_; } |
+ |
+ private: |
+ Conn* master_; |
+ int sock_; // UNIX descriptor. |
+ struct bufferevent* bev_; |
+ bool write_pending_; // Whether write buffer is not flushed yet. |
+ }; |
+ |
+ // Status of processing incoming data. |
+ enum Status { |
+ STATUS_OK, |
+ STATUS_INCOMPLETE, // Not all required data is present in buffer yet. |
+ STATUS_SKIP, |
+ STATUS_ABORT // Data is invalid. We must shut connection. |
+ }; |
+ |
+ // Unfortunately evdns callbacks are uncancellable, |
+ // so potentially we can receive callback for a deleted Conn. |
+ // Even worse, storage of deleted Conn may be reused |
+ // for a new connection and new connection can receive callback |
+ // destined for deleted Conn. |
+ // EventKey is introduced in order to prevent that. |
+ typedef void* EventKey; |
+ typedef std::map<EventKey, Conn*> EventKeyMap; |
+ |
+ explicit Conn(Serv* master); |
+ ~Conn(); |
+ |
+ static Conn* Get(EventKey evkey); |
+ |
+ void Shut(); |
+ |
+ void ConsiderSuicide(); |
+ |
+ Status ConsumeHeader(struct evbuffer*); |
+ Status ConsumeDestframe(struct evbuffer*); |
+ Status ConsumeFrameHeader(struct evbuffer*); |
+ Status ProcessFrameData(struct evbuffer*); |
+ |
+ // Returns true on success. |
+ bool EmitHandshake(Chan*); |
+ |
+ // Attempts to establish second connection (to remote TCP service). |
+ // Returns true on success. |
+ bool TryConnectDest(const struct sockaddr*, socklen_t); |
+ |
+ // Used as libevent callbacks. |
+ static void OnDestConnectTimeout(int, short, EventKey); |
+ static void OnPrimchanRead(struct bufferevent*, EventKey); |
+ static void OnPrimchanWrite(struct bufferevent*, EventKey); |
+ static void OnPrimchanError(struct bufferevent*, short what, EventKey); |
+ static void OnDestResolutionIPv4(int result, char type, int count, |
+ int ttl, void* addr_list, EventKey); |
+ static void OnDestResolutionIPv6(int result, char type, int count, |
+ int ttl, void* addr_list, EventKey); |
+ static void OnDestchanRead(struct bufferevent*, EventKey); |
+ static void OnDestchanWrite(struct bufferevent*, EventKey); |
+ static void OnDestchanError(struct bufferevent*, short what, EventKey); |
+ |
+ Chan& primchan() { return primchan_; } |
+ EventKey evkey() const { return evkey_; } |
+ |
+ private: |
+ Serv* master_; |
+ Phase phase_; |
+ |
+ // We maintain two channels per Conn: |
+ // primary channel is websocket connection. |
+ Chan primchan_; |
+ // Destination channel is a proxied connection. |
+ Chan destchan_; |
+ |
+ EventKey evkey_; |
+ |
+ // Header fields supplied by client at initial websocket handshake. |
+ std::map<std::string, std::string> header_fields_; |
+ |
+ // Cryptohashed answer for websocket handshake. |
+ MD5Digest handshake_response_; |
+ |
+ // Hostname and port of destination socket. |
+ // Websocket client supplies them in first data frame (destframe). |
+ std::string destname_; |
+ uint32 destport_; |
+ |
+ // We try to DNS resolve hostname in both IPv4 and IPv6 domains. |
+ // Track resolution failures here. |
+ bool destresolution_ipv4_failed_; |
+ bool destresolution_ipv6_failed_; |
+ |
+ // Used to schedule a timeout for initial phase of connection. |
+ scoped_ptr<struct event> destconnect_timeout_event_; |
+ |
+ static EventKeyMap evkey_map_; |
+ static EventKey last_evkey_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(Conn); |
+}; |
+ |
+Serv::Serv( |
+ const std::vector<std::string>& allowed_origins, |
+ struct sockaddr* addr, int addr_len) |
+ : allowed_origins_(allowed_origins), |
+ addr_(addr), |
+ addr_len_(addr_len), |
+ evbase_(NULL), |
+ listening_sock_(-1), |
+ shutdown_requested_(false) { |
+ std::sort(allowed_origins_.begin(), allowed_origins_.end()); |
+ shutdown_descriptor_[0] = -1; |
+ shutdown_descriptor_[1] = -1; |
+} |
+ |
+Serv::~Serv() { |
+ CloseAll(); |
+} |
+ |
+void Serv::Run() { |
+ if (evbase_ || shutdown_requested_) |
+ return; |
+ |
+ evbase_ = event_init(); |
+ if (!evbase_) { |
+ LOG(ERROR) << "WebSocketProxy: Couldn't create libevent base"; |
+ return; |
+ } |
+ |
+ if (pipe(shutdown_descriptor_)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to create shutdown pipe"; |
+ return; |
+ } |
+ |
+ listening_sock_ = socket(AF_INET, SOCK_STREAM, 0); |
+ if (listening_sock_ < 0) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to create socket"; |
+ return; |
+ } |
+ if (bind(listening_sock_, addr_, addr_len_)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to bind server socket"; |
+ return; |
+ } |
+ if (listen(listening_sock_, 12)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to listen server socket"; |
+ return; |
+ } |
+ { |
+ int on = 1; |
+ setsockopt(listening_sock_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); |
+ } |
+ if (!SetNonBlock(listening_sock_)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to go non block"; |
+ return; |
+ } |
+ |
+ connection_event_.reset(new struct event); |
+ event_set(connection_event_.get(), listening_sock_, EV_READ | EV_PERSIST, |
+ &OnConnect, this); |
+ event_base_set(evbase_, connection_event_.get()); |
+ if (event_add(connection_event_.get(), NULL)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to add listening event"; |
+ return; |
+ } |
+ |
+ shutdown_event_.reset(new struct event); |
+ event_set(shutdown_event_.get(), shutdown_descriptor_[0], EV_READ, |
+ &OnShutdownRequest, this); |
+ event_base_set(evbase_, shutdown_event_.get()); |
+ if (event_add(shutdown_event_.get(), NULL)) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to add shutdown event"; |
+ return; |
+ } |
+ |
+ if (evdns_init()) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to initialize evDNS"; |
+ return; |
+ } |
+ if (!IgnoreSigPipe()) { |
+ LOG(ERROR) << "WebSocketProxy: Failed to ignore SIGPIPE"; |
+ return; |
+ } |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ NewRunnableFunction(&SendNotification)); |
+ |
+ LOG(INFO) << "WebSocketProxy: Starting event dispatch loop."; |
+ event_base_dispatch(evbase_); |
+ if (shutdown_requested_) |
+ LOG(INFO) << "WebSocketProxy: Event dispatch loop terminated upon request"; |
+ else |
+ LOG(ERROR) << "WebSocketProxy: Event dispatch loop terminated unexpectedly"; |
+ CloseAll(); |
+} |
+ |
+void Serv::Shutdown() { |
+ if (1 != write(shutdown_descriptor_[1], ".", 1)) |
+ NOTREACHED(); |
+} |
+ |
+void Serv::CloseAll() { |
+ while (!conn_pool_.empty()) |
+ ZapConn(conn_pool_.back()); |
+ if (listening_sock_ >= 0) { |
+ shutdown(listening_sock_, SHUT_RDWR); |
+ close(listening_sock_); |
+ } |
+ for (int i = 0; i < 2; ++i) { |
+ if (shutdown_descriptor_[i] >= 0) { |
+ shutdown_descriptor_[i] = -1; |
+ close(shutdown_descriptor_[i]); |
+ } |
+ } |
+ if (shutdown_event_.get()) { |
+ event_del(shutdown_event_.get()); |
+ shutdown_event_.reset(); |
+ } |
+ if (connection_event_.get()) { |
+ event_del(connection_event_.get()); |
+ connection_event_.reset(); |
+ } |
+ if (evbase_) { |
+ event_base_free(evbase_); |
+ evbase_ = NULL; |
+ } |
+} |
+ |
+void Serv::ZapConn(Conn* cs) { |
+ RevMap::iterator rit = rev_map_.find(cs); |
+ if (rit != rev_map_.end()) { |
+ conn_pool_.erase(rit->second); |
+ rev_map_.erase(rit); |
+ delete cs; |
+ } |
+} |
+ |
+void Serv::MarkConnImportance(Conn* cs, bool important) { |
+ if (conn_pool_.size() < WebSocketProxy::kConnPoolLimit / 4) { |
+ // Fast common path. |
+ return; |
+ } |
+ RevMap::iterator rit = rev_map_.find(cs); |
+ if (rit != rev_map_.end()) { |
+ ConnPool::iterator it = rit->second; |
+ CHECK(*it == cs); |
+ if (important && it == conn_pool_.begin()) { |
+ // Already at the top. Shortcut. |
+ return; |
+ } |
+ conn_pool_.erase(it); |
+ } |
+ if (important) { |
+ conn_pool_.push_front(cs); |
+ rev_map_[cs] = conn_pool_.begin(); |
+ } else { |
+ conn_pool_.push_back(cs); |
+ rev_map_[cs] = conn_pool_.end(); |
+ --rev_map_[cs]; |
+ } |
+} |
+ |
+Conn* Serv::GetFreshConn() { |
+ if (conn_pool_.size() > WebSocketProxy::kConnPoolLimit) { |
+ // Connections overflow. Shut those oldest not active. |
+ ConnPool::iterator it = conn_pool_.end(); |
+ --it; |
+ for (int i = conn_pool_.size() - WebSocketProxy::kConnPoolLimit; i-- > 0;) { |
+ // Shut may invalidate an iterator; hence postdecrement. |
+ (*it--)->Shut(); |
+ } |
+ if (conn_pool_.size() > WebSocketProxy::kConnPoolLimit + 12) { |
+ // Connections overflow. Zap the oldest not active. |
+ ZapConn(conn_pool_.back()); |
+ } |
+ } |
+ Conn* cs = new Conn(this); |
+ conn_pool_.push_front(cs); |
+ rev_map_[cs] = conn_pool_.begin(); |
+ return cs; |
+} |
+ |
+bool Serv::IsConnSane(Conn* cs) { |
+ return rev_map_.find(cs) != rev_map_.end(); |
+} |
+ |
+bool Serv::IsOriginAllowed(const std::string& origin) { |
+ return allowed_origins_.empty() || std::binary_search( |
+ allowed_origins_.begin(), allowed_origins_.end(), origin); |
+} |
+ |
+// static |
+void Serv::OnConnect(int listening_sock, short event, void* ctx) { |
+ Serv* self = static_cast<Serv*>(ctx); |
+ Conn* cs = self->GetFreshConn(); |
+ cs->primchan().sock() = accept(listening_sock, NULL, NULL); |
+ if (cs->primchan().sock() < 0 |
+ || !SetNonBlock(cs->primchan().sock())) { |
+ // Read readiness was triggered on listening socket |
+ // yet we failed to accept a connection; definitely weird. |
+ NOTREACHED(); |
+ self->ZapConn(cs); |
+ return; |
+ } |
+ |
+ cs->primchan().bev() = bufferevent_new( |
+ cs->primchan().sock(), |
+ &Conn::OnPrimchanRead, &Conn::OnPrimchanWrite, &Conn::OnPrimchanError, |
+ cs->evkey()); |
+ if (cs->primchan().bev() == NULL) { |
+ self->ZapConn(cs); |
+ return; |
+ } |
+ bufferevent_base_set(self->evbase_, cs->primchan().bev()); |
+ bufferevent_setwatermark( |
+ cs->primchan().bev(), EV_READ, 0, WebSocketProxy::kReadBufferLimit); |
+ if (bufferevent_enable(cs->primchan().bev(), EV_READ | EV_WRITE)) { |
+ self->ZapConn(cs); |
+ return; |
+ } |
+} |
+ |
+// static |
+void Serv::OnShutdownRequest(int fd, short event, void* ctx) { |
+ Serv* self = static_cast<Serv*>(ctx); |
+ self->shutdown_requested_ = true; |
+ event_base_loopbreak(self->evbase_); |
+} |
+ |
+Conn::Conn(Serv* master) |
+ : master_(master), |
+ phase_(PHASE_WAIT_HANDSHAKE), |
+ primchan_(this), |
+ destchan_(this), |
+ destresolution_ipv4_failed_(false), |
+ destresolution_ipv6_failed_(false) { |
+ while (evkey_map_.find(last_evkey_) != evkey_map_.end()) { |
+ evkey_ = last_evkey_ = |
+ reinterpret_cast<EventKey>(reinterpret_cast<size_t>(last_evkey_) + 1); |
+ } |
+ evkey_map_[evkey_] = this; |
+ // Schedule timeout for initial phase of connection. |
+ destconnect_timeout_event_.reset(new struct event); |
+ evtimer_set(destconnect_timeout_event_.get(), |
+ &OnDestConnectTimeout, evkey_); |
+ event_base_set(master_->evbase(), |
+ destconnect_timeout_event_.get()); |
+ |
+ struct timeval tv; |
+ tv.tv_sec = 20; |
+ tv.tv_usec = 0; |
+ evtimer_add(destconnect_timeout_event_.get(), &tv); |
+} |
+ |
+Conn::~Conn() { |
+ phase_ = PHASE_DEFUNCT; |
+ event_del(destconnect_timeout_event_.get()); |
+ if (evkey_map_[evkey_] == this) |
+ evkey_map_.erase(evkey_); |
+ else |
+ NOTREACHED(); |
+} |
+ |
+Conn* Conn::Get(EventKey evkey) { |
+ EventKeyMap::iterator it = evkey_map_.find(evkey); |
+ if (it == evkey_map_.end()) |
+ return NULL; |
+ Conn* cs = it->second; |
+ if (cs == NULL || |
+ cs->evkey_ != evkey || |
+ cs->master_ == NULL || |
+ cs->phase_ < 0 || |
+ cs->phase_ > PHASE_SHUT || |
+ !cs->master_->IsConnSane(cs)) { |
+ return NULL; |
+ } |
+ return cs; |
+} |
+ |
+void Conn::Shut() { |
+ if (phase_ >= PHASE_SHUT) |
+ return; |
+ master_->MarkConnImportance(this, false); |
+ static const uint8 closing_handshake[9] = { 0 }; |
+ primchan_.Write(closing_handshake, sizeof(closing_handshake)); |
+ primchan_.Shut(); |
+ destchan_.Shut(); |
+ phase_ = PHASE_SHUT; |
+} |
+ |
+void Conn::ConsiderSuicide() { |
+ if (!primchan_.write_pending() && !destchan_.write_pending()) |
+ master_->ZapConn(this); |
+} |
+ |
+Conn::Status Conn::ConsumeHeader(struct evbuffer* evb) { |
+ uint8* buf = EVBUFFER_DATA(evb); |
+ size_t buf_size = EVBUFFER_LENGTH(evb); |
+ |
+ static const uint8 kGetMagic[] = "GET " kProxyPath " "; |
+ static const uint8 kKeyValueDelimiter[] = ": "; |
+ |
+ if (buf_size <= 0) |
+ return STATUS_INCOMPLETE; |
+ if (!buf) |
+ return STATUS_ABORT; |
+ if (!std::equal(buf, buf + std::min(buf_size, strlen(kGetMagic)), |
+ kGetMagic)) { |
+ // Data head does not match what is expected. |
+ return STATUS_ABORT; |
+ } |
+ |
+ if (buf_size >= WebSocketProxy::kHeaderLimit) |
+ return STATUS_ABORT; |
+ uint8* buf_end = buf + buf_size; |
+ uint8* term_pos = std::search(buf, buf_end, kCRLFCRLF, |
+ kCRLFCRLF + strlen(kCRLFCRLF)); |
+ uint8 key3[8]; // Notation (key3) matches websocket RFC. |
+ if (buf_end - term_pos - strlen(kCRLFCRLF) < sizeof(key3)) |
+ return STATUS_INCOMPLETE; |
+ term_pos += strlen(kCRLFCRLF); |
+ memcpy(key3, term_pos, sizeof(key3)); |
+ term_pos += sizeof(key3); |
+ // First line is "GET /tcpproxy" line, so we skip it. |
+ uint8* pos = std::search(buf, term_pos, kCRLF, kCRLF + strlen(kCRLF)); |
+ if (pos == term_pos) |
+ return STATUS_ABORT; |
+ for (;;) { |
+ pos += strlen(kCRLF); |
+ if (term_pos - pos < |
+ static_cast<ptrdiff_t>(sizeof(key3) + strlen(kCRLF))) { |
+ return STATUS_ABORT; |
+ } |
+ if (term_pos - pos == |
+ static_cast<ptrdiff_t>(sizeof(key3) + strlen(kCRLF))) { |
+ break; |
+ } |
+ uint8* npos = std::search(pos, term_pos, kKeyValueDelimiter, |
+ kKeyValueDelimiter + strlen(kKeyValueDelimiter)); |
+ if (npos == term_pos) |
+ return STATUS_ABORT; |
+ std::string key = FetchLowerCasedASCIISnippet(pos, npos); |
+ pos = std::search(npos += strlen(kKeyValueDelimiter), term_pos, |
+ kCRLF, kCRLF + strlen(kCRLF)); |
+ if (pos == term_pos) |
+ return STATUS_ABORT; |
+ if (!key.empty()) |
+ header_fields_[key] = FetchLowerCasedASCIISnippet(npos, pos); |
+ } |
+ |
+ // Values of Upgrade and Connection fields are hardcoded in the protocol. |
+ if (header_fields_["upgrade"] != "websocket" || |
+ header_fields_["connection"] != "upgrade") { |
+ return STATUS_ABORT; |
+ } |
+ |
+ if (!master_->IsOriginAllowed(header_fields_["origin"])) |
+ return STATUS_ABORT; |
+ |
+ static const std::string kSecKey1 = "sec-websocket-key1"; |
+ static const std::string kSecKey2 = "sec-websocket-key2"; |
+ uint32 key_number1, key_number2; |
+ if (!FetchDecimalDigits(header_fields_[kSecKey1], |
+ &key_number1) || |
+ !FetchDecimalDigits(header_fields_[kSecKey2], |
+ &key_number2)) { |
+ return STATUS_ABORT; |
+ } |
+ |
+ // We limit incoming header size so following numbers shall not be too high. |
+ int spaces1 = CountSpaces(header_fields_[kSecKey1]); |
+ int spaces2 = CountSpaces(header_fields_[kSecKey2]); |
+ if (spaces1 == 0 || |
+ spaces2 == 0 || |
+ key_number1 % spaces1 != 0 || |
+ key_number2 % spaces2 != 0) { |
+ return STATUS_ABORT; |
+ } |
+ |
+ uint8 challenge[4 + 4 + sizeof(key3)]; |
+ uint32 part1 = htonl(key_number1 / spaces1); |
+ uint32 part2 = htonl(key_number2 / spaces2); |
+ memcpy(challenge, &part1, 4); |
+ memcpy(challenge + 4, &part2, 4); |
+ memcpy(challenge + sizeof(challenge) - sizeof(key3), key3, sizeof(key3)); |
+ MD5Sum(challenge, sizeof(challenge), &handshake_response_); |
+ |
+ evbuffer_drain(evb, term_pos - buf); |
+ return STATUS_OK; |
+} |
+ |
+bool Conn::EmitHandshake(Chan* chan) { |
+ std::vector<std::string> boilerplate; |
+ boilerplate.push_back("HTTP/1.1 101 WebSocket Protocol Handshake"); |
+ boilerplate.push_back("Upgrade: WebSocket"); |
+ boilerplate.push_back("Connection: Upgrade"); |
+ |
+ { |
+ // Take care of Location field. |
+ char buf[128]; |
+ int rv = snprintf(buf, sizeof(buf), |
+ "Sec-WebSocket-Location: ws://%s%s", |
+ header_fields_["host"].c_str(), |
+ kProxyPath); |
+ if (rv <= 0 || rv + 0u >= sizeof(buf)) |
+ return false; |
+ boilerplate.push_back(buf); |
+ } |
+ { |
+ // Take care of Origin field. |
+ if (header_fields_.find("origin") != header_fields_.end()) { |
+ char buf[128]; |
+ int rv = snprintf(buf, sizeof(buf), |
+ "Sec-WebSocket-Origin: %s", |
+ header_fields_["origin"].c_str()); |
+ if (rv <= 0 || rv + 0u >= sizeof(buf)) |
+ return false; |
+ boilerplate.push_back(buf); |
+ } |
+ } |
+ |
+ boilerplate.push_back(""); |
+ for (size_t i = 0; i < boilerplate.size(); ++i) { |
+ if (!chan->Write(boilerplate[i].c_str(), boilerplate[i].size()) || |
+ !chan->Write(kCRLF, strlen(kCRLF))) { |
+ return false; |
+ } |
+ } |
+ return chan->Write(&handshake_response_, sizeof(handshake_response_)); |
+} |
+ |
+Conn::Status Conn::ConsumeDestframe(struct evbuffer* evb) { |
+ uint8* buf = EVBUFFER_DATA(evb); |
+ size_t buf_size = EVBUFFER_LENGTH(evb); |
+ |
+ if (buf_size < 1) |
+ return STATUS_INCOMPLETE; |
+ if (buf[0] != 0) |
+ return STATUS_ABORT; |
+ if (buf_size < 1 + 1) |
+ return STATUS_INCOMPLETE; |
+ uint8* buf_end = buf + buf_size; |
+ uint8* term_pos = std::find(buf + 1, buf_end, 0xff); |
+ if (term_pos == buf_end) { |
+ if (buf_size >= WebSocketProxy::kHeaderLimit) { |
+ // So big and still worth nothing. |
+ return STATUS_ABORT; |
+ } |
+ return STATUS_INCOMPLETE; |
+ } |
+ |
+ std::string token; |
+ if (!FetchTokenNamePort(buf + 1, term_pos, &token, &destname_, &destport_)) |
+ return STATUS_ABORT; |
+ // TODO(dilmah): enable this once webSocketProxyPrivate.getToken is wired. |
+#if 0 |
+ std::map<std::string, std::string> map; |
+ map["hostname"] = destname_; |
+ map["port"] = base::IntToString(destport_); |
+ map["extension_id"] = FetchExtensionIdFromOrigin(header_fields_["origin"]); |
+ if (!browser::InternalAuthVerification::VerifyToken( |
+ "web_socket_proxy", token, map)) { |
+ return STATUS_ABORT; |
+ } |
+#endif |
+ |
+ evbuffer_drain(evb, term_pos - buf + 1); |
+ return STATUS_OK; |
+} |
+ |
+Conn::Status Conn::ConsumeFrameHeader(struct evbuffer* evb) { |
+ uint8* buf = EVBUFFER_DATA(evb); |
+ size_t buf_size = EVBUFFER_LENGTH(evb); |
+ |
+ if (buf_size < 1) |
+ return STATUS_INCOMPLETE; |
+ if (buf[0] != 0) |
+ return STATUS_ABORT; |
+ evbuffer_drain(evb, 1); |
+ return STATUS_OK; |
+} |
+ |
+Conn::Status Conn::ProcessFrameData(struct evbuffer* evb) { |
+ uint8* buf = EVBUFFER_DATA(evb); |
+ size_t buf_size = EVBUFFER_LENGTH(evb); |
+ |
+ if (buf_size < 1) |
+ return STATUS_INCOMPLETE; |
+ uint8* buf_end = buf + buf_size; |
+ uint8* term_pos = std::find(buf, buf_end, 0xff); |
+ bool term_detected = (term_pos != buf_end); |
+ if (term_detected) |
+ buf_size = term_pos - buf; |
+ switch (phase_) { |
+ case PHASE_INSIDE_FRAME_BASE64: { |
+ if (term_detected && buf_size % 4) { |
+ // base64 is encoded in chunks of 4 bytes. |
+ return STATUS_ABORT; |
+ } |
+ if (buf_size < 4) { |
+ DCHECK(!term_detected); |
+ return STATUS_INCOMPLETE; |
+ } |
+ size_t bytes_to_process_atm = (buf_size / 4) * 4; |
+ std::string out_bytes; |
+ base::Base64Decode(std::string(buf, buf + bytes_to_process_atm), |
+ &out_bytes); |
+ evbuffer_drain(evb, bytes_to_process_atm); |
+ DCHECK(destchan_.bev() != NULL); |
+ if (!destchan_.Write(out_bytes.c_str(), out_bytes.size())) |
+ return STATUS_ABORT; |
+ break; |
+ } |
+ case PHASE_INSIDE_FRAME_SKIP: { |
+ evbuffer_drain(evb, buf_size); |
+ break; |
+ } |
+ default: { |
+ return STATUS_ABORT; |
+ } |
+ } |
+ if (term_detected) { |
+ evbuffer_drain(evb, 1); |
+ return STATUS_OK; |
+ } |
+ return STATUS_INCOMPLETE; |
+} |
+ |
+bool Conn::TryConnectDest(const struct sockaddr* addr, |
+ socklen_t addrlen) { |
+ if (destchan_.sock() >= 0 || destchan_.bev() != NULL) |
+ return false; |
+ destchan_.sock() = socket(addr->sa_family, SOCK_STREAM, 0); |
+ if (destchan_.sock() < 0) |
+ return false; |
+ if (!SetNonBlock(destchan_.sock())) |
+ return false; |
+ if (connect(destchan_.sock(), addr, addrlen)) { |
+ if (errno != EINPROGRESS) |
+ return false; |
+ } |
+ destchan_.bev() = bufferevent_new( |
+ destchan_.sock(), |
+ &OnDestchanRead, &OnDestchanWrite, &OnDestchanError, |
+ evkey_); |
+ if (destchan_.bev() == NULL) |
+ return false; |
+ if (bufferevent_base_set(master_->evbase(), destchan_.bev())) |
+ return false; |
+ bufferevent_setwatermark( |
+ destchan_.bev(), EV_READ, 0, WebSocketProxy::kReadBufferLimit); |
+ return !bufferevent_enable(destchan_.bev(), EV_READ | EV_WRITE); |
+} |
+ |
+// static |
+void Conn::OnPrimchanRead(struct bufferevent* bev, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->primchan_.bev()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) <= 0) |
+ return; |
+ cs->master_->MarkConnImportance(cs, true); |
+ for (;;) { |
+ switch (cs->phase_) { |
+ case PHASE_WAIT_HANDSHAKE: { |
+ switch (cs->ConsumeHeader(EVBUFFER_INPUT(bev))) { |
+ case STATUS_OK: { |
+ break; |
+ } |
+ case STATUS_INCOMPLETE: { |
+ return; |
+ } |
+ case STATUS_ABORT: |
+ default: { |
+ cs->master_->ZapConn(cs); |
+ return; |
+ } |
+ } |
+ // Header consumed OK. Do respond. |
+ if (!cs->EmitHandshake(&cs->primchan_)) { |
+ cs->master_->ZapConn(cs); |
+ return; |
+ } |
+ cs->phase_ = PHASE_WAIT_DESTFRAME; |
+ return; |
+ } |
+ case PHASE_WAIT_DESTFRAME: { |
+ switch (cs->ConsumeDestframe(EVBUFFER_INPUT(bev))) { |
+ case STATUS_OK: { |
+ { |
+ struct sockaddr_in sa; |
+ memset(&sa, 0, sizeof(sa)); |
+ sa.sin_port = htons(cs->destport_); |
+ if (inet_pton(sa.sin_family = AF_INET, |
+ cs->destname_.c_str(), |
+ &sa.sin_addr) == 1) { |
+ // valid IPv4 address supplied. |
+ if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
+ cs->phase_ = PHASE_WAIT_DESTCONNECT; |
+ return; |
+ } |
+ } |
+ } |
+ { |
+ if (cs->destname_.size() >= 2 && |
+ cs->destname_[0] == '[' && |
+ cs->destname_[cs->destname_.size() - 1] == ']') { |
+ // Literal IPv6 address in brackets. |
+ cs->destname_ = |
+ cs->destname_.substr(1, cs->destname_.size() - 2); |
+ } |
+ struct sockaddr_in6 sa; |
+ memset(&sa, 0, sizeof(sa)); |
+ sa.sin6_port = htons(cs->destport_); |
+ if (inet_pton(sa.sin6_family = AF_INET6, |
+ cs->destname_.c_str(), |
+ &sa.sin6_addr) == 1) { |
+ // valid IPv6 address supplied. |
+ if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) { |
+ cs->phase_ = PHASE_WAIT_DESTCONNECT; |
+ return; |
+ } |
+ } |
+ } |
+ // Try to asynchronously perform DNS resolution. |
+ evdns_resolve_ipv4(cs->destname_.c_str(), 0, |
+ &OnDestResolutionIPv4, evkey); |
+ evdns_resolve_ipv6(cs->destname_.c_str(), 0, |
+ &OnDestResolutionIPv6, evkey); |
+ cs->phase_ = PHASE_WAIT_DESTCONNECT; |
+ return; |
+ } |
+ case STATUS_INCOMPLETE: { |
+ return; |
+ } |
+ case STATUS_ABORT: |
+ default: { |
+ cs->Shut(); |
+ return; |
+ } |
+ } |
+ } |
+ case PHASE_WAIT_DESTCONNECT: { |
+ if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) >= |
+ WebSocketProxy::kReadBufferLimit) { |
+ cs->Shut(); |
+ } |
+ return; |
+ } |
+ case PHASE_OUTSIDE_FRAME: { |
+ switch (cs->ConsumeFrameHeader(EVBUFFER_INPUT(bev))) { |
+ case STATUS_OK: { |
+ cs->phase_ = PHASE_INSIDE_FRAME_BASE64; |
+ // Process remaining data if any. |
+ break; |
+ } |
+ case STATUS_SKIP: { |
+ cs->phase_ = PHASE_INSIDE_FRAME_SKIP; |
+ // Process remaining data if any. |
+ break; |
+ } |
+ case STATUS_INCOMPLETE: { |
+ return; |
+ } |
+ case STATUS_ABORT: |
+ default: { |
+ cs->Shut(); |
+ return; |
+ } |
+ } |
+ break; |
+ } |
+ case PHASE_INSIDE_FRAME_BASE64: |
+ case PHASE_INSIDE_FRAME_SKIP: { |
+ switch (cs->ProcessFrameData(EVBUFFER_INPUT(bev))) { |
+ case STATUS_OK: { |
+ cs->phase_ = PHASE_OUTSIDE_FRAME; |
+ // Handle remaining data if any. |
+ break; |
+ } |
+ case STATUS_INCOMPLETE: { |
+ return; |
+ } |
+ case STATUS_ABORT: |
+ default: { |
+ cs->Shut(); |
+ return; |
+ } |
+ } |
+ break; |
+ } |
+ case PHASE_SHUT: { |
+ evbuffer_drain(EVBUFFER_INPUT(bev), |
+ EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))); |
+ return; |
+ } |
+ case PHASE_DEFUNCT: |
+ default: { |
+ NOTREACHED(); |
+ cs->master_->ZapConn(cs); |
+ return; |
+ } |
+ } |
+ } |
+} |
+ |
+// static |
+void Conn::OnPrimchanWrite(struct bufferevent* bev, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->primchan_.bev()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ cs->primchan_.write_pending() = false; |
+ if (cs->phase_ >= PHASE_SHUT) { |
+ cs->master_->ZapConn(cs); |
+ return; |
+ } |
+ if (cs->phase_ > PHASE_WAIT_DESTCONNECT) |
+ OnDestchanRead(cs->destchan_.bev(), evkey); |
+ if (cs->phase_ >= PHASE_SHUT) |
+ cs->primchan_.Zap(); |
+} |
+ |
+// static |
+void Conn::OnPrimchanError(struct bufferevent* bev, |
+ short what, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->primchan_.bev()) { |
+ return; |
+ } |
+ cs->primchan_.write_pending() = false; |
+ if (cs->phase_ >= PHASE_SHUT) |
+ cs->master_->ZapConn(cs); |
+ else |
+ cs->Shut(); |
+} |
+ |
+// static |
+void Conn::OnDestResolutionIPv4(int result, char type, |
+ int count, int ttl, |
+ void* addr_list, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (cs == NULL) |
+ return; |
+ if (cs->phase_ != PHASE_WAIT_DESTCONNECT) |
+ return; |
+ if (result == DNS_ERR_NONE && |
+ count >= 1 && |
+ addr_list != NULL && |
+ type == DNS_IPv4_A) { |
+ for (int i = 0; i < count; ++i) { |
+ struct sockaddr_in sa; |
+ memset(&sa, 0, sizeof(sa)); |
+ sa.sin_family = AF_INET; |
+ sa.sin_port = htons(cs->destport_); |
+ DCHECK(sizeof(sa.sin_addr) == sizeof(struct in_addr)); |
+ memcpy(&sa.sin_addr, |
+ static_cast<struct in_addr*>(addr_list) + i, |
+ sizeof(sa.sin_addr)); |
+ if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) |
+ return; |
+ } |
+ } |
+ cs->destresolution_ipv4_failed_ = true; |
+ if (cs->destresolution_ipv4_failed_ && cs->destresolution_ipv6_failed_) |
+ cs->Shut(); |
+} |
+ |
+// static |
+void Conn::OnDestResolutionIPv6(int result, char type, |
+ int count, int ttl, |
+ void* addr_list, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (cs == NULL) |
+ return; |
+ if (cs->phase_ != PHASE_WAIT_DESTCONNECT) |
+ return; |
+ if (result == DNS_ERR_NONE && |
+ count >= 1 && |
+ addr_list != NULL && |
+ type == DNS_IPv6_AAAA) { |
+ for (int i = 0; i < count; ++i) { |
+ struct sockaddr_in6 sa; |
+ memset(&sa, 0, sizeof(sa)); |
+ sa.sin6_family = AF_INET6; |
+ sa.sin6_port = htons(cs->destport_); |
+ DCHECK(sizeof(sa.sin6_addr) == sizeof(struct in6_addr)); |
+ memcpy(&sa.sin6_addr, |
+ static_cast<struct in6_addr*>(addr_list) + i, |
+ sizeof(sa.sin6_addr)); |
+ if (cs->TryConnectDest((struct sockaddr*)&sa, sizeof(sa))) |
+ return; |
+ } |
+ } |
+ cs->destresolution_ipv6_failed_ = true; |
+ if (cs->destresolution_ipv4_failed_ && cs->destresolution_ipv6_failed_) |
+ cs->Shut(); |
+} |
+ |
+// static |
+void Conn::OnDestConnectTimeout(int, short, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (cs == NULL) |
+ return; |
+ if (cs->phase_ > PHASE_WAIT_DESTCONNECT) |
+ return; |
+ cs->Shut(); |
+} |
+ |
+// static |
+void Conn::OnDestchanRead(struct bufferevent* bev, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->destchan_.bev()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) <= 0) |
+ return; |
+ if (cs->primchan_.bev() == NULL) { |
+ cs->master_->ZapConn(cs); |
+ return; |
+ } |
+ cs->master_->MarkConnImportance(cs, true); |
+ std::string out_bytes; |
+ base::Base64Encode( |
+ std::string( |
+ static_cast<const char*>(static_cast<void*>( |
+ EVBUFFER_DATA(EVBUFFER_INPUT(bev)))), |
+ EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))), |
+ &out_bytes); |
+ evbuffer_drain(EVBUFFER_INPUT(bev), EVBUFFER_LENGTH(EVBUFFER_INPUT(bev))); |
+ static const uint8 frame_header[] = { 0x00 }; |
+ static const uint8 frame_terminator[] = { 0xff }; |
+ if (!cs->primchan_.Write(frame_header, sizeof(frame_header)) || |
+ !cs->primchan_.Write(out_bytes.c_str(), out_bytes.size()) || |
+ !cs->primchan_.Write(frame_terminator, sizeof(frame_terminator))) { |
+ cs->Shut(); |
+ } |
+} |
+ |
+// static |
+void Conn::OnDestchanWrite(struct bufferevent* bev, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->destchan_.bev()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ cs->destchan_.write_pending() = false; |
+ if (cs->phase_ == PHASE_WAIT_DESTCONNECT) |
+ cs->phase_ = PHASE_OUTSIDE_FRAME; |
+ if (cs->phase_ < PHASE_SHUT) |
+ OnPrimchanRead(cs->primchan_.bev(), evkey); |
+ else |
+ cs->destchan_.Zap(); |
+} |
+ |
+// static |
+void Conn::OnDestchanError(struct bufferevent* bev, |
+ short what, EventKey evkey) { |
+ Conn* cs = Conn::Get(evkey); |
+ if (bev == NULL || |
+ cs == NULL || |
+ bev != cs->destchan_.bev()) { |
+ return; |
+ } |
+ cs->destchan_.write_pending() = false; |
+ if (cs->phase_ >= PHASE_SHUT) |
+ cs->master_->ZapConn(cs); |
+ else |
+ cs->Shut(); |
+} |
+ |
+Conn::EventKey Conn::last_evkey_ = 0; |
+Conn::EventKeyMap Conn::evkey_map_; |
+ |
+} // namespace |
+ |
+WebSocketProxy::WebSocketProxy( |
+ const std::vector<std::string>& allowed_origins, |
+ struct sockaddr* addr, int addr_len) |
+ : impl_(new Serv(allowed_origins, addr, addr_len)) { |
+} |
+ |
+WebSocketProxy::~WebSocketProxy() { |
+ delete static_cast<Serv*>(impl_); |
+ impl_ = NULL; |
+} |
+ |
+void WebSocketProxy::Run() { |
+ static_cast<Serv*>(impl_)->Run(); |
+} |
+ |
+void WebSocketProxy::Shutdown() { |
+ static_cast<Serv*>(impl_)->Shutdown(); |
+} |
+ |
+} // namespace chromeos |
+ |