Skip to content

Commit a5bae30

Browse files
committed
Python: Add tests of http.client.HTTPResponse
1 parent cf2ee06 commit a5bae30

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import sys
2+
import ssl
3+
4+
PY2 = sys.version_info[0] == 2
5+
PY3 = sys.version_info[0] == 3
6+
7+
if PY2:
8+
from httplib import HTTPConnection, HTTPSConnection
9+
if PY3:
10+
from http.client import HTTPConnection, HTTPSConnection
11+
12+
13+
# NOTE: the URL may be relative to host, or may be full URL.
14+
conn = HTTPConnection("example.com") # $ MISSING: clientRequestUrl="example.com"
15+
conn.request("GET", "/") # $ MISSING: clientRequestUrl="/"
16+
conn.request("GET", "http://example.com/") # $ MISSING: clientRequestUrl="http://example.com/"
17+
18+
# kwargs
19+
conn = HTTPConnection(host="example.com") # $ MISSING: clientRequestUrl="example.com"
20+
conn.request(method="GET", url="/") # $ MISSING: clientRequestUrl="/"
21+
22+
# using internal method... you shouldn't but you can
23+
conn._send_request("GET", "url", body=None, headers={}, encode_chunked=False) # $ MISSING: clientRequestUrl="url"
24+
25+
# low level sending of request
26+
conn.putrequest("GET", "url") # $ MISSING: clientRequestUrl="url"
27+
conn.putheader("X-Foo", "value")
28+
conn.endheaders(message_body=None)
29+
30+
# HTTPS
31+
conn = HTTPSConnection("host") # $ MISSING: clientRequestUrl="host"
32+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
33+
34+
# six aliases
35+
import six
36+
37+
conn = six.moves.http_client.HTTPConnection("host") # $ MISSING: clientRequestUrl="host"
38+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
39+
40+
conn = six.moves.http_client.HTTPSConnection("host") # $ MISSING: clientRequestUrl="host"
41+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
42+
43+
# ==============================================================================
44+
# Certificate validation disabled
45+
# ==============================================================================
46+
47+
# default SSL context is the one given by `_create_default_https_context`
48+
context = ssl._create_default_https_context()
49+
assert context.check_hostname == True
50+
assert context.verify_mode == ssl.CERT_REQUIRED
51+
52+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
53+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
54+
55+
# `_create_default_https_context` is currently just an alias for `create_default_context`
56+
# which creates a context for SERVER_AUTH purpose.
57+
context = ssl.create_default_context()
58+
assert context.check_hostname == True
59+
assert context.verify_mode == ssl.CERT_REQUIRED
60+
61+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
62+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
63+
64+
# however, if you supply your own SSLContext, you need to set it manually
65+
context = ssl.SSLContext()
66+
assert context.check_hostname == False
67+
assert context.verify_mode == ssl.CERT_NONE
68+
69+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
70+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
71+
72+
# and if you misunderstood whether to use server/client in the purpose, you will also
73+
# get a context without hostname verification.
74+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
75+
assert context.check_hostname == False
76+
assert context.verify_mode == ssl.CERT_NONE
77+
78+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
79+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
80+
81+
# NOTICE that current documentation says
82+
#
83+
# > Enabling hostname checking automatically sets verify_mode from CERT_NONE to
84+
# > CERT_REQUIRED. It cannot be set back to CERT_NONE as long as hostname checking is
85+
# > enabled.
86+
# - https://docs.python.org/3.10/library/ssl.html#ssl.SSLContext.check_hostname
87+
88+
context = ssl.SSLContext()
89+
context.check_hostname = True
90+
assert context.verify_mode == ssl.CERT_REQUIRED
91+
92+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
93+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url"
94+
95+
# only setting verify_mode is not enough, since check_hostname is not enabled
96+
97+
context = ssl.SSLContext()
98+
context.verify_mode = ssl.CERT_REQUIRED
99+
assert context.check_hostname == False
100+
101+
conn = HTTPSConnection("host", context=context) # $ MISSING: clientRequestUrl="host"
102+
conn.request("GET", "url") # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
103+
104+
# ==============================================================================
105+
# taint test
106+
# ==============================================================================
107+
108+
from flask import request
109+
110+
def taint_test():
111+
host = request.args['host']
112+
url = request.args['url']
113+
114+
conn = HTTPConnection(host) # $ MISSING: clientRequestUrl=host
115+
conn.request("GET", url) # $ MISSING: clientRequestUrl=url
116+
117+
resp = conn.getresponse()
118+
119+
ensure_tainted(
120+
# see
121+
# https://docs.python.org/3.10/library/http.client.html#httpresponse-objects
122+
# https://docs.python.org/3/library/http.client.html#http.client.HTTPResponse
123+
124+
# a HTTPResponse itself is file-like
125+
resp, # $ MISSING: tainted
126+
resp.read(), # $ MISSING: tainted
127+
128+
resp.getheader("name"), # $ MISSING: tainted
129+
resp.getheaders(), # $ MISSING: tainted
130+
131+
# http.client.HTTPMessage
132+
resp.headers, # $ MISSING: tainted
133+
resp.headers.get_all(), # $ MISSING: tainted
134+
135+
# Alias for .headers
136+
# http.client.HTTPMessage
137+
resp.msg, # $ MISSING: tainted
138+
resp.msg.get_all(), # $ MISSING: tainted
139+
140+
# Alias for .headers
141+
resp.info(), # $ MISSING: tainted
142+
resp.info().get_all(), # $ MISSING: tainted
143+
144+
# although this would usually be the textual version of the status
145+
# ("OK" for 200), it is possible to put your own evil data in here.
146+
resp.reason, # $ MISSING: tainted
147+
148+
# the URL of the recourse that was visited, if redirects were followed.
149+
# I don't see any reason this could not contain evil data.
150+
resp.url, # $ MISSING: tainted
151+
resp.geturl(), # $ MISSING: tainted
152+
)
153+
154+
ensure_not_tainted(
155+
resp.status,
156+
resp.code,
157+
resp.getcode(),
158+
)

0 commit comments

Comments
 (0)