Title: [199590] trunk
Revision
199590
Author
[email protected]
Date
2016-04-15 09:39:00 -0700 (Fri, 15 Apr 2016)

Log Message

Refactor WebSockets handshake to use StringView instead of String for header validation.
https://bugs.webkit.org/show_bug.cgi?id=155602

Patch by John Wilander <[email protected]> on 2016-04-15
Reviewed by Darin Adler.

Source/WebCore:

No new tests. Existing test have been augmented.

* Modules/websockets/WebSocketHandshake.cpp:
(WebCore::WebSocketHandshake::readServerHandshake):
    Made sure failure reason was set consistently with makeString().
(WebCore::headerHasValidHTTPVersion):
    Now operates on the HTTP status line with StringView.
(WebCore::WebSocketHandshake::readStatusLine):
    Now operates on the HTTP status line with StringView.
(WebCore::WebSocketHandshake::readHTTPHeaders):
    Now operates on header names with StringView.
    Made sure failure reason was set consistently with makeString() and ASCIILiteral().
(WebCore::WebSocketHandshake::checkResponseHeaders):
    Made sure failure reason was set consistently with ASCIILiteral().
* platform/network/HTTPParsers.cpp:
(WebCore::parseHTTPRequestLine):
    Made sure failure reason was set consistently with ASCIILiteral().
(WebCore::isValidHeaderNameCharacter):
    Inlined function to check if a character is allowed in an HTTP header name according to RFC 7230.
    https://tools.ietf.org/html/rfc7230 (June 2014)
(WebCore::parseHTTPHeader):
* platform/network/HTTPParsers.h:
    Now receives the HTTP header name as a StringView.
    Checks that header names only contain valid characters according to RFC 7230 (see above).
* platform/network/ResourceRequestBase.cpp:
(WebCore::ResourceRequestBase::addHTTPHeaderField):
* platform/network/ResourceRequestBase.h:
     Now has an overloaded function which receives the HTTP header name as an HTTPHeaderName enum value.
* platform/network/ResourceResponseBase.cpp:
(WebCore::ResourceResponseBase::addHTTPHeaderField):
* platform/network/ResourceResponseBase.h:
     Now has an overloaded function which receives the HTTP header name as an HTTPHeaderName enum value.

Source/WebKit2:

* UIProcess/InspectorServer/HTTPRequest.cpp:
(WebKit::HTTPRequest::parseHeaders):
    Now declares the HTTP header name as a StringView to match the change in WebCore::parseHTTPHeader.

LayoutTests:

* http/tests/websocket/tests/hybi/bad-handshake-crash-expected.txt:
    Fixed so that new error output is expected.
* http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1-expected.txt:
* http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1.html:
* http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1_wsh.py:
    Now tests HTTP versions that are higher than 1.1, are lower than 1.1, have bad characters, and are empty.
* http/tests/websocket/tests/hybi/long-invalid-header-expected.txt:
    Fixed so that slightly refined error output is expected.

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (199589 => 199590)


--- trunk/LayoutTests/ChangeLog	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/ChangeLog	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,3 +1,19 @@
+2016-04-15  John Wilander  <[email protected]>
+
+        Refactor WebSockets handshake to use StringView instead of String for header validation.
+        https://bugs.webkit.org/show_bug.cgi?id=155602
+
+        Reviewed by Darin Adler.
+
+        * http/tests/websocket/tests/hybi/bad-handshake-crash-expected.txt:
+            Fixed so that new error output is expected.
+        * http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1-expected.txt:
+        * http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1.html:
+        * http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1_wsh.py:
+            Now tests HTTP versions that are higher than 1.1, are lower than 1.1, have bad characters, and are empty.
+        * http/tests/websocket/tests/hybi/long-invalid-header-expected.txt:
+            Fixed so that slightly refined error output is expected.
+
 2016-04-15  Joanmarie Diggs  <[email protected]>
 
         AX: Presentational role on SVG elements is trumped by child 'title' and 'desc' elements

Modified: trunk/LayoutTests/http/tests/websocket/tests/hybi/bad-handshake-crash-expected.txt (199589 => 199590)


--- trunk/LayoutTests/http/tests/websocket/tests/hybi/bad-handshake-crash-expected.txt	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/bad-handshake-crash-expected.txt	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: WebSocket connection to 'ws://127.0.0.1:8880/websocket/tests/hybi/bad-handshake-crash' failed: Invalid UTF-8 sequence in header name
+CONSOLE MESSAGE: WebSocket connection to 'ws://127.0.0.1:8880/websocket/tests/hybi/bad-handshake-crash' failed: Unexpected start character in header name
 Make sure WebSocket doesn't crash with bad handshake message.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

