big refactor for privoxy

master
Michele Guerini Rocco 2019-05-07 12:06:21 +02:00
parent da06c097fb
commit ef69565f31
Signed by: rnhmjoj
GPG Key ID: 91BE884FBA4B591A
13 changed files with 497 additions and 6091 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

52
CA.crt
View File

@ -1,52 +0,0 @@
-----BEGIN CERTIFICATE-----
MIID+DCCAuCgAwIBAgIBADANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEX
MBUGA1UECgwOUHJveEhUVFBTUHJveHkxEjAQBgNVBAsMCXB5T3BlblNTTDEaMBgG
A1UEAwwRUHJveEhUVFBTUHJveHkgQ0EwHhcNMTUxMDAxMDc0MDI1WhcNMjUwOTMw
MDc0MDI1WjBWMQswCQYDVQQGEwJDTjEXMBUGA1UECgwOUHJveEhUVFBTUHJveHkx
EjAQBgNVBAsMCXB5T3BlblNTTDEaMBgGA1UEAwwRUHJveEhUVFBTUHJveHkgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9mQTpJlPDjEqLnovcT/AL
YwjoP2Siowor2yeEKaGKJjBamu3OkYhS+2kzJhcii705uTCal/f6gDIlnhYXlPEh
L7Z0wsT9IePJSU9+yNtUrWYILfRg1+XkpZVqrPfjBk8usTjtC4kG9xRZno/TeZj/
2Qror/C989Hl+bqZ4p31/l1Jcml/W01PDiGcqESS15bKk24azJ1w69Zhjwn8uZKc
Mnq2myrJsl8fZ82gV2fV8yydhpDudPpHy8y/9U8FfsmODi75aH4A1NkK/2FZyBKE
1OEYd+JfL7QmBCCjIt9AREXA/77HSuj6OXoKWZ0AVuiHLA/psfcRL4+QXd1UtXbF
AgMBAAGjgdAwgc0wDwYDVR0TAQH/BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAgQw
ewYDVR0lAQH/BHEwbwYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDBAYIKwYB
BQUHAwgGCisGAQQBgjcCARUGCisGAQQBgjcCARYGCisGAQQBgjcKAwEGCisGAQQB
gjcKAwMGCisGAQQBgjcKAwQGCWCGSAGG+EIEATALBgNVHQ8EBAMCAQYwHQYDVR0O
BBYEFKPBao+B+YH0tMNHNGoLv/3ncZyvMA0GCSqGSIb3DQEBCwUAA4IBAQCFZOPd
SldrKkekP/tO/WnGgXEus8z4Ei7TEAm6qkSJ/r0ZaTKmGek370xvVG4myl0Hngr+
F6blIUzGi8e9mp/2vONhPYKTAg+Y4h5tKz9S6SyvbypBMa4YNZw8DNfd4uVLL/b6
psQcYfMPMpRdM7GlLZbxY9AHyCaHZszc3bSBM/lIhLWJH0pR7QSZZ+cJUHYKODZ8
Cs8goAcA/mJ4h1g63EP1Snlw4U3vMJ8ZQRAeg46FAZATwte9SaahAq1kLql/P8jg
A4gM9xvfRgVOIrfxSHDlnw6gVK6u/WhD4SWIsS2JfNljgUmrcMWB37kNdT3i0yO7
Vydw/UIJw1pqktqz
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9mQTpJlPDjEqL
novcT/ALYwjoP2Siowor2yeEKaGKJjBamu3OkYhS+2kzJhcii705uTCal/f6gDIl
nhYXlPEhL7Z0wsT9IePJSU9+yNtUrWYILfRg1+XkpZVqrPfjBk8usTjtC4kG9xRZ
no/TeZj/2Qror/C989Hl+bqZ4p31/l1Jcml/W01PDiGcqESS15bKk24azJ1w69Zh
jwn8uZKcMnq2myrJsl8fZ82gV2fV8yydhpDudPpHy8y/9U8FfsmODi75aH4A1NkK
/2FZyBKE1OEYd+JfL7QmBCCjIt9AREXA/77HSuj6OXoKWZ0AVuiHLA/psfcRL4+Q
Xd1UtXbFAgMBAAECggEAK5AHEtLdmCaZ0i6hkANF8jfVChfWtY+kfKMkFzfBiA5y
Ob8zOK0zl21wpHHyCtv0pFiqlDqqnYHrA72o8c4lAS0HTRibTlYFEnCntUfNLU2S
DfsRFVdF2R06kYIgiqcedmn93Gk0GMeYg2btQPfFcbOa0A/szphA+AhDGax6AtUD
gl7+QT4j5HE598ghtl5/DZ4tiw4cfuWjC6ph7tHbKKq9wCH6wQf9kcyIA4ozVBKV
fejE9t4BfVPxzbxN+Quu0+S5SGnKzg1uY+/99Jo1IqtJGQq1OlPFLjVnxUF1N+Wp
nJVBHorILQtGhYxW4QlWsHMdc7iB5r4eFSuKaivMGQKBgQDrCDviK35IuQylxKE8
Xu/eSwPpOwF4ibASgaPmJt+t4O4JLX1GLZX899KDIeXaAFqcp2EF4QUhX2ioGIiO
GGFFAmOHIDvCFfiNpM1m7F0Njj8gedFfT4Yhv73htUlh5zA8vfuv4PN4ZGfjK3L9
sW9OEMUDTey5D/6Wq/IZ8ZGTwwKBgQDOgyJSJQk8K0n4AGLPyP/wmXL4w/xi8IOC
kafs1XsQCn5OvKJZY5ZNyoSzhkKmlUQTO/tmZ5flOk6wVs34StSNSo+JQub5vEYi
gXVNwYB6oPYMtdfPYLSy59h0REugNfkunRj5crPyVttJiVZpxBJHxgnIqJcBj+WT
ehHNJpRK1wKBgFx4s97rj9ca/4/lCi8Phz6lsxc7gPuk6KKPYSX3W4A1BFKWFDjd
TKrn8mpnluCrzPrfm/vNKdCUkj+4z1lg3DxjkTckBn75V/6avbnl+0KPGeU0KJ1g
U3zJzPKV+hZL+J2dff4X+pL+piUp/ic0fX9wd6MyMJYrZdZwNmPguI8zAoGAARJF
F1AB4EIJPDQkTxen3EOviQLbSFgfFopS6LOi0856IUZxQS13Fig60AOeTObxV3g0
Ma/P5eyLg/avUt5wg9sjK38hW6JSatNpHGIonHpBTIeU+wpxZYw2X0QLcGVXSZqf
CoxByrwQny0LObk+rwij/FqDjgqFEmLLvNi6ZDkCgYEA3xgeLNBGf5ghYhgX9PKO
Y1Rg6y1ElqxMCoovkpNlA6bVkyxcYIItIW1npsSeM45x+6Blit74LuleE9UYoN8j
BC8ADhYN7ywb0juCnpLrKuWl/3XNg3wREhvhHfEK1agEysVFUohFwdtfyW4gNWia
wli1LGvTwY1aFj8K29VKvkE=
-----END PRIVATE KEY-----

View File

