~hudson-openstack/nova/trunk

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/test/test_http.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""
 
5
Test HTTP support.
 
6
"""
 
7
 
 
8
from urlparse import urlparse, urlunsplit, clear_cache
 
9
import random, urllib, cgi
 
10
 
 
11
from twisted.python.compat import set
 
12
from twisted.python.failure import Failure
 
13
from twisted.trial import unittest
 
14
from twisted.trial.unittest import TestCase
 
15
from twisted.web import http, http_headers
 
16
from twisted.web.http import PotentialDataLoss, _DataLoss
 
17
from twisted.web.http import _IdentityTransferDecoder
 
18
from twisted.protocols import loopback
 
19
from twisted.internet.task import Clock
 
20
from twisted.internet.error import ConnectionLost
 
21
from twisted.test.proto_helpers import StringTransport
 
22
from twisted.test.test_internet import DummyProducer
 
23
from twisted.web.test.test_web import DummyChannel
 
24
 
 
25
 
 
26
class DateTimeTest(unittest.TestCase):
 
27
    """Test date parsing functions."""
 
28
 
 
29
    def testRoundtrip(self):
 
30
        for i in range(10000):
 
31
            time = random.randint(0, 2000000000)
 
32
            timestr = http.datetimeToString(time)
 
33
            time2 = http.stringToDatetime(timestr)
 
34
            self.assertEquals(time, time2)
 
35
 
 
36
 
 
37
class DummyHTTPHandler(http.Request):
 
38
 
 
39
    def process(self):
 
40
        self.content.seek(0, 0)
 
41
        data = self.content.read()
 
42
        length = self.getHeader('content-length')
 
43
        request = "'''\n"+str(length)+"\n"+data+"'''\n"
 
44
        self.setResponseCode(200)
 
45
        self.setHeader("Request", self.uri)
 
46
        self.setHeader("Command", self.method)
 
47
        self.setHeader("Version", self.clientproto)
 
48
        self.setHeader("Content-Length", len(request))
 
49
        self.write(request)
 
50
        self.finish()
 
51
 
 
52
 
 
53
class LoopbackHTTPClient(http.HTTPClient):
 
54
 
 
55
    def connectionMade(self):
 
56
        self.sendCommand("GET", "/foo/bar")
 
57
        self.sendHeader("Content-Length", 10)
 
58
        self.endHeaders()
 
59
        self.transport.write("0123456789")
 
60
 
 
61
 
 
62
class ResponseTestMixin(object):
 
63
    """
 
64
    A mixin that provides a simple means of comparing an actual response string
 
65
    to an expected response string by performing the minimal parsing.
 
66
    """
 
67
 
 
68
    def assertResponseEquals(self, responses, expected):
 
69
        """
 
70
        Assert that the C{responses} matches the C{expected} responses.
 
71
 
 
72
        @type responses: C{str}
 
73
        @param responses: The bytes sent in response to one or more requests.
 
74
 
 
75
        @type expected: C{list} of C{tuple} of C{str}
 
76
        @param expected: The expected values for the responses.  Each tuple
 
77
            element of the list represents one response.  Each string element
 
78
            of the tuple is a full header line without delimiter, except for
 
79
            the last element which gives the full response body.
 
80
        """
 
81
        for response in expected:
 
82
            expectedHeaders, expectedContent = response[:-1], response[-1]
 
83
            headers, rest = responses.split('\r\n\r\n', 1)
 
84
            headers = headers.splitlines()
 
85
            self.assertEqual(set(headers), set(expectedHeaders))
 
86
            content = rest[:len(expectedContent)]
 
87
            responses = rest[len(expectedContent):]
 
88
            self.assertEqual(content, expectedContent)
 
89
 
 
90
 
 
91
 
 
92
class HTTP1_0TestCase(unittest.TestCase, ResponseTestMixin):
 
93
    requests = (
 
94
        "GET / HTTP/1.0\r\n"
 
95
        "\r\n"
 
96
        "GET / HTTP/1.1\r\n"
 
97
        "Accept: text/html\r\n"
 
98
        "\r\n")
 
99
 
 
100
    expected_response = [
 
101
        ("HTTP/1.0 200 OK",
 
102
         "Request: /",
 
103
         "Command: GET",
 
104
         "Version: HTTP/1.0",
 
105
         "Content-Length: 13",
 
106
         "'''\nNone\n'''\n")]
 
107
 
 
108
    def test_buffer(self):
 
109
        """
 
110
        Send requests over a channel and check responses match what is expected.
 
111
        """
 
112
        b = StringTransport()
 
113
        a = http.HTTPChannel()
 
114
        a.requestFactory = DummyHTTPHandler
 
115
        a.makeConnection(b)
 
116
        # one byte at a time, to stress it.
 
117
        for byte in self.requests:
 
118
            a.dataReceived(byte)
 
119
        a.connectionLost(IOError("all one"))
 
120
        value = b.value()
 
121
        self.assertResponseEquals(value, self.expected_response)
 
122
 
 
123
 
 
124
    def test_requestBodyTimeout(self):
 
125
        """
 
126
        L{HTTPChannel} resets its timeout whenever data from a request body is
 
127
        delivered to it.
 
128
        """
 
129
        clock = Clock()
 
130
        transport = StringTransport()
 
131
        protocol = http.HTTPChannel()
 
132
        protocol.timeOut = 100
 
133
        protocol.callLater = clock.callLater
 
134
        protocol.makeConnection(transport)
 
135
        protocol.dataReceived('POST / HTTP/1.0\r\nContent-Length: 2\r\n\r\n')
 
136
        clock.advance(99)
 
137
        self.assertFalse(transport.disconnecting)
 
138
        protocol.dataReceived('x')
 
139
        clock.advance(99)
 
140
        self.assertFalse(transport.disconnecting)
 
141
        protocol.dataReceived('x')
 
142
        self.assertEqual(len(protocol.requests), 1)
 
143
 
 
144
 
 
145
 
 
146
class HTTP1_1TestCase(HTTP1_0TestCase):
 
147
 
 
148
    requests = (
 
149
        "GET / HTTP/1.1\r\n"
 
150
        "Accept: text/html\r\n"
 
151
        "\r\n"
 
152
        "POST / HTTP/1.1\r\n"
 
153
        "Content-Length: 10\r\n"
 
154
        "\r\n"
 
155
        "0123456789POST / HTTP/1.1\r\n"
 
156
        "Content-Length: 10\r\n"
 
157
        "\r\n"
 
158
        "0123456789HEAD / HTTP/1.1\r\n"
 
159
        "\r\n")
 
160
 
 
161
    expected_response = [
 
162
        ("HTTP/1.1 200 OK",
 
163
         "Request: /",
 
164
         "Command: GET",
 
165
         "Version: HTTP/1.1",
 
166
         "Content-Length: 13",
 
167
         "'''\nNone\n'''\n"),
 
168
        ("HTTP/1.1 200 OK",
 
169
         "Request: /",
 
170
         "Command: POST",
 
171
         "Version: HTTP/1.1",
 
172
         "Content-Length: 21",
 
173
         "'''\n10\n0123456789'''\n"),
 
174
        ("HTTP/1.1 200 OK",
 
175
         "Request: /",
 
176
         "Command: POST",
 
177
         "Version: HTTP/1.1",
 
178
         "Content-Length: 21",
 
179
         "'''\n10\n0123456789'''\n"),
 
180
        ("HTTP/1.1 200 OK",
 
181
         "Request: /",
 
182
         "Command: HEAD",
 
183
         "Version: HTTP/1.1",
 
184
         "Content-Length: 13",
 
185
         "")]
 
186
 
 
187
 
 
188
 
 
189
class HTTP1_1_close_TestCase(HTTP1_0TestCase):
 
190
 
 
191
    requests = (
 
192
        "GET / HTTP/1.1\r\n"
 
193
        "Accept: text/html\r\n"
 
194
        "Connection: close\r\n"
 
195
        "\r\n"
 
196
        "GET / HTTP/1.0\r\n"
 
197
        "\r\n")
 
198
 
 
199
    expected_response = [
 
200
        ("HTTP/1.1 200 OK",
 
201
         "Connection: close",
 
202
         "Request: /",
 
203
         "Command: GET",
 
204
         "Version: HTTP/1.1",
 
205
         "Content-Length: 13",
 
206
         "'''\nNone\n'''\n")]
 
207
 
 
208
 
 
209
 
 
210
class HTTP0_9TestCase(HTTP1_0TestCase):
 
211
 
 
212
    requests = (
 
213
        "GET /\r\n")
 
214
 
 
215
    expected_response = "HTTP/1.1 400 Bad Request\r\n\r\n"
 
216
 
 
217
 
 
218
    def assertResponseEquals(self, response, expectedResponse):
 
219
        self.assertEquals(response, expectedResponse)
 
220
 
 
221
 
 
222
class HTTPLoopbackTestCase(unittest.TestCase):
 
223
 
 
224
    expectedHeaders = {'request' : '/foo/bar',
 
225
                       'command' : 'GET',
 
226
                       'version' : 'HTTP/1.0',
 
227
                       'content-length' : '21'}
 
228
    numHeaders = 0
 
229
    gotStatus = 0
 
230
    gotResponse = 0
 
231
    gotEndHeaders = 0
 
232
 
 
233
    def _handleStatus(self, version, status, message):
 
234
        self.gotStatus = 1
 
235
        self.assertEquals(version, "HTTP/1.0")
 
236
        self.assertEquals(status, "200")
 
237
 
 
238
    def _handleResponse(self, data):
 
239
        self.gotResponse = 1
 
240
        self.assertEquals(data, "'''\n10\n0123456789'''\n")
 
241
 
 
242
    def _handleHeader(self, key, value):
 
243
        self.numHeaders = self.numHeaders + 1
 
244
        self.assertEquals(self.expectedHeaders[key.lower()], value)
 
245
 
 
246
    def _handleEndHeaders(self):
 
247
        self.gotEndHeaders = 1
 
248
        self.assertEquals(self.numHeaders, 4)
 
249
 
 
250
    def testLoopback(self):
 
251
        server = http.HTTPChannel()
 
252
        server.requestFactory = DummyHTTPHandler
 
253
        client = LoopbackHTTPClient()
 
254
        client.handleResponse = self._handleResponse
 
255
        client.handleHeader = self._handleHeader
 
256
        client.handleEndHeaders = self._handleEndHeaders
 
257
        client.handleStatus = self._handleStatus
 
258
        d = loopback.loopbackAsync(server, client)
 
259
        d.addCallback(self._cbTestLoopback)
 
260
        return d
 
261
 
 
262
    def _cbTestLoopback(self, ignored):
 
263
        if not (self.gotStatus and self.gotResponse and self.gotEndHeaders):
 
264
            raise RuntimeError(
 
265
                "didn't got all callbacks %s"
 
266
                % [self.gotStatus, self.gotResponse, self.gotEndHeaders])
 
267
        del self.gotEndHeaders
 
268
        del self.gotResponse
 
269
        del self.gotStatus
 
270
        del self.numHeaders
 
271
 
 
272
 
 
273
 
 
274
def _prequest(**headers):
 
275
    """
 