Modified: trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1-expected.txt (199589 => 199590)


--- trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1-expected.txt	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1-expected.txt	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,8 +1,16 @@
-Test whether WebSocket handshake is OK with if server sends status line with http version beyond 1.1.
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?01.00' failed: Invalid HTTP version string: HTTP/01.00
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?000.99' failed: Invalid HTTP version string: HTTP/000.99
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?0.00' failed: Invalid HTTP version string: HTTP/0.00
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?-11.9' failed: Invalid HTTP version string: HTTP/-11.9
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?0x1.0x00' failed: Invalid HTTP version string: HTTP/0x1.0x00
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?%EF%A3%BF.1' failed: Invalid HTTP version string: HTTP/%EF%A3%BF.1
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?' failed: Invalid HTTP version string: HTTP/
+CONSOLE MESSAGE: WebSocket connection to 'ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?1.1%EF%A3%BF' failed: Invalid HTTP version string: HTTP/1.1%EF%A3%BF
+Test http version parsing and validation. HTTP version 1.1 and above should be accepted for WebSockets.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS handshake_success is true
+PASS for all URLs.
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1.html (199589 => 199590)


--- trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1.html	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1.html	2016-04-15 16:39:00 UTC (rev 199590)
@@ -7,31 +7,64 @@
 <div id="description"></div>
 <div id="console"></div>
 <script>
-    description("Test whether WebSocket handshake is OK with if server sends status line with http version beyond 1.1.");
+    description("Test http version parsing and validation. HTTP version 1.1 and above should be accepted for WebSockets.");
 
     window.jsTestIsAsync = true;
 
