py3: Stop using stdlib's putrequest(); it only does ASCII · openstack/swift@c0ae48b

@@ -111,6 +111,91 @@ def listing_items(method):

111111

items = []

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+114199

class Connection(object):

115200

def __init__(self, config):

116201

for key in 'auth_host auth_port auth_ssl username password'.split():

@@ -132,6 +217,7 @@ def __init__(self, config):

132217

self.storage_netloc = None

133218

self.storage_path = None

134219

self.conn_class = None

220+

self.connection = None # until you call .http_connect()

135221136222

@property

137223

def storage_url(self):

@@ -235,6 +321,7 @@ def http_connect(self):

235321

context=ssl._create_unverified_context())

236322

else:

237323

self.connection = self.conn_class(self.storage_netloc)

324+

self.connection.putrequest = putrequest.__get__(self.connection)

238325239326

def make_path(self, path=None, cfg=None):

240327

if path is None: