test_http.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """
  2. :codeauthor: Nicole Thomas <nicole@saltstack.com>
  3. """
  4. import socket
  5. from contextlib import closing
  6. import salt.utils.http as http
  7. from tests.support.helpers import MirrorPostHandler, Webserver, slowTest
  8. from tests.support.unit import TestCase
  9. class HTTPTestCase(TestCase):
  10. """
  11. Unit TestCase for the salt.utils.http module.
  12. """
  13. @classmethod
  14. def setUpClass(cls):
  15. cls.post_webserver = Webserver(handler=MirrorPostHandler)
  16. cls.post_webserver.start()
  17. cls.post_web_root = cls.post_webserver.web_root
  18. @classmethod
  19. def tearDownClass(cls):
  20. cls.post_webserver.stop()
  21. del cls.post_webserver
  22. # sanitize_url tests
  23. def test_sanitize_url_hide_fields_none(self):
  24. """
  25. Tests sanitizing a url when the hide_fields kwarg is None.
  26. """
  27. mock_url = "https://api.testing.com/?&foo=bar&test=testing"
  28. ret = http.sanitize_url(mock_url, hide_fields=None)
  29. self.assertEqual(ret, mock_url)
  30. def test_sanitize_url_no_elements(self):
  31. """
  32. Tests sanitizing a url when no elements should be sanitized.
  33. """
  34. mock_url = "https://api.testing.com/?&foo=bar&test=testing"
  35. ret = http.sanitize_url(mock_url, [""])
  36. self.assertEqual(ret, mock_url)
  37. def test_sanitize_url_single_element(self):
  38. """
  39. Tests sanitizing a url with only a single element to be sanitized.
  40. """
  41. mock_url = (
  42. "https://api.testing.com/?&keep_it_secret=abcdefghijklmn"
  43. "&api_action=module.function"
  44. )
  45. mock_ret = (
  46. "https://api.testing.com/?&keep_it_secret=XXXXXXXXXX&"
  47. "api_action=module.function"
  48. )
  49. ret = http.sanitize_url(mock_url, ["keep_it_secret"])
  50. self.assertEqual(ret, mock_ret)
  51. def test_sanitize_url_multiple_elements(self):
  52. """
  53. Tests sanitizing a url with multiple elements to be sanitized.
  54. """
  55. mock_url = (
  56. "https://api.testing.com/?rootPass=badpassword%21"
  57. "&skipChecks=True&api_key=abcdefghijklmn"
  58. "&NodeID=12345&api_action=module.function"
  59. )
  60. mock_ret = (
  61. "https://api.testing.com/?rootPass=XXXXXXXXXX"
  62. "&skipChecks=True&api_key=XXXXXXXXXX"
  63. "&NodeID=12345&api_action=module.function"
  64. )
  65. ret = http.sanitize_url(mock_url, ["api_key", "rootPass"])
  66. self.assertEqual(ret, mock_ret)
  67. # _sanitize_components tests
  68. def test_sanitize_components_no_elements(self):
  69. """
  70. Tests when zero elements need to be sanitized.
  71. """
  72. mock_component_list = ["foo=bar", "bar=baz", "hello=world"]
  73. mock_ret = "foo=bar&bar=baz&hello=world&"
  74. ret = http._sanitize_url_components(mock_component_list, "api_key")
  75. self.assertEqual(ret, mock_ret)
  76. def test_sanitize_components_one_element(self):
  77. """
  78. Tests a single component to be sanitized.
  79. """
  80. mock_component_list = ["foo=bar", "api_key=abcdefghijklmnop"]
  81. mock_ret = "foo=bar&api_key=XXXXXXXXXX&"
  82. ret = http._sanitize_url_components(mock_component_list, "api_key")
  83. self.assertEqual(ret, mock_ret)
  84. def test_sanitize_components_multiple_elements(self):
  85. """
  86. Tests two componenets to be sanitized.
  87. """
  88. mock_component_list = ["foo=bar", "foo=baz", "api_key=testing"]
  89. mock_ret = "foo=XXXXXXXXXX&foo=XXXXXXXXXX&api_key=testing&"
  90. ret = http._sanitize_url_components(mock_component_list, "foo")
  91. self.assertEqual(ret, mock_ret)
  92. @slowTest
  93. def test_query_null_response(self):
  94. """
  95. This tests that we get a null response when raise_error=False and the
  96. host/port cannot be reached.
  97. """
  98. host = "127.0.0.1"
  99. # Find unused port
  100. with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
  101. sock.bind((host, 0))
  102. port = sock.getsockname()[1]
  103. url = "http://{host}:{port}/".format(host=host, port=port)
  104. result = http.query(url, raise_error=False)
  105. assert result == {"body": None}, result
  106. def test_query_error_handling(self):
  107. ret = http.query("http://127.0.0.1:0")
  108. self.assertTrue(isinstance(ret, dict))
  109. self.assertTrue(isinstance(ret.get("error", None), str))
  110. ret = http.query("http://myfoobardomainthatnotexist")
  111. self.assertTrue(isinstance(ret, dict))
  112. self.assertTrue(isinstance(ret.get("error", None), str))
  113. def test_parse_cookie_header(self):
  114. header = "; ".join(
  115. [
  116. "foo=bar",
  117. "expires=Mon, 03-Aug-20 14:26:27 GMT",
  118. "path=/",
  119. "domain=.mydomain.tld",
  120. "HttpOnly",
  121. "SameSite=Lax",
  122. "Secure",
  123. ]
  124. )
  125. ret = http.parse_cookie_header(header)
  126. cookie = ret.pop(0)
  127. assert cookie.name == "foo", cookie.name
  128. assert cookie.value == "bar", cookie.value
  129. assert cookie.expires == 1596464787, cookie.expires
  130. assert cookie.path == "/", cookie.path
  131. assert cookie.domain == ".mydomain.tld", cookie.domain
  132. assert cookie.secure
  133. # Only one cookie should have been returned, if anything is left in the
  134. # parse_cookie_header return then something went wrong.
  135. assert not ret
  136. class HTTPPostTestCase(TestCase):
  137. """
  138. Unit TestCase for the salt.utils.http module when
  139. using POST method
  140. """
  141. @classmethod
  142. def setUpClass(cls):
  143. cls.post_webserver = Webserver(handler=MirrorPostHandler)
  144. cls.post_webserver.start()
  145. cls.post_web_root = cls.post_webserver.web_root
  146. @classmethod
  147. def tearDownClass(cls):
  148. cls.post_webserver.stop()
  149. del cls.post_webserver
  150. def test_requests_multipart_formdata_post(self):
  151. """
  152. Test handling of a multipart/form-data POST using the requests backend
  153. """
  154. match_this = '{0}\r\nContent-Disposition: form-data; name="fieldname_here"\r\n\r\nmydatahere\r\n{0}--\r\n'
  155. ret = http.query(
  156. self.post_web_root,
  157. method="POST",
  158. data="mydatahere",
  159. formdata=True,
  160. formdata_fieldname="fieldname_here",
  161. backend="requests",
  162. )
  163. body = ret.get("body", "")
  164. boundary = body[: body.find("\r")]
  165. self.assertEqual(body, match_this.format(boundary))
  166. class HTTPGetTestCase(TestCase):
  167. """
  168. Unit TestCase for the salt.utils.http module when
  169. using Get method
  170. """
  171. @classmethod
  172. def setUpClass(cls):
  173. cls.get_webserver = Webserver()
  174. cls.get_webserver.start()
  175. @classmethod
  176. def tearDownClass(cls):
  177. cls.get_webserver.stop()
  178. del cls.get_webserver
  179. def test_backends_decode_body_false(self):
  180. """
  181. test all backends when using
  182. decode_body=False that it returns
  183. bytes and does not try to decode
  184. """
  185. for backend in ["tornado", "requests", "urllib2"]:
  186. ret = http.query(
  187. self.get_webserver.url("custom.tar.gz"),
  188. backend=backend,
  189. decode_body=False,
  190. )
  191. body = ret.get("body", "")
  192. assert isinstance(body, bytes)
  193. def test_backends_decode_body_true(self):
  194. """
  195. test all backends when using
  196. decode_body=True that it returns
  197. string and decodes it.
  198. """
  199. for backend in ["tornado", "requests", "urllib2"]:
  200. ret = http.query(self.get_webserver.url("core.sls"), backend=backend)
  201. body = ret.get("body", "")
  202. assert isinstance(body, str)