-    var url = ""
-    var handshake_success = false;
-    var ws = new WebSocket(url);
-    var closeEvent;
+    var testCases = [
+        { url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?1.11", shouldSucceed : true }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?11.0", shouldSucceed : true }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?001.01", shouldSucceed : true }
 
-    function endTest() {
-        clearTimeout(timeoutID);
-        shouldBeTrue("handshake_success");
-        finishJSTest();
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?01.00", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?000.99", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?0.00", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?-11.9", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?0x1.0x00", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?.1", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?", shouldSucceed : false }
+        ,{ url : "ws://localhost:8880/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1?1.1", shouldSucceed : false }
+    ];
+
+    var currentTest = 0;
+    var allTestsPassed = true;
+
+    function runATest () {
+        if (currentTest === testCases.length) {
+            if (!allTestsPassed) {
+                testFailed("At least one test failed.");
+            } else {
+                debug("PASS for all URLs.");
+            }
+            finishJSTest();
+        } else {
+            var testCase = testCases[currentTest++];
+            var ws = new WebSocket(testCase.url);
+
+            ws._onopen_ = (function(shouldSucceed, url) {
+                    return function () {
+                            if (!shouldSucceed) {
+                                debug("FAIL The following URL should have caused an error: " + url);
+                                allTestsPassed = false;
+                            }
+                            runATest();
+                        };
+                })(testCase.shouldSucceed, testCase.url);
+
+            ws._onerror_ = (function(shouldSucceed, url) {
+                    return function () {
+                            if (shouldSucceed) {
+                                debug("FAIL The following URL should have been allowed to be opened: " + url);
+                                allTestsPassed = false;
+                            }
+                            runATest();
+                        };
+                })(testCase.shouldSucceed, testCase.url);
+        }
     }
-    ws._onopen_ = function() {
-        handshake_success = true;
-        ws.close();
-    };
 
-    ws._onclose_ = function () {
-        endTest();
-    };
-    
-    var timeoutID = setTimeout("endTest()", 2000);
+    runATest();
 </script>
 <script src=""
 </body>
-</html>
\ No newline at end of file
+</html>

Modified: trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1_wsh.py (199589 => 199590)


--- trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1_wsh.py	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/handshake-ok-with-http-version-beyond-1_1_wsh.py	2016-04-15 16:39:00 UTC (rev 199590)
@@ -4,7 +4,14 @@
 
 
 def web_socket_do_extra_handshake(request):
-    message = 'HTTP/3.0 101 Switching Protocols\r\n'
+
+    resources = request.ws_resource.split('?', 1)
+    parameter = None
+    if len(resources) == 2:
+        parameter = resources[1]
+        message = 'HTTP/'
+        message += parameter
+        message += ' 101 Switching Protocols\r\n'
     message += 'Upgrade: websocket\r\n'
     message += 'Connection: Upgrade\r\n'
     message += 'Sec-WebSocket-Accept: %s\r\n' % compute_accept(request.headers_in['Sec-WebSocket-Key'])[0]

Modified: trunk/LayoutTests/http/tests/websocket/tests/hybi/long-invalid-header-expected.txt (199589 => 199590)


--- trunk/LayoutTests/http/tests/websocket/tests/hybi/long-invalid-header-expected.txt	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/long-invalid-header-expected.txt	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: WebSocket connection to 'ws://127.0.0.1:8880/websocket/tests/hybi/long-invalid-header' failed: Unexpected CR in name at pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp…
+CONSOLE MESSAGE: WebSocket connection to 'ws://127.0.0.1:8880/websocket/tests/hybi/long-invalid-header' failed: Unexpected CR in header name at pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp…
 Make sure WebSocket gives errors on long invalid upgrade header.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

Modified: trunk/Source/WebCore/ChangeLog (199589 => 199590)


--- trunk/Source/WebCore/ChangeLog	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/ChangeLog	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,3 +1,43 @@
+2016-04-15  John Wilander  <[email protected]>
+
+        Refactor WebSockets handshake to use StringView instead of String for header validation.
+        https://bugs.webkit.org/show_bug.cgi?id=155602
+
+        Reviewed by Darin Adler.
+
+        No new tests. Existing test have been augmented.
+
+        * Modules/websockets/WebSocketHandshake.cpp:
+        (WebCore::WebSocketHandshake::readServerHandshake):
+            Made sure failure reason was set consistently with makeString().
+        (WebCore::headerHasValidHTTPVersion):
+            Now operates on the HTTP status line with StringView.
+        (WebCore::WebSocketHandshake::readStatusLine):
+            Now operates on the HTTP status line with StringView.
+        (WebCore::WebSocketHandshake::readHTTPHeaders):
+            Now operates on header names with StringView.
+            Made sure failure reason was set consistently with makeString() and ASCIILiteral().
+        (WebCore::WebSocketHandshake::checkResponseHeaders):
+            Made sure failure reason was set consistently with ASCIILiteral().
+        * platform/network/HTTPParsers.cpp:
+        (WebCore::parseHTTPRequestLine):
+            Made sure failure reason was set consistently with ASCIILiteral().
+        (WebCore::isValidHeaderNameCharacter):
+            Inlined function to check if a character is allowed in an HTTP header name according to RFC 7230.
+            https://tools.ietf.org/html/rfc7230 (June 2014)
+        (WebCore::parseHTTPHeader):
+        * platform/network/HTTPParsers.h:
+            Now receives the HTTP header name as a StringView.
+            Checks that header names only contain valid characters according to RFC 7230 (see above).
+        * platform/network/ResourceRequestBase.cpp:
+        (WebCore::ResourceRequestBase::addHTTPHeaderField):
+        * platform/network/ResourceRequestBase.h:
+             Now has an overloaded function which receives the HTTP header name as an HTTPHeaderName enum value.
+        * platform/network/ResourceResponseBase.cpp:
+        (WebCore::ResourceResponseBase::addHTTPHeaderField):
+        * platform/network/ResourceResponseBase.h:
+             Now has an overloaded function which receives the HTTP header name as an HTTPHeaderName enum value.
+
 2016-04-15  Joanmarie Diggs  <[email protected]>
 
         AX: Presentational role on SVG elements is trumped by child 'title' and 'desc' elements

Modified: trunk/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp (199589 => 199590)


--- trunk/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/Modules/websockets/WebSocketHandshake.cpp	2016-04-15 16:39:00 UTC (rev 199590)
@@ -50,6 +50,7 @@
 #include <wtf/ASCIICType.h>
 #include <wtf/CryptographicallyRandomNumber.h>
 #include <wtf/MD5.h>
+#include <wtf/NeverDestroyed.h>
 #include <wtf/SHA1.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/StringExtras.h>
@@ -304,7 +305,7 @@
 
     if (statusCode != 101) {
         m_mode = Failed;
-        m_failureReason = "Unexpected response code: " + String::number(statusCode);
+        m_failureReason = makeString("Unexpected response code: ", String::number(statusCode));
         return len;
     }
     m_mode = Normal;
@@ -394,27 +395,39 @@
 
 // https://tools.ietf.org/html/rfc6455#section-4.1
 // "The HTTP version MUST be at least 1.1."
-static inline bool headerHasValidHTTPVersion(const String& httpVersionString, const size_t headerLength)
+static inline bool headerHasValidHTTPVersion(StringView httpStatusLine)
 {
-    const size_t posOfHttp = httpVersionString.find("HTTP/");
-    if (posOfHttp == notFound)
+    const char* httpVersionStaticPreambleLiteral = "HTTP/";
+    StringView httpVersionStaticPreamble(reinterpret_cast<const LChar*>(httpVersionStaticPreambleLiteral), strlen(httpVersionStaticPreambleLiteral));
+    if (!httpStatusLine.startsWith(httpVersionStaticPreamble))
         return false;
 
-    // Check that there is a version number which should be three characters after "HTTP/"
-    const size_t posOfHttpVersionNumberString = posOfHttp + 5;
-    if (posOfHttpVersionNumberString + 3 >= headerLength)
+    // Check that there is a version number which should be at least three characters after "HTTP/"
+    unsigned preambleLength = httpVersionStaticPreamble.length();
+    if (httpStatusLine.length() < preambleLength + 3)
         return false;
 
-    // Check that the version number is 1.1 or above
-    bool versionNumberIsOneDotOneOrAbove = false;
-    if (httpVersionString.characterAt(posOfHttpVersionNumberString + 1) == '.') {
-        UChar majorVersion = httpVersionString.characterAt(posOfHttpVersionNumberString);
-        UChar minorVersion = httpVersionString.characterAt(posOfHttpVersionNumberString + 2);
-        if ((majorVersion == '1' && minorVersion >= '1' && minorVersion <= '9')
-            || (majorVersion > '1' && majorVersion <= '9' && minorVersion >= '0' && minorVersion <= '9'))
-            versionNumberIsOneDotOneOrAbove = true;
+    auto dotPosition = httpStatusLine.find('.', preambleLength);
+    if (dotPosition == notFound)
+        return false;
+
+    StringView majorVersionView = httpStatusLine.substring(preambleLength, dotPosition - preambleLength);
+    bool isValid;
+    int majorVersion = majorVersionView.toIntStrict(isValid);
+    if (!isValid)
+        return false;
+
+    unsigned minorVersionLength;
+    unsigned charactersLeftAfterDotPosition = httpStatusLine.length() - dotPosition;
+    for (minorVersionLength = 1; minorVersionLength < charactersLeftAfterDotPosition; minorVersionLength++) {
+        if (!isASCIIDigit(httpStatusLine[dotPosition + minorVersionLength]))
+            break;
     }
-    return versionNumberIsOneDotOneOrAbove;
+    int minorVersion = (httpStatusLine.substring(dotPosition + 1, minorVersionLength)).toIntStrict(isValid);
+    if (!isValid)
+        return false;
+
+    return (majorVersion >= 1 && minorVersion >= 1) || majorVersion >= 2;
 }
 
 // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet.
@@ -444,10 +457,10 @@
             // The caller isn't prepared to deal with null bytes in status
             // line. WebSockets specification doesn't prohibit this, but HTTP
             // does, so we'll just treat this as an error.
-            m_failureReason = "Status line contains embedded null";
+            m_failureReason = ASCIILiteral("Status line contains embedded null");
             return p + 1 - header;
         } else if (!isASCII(*p)) {
-            m_failureReason = "Status line contains non-ASCII character";
+            m_failureReason = ASCIILiteral("Status line contains non-ASCII character");
             return p + 1 - header;
         } else if (*p == '\n')
             break;
@@ -458,38 +471,38 @@
     const char* end = p + 1;
     int lineLength = end - header;
     if (lineLength > maximumLength) {
-        m_failureReason = "Status line is too long";
+        m_failureReason = ASCIILiteral("Status line is too long");
         return maximumLength;
     }
 
     // The line must end with "\r\n".
     if (lineLength < 2 || *(end - 2) != '\r') {
-        m_failureReason = "Status line does not end with CRLF";
+        m_failureReason = ASCIILiteral("Status line does not end with CRLF");
         return lineLength;
     }
 
     if (!space1 || !space2) {
-        m_failureReason = "No response code found: " + trimInputSample(header, lineLength - 2);
+        m_failureReason = makeString("No response code found: ", trimInputSample(header, lineLength - 2));
         return lineLength;
     }
 
-    String httpVersionString(header, space1 - header);
-    if (!headerHasValidHTTPVersion(httpVersionString, headerLength)) {
-        m_failureReason = "Invalid HTTP version string: " + httpVersionString;
+    StringView httpStatusLine(reinterpret_cast<const LChar*>(header), space1 - header);
+    if (!headerHasValidHTTPVersion(httpStatusLine)) {
+        m_failureReason = makeString("Invalid HTTP version string: ", httpStatusLine);
         return lineLength;
     }
 
-    String statusCodeString(space1 + 1, space2 - space1 - 1);
+    StringView statusCodeString(reinterpret_cast<const LChar*>(space1 + 1), space2 - space1 - 1);
     if (statusCodeString.length() != 3) // Status code must consist of three digits.
         return lineLength;
     for (int i = 0; i < 3; ++i)
         if (statusCodeString[i] < '0' || statusCodeString[i] > '9') {
-            m_failureReason = "Invalid status code: " + statusCodeString;
+            m_failureReason = makeString("Invalid status code: ", statusCodeString);
             return lineLength;
         }
 
     bool ok = false;
-    statusCode = statusCodeString.toInt(&ok);
+    statusCode = statusCodeString.toIntStrict(ok);
     ASSERT(ok);
 
     statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n".
@@ -498,7 +511,7 @@
 
 const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end)
 {
-    String name;
+    StringView name;
     String value;
     bool sawSecWebSocketExtensionsHeaderField = false;
     bool sawSecWebSocketAcceptHeaderField = false;
@@ -514,19 +527,25 @@
         if (name.isEmpty())
             break;
 
+        HTTPHeaderName headerName;
+        if (!findHTTPHeaderName(name, headerName)) {
+            m_failureReason = makeString("Unknown header name ", name, " in HTTP response");
+            return nullptr;
+        }
+
         // https://tools.ietf.org/html/rfc7230#section-3.2.4
         // "Newly defined header fields SHOULD limit their field values to US-ASCII octets."
-        if ((equalLettersIgnoringASCIICase(name, "sec-websocket-extensions")
-            || equalLettersIgnoringASCIICase(name, "sec-websocket-accept")
-            || equalLettersIgnoringASCIICase(name, "sec-websocket-protocol"))
+        if ((headerName == HTTPHeaderName::SecWebSocketExtensions
+            || headerName == HTTPHeaderName::SecWebSocketAccept
+            || headerName == HTTPHeaderName::SecWebSocketProtocol)
             && !value.containsOnlyASCII()) {
-            m_failureReason = name + " header value should only contain ASCII characters";
+            m_failureReason = makeString(name, " header value should only contain ASCII characters");
             return nullptr;
         }
         
-        if (equalLettersIgnoringASCIICase(name, "sec-websocket-extensions")) {
+        if (headerName == HTTPHeaderName::SecWebSocketExtensions) {
             if (sawSecWebSocketExtensionsHeaderField) {
-                m_failureReason = "The Sec-WebSocket-Extensions header must not appear more than once in an HTTP response";
+                m_failureReason = ASCIILiteral("The Sec-WebSocket-Extensions header must not appear more than once in an HTTP response");
                 return nullptr;
             }
             if (!m_extensionDispatcher.processHeaderValue(value)) {
@@ -534,22 +553,23 @@
                 return nullptr;
             }
             sawSecWebSocketExtensionsHeaderField = true;
-        } else if (equalLettersIgnoringASCIICase(name, "sec-websocket-accept")) {
-            if (sawSecWebSocketAcceptHeaderField) {
-                m_failureReason = "The Sec-WebSocket-Accept header must not appear more than once in an HTTP response";
-                return nullptr;
+        } else {
+            if (headerName == HTTPHeaderName::SecWebSocketAccept) {
+                if (sawSecWebSocketAcceptHeaderField) {
+                    m_failureReason = ASCIILiteral("The Sec-WebSocket-Accept header must not appear more than once in an HTTP response");
+                    return nullptr;
+                }
+                sawSecWebSocketAcceptHeaderField = true;
+            } else if (headerName == HTTPHeaderName::SecWebSocketProtocol) {
+                if (sawSecWebSocketProtocolHeaderField) {
+                    m_failureReason = ASCIILiteral("The Sec-WebSocket-Protocol header must not appear more than once in an HTTP response");
+                    return nullptr;
+                }
+                sawSecWebSocketProtocolHeaderField = true;
             }
-            m_serverHandshakeResponse.addHTTPHeaderField(name, value);
-            sawSecWebSocketAcceptHeaderField = true;
-        } else if (equalLettersIgnoringASCIICase(name, "sec-websocket-protocol")) {
-            if (sawSecWebSocketProtocolHeaderField) {
-                m_failureReason = "The Sec-WebSocket-Protocol header must not appear more than once in an HTTP response";
-                return nullptr;
-            }
-            m_serverHandshakeResponse.addHTTPHeaderField(name, value);
-            sawSecWebSocketProtocolHeaderField = true;
-        } else
-            m_serverHandshakeResponse.addHTTPHeaderField(name, value);
+
+            m_serverHandshakeResponse.addHTTPHeaderField(headerName, value);
+        }
     }
     return p;
 }
@@ -562,40 +582,40 @@
     const String& serverWebSocketAccept = this->serverWebSocketAccept();
 
     if (serverUpgrade.isNull()) {
-        m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: 'Upgrade' header is missing");
         return false;
     }
     if (serverConnection.isNull()) {
-        m_failureReason = "Error during WebSocket handshake: 'Connection' header is missing";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: 'Connection' header is missing");
         return false;
     }
     if (serverWebSocketAccept.isNull()) {
-        m_failureReason = "Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing");
         return false;
     }
 
     if (!equalLettersIgnoringASCIICase(serverUpgrade, "websocket")) {
-        m_failureReason = "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'");
         return false;
     }
     if (!equalLettersIgnoringASCIICase(serverConnection, "upgrade")) {
-        m_failureReason = "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'");
         return false;
     }
 
     if (serverWebSocketAccept != m_expectedAccept) {
-        m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch";
+        m_failureReason = ASCIILiteral("Error during WebSocket handshake: Sec-WebSocket-Accept mismatch");
         return false;
     }
     if (!serverWebSocketProtocol.isNull()) {
         if (m_clientProtocol.isEmpty()) {
-            m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch";
+            m_failureReason = ASCIILiteral("Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch");
             return false;
         }
         Vector<String> result;
         m_clientProtocol.split(String(WebSocket::subProtocolSeperator()), result);
         if (!result.contains(serverWebSocketProtocol)) {
-            m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch";
+            m_failureReason = ASCIILiteral("Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch");
             return false;
         }
     }

