Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(222)

Side by Side Diff: net/websockets/websocket_channel.cc

Issue 12764006: WebSocketChannel implementation (Closed) Base URL: http://git.chromium.org/chromium/src.git@web_socket_dispatcher
Patch Set: Fix "OverFlow" -> "Overflow" Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 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 "net/websockets/websocket_channel.h"
6
7 #include <algorithm>
8
9 #include "base/basictypes.h" // for size_t
10 #include "base/bind.h"
11 #include "base/safe_numerics.h"
12 #include "base/strings/string_util.h"
13 #include "net/base/big_endian.h"
14 #include "net/base/io_buffer.h"
15 #include "net/http/http_request_info.h"
16 #include "net/http/http_stream_factory.h"
17 #include "net/ssl/ssl_config_service.h"
tyoshino (SeeGerritForStatus) 2013/07/02 03:26:42 where is this used?
Adam Rice 2013/07/02 04:51:09 Sorry, I am used to depending on iwyu. I have take
18 #include "net/websockets/websocket_errors.h"
19 #include "net/websockets/websocket_event_interface.h"
20 #include "net/websockets/websocket_frame.h"
21 #include "net/websockets/websocket_mux.h"
22 #include "net/websockets/websocket_stream.h"
23
24 namespace net {
25
26 namespace {
27
28 const int kDefaultSendQuotaLowWaterMark = 1 << 16;
29 const int kDefaultSendQuotaHighWaterMark = 1 << 17;
30 const size_t kWebSocketCloseCodeLength = 2;
31
32 // Concatenate the data from two IOBufferWithSize objects into a single one.
33 IOBufferWithSize* ConcatenateIOBuffers(
34 const scoped_refptr<IOBufferWithSize>& part1,
35 const scoped_refptr<IOBufferWithSize>& part2) {
36 int newsize = part1->size() + part2->size();
37 IOBufferWithSize* newbuffer = new IOBufferWithSize(newsize);
38 std::copy(part1->data(), part1->data() + part1->size(), newbuffer->data());
39 std::copy(part2->data(),
40 part2->data() + part2->size(),
41 newbuffer->data() + part1->size());
42 return newbuffer;
43 }
44
45 } // namespace
46
47 struct WebSocketChannel::SendBuffer {
48 SendBuffer() : total_bytes(0) {}
49 ScopedVector<WebSocketFrameChunk> frames;
50 size_t total_bytes;
51 };
52
53 // Implementation of WebSocketStream::ConnectDelegate that simply forwards the
54 // calls on to the WebSocketChannel that created it.
55 class WebSocketChannel::ConnectDelegate
56 : public WebSocketStream::ConnectDelegate {
57 public:
58 explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
59
60 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
61 creator_->OnConnectSuccess(stream.Pass());
62 }
63
64 virtual void OnFailure(unsigned short websocket_error) OVERRIDE {
65 creator_->OnConnectFailure(websocket_error);
66 }
67
68 private:
69 // A pointer to the WebSocketChannel that created us. We do not need to worry
70 // about this pointer being stale, because deleting WebSocketChannel cancels
71 // the connect process, deleting this object and preventing its callbacks from
72 // being called.
tyoshino (SeeGerritForStatus) 2013/07/02 03:26:42 double space
Adam Rice 2013/07/02 04:51:09 Done.
73 WebSocketChannel* const creator_;
74
75 DISALLOW_COPY_AND_ASSIGN(ConnectDelegate);
76 };
77
78 WebSocketChannel::WebSocketChannel(
79 const GURL& socket_url,
80 scoped_ptr<WebSocketEventInterface> event_interface)
81 : socket_url_(socket_url),
82 event_interface_(event_interface.Pass()),
83 send_quota_low_water_mark_(kDefaultSendQuotaLowWaterMark),
84 send_quota_high_water_mark_(kDefaultSendQuotaHighWaterMark),
85 current_send_quota_(0),
86 state_(FRESHLY_CONSTRUCTED) {}
87
88 WebSocketChannel::~WebSocketChannel() {
89 // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
90 // destroyed first.
91 stream_.reset();
92 }
93
94 void WebSocketChannel::SendAddChannelRequest(
95 const std::vector<std::string>& requested_subprotocols,
96 const GURL& origin,
97 URLRequestContext* url_request_context) {
98 // Delegate to the tested version.
99 SendAddChannelRequestWithFactory(
100 requested_subprotocols,
101 origin,
102 url_request_context,
103 base::Bind(&WebSocketStream::CreateAndConnectStream));
104 }
105
106 void WebSocketChannel::SendAddChannelRequestWithFactory(
107 const std::vector<std::string>& requested_subprotocols,
108 const GURL& origin,
109 URLRequestContext* url_request_context,
110 base::Callback<scoped_ptr<WebSocketStreamRequest>(
111 const GURL&,
112 const std::vector<std::string>&,
113 const GURL&,
114 URLRequestContext*,
115 const BoundNetLog&,
116 scoped_ptr<WebSocketStream::ConnectDelegate>)> factory) {
117 DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
118 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
119 new WebSocketChannel::ConnectDelegate(this));
120 stream_request_ = factory.Run(socket_url_,
121 requested_subprotocols,
122 origin,
123 url_request_context,
124 BoundNetLog(),
125 connect_delegate.Pass());
126 state_ = CONNECTING;
127 }
128
129 void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
130 DCHECK(stream);
131 DCHECK_EQ(CONNECTING, state_);
132 stream_ = stream.Pass();
133 state_ = CONNECTED;
134 event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
135
136 // TODO(ricea): Get flow control information from the WebSocketStream once we
137 // have a multiplexing WebSocketStream.
138 current_send_quota_ = send_quota_high_water_mark_;
139 event_interface_->OnFlowControl(send_quota_high_water_mark_);
140
141 // We don't need this any more.
142 stream_request_.reset();
143 ReadFrames();
144 }
145
146 void WebSocketChannel::OnConnectFailure(unsigned short web_socket_error) {
147 DCHECK_EQ(CONNECTING, state_);
148 state_ = CLOSED;
149 stream_request_.reset();
150 event_interface_->OnAddChannelResponse(true, "");
151 }
152
153 void WebSocketChannel::SendFrame(bool fin,
154 WebSocketFrameHeader::OpCode op_code,
155 const std::vector<char>& data) {
156 if (data.size() > INT_MAX) {
157 NOTREACHED() << "Frame size sanity check failed";
158 return;
159 }
160 if (stream_ == NULL) {
161 LOG(DFATAL) << "Got SendFrame without a connection established; "
162 << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
163 << " data.size()=" << data.size();
164 return;
165 }
166 if (state_ == SEND_CLOSED || state_ == RECV_CLOSED || state_ == CLOSED) {
167 VLOG(1) << "SendFrame called in state " << state_
168 << ". This may be a bug, or a harmless race.";
169 return;
170 }
171 if (state_ != CONNECTED) {
172 NOTREACHED() << "SendFrame() called in state " << state_;
173 return;
174 }
175 if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
176 FailChannel(SEND_INTERNAL_ERROR,
177 kWebSocketMuxErrorSendQuotaViolation,
178 "Send quota exceeded");
179 return;
180 }
181 if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
182 LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
183 << "; misbehaving renderer? fin=" << fin
184 << " data.size()=" << data.size();
185 return;
186 }
187 current_send_quota_ -= data.size();
188 // TODO(ricea): If current_send_quota_ has dropped below
189 // send_quota_low_water_mark_, we may want to consider increasing the "low
190 // water mark" and "high water mark", but only if we think we are not
191 // saturating the link to the WebSocket server.
192 // TODO(ricea): For kOpCodeText, do UTF-8 validation?
193 scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
194 std::copy(data.begin(), data.end(), buffer->data());
195 SendIOBufferWithSize(fin, op_code, buffer);
196 }
197
198 void WebSocketChannel::SendIOBufferWithSize(
199 bool fin,
200 WebSocketFrameHeader::OpCode op_code,
201 const scoped_refptr<IOBufferWithSize>& buffer) {
202 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
203 DCHECK(stream_);
204 scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
205 header->final = fin;
206 header->masked = true;
207 header->payload_length = buffer->size();
208 scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
209 chunk->header = header.Pass();
210 chunk->final_chunk = true;
211 chunk->data = buffer;
212 if (data_being_sent_) {
213 // Either the link to the WebSocket server is saturated, or we are simply
214 // processing a batch of messages.
215 // TODO(ricea): We need to keep some statistics to work out which situation
216 // we are in and adjust quota appropriately.
217 if (!data_to_send_next_) {
218 data_to_send_next_.reset(new SendBuffer);
219 }
220 data_to_send_next_->frames.push_back(chunk.release());
221 data_to_send_next_->total_bytes += buffer->size();
222 } else {
223 data_being_sent_.reset(new SendBuffer);
224 data_being_sent_->frames.push_back(chunk.release());
225 data_being_sent_->total_bytes += buffer->size();
226 WriteFrames();
227 }
228 }
229
230 void WebSocketChannel::WriteFrames() {
231 // This is safe because we own the WebSocketStream and destroying it cancels
232 // all callbacks.
233 int result = stream_->WriteFrames(
234 &(data_being_sent_->frames),
235 base::Bind(&WebSocketChannel::OnWriteDone, base::Unretained(this)));
236 if (result != ERR_IO_PENDING) {
237 OnWriteDone(result);
238 }
239 }
240
241 void WebSocketChannel::OnWriteDone(int result) {
242 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
243 DCHECK_NE(ERR_IO_PENDING, result);
244 DCHECK(data_being_sent_);
245 switch (result) {
246 case OK:
247 if (data_to_send_next_) {
248 data_being_sent_ = data_to_send_next_.Pass();
249 WriteFrames();
250 } else {
251 data_being_sent_.reset();
252 if (current_send_quota_ < send_quota_low_water_mark_) {
253 // TODO(ricea): Increase low_water_mark and high_water_mark if
254 // throughput is high, reduce them if throughput is low. Low water
255 // mark needs to be >= the bandwidth delay product *of the IPC
256 // channel*. Because factors like context-switch time, thread wake-up
257 // time, and bus speed come into play it is complex and probably needs
258 // to be determined empirically.
259 DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
260 // TODO(ricea): Truncate quota by the quota specified by the remote
261 // server, if the protocol in use supports quota.
262 int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
263 current_send_quota_ += fresh_quota;
264 event_interface_->OnFlowControl(fresh_quota);
265 }
266 }
267 break;
268
269 // If a recoverable error condition existed, it would go here.
270
271 default:
272 DCHECK_LT(result, 0)
273 << "WriteFrames() should only return OK or ERR_ codes";
274 stream_->Close();
275 state_ = CLOSED;
276 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
277 "Abnormal Closure");
278 break;
279 }
280 }
281
282 void WebSocketChannel::ReadFrames() {
283 // This use of base::Unretained is safe because we own the WebSocketStream,
284 // and any pending reads will be cancelled when it is destroyed.
285 int result = stream_->ReadFrames(
286 &read_frame_chunks_,
287 base::Bind(&WebSocketChannel::OnReadDone, base::Unretained(this)));
288 if (result != ERR_IO_PENDING) {
289 OnReadDone(result);
290 }
291 }
292
293 void WebSocketChannel::OnReadDone(int result) {
294 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
295 DCHECK_NE(ERR_IO_PENDING, result);
296 switch (result) {
297 case OK:
298 // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
299 // with no data read, not an empty response.
300 DCHECK(!read_frame_chunks_.empty())
301 << "ReadFrames() returned OK, but nothing was read.";
302 for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
303 scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
304 read_frame_chunks_[i] = NULL;
305 ProcessFrameChunk(chunk.Pass());
306 }
307 read_frame_chunks_.clear();
308 // We need to always keep a call to ReadFrames pending.
309 ReadFrames();
310 return;
311
312 case ERR_CONNECTION_CLOSED: {
313 State old_state = state_;
314 state_ = CLOSED;
315 if (old_state != RECV_CLOSED && old_state != CLOSED) {
316 // We need to inform the render process of the unexpected closure.
317 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
318 "Abnormal Closure");
319 }
320 return;
321 }
322
323 default: {
324 DCHECK_LT(result, 0)
325 << "ReadFrames() should only return OK or ERR_ codes";
326 stream_->Close();
327 State old_state = state_;
328 state_ = CLOSED;
329 if (old_state != RECV_CLOSED && old_state != CLOSED) {
330 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
331 "Abnormal Closure");
332 }
333 return;
334 }
335 }
336 }
337
338 // TODO(ricea): This method is too long. Break it up.
339 void WebSocketChannel::ProcessFrameChunk(
340 scoped_ptr<WebSocketFrameChunk> chunk) {
341 bool first_chunk = false;
342 if (chunk->header) {
343 first_chunk = true;
344 current_frame_header_.swap(chunk->header);
345 if (current_frame_header_->masked) {
346 // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
347 // masked frame."
348 FailChannel(SEND_REAL_ERROR,
349 kWebSocketErrorProtocolError,
350 "Masked frame from server");
351 return;
352 }
353 }
354 if (!current_frame_header_) {
355 DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
356 << "(final_chunk = " << chunk->final_chunk
357 << ", data size = " << chunk->data->size()
358 << ")";
359 return;
360 }
361 scoped_refptr<IOBufferWithSize> data_buffer;
362 data_buffer.swap(chunk->data);
363 WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
364 if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
365 if (chunk->final_chunk) {
366 if (incomplete_control_frame_body_) {
367 VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
368 data_buffer =
369 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
370 incomplete_control_frame_body_ = NULL;
371 }
372 } else {
373 // TODO(ricea): Enforce a maximum size of 125 bytes on the control frames
374 // we accept.
375 VLOG(2) << "Encountered a split control frame, opcode " << opcode;
376 if (incomplete_control_frame_body_) {
377 // The really horrid case. We need to create a new IOBufferWithSize
378 // combining the new one and the old one. This should virtually never
379 // happen.
380 // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte byffer
381 // instead.
382 VLOG(3) << "Hit the really horrid case";
383 incomplete_control_frame_body_ =
384 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
385 } else {
386 // The merely horrid case. Store the IOBufferWithSize to use when the
387 // rest of the control frame arrives.
388 incomplete_control_frame_body_.swap(data_buffer);
389 }
390 return;
391 }
392 }
393
394 switch (opcode) {
395 case WebSocketFrameHeader::kOpCodeText: // fall-thru
396 case WebSocketFrameHeader::kOpCodeBinary:
397 if (!first_chunk) {
398 opcode = WebSocketFrameHeader::kOpCodeContinuation;
399 }
400 // fall-thru
401 case WebSocketFrameHeader::kOpCodeContinuation:
402 if (state_ == RECV_CLOSED) {
403 FailChannel(SEND_REAL_ERROR,
404 kWebSocketErrorProtocolError,
405 "Data packet received after close");
406 return;
407 } else if (state_ == CONNECTED) {
408 const bool final = chunk->final_chunk && current_frame_header_->final;
409 // TODO(ricea): Can this copy be eliminated?
410 const char* const data_begin = data_buffer->data();
411 const char* const data_end = data_begin + data_buffer->size();
412 const std::vector<char> data(data_begin, data_end);
413 // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
414 // more data at once than we want to send in a single IPC (in which case
415 // we need to buffer the data and return to the event loop with a
416 // callback to send the rest in 32K chunks).
417
418 // Send the received frame to the renderer process.
419 event_interface_->OnDataFrame(final, opcode, data);
420 } else {
421 VLOG(3) << "Ignored data packet received in state " << state_;
422 }
423 break;
424
425 case WebSocketFrameHeader::kOpCodePing:
426 VLOG(1) << "Got Ping of size " << data_buffer->size();
427 if (state_ == RECV_CLOSED) {
428 FailChannel(SEND_REAL_ERROR,
429 kWebSocketErrorProtocolError,
430 "Ping received after Close");
431 return;
432 } else if (state_ == CONNECTED) {
433 SendIOBufferWithSize(
434 true, WebSocketFrameHeader::kOpCodePong, data_buffer);
435 } else {
436 VLOG(3) << "Ignored ping in state " << state_;
437 }
438 break;
439
440 case WebSocketFrameHeader::kOpCodePong:
441 VLOG(1) << "Got Pong of size " << data_buffer->size();
442 if (state_ == RECV_CLOSED) {
443 FailChannel(SEND_REAL_ERROR,
444 kWebSocketErrorProtocolError,
445 "Pong received after Close");
446 return;
447 }
448 // We do not need to do anything with pong messages.
449 break;
450
451 case WebSocketFrameHeader::kOpCodeClose: {
452 unsigned short code = kWebSocketNormalClosure;
453 std::string reason;
454 ParseClose(data_buffer, &code, &reason);
455 // TODO(ricea): Find a way to safely log the message from the close
456 // message (escape control codes and so on).
457 VLOG(1) << "Got Close with code " << code;
458 switch (state_) {
459 case CONNECTED:
460 state_ = RECV_CLOSED;
461 SendClose(code, reason);
462 event_interface_->OnDropChannel(code, reason);
463 break;
464
465 case RECV_CLOSED:
466 FailChannel(SEND_REAL_ERROR,
467 kWebSocketErrorProtocolError,
468 "Close received after Close");
469 break;
470
471 case SEND_CLOSED:
472 state_ = CLOSED;
473 event_interface_->OnDropChannel(code, reason);
474 break;
475
476 default:
477 LOG(DFATAL) << "Got Close in unexpected state " << state_;
478 break;
479 }
480 break;
481 }
482
483 default:
484 FailChannel(
485 SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
486 break;
487 }
488 if (chunk->final_chunk) {
489 // Make sure we do not apply this frame header to any future chunks.
490 current_frame_header_.reset();
491 }
492 }
493
494 void WebSocketChannel::SendFlowControl(int64 quota) {
495 DCHECK_EQ(CONNECTED, state_);
496 // TODO(ricea): Add interface to WebSocketStream and implement.
497 // stream_->SendFlowControl(quota);
498 }
499
500 void WebSocketChannel::SendDropChannel(unsigned short code,
501 const std::string& reason) {
502 if (state_ == SEND_CLOSED || state_ == CLOSED) {
503 VLOG(1) << "SendDropChannel called in state " << state_
504 << ". This may be a bug, or a harmless race.";
505 return;
506 }
507 DCHECK_EQ(CONNECTED, state_);
tyoshino (SeeGerritForStatus) 2013/07/02 03:26:42 Do the same as L171?
Adam Rice 2013/07/02 04:51:09 Done.
508 // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
509 // TODO(ricea): There should be a timeout for the closing handshake.
510 SendClose(code, reason);
511 }
512
513 void WebSocketChannel::FailChannel(ExposeError expose,
514 unsigned short code,
515 const std::string& reason) {
516 // TODO(ricea): Logging.
517 State old_state = state_;
518 if (state_ == CONNECTED) {
519 unsigned short send_code = kWebSocketErrorGoingAway;
520 std::string send_reason = "Internal Error";
521 if (expose == SEND_REAL_ERROR) {
522 send_code = code;
523 send_reason = reason;
524 }
525 SendClose(send_code, send_reason);
526 }
527 // This method is mostly called in response to an invalid frame, in
528 // which case we should not re-use the header.
529 current_frame_header_.reset();
530 if (old_state != RECV_CLOSED && old_state != CLOSED) {
531 event_interface_->OnDropChannel(code, reason);
532 }
533 }
534
535 void WebSocketChannel::SendClose(unsigned short code,
tyoshino (SeeGerritForStatus) 2013/07/02 03:26:42 let's use uint16
Adam Rice 2013/07/02 04:51:09 Done.
536 const std::string& reason) {
537 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
538 uint64 payload_length = kWebSocketCloseCodeLength + reason.length();
539 scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(payload_length);
540 WriteBigEndian(body->data(), code);
541 COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
542 they_should_both_be_two);
543 std::copy(
544 reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
545 SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
546 state_ = state_ == CONNECTED ? SEND_CLOSED : CLOSED;
547 }
548
549 void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
550 unsigned short* code,
551 std::string* reason) {
552 const char* data = buffer->data();
553 size_t size = base::checked_numeric_cast<size_t>(buffer->size());
554 reason->clear();
555 if (size < kWebSocketCloseCodeLength) {
556 *code = kWebSocketErrorNoStatusReceived;
557 if (size != 0) {
558 VLOG(1) << "Close frame with payload size " << size << " received "
559 << "(the first byte is " << std::hex << static_cast<int>(data[0])
560 << ")";
561 return;
562 }
563 return;
564 }
565 unsigned short unchecked_code = 0;
566 ReadBigEndian(data, &unchecked_code);
567 COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
568 they_should_both_be_two_bytes);
569 if (unchecked_code >= static_cast<unsigned short>(kWebSocketNormalClosure) &&
570 unchecked_code <=
571 static_cast<unsigned short>(kWebSocketErrorPrivateReservedMax)) {
572 *code = unchecked_code;
573 } else {
574 VLOG(1) << "Close frame contained code outside of the valid range: "
575 << unchecked_code;
576 *code = kWebSocketErrorProtocolError;
577 }
578 std::string text(data + kWebSocketCloseCodeLength, data + size);
579 // TODO(ricea): Is this check strict enough? In particular, check the
580 // "Security Considerations" from RFC3629.
581 if (IsStringUTF8(text)) {
582 using std::swap;
583 swap(*reason, text);
584 }
585 }
586
587 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698