Skip to content

Commit

Permalink
Merge "deviate-redirects-3624" branch.
Browse files Browse the repository at this point in the history
Author: truekonrads
Reviewer: exarkun, jonathanj
Fixes: #3624

Introduce a parameter to HTTPClientFactory that enables redirection behaviour
commonly found in web browsers: following a POST with a 302 response status.



git-svn-id: svn://svn.twistedmatrix.com/svn/Twisted/trunk@27250 bbbe8e31-12d6-0310-92fd-ac37d47ddeeb
  • Loading branch information
jonathanj committed Aug 27, 2009
1 parent 0282008 commit 4151351
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
17 changes: 14 additions & 3 deletions twisted/web/client.py
Expand Up @@ -123,7 +123,11 @@ def handleStatus_301(self):
self.quietLoss = True
self.transport.loseConnection()

handleStatus_302 = lambda self: self.handleStatus_301()
def handleStatus_302(self):
if self.afterFoundGet:
self.handleStatus_303()
self.handleStatus_301()


def handleStatus_303(self):
self.factory.method = 'GET'
Expand Down Expand Up @@ -226,6 +230,11 @@ class HTTPClientFactory(protocol.ClientFactory):
@ivar redirectLimit: The maximum number of HTTP redirects that can occur
before it is assumed that the redirection is endless.
@type afterFoundGet: C{bool}
@ivar afterFoundGet: Deviate from the HTTP 1.1 RFC by handling redirects
the same way as most web browsers; if the request method is POST and a
302 status is encountered, the redirect is followed with a GET method
@type _redirectCount: int
@ivar _redirectCount: The current number of HTTP redirects encountered.
"""
Expand All @@ -240,13 +249,14 @@ class HTTPClientFactory(protocol.ClientFactory):

def __init__(self, url, method='GET', postdata=None, headers=None,
agent="Twisted PageGetter", timeout=0, cookies=None,
followRedirect=True, redirectLimit=20):
followRedirect=True, redirectLimit=20,
afterFoundGet=False):
self.followRedirect = followRedirect
self.redirectLimit = redirectLimit
self._redirectCount = 0
self.timeout = timeout
self.agent = agent

self.afterFoundGet = afterFoundGet
if cookies is None:
cookies = {}
self.cookies = cookies
Expand Down Expand Up @@ -282,6 +292,7 @@ def setURL(self, url):
def buildProtocol(self, addr):
p = protocol.ClientFactory.buildProtocol(self, addr)
p.followRedirect = self.followRedirect
p.afterFoundGet = self.afterFoundGet
if self.timeout:
timeoutCall = reactor.callLater(self.timeout, p.timeout)
self.deferred.addBoth(self._cancelTimeout, timeoutCall)
Expand Down
65 changes: 65 additions & 0 deletions twisted/web/test/test_webclient.py
Expand Up @@ -24,6 +24,46 @@
ssl = None



class ExtendedRedirect(resource.Resource):
"""
Redirection resource.
The HTTP status code is set according to the C{code} query parameter.
@type lastMethod: C{str}
@ivar lastMethod: Last handled HTTP request method
"""
isLeaf = 1
lastMethod = None


def __init__(self, url):
resource.Resource.__init__(self)
self.url = url


def render(self, request):
if self.lastMethod:
self.lastMethod = request.method
return "OK Thnx!"
else:
self.lastMethod = request.method
code = int(request.args['code'][0])
return self.redirectTo(self.url, request, code)


def getChild(self, name, request):
return self


def redirectTo(self, url, request, code):
request.setResponseCode(code)
request.setHeader("location", url)
return "OK Bye!"



class ForeverTakingResource(resource.Resource):
"""
L{ForeverTakingResource} is a resource which never finishes responding
Expand Down Expand Up @@ -231,6 +271,8 @@ def setUp(self):
r.putChild("payload", PayloadResource())
r.putChild("broken", BrokenDownloadResource())
r.putChild("cookiemirror", CookieMirrorResource())
self.extendedRedirect = ExtendedRedirect('/extendedRedirect')
r.putChild("extendedRedirect", self.extendedRedirect)
self.site = server.Site(r, timeout=None)
self.wrapper = WrappingFactory(self.site)
self.port = self._listen(self.wrapper)
Expand Down Expand Up @@ -494,6 +536,29 @@ def test_isolatedFollowRedirect(self):
return d


def test_afterFoundGet(self):
"""
Enabling unsafe redirection behaviour overwrites the method of
redirected C{POST} requests with C{GET}.
"""
url = self.getURL('extendedRedirect?code=302')
f = client.HTTPClientFactory(url, followRedirect=True, method="POST")
self.assertFalse(
f.afterFoundGet,
"By default, afterFoundGet must be disabled")

def gotPage(page):
self.assertEquals(
self.extendedRedirect.lastMethod,
"GET",
"With afterFoundGet, the HTTP method must change to GET")

d = client.getPage(
url, followRedirect=True, afterFoundGet=True, method="POST")
d.addCallback(gotPage)
return d


def testPartial(self):
name = self.mktemp()
f = open(name, "wb")
Expand Down

0 comments on commit 4151351

Please sign in to comment.