Modified: trunk/Source/WebCore/platform/network/HTTPParsers.cpp (199589 => 199590)


--- trunk/Source/WebCore/platform/network/HTTPParsers.cpp	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/HTTPParsers.cpp	2016-04-15 16:39:00 UTC (rev 199590)
@@ -582,20 +582,20 @@
 
     // Haven't finished header line.
     if (consumedLength == length) {
-        failureReason = "Incomplete Request Line";
+        failureReason = ASCIILiteral("Incomplete Request Line");
         return 0;
     }
 
     // RequestLine does not contain 3 parts.
     if (!space1 || !space2) {
-        failureReason = "Request Line does not appear to contain: <Method> <Url> <HTTPVersion>.";
+        failureReason = ASCIILiteral("Request Line does not appear to contain: <Method> <Url> <HTTPVersion>.");
         return 0;
     }
 
     // The line must end with "\r\n".
     const char* end = p + 1;
     if (*(end - 2) != '\r') {
-        failureReason = "Request line does not end with CRLF";
+        failureReason = ASCIILiteral("Request line does not end with CRLF");
         return 0;
     }
 
@@ -619,14 +619,48 @@
     return end - data;
 }
 
-size_t parseHTTPHeader(const char* start, size_t length, String& failureReason, String& nameStr, String& valueStr, bool strict)
+static inline bool isValidHeaderNameCharacter(const char* character)
 {
+    // https://tools.ietf.org/html/rfc7230#section-3.2
+    // A header name should only contain one or more of
+    // alphanumeric or ! # $ % & ' * + - . ^ _ ` | ~
+    if (isASCIIAlphanumeric(*character))
+        return true;
+    switch (*character) {
+    case '!':
+    case '#':
+    case '$':
+    case '%':
+    case '&':
+    case '\'':
+    case '*':
+    case '+':
+    case '-':
+    case '.':
+    case '^':
+    case '_':
+    case '`':
+    case '|':
+    case '~':
+        return true;
+    default:
+        return false;
+    }
+}
+
+size_t parseHTTPHeader(const char* start, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict)
+{
     const char* p = start;
     const char* end = start + length;
 
     Vector<char> name;
     Vector<char> value;
-    nameStr = String();
+
+    bool foundFirstNameChar = false;
+    const char* namePtr;
+    size_t nameSize = 0;
+
+    nameStr = StringView();
     valueStr = String();
 
     for (; p < end; p++) {
@@ -635,18 +669,29 @@
             if (name.isEmpty()) {
                 if (p + 1 < end && *(p + 1) == '\n')
                     return (p + 2) - start;
-                failureReason = "CR doesn't follow LF at " + trimInputSample(p, end - p);
+                failureReason = makeString("CR doesn't follow LF in header name at ", trimInputSample(p, end - p));
                 return 0;
             }
-            failureReason = "Unexpected CR in name at " + trimInputSample(name.data(), name.size());
+            failureReason = makeString("Unexpected CR in header name at ", trimInputSample(name.data(), name.size()));
             return 0;
         case '\n':
-            failureReason = "Unexpected LF in name at " + trimInputSample(name.data(), name.size());
+            failureReason = makeString("Unexpected LF in header name at ", trimInputSample(name.data(), name.size()));
             return 0;
         case ':':
             break;
         default:
+            if (!isValidHeaderNameCharacter(p)) {
+                if (name.size() < 1)
+                    failureReason = "Unexpected start character in header name";
+                else
+                    failureReason = makeString("Unexpected character in header name at ", trimInputSample(name.data(), name.size()));
+                return 0;
+            }
             name.append(*p);
+            if (!foundFirstNameChar) {
+                namePtr = p;
+                foundFirstNameChar = true;
+            }
             continue;
         }
         if (*p == ':') {
@@ -655,6 +700,9 @@
         }
     }
 
