1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/pepper/pepper_websocket_host.h"
6
7#include <string>
8
9#include "content/public/renderer/renderer_ppapi_host.h"
10#include "net/base/net_util.h"
11#include "ppapi/c/pp_errors.h"
12#include "ppapi/c/ppb_websocket.h"
13#include "ppapi/host/dispatch_host_message.h"
14#include "ppapi/host/host_message_context.h"
15#include "ppapi/host/ppapi_host.h"
16#include "ppapi/proxy/ppapi_messages.h"
17#include "third_party/WebKit/public/platform/WebArrayBuffer.h"
18#include "third_party/WebKit/public/platform/WebString.h"
19#include "third_party/WebKit/public/platform/WebURL.h"
20#include "third_party/WebKit/public/web/WebDocument.h"
21#include "third_party/WebKit/public/web/WebElement.h"
22#include "third_party/WebKit/public/web/WebPluginContainer.h"
23#include "third_party/WebKit/public/web/WebSocket.h"
24
25using blink::WebArrayBuffer;
26using blink::WebDocument;
27using blink::WebString;
28using blink::WebSocket;
29using blink::WebURL;
30
31namespace content {
32
33#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, np_name)                   \
34  COMPILE_ASSERT(                                                            \
35      static_cast<int>(WebSocket::webkit_name) == static_cast<int>(np_name), \
36      mismatching_enums)
37
38COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeNormalClosure,
39                             PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE);
40COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeGoingAway,
41                             PP_WEBSOCKETSTATUSCODE_GOING_AWAY);
42COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeProtocolError,
43                             PP_WEBSOCKETSTATUSCODE_PROTOCOL_ERROR);
44COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeUnsupportedData,
45                             PP_WEBSOCKETSTATUSCODE_UNSUPPORTED_DATA);
46COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeNoStatusRcvd,
47                             PP_WEBSOCKETSTATUSCODE_NO_STATUS_RECEIVED);
48COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeAbnormalClosure,
49                             PP_WEBSOCKETSTATUSCODE_ABNORMAL_CLOSURE);
50COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeInvalidFramePayloadData,
51                             PP_WEBSOCKETSTATUSCODE_INVALID_FRAME_PAYLOAD_DATA);
52COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodePolicyViolation,
53                             PP_WEBSOCKETSTATUSCODE_POLICY_VIOLATION);
54COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeMessageTooBig,
55                             PP_WEBSOCKETSTATUSCODE_MESSAGE_TOO_BIG);
56COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeMandatoryExt,
57                             PP_WEBSOCKETSTATUSCODE_MANDATORY_EXTENSION);
58COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeInternalError,
59                             PP_WEBSOCKETSTATUSCODE_INTERNAL_SERVER_ERROR);
60COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeTLSHandshake,
61                             PP_WEBSOCKETSTATUSCODE_TLS_HANDSHAKE);
62COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeMinimumUserDefined,
63                             PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN);
64COMPILE_ASSERT_MATCHING_ENUM(CloseEventCodeMaximumUserDefined,
65                             PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX);
66
67PepperWebSocketHost::PepperWebSocketHost(RendererPpapiHost* host,
68                                         PP_Instance instance,
69                                         PP_Resource resource)
70    : ResourceHost(host->GetPpapiHost(), instance, resource),
71      renderer_ppapi_host_(host),
72      connecting_(false),
73      initiating_close_(false),
74      accepting_close_(false),
75      error_was_received_(false) {}
76
77PepperWebSocketHost::~PepperWebSocketHost() {
78  if (websocket_)
79    websocket_->disconnect();
80}
81
82int32_t PepperWebSocketHost::OnResourceMessageReceived(
83    const IPC::Message& msg,
84    ppapi::host::HostMessageContext* context) {
85  PPAPI_BEGIN_MESSAGE_MAP(PepperWebSocketHost, msg)
86    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_WebSocket_Connect,
87                                      OnHostMsgConnect)
88    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_WebSocket_Close,
89                                      OnHostMsgClose)
90    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_WebSocket_SendText,
91                                      OnHostMsgSendText)
92    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_WebSocket_SendBinary,
93                                      OnHostMsgSendBinary)
94    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_WebSocket_Fail,
95                                      OnHostMsgFail)
96  PPAPI_END_MESSAGE_MAP()
97  return PP_ERROR_FAILED;
98}
99
100void PepperWebSocketHost::didConnect() {
101  std::string protocol;
102  if (websocket_)
103    protocol = websocket_->subprotocol().utf8();
104  connecting_ = false;
105  connect_reply_.params.set_result(PP_OK);
106  host()->SendReply(connect_reply_,
107                    PpapiPluginMsg_WebSocket_ConnectReply(url_, protocol));
108}
109
110void PepperWebSocketHost::didReceiveMessage(const blink::WebString& message) {
111  // Dispose packets after receiving an error.
112  if (error_was_received_)
113    return;
114
115  // Send an IPC to transport received data.
116  std::string string_message = message.utf8();
117  host()->SendUnsolicitedReply(
118      pp_resource(), PpapiPluginMsg_WebSocket_ReceiveTextReply(string_message));
119}
120
121void PepperWebSocketHost::didReceiveArrayBuffer(
122    const blink::WebArrayBuffer& binaryData) {
123  // Dispose packets after receiving an error.
124  if (error_was_received_)
125    return;
126
127  // Send an IPC to transport received data.
128  uint8_t* data = static_cast<uint8_t*>(binaryData.data());
129  std::vector<uint8_t> array_message(data, data + binaryData.byteLength());
130  host()->SendUnsolicitedReply(
131      pp_resource(),
132      PpapiPluginMsg_WebSocket_ReceiveBinaryReply(array_message));
133}
134
135void PepperWebSocketHost::didReceiveMessageError() {
136  // Records the error, then stops receiving any frames after this error.
137  // The error must be notified after all queued messages are read.
138  error_was_received_ = true;
139
140  // Send an IPC to report the error. After this IPC, ReceiveTextReply and
141  // ReceiveBinaryReply IPC are not sent anymore because |error_was_received_|
142  // blocks.
143  host()->SendUnsolicitedReply(pp_resource(),
144                               PpapiPluginMsg_WebSocket_ErrorReply());
145}
146
147void PepperWebSocketHost::didUpdateBufferedAmount(
148    unsigned long buffered_amount) {
149  // Send an IPC to update buffered amount.
150  host()->SendUnsolicitedReply(
151      pp_resource(),
152      PpapiPluginMsg_WebSocket_BufferedAmountReply(buffered_amount));
153}
154
155void PepperWebSocketHost::didStartClosingHandshake() {
156  accepting_close_ = true;
157
158  // Send an IPC to notice that server starts closing handshake.
159  host()->SendUnsolicitedReply(
160      pp_resource(),
161      PpapiPluginMsg_WebSocket_StateReply(PP_WEBSOCKETREADYSTATE_CLOSING));
162}
163
164void PepperWebSocketHost::didClose(unsigned long unhandled_buffered_amount,
165                                   ClosingHandshakeCompletionStatus status,
166                                   unsigned short code,
167                                   const blink::WebString& reason) {
168  if (connecting_) {
169    connecting_ = false;
170    connect_reply_.params.set_result(PP_ERROR_FAILED);
171    host()->SendReply(
172        connect_reply_,
173        PpapiPluginMsg_WebSocket_ConnectReply(url_, std::string()));
174  }
175
176  // Set close_was_clean_.
177  bool was_clean = (initiating_close_ || accepting_close_) &&
178                   !unhandled_buffered_amount &&
179                   status == WebSocketClient::ClosingHandshakeComplete;
180
181  if (initiating_close_) {
182    initiating_close_ = false;
183    close_reply_.params.set_result(PP_OK);
184    host()->SendReply(
185        close_reply_,
186        PpapiPluginMsg_WebSocket_CloseReply(
187            unhandled_buffered_amount, was_clean, code, reason.utf8()));
188  } else {
189    accepting_close_ = false;
190    host()->SendUnsolicitedReply(
191        pp_resource(),
192        PpapiPluginMsg_WebSocket_ClosedReply(
193            unhandled_buffered_amount, was_clean, code, reason.utf8()));
194  }
195
196  // Disconnect.
197  if (websocket_) {
198    websocket_->disconnect();
199    websocket_.reset();
200  }
201}
202
203int32_t PepperWebSocketHost::OnHostMsgConnect(
204    ppapi::host::HostMessageContext* context,
205    const std::string& url,
206    const std::vector<std::string>& protocols) {
207  // Validate url and convert it to WebURL.
208  GURL gurl(url);
209  url_ = gurl.spec();
210  if (!gurl.is_valid())
211    return PP_ERROR_BADARGUMENT;
212  if (!gurl.SchemeIs("ws") && !gurl.SchemeIs("wss"))
213    return PP_ERROR_BADARGUMENT;
214  if (gurl.has_ref())
215    return PP_ERROR_BADARGUMENT;
216  if (!net::IsPortAllowedByDefault(gurl.IntPort()))
217    return PP_ERROR_BADARGUMENT;
218  WebURL web_url(gurl);
219
220  // Validate protocols.
221  std::string protocol_string;
222  for (std::vector<std::string>::const_iterator vector_it = protocols.begin();
223       vector_it != protocols.end();
224       ++vector_it) {
225
226    // Check containing characters.
227    for (std::string::const_iterator string_it = vector_it->begin();
228         string_it != vector_it->end();
229         ++string_it) {
230      uint8_t character = *string_it;
231      // WebSocket specification says "(Subprotocol string must consist of)
232      // characters in the range U+0021 to U+007E not including separator
233      // characters as defined in [RFC2616]."
234      const uint8_t minimumProtocolCharacter = '!';  // U+0021.
235      const uint8_t maximumProtocolCharacter = '~';  // U+007E.
236      if (character < minimumProtocolCharacter ||
237          character > maximumProtocolCharacter || character == '"' ||
238          character == '(' || character == ')' || character == ',' ||
239          character == '/' ||
240          (character >= ':' && character <= '@') ||  // U+003A - U+0040
241          (character >= '[' && character <= ']') ||  // U+005B - u+005D
242          character == '{' ||
243          character == '}')
244        return PP_ERROR_BADARGUMENT;
245    }
246    // Join protocols with the comma separator.
247    if (vector_it != protocols.begin())
248      protocol_string.append(",");
249    protocol_string.append(*vector_it);
250  }
251
252  // Convert protocols to WebString.
253  WebString web_protocols = WebString::fromUTF8(protocol_string);
254
255  // Create blink::WebSocket object and connect.
256  blink::WebPluginContainer* container =
257      renderer_ppapi_host_->GetContainerForInstance(pp_instance());
258  if (!container)
259    return PP_ERROR_BADARGUMENT;
260  // TODO(toyoshim) Remove following WebDocument object copy.
261  WebDocument document = container->element().document();
262  websocket_.reset(WebSocket::create(document, this));
263  DCHECK(websocket_.get());
264  if (!websocket_)
265    return PP_ERROR_NOTSUPPORTED;
266
267  // Set receiving binary object type.
268  websocket_->setBinaryType(WebSocket::BinaryTypeArrayBuffer);
269  websocket_->connect(web_url, web_protocols);
270
271  connect_reply_ = context->MakeReplyMessageContext();
272  connecting_ = true;
273  return PP_OK_COMPLETIONPENDING;
274}
275
276int32_t PepperWebSocketHost::OnHostMsgClose(
277    ppapi::host::HostMessageContext* context,
278    int32_t code,
279    const std::string& reason) {
280  if (!websocket_)
281    return PP_ERROR_FAILED;
282  close_reply_ = context->MakeReplyMessageContext();
283  initiating_close_ = true;
284
285  blink::WebSocket::CloseEventCode event_code =
286      static_cast<blink::WebSocket::CloseEventCode>(code);
287  if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
288    // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are
289    // assigned to different values. A conversion is needed if
290    // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified.
291    event_code = blink::WebSocket::CloseEventCodeNotSpecified;
292  }
293
294  WebString web_reason = WebString::fromUTF8(reason);
295  websocket_->close(event_code, web_reason);
296  return PP_OK_COMPLETIONPENDING;
297}
298
299int32_t PepperWebSocketHost::OnHostMsgSendText(
300    ppapi::host::HostMessageContext* context,
301    const std::string& message) {
302  if (websocket_) {
303    WebString web_message = WebString::fromUTF8(message);
304    websocket_->sendText(web_message);
305  }
306  return PP_OK;
307}
308
309int32_t PepperWebSocketHost::OnHostMsgSendBinary(
310    ppapi::host::HostMessageContext* context,
311    const std::vector<uint8_t>& message) {
312  if (websocket_.get() && !message.empty()) {
313    WebArrayBuffer web_message = WebArrayBuffer::create(message.size(), 1);
314    memcpy(web_message.data(), &message.front(), message.size());
315    websocket_->sendArrayBuffer(web_message);
316  }
317  return PP_OK;
318}
319
320int32_t PepperWebSocketHost::OnHostMsgFail(
321    ppapi::host::HostMessageContext* context,
322    const std::string& message) {
323  if (websocket_)
324    websocket_->fail(WebString::fromUTF8(message));
325  return PP_OK;
326}
327
328}  // namespace content
329