1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

from __future__ import absolute_import 

import re 

import datetime 

import logging 

import os 

import socket 

from socket import error as SocketError, timeout as SocketTimeout 

import warnings 

from .packages import six 

from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection 

from .packages.six.moves.http_client import HTTPException # noqa: F401 

 

try: # Compiled with SSL? 

import ssl 

 

BaseSSLError = ssl.SSLError 

except (ImportError, AttributeError): # Platform-specific: No SSL. 

ssl = None 

 

class BaseSSLError(BaseException): 

pass 

 

 

try: 

# Python 3: not a no-op, we're adding this to the namespace so it can be imported. 

ConnectionError = ConnectionError 

except NameError: 

# Python 2 

class ConnectionError(Exception): 

pass 

 

 

from .exceptions import ( 

NewConnectionError, 

ConnectTimeoutError, 

SubjectAltNameWarning, 

SystemTimeWarning, 

) 

from .packages.ssl_match_hostname import match_hostname, CertificateError 

 

from .util.ssl_ import ( 

resolve_cert_reqs, 

resolve_ssl_version, 

assert_fingerprint, 

create_urllib3_context, 

ssl_wrap_socket, 

) 

 

 

from .util import connection 

 

from ._collections import HTTPHeaderDict 

 

log = logging.getLogger(__name__) 

 

port_by_scheme = {"http": 80, "https": 443} 

 

# When it comes time to update this value as a part of regular maintenance 

# (ie test_recent_date is failing) update it to ~6 months before the current date. 

RECENT_DATE = datetime.date(2019, 1, 1) 

 

_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") 

 

 

class DummyConnection(object): 

"""Used to detect a failed ConnectionCls import.""" 

 

pass 

 

 

class HTTPConnection(_HTTPConnection, object): 

""" 

Based on httplib.HTTPConnection but provides an extra constructor 

backwards-compatibility layer between older and newer Pythons. 

 

Additional keyword parameters are used to configure attributes of the connection. 

Accepted parameters include: 

 

- ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` 

- ``source_address``: Set the source address for the current connection. 

- ``socket_options``: Set specific options on the underlying socket. If not specified, then 

defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling 

Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. 

 

For example, if you wish to enable TCP Keep Alive in addition to the defaults, 

you might pass:: 

 

HTTPConnection.default_socket_options + [ 

(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), 

] 

 

Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). 

""" 

 

default_port = port_by_scheme["http"] 

 

#: Disable Nagle's algorithm by default. 

#: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` 

default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] 

 

#: Whether this connection verifies the host's certificate. 

is_verified = False 

 

def __init__(self, *args, **kw): 

if not six.PY2: 

kw.pop("strict", None) 

 

# Pre-set source_address. 

self.source_address = kw.get("source_address") 

 

#: The socket options provided by the user. If no options are 

#: provided, we use the default options. 

self.socket_options = kw.pop("socket_options", self.default_socket_options) 

 

_HTTPConnection.__init__(self, *args, **kw) 

 

@property 

def host(self): 

""" 

Getter method to remove any trailing dots that indicate the hostname is an FQDN. 

 

In general, SSL certificates don't include the trailing dot indicating a 

fully-qualified domain name, and thus, they don't validate properly when 

checked against a domain name that includes the dot. In addition, some 

servers may not expect to receive the trailing dot when provided. 

 

However, the hostname with trailing dot is critical to DNS resolution; doing a 

lookup with the trailing dot will properly only resolve the appropriate FQDN, 

whereas a lookup without a trailing dot will search the system's search domain 

list. Thus, it's important to keep the original host around for use only in 

those cases where it's appropriate (i.e., when doing DNS lookup to establish the 

actual TCP connection across which we're going to send HTTP requests). 

""" 

return self._dns_host.rstrip(".") 

 

@host.setter 

def host(self, value): 

""" 

Setter for the `host` property. 

 

We assume that only urllib3 uses the _dns_host attribute; httplib itself 

only uses `host`, and it seems reasonable that other libraries follow suit. 

""" 

self._dns_host = value 

 