+    nameSize = name.size();
+    nameStr = StringView(reinterpret_cast<const LChar*>(namePtr), nameSize);
+
     for (; p < end && *p == 0x20; p++) { }
 
     for (; p < end; p++) {
@@ -663,7 +711,7 @@
             break;
         case '\n':
             if (strict) {
-                failureReason = "Unexpected LF in value at " + trimInputSample(value.data(), value.size());
+                failureReason = makeString("Unexpected LF in header value at ", trimInputSample(value.data(), value.size()));
                 return 0;
             }
             break;
@@ -676,17 +724,12 @@
         }
     }
     if (p >= end || (strict && *p != '\n')) {
-        failureReason = "CR doesn't follow LF after value at " + trimInputSample(p, end - p);
+        failureReason = makeString("CR doesn't follow LF after header value at ", trimInputSample(p, end - p));
         return 0;
     }
-    nameStr = String::fromUTF8(name.data(), name.size());
     valueStr = String::fromUTF8(value.data(), value.size());
-    if (nameStr.isNull()) {
-        failureReason = "Invalid UTF-8 sequence in header name";
-        return 0;
-    }
     if (valueStr.isNull()) {
-        failureReason = "Invalid UTF-8 sequence in header value";
+        failureReason = ASCIILiteral("Invalid UTF-8 sequence in header value");
         return 0;
     }
     return p - start;

