Ticket #6927: 6927-2.diff

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

    diff --git twisted/web/http.py twisted/web/http.py
    index 4024b0f..fe00adf 100644
    class Request: 
    818818                        # content-dispostion headers in multipart/form-data
    819819                        # parts, so we catch the exception and tell the client
    820820                        # it was a bad request.
    821                         self.channel.transport.write(
    822                                 b"HTTP/1.1 400 Bad Request\r\n\r\n")
    823                         self.channel.transport.loseConnection()
     821                        self.channel.respondToBadRequestAndDisconnect()
    824822                        return
    825823                    raise
    826824            self.content.seek(0, 0)
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    15901588    """
    15911589    A receiver for HTTP requests.
    15921590
    1593     @ivar _transferDecoder: C{None} or an instance of
    1594         L{_ChunkedTransferDecoder} if the request body uses the I{chunked}
    1595         Transfer-Encoding.
     1591    Maximum length for initial request line and each header is defined by
     1592    L{basic.LineReceiver.MAX_LENGTH}.
     1593
     1594    @ivar _transferDecoder: C{None} or a decoder instance if the request body
     1595        uses the I{chunked} Transfer-Encoding.
     1596    @type maxHeaders: L{_ChunkedTransferDecoder}
     1597
     1598    @ivar maxHeaders: Maximum number of headers allowed per request.
     1599    @type maxHeaders: C{int}
     1600
     1601    @ivar totalHeadersSize: Maximum bytes for request line plus all headers
     1602        from the request.
     1603    @type totalHeadersSize: C{int}
     1604
     1605    @ivar _receivedHeaderSize: Bytes received so far for the header.
     1606    @type _receivedHeaderSize: C{int}
    15961607    """
    15971608
    1598     maxHeaders = 500 # max number of headers allowed per request
     1609    maxHeaders = 500
     1610    totalHeadersSize = 16384
    15991611
    16001612    length = 0
    16011613    persistent = 1
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16081620
    16091621    _savedTimeOut = None
    16101622    _receivedHeaderCount = 0
     1623    _receivedHeaderSize = 0
    16111624
    16121625    def __init__(self):
    16131626        # the request queue
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16181631    def connectionMade(self):
    16191632        self.setTimeout(self.timeOut)
    16201633
     1634
    16211635    def lineReceived(self, line):
     1636        """
     1637        Called for each line from request until the end of headers when
     1638        it enters binary mode.
     1639        """
    16221640        self.resetTimeout()
    16231641
     1642        self._receivedHeaderSize += len(line)
     1643        if (self._receivedHeaderSize > self.totalHeadersSize):
     1644            self.respondToBadRequestAndDisconnect()
     1645            return
     1646
    16241647        if self.__first_line:
    16251648            # if this connection is not persistent, drop any data which
    16261649            # the client (illegally) sent after the last request.
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16411664            self.__first_line = 0
    16421665            parts = line.split()
    16431666            if len(parts) != 3:
    1644                 self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
    1645                 self.transport.loseConnection()
     1667                self.respondToBadRequestAndDisconnect()
    16461668                return
    16471669            command, request, version = parts
    16481670            self._command = command
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16651687            self.__header = line
    16661688
    16671689
     1690    def respondToBadRequestAndDisconnect(self):
     1691        """
     1692        This is a quick and dirty way of responding to bad requests.
     1693
     1694        As described by HTTP standard we should be patient and accept the
     1695        whole request from the client, before sending a polite bad request
     1696        response, even in the case when clients send tons of data.
     1697        """
     1698        self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
     1699        self.transport.loseConnection()
     1700
     1701
    16681702    def _finishRequestBody(self, data):
    16691703        self.allContentReceived()
    16701704        self.setLineMode(data)
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    16861720            try:
    16871721                self.length = int(data)
    16881722            except ValueError:
    1689                 self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
     1723                self.respondToBadRequestAndDisconnect()
    16901724                self.length = None
    1691                 self.transport.loseConnection()
    16921725                return
    16931726            self._transferDecoder = _IdentityTransferDecoder(
    16941727                self.length, self.requests[-1].handleContentChunk, self._finishRequestBody)
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    17071740
    17081741        self._receivedHeaderCount += 1
    17091742        if self._receivedHeaderCount > self.maxHeaders:
    1710             self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
    1711             self.transport.loseConnection()
     1743            self.respondToBadRequestAndDisconnect()
     1744            return
    17121745
    17131746
    17141747    def allContentReceived(self):
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    17191752        # reset ALL state variables, so we don't interfere with next request
    17201753        self.length = 0
    17211754        self._receivedHeaderCount = 0
     1755        self._receivedHeaderSize = 0
    17221756        self.__first_line = 1
    17231757        self._transferDecoder = None
    17241758        del self._command, self._path, self._version
    class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): 
    17371771        try:
    17381772            self._transferDecoder.dataReceived(data)
    17391773        except _MalformedChunkedDataError:
    1740             self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
    1741             self.transport.loseConnection()
     1774            self.respondToBadRequestAndDisconnect()
    17421775
    17431776
    17441777    def allHeadersReceived(self):
  • twisted/web/test/test_http.py

    diff --git twisted/web/test/test_http.py twisted/web/test/test_http.py
    index 5a0a853..9563c7a 100644
    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 web request based on plain text content.
     668
     669        @param httpRequest: Content for the request which is processed.
     670        @type httpRequest: C{bytes}
     671
     672        @param requestFactory: Request factory used for the channel.
     673        @type requestFactory: L{Request}
     674
     675        @param channel: Channel instance over which the request is processed.
     676        @type channel: L{HTTPChannel}
     677
     678        @param success: Value to compare agains I{self.didRequest}.
     679        @type success: C{bool}
     680
     681        @return: Returns the channel used for processing the request.
     682        @rtype: L{HTTPChannel}
     683        """
     684        if not channel:
     685            channel = http.HTTPChannel()
     686
     687        if requestFactory:
     688            channel.requestFactory = requestFactory
     689
    664690        httpRequest = httpRequest.replace(b"\n", b"\r\n")
    665         b = StringTransport()
    666         a = http.HTTPChannel()
    667         a.requestFactory = requestClass
    668         a.makeConnection(b)
     691        transport = StringTransport()
     692
     693        channel.makeConnection(transport)
    669694        # one byte at a time, to stress it.
    670695        for byte in iterbytes(httpRequest):
    671             if a.transport.disconnecting:
     696            if channel.transport.disconnecting:
    672697                break
    673             a.dataReceived(byte)
    674         a.connectionLost(IOError("all done"))
     698            channel.dataReceived(byte)
     699        channel.connectionLost(IOError("all done"))
     700
    675701        if success:
    676702            self.assertTrue(self.didRequest)
    677703        else:
    678704            self.assertFalse(self.didRequest)
    679         return a
     705        return channel
    680706
    681707
    682708    def test_basicAuth(self):
    class ParsingTestCase(unittest.TestCase): 
    803829            b'\r\n')
    804830
    805831
     832    def test_headersTooBigInitialCommand(self):
     833        """
     834        Enforces a limit of C{HTTPChannel.totalHeadersSize}
     835        on the size of headers received per request starting from initial
     836        command line.
     837        """
     838        channel = http.HTTPChannel()
     839        channel.totalHeadersSize = 10
     840        httpRequest = b'GET /path/longer/than/10 HTTP/1.1\n'
     841
     842        channel = self.runRequest(httpRequest=httpRequest, channel=channel)
     843
     844        self.assertEqual(
     845            channel.transport.value(),
     846            b"HTTP/1.1 400 Bad Request\r\n\r\n")
     847
     848
     849    def test_headersTooBigOtherHeaders(self):
     850        """
     851        Enforces a limit of C{HTTPChannel.totalHeadersSize}
     852        on the size of headers received per request counting first line
     853        and total headers.
     854        """
     855        channel = http.HTTPChannel()
     856        channel.totalHeadersSize = 40
     857        httpRequest = (
     858            b'GET /less/than/40 HTTP/1.1\n'
     859            b'Some-Header: less-than-40\n'
     860            )
     861
     862        channel = self.runRequest(httpRequest=httpRequest, channel=channel)
     863
     864        self.assertEqual(
     865            channel.transport.value(),
     866            b"HTTP/1.1 400 Bad Request\r\n\r\n")
     867
     868
     869    def test_headersTooBigPerRequest(self):
     870        """
     871        Enforces total size of headers per individual request and counter
     872        is reset at the end of each request.
     873        """
     874        class SimpleRequest(http.Request):
     875            def process(self):
     876                self.finish()
     877        channel = http.HTTPChannel()
     878        channel.totalHeadersSize = 60
     879        channel.requestFactory = SimpleRequest
     880        httpRequest = (
     881            b'GET / HTTP/1.1\n'
     882            b'Some-Header: total-less-than-60\n'
     883            b'\n'
     884            b'GET / HTTP/1.1\n'
     885            b'Some-Header: less-than-60\n'
     886            b'\n'
     887            )
     888
     889        channel = self.runRequest(
     890            httpRequest=httpRequest, channel=channel)
     891
     892        self.assertEqual(
     893            channel.transport.value(),
     894            b'HTTP/1.1 200 OK\r\n'
     895            b'Transfer-Encoding: chunked\r\n'
     896            b'\r\n'
     897            b'0\r\n'
     898            b'\r\n'
     899            b'HTTP/1.1 200 OK\r\n'
     900            b'Transfer-Encoding: chunked\r\n'
     901            b'\r\n'
     902            b'0\r\n'
     903            b'\r\n'
     904            )
     905
     906
    806907    def testCookies(self):
    807908        """
    808909        Test cookies parsing and reading.
    Cookie: rabbit="eat carrot"; ninja=secret; spam="hey 1=1!" 
    821922                testcase.didRequest = True
    822923                self.finish()
    823924
    824         self.runRequest(httpRequest, MyRequest)
     925        self.runRequest(httpRequest, MyRequest, success=True)
    825926
    826927        self.assertEqual(
    827928            cookies, {
    GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0 
    848949                testcase.didRequest = True
    849950                self.finish()
    850951
    851         self.runRequest(httpRequest, MyRequest)
     952        self.runRequest(httpRequest, MyRequest, success=True)
    852953        self.assertEqual(method, [b"GET"])
    853954        self.assertEqual(
    854955            args, [[b"value"], [b""], [b"two words", b"more words"]])
    GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0 
    874975                testcase.didRequest = True
    875976                self.finish()
    876977
    877         self.runRequest(httpRequest, MyRequest)
     978        self.runRequest(httpRequest, MyRequest, success=True)
    878979        self.assertEqual(method, [b'GET'])
    879980        self.assertEqual(path, [b'/foo'])
    880981        self.assertEqual(args, [[b'?'], [b'quux']])
    Content-Type: application/x-www-form-urlencoded 
    9101011                testcase.didRequest = True
    9111012                self.finish()
    9121013
    913         self.runRequest(httpRequest, MyRequest)
     1014        self.runRequest(httpRequest, MyRequest, success=True)
    9141015        self.assertEqual(method, [b"POST"])
    9151016        self.assertEqual(
    9161017            args, [[b"value"], [b""], [b"two words", b"more words"]])
    Hello, 
    9711072                testcase.didRequest = True
    9721073                self.finish()
    9731074
    974         self.runRequest(httpRequest, MyRequest)
     1075        self.runRequest(httpRequest, MyRequest, success=True)
    9751076        # The tempfile API used to create content returns an
    9761077        # instance of a different type depending on what platform
    9771078        # we're running on.  The point here is to verify that the
  • new file twisted/web/topfiles/6933.misc

    diff --git twisted/web/topfiles/6933.misc 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