276
    Make a request with the given request headers for the persistence tests.
 
277
    """
 
278
    request = http.Request(DummyChannel(), None)
 
279
    for k, v in headers.iteritems():
 
280
        request.requestHeaders.setRawHeaders(k, v)
 
281
    return request
 
282
 
 
283
 
 
284
class PersistenceTestCase(unittest.TestCase):
 
285
    """
 
286
    Tests for persistent HTTP connections.
 
287
    """
 
288
 
 
289
    ptests = [#(PRequest(connection="Keep-Alive"), "HTTP/1.0", 1, {'connection' : 'Keep-Alive'}),
 
290
              (_prequest(), "HTTP/1.0", 0, {'connection': None}),
 
291
              (_prequest(connection=["close"]), "HTTP/1.1", 0, {'connection' : ['close']}),
 
292
              (_prequest(), "HTTP/1.1", 1, {'connection': None}),
 
293
              (_prequest(), "HTTP/0.9", 0, {'connection': None}),
 
294
              ]
 
295
 
 
296
 
 
297
    def testAlgorithm(self):
 
298
        c = http.HTTPChannel()
 
299
        for req, version, correctResult, resultHeaders in self.ptests:
 
300
            result = c.checkPersistence(req, version)
 
301
            self.assertEquals(result, correctResult)
 
302
            for header in resultHeaders.keys():
 
303
                self.assertEquals(req.responseHeaders.getRawHeaders(header, None), resultHeaders[header])
 
304
 
 
305
 
 
306
 
 
307
class IdentityTransferEncodingTests(TestCase):
 
308
    """
 
309
    Tests for L{_IdentityTransferDecoder}.
 
310
    """
 
311
    def setUp(self):
 
312
        """
 
313
        Create an L{_IdentityTransferDecoder} with callbacks hooked up so that
 
314
        calls to them can be inspected.
 
315
        """
 
316
        self.data = []
 
317
        self.finish = []
 
318
        self.contentLength = 10
 
319
        self.decoder = _IdentityTransferDecoder(
 
320
            self.contentLength, self.data.append, self.finish.append)
 
321
 
 
322
 
 
323
    def test_exactAmountReceived(self):
 
324
        """
 
325
        If L{_IdentityTransferDecoder.dataReceived} is called with a string
 
326
        with length equal to the content length passed to
 
327
        L{_IdentityTransferDecoder}'s initializer, the data callback is invoked
 
328
        with that string and the finish callback is invoked with a zero-length
 
329
        string.
 
330
        """
 
331
        self.decoder.dataReceived('x' * self.contentLength)
 
332
        self.assertEqual(self.data, ['x' * self.contentLength])
 
333
        self.assertEqual(self.finish, [''])
 
334
 
 
335
 
 
336
    def test_shortStrings(self):
 
337
        """
 
338
        If L{_IdentityTransferDecoder.dataReceived} is called multiple times
 
339
        with strings which, when concatenated, are as long as the content
 
340
        length provided, the data callback is invoked with each string and the
 
341
        finish callback is invoked only after the second call.
 
342
        """
 
343
        self.decoder.dataReceived('x')
 
344
        self.assertEqual(self.data, ['x'])
 
345
        self.assertEqual(self.finish, [])
 
346
        self.decoder.dataReceived('y' * (self.contentLength - 1))
 
347
        self.assertEqual(self.data, ['x', 'y' * (self.contentLength - 1)])
 
348
        self.assertEqual(self.finish, [''])
 
349
 
 
350
 
 
351
    def test_longString(self):
 
352
        """
 
353
        If L{_IdentityTransferDecoder.dataReceived} is called with a string
 
354
        with length greater than the provided content length, only the prefix
 
355
        of that string up to the content length is passed to the data callback
 
356
        and the remainder is passed to the finish callback.
 
357
        """
 
358
        self.decoder.dataReceived('x' * self.contentLength + 'y')
 
359
        self.assertEqual(self.data, ['x' * self.contentLength])
 
360
        self.assertEqual(self.finish, ['y'])
 
361
 
 
362
 
 
363
    def test_rejectDataAfterFinished(self):
 
364
        """
 
365
        If data is passed to L{_IdentityTransferDecoder.dataReceived} after the
 
366
        finish callback has been invoked, L{RuntimeError} is raised.
 
367
        """
 
368
        failures = []
 
369
        def finish(bytes):
 
370
            try:
 
371
                decoder.dataReceived('foo')
 
372
            except:
 
373
                failures.append(Failure())
 
374
        decoder = _IdentityTransferDecoder(5, self.data.append, finish)
 
375
        decoder.dataReceived('x' * 4)
 
376
        self.assertEqual(failures, [])
 
377
        decoder.dataReceived('y')
 
378
        failures[0].trap(RuntimeError)
 
379
        self.assertEqual(
 
380
            str(failures[0].value),
 
381
            "_IdentityTransferDecoder cannot decode data after finishing")
 
382
 
 
383
 
 
384
    def test_unknownContentLength(self):
 
385
        """
 
386
        If L{_IdentityTransferDecoder} is constructed with C{None} for the
 
387
        content length, it passes all data delivered to it through to the data
 
388
        callback.
 
389
        """
 
390
        data = []
 
391
        finish = []
 
392
        decoder = _IdentityTransferDecoder(None, data.append, finish.append)
 
393
        decoder.dataReceived('x')
 
394
        self.assertEqual(data, ['x'])
 
395
        decoder.dataReceived('y')
 
396
        self.assertEqual(data, ['x', 'y'])
 
397
        self.assertEqual(finish, [])
 
398
 
 
399
 
 
400
    def _verifyCallbacksUnreferenced(self, decoder):
 
401
        """
 