Modified: trunk/Source/WebCore/platform/network/HTTPParsers.h (199589 => 199590)


--- trunk/Source/WebCore/platform/network/HTTPParsers.h	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/HTTPParsers.h	2016-04-15 16:39:00 UTC (rev 199590)
@@ -91,7 +91,7 @@
 // Parsing Complete HTTP Messages.
 enum HTTPVersion { Unknown, HTTP_1_0, HTTP_1_1 };
 size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReason, String& method, String& url, HTTPVersion&);
-size_t parseHTTPHeader(const char* data, size_t length, String& failureReason, String& nameStr, String& valueStr, bool strict = true);
+size_t parseHTTPHeader(const char* data, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict = true);
 size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned char>& body);
 
 inline bool isHTTPSpace(UChar character)

Modified: trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp (199589 => 199590)


--- trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp	2016-04-15 16:39:00 UTC (rev 199590)
@@ -465,7 +465,7 @@
         m_platformRequestUpdated = false;
 }
 
-void ResourceRequestBase::addHTTPHeaderField(const String& name, const String& value) 
+void ResourceRequestBase::addHTTPHeaderField(HTTPHeaderName name, const String& value)
 {
     updateResourceRequest();
 
@@ -475,6 +475,16 @@
         m_platformRequestUpdated = false;
 }
 