def _new_conn(self): 

""" Establish a socket connection and set nodelay settings on it. 

 

:return: New socket connection. 

""" 

extra_kw = {} 

if self.source_address: 

extra_kw["source_address"] = self.source_address 

 

if self.socket_options: 

extra_kw["socket_options"] = self.socket_options 

 

try: 

conn = connection.create_connection( 

(self._dns_host, self.port), self.timeout, **extra_kw 

) 

 

except SocketTimeout: 

raise ConnectTimeoutError( 

self, 

"Connection to %s timed out. (connect timeout=%s)" 

% (self.host, self.timeout), 

) 

 

except SocketError as e: 

raise NewConnectionError( 

self, "Failed to establish a new connection: %s" % e 

) 

 

return conn 

 

def _prepare_conn(self, conn): 

self.sock = conn 

# Google App Engine's httplib does not define _tunnel_host 

if getattr(self, "_tunnel_host", None): 

# TODO: Fix tunnel so it doesn't depend on self.sock state. 

self._tunnel() 

# Mark this connection as not reusable 

self.auto_open = 0 

 

def connect(self): 

conn = self._new_conn() 

self._prepare_conn(conn) 

 

def putrequest(self, method, url, *args, **kwargs): 

"""Send a request to the server""" 

match = _CONTAINS_CONTROL_CHAR_RE.search(method) 

if match: 

raise ValueError( 

"Method cannot contain non-token characters %r (found at least %r)" 

% (method, match.group()) 

) 

 

return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) 

 

def request_chunked(self, method, url, body=None, headers=None): 

""" 

Alternative to the common request method, which sends the 

body with chunked encoding and not as one block 

""" 

headers = HTTPHeaderDict(headers if headers is not None else {}) 

skip_accept_encoding = "accept-encoding" in headers 

skip_host = "host" in headers 

self.putrequest( 

method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host 

) 

for header, value in headers.items(): 

self.putheader(header, value) 

if "transfer-encoding" not in headers: 

self.putheader("Transfer-Encoding", "chunked") 

self.endheaders() 

 

if body is not None: 

stringish_types = six.string_types + (bytes,) 

if isinstance(body, stringish_types): 

body = (body,) 

for chunk in body: 

if not chunk: 

continue 

if not isinstance(chunk, bytes): 

chunk = chunk.encode("utf8") 

len_str = hex(len(chunk))[2:] 

to_send = bytearray(len_str.encode()) 

to_send += b"\r\n" 

to_send += chunk 

to_send += b"\r\n" 

self.send(to_send) 

 

# After the if clause, to always have a closed body 

self.send(b"0\r\n\r\n") 

 

 

class HTTPSConnection(HTTPConnection): 

default_port = port_by_scheme["https"] 

 

cert_reqs = None 

ca_certs = None 

ca_cert_dir = None 

ca_cert_data = None 

ssl_version = None 

assert_fingerprint = None 

 

def __init__( 

self, 

host, 

port=None, 

key_file=None, 

cert_file=None, 

key_password=None, 

strict=None, 

timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 

ssl_context=None, 

server_hostname=None, 

**kw 

): 

 

HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) 

 

self.key_file = key_file 

self.cert_file = cert_file 

self.key_password = key_password 

self.ssl_context = ssl_context 

self.server_hostname = server_hostname 

 

# Required property for Google AppEngine 1.9.0 which otherwise causes 

# HTTPS requests to go out as HTTP. (See Issue #356) 

self._protocol = "https" 

 

def set_cert( 

self, 

key_file=None, 

cert_file=None, 

cert_reqs=None, 

key_password=None, 

ca_certs=None, 

assert_hostname=None, 

assert_fingerprint=None, 

ca_cert_dir=None, 

ca_cert_data=None, 

): 

""" 

This method should only be called once, before the connection is used. 

""" 

# If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also 

# have an SSLContext object in which case we'll use its verify_mode. 

if cert_reqs is None: 

if self.ssl_context is not None: 

cert_reqs = self.ssl_context.verify_mode 

else: 