402
        Check the decoder's data and finish callbacks and make sure they are
 
403
        None in order to help avoid references cycles.
 
404
        """
 
405
        self.assertIdentical(decoder.dataCallback, None)
 
406
        self.assertIdentical(decoder.finishCallback, None)
 
407
 
 
408
 
 
409
    def test_earlyConnectionLose(self):
 
410
        """
 
411
        L{_IdentityTransferDecoder.noMoreData} raises L{_DataLoss} if it is
 
412
        called and the content length is known but not enough bytes have been
 
413
        delivered.
 
414
        """
 
415
        self.decoder.dataReceived('x' * (self.contentLength - 1))
 
416
        self.assertRaises(_DataLoss, self.decoder.noMoreData)
 
417
        self._verifyCallbacksUnreferenced(self.decoder)
 
418
 
 
419
 
 
420
    def test_unknownContentLengthConnectionLose(self):
 
421
        """
 
422
        L{_IdentityTransferDecoder.noMoreData} calls the finish callback and
 
423
        raises L{PotentialDataLoss} if it is called and the content length is
 
424
        unknown.
 
425
        """
 
426
        body = []
 
427
        finished = []
 
428
        decoder = _IdentityTransferDecoder(None, body.append, finished.append)
 
429
        self.assertRaises(PotentialDataLoss, decoder.noMoreData)
 
430
        self.assertEqual(body, [])
 
431
        self.assertEqual(finished, [''])
 
432
        self._verifyCallbacksUnreferenced(decoder)
 
433
 
 
434
 
 
435
    def test_finishedConnectionLose(self):
 
436
        """
 
437
        L{_IdentityTransferDecoder.noMoreData} does not raise any exception if
 
438
        it is called when the content length is known and that many bytes have
 
439
        been delivered.
 
440
        """
 
441
        self.decoder.dataReceived('x' * self.contentLength)
 
442
        self.decoder.noMoreData()
 
443
        self._verifyCallbacksUnreferenced(self.decoder)
 
444
 
 
445
 
 
446
 
 
447
class ChunkedTransferEncodingTests(unittest.TestCase):
 
448
    """
 
449
    Tests for L{_ChunkedTransferDecoder}, which turns a byte stream encoded
 
450
    using HTTP I{chunked} C{Transfer-Encoding} back into the original byte
 
451
    stream.
 
452
    """
 
453
    def test_decoding(self):
 
454
        """
 
455
        L{_ChunkedTransferDecoder.dataReceived} decodes chunked-encoded data
 
456
        and passes the result to the specified callback.
 
457
        """
 
458
        L = []
 
459
        p = http._ChunkedTransferDecoder(L.append, None)
 
460
        p.dataReceived('3\r\nabc\r\n5\r\n12345\r\n')
 
461
        p.dataReceived('a\r\n0123456789\r\n')
 
462
        self.assertEqual(L, ['abc', '12345', '0123456789'])
 
463
 
 
464
 
 
465
    def test_short(self):
 
466
        """
 
467
        L{_ChunkedTransferDecoder.dataReceived} decodes chunks broken up and
 
468
        delivered in multiple calls.
 
469
        """
 
470
        L = []
 
471
        finished = []
 
472
        p = http._ChunkedTransferDecoder(L.append, finished.append)
 
473
        for s in '3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\n':
 
474
            p.dataReceived(s)
 
475
        self.assertEqual(L, ['a', 'b', 'c', '1', '2', '3', '4', '5'])
 
476
        self.assertEqual(finished, [''])
 
477
 
 
478
 
 
479
    def test_newlines(self):
 
480
        """
 
481
        L{_ChunkedTransferDecoder.dataReceived} doesn't treat CR LF pairs
 
482
        embedded in chunk bodies specially.
 
483
        """
 
484
        L = []
 
485
        p = http._ChunkedTransferDecoder(L.append, None)
 
486
        p.dataReceived('2\r\n\r\n\r\n')
 
487
        self.assertEqual(L, ['\r\n'])
 
488
 
 
489
 
 
490
    def test_extensions(self):
 
491
        """
 
492
        L{_ChunkedTransferDecoder.dataReceived} disregards chunk-extension
 
493
        fields.
 
494
        """
 
495
        L = []
 
496
        p = http._ChunkedTransferDecoder(L.append, None)
 
497
        p.dataReceived('3; x-foo=bar\r\nabc\r\n')
 
498
        self.assertEqual(L, ['abc'])
 
499
 
 
500
 
 
501
    def test_finish(self):
 
502
        """
 
503
        L{_ChunkedTransferDecoder.dataReceived} interprets a zero-length
 
504
        chunk as the end of the chunked data stream and calls the completion
 
505
        callback.
 
506
        """
 
507
        finished = []
 
508
        p = http._ChunkedTransferDecoder(None, finished.append)
 
509
        p.dataReceived('0\r\n\r\n')
 
510
        self.assertEqual(finished, [''])
 
511
 
 
512
 
 
513
    def test_extra(self):
 
514
        """
 
515
        L{_ChunkedTransferDecoder.dataReceived} passes any bytes which come
 
516
        after the terminating zero-length chunk to the completion callback.
 
517
        """
 
518
        finished = []
 
519
        p = http._ChunkedTransferDecoder(None, finished.append)
 
520
        p.dataReceived('0\r\n\r\nhello')
 
521
        self.assertEqual(finished, ['hello'])
 
522
 
 
523
 
 
524
    def test_afterFinished(self):
 
525
        """
 
526
        L{_ChunkedTransferDecoder.dataReceived} raises L{RuntimeError} if it
 
527
        is called after it has seen the last chunk.
 
528
        """
 
529
        p = http._ChunkedTransferDecoder(None, lambda bytes: None)
 
530
        p.dataReceived('0\r\n\r\n')
 
531
        self.assertRaises(RuntimeError, p.dataReceived, 'hello')
 
532
 
 
533
 
 
534
    def test_earlyConnectionLose(self):
 
535
        """
 
536
        L{_ChunkedTransferDecoder.noMoreData} raises L{_DataLoss} if it is
 
537
        called and the end of the last trailer has not yet been received.
 
538
        """
 
539
        parser = http._ChunkedTransferDecoder(None, lambda bytes: None)
 
540
        parser.dataReceived('0\r\n\r')
 
541
        exc = self.assertRaises(_DataLoss, parser.noMoreData)
 
542
        self.assertEqual(
 
543
            str(exc),
 
544
            "Chunked decoder in 'trailer' state, still expecting more data "
 
545
            "to get to finished state.")
 
546
 
 
547
 
 
548
    def test_finishedConnectionLose(self):
 
549
        """
 
550
        L{_ChunkedTransferDecoder.noMoreData} does not raise any exception if
 
551
        it is called after the terminal zero length chunk is received.
 
552
        """
 
553
        parser = http._ChunkedTransferDecoder(None, lambda bytes: None)
 
554
        parser.dataReceived('0\r\n\r\n')
 
555
        parser.noMoreData()
 
556
 
 
557
 
 
558
    def test_reentrantFinishedNoMoreData(self):
 
559
        """
 
560
        L{_ChunkedTransferDecoder.noMoreData} can be called from the finished
 
561
        callback without raising an exception.
 
562
        """
 
563
        errors = []
 
564
        successes = []
 
565
        def finished(extra):
 
566
            try:
 
567
                parser.noMoreData()
 
568
            except:
 
569
                errors.append(Failure())
 
570
            else:
 
571
                successes.append(True)
 
572
        parser = http._ChunkedTransferDecoder(None, finished)
 
573
        parser.dataReceived('0\r\n\r\n')
 
574
        self.assertEqual(errors, [])
 
575
        self.assertEqual(successes, [True])
 
576
 
 
577
 
 
578
 
 
579
class ChunkingTestCase(unittest.TestCase):
 
580
 
 
581
    strings = ["abcv", "", "fdfsd423", "Ffasfas\r\n",
 
582
               "523523\n\rfsdf", "4234"]
 
583
 
 
584
    def testChunks(self):
 
585
        for s in self.strings:
 
586
            self.assertEquals((s, ''), http.fromChunk(''.join(http.toChunk(s))))
 
587
        self.assertRaises(ValueError, http.fromChunk, '-5\r\nmalformed!\r\n')
 
588
 
 
589
    def testConcatenatedChunks(self):
 
590
        chunked = ''.join([''.join(http.toChunk(t)) for t in self.strings])
 
591
        result = []
 
592
        buffer = ""
 
593
        for c in chunked:
 
594
            buffer = buffer + c
 
595
            try:
 
596
                data, buffer = http.fromChunk(buffer)
 
597
                result.append(data)
 
598
            except ValueError:
 
599
                pass
 
600
        self.assertEquals(result, self.strings)
 
601
 
 
602
 
 
603
 
 
604
class ParsingTestCase(unittest.TestCase):
 
605
    """
 