+void ResourceRequestBase::addHTTPHeaderField(const String& name, const String& value)
+{
+    updateResourceRequest();
+    
+    m_httpHeaderFields.add(name, value);
+    
+    if (url().protocolIsInHTTPFamily())
+        m_platformRequestUpdated = false;
+}
+    
 void ResourceRequestBase::setHTTPHeaderFields(HTTPHeaderMap headerFields)
 {
     updateResourceRequest();

Modified: trunk/Source/WebCore/platform/network/ResourceRequestBase.h (199589 => 199590)


--- trunk/Source/WebCore/platform/network/ResourceRequestBase.h	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/ResourceRequestBase.h	2016-04-15 16:39:00 UTC (rev 199590)
@@ -86,6 +86,7 @@
         WEBCORE_EXPORT String httpHeaderField(HTTPHeaderName) const;
         WEBCORE_EXPORT void setHTTPHeaderField(const String& name, const String& value);
         WEBCORE_EXPORT void setHTTPHeaderField(HTTPHeaderName, const String& value);
+        void addHTTPHeaderField(HTTPHeaderName, const String& value);
         void addHTTPHeaderField(const String& name, const String& value);
 
         // Instead of passing a string literal to any of these functions, just use a HTTPHeaderName instead.

Modified: trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp (199589 => 199590)


--- trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp	2016-04-15 16:39:00 UTC (rev 199590)
@@ -334,15 +334,22 @@
     // FIXME: Should invalidate or update platform response if present.
 }
 
