12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+ import base64
15
16
import logging
17
+ from typing import Optional
16
18
19
+ import attr
17
20
from zope .interface import implementer
18
21
19
22
from twisted .internet import defer , protocol
20
23
from twisted .internet .error import ConnectError
21
24
from twisted .internet .interfaces import IReactorCore , IStreamClientEndpoint
22
25
from twisted .internet .protocol import ClientFactory , Protocol , connectionDone
23
26
from twisted .web import http
24
- from twisted .web .http_headers import Headers
25
27
26
28
logger = logging .getLogger (__name__ )
27
29
@@ -30,6 +32,22 @@ class ProxyConnectError(ConnectError):
30
32
pass
31
33
32
34
35
+ @attr .s
36
+ class ProxyCredentials :
37
+ username_password = attr .ib (type = bytes )
38
+
39
+ def as_proxy_authorization_value (self ) -> bytes :
40
+ """
41
+ Return the value for a Proxy-Authorization header (i.e. 'Basic abdef==').
42
+
43
+ Returns:
44
+ A transformation of the authentication string the encoded value for
45
+ a Proxy-Authorization header.
46
+ """
47
+ # Encode as base64 and prepend the authorization type
48
+ return b"Basic " + base64 .encodebytes (self .username_password )
49
+
50
+
33
51
@implementer (IStreamClientEndpoint )
34
52
class HTTPConnectProxyEndpoint :
35
53
"""An Endpoint implementation which will send a CONNECT request to an http proxy
@@ -46,7 +64,7 @@ class HTTPConnectProxyEndpoint:
46
64
proxy_endpoint: the endpoint to use to connect to the proxy
47
65
host: hostname that we want to CONNECT to
48
66
port: port that we want to connect to
49
- headers: Extra HTTP headers to include in the CONNECT request
67
+ proxy_creds: credentials to authenticate at proxy
50
68
"""
51
69
52
70
def __init__ (
@@ -55,20 +73,20 @@ def __init__(
55
73
proxy_endpoint : IStreamClientEndpoint ,
56
74
host : bytes ,
57
75
port : int ,
58
- headers : Headers ,
76
+ proxy_creds : Optional [ ProxyCredentials ] ,
59
77
):
60
78
self ._reactor = reactor
61
79
self ._proxy_endpoint = proxy_endpoint
62
80
self ._host = host
63
81
self ._port = port
64
- self ._headers = headers
82
+ self ._proxy_creds = proxy_creds
65
83
66
84
def __repr__ (self ):
67
85
return "<HTTPConnectProxyEndpoint %s>" % (self ._proxy_endpoint ,)
68
86
69
87
def connect (self , protocolFactory : ClientFactory ):
70
88
f = HTTPProxiedClientFactory (
71
- self ._host , self ._port , protocolFactory , self ._headers
89
+ self ._host , self ._port , protocolFactory , self ._proxy_creds
72
90
)
73
91
d = self ._proxy_endpoint .connect (f )
74
92
# once the tcp socket connects successfully, we need to wait for the
@@ -87,20 +105,20 @@ class HTTPProxiedClientFactory(protocol.ClientFactory):
87
105
dst_host: hostname that we want to CONNECT to
88
106
dst_port: port that we want to connect to
89
107
wrapped_factory: The original Factory
90
- headers: Extra HTTP headers to include in the CONNECT request
108
+ proxy_creds: credentials to authenticate at proxy
91
109
"""
92
110
93
111
def __init__ (
94
112
self ,
95
113
dst_host : bytes ,
96
114
dst_port : int ,
97
115
wrapped_factory : ClientFactory ,
98
- headers : Headers ,
116
+ proxy_creds : Optional [ ProxyCredentials ] ,
99
117
):
100
118
self .dst_host = dst_host
101
119
self .dst_port = dst_port
102
120
self .wrapped_factory = wrapped_factory
103
- self .headers = headers
121
+ self .proxy_creds = proxy_creds
104
122
self .on_connection = defer .Deferred ()
105
123
106
124
def startedConnecting (self , connector ):
@@ -114,7 +132,7 @@ def buildProtocol(self, addr):
114
132
self .dst_port ,
115
133
wrapped_protocol ,
116
134
self .on_connection ,
117
- self .headers ,
135
+ self .proxy_creds ,
118
136
)
119
137
120
138
def clientConnectionFailed (self , connector , reason ):
@@ -145,7 +163,7 @@ class HTTPConnectProtocol(protocol.Protocol):
145
163
connected_deferred: a Deferred which will be callbacked with
146
164
wrapped_protocol when the CONNECT completes
147
165
148
- headers: Extra HTTP headers to include in the CONNECT request
166
+ proxy_creds: credentials to authenticate at proxy
149
167
"""
150
168
151
169
def __init__ (
@@ -154,16 +172,16 @@ def __init__(
154
172
port : int ,
155
173
wrapped_protocol : Protocol ,
156
174
connected_deferred : defer .Deferred ,
157
- headers : Headers ,
175
+ proxy_creds : Optional [ ProxyCredentials ] ,
158
176
):
159
177
self .host = host
160
178
self .port = port
161
179
self .wrapped_protocol = wrapped_protocol
162
180
self .connected_deferred = connected_deferred
163
- self .headers = headers
181
+ self .proxy_creds = proxy_creds
164
182
165
183
self .http_setup_client = HTTPConnectSetupClient (
166
- self .host , self .port , self .headers
184
+ self .host , self .port , self .proxy_creds
167
185
)
168
186
self .http_setup_client .on_connected .addCallback (self .proxyConnected )
169
187
@@ -205,30 +223,38 @@ class HTTPConnectSetupClient(http.HTTPClient):
205
223
Args:
206
224
host: The hostname to send in the CONNECT message
207
225
port: The port to send in the CONNECT message
208
- headers: Extra headers to send with the CONNECT message
226
+ proxy_creds: credentials to authenticate at proxy
209
227
"""
210
228
211
- def __init__ (self , host : bytes , port : int , headers : Headers ):
229
+ def __init__ (
230
+ self ,
231
+ host : bytes ,
232
+ port : int ,
233
+ proxy_creds : Optional [ProxyCredentials ],
234
+ ):
212
235
self .host = host
213
236
self .port = port
214
- self .headers = headers
237
+ self .proxy_creds = proxy_creds
215
238
self .on_connected = defer .Deferred ()
216
239
217
240
def connectionMade (self ):
218
241
logger .debug ("Connected to proxy, sending CONNECT" )
219
242
self .sendCommand (b"CONNECT" , b"%s:%d" % (self .host , self .port ))
220
243
221
- # Send any additional specified headers
222
- for name , values in self .headers .getAllRawHeaders ():
223
- for value in values :
224
- self .sendHeader (name , value )
244
+ # Determine whether we need to set Proxy-Authorization headers
245
+ if self .proxy_creds :
246
+ # Set a Proxy-Authorization header
247
+ self .sendHeader (
248
+ b"Proxy-Authorization" ,
249
+ self .proxy_creds .as_proxy_authorization_value (),
250
+ )
225
251
226
252
self .endHeaders ()
227
253
228
254
def handleStatus (self , version : bytes , status : bytes , message : bytes ):
229
255
logger .debug ("Got Status: %s %s %s" , status , message , version )
230
256
if status != b"200" :
231
- raise ProxyConnectError ("Unexpected status on CONNECT: %s" % status )
257
+ raise ProxyConnectError (f "Unexpected status on CONNECT: { status !s } " )
232
258
233
259
def handleEndHeaders (self ):
234
260
logger .debug ("End Headers" )
0 commit comments