606
    Tests for protocol parsing in L{HTTPChannel}.
 
607
    """
 
608
    def runRequest(self, httpRequest, requestClass, success=1):
 
609
        httpRequest = httpRequest.replace("\n", "\r\n")
 
610
        b = StringTransport()
 
611
        a = http.HTTPChannel()
 
612
        a.requestFactory = requestClass
 
613
        a.makeConnection(b)
 
614
        # one byte at a time, to stress it.
 
615
        for byte in httpRequest:
 
616
            if a.transport.disconnecting:
 
617
                break
 
618
            a.dataReceived(byte)
 
619
        a.connectionLost(IOError("all done"))
 
620
        if success:
 
621
            self.assertEquals(self.didRequest, 1)
 
622
            del self.didRequest
 
623
        else:
 
624
            self.assert_(not hasattr(self, "didRequest"))
 
625
        return a
 
626
 
 
627
 
 
628
    def test_basicAuth(self):
 
629
        """
 
630
        L{HTTPChannel} provides username and password information supplied in
 
631
        an I{Authorization} header to the L{Request} which makes it available
 
632
        via its C{getUser} and C{getPassword} methods.
 
633
        """
 
634
        testcase = self
 
635
        class Request(http.Request):
 
636
            l = []
 
637
            def process(self):
 
638
                testcase.assertEquals(self.getUser(), self.l[0])
 
639
                testcase.assertEquals(self.getPassword(), self.l[1])
 
640
        for u, p in [("foo", "bar"), ("hello", "there:z")]:
 
641
            Request.l[:] = [u, p]
 
642
            s = "%s:%s" % (u, p)
 
643
            f = "GET / HTTP/1.0\nAuthorization: Basic %s\n\n" % (s.encode("base64").strip(), )
 
644
            self.runRequest(f, Request, 0)
 
645
 
 
646
 
 
647
    def test_headers(self):
 
648
        """
 
649
        Headers received by L{HTTPChannel} in a request are made available to
 
650
        the L{Request}.
 
651
        """
 
652
        processed = []
 
653
        class MyRequest(http.Request):
 
654
            def process(self):
 
655
                processed.append(self)
 
656
                self.finish()
 
657
 
 
658
        requestLines = [
 
659
            "GET / HTTP/1.0",
 
660
            "Foo: bar",
 
661
            "baz: Quux",
 
662
            "baz: quux",
 
663
            "",
 
664
            ""]
 
665
 
 
666
        self.runRequest('\n'.join(requestLines), MyRequest, 0)
 
667
        [request] = processed
 
668
        self.assertEquals(
 
669
            request.requestHeaders.getRawHeaders('foo'), ['bar'])
 
670
        self.assertEquals(
 
671
            request.requestHeaders.getRawHeaders('bAz'), ['Quux', 'quux'])
 
672
 
 
673
 
 
674
    def test_tooManyHeaders(self):
 
675
        """
 
