Implement WebSocketExtensionParser
Implement WebSocketExtensionParser, which parses Sec-WebSocket-Extension
header value as specified in RFC6455.
BUG=280910
Review URL: https://chromiumcodereview.appspot.com/23872029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@224102 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/net.gyp b/net/net.gyp
index 905f70b..7f56c9b 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -1106,6 +1106,10 @@
'websockets/websocket_deflater.cc',
'websockets/websocket_errors.cc',
'websockets/websocket_errors.h',
+ 'websockets/websocket_extension.cc',
+ 'websockets/websocket_extension.h',
+ 'websockets/websocket_extension_parser.cc',
+ 'websockets/websocket_extension_parser.h',
'websockets/websocket_frame.cc',
'websockets/websocket_frame.h',
'websockets/websocket_frame_parser.cc',
@@ -1879,6 +1883,7 @@
'websockets/websocket_channel_test.cc',
'websockets/websocket_deflater_test.cc',
'websockets/websocket_errors_test.cc',
+ 'websockets/websocket_extension_parser_test.cc',
'websockets/websocket_frame_parser_test.cc',
'websockets/websocket_frame_test.cc',
'websockets/websocket_handshake_handler_test.cc',
diff --git a/net/websockets/README b/net/websockets/README
index 25b3e84..1d1e1c3 100644
--- a/net/websockets/README
+++ b/net/websockets/README
@@ -40,6 +40,11 @@
websocket_deflater_test.cc
websocket_errors.cc
websocket_errors.h
+websocket_extension.cc
+websocket_extension.h
+websocket_extension_parser.cc
+websocket_extension_parser.h
+websocket_extension_parser_test.cc
websocket_errors_test.cc
websocket_event_interface.h
websocket_frame.cc
diff --git a/net/websockets/websocket_extension.cc b/net/websockets/websocket_extension.cc
new file mode 100644
index 0000000..edcd8e8
--- /dev/null
+++ b/net/websockets/websocket_extension.cc
@@ -0,0 +1,43 @@
+// Copyright 2013 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 "net/websockets/websocket_extension.h"
+
+#include <string>
+
+#include "base/logging.h"
+
+namespace net {
+
+WebSocketExtension::Parameter::Parameter(const std::string& name)
+ : name_(name) {}
+
+WebSocketExtension::Parameter::Parameter(const std::string& name,
+ const std::string& value)
+ : name_(name), value_(value) {
+ DCHECK(!value.empty());
+}
+
+bool WebSocketExtension::Parameter::Equals(const Parameter& other) const {
+ return name_ == other.name_ && value_ == other.value_;
+}
+
+WebSocketExtension::WebSocketExtension() {}
+
+WebSocketExtension::WebSocketExtension(const std::string& name)
+ : name_(name) {}
+
+WebSocketExtension::~WebSocketExtension() {}
+
+bool WebSocketExtension::Equals(const WebSocketExtension& other) const {
+ if (name_ != other.name_) return false;
+ if (parameters_.size() != other.parameters_.size()) return false;
+ for (size_t i = 0; i < other.parameters_.size(); ++i) {
+ if (!parameters_[i].Equals(other.parameters_[i]))
+ return false;
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_extension.h b/net/websockets/websocket_extension.h
new file mode 100644
index 0000000..5af4023
--- /dev/null
+++ b/net/websockets/websocket_extension.h
@@ -0,0 +1,57 @@
+// Copyright 2013 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_EXTENSION_H_
+#define NET_WEBSOCKETS_WEBSOCKET_EXTENSION_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A WebSocketExtension instance represents a WebSocket extension specified
+// in RFC6455.
+class NET_EXPORT_PRIVATE WebSocketExtension {
+ public:
+ // Note that RFC6455 does not allow a parameter with an empty value.
+ class NET_EXPORT_PRIVATE Parameter {
+ public:
+ // Construct a parameter which does not have a value.
+ explicit Parameter(const std::string& name);
+ // Construct a parameter with a non-empty value.
+ Parameter(const std::string& name, const std::string& value);
+
+ bool HasValue() const { return !value_.empty(); }
+ const std::string& name() const { return name_; }
+ const std::string& value() const { return value_; }
+ bool Equals(const Parameter& other) const;
+
+ // The default copy constructor and the assignment operator are defined:
+ // we need them.
+ private:
+ std::string name_;
+ std::string value_;
+ };
+
+ WebSocketExtension();
+ explicit WebSocketExtension(const std::string& name);
+ ~WebSocketExtension();
+
+ void Add(const Parameter& parameter) { parameters_.push_back(parameter); }
+ const std::string& name() const { return name_; }
+ const std::vector<Parameter>& parameters() const { return parameters_; }
+ bool Equals(const WebSocketExtension& other) const;
+
+ // The default copy constructor and the assignment operator are defined:
+ // we need them.
+ private:
+ std::string name_;
+ std::vector<Parameter> parameters_;
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_EXTENSION_H_
diff --git a/net/websockets/websocket_extension_parser.cc b/net/websockets/websocket_extension_parser.cc
new file mode 100644
index 0000000..28a2db1
--- /dev/null
+++ b/net/websockets/websocket_extension_parser.cc
@@ -0,0 +1,158 @@
+// Copyright 2013 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 "net/websockets/websocket_extension_parser.h"
+
+#include "base/strings/string_util.h"
+
+namespace net {
+
+WebSocketExtensionParser::WebSocketExtensionParser() {}
+
+WebSocketExtensionParser::~WebSocketExtensionParser() {}
+
+void WebSocketExtensionParser::Parse(const char* data, size_t size) {
+ current_ = data;
+ end_ = data + size;
+ has_error_ = false;
+
+ ConsumeExtension(&extension_);
+ if (has_error_) return;
+ ConsumeSpaces();
+ has_error_ = has_error_ || (current_ != end_);
+}
+
+void WebSocketExtensionParser::Consume(char c) {
+ DCHECK(!has_error_);
+ ConsumeSpaces();
+ DCHECK(!has_error_);
+ if (current_ == end_ || c != current_[0]) {
+ has_error_ = true;
+ return;
+ }
+ ++current_;
+}
+
+void WebSocketExtensionParser::ConsumeExtension(WebSocketExtension* extension) {
+ DCHECK(!has_error_);
+ base::StringPiece name;
+ ConsumeToken(&name);
+ if (has_error_) return;
+ *extension = WebSocketExtension(name.as_string());
+
+ while (ConsumeIfMatch(';')) {
+ WebSocketExtension::Parameter parameter((std::string()));
+ ConsumeExtensionParameter(¶meter);
+ if (has_error_) return;
+ extension->Add(parameter);
+ }
+}
+
+void WebSocketExtensionParser::ConsumeExtensionParameter(
+ WebSocketExtension::Parameter* parameter) {
+ DCHECK(!has_error_);
+ base::StringPiece name, value;
+ std::string value_string;
+
+ ConsumeToken(&name);
+ if (has_error_) return;
+ if (!ConsumeIfMatch('=')) {
+ *parameter = WebSocketExtension::Parameter(name.as_string());
+ return;
+ }
+
+ if (Lookahead('\"')) {
+ ConsumeQuotedToken(&value_string);
+ } else {
+ ConsumeToken(&value);
+ value_string = value.as_string();
+ }
+ if (has_error_) return;
+ *parameter = WebSocketExtension::Parameter(name.as_string(), value_string);
+}
+
+void WebSocketExtensionParser::ConsumeToken(base::StringPiece* token) {
+ DCHECK(!has_error_);
+ ConsumeSpaces();
+ DCHECK(!has_error_);
+ const char* head = current_;
+ while (current_ < end_ &&
+ !IsControl(current_[0]) && !IsSeparator(current_[0]))
+ ++current_;
+ if (current_ == head) {
+ has_error_ = true;
+ return;
+ }
+ *token = base::StringPiece(head, current_ - head);
+}
+
+void WebSocketExtensionParser::ConsumeQuotedToken(std::string* token) {
+ DCHECK(!has_error_);
+ Consume('"');
+ if (has_error_) return;
+ *token = "";
+ while (current_ < end_ && !IsControl(current_[0])) {
+ if (UnconsumedBytes() >= 2 && current_[0] == '\\') {
+ char next = current_[1];
+ if (IsControl(next) || IsSeparator(next)) break;
+ *token += next;
+ current_ += 2;
+ } else if (IsSeparator(current_[0])) {
+ break;
+ } else {
+ *token += current_[0];
+ ++current_;
+ }
+ }
+ // We can't use Consume here because we don't want to consume spaces.
+ if (current_ < end_ && current_[0] == '"')
+ ++current_;
+ else
+ has_error_ = true;
+ has_error_ = has_error_ || token->empty();
+}
+
+void WebSocketExtensionParser::ConsumeSpaces() {
+ DCHECK(!has_error_);
+ while (current_ < end_ && (current_[0] == ' ' || current_[0] == '\t'))
+ ++current_;
+ return;
+}
+
+bool WebSocketExtensionParser::Lookahead(char c) {
+ DCHECK(!has_error_);
+ const char* head = current_;
+
+ Consume(c);
+ bool result = !has_error_;
+ current_ = head;
+ has_error_ = false;
+ return result;
+}
+
+bool WebSocketExtensionParser::ConsumeIfMatch(char c) {
+ DCHECK(!has_error_);
+ const char* head = current_;
+
+ Consume(c);
+ if (has_error_) {
+ current_ = head;
+ has_error_ = false;
+ return false;
+ }
+ return true;
+}
+
+// static
+bool WebSocketExtensionParser::IsControl(char c) {
+ return (0 <= c && c <= 31) || c == 127;
+}
+
+// static
+bool WebSocketExtensionParser::IsSeparator(char c) {
+ const char separators[] = "()<>@,;:\\\"/[]?={} \t";
+ return strchr(separators, c) != NULL;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_extension_parser.h b/net/websockets/websocket_extension_parser.h
new file mode 100644
index 0000000..ef7fe03
--- /dev/null
+++ b/net/websockets/websocket_extension_parser.h
@@ -0,0 +1,59 @@
+// Copyright 2013 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_extension.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE WebSocketExtensionParser {
+ public:
+ WebSocketExtensionParser();
+ ~WebSocketExtensionParser();
+
+ // Parses the given string as a WebSocket extension header value.
+ // This parser assumes some preprocesses are made.
+ // - The parser parses single extension at a time. This means that
+ // the parser parses |extension| in RFC6455 9.1, not |extension-list|.
+ // - There is no newline characters in the input. LWS-concatenation must
+ // have already been done.
+ void Parse(const char* data, size_t size);
+ void Parse(const std::string& data) {
+ Parse(data.data(), data.size());
+ }
+
+ bool has_error() const { return has_error_; }
+ const WebSocketExtension& extension() const { return extension_; }
+
+ private:
+ void Consume(char c);
+ void ConsumeExtension(WebSocketExtension* extension);
+ void ConsumeExtensionParameter(WebSocketExtension::Parameter* parameter);
+ void ConsumeToken(base::StringPiece* token);
+ void ConsumeQuotedToken(std::string* token);
+ void ConsumeSpaces();
+ bool Lookahead(char c);
+ bool ConsumeIfMatch(char c);
+ size_t UnconsumedBytes() const { return end_ - current_; }
+
+ static bool IsControl(char c);
+ static bool IsSeparator(char c);
+
+ const char* current_;
+ const char* end_;
+ bool has_error_;
+ WebSocketExtension extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketExtensionParser);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_EXTENSION_PARSER_H_
diff --git a/net/websockets/websocket_extension_parser_test.cc b/net/websockets/websocket_extension_parser_test.cc
new file mode 100644
index 0000000..dc7dc85
--- /dev/null
+++ b/net/websockets/websocket_extension_parser_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 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 "net/websockets/websocket_extension_parser.h"
+
+#include <string>
+
+#include "net/websockets/websocket_extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(WebSocketExtensionParserTest, ParseEmpty) {
+ WebSocketExtensionParser parser;
+ parser.Parse("", 0);
+
+ EXPECT_TRUE(parser.has_error());
+}
+
+TEST(WebSocketExtensionParserTest, ParseSimple) {
+ WebSocketExtensionParser parser;
+ WebSocketExtension expected("foo");
+
+ parser.Parse("foo");
+
+ ASSERT_FALSE(parser.has_error());
+ EXPECT_TRUE(expected.Equals(parser.extension()));
+}
+
+TEST(WebSocketExtensionParserTest, ParseOneExtensionWithOneParamWithoutValue) {
+ WebSocketExtensionParser parser;
+ WebSocketExtension expected("foo");
+ expected.Add(WebSocketExtension::Parameter("bar"));
+
+ parser.Parse("\tfoo ; bar");
+
+ ASSERT_FALSE(parser.has_error());
+ EXPECT_TRUE(expected.Equals(parser.extension()));
+}
+
+TEST(WebSocketExtensionParserTest, ParseOneExtensionWithOneParamWithValue) {
+ WebSocketExtensionParser parser;
+ WebSocketExtension expected("foo");
+ expected.Add(WebSocketExtension::Parameter("bar", "baz"));
+
+ parser.Parse("foo ; bar= baz\t");
+
+ ASSERT_FALSE(parser.has_error());
+ EXPECT_TRUE(expected.Equals(parser.extension()));
+}
+
+TEST(WebSocketExtensionParserTest, ParseOneExtensionWithParams) {
+ WebSocketExtensionParser parser;
+ WebSocketExtension expected("foo");
+ expected.Add(WebSocketExtension::Parameter("bar", "baz"));
+ expected.Add(WebSocketExtension::Parameter("hoge", "fuga"));
+
+ parser.Parse("foo ; bar= baz;\t \thoge\t\t=fuga");
+
+ ASSERT_FALSE(parser.has_error());
+ EXPECT_TRUE(expected.Equals(parser.extension()));
+}
+
+TEST(WebSocketExtensionParserTest, InvalidPatterns) {
+ const char* patterns[] = {
+ "fo\ao", // control in extension name
+ "fo\x01o", // control in extension name
+ "fo<o", // separator in extension name
+ "foo/", // separator in extension name
+ ";bar", // empty extension name
+ "foo bar", // missing ';'
+ "foo;", // extension parameter without name and value
+ "foo; b\ar", // control in parameter name
+ "foo; b\x7fr", // control in parameter name
+ "foo; b[r", // separator in parameter name
+ "foo; ba:", // separator in parameter name
+ "foo; =baz", // empty parameter name
+ "foo; bar=", // empty parameter value
+ "foo; =", // empty parameter name and value
+ "foo; bar=b\x02z", // control in parameter value
+ "foo; bar=b@z", // separator in parameter value
+ "foo; bar=b\\z", // separator in parameter value
+ "foo; bar=b?z", // separator in parameter value
+ "\"foo\"", // quoted extension name
+ "foo; \"bar\"", // quoted parameter name
+ "foo; bar=\"\a2\"", // control in quoted parameter value
+ "foo; bar=\"b@z\"", // separator in quoted parameter value
+ "foo; bar=\"b\\\\z\"", // separator in quoted parameter value
+ "foo; bar=\"\"", // quoted empty parameter value
+ "foo; bar=\"baz", // unterminated quoted string
+ "foo; bar=\"baz \"", // space in quoted string
+ "foo; bar baz", // mising '='
+ "foo; bar - baz", // '-' instead of '=' (note: "foo; bar-baz" is valid).
+ "foo; bar=\r\nbaz", // CRNL not followed by a space
+ "foo; bar=\r\n baz", // CRNL followed by a space
+ "foo, bar" // multiple extensions
+ };
+
+ for (size_t i = 0; i < arraysize(patterns); ++i) {
+ WebSocketExtensionParser parser;
+ parser.Parse(patterns[i]);
+ EXPECT_TRUE(parser.has_error());
+ }
+}
+
+TEST(WebSocketExtensionParserTest, QuotedParameterValue) {
+ WebSocketExtensionParser parser;
+ WebSocketExtension expected("foo");
+ expected.Add(WebSocketExtension::Parameter("bar", "baz"));
+
+ parser.Parse("foo; bar = \"ba\\z\" ");
+
+ ASSERT_FALSE(parser.has_error());
+ EXPECT_TRUE(expected.Equals(parser.extension()));
+}
+
+} // namespace
+
+} // namespace net