Ticket #6927: 6927-1.diff

File 6927-1.diff, 8.7 KB (added by Adi Roiban, 8 years ago)
  • twisted/web/http.py

    diff --git a/twisted/web/http.py b/twisted/web/http.py
    index 4024b0f..c92108a 100644
    a b class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    15931593    @ivar _transferDecoder: C{None} or an instance of
    15941594        L{_ChunkedTransferDecoder} if the request body uses the I{chunked}
    15951595        Transfer-Encoding.
     1596    @ivar maxHeaders: Maximum number of headers allowed per request.
     1597    @ivar totalHeadersSize: Maximum bytes for request line plus all headers
     1598        from the request.
     1599
     1600    Maximum length for initial request line and each header is defined
     1601    by L{basic.LineReceiver.MAX_LENGTH}.
    15961602    """
    15971603
    1598     maxHeaders = 500 # max number of headers allowed per request
     1604    maxHeaders = 500
     1605    totalHeadersSize = 16384
    15991606
    16001607    length = 0
    16011608    persistent = 1
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16081615
    16091616    _savedTimeOut = None
    16101617    _receivedHeaderCount = 0
     1618    _receivedHeaderSize = 0
    16111619
    16121620    def __init__(self):
    16131621        # the request queue
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16181626    def connectionMade(self):
    16191627        self.setTimeout(self.timeOut)
    16201628
     1629
    16211630    def lineReceived(self, line):
     1631        """
     1632        Called for each line from request until the end of headers when
     1633        it enters binary mode.
     1634        """
    16221635        self.resetTimeout()
    16231636
     1637        self._receivedHeaderSize += len(line)
     1638        if (self._receivedHeaderSize > self.totalHeadersSize):
     1639            self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
     1640            self.transport.loseConnection()
     1641            return
     1642
    16241643        if self.__first_line:
    16251644            # if this connection is not persistent, drop any data which
    16261645            # the client (illegally) sent after the last request.
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    17191738        # reset ALL state variables, so we don't interfere with next request
    17201739        self.length = 0
    17211740        self._receivedHeaderCount = 0
     1741        self._receivedHeaderSize = 0
    17221742        self.__first_line = 1
    17231743        self._transferDecoder = None
    17241744        del self._command, self._path, self._version
  • twisted/web/test/test_http.py

    diff --git a/twisted/web/test/test_http.py b/twisted/web/test/test_http.py
    index 5a0a853..370682f 100644
    a b class ParsingTestCase(unittest.TestCase): 
    660660        self.didRequest = False
    661661
    662662
    663     def runRequest(self, httpRequest, requestClass, success=1):
     663    def runRequest(
     664            self, httpRequest, requestFactory=None, channel=None,
     665            success=False):
     666        """
     667        Execute a request having content in `httpRequest`.
     668
     669        `channel` is the instance processing the request.
     670
     671        When defined `requestFactory` is used for the channel.
     672
     673        It will check `self.didRequest` for `success` value.
     674
     675        Returns the channel used for processing the request.
     676        """
     677        if not channel:
     678            channel = http.HTTPChannel()
     679
     680        if requestFactory:
     681            channel.requestFactory = requestFactory
     682
    664683        httpRequest = httpRequest.replace(b"\n", b"\r\n")
    665         b = StringTransport()
    666         a = http.HTTPChannel()
    667         a.requestFactory = requestClass
    668         a.makeConnection(b)
     684        transport = StringTransport()
     685
     686        channel.makeConnection(transport)
    669687        # one byte at a time, to stress it.
    670688        for byte in iterbytes(httpRequest):
    671             if a.transport.disconnecting:
     689            if channel.transport.disconnecting:
    672690                break
    673             a.dataReceived(byte)
    674         a.connectionLost(IOError("all done"))
     691            channel.dataReceived(byte)
     692        channel.connectionLost(IOError("all done"))
     693
    675694        if success:
    676695            self.assertTrue(self.didRequest)
    677696        else:
    678697            self.assertFalse(self.didRequest)
    679         return a
     698        return channel
    680699
    681700
    682701    def test_basicAuth(self):
    class ParsingTestCase(unittest.TestCase): 
    803822            b'\r\n')
    804823
    805824
     825    def test_headersTooBigInitialCommand(self):
     826        """
     827        Enforces a limit of C{HTTPChannel.totalHeadersSize}
     828        on the size of headers received per request starting from initial
     829        command line.
     830        """
     831        channel = http.HTTPChannel()
     832        channel.totalHeadersSize = 10
     833        httpRequest = b'GET /path/longer/than/10 HTTP/1.1\n'
     834
     835        channel = self.runRequest(httpRequest=httpRequest, channel=channel)
     836
     837        self.assertEqual(
     838            channel.transport.value(),
     839            b"HTTP/1.1 400 Bad Request\r\n\r\n")
     840
     841
     842    def test_headersTooBigOtherHeaders(self):
     843        """
     844        Enforces a limit of C{HTTPChannel.totalHeadersSize}
     845        on the size of headers received per request counting first line
     846        and total headers.
     847        """
     848        channel = http.HTTPChannel()
     849        channel.totalHeadersSize = 40
     850        httpRequest = (
     851            b'GET /less/than/40 HTTP/1.1\n'
     852            b'Some-Header: less-than-40\n'
     853            )
     854
     855        channel = self.runRequest(httpRequest=httpRequest, channel=channel)
     856
     857        self.assertEqual(
     858            channel.transport.value(),
     859            b"HTTP/1.1 400 Bad Request\r\n\r\n")
     860
     861
     862    def test_headersTooBigPerRequest(self):
     863        """
     864        Enforces total size of headers per individual request and counter
     865        is reset at the end of each request.
     866        """
     867        class SimpleRequest(http.Request):
     868            def process(self):
     869                self.finish()
     870        channel = http.HTTPChannel()
     871        channel.totalHeadersSize = 60
     872        channel.requestFactory = SimpleRequest
     873        httpRequest = (
     874            b'GET / HTTP/1.1\n'
     875            b'Some-Header: total-less-than-60\n'
     876            b'\n'
     877            b'GET / HTTP/1.1\n'
     878            b'Some-Header: less-than-60\n'
     879            b'\n'
     880            )
     881
     882        channel = self.runRequest(
     883            httpRequest=httpRequest, channel=channel)
     884
     885        self.assertEqual(
     886            channel.transport.value(),
     887            b'HTTP/1.1 200 OK\r\n'
     888            b'Transfer-Encoding: chunked\r\n'
     889            b'\r\n'
     890            b'0\r\n'
     891            b'\r\n'
     892            b'HTTP/1.1 200 OK\r\n'
     893            b'Transfer-Encoding: chunked\r\n'
     894            b'\r\n'
     895            b'0\r\n'
     896            b'\r\n'
     897            )
     898
     899
    806900    def testCookies(self):
    807901        """
    808902        Test cookies parsing and reading.
    Cookie: rabbit="eat carrot"; ninja=secret; spam="hey 1=1!" 
    821915                testcase.didRequest = True
    822916                self.finish()
    823917
    824         self.runRequest(httpRequest, MyRequest)
     918        self.runRequest(httpRequest, MyRequest, success=True)
    825919
    826920        self.assertEqual(
    827921            cookies, {
    GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0 
    848942                testcase.didRequest = True
    849943                self.finish()
    850944
    851         self.runRequest(httpRequest, MyRequest)
     945        self.runRequest(httpRequest, MyRequest, success=True)
    852946        self.assertEqual(method, [b"GET"])
    853947        self.assertEqual(
    854948            args, [[b"value"], [b""], [b"two words", b"more words"]])
    GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0 
    874968                testcase.didRequest = True
    875969                self.finish()
    876970
    877         self.runRequest(httpRequest, MyRequest)
     971        self.runRequest(httpRequest, MyRequest, success=True)
    878972        self.assertEqual(method, [b'GET'])
    879973        self.assertEqual(path, [b'/foo'])
    880974        self.assertEqual(args, [[b'?'], [b'quux']])
    Content-Type: application/x-www-form-urlencoded 
    9101004                testcase.didRequest = True
    9111005                self.finish()
    9121006
    913         self.runRequest(httpRequest, MyRequest)
     1007        self.runRequest(httpRequest, MyRequest, success=True)
    9141008        self.assertEqual(method, [b"POST"])
    9151009        self.assertEqual(
    9161010            args, [[b"value"], [b""], [b"two words", b"more words"]])
    Hello, 
    9711065                testcase.didRequest = True
    9721066                self.finish()
    9731067
    974         self.runRequest(httpRequest, MyRequest)
     1068        self.runRequest(httpRequest, MyRequest, success=True)
    9751069        # The tempfile API used to create content returns an
    9761070        # instance of a different type depending on what platform
    9771071        # we're running on.  The point here is to verify that the
  • new file twisted/web/topfiles/6933.misc

    diff --git a/twisted/web/topfiles/6933.misc b/twisted/web/topfiles/6933.misc
    new file mode 100644
    index 0000000..f72a31f
    - +  
     1twisted.web.http.HTTPChannel now limit the total headers size, including first command line, to 16KB.
     2 No newline at end of file