py3: Stop using stdlib's putrequest(); it only does ASCII · openstack/swift@c0ae48b
@@ -111,6 +111,91 @@ def listing_items(method):
111111items = []
112112113113114+def putrequest(self, method, url, skip_host=False, skip_accept_encoding=False):
115+'''Send a request to the server.
116+117+ This is mostly a regurgitation of CPython's HTTPConnection.putrequest,
118+ but fixed up so we can still send arbitrary bytes in the request line
119+ on py3. See also: https://bugs.python.org/issue36274
120+121+ To use, swap out a HTTP(S)Connection's putrequest with something like::
122+123+ conn.putrequest = putrequest.__get__(conn)
124+125+ :param method: specifies an HTTP request method, e.g. 'GET'.
126+ :param url: specifies the object being requested, e.g. '/index.html'.
127+ :param skip_host: if True does not add automatically a 'Host:' header
128+ :param skip_accept_encoding: if True does not add automatically an
129+ 'Accept-Encoding:' header
130+ '''
131+# (Mostly) inline the HTTPConnection implementation; just fix it
132+# so we can send non-ascii request lines. For comparison, see
133+# https://github.com/python/cpython/blob/v2.7.16/Lib/httplib.py#L888-L1003
134+# and https://github.com/python/cpython/blob/v3.7.2/
135+# Lib/http/client.py#L1061-L1183
136+if self._HTTPConnection__response \
137+and self._HTTPConnection__response.isclosed():
138+self._HTTPConnection__response = None
139+140+if self._HTTPConnection__state == http_client._CS_IDLE:
141+self._HTTPConnection__state = http_client._CS_REQ_STARTED
142+else:
143+raise http_client.CannotSendRequest(self._HTTPConnection__state)
144+145+self._method = method
146+if not url:
147+url = '/'
148+self._path = url
149+request = '%s %s %s' % (method, url, self._http_vsn_str)
150+if not isinstance(request, bytes):
151+# This choice of encoding is the whole reason we copy/paste from
152+# cpython. When making backend requests, it should never be
153+# necessary; however, we have some functional tests that want
154+# to send non-ascii bytes.
155+# TODO: when https://bugs.python.org/issue36274 is resolved, make
156+# sure we fix up our API to match whatever upstream chooses to do
157+self._output(request.encode('latin1'))
158+else:
159+self._output(request)
160+161+if self._http_vsn == 11:
162+if not skip_host:
163+netloc = ''
164+if url.startswith('http'):
165+nil, netloc, nil, nil, nil = urllib.parse.urlsplit(url)
166+167+if netloc:
168+try:
169+netloc_enc = netloc.encode("ascii")
170+except UnicodeEncodeError:
171+netloc_enc = netloc.encode("idna")
172+self.putheader('Host', netloc_enc)
173+else:
174+if self._tunnel_host:
175+host = self._tunnel_host
176+port = self._tunnel_port
177+else:
178+host = self.host
179+port = self.port
180+181+try:
182+host_enc = host.encode("ascii")
183+except UnicodeEncodeError:
184+host_enc = host.encode("idna")
185+186+if host.find(':') >= 0:
187+host_enc = b'[' + host_enc + b']'
188+189+if port == self.default_port:
190+self.putheader('Host', host_enc)
191+else:
192+host_enc = host_enc.decode("ascii")
193+self.putheader('Host', "%s:%s" % (host_enc, port))
194+195+if not skip_accept_encoding:
196+self.putheader('Accept-Encoding', 'identity')
197+198+114199class Connection(object):
115200def __init__(self, config):
116201for key in 'auth_host auth_port auth_ssl username password'.split():
@@ -132,6 +217,7 @@ def __init__(self, config):
132217self.storage_netloc = None
133218self.storage_path = None
134219self.conn_class = None
220+self.connection = None # until you call .http_connect()
135221136222@property
137223def storage_url(self):
@@ -235,6 +321,7 @@ def http_connect(self):
235321context=ssl._create_unverified_context())
236322else:
237323self.connection = self.conn_class(self.storage_netloc)
324+self.connection.putrequest = putrequest.__get__(self.connection)
238325239326def make_path(self, path=None, cfg=None):
240327if path is None: