r47310 - beep boop

hawkowl-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org Sat, 23 Apr 2016 22:13:50 -0600 (MDT)
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: hawkowl
Date: Sat Apr 23 22:13:44 2016
New Revision: 47310

Modified:
   branches/proper-upgrade-8301-2/twisted/web/http.py
   branches/proper-upgrade-8301-2/twisted/web/test/test_http.py

Log:
beep boop

Modified: branches/proper-upgrade-8301-2/twisted/web/http.py
==============================================================================
--- branches/proper-upgrade-8301-2/twisted/web/http.py	(original)
+++ branches/proper-upgrade-8301-2/twisted/web/http.py	Sat Apr 23 22:13:44 2016
@@ -134,6 +134,7 @@
     _intTypes = (int, long)
 
 _version = networkString("TwistedWeb/%s" % (copyright.version,))
+_MAX_HEADERS_SIZE = 16384 # 16K, on par with IIS
 
 protocol_version = "HTTP/1.1"
 
@@ -1612,7 +1613,7 @@
     """
 
     maxHeaders = 500
-    totalHeadersSize = 16384
+    totalHeadersSize = _MAX_HEADERS_SIZE
 
     length = 0
     persistent = 1
@@ -1999,7 +2000,6 @@
     transport.write(b"Connection: Upgrade\r\n")
 
     for k, v in headers.items():
-
         transport.write(k + b": " + v + b"\r\n")
 
     transport.write(b"\r\n")
@@ -2035,9 +2035,12 @@
     _buffering = False
     _replay = False
 
+    _maxHeadersLength = _MAX_HEADERS_SIZE
+
 
     def __init__(self, *args, **kwargs):
-        self._buffer = []
+        self._buffer = b""
+        self._bufferLen = 0
         super(_GenericHTTPChannelProtocol, self).__init__(*args, **kwargs)
 
     @property
@@ -2082,51 +2085,78 @@
         """
         Look at the headers and determine if they want us to upgrade.
         """
-        content = b"".join(self._buffer).split(b"\r\n\r\n", 1)[0].split(b"\r\n")
+        content = self._buffer.split(b"\r\n\r\n", 1)[0].split(b"\r\n")
         headers = {}
 
+        requestLine = content[0].split(b" ")
+
+        if not len(requestLine) == 3:
+            _respondToBadRequestAndDisconnect(self._channel.transport)
+            return None
+
+        if requestLine[2].lower() == b"http/1.0":
+            # It's HTTP/1.0, return that
+            print("http1.0")
+            return b"http/1.0"
+
+        if not requestLine[2].lower() == b"http/1.1":
+            # If it's not HTTP/1.0 or HTTP/1.1, we don't really know what to
+            # do!
+            print("not 1.1")
+            _respondToBadRequestAndDisconnect(self._channel.transport)
+            return None
+
         for line in content[1:]:
             key, val = line.split(b":", 1)
             headers[key.lower()] = val.lstrip()
 
+        path = requestLine[1]
+
         if b"connection" not in headers or b"upgrade" not in headers:
-            print("not negotiating")
+            print("no upgrade")
             return b"http/1.1"
 
-        else:
-            if not b"upgrade" in headers[b"connection"].lower().split(b", "):
-                # connection is there and upgrade is there but its not saying to upgrade
-                return b"http/1.1"
-
-            for upgrade in headers[b"upgrade"].split(b", "):
-
-                upgrader = self.factory.upgradeables.get(upgrade.lower())
-
-                if upgrader:
-                    try:
-                        res = upgrader(self, headers)
-                        transport = self._channel.transport
-                        self._channel, self._replay, headersToSend = res
-                        _respondToUpgrade(transport, upgrade, headersToSend)
-                        self._channel.makeConnection(transport)
-                        return upgrade
-                    except CannotUpgrade:
-                        pass
+        if not b"upgrade" in headers[b"connection"].lower().split(b", "):
+            # connection is there and upgrade is there but its not saying to upgrade
+            print("no upgrade")
+            return b"http/1.1"
 
-                return b"http/1.1"
+        for upgrade in headers[b"upgrade"].split(b", "):
+            # See if we can upgrade to this. If we can't, try the next,
+            upgrader = self.factory.upgradeables.get(upgrade.lower())
 
-            return None
+            if upgrader:
+                try:
+                    res = upgrader(self, path, headers)
+                    transport = self._channel.transport
+                    self._channel, self._replay, headersToSend = res
+                    _respondToUpgrade(transport, upgrade, headersToSend)
+                    self._channel.makeConnection(transport)
+                    return upgrade
+                except CannotUpgrade:
+                    # This one failed, try the next one
+                    pass
+
+        print("no negotiate")
+        # Negotiation failed!
+        return b"http/1.1"
 
 
     def _bufferData(self, data):
+        """
+        Add data to the buffer.
+        """
+        self._buffer += (data)
+        self._bufferLen += len(data)
+
+        if self._bufferLen > self._maxHeadersLength:
+            _respondToBadRequestAndDisconnect(self._channel)
 
-        self._buffer.append(data)
-        if b"\r\n\r\n" in data:
+        if b"\r\n\r\n" in self._buffer:
             self._negotiatedProtocol = self._upgrade()
 
             if self._negotiatedProtocol == b"http/1.1":
-                for x in self._buffer:
-                    self._channel.dataReceived(x)
+                self._channel.dataReceived(self._buffer)
             self._buffering = False
             self._buffer = []
 

Modified: branches/proper-upgrade-8301-2/twisted/web/test/test_http.py
==============================================================================
--- branches/proper-upgrade-8301-2/twisted/web/test/test_http.py	(original)
+++ branches/proper-upgrade-8301-2/twisted/web/test/test_http.py	Sat Apr 23 22:13:44 2016
@@ -21,8 +21,9 @@
 from twisted.trial import unittest
 from twisted.trial.unittest import TestCase
 from twisted.web import http, http_headers
-from twisted.web.http import PotentialDataLoss, _DataLoss, _version
-from twisted.web.http import _IdentityTransferDecoder
+from twisted.web.http import (
+    _respondToUpgrade, HTTPFactory, PotentialDataLoss, _DataLoss, _version,
+    _IdentityTransferDecoder)
 from twisted.internet.protocol import Protocol, Factory
 from twisted.internet.task import Clock
 from twisted.internet.error import ConnectionLost
@@ -272,7 +273,8 @@
     def test_protocolUnspecified(self):
         """
         If the transport has no support for protocol negotiation (no
-        negotiatedProtocol attribute), HTTP/1.1 is assumed.
+        negotiatedProtocol attribute), the protocol of the first request is
+        used.
         """
         b = StringTransport()
         negotiatedProtocol = self._negotiatedProtocolForTransportInstance(b)
@@ -281,8 +283,8 @@
 
     def test_protocolNone(self):
         """
