@@ -111,6 +111,91 @@ def listing_items(method):
111111 items = []
112112
113113
114+ 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 ):
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()
135221
136222 @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 )
238325
239326 def make_path (self , path = None , cfg = None ):
240327 if path is None :
0 commit comments