# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: james@jamesh.id.au-20100625152527-euuvx1hdyfhtq940
# target_branch: bzr+ssh://bazaar.launchpad.net/~twisted-\
#   dev/twisted/trunk/
# testament_sha1: 1d0f101861fcf22a3614667211f3f704f0e34b59
# timestamp: 2010-06-25 23:27:37 +0800
# base_revision_id: svn-v4:bbbe8e31-12d6-0310-92fd-\
#   ac37d47ddeeb:trunk:29351
# 
# Begin patch
=== modified file 'twisted/web/http.py'
--- twisted/web/http.py	2010-01-18 00:16:57 +0000
+++ twisted/web/http.py	2010-06-25 15:25:27 +0000
@@ -597,7 +597,7 @@
     sentLength = 0 # content-length of response, or total bytes sent via chunking
     etag = None
     lastModified = None
-    args = None
+    _args = None
     path = None
     content = None
     _forceSSL = 0
@@ -759,7 +759,6 @@
         @param version: The HTTP version of this request.
         """
         self.content.seek(0,0)
-        self.args = {}
         self.stack = []
 
         self.method, self.uri = command, path
@@ -770,15 +769,27 @@
             self.path = self.uri
         else:
             self.path, argstring = x
-            self.args = parse_qs(argstring, 1)
 
         # cache the client and server information, we'll need this later to be
         # serialized and sent with the request so CGIs will work remotely
         self.client = self.channel.transport.getPeer()
         self.host = self.channel.transport.getHost()
 
-        # Argument processing
-        args = self.args
+        self.process()
+
+    def args(self):
+        if self._args is not None:
+            return self._args
+
+        # Parse query string arguments
+        x = self.uri.split('?', 1)
+        if len(x) != 1:
+            self._args = parse_qs(x[1], 1)
+        else:
+            self._args = {}
+
+        # Parse post body arguments
+        args = self._args
         ctype = self.requestHeaders.getRawHeaders('content-type')
         if ctype is not None:
             ctype = ctype[0]
@@ -803,9 +814,8 @@
                         return
                     raise
             self.content.seek(0, 0)
-
-        self.process()
-
+        return args
+    args = property(args)
 
     def __repr__(self):
         return '<%s %s %s>'% (self.method, self.uri, self.clientproto)

=== modified file 'twisted/web/test/test_http.py'
--- twisted/web/test/test_http.py	2010-01-18 00:16:57 +0000
+++ twisted/web/test/test_http.py	2010-06-25 15:25:27 +0000
@@ -826,6 +826,40 @@
 
         self.runRequest(httpRequest, MyRequest)
 
+    def test_formNotParsedUntilArgsAccessed(self):
+        """The post body is not parsed until request.args is accessed."""
+        query = 'key=value'
+        httpRequest = '''\
+POST / HTTP/1.0
+Content-Length: %d
+Content-Type: application/x-www-form-urlencoded
+
+%s''' % (len(query), query)
+
+        testcase = self
+        self.parse_qs_calls = []
+        orig_parse_qs = http.parse_qs
+        def parse_qs(query_string, *args):
+            testcase.parse_qs_calls.append(query_string)
+            return orig_parse_qs(query_string, *args)
+        self.patch(http, 'parse_qs', parse_qs)
+        class MyRequest(http.Request):
+            def process(self):
+                testcase.assertEquals(self.method, "POST")
+                # Arguments have not been parsed:
+                testcase.assertEquals(testcase.parse_qs_calls, [])
+                # But have been after first access to self.args:
+                testcase.assertEquals(self.args["key"], ["value"])
+                testcase.assertEquals(testcase.parse_qs_calls, [query])
+
+                # Reading from the content file-like must produce the entire
+                # request body.
+                testcase.assertEquals(self.content.read(), query)
+                testcase.didRequest = 1
+                self.finish()
+
+        self.runRequest(httpRequest, MyRequest)
+
     def testMissingContentDisposition(self):
         req = '''\
 POST / HTTP/1.0

# Begin bundle
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
1wA=