-        If the transport has no support for protocol negotiation (returns None
-        for negotiatedProtocol), HTTP/1.1 is assumed.
+        If the transport has no support for protocol negotiation, the request
+        is inspected and the protocol the request requests is returned.
         """
         b = StringTransport()
         b.negotiatedProtocol = None
@@ -2541,9 +2543,24 @@
     """
     Tests for HTTP/1.1 protocol upgrade.
     """
+    def _makeFactory(self):
+        """
+        Make a testing suitable L{HTTPFactory} which doesn't rely on a running
+        reactor.
+        """
+        factory = HTTPFactory()
+        factory._logDateTime = "sometime"
+        factory._logDateTimeCall = True
+        factory.startFactory()
+        return factory
 
-    def test_basic(self):
 
+    def test_upgrade(self):
+        """
+        A HTTP/1.1 request with a "Connection" header that contains "Upgrade"
+        and an "Upgrade" header that lists a protocol we support will be
+        upgraded to that protocol.
+        """
         piTimes = 10
 
         class Pitocol(Protocol):
@@ -2565,18 +2582,11 @@
         piFactory = Factory()
         piFactory.protocol = Pitocol
 
-        def piNegotiate(channel, headers):
+        def piNegotiate(channel, path, headers):
             pi = piFactory.buildProtocol(None)
-            return pi, False, {}
-
-
-        from twisted.web.http import _respondToUpgrade, HTTPFactory
-
-        factory = HTTPFactory()
-        factory._logDateTime = "sometime"
-        factory._logDateTimeCall = True
-        factory.startFactory()
+            return pi, False, {b"beep": b"boop"}
 
+        factory = self._makeFactory()
         factory.upgradeables[b"pitocol"] = piNegotiate
 
         protocol = factory.buildProtocol(None)
@@ -2587,10 +2597,11 @@
         val = [
             b"GET / HTTP/1.1\r\n"
             b"Connection: keep-alive, Upgrade\r\n",
-            b"Upgrade: pitocol\r\n\r\n",
+            b"Upgrade: pitocol\r\n",
+            b"beep: boop\r\n\r\n",
         ]
 
-        for x in val:
+        for x in iterbytes(b"".join(val)):
             protocol.dataReceived(x)
 
         expectedValue = b"".join([
@@ -2604,3 +2615,46 @@
 
         self.assertEqual(trans.value(), b"3.14" * 10)
         self.assertTrue(trans.disconnecting)
+
+
+    def test_notHTTP(self):
+        """
+        A non-HTTP request should return with a "bad request" error.
+        """
+        factory = self._makeFactory()
+        protocol = factory.buildProtocol(None)
+
+        trans = StringTransport()
+        protocol.makeConnection(trans)
+
+        val = [
+            b"BEEP BOOP IRC/1234\r\n\r\n",
+        ]
+
+        for x in iterbytes(b"".join(val)):
+            protocol.dataReceived(x)
+
+        expectedValue = b"HTTP/1.1 400 Bad Request\r\n\r\n"
+        self.assertEqual(trans.value(), expectedValue)
+
+
+    def test_mangledStatusLine(self):
+        """
+        A non-HTTP first-line (or a mangled one) will return with a
+        "bad request" error.
+        """
+        factory = self._makeFactory()
+        protocol = factory.buildProtocol(None)
+
+        trans = StringTransport()
+        protocol.makeConnection(trans)
+
+        val = [
+            b"GET/ HTTP/1.1\r\n\r\n",
+        ]
+
+        for x in iterbytes(b"".join(val)):
+            protocol.dataReceived(x)
+
+        expectedValue = b"HTTP/1.1 400 Bad Request\r\n\r\n"
+        self.assertEqual(trans.value(), expectedValue)