cert_reqs = resolve_cert_reqs(None) 

 

self.key_file = key_file 

self.cert_file = cert_file 

self.cert_reqs = cert_reqs 

self.key_password = key_password 

self.assert_hostname = assert_hostname 

self.assert_fingerprint = assert_fingerprint 

self.ca_certs = ca_certs and os.path.expanduser(ca_certs) 

self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) 

self.ca_cert_data = ca_cert_data 

 

def connect(self): 

# Add certificate verification 

conn = self._new_conn() 

hostname = self.host 

 

# Google App Engine's httplib does not define _tunnel_host 

if getattr(self, "_tunnel_host", None): 

self.sock = conn 

# Calls self._set_hostport(), so self.host is 

# self._tunnel_host below. 

self._tunnel() 

# Mark this connection as not reusable 

self.auto_open = 0 

 

# Override the host with the one we're requesting data from. 

hostname = self._tunnel_host 

 

server_hostname = hostname 

if self.server_hostname is not None: 

server_hostname = self.server_hostname 

 

is_time_off = datetime.date.today() < RECENT_DATE 

if is_time_off: 

warnings.warn( 

( 

"System time is way off (before {0}). This will probably " 

"lead to SSL verification errors" 

).format(RECENT_DATE), 

SystemTimeWarning, 

) 

 

# Wrap socket using verification with the root certs in 

# trusted_root_certs 

default_ssl_context = False 

if self.ssl_context is None: 

default_ssl_context = True 

self.ssl_context = create_urllib3_context( 

ssl_version=resolve_ssl_version(self.ssl_version), 

cert_reqs=resolve_cert_reqs(self.cert_reqs), 

) 

 

context = self.ssl_context 

context.verify_mode = resolve_cert_reqs(self.cert_reqs) 

 

# Try to load OS default certs if none are given. 

# Works well on Windows (requires Python3.4+) 

if ( 

not self.ca_certs 

and not self.ca_cert_dir 

and not self.ca_cert_data 

and default_ssl_context 

and hasattr(context, "load_default_certs") 

): 

context.load_default_certs() 

 

self.sock = ssl_wrap_socket( 

sock=conn, 

keyfile=self.key_file, 

certfile=self.cert_file, 

key_password=self.key_password, 

ca_certs=self.ca_certs, 

ca_cert_dir=self.ca_cert_dir, 

ca_cert_data=self.ca_cert_data, 

server_hostname=server_hostname, 

ssl_context=context, 

) 

 

if self.assert_fingerprint: 

assert_fingerprint( 

self.sock.getpeercert(binary_form=True), self.assert_fingerprint 

) 

elif ( 

context.verify_mode != ssl.CERT_NONE 

and not getattr(context, "check_hostname", False) 

and self.assert_hostname is not False 

): 

# While urllib3 attempts to always turn off hostname matching from 

# the TLS library, this cannot always be done. So we check whether 

# the TLS Library still thinks it's matching hostnames. 

cert = self.sock.getpeercert() 

if not cert.get("subjectAltName", ()): 

warnings.warn( 

( 

"Certificate for {0} has no `subjectAltName`, falling back to check for a " 

"`commonName` for now. This feature is being removed by major browsers and " 

"deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " 

"for details.)".format(hostname) 

), 

SubjectAltNameWarning, 

) 

_match_hostname(cert, self.assert_hostname or server_hostname) 

 

self.is_verified = ( 

context.verify_mode == ssl.CERT_REQUIRED 

or self.assert_fingerprint is not None 

) 

 

 

def _match_hostname(cert, asserted_hostname): 

try: 

match_hostname(cert, asserted_hostname) 

except CertificateError as e: 

log.warning( 

"Certificate did not match expected hostname: %s. Certificate: %s", 

asserted_hostname, 

cert, 

) 

# Add cert to exception and reraise so client code can inspect 

# the cert when catching the exception, if they want to 

e._peer_cert = cert 

raise 

 

 

if not ssl: 

HTTPSConnection = DummyConnection # noqa: F811 

 

 

VerifiedHTTPSConnection = HTTPSConnection