@ -1,119 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"Cert Tools, pyOpenSSL version"
__author__ = 'phoenix'
__version__ = '0.1'
CA = "CA.crt"
CERTDIR = "Certs"
# Temp list for generating certs
workingList = set()
import os
import time
import OpenSSL
def create_CA(capath):
key = OpenSSL.crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
ca = OpenSSL.crypto.X509()
ca.set_serial_number(0)
# Value 2 means v3
ca.set_version(2)
subj = ca.get_subject()
subj.countryName = 'CN'
subj.organizationName = 'ProxHTTPSProxy'
subj.organizationalUnitName = 'pyOpenSSL'
subj.commonName = 'ProxHTTPSProxy CA'
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(24 * 60 * 60 * 3652)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
ca.add_extensions(
[OpenSSL.crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE"),
# mozilla::pkix doesn't handle the Netscape Cert Type extension (which is problematic when it's marked critical)
# https://bugzilla.mozilla.org/show_bug.cgi?id=1009161
OpenSSL.crypto.X509Extension(b"nsCertType", False, b"sslCA"),
OpenSSL.crypto.X509Extension(b"extendedKeyUsage", True, b"serverAuth,clientAuth,emailProtection,timeStamping,msCodeInd,msCodeCom,msCTLSign,msSGC,msEFS,nsSGC"),
OpenSSL.crypto.X509Extension(b"keyUsage", False, b"keyCertSign, cRLSign"),
OpenSSL.crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=ca)])
ca.sign(key, 'sha256')
with open(capath, 'wb') as fp:
fp.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, ca))
fp.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
def get_cert(name, cafile=CA, certdir=CERTDIR):
"""Return cert file path. Create it if it doesn't exist.
cafile: the CA file to create dummpy cert files
certdir: the path where cert files are looked for or created
"""
certfile = os.path.join(certdir, name + '.crt')
if not os.path.exists(certfile):
dummy_cert(cafile, certfile, name)
return certfile
def dummy_cert(cafile, certfile, commonname):
"""Generates and writes a certificate to certfile
commonname: Common name for the generated certificate
Ref: https://github.com/mitmproxy/netlib/blob/master/netlib/certutils.py
"""
if certfile in workingList:
# Another thread is working on it, wait until it finish
while True:
time.sleep(0.2)
if certfile not in workingList: break
else:
workingList.add(certfile)
with open(cafile, "rb") as file:
content = file.read()
ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, content)
key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, content)
cert = OpenSSL.crypto.X509()
# Value 2 means v3
cert.set_version(2)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(60 * 60 * 24 * 3652)
cert.set_issuer(ca.get_subject())
if commonname.startswith('.'):
domain = '*' + commonname
else:
domain = commonname
cert.get_subject().CN = domain
cert.set_serial_number(int(time.time()*10000))
cert.set_pubkey(ca.get_pubkey())
cert.add_extensions(
[OpenSSL.crypto.X509Extension(b"subjectAltName", False, str.encode("DNS:"+domain))])
cert.sign(key, "sha256")
with open(certfile, 'wb') as fp:
fp.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
fp.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
workingList.remove(certfile)
def startup_check():
if not os.path.exists(CERTDIR):
os.mkdir(CERTDIR)
if not os.path.exists(CERTDIR):
print("%s directory does not exist!")
print("Please create it and restart the program!")
input()
raise SystemExit
if not os.path.exists(CA):
print("Creating CA ...")
create_CA(CA)
if not os.path.exists(CA):
print("Failed to create CA :(")
else:
print("* Please import created %s to your client's store of trusted certificate authorities." % CA)
print("* Please delete all files under %s directory!" % CERTDIR)
print("* Then restart the program!")
input()
raise SystemExit
startup_check()
if __name__ == "__main__":
print("All Good!")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,39 +0,0 @@
ProxHTTPSProxyMII
=================
Version 1.4 (20160112)
--------------
+ Socks proxy support (needs urllib3 >= 1.14)
* Certifications are now v3 instead of v1
Version 1.3.1 (20151001)
--------------
* Certifications are now signed via SHA256 instead of SHA1
Version 1.3 (20150114)
--------------
+ Each request has a number ranged from 001 to 999 for reference. 000 is reserved for SSL requests not MITMed like those in [BLACKLIST] and [SSL Pass-Thru] sections.
+ Log window now displays the length of the bytes submitted in POST method
Version 1.2 (20141221)
--------------
+ Content is streamed to client, while not cached before sending
* Fix config auto reload
* Less exception traceback dumped
* Tagged header changed from "Tagged:Proxomitron FrontProxy/*" to "Tagged:ProxHTTPSProxyMII FrontProxy/*"
Version 1.1 (20141024)
--------------
+ Support URL bypass
+ Handle both HTTP and HTTPS
+ Auto reload config upon chagnes
Version 1.0 (20140729)
--------------
Initial release

View File