-void ResourceResponseBase::addHTTPHeaderField(const String& name, const String& value)
+void ResourceResponseBase::addHTTPHeaderField(HTTPHeaderName name, const String& value)
 {
     lazyInit(AllFields);
+    updateHeaderParsedState(name);
+    m_httpHeaderFields.add(name, value);
+}
 
+void ResourceResponseBase::addHTTPHeaderField(const String& name, const String& value)
+{
     HTTPHeaderName headerName;
     if (findHTTPHeaderName(name, headerName))
-        updateHeaderParsedState(headerName);
-
-    m_httpHeaderFields.add(name, value);
+        addHTTPHeaderField(headerName, value);
+    else {
+        lazyInit(AllFields);
+        m_httpHeaderFields.add(name, value);
+    }
 }
 
 const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const

Modified: trunk/Source/WebCore/platform/network/ResourceResponseBase.h (199589 => 199590)


--- trunk/Source/WebCore/platform/network/ResourceResponseBase.h	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebCore/platform/network/ResourceResponseBase.h	2016-04-15 16:39:00 UTC (rev 199590)
@@ -84,6 +84,7 @@
     WEBCORE_EXPORT void setHTTPHeaderField(const String& name, const String& value);
     void setHTTPHeaderField(HTTPHeaderName, const String& value);
 
+    void addHTTPHeaderField(HTTPHeaderName, const String& value);
     void addHTTPHeaderField(const String& name, const String& value);
 
     // Instead of passing a string literal to any of these functions, just use a HTTPHeaderName instead.

Modified: trunk/Source/WebKit2/ChangeLog (199589 => 199590)


--- trunk/Source/WebKit2/ChangeLog	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebKit2/ChangeLog	2016-04-15 16:39:00 UTC (rev 199590)
@@ -1,3 +1,14 @@
+2016-04-15  John Wilander  <[email protected]>
+
+        Refactor WebSockets handshake to use StringView instead of String for header validation.
+        https://bugs.webkit.org/show_bug.cgi?id=155602
+
+        Reviewed by Darin Adler.
+
+        * UIProcess/InspectorServer/HTTPRequest.cpp:
+        (WebKit::HTTPRequest::parseHeaders):
+            Now declares the HTTP header name as a StringView to match the change in WebCore::parseHTTPHeader.
+
 2016-04-15  Alex Christensen  <[email protected]>
 
         Don't copy entire NSURLSessionConfiguration just to test for credentials

Modified: trunk/Source/WebKit2/UIProcess/InspectorServer/HTTPRequest.cpp (199589 => 199590)


--- trunk/Source/WebKit2/UIProcess/InspectorServer/HTTPRequest.cpp	2016-04-15 16:18:37 UTC (rev 199589)
+++ trunk/Source/WebKit2/UIProcess/InspectorServer/HTTPRequest.cpp	2016-04-15 16:39:00 UTC (rev 199590)
@@ -27,6 +27,7 @@
 #include "HTTPRequest.h"
 
 #include <wtf/text/CString.h>
+#include <wtf/text/StringView.h>
 
 using namespace WebCore;
 
@@ -82,7 +83,7 @@
 {
     const char* p = data;
     const char* end = data + length;
-    String name;
+    StringView name;
     String value;
     for (; p < data + length; p++) {
         size_t consumedLength = parseHTTPHeader(p, end - p, failureReason, name, value);
@@ -91,7 +92,7 @@
         p += consumedLength;
         if (name.isEmpty())
             break;
-        m_headerFields.add(name, value);
+        m_headerFields.add(name.toString(), value);
     }
 
     // If we got here and "name" is empty, it means the headers are valid and ended with a
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to