txhttputil 1.4.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,396 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ import logging
10
+ from binascii import hexlify
11
+ from typing import List, Set, Optional
12
+
13
+ import cryptography
14
+
15
+ import OpenSSL
16
+ from OpenSSL import crypto
17
+ from OpenSSL import SSL
18
+ from OpenSSL.crypto import FILETYPE_PEM
19
+ from twisted.internet._sslverify import (
20
+ PrivateCertificate,
21
+ KeyPair,
22
+ Certificate,
23
+ _setAcceptableProtocols,
24
+ ClientTLSOptions,
25
+ OpenSSLCertificateOptions,
26
+ IOpenSSLTrustRoot,
27
+ )
28
+ from twisted.internet.interfaces import IOpenSSLContextFactory
29
+ from twisted.internet.ssl import CertificateOptions, TLSVersion
30
+ from twisted.python.randbytes import secureRandom
31
+
32
+ from twisted.web.client import _requireSSL
33
+ from twisted.internet._sslverify import Certificate as TxCertificate
34
+ from twisted.web.iweb import IPolicyForHTTPS
35
+ from zope.interface import implementer
36
+
37
+ from txhttputil.util.PemUtil import (
38
+ parseTrustRootFromBundle,
39
+ parsePemBundleForClient,
40
+ PrivateKeyWithFullChain,
41
+ parsePemBundleForTrustedPeers,
42
+ )
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+
47
+ @implementer(IOpenSSLContextFactory)
48
+ class _CertificateOptions(CertificateOptions):
49
+ trustedPeers: List[Certificate] = None
50
+ trustedPeersASN1Bytes: Set[bytes] = None
51
+
52
+ def _makeContext(self):
53
+ ctx = self._contextFactory(self.method)
54
+ ctx.set_options(self._options)
55
+ ctx.set_mode(self._mode)
56
+
57
+ if self.certificate is not None and self.privateKey is not None:
58
+ ctx.use_certificate(self.certificate)
59
+ ctx.use_privatekey(self.privateKey)
60
+ for extraCert in self.extraCertChain:
61
+ ctx.add_extra_chain_cert(extraCert)
62
+ # Sanity check
63
+ ctx.check_privatekey()
64
+
65
+ verifyFlags = SSL.VERIFY_NONE
66
+ if self.verify:
67
+ verifyFlags = SSL.VERIFY_PEER
68
+ if self.requireCertificate:
69
+ verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
70
+ if self.verifyOnce:
71
+ verifyFlags |= SSL.VERIFY_CLIENT_ONCE
72
+ self.trustRoot._addCACertsToContext(ctx)
73
+
74
+ def verifyCallback(
75
+ conn: SSL.Connection,
76
+ cert: crypto.X509,
77
+ errno: int,
78
+ depth: int,
79
+ preverify_ok: int,
80
+ ):
81
+ # https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_CTX_verify_cb.html
82
+ # The ok parameter to the callback indicates the value
83
+ # the callback should return to retain the default behaviour.
84
+ # If it is zero then an error condition is indicated.
85
+ # If it is 1 then no error occurred.
86
+ # If the flag X509_V_FLAG_NOTIFY_POLICY is set
87
+ # then ok is set to 2 to indicate the policy checking is complete.
88
+
89
+ FAIL = 0
90
+
91
+ # only check peer's cert - not other certs in trust chain
92
+ # e.g. cert -> intermediate A -> intermediate B -> root CA
93
+ # depth 0 1 2 3
94
+ if depth != 0:
95
+ # pass through the result
96
+ return preverify_ok
97
+
98
+ # if peer verify is disabled
99
+ if self.trustedPeersASN1Bytes is None:
100
+ # pass through the result
101
+ return preverify_ok
102
+
103
+ # if verify failed
104
+ if preverify_ok == FAIL:
105
+ # pass through the result
106
+ return preverify_ok
107
+
108
+ # get PEM from public key
109
+ sessionPeerPublickeyBytes = crypto.dump_publickey(
110
+ crypto.FILETYPE_ASN1, cert.get_pubkey()
111
+ )
112
+
113
+ # check if peer is in trust list
114
+ if sessionPeerPublickeyBytes not in self.trustedPeersASN1Bytes:
115
+ logger.error(
116
+ f"mTLS peer verify failed at depth '{depth}', presenting "
117
+ f"certificate '{cert.get_subject()}'"
118
+ )
119
+ return FAIL
120
+
121
+ # peer verified, pass through the result
122
+ logger.debug(
123
+ f"mTLS peer verify success at depth '{depth}', presenting "
124
+ f"certificate '{cert.get_subject()}'"
125
+ )
126
+ return preverify_ok
127
+
128
+ ctx.set_verify(verifyFlags, verifyCallback)
129
+ if self.verifyDepth is not None:
130
+ ctx.set_verify_depth(self.verifyDepth)
131
+
132
+ # Until we know what's going on with
133
+ # https://twistedmatrix.com/trac/ticket/9764 let's be conservative
134
+ # in naming this; ASCII-only, short, as the recommended value (a
135
+ # hostname) might be:
136
+ sessionIDContext = hexlify(secureRandom(7))
137
+ # Note that this doesn't actually set the session ID (which had
138
+ # better be per-connection anyway!):
139
+ # https://github.com/pyca/pyopenssl/issues/845
140
+
141
+ # This is set unconditionally because it's apparently required for
142
+ # client certificates to work:
143
+ # https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_session_id_context.html
144
+ ctx.set_session_id(sessionIDContext)
145
+
146
+ if self.enableSessions:
147
+ ctx.set_session_cache_mode(SSL.SESS_CACHE_SERVER)
148
+ else:
149
+ ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF)
150
+
151
+ if self.dhParameters:
152
+ ctx.load_tmp_dh(self.dhParameters._dhFile.path)
153
+ ctx.set_cipher_list(self._cipherString.encode("ascii"))
154
+
155
+ self._ecChooser.configureECDHCurve(ctx)
156
+
157
+ if self._acceptableProtocols:
158
+ # Try to set NPN and ALPN. _acceptableProtocols cannot be set by
159
+ # the constructor unless at least one mechanism is supported.
160
+ _setAcceptableProtocols(ctx, self._acceptableProtocols)
161
+
162
+ return ctx
163
+
164
+
165
+ @implementer(IOpenSSLTrustRoot)
166
+ class _OpenSSLCertificateAuthorities:
167
+ """
168
+ Trust an explicitly specified set of certificates, represented by a list of
169
+ L{OpenSSL.crypto.X509} objects.
170
+ """
171
+
172
+ def __init__(self, caCerts: List[TxCertificate]):
173
+ """
174
+ @param caCerts: The certificate authorities to trust when using this
175
+ object as a C{trustRoot} for L{OpenSSLCertificateOptions}.
176
+ @type caCerts: L{list} of L{OpenSSL.crypto.X509}
177
+ """
178
+ self._caCerts = caCerts
179
+
180
+ def _addCACertsToContext(self, context):
181
+ store = context.get_cert_store()
182
+ for cert in self._caCerts:
183
+ # convert twisted Certificate objects
184
+ # to OpenSSL.crypto.X509 objects
185
+ # via `cryptography` package
186
+ opensslCert = OpenSSL.crypto.X509.from_cryptography(
187
+ cryptography.x509.load_pem_x509_certificate(cert.dumpPEM())
188
+ )
189
+ store.add_cert(opensslCert)
190
+
191
+
192
+ def buildCertificateOptionsForTwisted(
193
+ privateKeyWithFullChain: PrivateKeyWithFullChain,
194
+ trustRoot=None,
195
+ trustedPeerCertificates: List[Certificate] = None,
196
+ raiseMinimumTo=TLSVersion.TLSv1_2,
197
+ lowerMaximumSecurityTo=TLSVersion.TLSv1_3,
198
+ acceptableProtocols=None,
199
+ dhParameters=None,
200
+ ) -> CertificateOptions:
201
+ twistedLoadedChain = PrivateKeyWithFullChain(
202
+ privateKey=KeyPair.load(
203
+ privateKeyWithFullChain.privateKey.as_bytes(), FILETYPE_PEM
204
+ ).original,
205
+ certificate=Certificate.loadPEM(
206
+ privateKeyWithFullChain.certificate.as_bytes()
207
+ ).original,
208
+ intermediateCAs=[
209
+ Certificate.loadPEM(intermediateCA.as_bytes()).original
210
+ for intermediateCA in privateKeyWithFullChain.intermediateCAs
211
+ ],
212
+ rootCA=Certificate.loadPEM(
213
+ privateKeyWithFullChain.rootCA.as_bytes()
214
+ ).original,
215
+ )
216
+
217
+ if acceptableProtocols is None:
218
+ acceptableProtocols = [b"http/1.1"]
219
+
220
+ if trustedPeerCertificates is not None:
221
+ # load PEMs to ASN1 bytes
222
+ _CertificateOptions.trustedPeers = trustedPeerCertificates
223
+ _CertificateOptions.trustedPeersASN1Bytes = set([])
224
+ for trustedPeer in _CertificateOptions.trustedPeers:
225
+ # load as a pyopenssl cert
226
+ certificate: crypto.X509 = crypto.load_certificate(
227
+ crypto.FILETYPE_PEM, trustedPeer.as_bytes()
228
+ )
229
+ # get publickey in ASN1 bytes
230
+ publicKey: bytes = crypto.dump_publickey(
231
+ crypto.FILETYPE_ASN1, certificate.get_pubkey()
232
+ )
233
+ # add publickey to trusted peers
234
+ _CertificateOptions.trustedPeersASN1Bytes.add(publicKey)
235
+
236
+ return _CertificateOptions(
237
+ privateKey=twistedLoadedChain.privateKey,
238
+ certificate=twistedLoadedChain.certificate,
239
+ extraCertChain=twistedLoadedChain.intermediateCAs,
240
+ trustRoot=trustRoot,
241
+ raiseMinimumTo=raiseMinimumTo,
242
+ lowerMaximumSecurityTo=lowerMaximumSecurityTo,
243
+ acceptableProtocols=acceptableProtocols,
244
+ dhParameters=dhParameters,
245
+ )
246
+
247
+
248
+ @implementer(IPolicyForHTTPS)
249
+ class MutualAuthenticationPolicyForHTTPS:
250
+ def __init__(
251
+ self,
252
+ clientCertificate: PrivateCertificate = None,
253
+ trustRoot=None,
254
+ trustedPeers=None,
255
+ ):
256
+ self._extraCertificateOptions = {}
257
+
258
+ if clientCertificate is not None:
259
+ self._clientCertificate = clientCertificate
260
+ self._extraCertificateOptions.update(
261
+ privateKey=clientCertificate.privateKey.original,
262
+ certificate=clientCertificate.original,
263
+ )
264
+ self._trustRoot = trustRoot
265
+ self._trustedPeers = trustedPeers
266
+ self._trustedPeersASN1Bytes = None
267
+
268
+ if trustedPeers is not None:
269
+ self._trustedPeersASN1Bytes = set([])
270
+ for trustedPeer in self._trustedPeers:
271
+ # load as a pyopenssl cert
272
+ certificate: crypto.X509 = crypto.load_certificate(
273
+ crypto.FILETYPE_PEM, trustedPeer.as_bytes()
274
+ )
275
+ # get publickey in ASN1 bytes
276
+ publicKey: bytes = crypto.dump_publickey(
277
+ crypto.FILETYPE_ASN1, certificate.get_pubkey()
278
+ )
279
+ # add publickey to trusted peers
280
+ self._trustedPeersASN1Bytes.add(publicKey)
281
+
282
+ # verify enabled
283
+ self._verifyFlags = SSL.VERIFY_PEER
284
+ # verifyOnce:
285
+ self._verifyFlags |= SSL.VERIFY_CLIENT_ONCE
286
+ if self._trustRoot or self._trustedPeers:
287
+ # verify mTLS peer
288
+ self._verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
289
+
290
+ @_requireSSL
291
+ def creatorForNetloc(self, hostname, port):
292
+ certificateOptions = OpenSSLCertificateOptions(
293
+ trustRoot=self._trustRoot,
294
+ acceptableProtocols=[b"http/1.1"],
295
+ raiseMinimumTo=TLSVersion.TLSv1_2,
296
+ lowerMaximumSecurityTo=TLSVersion.TLSv1_3,
297
+ **self._extraCertificateOptions,
298
+ )
299
+ context = certificateOptions.getContext()
300
+
301
+ def verifyCallback(
302
+ conn: SSL.Connection,
303
+ cert: crypto.X509,
304
+ errno: int,
305
+ depth: int,
306
+ preverify_ok: int,
307
+ ):
308
+ # same as `_CertificateOptions._makeContext.verifyCallback`
309
+
310
+ # https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_CTX_verify_cb.html
311
+ # The ok parameter to the callback indicates the value
312
+ # the callback should return to retain the default behaviour.
313
+ # If it is zero then an error condition is indicated.
314
+ # If it is 1 then no error occurred.
315
+ # If the flag X509_V_FLAG_NOTIFY_POLICY is set
316
+ # then ok is set to 2 to indicate the policy checking is complete.
317
+
318
+ FAIL = 0
319
+
320
+ # only check peer's cert - not other certs in trust chain
321
+ # e.g. cert -> intermediate A -> intermediate B -> root CA
322
+ # depth 0 1 2 3
323
+ if depth != 0:
324
+ # pass through the result
325
+ return preverify_ok
326
+
327
+ # if peer verify is disabled
328
+ if self._trustedPeersASN1Bytes is None:
329
+ # pass through the result
330
+ return preverify_ok
331
+
332
+ # if verify failed
333
+ if preverify_ok == FAIL:
334
+ # pass through the result
335
+ return preverify_ok
336
+
337
+ # get PEM from public key
338
+ sessionPeerPublickeyBytes = crypto.dump_publickey(
339
+ crypto.FILETYPE_ASN1, cert.get_pubkey()
340
+ )
341
+
342
+ # check if peer is in trust list
343
+ if sessionPeerPublickeyBytes not in self._trustedPeersASN1Bytes:
344
+ logger.error(
345
+ f"mTLS peer verify failed at depth '{depth}', presenting "
346
+ f"certificate '{cert.get_subject()}'"
347
+ )
348
+ return FAIL
349
+
350
+ # peer verified, pass through the result
351
+ logger.debug(
352
+ f"mTLS peer verify success at depth '{depth}', presenting "
353
+ f"certificate '{cert.get_subject()}'"
354
+ )
355
+ return preverify_ok
356
+
357
+ context.set_verify(self._verifyFlags, verifyCallback)
358
+ return ClientTLSOptions(hostname, context)
359
+
360
+
361
+ def buildSSLContextFactoryForMutualTLS(
362
+ sslClientCertificateBundleFilePath: Optional[str],
363
+ sslTrustedPeerCertificateAuthorityBundleFilePath: Optional[str],
364
+ sslMutualTLSTrustedPeerCertificateBundleFilePath: Optional[str],
365
+ ) -> MutualAuthenticationPolicyForHTTPS:
366
+ trustRoot = None
367
+ if sslTrustedPeerCertificateAuthorityBundleFilePath:
368
+ rootCAsAndIntermediateCAs = parseTrustRootFromBundle(
369
+ sslTrustedPeerCertificateAuthorityBundleFilePath
370
+ )
371
+ trustRoot = _OpenSSLCertificateAuthorities(
372
+ caCerts=rootCAsAndIntermediateCAs
373
+ )
374
+ clientCertificate = None
375
+ if sslClientCertificateBundleFilePath is not None:
376
+ bank: PrivateKeyWithFullChain = parsePemBundleForClient(
377
+ sslClientCertificateBundleFilePath
378
+ )
379
+ clientKeyCertificateBytes: bytes = bank.privateKey.as_bytes()
380
+ clientKeyCertificateBytes += b"\n"
381
+ clientKeyCertificateBytes += bank.certificate.as_bytes()
382
+ clientCertificate = PrivateCertificate.loadPEM(
383
+ clientKeyCertificateBytes
384
+ )
385
+
386
+ trustedPeerCertificates = None
387
+ if sslMutualTLSTrustedPeerCertificateBundleFilePath is not None:
388
+ trustedPeerCertificates = parsePemBundleForTrustedPeers(
389
+ sslMutualTLSTrustedPeerCertificateBundleFilePath
390
+ )
391
+
392
+ return MutualAuthenticationPolicyForHTTPS(
393
+ clientCertificate=clientCertificate,
394
+ trustRoot=trustRoot,
395
+ trustedPeers=trustedPeerCertificates,
396
+ )
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016-2026 Flying Ion Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: txhttputil
3
+ Version: 1.4.9
4
+ Summary: Flying Ion utility classes for serving a static site with twisted.web with user permissions.
5
+ Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2016-2026 Flying Ion Pty Ltd
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+
29
+ Project-URL: Homepage, https://flyingion.com
30
+ Project-URL: Repository, https://gitlab.com/flyingion/txhttputil
31
+ Project-URL: Issues, https://gitlab.com/flyingion/txhttputil/-/issues
32
+ Keywords: twisted,resource,file,download,flying-ion
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3.9
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Requires-Python: >=3.9
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: pytz
42
+ Requires-Dist: txwebsocket>=1.0.1
43
+ Requires-Dist: filetype
44
+ Requires-Dist: pem
45
+ Requires-Dist: pytmpdir
46
+ Requires-Dist: treelib>=1.6.4
47
+ Requires-Dist: cryptography
48
+ Requires-Dist: pyOpenSSL
49
+ Provides-Extra: dev
50
+ Requires-Dist: mypy>=1.0; extra == "dev"
51
+ Requires-Dist: ruff>=0.15; extra == "dev"
52
+ Requires-Dist: vulture>=2.14; extra == "dev"
53
+ Requires-Dist: basedpyright>=1.20; extra == "dev"
54
+
55
+ # TxHttpUtil
56
+ Flying Ion utility classes for serving a static site with twisted.web with user permissions.
57
+
58
+ Whats of interest?
59
+
60
+ * Protected resource with simple form login - Not suitable for public networks at this
61
+ stage
62
+ * A resource that underlays a multi level file system search under a standard twisted
63
+ Resource.putChild type resource tree.
64
+ * Twisted HTTP File Downloader
65
+ * Consistent request data, providing a Bytes IO like oboject and switching to a
66
+ NamedTemporaryFile if fileno, or name is called, or the data exceeds 5mb.
67
+ * Support for single page applications
68
+
69
+ # TODO
70
+
71
+ Unit tests for :
72
+ * Creating resource trees
73
+ * Getting resources from the resource tree
74
+ * Getting files from an underlay resource with two underlays
75
+ * Downloading files with the
76
+ * Logging into the test site (Using Seleneium/Chrome WebDriver)
77
+ * Test uploading data under 5mb and over 5mb to and auto switching to NamedTemporaryFile
78
+ from BytesIO
79
+
80
+ ## License
81
+
82
+ Copyright (c) 2016-2026 [Flying Ion Pty Ltd](https://flyingion.com). Released under the [MIT License](LICENSE).
@@ -0,0 +1,34 @@
1
+ txhttputil/__init__.py,sha256=Y63nztT4icW-9PSSAFU6rEdaE25EK-ljXNMrWpRe01k,238
2
+ txhttputil/downloader/HttpFileDownloader.py,sha256=ugIg78QxOfMj3dPmxKdIoILX3vw35llCdvQ0H38TMQc,2867
3
+ txhttputil/downloader/HttpResourceProxy.py,sha256=XXFyTtcd9_aW0BdDxwTQVy-I73Moc0YYVFHUMW2woUQ,5033
4
+ txhttputil/downloader/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
5
+ txhttputil/login_page/LoginElement.py,sha256=N7veqpubgNpkg4p_FolAoiqb-0HZnttqrZmREDr9OXs,1074
6
+ txhttputil/login_page/LoginTemplate.xml,sha256=9D47ny5p_zNlr69jgWcmuQZXRrtix7fCaIgoWURQpVw,71705
7
+ txhttputil/login_page/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
8
+ txhttputil/site/AuthCredentials.py,sha256=3dl8NB_VIATMs3Yh1nLdAubj3G_ABKLBffledeDfZMI,880
9
+ txhttputil/site/AuthResource.py,sha256=ZOhAHInud4lnZ_3dIX4l33Ier2w76TmP0TWjxtMGYBI,1581
10
+ txhttputil/site/AuthSessionWrapper.py,sha256=apa46xcgTPQvo2g0-UtucKOjCxmlp05MCYL-5zf8c54,9035
11
+ txhttputil/site/AuthUserDetails.py,sha256=_DyZQ8FYoGPSlxE5LLgezM4thSJSmJUG_iKcSxI_U-o,1303
12
+ txhttputil/site/BasicResource.py,sha256=E8ffgKu_o08JA0nm1JHn3q41hslkDg3y__9zERioX7w,5083
13
+ txhttputil/site/BasicResourceTest.py,sha256=VQLYrlK-ZpEKK9DrfH4q9mqwbWfwxEf-18H7p5gKnFE,3414
14
+ txhttputil/site/FileUnderlayResource.py,sha256=ls9TQsIKxdcWs3ZV6Pr010o1JYfNUNe-4by-pCNV-Jw,5890
15
+ txhttputil/site/FileUnderlayResourceTest.py,sha256=EbdaZLPw702uW4sn2s8U-hz6rYrr_WTz5BRrxoNSIAE,3348
16
+ txhttputil/site/FileUploadRequest.py,sha256=oVs5kH3qaT_hBKfuX3dBRj7Y3Fe6X9_RRSgwoWYbaZE,9237
17
+ txhttputil/site/RedirectToHttpsResource.py,sha256=b1RgAS5qt3lfaiM1dt61InGbJG14jAbWJlQ2AwyGcrY,1301
18
+ txhttputil/site/RedirectionRule.py,sha256=Emv8xu63kp7arqmV47QgBrWWZxEM_Xw42SEN--oou7Y,736
19
+ txhttputil/site/ResourceCreator.py,sha256=ikkustGexoQ03mI0sYCCllpMlPt8cHFpv1PvhQF22kQ,270
20
+ txhttputil/site/RootResource.py,sha256=DrdOlsSM6wq7-B-thVzB6IVl1bpJx6UIpJ7hoESZT4g,912
21
+ txhttputil/site/RootSiteTest.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
22
+ txhttputil/site/SiteUtil.py,sha256=kkuC_uY_6dYLQnX5z5XXmUySB0MmXN8ABektI7Nxogk,5144
23
+ txhttputil/site/StaticFileResource.py,sha256=zGcf7F_1UP89Q2pO6BMKtZ2Sk89w5iig1Ymbg72zam8,5207
24
+ txhttputil/site/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
25
+ txhttputil/util/DeferUtil.py,sha256=RSVpZUTsvaxWOZll4fR5kyx_rZ4SaN4PpIirKwNIZOk,1318
26
+ txhttputil/util/ModuleUtil.py,sha256=RijQzsS7Bo_m-8s-3zMq-lYnrpUE7TDMmyvJO65Jm-8,659
27
+ txhttputil/util/PemUtil.py,sha256=lyz-NExGHRVQd4KsC51DIU92JTREzOd4wjg2W7ki6eQ,25050
28
+ txhttputil/util/SslUtil.py,sha256=0QTK5WUOCIO3FHGs5woyLHDFmngSKdJ0ui5u_GozWD0,14688
29
+ txhttputil/util/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
30
+ txhttputil-1.4.9.dist-info/LICENSE,sha256=ai2ZpUtxh0uJtP82roTLJogpfhiGVcjNpMEjQ0ZvxRQ,1081
31
+ txhttputil-1.4.9.dist-info/METADATA,sha256=viQe-aVUurKwBzRZ-2M7v31FPUSJG_RHqbq9WxQfrmU,3499
32
+ txhttputil-1.4.9.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
33
+ txhttputil-1.4.9.dist-info/top_level.txt,sha256=qCwwcG18r9jJUyy8StOVOET-ICSDsvdhhzW3Ue01GO0,11
34
+ txhttputil-1.4.9.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.47.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ txhttputil