@ -1,88 +0,0 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><title>ProxHTTPSProxyMII</title><style></style></head><body>
<h1 id="proxhttpsproxymii">ProxHTTPSProxyMII</h1>
<p>Created to provide modern nag-free HTTPS connections for an HTTP proxy.</p>
<h2 id="how-it-works">How it works</h2>
<p><img src="http://www.proxfilter.net/proxhttpsproxy/HowItWorks.gif" alt="how it works"></p>
<h2 id="eligible-http-proxies">Eligible HTTP Proxies</h2>
<ul>
<li>The <a href="http://www.proxomitron.info">Proxomitron</a>, for which ProxHTTPSProxy was created :)</li>
<li>Any that have the ability to forward all requests with a &quot;Tagged:ProxHTTPSProxyMII FrontProxy/*&quot; header to the ProxHTTPSProxyMII rear server.</li>
<li>Any that can be ran as two instances, one for true http and another for &quot;tagged&quot; http</li>
<li>Any that will only be used to monitor https traffic </li>
</ul>
<h2 id="install">Install</h2>
<ul>
<li>ProxHTTPSProxy&#39;s &quot;CA.crt&quot; to the Client&#39;s store of trusted certificate authorities.</li>
</ul>
<h2 id="configure">Configure</h2>
<ul>
<li>The Client to use the ProxHTTPSProxy front server at 127.0.0.1 on port 8079 for secure connections.</li>
<li>The HTTP proxy to receive requests at 127.0.0.1 on port 8080.</li>
<li>The HTTP proxy to forward requests to the ProxHTTPSProxy rear server at 127.0.0.1 on port 8081.</li>
<li>Edit &quot;Config.ini&quot; to change these requirements.</li>
</ul>
<h2 id="execute">Execute</h2>
<p>ProxHTTPSProxy.exe to start.</p>
<h2 id="remember">Remember</h2>
<p>Be aware and careful! Use a direct connection when you don&#39;t want any mistakes made.</p>
<p>Use at your own risk!</p>
<p>Have fun!</p>
<h2 id="discuss">Discuss</h2>
<p><a href="http://prxbx.com/forums/showthread.php?tid=2172">http://prxbx.com/forums/showthread.php?tid=2172</a></p>
<h2 id="author">Author</h2>
<ul>
<li>phoenix (aka whenever)</li>
<li>JJoe (test and doc)</li>
</ul>
<h1 id="proxomitron-tips">Proxomitron Tips</h1>
<h2 id="to-use">To use</h2>
<ul>
<li><p>Add the ProxHTTPSProxy rear server to the Proxomitron&#39;s list of external proxies</p>
<p><code>127.0.0.1:8081 ProxHTTPSProxy</code></p>
</li>
<li><p>Add to Proxomitron&#39;s &quot;Bypass URLs that match this expression&quot; field if it is empty</p>
<p><code>$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)</code></p>
</li>
<li><p>Add to the beginning of the entry in Proxomitron&#39;s &quot;Bypass URLs that match this expression&quot; field if it is <strong>not</strong> empty</p>
<p><code>$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)|</code> </p>
</li>
</ul>
<h2 id="tips">Tips</h2>
<ul>
<li><p>Proxomitron always executes some commands in &quot;Bypass URLs that match this expression&quot; field. Adding the entry there allows the Proxomitron to use the rear server when in Bypass mode.</p>
<p>This undocumented feature brings many possibilities but remember, an actual match triggers bypass of filtering!</p>
<ul>
<li><code>$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)</code> checks for the header that indicates an https request.</li>
<li><code>$SETPROXY(127.0.0.1:8081)</code> is executed when found.</li>
<li><code>(^)</code> expression never matches. </li>
</ul>
</li>
<li><p>Identify https connections by testing for the &quot;Tagged&quot; request header that the ProxHTTPSProxy front server adds to the request. </p>
<p><code>$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)</code></p>
</li>
<li><p>For local file requests, use an expression like </p>
<p><code>$USEPROXY(false)$RDIR(http://local.ptron/killed.gif)</code></p>
</li>
<li><p>Before redirecting &quot;Tagged&quot; connections to external resources consider removing the &quot;Tagged&quot; header. </p>
</li>
<li><p>If needed, the Proxomitron can still do https. After adding the ssl files to the Proxomitron, use a header filter like</p>
<pre><code>[HTTP headers]
In = FALSE
Out = TRUE
Key = &quot;Tagged: Use Proxomitron for https://badcert.com&quot;
URL = &quot;badcert.com$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$USEPROXY(false)$RDIR(https://badcert.com)&quot;
</code></pre><p>This filter also removes the &quot;Tagged&quot; header. </p>
</li>
</ul>
<h2 id="for-the-current-sidki-set">For the current sidki set</h2>
<ol>
<li><p>Add the following two lines to Exceptions-U</p>
<pre><code>$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
~(^$TST(keyword=i_proxy:[03].))$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
</code></pre></li>
<li><p>Redirect connections to http resources with an expression like</p>
<p><code>$USEPROXY(false)$SET(keyword=i_proxy:0.)$RDIR(http://local.ptron/killed.gif)</code></p>
</li>
</ol>
</body></html>

View File

@ -1,119 +0,0 @@
ProxHTTPSProxyMII
=================
Created to provide modern nag-free HTTPS connections for an HTTP proxy.
How it works
----
![how it works](http://www.proxfilter.net/proxhttpsproxy/HowItWorks.gif)
Eligible HTTP Proxies
----
* The [Proxomitron](http://www.proxomitron.info), for which ProxHTTPSProxy was created :)
* Any that have the ability to forward all requests with a "Tagged:ProxHTTPSProxyMII FrontProxy/*" header to the ProxHTTPSProxyMII rear server.
* Any that can be ran as two instances, one for true http and another for "tagged" http
* Any that will only be used to monitor https traffic
Install
----
* ProxHTTPSProxy's "CA.crt" to the Client's store of trusted certificate authorities.
Configure
----
* The Client to use the ProxHTTPSProxy front server at 127.0.0.1 on port 8079 for secure connections.
* The HTTP proxy to receive requests at 127.0.0.1 on port 8080.
* The HTTP proxy to forward requests to the ProxHTTPSProxy rear server at 127.0.0.1 on port 8081.
* Edit "Config.ini" to change these requirements.
Execute
----
ProxHTTPSProxy.exe to start.
Remember
----
Be aware and careful! Use a direct connection when you don't want any mistakes made.
Use at your own risk!
Have fun!
Discuss
----
<http://prxbx.com/forums/showthread.php?tid=2172>
Author
----
* phoenix (aka whenever)
* JJoe (test and doc)
Proxomitron Tips
================
To use
----
* Add the ProxHTTPSProxy rear server to the Proxomitron's list of external proxies
`127.0.0.1:8081 ProxHTTPSProxyMII`
* Add to Proxomitron's "Bypass URLs that match this expression" field if it is empty
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)`
* Add to the beginning of the entry in Proxomitron's "Bypass URLs that match this expression" field if it is **not** empty
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)|`
Tips
----
* Proxomitron always executes some commands in "Bypass URLs that match this expression" field. Adding the entry there allows the Proxomitron to use the rear server when in Bypass mode.
This undocumented feature brings many possibilities but remember, an actual match triggers bypass of filtering!
- `$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)` checks for the header that indicates an https request.
- `$SETPROXY(127.0.0.1:8081)` is executed when found.
- `(^)` expression never matches.
* Identify https connections by testing for the "Tagged" request header that the ProxHTTPSProxy front server adds to the request.
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)`
* For local file requests, use an expression like
`$USEPROXY(false)$RDIR(http://local.ptron/killed.gif)`
* Before redirecting "Tagged" connections to external resources consider removing the "Tagged" header.
* If needed, the Proxomitron can still do https. After adding the ssl files to the Proxomitron, use a header filter like
```
[HTTP headers]
In = FALSE
Out = TRUE
Key = "Tagged: Use Proxomitron for https://badcert.com"
URL = "badcert.com$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$USEPROXY(false)$RDIR(https://badcert.com)"
```
This filter also removes the "Tagged" header.
For the current sidki set
----
1. Add the following two lines to Exceptions-U
```
$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
~(^$TST(keyword=i_proxy:[03].))$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
```
2. Redirect connections to http resources with an expression like
`$USEPROXY(false)$SET(keyword=i_proxy:0.)$RDIR(http://local.ptron/killed.gif)`

120
README.md
View File

@ -1,120 +0,0 @@
ProxHTTPSProxyMII
=================
A Proxomitron SSL Helper Program, can also be used to make a HTTP proxy like Privoxy capable of filtering HTTPS.
How it works
----
![how it works](http://www.proxfilter.net/proxhttpsproxy/HowItWorks.gif)
Eligible HTTP Proxies
----
* The [Proxomitron](http://www.proxomitron.info), for which ProxHTTPSProxy was created :)
* Any that have the ability to forward all requests with a "Tagged:ProxHTTPSProxyMII FrontProxy/*" header to the ProxHTTPSProxyMII rear server.
* Any that can be ran as two instances, one for true http and another for "tagged" http
* Any that will only be used to monitor https traffic
Install
----
* ProxHTTPSProxy's "CA.crt" to the Client's store of trusted certificate authorities.
Configure
----
* The Client to use the ProxHTTPSProxy front server at 127.0.0.1 on port 8079 for secure connections.
* The HTTP proxy to receive requests at 127.0.0.1 on port 8080.
* The HTTP proxy to forward requests to the ProxHTTPSProxy rear server at 127.0.0.1 on port 8081.
* Edit "Config.ini" to change these requirements.
Execute
----
ProxHTTPSProxy.exe to start.
Remember
----
Be aware and careful! Use a direct connection when you don't want any mistakes made.
Use at your own risk!
Have fun!
Discuss
----
<http://prxbx.com/forums/showthread.php?tid=2172>
Author
----
* phoenix (aka whenever)
* JJoe (test and doc)
Proxomitron Tips
================
To use
----
* Add the ProxHTTPSProxy rear server to the Proxomitron's list of external proxies
`127.0.0.1:8081 ProxHTTPSProxyMII`
* Add to Proxomitron's "Bypass URLs that match this expression" field if it is empty
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)`
* Add to the beginning of the entry in Proxomitron's "Bypass URLs that match this expression" field if it is **not** empty
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SETPROXY(127.0.0.1:8081)(^)|`
Tips
----
* Proxomitron always executes some commands in "Bypass URLs that match this expression" field. Adding the entry there allows the Proxomitron to use the rear server when in Bypass mode.
This undocumented feature brings many possibilities but remember, an actual match triggers bypass of filtering!
- `$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)` checks for the header that indicates an https request.
- `$SETPROXY(127.0.0.1:8081)` is executed when found.
- `(^)` expression never matches.
* Identify https connections by testing for the "Tagged" request header that the ProxHTTPSProxy front server adds to the request.
`$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)`
* For local file requests, use an expression like
`$USEPROXY(false)$RDIR(http://local.ptron/killed.gif)`
* Before redirecting "Tagged" connections to external resources consider removing the "Tagged" header.
* If needed, the Proxomitron can still do https. After adding the ssl files to the Proxomitron, use a header filter like
```
[HTTP headers]
In = FALSE
Out = TRUE
Key = "Tagged: Use Proxomitron for https://badcert.com"
URL = "badcert.com$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$USEPROXY(false)$RDIR(https://badcert.com)"
```
This filter also removes the "Tagged" header.
For the current sidki set
----
1. Add the following two lines to Exceptions-U
```
$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
~(^$TST(keyword=i_proxy:[03].))$OHDR(Tagged:ProxHTTPSProxyMII FrontProxy/*)$SET(keyword=$GET(keyword)i_proxy:3.)(^)
```
2. Redirect connections to http resources with an expression like
`$USEPROXY(false)$SET(keyword=i_proxy:0.)$RDIR(http://local.ptron/killed.gif)`

5229
cacert.pem

File diff suppressed because it is too large Load Diff

116
cert.py Executable file
View File

@ -0,0 +1,116 @@
'Certificate generation utilities'
__author__ = 'phoenix'
__version__ = '0.1'
import os
import time
import OpenSSL.crypto as crypto
import argparse
import sys
# Temp list for generating certs
workingList = set()
def create_CA(file):
'''
Generate the CA root certificate.
'''
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
ca = crypto.X509()
ca.set_serial_number(0)
# Value 2 means v3
ca.set_version(2)
subj = ca.get_subject()
subj.countryName = 'CN'
subj.organizationName = 'Privoxy'
subj.organizationalUnitName = 'pyOpenSSL'
subj.commonName = 'Privoxy Fake CA'
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(24 * 60 * 60 * 3652)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
ca.add_extensions([
crypto.X509Extension(b'basicConstraints', True, b'CA:TRUE'),
crypto.X509Extension(b'nsCertType', False, b'sslCA'),
crypto.X509Extension(b'extendedKeyUsage', True,
b'serverAuth,clientAuth,emailProtection,'
b'timeStamping,msCodeInd,msCodeCom,msCTLSign'
b',msSGC,msEFS,nsSGC'),
crypto.X509Extension(b'keyUsage', False, b'keyCertSign, cRLSign'),
crypto.X509Extension(b'subjectKeyIdentifier', False, b'hash',
subject=ca)])
ca.sign(key, 'sha256')
file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca).decode())
file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key).decode())
def get_cert(name, config):
'''
Return cert file path. Create it if it doesn't exist.
Reads from config:
cafile: the CA file to create dummpy cert files
certdir: the path where cert files are looked for or created
'''
certfile = os.path.join(config.CERTDIR, name + '.crt')
if not os.path.exists(certfile):
dummy_cert(config.CA, certfile, name)
return certfile
def dummy_cert(cafile, certfile, commonname):
'''
Generates and writes a certificate to certfile
commonname: Common name for the generated certificate
Ref: https://github.com/mitmproxy/netlib/blob/master/netlib/certutils.py
'''
if certfile in workingList:
# Another thread is working on it, wait until it finish
while True:
time.sleep(0.2)
if certfile not in workingList:
break
else:
workingList.add(certfile)
with open(cafile, 'rb') as file:
content = file.read()
ca = crypto.load_certificate(crypto.FILETYPE_PEM, content)
key = crypto.load_privatekey(crypto.FILETYPE_PEM, content)
cert = crypto.X509()
# Value 2 means v3
cert.set_version(2)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(60 * 60 * 24 * 3652)
cert.set_issuer(ca.get_subject())
if commonname.startswith('.'):
domain = '*' + commonname
else:
domain = commonname
cert.get_subject().CN = domain
cert.set_serial_number(int(time.time()*10000))
cert.set_pubkey(ca.get_pubkey())
cert.add_extensions([
crypto.X509Extension(b'subjectAltName', False,
str.encode('DNS:'+domain))])
cert.sign(key, 'sha256')
with open(certfile, 'wb') as fp:
fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
workingList.remove(certfile)
def main():
parser = argparse.ArgumentParser(description='Generates CA Certificates.')
parser.add_argument('-f', '--file', type=argparse.FileType('w'),
default=sys.stdout,
help='CA certificate output file')
args = parser.parse_args()
create_CA(args.file)
if __name__ == '__main__':
main()

View File

@ -1,63 +0,0 @@
### The parent proxy has to support CONNECT method, if you want to proxy HTTPS requests
###
### Proxy setting applies to HTTPS requests only, as it is applied by the Rear Server
### HTTP requests are passed to and handled by Proxomitron, please set up Proxomitron for proxy
[GENERAL]
ProxAddr = http://localhost:8080
FrontPort = 8079
RearPort = 8081
# DefaultProxy = http://127.0.0.1:8118
# Proper values for LogLevel are ERROR, WARNING, INFO, DEBUG
# Default is INFO if unset
LogLevel =
# * matches everything
# ? matches any single character
# [seq] matches any character in seq
# [!seq] matches any character not in seq
[PROXY http://192.168.178.1:8123]
#duckduckgo.com
#*.s3.amazonaws.com
[PROXY socks5://192.168.178.3:1080]
test.com
### Ignore SSL certificate verify, Use at your own risk!!!
### Proxy setting still effective
[SSL No-Verify]
*.12306.cn
[BLACKLIST]
*.doubleclick.net
*.google-analytics.com
### Bypass Proxomitron and the Rear Server, Proxy setting still effective
### SSL certificate verify will be done by the browser
[SSL Pass-Thru]
pypi.python.org
www.gstatic.com
watson.telemetry.microsoft.com
*.sync.services.mozilla.com
*.mega.co.nz
# Microsoft SmartScreen Filter Service
*.smartscreen.microsoft.com
urs.microsoft.com
# NoScript uses https://secure.informaction.com/ipecho to detect the WAN IP
# https://addons.mozilla.org/en-US/firefox/addon/noscript/privacy/
secure.informaction.com
### Bypass Proxomitron and the Rear Server, Proxy setting still effective
### This section supports URL matching
[BYPASS URL]
http://www.abc.com/*
https://bcd.net/*
*://feedly.com/*
*.zip
*.rar
*.exe
*.pdf

View File

@ -1,14 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"A Proxomitron Helper Program"
"A Privoxy Helper Program"
_name = 'ProxHTTPSProxyMII'
__author__ = 'phoenix'
__version__ = 'v1.4'
CONFIG = "config.ini"
CA_CERTS = "cacert.pem"
from socketserver import ThreadingMixIn
from http.server import HTTPServer
from urllib.parse import urlparse
from proxy import Counter, ProxyRequestHandler, get_cert
from urllib3.contrib.socks import SOCKSProxyManager
import os
import time
@ -18,29 +20,27 @@ import logging
import threading
import ssl
import urllib3
from urllib3.contrib.socks import SOCKSProxyManager
#https://urllib3.readthedocs.org/en/latest/security.html#insecurerequestwarning
urllib3.disable_warnings()
import argparse
import collections
from socketserver import ThreadingMixIn
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse
from ProxyTool import ProxyRequestHandler, get_cert, counter
from colorama import init, Fore, Back, Style
init(autoreset=True)
_name = 'privoxy-tls'
class LoadConfig:
def __init__(self, configfile):
self.config = configparser.ConfigParser(allow_no_value=True,
inline_comment_prefixes=('#',))
self.config.read(configfile)
self.PROXADDR = self.config['GENERAL'].get('ProxAddr')
self.FRONTPORT = int(self.config['GENERAL'].get('FrontPort'))
self.REARPORT = int(self.config['GENERAL'].get('RearPort'))
self.DEFAULTPROXY = self.config['GENERAL'].get('DefaultProxy')
self.LOGLEVEL = self.config['GENERAL'].get('LogLevel')
self.PROXADDR = self.config['General'].get('ProxAddr')
self.FRONTPORT = int(self.config['General'].get('FrontPort'))
self.REARPORT = int(self.config['General'].get('RearPort'))
self.GeneralPROXY = self.config['General'].get('DefaultProxy')
self.LOGLEVEL = self.config['General'].get('LogLevel')
self.CA = self.config['General'].get('CACert')
self.CERTDIR = self.config['General'].get('CertDir')
class ConnectionPools:
"""
self.pools is a list of {'proxy': 'http://127.0.0.1:8080',
@ -48,19 +48,10 @@ class ConnectionPools:
'patterns': ['ab.com', 'bc.net', ...]}
self.getpool() is a method that returns pool based on host matching
"""
# Windows default CA certificates are incomplete
# See: http://bugs.python.org/issue20916
# cacert.pem sources:
# - http://curl.haxx.se/docs/caextract.html
# - http://certifi.io/en/latest/
# ssl_version="TLSv1" to specific version
sslparams = dict(cert_reqs="REQUIRED", ca_certs=CA_CERTS)
# IE: http://support2.microsoft.com/kb/181050/en-us
# Firefox about:config
# network.http.connection-timeout 90
# network.http.response.timeout 300
timeout = urllib3.util.timeout.Timeout(connect=90.0, read=300.0)
sslparams = dict(
cert_reqs="REQUIRED",
ca_certs='/etc/ssl/certs/ca-bundle.crt')
timeout = urllib3.util.timeout.Timeout(connect=90.0, read=90.0)
def __init__(self, config):
self.file = config
@ -69,27 +60,40 @@ class ConnectionPools:
def loadConfig(self):
# self.conf has to be inited each time for reloading
self.conf = configparser.ConfigParser(allow_no_value=True, delimiters=('=',),
inline_comment_prefixes=('#',))
self.conf = configparser.ConfigParser(
allow_no_value=True, delimiters=('=',),
inline_comment_prefixes=('#',))
self.conf.read(self.file)
self.pools = []
proxy_sections = [section for section in self.conf.sections()
if section.startswith('PROXY')]
if section.startswith('Proxy')]
for section in proxy_sections:
proxy = section.split()[1]
self.pools.append(dict(proxy=proxy,
pool=self.setProxyPool(proxy),
patterns=list(self.conf[section].keys())))
default_proxy = self.conf['GENERAL'].get('DefaultProxy')
default_pool = (self.setProxyPool(default_proxy) if default_proxy else
[urllib3.PoolManager(num_pools=10, maxsize=8, timeout=self.timeout, **self.sslparams),
urllib3.PoolManager(num_pools=10, maxsize=8, timeout=self.timeout)])
self.pools.append({'proxy': default_proxy, 'pool': default_pool, 'patterns': '*'})
default_proxy = self.conf['General'].get('DefaultProxy')
self.noverifylist = list(self.conf['SSL No-Verify'].keys())
self.blacklist = list(self.conf['BLACKLIST'].keys())
self.sslpasslist = list(self.conf['SSL Pass-Thru'].keys())
self.bypasslist = list(self.conf['BYPASS URL'].keys())
if default_proxy:
default_pool = self.setProxyPool(default_proxy)
else:
default_pool = [
urllib3.PoolManager(num_pools=10, maxsize=8,
timeout=self.timeout, **self.sslparams),
urllib3.PoolManager(num_pools=10, maxsize=8,
timeout=self.timeout)]
self.pools.append(dict(proxy=default_proxy,
pool=default_pool, patterns='*'))
# handle missing sections
sections = collections.defaultdict(dict)
for name in self.conf.sections():
sections[name] = self.conf[name]
self.noverifylist = list(sections['TLS NoVerify'].keys())
self.sslpasslist = list(sections['TLS Passthru'].keys())
self.blacklist = list(sections['Blacklist'].keys())
self.bypasslist = list(sections['Bypass URL'].keys())
def reloadConfig(self):
while True:
@ -97,14 +101,18 @@ class ConnectionPools:
if mtime > self.file_timestamp:
self.file_timestamp = mtime
self.loadConfig()
logger.info(Fore.RED + Style.BRIGHT
+ "*" * 20 + " CONFIG RELOADED " + "*" * 20)
logger.info("*" * 20 + " CONFIG RELOADED " + "*" * 20)
time.sleep(1)
def getpool(self, host, httpmode=False):
noverify = True if httpmode or any((fnmatch.fnmatch(host, pattern) for pattern in self.noverifylist)) else False
things = (fnmatch.fnmatch(host, pattern)
for pattern in self.noverifylist)
noverify = True if httpmode or any(things) else False
logger.debug(f'host: {host}, noverify: {noverify}')
for pool in self.pools:
if any((fnmatch.fnmatch(host, pattern) for pattern in pool['patterns'])):
things = (fnmatch.fnmatch(host, pattern)
for pattern in pool['patterns'])
if any(things):
return pool['proxy'], pool['pool'][noverify], noverify
def setProxyPool(self, proxy):
@ -119,23 +127,28 @@ class ConnectionPools:
input()
raise SystemExit
# maxsize is the max. number of connections to the same server
return [ProxyManager(proxy, num_pools=10, maxsize=8, timeout=self.timeout, **self.sslparams),
ProxyManager(proxy, num_pools=10, maxsize=8, timeout=self.timeout)]
return [
ProxyManager(proxy, num_pools=10, maxsize=8, timeout=self.timeout,
**self.sslparams),
ProxyManager(proxy, num_pools=10, maxsize=8, timeout=self.timeout)]
class FrontServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
pass
class RearServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
pass
class FrontRequestHandler(ProxyRequestHandler):
"""
Sit between the client and Proxomitron
Sit between the client and Privoxy
Convert https request to http
"""
server_version = "%s FrontProxy/%s" % (_name, __version__)
server_version = "%s front/%s" % (_name, __version__)
def do_CONNECT(self):
"Descrypt https request and dispatch to http handler"
@ -143,11 +156,15 @@ class FrontRequestHandler(ProxyRequestHandler):
# request line: CONNECT www.example.com:443 HTTP/1.1
self.host, self.port = self.path.split(":")
self.proxy, self.pool, self.noverify = pools.getpool(self.host)
if any((fnmatch.fnmatch(self.host, pattern) for pattern in pools.blacklist)):
things = (fnmatch.fnmatch(self.host, pattern)
for pattern in pools.blacklist)
if any(things):
# BLACK LIST
self.deny_request()
logger.info("%03d " % self.reqNum + Fore.CYAN + 'Denied by blacklist: %s' % self.host)
elif any((fnmatch.fnmatch(self.host, pattern) for pattern in pools.sslpasslist)):
logger.info("%03d " % self.reqNum + 'Denied by blacklist: s'
% self.host)
elif any((fnmatch.fnmatch(self.host, pattern)
for pattern in pools.sslpasslist)):
# SSL Pass-Thru
if self.proxy and self.proxy.startswith('https'):
self.forward_to_https_proxy()
@ -155,19 +172,24 @@ class FrontRequestHandler(ProxyRequestHandler):
self.forward_to_socks5_proxy()
else:
self.tunnel_traffic()
# Upstream server or proxy of the tunnel is closed explictly, so we close the local connection too
# Upstream server or proxy of the tunnel is
# closed explictly, so we close the local connection too
self.close_connection = 1
else:
# SSL MITM
self.wfile.write(("HTTP/1.1 200 Connection established\r\n" +
"Proxy-agent: %s\r\n" % self.version_string() +
"\r\n").encode('ascii'))
commonname = '.' + self.host.partition('.')[-1] if self.host.count('.') >= 2 else self.host
dummycert = get_cert(commonname)
if self.host.count('.') >= 2:
commonname = '.' + self.host.partition('.')[-1]
else:
commonname = self.host
dummycert = get_cert(commonname, config)
# set a flag for do_METHOD
self.ssltunnel = True
ssl_sock = ssl.wrap_socket(self.connection, keyfile=dummycert, certfile=dummycert, server_side=True)
ssl_sock = ssl.wrap_socket(self.connection, keyfile=dummycert,
certfile=dummycert, server_side=True)
# Ref: Lib/socketserver.py#StreamRequestHandler.setup()
self.connection = ssl_sock
self.rfile = self.connection.makefile('rb', self.rbufsize)
@ -176,48 +198,57 @@ class FrontRequestHandler(ProxyRequestHandler):
self.handle_one_request()
def do_METHOD(self):
"Forward request to Proxomitron"
"Forward request to Privoxy"
counter.increment_and_set(self, 'reqNum')
if self.ssltunnel:
# https request
host = self.host if self.port == '443' else "%s:%s" % (self.host, self.port)
host = (self.host if self.port == '443'
else "%s:%s" % (self.host, self.port))
url = "https://%s%s" % (host, self.path)
self.bypass = any((fnmatch.fnmatch(url, pattern) for pattern in pools.bypasslist))
self.bypass = any((fnmatch.fnmatch(url, pattern)
for pattern in pools.bypasslist))
if not self.bypass:
url = "http://%s%s" % (host, self.path)
# Tag the request so Proxomitron can recognize it
self.headers["Tagged"] = self.version_string() + ":%d" % self.reqNum
# Tag the request so Privoxy can recognize it
self.headers["Tagged"] = (self.version_string()
+ ":%d" % self.reqNum)
else:
# http request
self.host = urlparse(self.path).hostname
if any((fnmatch.fnmatch(self.host, pattern) for pattern in pools.blacklist)):
if any((fnmatch.fnmatch(self.host, pattern)
for pattern in pools.blacklist)):
# BLACK LIST
self.deny_request()
logger.info("%03d " % self.reqNum + Fore.CYAN + 'Denied by blacklist: %s' % self.host)
logger.info("%03d " % self.reqNum
+ 'Denied by blacklist: %s' % self.host)
return
host = urlparse(self.path).netloc
self.proxy, self.pool, self.noverify = pools.getpool(self.host, httpmode=True)
self.bypass = any((fnmatch.fnmatch('http://' + host + urlparse(self.path).path, pattern) for pattern in pools.bypasslist))
self.proxy, self.pool, self.noverify = pools.getpool(
self.host, httpmode=True)
self.bypass = any((fnmatch.fnmatch('http://' + host +
urlparse(self.path).path, pattern)
for pattern in pools.bypasslist))
url = self.path
self.url = url
pool = self.pool if self.bypass else proxpool
data_length = self.headers.get("Content-Length")
self.postdata = self.rfile.read(int(data_length)) if data_length and int(data_length) > 0 else None
self.postdata = (self.rfile.read(int(data_length))
if data_length and int(data_length) > 0 else None)
if self.command == "POST" and "Content-Length" not in self.headers:
buffer = self.rfile.read()
if buffer:
logger.warning("%03d " % self.reqNum + Fore.RED +
'POST w/o "Content-Length" header (Bytes: %d | Transfer-Encoding: %s | HTTPS: %s',
len(buffer), "Transfer-Encoding" in self.headers, self.ssltunnel)
logger.warning(
"%03d " % self.reqNum +
'POST w/o "Content-Length" header (Bytes: %d |'
' Transfer-Encoding: %s | HTTPS: %s',
len(buffer), "Transfer-Encoding" in self.headers,
self.ssltunnel)
# Remove hop-by-hop headers
self.purge_headers(self.headers)
r = None
# Below code in connectionpool.py expect the headers to has a copy() and update() method
# That's why we can't use self.headers directly when call pool.urlopen()
#
# Merge the proxy headers. Only do this in HTTP. We have to copy the
# headers dict so we can safely change it without those changes being
# reflected in anyone else's copy.
@ -227,43 +258,55 @@ class FrontRequestHandler(ProxyRequestHandler):
headers = urllib3._collections.HTTPHeaderDict(self.headers)
try:
# Sometimes 302 redirect would fail with "BadStatusLine" exception, and IE11 doesn't restart the request.
# Sometimes 302 redirect would fail with "BadStatusLine"
# exception, and IE11 doesn't restart the request.
# retries=1 instead of retries=False fixes it.
#! Retry may cause the requests with the same reqNum appear in the log window
r = pool.urlopen(self.command, url, body=self.postdata, headers=headers,
retries=1, redirect=False, preload_content=False, decode_content=False)
# ! Retry may cause the requests with the same reqNum appear
# in the log window
r = pool.urlopen(
self.command, url, body=self.postdata, headers=headers,
retries=1, redirect=False, preload_content=False,
decode_content=False)
if not self.ssltunnel:
if self.bypass:
prefix = '[BP]' if self.proxy else '[BD]'
else:
prefix = '[D]'
if self.command in ("GET", "HEAD"):
logger.info("%03d " % self.reqNum + Fore.MAGENTA + '%s "%s %s" %s %s' %
(prefix, self.command, url, r.status, r.getheader('Content-Length', '-')))
logger.info("%03d " % self.reqNum + '%s "%s %s" %s %s' %
(prefix, self.command, url, r.status,
r.getheader('Content-Length', '-')))
else:
logger.info("%03d " % self.reqNum + Fore.MAGENTA + '%s "%s %s %s" %s %s' %
(prefix, self.command, url, data_length, r.status, r.getheader('Content-Length', '-')))
logger.info("%03d " % self.reqNum + '%s "%s %s %s" %s %s' %
(prefix, self.command, url, data_length,
r.status, r.getheader('Content-Length', '-')))
self.send_response_only(r.status, r.reason)
# HTTPResponse.msg is easier to handle than urllib3._collections.HTTPHeaderDict
# HTTPResponse.msg is easier to handle
# than urllib3._collections.HTTPHeaderDict
r.headers = r._original_response.msg
self.purge_write_headers(r.headers)
if self.command == 'HEAD' or r.status in (100, 101, 204, 304) or r.getheader("Content-Length") == '0':
if (self.command == 'HEAD' or r.status in (100, 101, 204, 304)
or r.getheader("Content-Length") == '0'):
written = None
else:
written = self.stream_to_client(r)
if "Content-Length" not in r.headers and 'Transfer-Encoding' not in r.headers:
if ("Content-Length" not in r.headers
and 'Transfer-Encoding' not in r.headers):
self.close_connection = 1
# Intend to catch regular http and bypass http/https requests exceptions
# Intend to catch regular http and bypass
# http/https requests exceptions
# Regular https request exceptions should be handled by rear server
except urllib3.exceptions.TimeoutError as e:
self.sendout_error(url, 504, message="Timeout", explain=e)
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '[F] %s on "%s %s"', e, self.command, url)
logger.warning("%03d " % self.reqNum + '[F] %s on "%s %s"',
e, self.command, url)
except (urllib3.exceptions.HTTPError,) as e:
self.sendout_error(url, 502, message="HTTPError", explain=e)
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '[F] %s on "%s %s"', e, self.command, url)
logger.warning("%03d " % self.reqNum + '[F] %s on "%s %s"',
e, self.command, url)
finally:
if r:
# Release the connection back into the pool
@ -271,27 +314,30 @@ class FrontRequestHandler(ProxyRequestHandler):
do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = do_OPTIONS = do_METHOD
class RearRequestHandler(ProxyRequestHandler):
"""
Supposed to be the parent proxy for Proxomitron for tagged requests
Supposed to be the parent proxy for Privoxy for tagged requests
Convert http request to https
"""
server_version = "%s RearProxy/%s" % (_name, __version__)
server_version = "%s rear/%s" % (_name, __version__)
def do_METHOD(self):
"Convert http request to https"
if self.headers.get("Tagged") and self.headers["Tagged"].startswith(_name):
if (self.headers.get("Tagged")
and self.headers["Tagged"].startswith(_name)):
self.reqNum = int(self.headers["Tagged"].split(":")[1])
# Remove the tag
del self.headers["Tagged"]
else:
self.sendout_error(self.path, 400,
explain="The proxy setting of the client is misconfigured.\n\n" +
"Please set the HTTPS proxy port to %s " % config.FRONTPORT +
"and check the Docs for other settings.")
logger.error(Fore.RED + Style.BRIGHT + "[Misconfigured HTTPS proxy port] " + self.path)
self.sendout_error(
self.path, 400,
explain="The proxy setting of the client"
" is misconfigured.\n\n" +
"Please set the HTTPS proxy port to %s " % config.FRONTPORT +
"and check the Docs for other settings.")
logger.error("[Misconfigured HTTPS proxy port] " + self.path)
return
# request line: GET http://somehost.com/path?attr=value HTTP/1.1
@ -300,12 +346,15 @@ class RearRequestHandler(ProxyRequestHandler):
proxy, pool, noverify = pools.getpool(self.host)
prefix = '[P]' if proxy else '[D]'
data_length = self.headers.get("Content-Length")
self.postdata = self.rfile.read(int(data_length)) if data_length else None
self.postdata = (self.rfile.read(int(data_length))
if data_length else None)
self.purge_headers(self.headers)
r = None
# Below code in connectionpool.py expect the headers to has a copy() and update() method
# That's why we can't use self.headers directly when call pool.urlopen()
# Below code in connectionpool.py expect the headers
# to has a copy() and update() method
# That's why we can't use self.headers directly when
# call pool.urlopen()
#
# Merge the proxy headers. Only do this in HTTP. We have to copy the
# headers dict so we can safely change it without those changes being
@ -316,39 +365,51 @@ class RearRequestHandler(ProxyRequestHandler):
headers = urllib3._collections.HTTPHeaderDict(self.headers)
try:
r = pool.urlopen(self.command, url, body=self.postdata, headers=headers,
retries=1, redirect=False, preload_content=False, decode_content=False)
r = pool.urlopen(
self.command, url, body=self.postdata, headers=headers,
retries=1, redirect=False, preload_content=False,
decode_content=False)
if proxy:
logger.debug('Using Proxy - %s' % proxy)
color = Fore.RED if noverify else Fore.GREEN
if self.command in ("GET", "HEAD"):
logger.info("%03d " % self.reqNum + color + '%s "%s %s" %s %s' %
(prefix, self.command, url, r.status, r.getheader('Content-Length', '-')))
logger.info(
"%03d " % self.reqNum + '%s "%s %s" %s %s' %
(prefix, self.command, url, r.status,
r.getheader('Content-Length', '-')))
else:
logger.info("%03d " % self.reqNum + color + '%s "%s %s %s" %s %s' %
(prefix, self.command, url, data_length, r.status, r.getheader('Content-Length', '-')))
logger.info(
"%03d " % self.reqNum + '%s "%s %s %s" %s %s' %
(prefix, self.command, url, data_length, r.status,
r.getheader('Content-Length', '-')))
self.send_response_only(r.status, r.reason)
# HTTPResponse.msg is easier to handle than urllib3._collections.HTTPHeaderDict
# HTTPResponse.msg is easier to handle than
# urllib3._collections.HTTPHeaderDict
r.headers = r._original_response.msg
self.purge_write_headers(r.headers)
if self.command == 'HEAD' or r.status in (100, 101, 204, 304) or r.getheader("Content-Length") == '0':
if (self.command == 'HEAD' or r.status in (100, 101, 204, 304)
or r.getheader("Content-Length") == '0'):
written = None
else:
written = self.stream_to_client(r)
if "Content-Length" not in r.headers and 'Transfer-Encoding' not in r.headers:
if ("Content-Length" not in r.headers
and 'Transfer-Encoding' not in r.headers):
self.close_connection = 1
except urllib3.exceptions.SSLError as e:
self.sendout_error(url, 417, message="SSL Certificate Failed", explain=e)
logger.error("%03d " % self.reqNum + Fore.RED + Style.BRIGHT + "[SSL Certificate Error] " + url)
self.sendout_error(url, 417, message="SSL Certificate Failed",
explain=e)
logger.error("%03d " % self.reqNum + "[SSL Certificate Error] "
+ url)
except urllib3.exceptions.TimeoutError as e:
self.sendout_error(url, 504, message="Timeout", explain=e)
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '[R]%s "%s %s" %s', prefix, self.command, url, e)
logger.warning("%03d " % self.reqNum + '[R]%s "%s %s" %s',
prefix, self.command, url, e)
except (urllib3.exceptions.HTTPError,) as e:
self.sendout_error(url, 502, message="HTTPError", explain=e)
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '[R]%s "%s %s" %s', prefix, self.command, url, e)
logger.warning("%03d " % self.reqNum + '[R]%s "%s %s" %s',
prefix, self.command, url, e)
finally:
if r:
@ -357,51 +418,53 @@ class RearRequestHandler(ProxyRequestHandler):
do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = do_OPTIONS = do_METHOD
"""
#Information#
* Python default ciphers: http://bugs.python.org/issue20995
* SSL Cipher Suite Details of Your Browser: https://cc.dcsec.uni-hannover.de/
* https://wiki.mozilla.org/Security/Server_Side_TLS
"""
try:
if os.name == 'nt':
import ctypes
ctypes.windll.kernel32.SetConsoleTitleW('%s %s' % (_name, __version__))
config = LoadConfig(CONFIG)
logger = logging.getLogger(__name__)
def main():
urllib3.disable_warnings()
logger.setLevel(getattr(logging, config.LOGLEVEL, logging.INFO))
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(message)s', datefmt='[%H:%M]')
handler.setFormatter(formatter)
logger.addHandler(handler)
pools = ConnectionPools(CONFIG)
proxpool = urllib3.ProxyManager(config.PROXADDR, num_pools=10, maxsize=8,
# A little longer than timeout of rear pool
# to avoid trigger front server exception handler
timeout=urllib3.util.timeout.Timeout(connect=90.0, read=310.0))
frontserver = FrontServer(('', config.FRONTPORT), FrontRequestHandler)
rearserver = RearServer(('', config.REARPORT), RearRequestHandler)
frontserver.config = config
for worker in (frontserver.serve_forever, rearserver.serve_forever,
pools.reloadConfig):
thread = threading.Thread(target=worker)
thread.daemon = True
thread.start()
thread = threading.Thread(target=worker)
thread.daemon = True
thread.start()
print("=" * 76)
print("=" * 40)
print('%s %s (urllib3/%s)' % (_name, __version__, urllib3.__version__))
print()
print(' FrontServer : localhost:%s' % config.FRONTPORT)
print(' RearServer : localhost:%s' % config.REARPORT)
print(' ParentServer : %s' % config.DEFAULTPROXY)
print(' Proxomitron : ' + config.PROXADDR)
print("=" * 76)
print('Front : localhost:%s' % config.FRONTPORT)
print('Privoxy :', config.PROXADDR)
print('Rear : localhost:%s' % config.REARPORT)
print('Parent : %s' % config.GeneralPROXY)
print("=" * 40)
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Quitting...")
if __name__ == '__main__':
parser = argparse.ArgumentParser('Privoxy TLS proxy wrapper.')
parser.add_argument('-c', '--config', type=argparse.FileType('r'),
default='config.ini',
help='Privoxy TLS configuration file.')
args = parser.parse_args()
# globals
CONFIG = args.config.name
logger = logging.getLogger(__name__)
config = LoadConfig(CONFIG)
proxpool = urllib3.ProxyManager(
config.PROXADDR, num_pools=10, maxsize=8,
timeout=urllib3.util.timeout.Timeout(
connect=90.0, read=310.0))
pools = ConnectionPools(CONFIG)
counter = Counter()
try:
main()
except KeyboardInterrupt:
print("Quitting...")

View File

@ -1,13 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"HTTP Proxy Tools, pyOpenSSL version"
"HTTP Proxy utilities, pyOpenSSL version"
_name = "ProxyTool"
__author__ = 'phoenix'
__version__ = '1.0'
import time
from datetime import datetime
import logging
import threading
@ -15,40 +12,45 @@ import cgi
import socket
import select
import ssl
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from CertTool import get_cert
from colorama import init, Fore, Back, Style
init(autoreset=True)
from cert import get_cert
_name = 'proxy'
logger = logging.getLogger('__main__')
message_format = """\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
message_format = '''\
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Proxy Error: %(code)d</title>
</head>
<body>
<h1>%(code)d: %(message)s</h1>
<p>The following error occurred while trying to access <strong>%(url)s</strong></p>
<p><strong>%(explain)s</strong></p>
<hr>Generated on %(now)s by %(server)s.
</body>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Proxy Error: {code}</title>
</head>
<body>
<h1>{code}: {message}</h1>
<p>The following error occurred while
trying to access <strong>{url}</strong>
</p>
<p><strong>{explain}</strong></p>
<hr>Generated on {now} by {server}.
</body>
</html>
"""
'''
def read_write(socket1, socket2, max_idling=10):
"Read and Write contents between 2 sockets"
'''
Read and Write contents between 2 sockets
'''
iw = [socket1, socket2]
ow = []
count = 0
while True:
count += 1
(ins, _, exs) = select.select(iw, ow, iw, 1)
if exs: break
if exs:
break
if ins:
for reader in ins:
writer = socket2 if reader is socket1 else socket1
@ -57,26 +59,32 @@ def read_write(socket1, socket2, max_idling=10):
if data:
writer.send(data)
count = 0
except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError):
except (ConnectionAbortedError,
ConnectionResetError,
BrokenPipeError):
pass
if count == max_idling: break
if count == max_idling:
break
class Counter:
reset_value = 999
def __init__(self, start=0):
self.lock = threading.Lock()
self.value = start
def increment_and_set(self, obj, attr):
with self.lock:
self.value = self.value + 1 if self.value < self.reset_value else 1
setattr(obj, attr, self.value)
counter = Counter()
class ProxyRequestHandler(BaseHTTPRequestHandler):
"""RequestHandler with do_CONNECT method defined
"""
server_version = "%s/%s" % (_name, __version__)
'''
RequestHandler with do_CONNECT method defined
'''
server_version = f'{_name}/{__version__}'
# do_CONNECT() will set self.ssltunnel to override this
ssltunnel = False
# Override default value 'HTTP/1.0'
@ -85,19 +93,27 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
reqNum = 0
def do_CONNECT(self):
"Descrypt https request and dispatch to http handler"
'''
Descrypt https request and dispatch to http handler
'''
# request line: CONNECT www.example.com:443 HTTP/1.1
self.host, self.port = self.path.split(":")
self.host, self.port = self.path.split(':')
# SSL MITM
self.wfile.write(("HTTP/1.1 200 Connection established\r\n" +
"Proxy-agent: %s\r\n" % self.version_string() +
"\r\n").encode('ascii'))
commonname = '.' + self.host.partition('.')[-1] if self.host.count('.') >= 2 else self.host
dummycert = get_cert(commonname)
self.wfile.write(
('HTTP/1.1 200 Connection established\r\n'
+ f'Proxy-agent: {self.version_string()}\r\n'
+ '\r\n').encode('ascii'))
if self.host.count('.') >= 2:
commonname = '.' + self.host.partition('.')[-1]
else:
commonname = self.host
dummycert = get_cert(commonname, self.config)
# set a flag for do_METHOD
self.ssltunnel = True
ssl_sock = ssl.wrap_socket(self.connection, keyfile=dummycert, certfile=dummycert, server_side=True)
ssl_sock = ssl.wrap_socket(self.connection, keyfile=dummycert,
certfile=dummycert, server_side=True)
# Ref: Lib/socketserver.py#StreamRequestHandler.setup()
self.connection = ssl_sock
self.rfile = self.connection.makefile('rb', self.rbufsize)
@ -106,26 +122,33 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
self.handle_one_request()
def handle_one_request(self):
"""Catch more exceptions than default
'''Catch more exceptions than default
Intend to catch exceptions on local side
Exceptions on remote side should be handled in do_*()
"""
'''
try:
BaseHTTPRequestHandler.handle_one_request(self)
return
except (ConnectionError, FileNotFoundError) as e:
logger.warning("%03d " % self.reqNum + Fore.RED + "%s %s", self.server_version, e)
logger.warning('{:03d} {} {}'.format(
self.reqNum, self.server_version, e))
except (ssl.SSLEOFError, ssl.SSLError) as e:
if hasattr(self, 'url'):
# Happens after the tunnel is established
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '"%s" while operating on established local SSL tunnel for [%s]' % (e, self.url))
logger.warning(f'{self.reqNum:03d} "{e}" while operating'
f' on established local SSL tunnel for'
f' [{self.url}]')
else:
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '"%s" while trying to establish local SSL tunnel for [%s]' % (e, self.path))
logger.warning(f'{self.reqNum:03d} "{e}" while trying to'
' establish local SSL tunnel for'
f'[{self.path}]')
self.close_connection = 1
def sendout_error(self, url, code, message=None, explain=None):
"Modified from http.server.send_error() for customized display"
'''
Modified from http.server.send_error() for customized display
'''
try:
shortmsg, longmsg = self.responses[code]
except KeyError:
@ -134,12 +157,14 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
message = shortmsg
if explain is None:
explain = longmsg
content = (message_format %
{'code': code, 'message': message, 'explain': explain,
'url': url, 'now': datetime.today(), 'server': self.server_version})
content = message_format.format(
code=code, message=message,
explain=explain, url=url,
now=datetime.today(),
server=self.server_version)
body = content.encode('UTF-8', 'replace')
self.send_response_only(code, message)
self.send_header("Content-Type", self.error_content_type)
self.send_header('Content-Type', self.error_content_type)
self.send_header('Content-Length', int(len(body)))
self.end_headers()
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
@ -157,13 +182,16 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
self.end_headers()
def forward_to_https_proxy(self):
"Forward https request to upstream https proxy"
logger.debug('Using Proxy - %s' % self.proxy)
'''
Forward https request to upstream https proxy
'''
logger.debug(f'Using Proxy - {self.proxy}')
proxy_host, proxy_port = self.proxy.split('//')[1].split(':')
server_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_conn.connect((proxy_host, int(proxy_port)))
server_conn.send(('CONNECT %s HTTP/1.1\r\n\r\n' % self.path).encode('ascii'))
server_conn.send((
f'CONNECT {self.path} HTTP/1.1\r\n\r\n').encode('ascii'))
server_conn.settimeout(0.1)
datas = b''
while True:
@ -177,47 +205,66 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
break
server_conn.setblocking(True)
if b'200' in datas and b'established' in datas.lower():
logger.info("%03d " % self.reqNum + Fore.CYAN + '[P] SSL Pass-Thru: https://%s/' % self.path)
self.wfile.write(("HTTP/1.1 200 Connection established\r\n" +
"Proxy-agent: %s\r\n\r\n" % self.version_string()).encode('ascii'))
logger.info('{:03d} [P] SSL Pass-Thru: https://{}/'.format(
self.reqNum, self.path))
self.wfile.write(('HTTP/1.1 200 Connection established\r\n' +
'Proxy-agent: {}\r\n\r\n'.format(
self.version_string()).encode('ascii')))
read_write(self.connection, server_conn)
else:
logger.warning("%03d " % self.reqNum + Fore.YELLOW + 'Proxy %s failed.', self.proxy)
logger.warning('{:03d} Proxy {} failed.'.format(
self.reqNum, self.proxy))
if datas:
logger.debug(datas)
self.wfile.write(datas)
finally:
# We don't maintain a connection reuse pool, so close the connection anyway
# We don't maintain a connection reuse pool,
# so close the connection anyway
server_conn.close()
def forward_to_socks5_proxy(self):
"Forward https request to upstream socks5 proxy"
logger.warning(Fore.YELLOW + 'Socks5 proxy not implemented yet, please use https proxy')
'''
Forward https request to upstream socks5 proxy
'''
logger.warning('Socks5 proxy not implemented yet, '
'please use https proxy')
def tunnel_traffic(self):
"Tunnel traffic to remote host:port"
logger.info("%03d " % self.reqNum + Fore.CYAN + '[D] SSL Pass-Thru: https://%s/' % self.path)
'''
Tunnel traffic to remote host:port
'''
logger.info('{:03d} [D] SSL Pass-Thru: https://{}/'.format(
self.reqNum, self.path))
server_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_conn.connect((self.host, int(self.port)))
self.wfile.write(("HTTP/1.1 200 Connection established\r\n" +
"Proxy-agent: %s\r\n" % self.version_string() +
"\r\n").encode('ascii'))
self.wfile.write(('HTTP/1.1 200 Connection established\r\n'
+ 'Proxy-agent: {}\r\n'.format(
self.version_string())
+ '\r\n').encode('ascii'))
read_write(self.connection, server_conn)
except TimeoutError:
self.wfile.write(b"HTTP/1.1 504 Gateway Timeout\r\n\r\n")
logger.warning("%03d " % self.reqNum + Fore.YELLOW + 'Timed Out: https://%s:%s/' % (self.host, self.port))
self.wfile.write(b'HTTP/1.1 504 Gateway Timeout\r\n\r\n')
logger.warning('{:03d} Timed Out: https://{}:{}/'.format(
self.reqNum, self.host, self.port))
except socket.gaierror as e:
self.wfile.write(b"HTTP/1.1 503 Service Unavailable\r\n\r\n")
logger.warning("%03d " % self.reqNum + Fore.YELLOW + '%s: https://%s:%s/' % (e, self.host, self.port))
self.wfile.write(b'HTTP/1.1 503 Service Unavailable\r\n\r\n')
logger.warning('{:03d} {}: https://{}:{}/'.format(
self.reqNum, e, self.host, self.port))
finally:
# We don't maintain a connection reuse pool, so close the connection anyway
# We don't maintain a connection reuse pool,
# so close the connection anyway
server_conn.close()
def ssl_get_response(self, conn):
try:
server_conn = ssl.wrap_socket(conn, cert_reqs=ssl.CERT_REQUIRED, ca_certs="cacert.pem", ssl_version=ssl.PROTOCOL_TLSv1)
server_conn.sendall(('%s %s HTTP/1.1\r\n' % (self.command, self.path)).encode('ascii'))
server_conn = ssl.wrap_socket(
conn, cert_reqs=ssl.CERT_REQUIRED,
ca_certs="cacert.pem",
ssl_version=ssl.PROTOCOL_TLSv1)
server_conn.sendall(
('{} {} HTTP/1.1\r\n'.format(
self.command, self.path)).encode('ascii'))
server_conn.sendall(self.headers.as_bytes())
if self.postdata:
server_conn.sendall(self.postdata)
@ -225,15 +272,19 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
data = server_conn.recv(4096)
if data:
self.wfile.write(data)
else: break
else:
break
except (ssl.SSLEOFError, ssl.SSLError) as e:
logger.error(Fore.RED + Style.BRIGHT + "[SSLError]")
self.send_error(417, message="Exception %s" % str(e.__class__), explain=str(e))
logger.error("[SSLError]")
self.send_error(417, message="Exception "
+ str(e.__class__), explain=str(e))
def purge_headers(self, headers):
"Remove hop-by-hop headers that shouldn't pass through a Proxy"
for name in ["Connection", "Keep-Alive", "Upgrade",
"Proxy-Connection", "Proxy-Authenticate"]:
'''
Remove hop-by-hop headers that shouldn't pass through a Proxy
'''
for name in ['Connection', 'Keep-Alive', 'Upgrade',
'Proxy-Connection', 'Proxy-Authenticate']:
del headers[name]
def purge_write_headers(self, headers):
@ -241,7 +292,7 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
for key, value in headers.items():
self.send_header(key, value)
self.end_headers()
def stream_to_client(self, response):
bufsize = 1024 * 64
need_chunked = 'Transfer-Encoding' in response.headers
@ -253,34 +304,35 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(b'0\r\n\r\n')
break
if need_chunked:
self.wfile.write(('%x\r\n' % len(data)).encode('ascii'))
self.wfile.write((f'{len(data):x}\r\n').encode('ascii'))
self.wfile.write(data)
if need_chunked:
self.wfile.write(b'\r\n')
written += len(data)
return written
def http_request_info(self):
"""Return HTTP request information in bytes
"""
context = ["CLIENT VALUES:",
"client_address = %s" % str(self.client_address),
"requestline = %s" % self.requestline,
"command = %s" % self.command,
"path = %s" % self.path,
"request_version = %s" % self.request_version,
"",
"SERVER VALUES:",
"server_version = %s" % self.server_version,
"sys_version = %s" % self.sys_version,
"protocol_version = %s" % self.protocol_version,
"",
"HEADER RECEIVED:"]
for name, value in sorted(self.headers.items()):
context.append("%s = %s" % (name, value.rstrip()))
if self.command == "POST":
context.append("\r\nPOST VALUES:")
def http_request_info(self):
'''
Return HTTP request information in bytes
'''
context = ['CLIENT VALUES:',
f'client_address = {self.client_address}',
f'requestline = {self.requestline}',
f'command = {self.command}',
f'path = {self.path}',
f'request_version = {self.request_version}',
'',
'SERVER VALUES:',
'server_version = {self.server_version}',
'sys_version = {self.sys_version}',
'protocol_version = {self.protocol_version}',
'',
'HEADER RECEIVED:']
for name, value in sorted(self.headers.items()):
context.append(f'{name} = {value.rstrip()}')
if self.command == 'POST':
context.append('\r\nPOST VALUES:')
form = cgi.FieldStorage(fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST'})
@ -290,27 +342,28 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
# The field contains an uploaded file
file_data = fielditem.file.read()
file_len = len(file_data)
context.append('Uploaded %s as "%s" (%d bytes)'
% (field, fielditem.filename, file_len))
context.append('Uploaded {} as "{}" ({} bytes)'.format(
field, fielditem.filename, file_len))
else:
# Regular form value
context.append("%s = %s" % (field, fielditem.value))
return("\r\n".join(context).encode('ascii'))
context.append(f'{field} = {fielditem.value}')
return('\r\n'.join(context).encode('ascii'))
def demo():
PORT = 8000
class ProxyServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
'''Handle requests in a separate thread.'''
pass
class RequestHandler(ProxyRequestHandler):
"Displaying HTTP request information"
server_version = "DemoProxy/0.1"
'Displaying HTTP request information'
server_version = 'DemoProxy/0.1'
def do_METHOD(self):
"Universal method for GET, POST, HEAD, PUT and DELETE"
'Universal method for GET, POST, HEAD, PUT and DELETE'
message = self.http_request_info()
self.send_response(200)
# 'Content-Length' is important for HTTP/1.1
@ -318,16 +371,18 @@ def demo():
self.end_headers()
self.wfile.write(message)
do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = do_OPTIONS = do_METHOD
do_GET = do_POST = do_HEAD = do_METHOD
do_PUT = do_DELETE = do_OPTIONS = do_METHOD
print('%s serving now, <Ctrl-C> to stop ...' % RequestHandler.server_version)
print('Listen Addr : localhost:%s' % PORT)
print("-" * 10)
print(RequestHandler.server_version, 'serving now, <Ctrl-C> to stop ...')
print(f'Listen Addr : localhost:{PORT}')
print('-' * 10)
server = ProxyServer(('', PORT), RequestHandler)
server.serve_forever()
if __name__ == '__main__':
try:
demo()
except KeyboardInterrupt:
print("Quitting...")
print('Quitting...')