aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libpathod/pathod.py21
-rw-r--r--libpathod/templates/docs_pathod.html16
-rw-r--r--libpathod/test.py1
-rw-r--r--libpathod/utils.py13
-rwxr-xr-xpathod30
-rw-r--r--test/test_test.py1
-rw-r--r--test/test_utils.py10
7 files changed, 77 insertions, 15 deletions
diff --git a/libpathod/pathod.py b/libpathod/pathod.py
index d52af15b..587e51bf 100644
--- a/libpathod/pathod.py
+++ b/libpathod/pathod.py
@@ -1,7 +1,7 @@
import urllib, threading, re, logging, socket, sys, base64
from netlib import tcp, http, odict, wsgi
import netlib.utils
-import version, app, language
+import version, app, language, utils
logger = logging.getLogger('pathod')
@@ -54,13 +54,24 @@ class PathodHandler(tcp.BaseHandler):
# Normal termination
return False, None
- parts = http.parse_init_http(line)
- if not parts:
+ m = utils.MemBool()
+ if m(http.parse_init_connect(line)):
+ self.wfile.write(
+ 'HTTP/1.1 200 Connection established\r\n' +
+ ('Proxy-agent: %s\r\n'%version.NAMEVERSION) +
+ '\r\n'
+ )
+ self.wfile.flush()
+
+ if m(http.parse_init_proxy(line)):
+ method, _, _, _, path, httpversion = m.v
+ elif m(http.parse_init_http(line)):
+ method, path, httpversion = m.v
+ else:
s = "Invalid first line: %s"%repr(line)
self.info(s)
return False, dict(type = "error", msg = s)
- method, path, httpversion = parts
headers = http.read_headers(self.rfile)
if headers is None:
s = "Invalid headers"
@@ -133,7 +144,7 @@ class PathodHandler(tcp.BaseHandler):
self.info("\n".join(s))
def handle(self):
- if self.server.ssloptions:
+ if self.server.ssloptions and not self.server.ssloptions["ssl_after_connect"]:
try:
self.convert_to_ssl(
self.server.ssloptions["certfile"],
diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html
index 8b345d71..42459352 100644
--- a/libpathod/templates/docs_pathod.html
+++ b/libpathod/templates/docs_pathod.html
@@ -45,6 +45,22 @@ those, use the command-line help:</p>
</section>
+<section>
+ <div class="page-header">
+ <h1>Acting as a proxy</h1>
+ </div>
+
+<p>Pathod automatically responds to both straight HTTP and proxy requests. For
+proxy requests, the upstream host is ignored, and the path portion of the URL
+is used to match anchors. This lets you test software that supports a proxy
+configuration by spoofing responses from upstream servers.</p>
+
+<p>Proxy mode operates even when Pathod is run in SSL mode, but we do not
+support nested SSL connections. This means that CONNECT requests will cause an
+error response.</p>
+
+</section>
+
<section>
<div class="page-header">
diff --git a/libpathod/test.py b/libpathod/test.py
index ed0ee26a..22dc035d 100644
--- a/libpathod/test.py
+++ b/libpathod/test.py
@@ -75,6 +75,7 @@ class _PaThread(threading.Thread):
ssloptions = dict(
keyfile = utils.data.path("resources/server.key"),
certfile = utils.data.path("resources/server.crt"),
+ ssl_after_connect = False
)
else:
ssloptions = self.ssl
diff --git a/libpathod/utils.py b/libpathod/utils.py
index 70a97cff..26e9427d 100644
--- a/libpathod/utils.py
+++ b/libpathod/utils.py
@@ -8,6 +8,19 @@ SIZE_UNITS = dict(
t = 1024**4,
)
+
+class MemBool:
+ """
+ Truth-checking with a memory, for use in chained if statements.
+ """
+ def __init__(self):
+ self.v = None
+
+ def __call__(self, v):
+ self.v = v
+ return bool(v)
+
+
def parse_size(s):
try:
return int(s)
diff --git a/pathod b/pathod
index 27e7e040..685352ba 100755
--- a/pathod
+++ b/pathod
@@ -36,12 +36,13 @@ def main(parser, args):
parser.error("Both --certfile and --keyfile must be specified.")
if args.ssl:
- ssl = dict(
+ ssloptions = dict(
keyfile = args.ssl_keyfile or utils.data.path("resources/server.key"),
certfile = args.ssl_certfile or utils.data.path("resources/server.crt"),
+ ssl_after_connect = args.ssl_after_connect
)
else:
- ssl = None
+ ssloptions = None
alst = []
for i in args.anchors:
@@ -81,7 +82,7 @@ def main(parser, args):
pd = pathod.Pathod(
(args.address, args.port),
craftanchor = args.craftanchor,
- ssloptions = ssl,
+ ssloptions = ssloptions,
staticdir = args.staticdir,
anchors = alst,
sizelimit = sizelimit,
@@ -93,7 +94,7 @@ def main(parser, args):
logreq = args.logreq,
logresp = args.logresp,
hexdump = args.hexdump,
- explain = args.explain
+ explain = args.explain,
)
except pathod.PathodError, v:
parser.error(str(v))
@@ -128,10 +129,6 @@ if __name__ == "__main__":
help='Daemonize.'
)
parser.add_argument(
- "-s", dest='ssl', default=False, action="store_true",
- help='Serve with SSL.'
- )
- parser.add_argument(
"-t", dest="timeout", type=int, default=None,
help="Connection timeout"
)
@@ -155,11 +152,24 @@ if __name__ == "__main__":
"--nocraft", dest='nocraft', default=False, action="store_true",
help='Disable response crafting. If anchors are specified, they still work.'
)
- parser.add_argument(
+
+
+ group = parser.add_argument_group(
+ 'SSL',
+ )
+ group.add_argument(
+ "-C", dest='ssl_after_connect', default=False, action="store_true",
+ help='Expect SSL after a CONNECT request.'
+ )
+ group.add_argument(
+ "-s", dest='ssl', default=False, action="store_true",
+ help='Serve with SSL.'
+ )
+ group.add_argument(
"--keyfile", dest='ssl_keyfile', default=None, type=str,
help='SSL key file. If not specified, a default key is used.'
)
- parser.add_argument(
+ group.add_argument(
"--certfile", dest='ssl_certfile', default=None, type=str,
help='SSL cert file. If not specified, a default cert is used.'
)
diff --git a/test/test_test.py b/test/test_test.py
index fc97d263..89889ba1 100644
--- a/test/test_test.py
+++ b/test/test_test.py
@@ -25,6 +25,7 @@ class TestDaemonManual:
ssloptions = dict(
keyfile = utils.data.path("resources/server.key"),
certfile = utils.data.path("resources/server.crt"),
+ ssl_after_connect = False
)
d = test.Daemon(ssl=ssloptions)
rsp = requests.get("https://localhost:%s/p/202:da"%d.port, verify=False)
diff --git a/test/test_utils.py b/test/test_utils.py
index 0a48d87d..b8aa7f12 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -1,6 +1,16 @@
from libpathod import utils
import tutils
+
+def test_membool():
+ m = utils.MemBool()
+ assert not m.v
+ assert m(1)
+ assert m.v == 1
+ assert m(2)
+ assert m.v == 2
+
+
def test_parse_size():
assert utils.parse_size("100") == 100
assert utils.parse_size("100k") == 100 * 1024