676
        L{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
 
677
        number of headers received per request.
 
678
        """
 
679
        processed = []
 
680
        class MyRequest(http.Request):
 
681
            def process(self):
 
682
                processed.append(self)
 
683
 
 
684
        requestLines = ["GET / HTTP/1.0"]
 
685
        for i in range(http.HTTPChannel.maxHeaders + 2):
 
686
            requestLines.append("%s: foo" % (i,))
 
687
        requestLines.extend(["", ""])
 
688
 
 
689
        channel = self.runRequest("\n".join(requestLines), MyRequest, 0)
 
690
        self.assertEqual(processed, [])
 
691
        self.assertEqual(
 
692
            channel.transport.value(),
 
693
            "HTTP/1.1 400 Bad Request\r\n\r\n")
 
694
 
 
695
 
 
696
    def test_headerLimitPerRequest(self):
 
697
        """
 
698
        L{HTTPChannel} enforces the limit of C{HTTPChannel.maxHeaders} per
 
699
        request so that headers received in an earlier request do not count
 
700
        towards the limit when processing a later request.
 
701
        """
 
702
        processed = []
 
703
        class MyRequest(http.Request):
 
704
            def process(self):
 
705
                processed.append(self)
 
706
                self.finish()
 
707
 
 
708
        self.patch(http.HTTPChannel, 'maxHeaders', 1)
 
709
        requestLines = [
 
710
            "GET / HTTP/1.1",
 
711
            "Foo: bar",
 
712
            "",
 
713
            "",
 
714
            "GET / HTTP/1.1",
 
715
            "Bar: baz",
 
716
            "",
 
717
            ""]
 
718
 
 
719
        channel = self.runRequest("\n".join(requestLines), MyRequest, 0)
 
720
        [first, second] = processed
 
721
        self.assertEqual(first.getHeader('foo'), 'bar')
 
722
        self.assertEqual(second.getHeader('bar'), 'baz')
 
723
        self.assertEqual(
 
724
            channel.transport.value(),
 
725
            'HTTP/1.1 200 OK\r\n'
 
726
            'Transfer-Encoding: chunked\r\n'
 
727
            '\r\n'
 
728
            '0\r\n'
 
729
            '\r\n'
 
730
            'HTTP/1.1 200 OK\r\n'
 
731
            'Transfer-Encoding: chunked\r\n'
 
732
            '\r\n'
 
733
            '0\r\n'
 
734
            '\r\n')
 
735
 
 
736
 
 
737
    def testCookies(self):
 
738
        """
 
739
        Test cookies parsing and reading.
 
740
        """
 
741
        httpRequest = '''\
 
742
GET / HTTP/1.0
 
743
Cookie: rabbit="eat carrot"; ninja=secret; spam="hey 1=1!"
 
744
 
 
745
'''
 
746
        testcase = self
 
747
 
 
748
        class MyRequest(http.Request):
 
749
            def process(self):
 
750
                testcase.assertEquals(self.getCookie('rabbit'), '"eat carrot"')
 
751
                testcase.assertEquals(self.getCookie('ninja'), 'secret')
 
752
                testcase.assertEquals(self.getCookie('spam'), '"hey 1=1!"')
 
753
                testcase.didRequest = 1
 
754
                self.finish()
 
755
 
 
756
        self.runRequest(httpRequest, MyRequest)
 
757
 
 
758
    def testGET(self):
 
759
        httpRequest = '''\
 
760
GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0
 
761
 
 
762
'''
 
763
        testcase = self
 
764
        class MyRequest(http.Request):
 
765
            def process(self):
 
766
                testcase.assertEquals(self.method, "GET")
 
767
                testcase.assertEquals(self.args["key"], ["value"])
 
768
                testcase.assertEquals(self.args["empty"], [""])
 
769
                testcase.assertEquals(self.args["multiple"], ["two words", "more words"])
 
770
                testcase.didRequest = 1
 
771
                self.finish()
 
772
 
 
773
        self.runRequest(httpRequest, MyRequest)
 
774
 
 
775
 
 
776
    def test_extraQuestionMark(self):
 
777
        """
 
778
        While only a single '?' is allowed in an URL, several other servers
 
779
        allow several and pass all after the first through as part of the
 
780
        query arguments.  Test that we emulate this behavior.
 
781
        """
 
782
        httpRequest = 'GET /foo?bar=?&baz=quux HTTP/1.0\n\n'
 
783
 
 
784
        testcase = self
 
785
        class MyRequest(http.Request):
 
786
            def process(self):
 
787
                testcase.assertEqual(self.method, 'GET')
 
788
                testcase.assertEqual(self.path, '/foo')
 
789
                testcase.assertEqual(self.args['bar'], ['?'])
 
790
                testcase.assertEqual(self.args['baz'], ['quux'])
 
791
                testcase.didRequest = 1
 
792
                self.finish()
 
793
 
 
794
        self.runRequest(httpRequest, MyRequest)
 
795
 
 
796
 
 
797
    def test_formPOSTRequest(self):
 
798
        """
 
799
        The request body of a I{POST} request with a I{Content-Type} header
 
800
        of I{application/x-www-form-urlencoded} is parsed according to that
 
801
        content type and made available in the C{args} attribute of the
 
802
        request object.  The original bytes of the request may still be read
 
803
        from the C{content} attribute.
 
804
        """
 
805
        query = 'key=value&multiple=two+words&multiple=more%20words&empty='
 
806
        httpRequest = '''\
 
807
POST / HTTP/1.0
 
808
Content-Length: %d
 
809
Content-Type: application/x-www-form-urlencoded
 
810
 
 
811
%s''' % (len(query), query)
 
812
 
 
813
        testcase = self
 
814
        class MyRequest(http.Request):
 
815
            def process(self):
 
816
                testcase.assertEquals(self.method, "POST")
 
817
                testcase.assertEquals(self.args["key"], ["value"])
 
818
                testcase.assertEquals(self.args["empty"], [""])
 
819
                testcase.assertEquals(self.args["multiple"], ["two words", "more words"])
 
820
 
 
821
                # Reading from the content file-like must produce the entire
 
822
                # request body.
 
823
                testcase.assertEquals(self.content.read(), query)
 
824
                testcase.didRequest = 1
 
825
                self.finish()
 
826
 
 
827
        self.runRequest(httpRequest, MyRequest)
 
828
 
 
829
    def testMissingContentDisposition(self):
 
830
        req = '''\
 
831
POST / HTTP/1.0
 
832
Content-Type: multipart/form-data; boundary=AaB03x
 
833
Content-Length: 103
 
834
 
 
835
--AaB03x
 
836
Content-Type: text/plain
 
837
Content-Transfer-Encoding: quoted-printable
 
838
 
 
839
abasdfg
 
840
--AaB03x--
 
841
'''
 
842
        self.runRequest(req, http.Request, success=False)
 
843
 
 
844
    def test_chunkedEncoding(self):
 
845
        """
 
846
        If a request uses the I{chunked} transfer encoding, the request body is
 
847
        decoded accordingly before it is made available on the request.
 
848
        """
 
849
        httpRequest = '''\
 
850
GET / HTTP/1.0
 
851
Content-Type: text/plain
 
852
Transfer-Encoding: chunked
 
853
 
 
854
6
 
855
Hello,
 
856
14
 
857
 spam,eggs spam spam
 
858
0
 
859
 
 
860
'''
 
861
        testcase = self
 
862
        class MyRequest(http.Request):
 
863
            def process(self):
 
864
                # The tempfile API used to create content returns an
 
865
                # instance of a different type depending on what platform
 
866
                # we're running on.  The point here is to verify that the
 
867
                # request body is in a file that's on the filesystem. 
 
868
                # Having a fileno method that returns an int is a somewhat
 
869
                # close approximation of this. -exarkun
 
870
                testcase.assertIsInstance(self.content.fileno(), int)
 
871
                testcase.assertEqual(self.method, 'GET')
 
872
                testcase.assertEqual(self.path, '/')
 
873
                content = self.content.read()
 
874
                testcase.assertEqual(content, 'Hello, spam,eggs spam spam')
 
875
                testcase.assertIdentical(self.channel._transferDecoder, None)
 
876
                testcase.didRequest = 1
 
877
                self.finish()
 
878
 
 
879
        self.runRequest(httpRequest, MyRequest)
 
880
 
 
881
 
 
882
 
 
883
class QueryArgumentsTestCase(unittest.TestCase):
 
884
    def testUnquote(self):
 
885
        try:
 
886
            from twisted.protocols import _c_urlarg
 
887
        except ImportError:
 
888
            raise unittest.SkipTest("_c_urlarg module is not available")
 
889
        # work exactly like urllib.unquote, including stupid things
 
890
        # % followed by a non-hexdigit in the middle and in the end
 
891
        self.failUnlessEqual(urllib.unquote("%notreally%n"),
 
892
            _c_urlarg.unquote("%notreally%n"))
 
893
        # % followed by hexdigit, followed by non-hexdigit
 
894
        self.failUnlessEqual(urllib.unquote("%1quite%1"),
 
895
            _c_urlarg.unquote("%1quite%1"))
 
896
        # unquoted text, followed by some quoted chars, ends in a trailing %
 
897
        self.failUnlessEqual(urllib.unquote("blah%21%40%23blah%"),
 
898
            _c_urlarg.unquote("blah%21%40%23blah%"))
 
899
        # Empty string
 
900
        self.failUnlessEqual(urllib.unquote(""), _c_urlarg.unquote(""))
 
901
 
 
902
    def testParseqs(self):
 
903
        self.failUnlessEqual(cgi.parse_qs("a=b&d=c;+=f"),
 
904
            http.parse_qs("a=b&d=c;+=f"))
 
905
        self.failUnlessRaises(ValueError, http.parse_qs, "blah",
 
906
            strict_parsing = 1)
 
907
        self.failUnlessEqual(cgi.parse_qs("a=&b=c", keep_blank_values = 1),
 
908
            http.parse_qs("a=&b=c", keep_blank_values = 1))
 
909
        self.failUnlessEqual(cgi.parse_qs("a=&b=c"),
 
910
            http.parse_qs("a=&b=c"))
 
911
 
 
912
 
 
913
    def test_urlparse(self):
 
914
        """
 
915
        For a given URL, L{http.urlparse} should behave the same as
 
916
        L{urlparse}, except it should always return C{str}, never C{unicode}.
 
917
        """
 
918
        def urls():
 
919
            for scheme in ('http', 'https'):
 
920
                for host in ('example.com',):
 
921
                    for port in (None, 100):
 
922
                        for path in ('', 'path'):
 
923
                            if port is not None:
 
924
                                host = host + ':' + str(port)
 
925
                                yield urlunsplit((scheme, host, path, '', ''))
 
926
 
 
927
 
 
928
        def assertSameParsing(url, decode):
 
929
            """
 
930
            Verify that C{url} is parsed into the same objects by both
 
931
            L{http.urlparse} and L{urlparse}.
 
932
            """
 
933
            urlToStandardImplementation = url
 
934
            if decode:
 
935
                urlToStandardImplementation = url.decode('ascii')
 
936
            standardResult = urlparse(urlToStandardImplementation)
 
937
            scheme, netloc, path, params, query, fragment = http.urlparse(url)
 
938
            self.assertEqual(
 
939
                (scheme, netloc, path, params, query, fragment),
 
940
                standardResult)
 
941
            self.assertTrue(isinstance(scheme, str))
 
942
            self.assertTrue(isinstance(netloc, str))
 
943
            self.assertTrue(isinstance(path, str))
 
944
            self.assertTrue(isinstance(params, str))
 
945
            self.assertTrue(isinstance(query, str))
 
946
            self.assertTrue(isinstance(fragment, str))
 
947
 
 
948
        # With caching, unicode then str
 
949
        clear_cache()
 
950
        for url in urls():
 
951
            assertSameParsing(url, True)
 
952
            assertSameParsing(url, False)
 
953
 
 
954
        # With caching, str then unicode
 
955
        clear_cache()
 
956
        for url in urls():
 
957
            assertSameParsing(url, False)
 
958
            assertSameParsing(url, True)
 
959
 
 
960
        # Without caching
 
961
        for url in urls():
 
962
            clear_cache()
 
963
            assertSameParsing(url, True)
 
964
            clear_cache()
 
965
            assertSameParsing(url, False)
 
966
 
 
967
 
 
968
    def test_urlparseRejectsUnicode(self):
 
969
        """
 
970
        L{http.urlparse} should reject unicode input early.
 
971
        """
 
972
        self.assertRaises(TypeError, http.urlparse, u'http://example.org/path')
 
973
 
 
974
 
 
975
    def testEscchar(self):
 
976
        try:
 
977
            from twisted.protocols import _c_urlarg
 
978
        except ImportError:
 
979
            raise unittest.SkipTest("_c_urlarg module is not available")
 
980
        self.failUnlessEqual("!@#+b",
 
981
            _c_urlarg.unquote("+21+40+23+b", "+"))
 
982
 
 
983
class ClientDriver(http.HTTPClient):
 
984
    def handleStatus(self, version, status, message):
 
985
        self.version = version
 
986
        self.status = status
 
987
        self.message = message
 
988
 
 
989
class ClientStatusParsing(unittest.TestCase):
 
990
    def testBaseline(self):
 
991
        c = ClientDriver()
 
992
        c.lineReceived('HTTP/1.0 201 foo')
 
993
        self.failUnlessEqual(c.version, 'HTTP/1.0')
 
994
        self.failUnlessEqual(c.status, '201')
 
995
        self.failUnlessEqual(c.message, 'foo')
 
996
 
 
997
    def testNoMessage(self):
 
998
        c = ClientDriver()
 
999
        c.lineReceived('HTTP/1.0 201')
 
1000
        self.failUnlessEqual(c.version, 'HTTP/1.0')
 
1001
        self.failUnlessEqual(c.status, '201')
 
1002
        self.failUnlessEqual(c.message, '')
 
1003
 
 
1004
    def testNoMessage_trailingSpace(self):
 
1005
        c = ClientDriver()
 
1006
        c.lineReceived('HTTP/1.0 201 ')
 
1007
        self.failUnlessEqual(c.version, 'HTTP/1.0')
 
1008
        self.failUnlessEqual(c.status, '201')
 
1009
        self.failUnlessEqual(c.message, '')
 
1010
 
 
1011
 
 
1012
 
 
1013
class RequestTests(unittest.TestCase, ResponseTestMixin):
 
1014
    """
 
1015
    Tests for L{http.Request}
 
1016
    """
 
1017
    def _compatHeadersTest(self, oldName, newName):
 
1018
        """
 
1019
        Verify that each of two different attributes which are associated with
 
1020
        the same state properly reflect changes made through the other.
 
1021
 
 
1022
        This is used to test that the C{headers}/C{responseHeaders} and
 
1023
        C{received_headers}/C{requestHeaders} pairs interact properly.
 
1024
        """
 
1025
        req = http.Request(DummyChannel(), None)
 
1026
        getattr(req, newName).setRawHeaders("test", ["lemur"])
 
1027
        self.assertEqual(getattr(req, oldName)["test"], "lemur")
 
1028
        setattr(req, oldName, {"foo": "bar"})
 
1029
        self.assertEqual(
 
1030
            list(getattr(req, newName).getAllRawHeaders()),
 
1031
            [("Foo", ["bar"])])
 
1032
        setattr(req, newName, http_headers.Headers())
 
1033
        self.assertEqual(getattr(req, oldName), {})
 
1034
 
 
1035
 
 
1036
    def test_received_headers(self):
 
1037
        """
 
1038
        L{Request.received_headers} is a backwards compatible API which
 
1039
        accesses and allows mutation of the state at L{Request.requestHeaders}.
 
1040
        """
 
1041
        self._compatHeadersTest('received_headers', 'requestHeaders')
 
1042
 
 
1043
 
 
1044
    def test_headers(self):
 
1045
        """
 
1046
        L{Request.headers} is a backwards compatible API which accesses and
 
1047
        allows mutation of the state at L{Request.responseHeaders}.
 
1048
        """
 
1049
        self._compatHeadersTest('headers', 'responseHeaders')
 
1050
 
 
1051
 
 
1052
    def test_getHeader(self):
 
1053
        """
 
1054
        L{http.Request.getHeader} returns the value of the named request
 
1055
        header.
 
1056
        """
 
1057
        req = http.Request(DummyChannel(), None)
 
1058
        req.requestHeaders.setRawHeaders("test", ["lemur"])
 
1059
        self.assertEquals(req.getHeader("test"), "lemur")
 
1060
 
 
1061
 
 
1062
    def test_getHeaderReceivedMultiples(self):
 
1063
        """
 
1064
        When there are multiple values for a single request header,
 
1065
        L{http.Request.getHeader} returns the last value.
 
1066
        """
 
1067
        req = http.Request(DummyChannel(), None)
 
1068
        req.requestHeaders.setRawHeaders("test", ["lemur", "panda"])
 
1069
        self.assertEquals(req.getHeader("test"), "panda")
 
1070
 
 
1071
 
 
1072
    def test_getHeaderNotFound(self):
 
1073
        """
 
1074
        L{http.Request.getHeader} returns C{None} when asked for the value of a
 
1075
        request header which is not present.
 
1076
        """
 
1077
        req = http.Request(DummyChannel(), None)
 
1078
        self.assertEquals(req.getHeader("test"), None)
 
1079
 
 
1080
 
 
1081
    def test_getAllHeaders(self):
 
1082
        """
 
1083
        L{http.Request.getAllheaders} returns a C{dict} mapping all request
 
1084
        header names to their corresponding values.
 
1085
        """
 
1086
        req = http.Request(DummyChannel(), None)
 
1087
        req.requestHeaders.setRawHeaders("test", ["lemur"])
 
1088
        self.assertEquals(req.getAllHeaders(), {"test": "lemur"})
 
1089
 
 
1090
 
 
1091
    def test_getAllHeadersNoHeaders(self):
 
1092
        """
 
1093
        L{http.Request.getAllHeaders} returns an empty C{dict} if there are no
 
1094
        request headers.
 
1095
        """
 
1096
        req = http.Request(DummyChannel(), None)
 
1097
        self.assertEquals(req.getAllHeaders(), {})
 
1098
 
 
1099
 
 
1100
    def test_getAllHeadersMultipleHeaders(self):
 
1101
        """
 
1102
        When there are multiple values for a single request header,
 
1103
        L{http.Request.getAllHeaders} returns only the last value.
 
1104
        """
 
1105
        req = http.Request(DummyChannel(), None)
 
1106
        req.requestHeaders.setRawHeaders("test", ["lemur", "panda"])
 
1107
        self.assertEquals(req.getAllHeaders(), {"test": "panda"})
 
1108
 
 
1109
 
 
1110
    def test_setResponseCode(self):
 
1111
        """
 
1112
        L{http.Request.setResponseCode} takes a status code and causes it to be
 
1113
        used as the response status.
 
1114
        """
 
1115
        channel = DummyChannel()
 
1116
        req = http.Request(channel, None)
 
1117
        req.setResponseCode(201)
 
1118
        req.write('')
 
1119
        self.assertEqual(
 
1120
            channel.transport.written.getvalue().splitlines()[0],
 
1121
            '%s 201 Created' % (req.clientproto,))
 
1122
 
 
1123
 
 
1124
    def test_setResponseCodeAndMessage(self):
 
1125
        """
 
1126
        L{http.Request.setResponseCode} takes a status code and a message and
 
1127
        causes them to be used as the response status.
 
1128
        """
 
1129
        channel = DummyChannel()
 
1130
        req = http.Request(channel, None)
 
1131
        req.setResponseCode(202, "happily accepted")
 
1132
        req.write('')
 
1133
        self.assertEqual(
 
1134
            channel.transport.written.getvalue().splitlines()[0],
 
1135
            '%s 202 happily accepted' % (req.clientproto,))
 
1136
 
 
1137
 
 
1138
    def test_setResponseCodeAcceptsIntegers(self):
 
1139
        """
 
1140
        L{http.Request.setResponseCode} accepts C{int} or C{long} for the code
 
1141
        parameter and raises L{TypeError} if passed anything else.
 
1142
        """
 
1143
        req = http.Request(DummyChannel(), None)
 
1144
        req.setResponseCode(1)
 
1145
        req.setResponseCode(1L)
 
1146
        self.assertRaises(TypeError, req.setResponseCode, "1")
 
1147
 
 
1148
 
 
1149
    def test_setHost(self):
 
1150
        """
 
1151
        L{http.Request.setHost} sets the value of the host request header.
 
1152
        """
 
1153
        req = http.Request(DummyChannel(), None)
 
1154
        req.setHost("example.com", 443)
 
1155
        self.assertEqual(
 
1156
            req.requestHeaders.getRawHeaders("host"), ["example.com"])
 
1157
 
 
1158
 
 
1159
    def test_setHeader(self):
 
1160
        """
 
1161
        L{http.Request.setHeader} sets the value of the given response header.
 
1162
        """
 
1163
        req = http.Request(DummyChannel(), None)
 
1164
        req.setHeader("test", "lemur")
 
1165
        self.assertEquals(req.responseHeaders.getRawHeaders("test"), ["lemur"])
 
1166
 
 
1167
 
 
1168
    def test_firstWrite(self):
 
1169
        """
 
1170
        For an HTTP 1.0 request, L{http.Request.write} sends an HTTP 1.0
 
1171
        Response-Line and whatever response headers are set.
 
1172
        """
 
1173
        req = http.Request(DummyChannel(), None)
 
1174
        trans = StringTransport()
 
1175
 
 
1176
        req.transport = trans
 
1177
 
 
1178
        req.setResponseCode(200)
 
1179
        req.clientproto = "HTTP/1.0"
 
1180
        req.responseHeaders.setRawHeaders("test", ["lemur"])
 
1181
        req.write('Hello')
 
1182
 
 
1183
        self.assertResponseEquals(
 
1184
            trans.value(),
 
1185
            [("HTTP/1.0 200 OK",
 
1186
              "Test: lemur",
 
1187
              "Hello")])
 
1188
 
 
1189
 
 
1190
    def test_firstWriteHTTP11Chunked(self):
 
1191
        """
 
1192
        For an HTTP 1.1 request, L{http.Request.write} sends an HTTP 1.1
 
1193
        Response-Line, whatever response headers are set, and uses chunked
 
1194
        encoding for the response body.
 
1195
        """
 
1196
        req = http.Request(DummyChannel(), None)
 
1197
        trans = StringTransport()
 
1198
 
 
1199
        req.transport = trans
 
1200
 
 
1201
        req.setResponseCode(200)
 
1202
        req.clientproto = "HTTP/1.1"
 
1203
        req.responseHeaders.setRawHeaders("test", ["lemur"])
 
1204
        req.write('Hello')
 
1205
        req.write('World!')
 
1206
 
 
1207
        self.assertResponseEquals(
 
1208
            trans.value(),
 
1209
            [("HTTP/1.1 200 OK",
 
1210
              "Test: lemur",
 
1211
              "Transfer-Encoding: chunked",
 
1212
              "5\r\nHello\r\n6\r\nWorld!\r\n")])
 
1213
 
 
1214
 
 
1215
    def test_firstWriteLastModified(self):
 
1216
        """
 
1217
        For an HTTP 1.0 request for a resource with a known last modified time,
 
1218
        L{http.Request.write} sends an HTTP Response-Line, whatever response
 
1219
        headers are set, and a last-modified header with that time.
 
1220
        """
 
1221
        req = http.Request(DummyChannel(), None)
 
1222
        trans = StringTransport()
 
1223
 
 
1224
        req.transport = trans
 
1225
 
 
1226
        req.setResponseCode(200)
 
1227
        req.clientproto = "HTTP/1.0"
 
1228
        req.lastModified = 0
 
1229
        req.responseHeaders.setRawHeaders("test", ["lemur"])
 
1230
        req.write('Hello')
 
1231
 
 
1232
        self.assertResponseEquals(
 
1233
            trans.value(),
 
1234
            [("HTTP/1.0 200 OK",
 
1235
              "Test: lemur",
 
1236
              "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT",
 
1237
              "Hello")])
 
1238
 
 
1239
 
 
1240
    def test_parseCookies(self):
 
1241
        """
 
1242
        L{http.Request.parseCookies} extracts cookies from C{requestHeaders}
 
1243
        and adds them to C{received_cookies}.
 
1244
        """
 
1245
        req = http.Request(DummyChannel(), None)
 
1246
        req.requestHeaders.setRawHeaders(
 
1247
            "cookie", ['test="lemur"; test2="panda"'])
 
1248
        req.parseCookies()
 
1249
        self.assertEquals(req.received_cookies, {"test": '"lemur"',
 
1250
                                                 "test2": '"panda"'})
 
1251
 
 
1252
 
 
1253
    def test_parseCookiesMultipleHeaders(self):
 
1254
        """
 
1255
        L{http.Request.parseCookies} can extract cookies from multiple Cookie
 
1256
        headers.
 
1257
        """
 
1258
        req = http.Request(DummyChannel(), None)
 
1259
        req.requestHeaders.setRawHeaders(
 
1260
            "cookie", ['test="lemur"', 'test2="panda"'])
 
1261
        req.parseCookies()
 
1262
        self.assertEquals(req.received_cookies, {"test": '"lemur"',
 
1263
                                                 "test2": '"panda"'})
 
1264
 
 
1265
 
 
1266
    def test_connectionLost(self):
 
1267
        """
 
1268
        L{http.Request.connectionLost} closes L{Request.content} and drops the
 
1269
        reference to the L{HTTPChannel} to assist with garbage collection.
 
1270
        """
 
1271
        req = http.Request(DummyChannel(), None)
 
1272
 
 
1273
        # Cause Request.content to be created at all.
 
1274
        req.gotLength(10)
 
1275
 
 
1276
        # Grab a reference to content in case the Request drops it later on.
 
1277
        content = req.content
 
1278
 
 
1279
        # Put some bytes into it
 
1280
        req.handleContentChunk("hello")
 
1281
 
 
1282
        # Then something goes wrong and content should get closed.
 
1283
        req.connectionLost(Failure(ConnectionLost("Finished")))
 
1284
        self.assertTrue(content.closed)
 
1285
        self.assertIdentical(req.channel, None)
 
1286
 
 
1287
 
 
1288
    def test_registerProducerTwiceFails(self):
 
1289
        """
 
1290
        Calling L{Request.registerProducer} when a producer is already
 
1291
        registered raises ValueError.
 
1292
        """
 
1293
        req = http.Request(DummyChannel(), None)
 
1294
        req.registerProducer(DummyProducer(), True)
 
1295
        self.assertRaises(
 
1296
            ValueError, req.registerProducer, DummyProducer(), True)
 
1297
 
 
1298
 
 
1299
    def test_registerProducerWhenQueuedPausesPushProducer(self):
 
1300
        """
 
1301
        Calling L{Request.registerProducer} with an IPushProducer when the
 
1302
        request is queued pauses the producer.
 
1303
        """
 
1304
        req = http.Request(DummyChannel(), True)
 
1305
        producer = DummyProducer()
 
1306
        req.registerProducer(producer, True)
 
1307
        self.assertEquals(['pause'], producer.events)
 
1308
 
 
1309
 
 
1310
    def test_registerProducerWhenQueuedDoesntPausePullProducer(self):
 
1311
        """
 
1312
        Calling L{Request.registerProducer} with an IPullProducer when the
 
1313
        request is queued does not pause the producer, because it doesn't make
 
1314
        sense to pause a pull producer.
 
1315
        """
 
1316
        req = http.Request(DummyChannel(), True)
 
1317
        producer = DummyProducer()
 
1318
        req.registerProducer(producer, False)
 
1319
        self.assertEquals([], producer.events)
 
1320
 
 
1321
 
 
1322
    def test_registerProducerWhenQueuedDoesntRegisterPushProducer(self):
 
1323
        """
 
1324
        Calling L{Request.registerProducer} with an IPushProducer when the
 
1325
        request is queued does not register the producer on the request's
 
1326
        transport.
 
1327
        """
 
1328
        self.assertIdentical(
 
1329
            None, getattr(http.StringTransport, 'registerProducer', None),
 
1330
            "StringTransport cannot implement registerProducer for this test "
 
1331
            "to be valid.")
 
1332
        req = http.Request(DummyChannel(), True)
 
1333
        producer = DummyProducer()
 
1334
        req.registerProducer(producer, True)
 
1335
        # This is a roundabout assertion: http.StringTransport doesn't
 
1336
        # implement registerProducer, so Request.registerProducer can't have
 
1337
        # tried to call registerProducer on the transport.
 
1338
        self.assertIsInstance(req.transport, http.StringTransport)
 
1339
 
 
1340
 
 
1341
    def test_registerProducerWhenQueuedDoesntRegisterPullProducer(self):
 
1342
        """
 
1343
        Calling L{Request.registerProducer} with an IPullProducer when the
 
1344
        request is queued does not register the producer on the request's
 
1345
        transport.
 
1346
        """
 
1347
        self.assertIdentical(
 
1348
            None, getattr(http.StringTransport, 'registerProducer', None),
 
1349
            "StringTransport cannot implement registerProducer for this test "
 
1350
            "to be valid.")
 
1351
        req = http.Request(DummyChannel(), True)
 
1352
        producer = DummyProducer()
 
1353
        req.registerProducer(producer, False)
 
1354
        # This is a roundabout assertion: http.StringTransport doesn't
 
1355
        # implement registerProducer, so Request.registerProducer can't have
 
1356
        # tried to call registerProducer on the transport.
 
1357
        self.assertIsInstance(req.transport, http.StringTransport)
 
1358
 
 
1359
 
 
1360
    def test_registerProducerWhenNotQueuedRegistersPushProducer(self):
 
1361
        """
 
1362
        Calling L{Request.registerProducer} with an IPushProducer when the
 
1363
        request is not queued registers the producer as a push producer on the
 
1364
        request's transport.
 
1365
        """
 
1366
        req = http.Request(DummyChannel(), False)
 
1367
        producer = DummyProducer()
 
1368
        req.registerProducer(producer, True)
 
1369
        self.assertEquals([(producer, True)], req.transport.producers)
 
1370
 
 
1371
 
 
1372
    def test_registerProducerWhenNotQueuedRegistersPullProducer(self):
 
1373
        """
 
1374
        Calling L{Request.registerProducer} with an IPullProducer when the
 
1375
        request is not queued registers the producer as a pull producer on the
 
1376
        request's transport.
 
1377
        """
 
1378
        req = http.Request(DummyChannel(), False)
 
1379
        producer = DummyProducer()
 
1380
        req.registerProducer(producer, False)
 
1381
        self.assertEquals([(producer, False)], req.transport.producers)
 
1382
 
 
1383
 
 
1384
    def test_connectionLostNotification(self):
 
1385
        """
 
1386
        L{Request.connectionLost} triggers all finish notification Deferreds
 
1387
        and cleans up per-request state.
 
1388
        """
 
1389
        d = DummyChannel()
 
1390
        request = http.Request(d, True)
 
1391
        finished = request.notifyFinish()
 
1392
        request.connectionLost(Failure(ConnectionLost("Connection done")))
 
1393
        self.assertIdentical(request.channel, None)
 
1394
        return self.assertFailure(finished, ConnectionLost)
 
1395
 
 
1396
 
 
1397
    def test_finishNotification(self):
 
1398
        """
 
1399
        L{Request.finish} triggers all finish notification Deferreds.
 
1400
        """
 
1401
        request = http.Request(DummyChannel(), False)
 
1402
        finished = request.notifyFinish()
 
1403
        # Force the request to have a non-None content attribute.  This is
 
1404
        # probably a bug in Request.
 
1405
        request.gotLength(1)
 
1406
        request.finish()
 
1407
        return finished
 
1408
 
 
1409
 
 
1410
    def test_finishAfterConnectionLost(self):
 
1411
        """
 
1412
        Calling L{Request.finish} after L{Request.connectionLost} has been
 
1413
        called results in a L{RuntimeError} being raised.
 
1414
        """
 
1415
        channel = DummyChannel()
 
1416
        transport = channel.transport
 
1417
        req = http.Request(channel, False)
 
1418
        req.connectionLost(Failure(ConnectionLost("The end.")))
 
1419
        self.assertRaises(RuntimeError, req.finish)
 
1420
 
 
1421
 
 
1422
 
 
1423
class MultilineHeadersTestCase(unittest.TestCase):
 
1424
    """
 
1425
    Tests to exercise handling of multiline headers by L{HTTPClient}.  RFCs 1945
 
1426
    (HTTP 1.0) and 2616 (HTTP 1.1) state that HTTP message header fields can
 
1427
    span multiple lines if each extra line is preceded by at least one space or
 
1428
    horizontal tab.
 
1429
    """
 
1430
    def setUp(self):
 
1431
        """
 
1432
        Initialize variables used to verify that the header-processing functions
 
1433
        are getting called.
 
1434
        """
 
1435
        self.handleHeaderCalled = False
 
1436
        self.handleEndHeadersCalled = False
 
1437
 
 
1438
    # Dictionary of sample complete HTTP header key/value pairs, including
 
1439
    # multiline headers.
 
1440
    expectedHeaders = {'Content-Length': '10',
 
1441
                       'X-Multiline' : 'line-0\tline-1',
 
1442
                       'X-Multiline2' : 'line-2 line-3'}
 
1443
 
 
1444
    def ourHandleHeader(self, key, val):
 
1445
        """
 
1446
        Dummy implementation of L{HTTPClient.handleHeader}.
 
1447
        """
 
1448
        self.handleHeaderCalled = True
 
1449
        self.assertEquals(val, self.expectedHeaders[key])
 
1450
 
 
1451
 
 
1452
    def ourHandleEndHeaders(self):
 
1453
        """
 
1454
        Dummy implementation of L{HTTPClient.handleEndHeaders}.
 
1455
        """
 
1456
        self.handleEndHeadersCalled = True
 
1457
 
 
1458
 
 
1459
    def test_extractHeader(self):
 
1460
        """
 
1461
        A header isn't processed by L{HTTPClient.extractHeader} until it is
 
1462
        confirmed in L{HTTPClient.lineReceived} that the header has been
 
1463
        received completely.
 
1464
        """
 
1465
        c = ClientDriver()
 
1466
        c.handleHeader = self.ourHandleHeader
 
1467
        c.handleEndHeaders = self.ourHandleEndHeaders
 
1468
 
 
1469
        c.lineReceived('HTTP/1.0 201')
 
1470
        c.lineReceived('Content-Length: 10')
 
1471
        self.assertIdentical(c.length, None)
 
1472
        self.assertFalse(self.handleHeaderCalled)
 
1473
        self.assertFalse(self.handleEndHeadersCalled)
 
1474
 
 
1475
        # Signal end of headers.
 
1476
        c.lineReceived('')
 
1477
        self.assertTrue(self.handleHeaderCalled)
 
1478
        self.assertTrue(self.handleEndHeadersCalled)
 
1479
 
 
1480
        self.assertEquals(c.length, 10)
 
1481
 
 
1482
 
 
1483
    def test_noHeaders(self):
 
1484
        """
 
1485
        An HTTP request with no headers will not cause any calls to
 
1486
        L{handleHeader} but will cause L{handleEndHeaders} to be called on
 
1487
        L{HTTPClient} subclasses.
 
1488
        """
 
1489
        c = ClientDriver()
 
1490
        c.handleHeader = self.ourHandleHeader
 
1491
        c.handleEndHeaders = self.ourHandleEndHeaders
 
1492
        c.lineReceived('HTTP/1.0 201')
 
1493
 
 
1494
        # Signal end of headers.
 
1495
        c.lineReceived('')
 
1496
        self.assertFalse(self.handleHeaderCalled)
 
1497
        self.assertTrue(self.handleEndHeadersCalled)
 
1498
 
 
1499
        self.assertEquals(c.version, 'HTTP/1.0')
 
1500
        self.assertEquals(c.status, '201')
 
1501
 
 
1502
 
 
1503
    def test_multilineHeaders(self):
 
1504
        """
 
1505
        L{HTTPClient} parses multiline headers by buffering header lines until
 
1506
        an empty line or a line that does not start with whitespace hits
 
1507
        lineReceived, confirming that the header has been received completely.
 
1508
        """
 
1509
        c = ClientDriver()
 
1510
        c.handleHeader = self.ourHandleHeader
 
1511
        c.handleEndHeaders = self.ourHandleEndHeaders
 
1512
 
 
1513
        c.lineReceived('HTTP/1.0 201')
 
1514
        c.lineReceived('X-Multiline: line-0')
 
1515
        self.assertFalse(self.handleHeaderCalled)
 
1516
        # Start continuing line with a tab.
 
1517
        c.lineReceived('\tline-1')
 
1518
        c.lineReceived('X-Multiline2: line-2')
 
1519
        # The previous header must be complete, so now it can be processed.
 
1520
        self.assertTrue(self.handleHeaderCalled)
 
1521
        # Start continuing line with a space.
 
1522
        c.lineReceived(' line-3')
 
1523
        c.lineReceived('Content-Length: 10')
 
1524
 
 
1525
        # Signal end of headers.
 
1526
        c.lineReceived('')
 
1527
        self.assertTrue(self.handleEndHeadersCalled)
 
1528
 
 
1529
        self.assertEquals(c.version, 'HTTP/1.0')
 
1530
        self.assertEquals(c.status, '201')
 
1531
        self.assertEquals(c.length, 10)