httpclient_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #!/usr/bin/env python
  2. # Copyright 2012 Google Inc. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import mock
  16. import unittest
  17. import datetime
  18. import dnsproxy
  19. import httparchive
  20. import httpclient
  21. import platformsettings
  22. import script_injector
  23. import test_utils
  24. class RealHttpFetchTest(unittest.TestCase):
  25. # Initialize test data
  26. CONTENT_TYPE = 'content-type: image/x-icon'
  27. COOKIE_1 = ('Set-Cookie: GMAIL_IMP=EXPIRED; '
  28. 'Expires=Thu, 12-Jul-2012 22:41:22 GMT; '
  29. 'Path=/mail; Secure')
  30. COOKIE_2 = ('Set-Cookie: GMAIL_STAT_205a=EXPIRED; '
  31. 'Expires=Thu, 12-Jul-2012 22:42:24 GMT; '
  32. 'Path=/mail; Secure')
  33. FIRST_LINE = 'fake-header: first line'
  34. SECOND_LINE = ' second line'
  35. THIRD_LINE = '\tthird line'
  36. BAD_HEADER = 'this is a bad header'
  37. def test__GetHeaderNameValueBasic(self):
  38. """Test _GetHeaderNameValue with normal header."""
  39. real_http_fetch = httpclient.RealHttpFetch
  40. name_value = real_http_fetch._GetHeaderNameValue(self.CONTENT_TYPE)
  41. self.assertEqual(name_value, ('content-type', 'image/x-icon'))
  42. def test__GetHeaderNameValueLowercasesName(self):
  43. """_GetHeaderNameValue lowercases header name."""
  44. real_http_fetch = httpclient.RealHttpFetch
  45. header = 'X-Google-Gfe-Backend-Request-Info: eid=1KMAUMeiK4eMiAL52YyMBg'
  46. expected = ('x-google-gfe-backend-request-info',
  47. 'eid=1KMAUMeiK4eMiAL52YyMBg')
  48. name_value = real_http_fetch._GetHeaderNameValue(header)
  49. self.assertEqual(name_value, expected)
  50. def test__GetHeaderNameValueBadLineGivesNone(self):
  51. """_GetHeaderNameValue returns None for a header in wrong format."""
  52. real_http_fetch = httpclient.RealHttpFetch
  53. name_value = real_http_fetch._GetHeaderNameValue(self.BAD_HEADER)
  54. self.assertIsNone(name_value)
  55. def test__ToTuplesBasic(self):
  56. """Test _ToTuples with normal input."""
  57. real_http_fetch = httpclient.RealHttpFetch
  58. headers = [self.CONTENT_TYPE, self.COOKIE_1, self.FIRST_LINE]
  59. result = real_http_fetch._ToTuples(headers)
  60. expected = [('content-type', 'image/x-icon'),
  61. ('set-cookie', self.COOKIE_1[12:]),
  62. ('fake-header', 'first line')]
  63. self.assertEqual(result, expected)
  64. def test__ToTuplesMultipleHeadersWithSameName(self):
  65. """Test mulitple headers with the same name."""
  66. real_http_fetch = httpclient.RealHttpFetch
  67. headers = [self.CONTENT_TYPE, self.COOKIE_1, self.COOKIE_2, self.FIRST_LINE]
  68. result = real_http_fetch._ToTuples(headers)
  69. expected = [('content-type', 'image/x-icon'),
  70. ('set-cookie', self.COOKIE_1[12:]),
  71. ('set-cookie', self.COOKIE_2[12:]),
  72. ('fake-header', 'first line')]
  73. self.assertEqual(result, expected)
  74. def test__ToTuplesAppendsContinuationLine(self):
  75. """Test continuation line is handled."""
  76. real_http_fetch = httpclient.RealHttpFetch
  77. headers = [self.CONTENT_TYPE, self.COOKIE_1, self.FIRST_LINE,
  78. self.SECOND_LINE, self.THIRD_LINE]
  79. result = real_http_fetch._ToTuples(headers)
  80. expected = [('content-type', 'image/x-icon'),
  81. ('set-cookie', self.COOKIE_1[12:]),
  82. ('fake-header', 'first line\n second line\n third line')]
  83. self.assertEqual(result, expected)
  84. def test__ToTuplesIgnoresBadHeader(self):
  85. """Test bad header is ignored."""
  86. real_http_fetch = httpclient.RealHttpFetch
  87. bad_headers = [self.CONTENT_TYPE, self.BAD_HEADER, self.COOKIE_1]
  88. expected = [('content-type', 'image/x-icon'),
  89. ('set-cookie', self.COOKIE_1[12:])]
  90. result = real_http_fetch._ToTuples(bad_headers)
  91. self.assertEqual(result, expected)
  92. def test__ToTuplesIgnoresMisplacedContinuationLine(self):
  93. """Test misplaced continuation line is ignored."""
  94. real_http_fetch = httpclient.RealHttpFetch
  95. misplaced_headers = [self.THIRD_LINE, self.CONTENT_TYPE,
  96. self.COOKIE_1, self.FIRST_LINE, self.SECOND_LINE]
  97. result = real_http_fetch._ToTuples(misplaced_headers)
  98. expected = [('content-type', 'image/x-icon'),
  99. ('set-cookie', self.COOKIE_1[12:]),
  100. ('fake-header', 'first line\n second line')]
  101. self.assertEqual(result, expected)
  102. class RealHttpFetchGetConnectionTest(unittest.TestCase):
  103. """Test that a connection is made with request IP/port or proxy IP/port."""
  104. def setUp(self):
  105. def real_dns_lookup(host):
  106. return {
  107. 'example.com': '127.127.127.127',
  108. 'proxy.com': '2.2.2.2',
  109. }[host]
  110. self.fetch = httpclient.RealHttpFetch(real_dns_lookup)
  111. self.https_proxy = None
  112. self.http_proxy = None
  113. def get_proxy(is_ssl):
  114. return self.https_proxy if is_ssl else self.http_proxy
  115. self.fetch._get_system_proxy = get_proxy
  116. def set_http_proxy(self, host, port):
  117. self.http_proxy = platformsettings.SystemProxy(host, port)
  118. def set_https_proxy(self, host, port):
  119. self.https_proxy = platformsettings.SystemProxy(host, port)
  120. def test_get_connection_without_proxy_connects_to_host_ip(self):
  121. """HTTP connection with no proxy connects to host IP."""
  122. self.set_http_proxy(host=None, port=None)
  123. connection = self.fetch._get_connection('example.com', None, is_ssl=False)
  124. self.assertEqual('127.127.127.127', connection.host)
  125. self.assertEqual(80, connection.port) # default HTTP port
  126. def test_get_connection_without_proxy_uses_nondefault_request_port(self):
  127. """HTTP connection with no proxy connects with request port."""
  128. self.set_https_proxy(host=None, port=None)
  129. connection = self.fetch._get_connection('example.com', 8888, is_ssl=False)
  130. self.assertEqual('127.127.127.127', connection.host)
  131. self.assertEqual(8888, connection.port) # request HTTP port
  132. def test_get_connection_with_proxy_uses_proxy_port(self):
  133. """HTTP connection with proxy connects used proxy port."""
  134. self.set_http_proxy(host='proxy.com', port=None)
  135. connection = self.fetch._get_connection('example.com', 8888, is_ssl=False)
  136. self.assertEqual('2.2.2.2', connection.host) # proxy IP
  137. self.assertEqual(80, connection.port) # proxy port (default HTTP)
  138. def test_ssl_get_connection_without_proxy_connects_to_host_ip(self):
  139. """HTTPS (SSL) connection with no proxy connects to host IP."""
  140. self.set_https_proxy(host=None, port=None)
  141. connection = self.fetch._get_connection('example.com', None, is_ssl=True)
  142. self.assertEqual('127.127.127.127', connection.host)
  143. self.assertEqual(443, connection.port) # default SSL port
  144. def test_ssl_get_connection_with_proxy_connects_to_proxy_ip(self):
  145. """HTTPS (SSL) connection with proxy connects to proxy IP."""
  146. self.set_https_proxy(host='proxy.com', port=8443)
  147. connection = self.fetch._get_connection('example.com', None, is_ssl=True)
  148. self.assertEqual('2.2.2.2', connection.host) # proxy IP
  149. self.assertEqual(8443, connection.port) # SSL proxy port
  150. def test_ssl_get_connection_with_proxy_tunnels_to_host(self):
  151. """HTTPS (SSL) connection with proxy tunnels to target host."""
  152. self.set_https_proxy(host='proxy.com', port=8443)
  153. connection = self.fetch._get_connection('example.com', None, is_ssl=True)
  154. self.assertEqual('example.com', connection._tunnel_host) # host name
  155. self.assertEqual(None, connection._tunnel_port) # host port
  156. class ActualNetworkFetchTest(test_utils.RealNetworkFetchTest):
  157. def testFetchNonSSLRequest(self):
  158. real_dns_lookup = dnsproxy.RealDnsLookup(
  159. name_servers=[platformsettings.get_original_primary_nameserver()])
  160. fetch = httpclient.RealHttpFetch(real_dns_lookup)
  161. request = httparchive.ArchivedHttpRequest(
  162. command='GET', host='google.com', full_path='/search?q=dogs',
  163. request_body=None, headers={}, is_ssl=False)
  164. response = fetch(request)
  165. self.assertIsNotNone(response)
  166. def testFetchSSLRequest(self):
  167. real_dns_lookup = dnsproxy.RealDnsLookup(
  168. name_servers=[platformsettings.get_original_primary_nameserver()])
  169. fetch = httpclient.RealHttpFetch(real_dns_lookup)
  170. request = httparchive.ArchivedHttpRequest(
  171. command='GET', host='google.com', full_path='/search?q=dogs',
  172. request_body=None, headers={}, is_ssl=True)
  173. response = fetch(request)
  174. self.assertIsNotNone(response)
  175. class HttpArchiveFetchTest(unittest.TestCase):
  176. TEST_REQUEST_TIME = datetime.datetime(2016, 11, 17, 1, 2, 3, 456)
  177. def createTestResponse(self):
  178. return httparchive.ArchivedHttpResponse(
  179. 11, 200, 'OK', [('content-type', 'text/html')],
  180. ['<body>test</body>'],
  181. request_time=HttpArchiveFetchTest.TEST_REQUEST_TIME)
  182. def checkTestResponse(self, actual_response, archive, request):
  183. self.assertEqual(actual_response, archive[request])
  184. self.assertEqual(['<body>test</body>'], actual_response.response_data)
  185. self.assertEqual(HttpArchiveFetchTest.TEST_REQUEST_TIME,
  186. actual_response.request_time)
  187. @staticmethod
  188. def dummy_injector(_):
  189. return '<body>test</body>'
  190. class RecordHttpArchiveFetchTest(HttpArchiveFetchTest):
  191. @mock.patch('httpclient.RealHttpFetch')
  192. def testFetch(self, real_http_fetch):
  193. http_fetch_instance = real_http_fetch.return_value
  194. response = self.createTestResponse()
  195. http_fetch_instance.return_value = response
  196. archive = httparchive.HttpArchive()
  197. fetch = httpclient.RecordHttpArchiveFetch(archive, self.dummy_injector)
  198. request = httparchive.ArchivedHttpRequest(
  199. 'GET', 'www.test.com', '/', None, {})
  200. self.checkTestResponse(fetch(request), archive, request)
  201. class ReplayHttpArchiveFetchTest(HttpArchiveFetchTest):
  202. def testFetch(self):
  203. request = httparchive.ArchivedHttpRequest(
  204. 'GET', 'www.test.com', '/', None, {})
  205. response = self.createTestResponse()
  206. archive = httparchive.HttpArchive()
  207. archive[request] = response
  208. fetch = httpclient.ReplayHttpArchiveFetch(
  209. archive, None, self.dummy_injector)
  210. self.checkTestResponse(fetch(request), archive, request)
  211. @mock.patch('script_injector.util.resource_string')
  212. @mock.patch('script_injector.util.resource_exists')
  213. @mock.patch('script_injector.os.path.exists')
  214. def testInjectedDate(self, os_path, util_exists, util_resource_string):
  215. os_path.return_value = False
  216. util_exists.return_value = True
  217. util_resource_string.return_value = \
  218. ["""var time_seed={}""".format(script_injector.TIME_SEED_MARKER)]
  219. request = httparchive.ArchivedHttpRequest(
  220. 'GET', 'www.test.com', '/', None, {})
  221. response = self.createTestResponse()
  222. archive = httparchive.HttpArchive()
  223. archive[request] = response
  224. fetch = httpclient.ReplayHttpArchiveFetch(
  225. archive, None, script_injector.GetScriptInjector("time_script.js"))
  226. self.assertEqual(
  227. ['<script>var time_seed=1479344523000</script><body>test</body>'],
  228. fetch(request).response_data)
  229. if __name__ == '__main__':
  230. unittest.main()