[tor-commits] [flashproxy/master] split Makefile into source-level vs binary-level packaging scripts

infinity0 at torproject.org infinity0 at torproject.org
Tue Nov 5 23:54:35 UTC 2013


commit f6ca5f1d2cfd7b32a957cba14624773089a3e27f
Author: Ximin Luo <infinity0 at gmx.com>
Date:   Tue Oct 15 23:18:11 2013 +0100

    split Makefile into source-level vs binary-level packaging scripts
    - Makefile retains the same behaviour as before and builds dist/dist-exe
    - Makefile.client acts on the client component only, following GNU standards
---
 Makefile                  |  133 ++++++++-------
 Makefile.client           |   98 +++++++++++
 flashproxy-client-test    |  402 ---------------------------------------------
 flashproxy-client-test.py |  401 ++++++++++++++++++++++++++++++++++++++++++++
 setup-client-exe.py       |    1 +
 setup-common.py           |   20 +++
 6 files changed, 595 insertions(+), 460 deletions(-)

diff --git a/Makefile b/Makefile
index d8c85cb..24ef960 100644
--- a/Makefile
+++ b/Makefile
@@ -1,71 +1,88 @@
+# Makefile for a self-contained binary distribution of flashproxy-client.
+#
+# This builds two zipball targets, dist and dist-exe, for POSIX and Windows
+# respectively. Both can be extracted and run in-place by the end user.
+# (PGP-signed forms also exist, sign and sign-exe.)
+#
+# If you are a distro packager, instead see the separate build scripts for each
+# source component, all of which have an `install` target:
+# - client: Makefile.client
+# - common: setup-common.py
+# - facilitator: facilitator/{configure.ac,Makefile.am}
+#
+# Not for the faint-hearted: it is possible to build dist-exe on GNU/Linux by
+# using wine to install the windows versions of Python, py2exe, and m2crypto,
+# then running `make PYTHON_W32="wine python" dist-exe`.
+
+PACKAGE = flashproxy-client
 VERSION = 1.3
+DISTNAME = $(PACKAGE)-$(VERSION)
 
-DESTDIR =
-PREFIX = /usr/local
-BINDIR = $(PREFIX)/bin
-MANDIR = $(PREFIX)/share/man
+THISFILE = $(lastword $(MAKEFILE_LIST))
+PYTHON = python
+PYTHON_W32 = $(PYTHON)
 
-PYTHON ?= python
-export PY2EXE_TMPDIR = py2exe-tmp
+MAKE_CLIENT = $(MAKE) -f Makefile.client PYTHON="$(PYTHON)"
 
-CLIENT_BIN = flashproxy-client flashproxy-reg-appspot flashproxy-reg-email flashproxy-reg-http flashproxy-reg-url
-CLIENT_MAN = doc/flashproxy-client.1 doc/flashproxy-reg-appspot.1 doc/flashproxy-reg-email.1 doc/flashproxy-reg-http.1 doc/flashproxy-reg-url.1
-CLIENT_DIST_FILES = $(CLIENT_BIN) README LICENSE ChangeLog torrc
-CLIENT_DIST_DOC_FILES = $(CLIENT_MAN)
-CLIENT_DIST_LIB_COMMON = flashproxy/__init__.py flashproxy/keys.py flashproxy/util.py
+# all is N/A for a binary package, but include for completeness
+all: dist
 
-all: $(CLIENT_DIST_FILES) $(CLIENT_MAN)
-	:
+DISTDIR = dist/$(DISTNAME)
+$(DISTDIR): Makefile.client setup-common.py $(THISFILE)
+	mkdir -p $(DISTDIR)
+	$(MAKE_CLIENT) DESTDIR=$(DISTDIR) bindir=/ docdir=/ man1dir=/doc/ \
+	  install
+	$(PYTHON) setup-common.py build_py -d $(DISTDIR)
 
-%.1: %.1.txt
-	rm -f $@
-	a2x --no-xmllint --xsltproc-opts "--stringparam man.th.title.max.length 24" -d manpage -f manpage $<
+dist/%.zip: dist/%
+	cd dist && zip -q -r -9 "$(@:dist/%=%)" "$(<:dist/%=%)"
 
-install:
-	mkdir -p $(DESTDIR)$(BINDIR)
-	mkdir -p $(DESTDIR)$(MANDIR)/man1
-	cp -f $(CLIENT_BIN) $(DESTDIR)$(BINDIR)
-	cp -f $(CLIENT_MAN) $(DESTDIR)$(MANDIR)/man1
+dist/%.zip.asc: dist/%.zip
+	rm -f "$@"
+	gpg --sign --detach-sign --armor "$<"
+	gpg --verify "$@" "$<"
 
-DISTNAME = flashproxy-client-$(VERSION)
-DISTDIR = dist/$(DISTNAME)
-dist: $(CLIENT_MAN)
-	rm -rf dist
-	mkdir -p $(DISTDIR)
-	mkdir $(DISTDIR)/doc
-	cp -f $(CLIENT_DIST_FILES) $(DISTDIR)
-	cp -f $(CLIENT_DIST_DOC_FILES) $(DISTDIR)/doc
-	test -n "$(CLIENT_DIST_LIB_COMMON)" && \
-	  { mkdir $(DISTDIR)/flashproxy && \
-	  cp -f $(CLIENT_DIST_LIB_COMMON) $(DISTDIR)/flashproxy; } || true
-	cd dist && zip -q -r -9 $(DISTNAME).zip $(DISTNAME)
-
-dist/$(DISTNAME).zip: $(CLIENT_DIST_FILES)
-	$(MAKE) dist
-
-sign: dist/$(DISTNAME).zip
-	rm -f dist/$(DISTNAME).zip.asc
-	cd dist && gpg --sign --detach-sign --armor $(DISTNAME).zip
-	cd dist && gpg --verify $(DISTNAME).zip.asc $(DISTNAME).zip
-
-$(PY2EXE_TMPDIR)/dist: $(CLIENT_BIN)
-	rm -rf $(PY2EXE_TMPDIR)
-	$(PYTHON) setup-client-exe.py py2exe -q
-
-dist-exe: DISTNAME := $(DISTNAME)-win32
-dist-exe: CLIENT_BIN := $(PY2EXE_TMPDIR)/dist/*
-dist-exe: CLIENT_MAN := $(addsuffix .txt,$(CLIENT_MAN))
-dist-exe: CLIENT_DIST_LIB_COMMON :=# py2exe static-links dependencies
-# Delegate to the "dist" target using the substitutions above.
-dist-exe: $(PY2EXE_TMPDIR)/dist setup-client-exe.py dist
-
-clean:
-	rm -f *.pyc
+dist: force-dist $(DISTDIR).zip
+
+sign: force-dist $(DISTDIR).zip.asc
+
+PY2EXE_TMPDIR = py2exe-tmp
+export PY2EXE_TMPDIR
+$(PY2EXE_TMPDIR): setup-client-exe.py
+	$(PYTHON_W32) setup-client-exe.py py2exe -q
+
+DISTDIR_W32 = $(DISTDIR)-win32
+# below, we override DST_SCRIPT and DST_MAN1 for windows
+$(DISTDIR_W32): $(PY2EXE_TMPDIR) $(THISFILE)
+	mkdir -p $(DISTDIR_W32)
+	$(MAKE_CLIENT) DESTDIR=$(DISTDIR_W32) bindir=/ docdir=/ man1dir=/doc/ \
+	  DST_SCRIPT= DST_MAN1='$$(SRC_MAN1)' \
+	  install
+	cp -t $(DISTDIR_W32) $(PY2EXE_TMPDIR)/dist/*
+
+dist-exe: force-dist-exe $(DISTDIR_W32).zip
+
+sign-exe: force-dist-exe $(DISTDIR_W32).zip.asc
+
+# clean is N/A for a binary package, but include for completeness
+clean: distclean
+
+distclean:
+	$(MAKE_CLIENT) clean
+	$(PYTHON) setup-common.py clean --all
 	rm -rf dist $(PY2EXE_TMPDIR)
 
-test:
-	./flashproxy-client-test
+test: check
+check:
+	$(MAKE_CLIENT) check
+	$(PYTHON) setup-common.py check
 	cd facilitator && ./facilitator-test
 	cd proxy && ./flashproxy-test.js
 
-.PHONY: all install dist sign dist-exe clean test
+force-dist:
+	rm -rf $(DISTDIR) $(DISTDIR).zip
+
+force-dist-exe:
+	rm -rf $(DISTDIR_W32) $(DISTDIR_W32).zip $(PY2EXE_TMPDIR)
+
+.PHONY: all dist sign dist-exe sign-exe clean distclean test check force-dist force-dist-exe
diff --git a/Makefile.client b/Makefile.client
new file mode 100644
index 0000000..5aec18a
--- /dev/null
+++ b/Makefile.client
@@ -0,0 +1,98 @@
+# Makefile for a source distribution of flashproxy-client.
+#
+# This package is not self-contained and the build products may require other
+# dependencies to function; it is given as a reference for distro packagers.
+
+PACKAGE = flashproxy-client
+VERSION = 1.3
+DISTNAME = $(PACKAGE)-$(VERSION)
+DESTDIR =
+
+THISFILE = $(lastword $(MAKEFILE_LIST))
+PYTHON = python
+
+# GNU command variables
+# see http://www.gnu.org/prep/standards/html_node/Command-Variables.html
+
+INSTALL = install
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_PROGRAM = $(INSTALL)
+INSTALL_SCRIPT = $(INSTALL)
+
+# GNU directory variables
+# see http://www.gnu.org/prep/standards/html_node/Directory-Variables.html
+
+prefix = /usr/local
+exec_prefix = $(prefix)
+bindir = $(exec_prefix)/bin
+
+datarootdir = $(prefix)/share
+datadir = $(datarootdir)
+sysconfdir = $(prefix)/etc
+
+docdir = $(datarootdir)/doc/$(PACKAGE)
+mandir = $(datarootdir)/man
+man1dir = $(mandir)/man1
+
+srcdir = .
+
+SRC_MAN1 = doc/flashproxy-client.1.txt doc/flashproxy-reg-appspot.1.txt doc/flashproxy-reg-email.1.txt doc/flashproxy-reg-http.1.txt doc/flashproxy-reg-url.1.txt
+SRC_SCRIPT = flashproxy-client flashproxy-reg-appspot flashproxy-reg-email flashproxy-reg-http flashproxy-reg-url
+SRC_DOC = README LICENSE ChangeLog torrc
+SRC_ALL = $(SRC_SCRIPT) $(SRC_DOC) $(SRC_MAN1)
+
+DST_MAN1 = $(SRC_MAN1:%.1.txt=%.1)
+DST_SCRIPT = $(SRC_SCRIPT)
+DST_DOC = $(SRC_DOC)
+DST_ALL = $(DST_SCRIPT) $(DST_DOC) $(DST_MAN1)
+
+TEST_PY = flashproxy-client-test.py
+TEST_ALL = $(TEST_PY)
+
+all: $(DST_ALL) $(THISFILE)
+
+%.1: %.1.txt $(THISFILE)
+	rm -f $@
+	a2x --no-xmllint --xsltproc-opts "--stringparam man.th.title.max.length 24" -d manpage -f manpage $<
+
+install: all
+	mkdir -p $(DESTDIR)$(bindir)
+	for i in $(DST_SCRIPT); do $(INSTALL_SCRIPT) "$$i" $(DESTDIR)$(bindir); done
+	mkdir -p $(DESTDIR)$(docdir)
+	for i in $(DST_DOC); do $(INSTALL_DATA) "$$i" $(DESTDIR)$(docdir); done
+	mkdir -p $(DESTDIR)$(man1dir)
+	for i in $(DST_MAN1); do $(INSTALL_DATA) "$$i" $(DESTDIR)$(man1dir); done
+
+uninstall:
+	for i in $(notdir $(DST_SCRIPT)); do rm $(DESTDIR)$(bindir)/"$$i"; done
+	for i in $(notdir $(DST_DOC)); do rm $(DESTDIR)$(docdir)/"$$i"; done
+	for i in $(notdir $(DST_MAN1)); do rm $(DESTDIR)$(man1dir)/"$$i"; done
+
+clean:
+	rm -f *.pyc
+
+distclean: clean
+	rm -rf $(DISTNAME)
+	rm -f $(DISTNAME).tar.gz
+
+maintainer-clean: distclean
+	rm -f $(DST_MAN1)
+
+$(DISTNAME): $(SRC_ALL) $(TEST_ALL) $(THISFILE)
+	mkdir -p $@
+	cp --parents -t "$@" $^
+
+$(DISTNAME).tar.gz: $(DISTNAME)
+	tar czf "$@" "$<"
+
+# we never actually use this target, but it is given for completeness, in case
+# distro packagers want to do something with a client-only source tarball
+dist: force-dist $(DISTNAME).tar.gz
+
+check: $(THISFILE)
+	for i in $(TEST_PY); do $(PYTHON) "$$i"; done
+
+force-dist:
+	rm -rf $(DISTNAME) $(DISTNAME).tar.gz
+
+.PHONY: all install uninstall clean distclean maintainer-clean dist check force-dist
diff --git a/flashproxy-client-test b/flashproxy-client-test
deleted file mode 100755
index e3c6644..0000000
--- a/flashproxy-client-test
+++ /dev/null
@@ -1,402 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import base64
-import cStringIO
-import httplib
-import socket
-import subprocess
-import sys
-import unittest
-print sys.path
-try:
-    from hashlib import sha1
-except ImportError:
-    # Python 2.4 uses this name.
-    from sha import sha as sha1
-
-# Special tricks to load a module whose filename contains a dash and doesn't end
-# in ".py".
-import imp
-dont_write_bytecode = sys.dont_write_bytecode
-sys.dont_write_bytecode = True
-fp_client = imp.load_source("fp_client", "flashproxy-client")
-parse_socks_request = fp_client.parse_socks_request
-handle_websocket_request = fp_client.handle_websocket_request
-WebSocketDecoder = fp_client.WebSocketDecoder
-WebSocketEncoder = fp_client.WebSocketEncoder
-sys.dont_write_bytecode = dont_write_bytecode
-del dont_write_bytecode
-del fp_client
-
-LOCAL_ADDRESS = ("127.0.0.1", 40000)
-REMOTE_ADDRESS = ("127.0.0.1", 40001)
-
-class TestSocks(unittest.TestCase):
-    def test_parse_socks_request_empty(self):
-        self.assertRaises(ValueError, parse_socks_request, "")
-    def test_parse_socks_request_short(self):
-        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x01\x02\x03\x04")
-    def test_parse_socks_request_ip_userid_missing(self):
-        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04\x00")
-        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04\x00userid")
-        self.assertEqual((dest, port), ("1.2.3.4", 0x9999))
-    def test_parse_socks_request_ip(self):
-        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04userid\x00")
-        self.assertEqual((dest, port), ("1.2.3.4", 0x9999))
-    def test_parse_socks_request_hostname_missing(self):
-        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x00\x00\x00\x01userid\x00")
-        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x00\x00\x00\x01userid\x00abc")
-    def test_parse_socks_request_hostname(self):
-        dest, port = parse_socks_request("\x04\x01\x99\x99\x00\x00\x00\x01userid\x00abc\x00")
-
-class DummySocket(object):
-    def __init__(self, read_fd, write_fd):
-        self.read_fd = read_fd
-        self.write_fd = write_fd
-        self.readp = 0
-
-    def read(self, *args, **kwargs):
-        self.read_fd.seek(self.readp, 0)
-        data = self.read_fd.read(*args, **kwargs)
-        self.readp = self.read_fd.tell()
-        return data
-
-    def readline(self, *args, **kwargs):
-        self.read_fd.seek(self.readp, 0)
-        data = self.read_fd.readline(*args, **kwargs)
-        self.readp = self.read_fd.tell()
-        return data
-
-    def recv(self, size, *args, **kwargs):
-        return self.read(size)
-
-    def write(self, data):
-        self.write_fd.seek(0, 2)
-        self.write_fd.write(data)
-
-    def send(self, data, *args, **kwargs):
-        return self.write(data)
-
-    def sendall(self, data, *args, **kwargs):
-        return self.write(data)
-
-    def makefile(self, *args, **kwargs):
-        return self
-
-def dummy_socketpair():
-    f1 = cStringIO.StringIO()
-    f2 = cStringIO.StringIO()
-    return (DummySocket(f1, f2), DummySocket(f2, f1))
-
-class HTTPRequest(object):
-    def __init__(self):
-        self.method = "GET"
-        self.path = "/"
-        self.headers = {}
-
-def transact_http(req):
-    l, r = dummy_socketpair()
-    r.send("%s %s HTTP/1.0\r\n" % (req.method, req.path))
-    for k, v in req.headers.items():
-        r.send("%s: %s\r\n" % (k, v))
-    r.send("\r\n")
-    protocols = handle_websocket_request(l)
-
-    resp = httplib.HTTPResponse(r)
-    resp.begin()
-    return resp, protocols
-
-class TestHandleWebSocketRequest(unittest.TestCase):
-    DEFAULT_KEY = "0123456789ABCDEF"
-    DEFAULT_KEY_BASE64 = base64.b64encode(DEFAULT_KEY)
-    MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-
-    @staticmethod
-    def default_req():
-        req = HTTPRequest()
-        req.method = "GET"
-        req.path = "/"
-        req.headers["Upgrade"] = "websocket"
-        req.headers["Connection"] = "Upgrade"
-        req.headers["Sec-WebSocket-Key"] = TestHandleWebSocketRequest.DEFAULT_KEY_BASE64
-        req.headers["Sec-WebSocket-Version"] = "13"
-
-        return req
-
-    def assert_ok(self, req):
-        resp, protocols = transact_http(req)
-        self.assertEqual(resp.status, 101)
-        self.assertEqual(resp.getheader("Upgrade").lower(), "websocket")
-        self.assertEqual(resp.getheader("Connection").lower(), "upgrade")
-        self.assertEqual(resp.getheader("Sec-WebSocket-Accept"), base64.b64encode(sha1(self.DEFAULT_KEY_BASE64 + self.MAGIC_GUID).digest()))
-        self.assertEqual(protocols, [])
-
-    def assert_not_ok(self, req):
-        resp, protocols = transact_http(req)
-        self.assertEqual(resp.status // 100, 4)
-        self.assertEqual(protocols, None)
-
-    def test_default(self):
-        req = self.default_req()
-        self.assert_ok(req)
-
-    def test_missing_upgrade(self):
-        req = self.default_req()
-        del req.headers["Upgrade"]
-        self.assert_not_ok(req)
-
-    def test_missing_connection(self):
-        req = self.default_req()
-        del req.headers["Connection"]
-        self.assert_not_ok(req)
-
-    def test_case_insensitivity(self):
-        """Test that the values of the Upgrade and Connection headers are
-        case-insensitive."""
-        req = self.default_req()
-        req.headers["Upgrade"] = req.headers["Upgrade"].lower()
-        self.assert_ok(req)
-        req.headers["Upgrade"] = req.headers["Upgrade"].upper()
-        self.assert_ok(req)
-        req.headers["Connection"] = req.headers["Connection"].lower()
-        self.assert_ok(req)
-        req.headers["Connection"] = req.headers["Connection"].upper()
-        self.assert_ok(req)
-
-    def test_bogus_key(self):
-        req = self.default_req()
-        req.headers["Sec-WebSocket-Key"] = base64.b64encode(self.DEFAULT_KEY[:-1])
-        self.assert_not_ok(req)
-
-        req.headers["Sec-WebSocket-Key"] = "///"
-        self.assert_not_ok(req)
-
-    def test_versions(self):
-        req = self.default_req()
-        req.headers["Sec-WebSocket-Version"] = "13"
-        self.assert_ok(req)
-        req.headers["Sec-WebSocket-Version"] = "8"
-        self.assert_ok(req)
-
-        req.headers["Sec-WebSocket-Version"] = "7"
-        self.assert_not_ok(req)
-        req.headers["Sec-WebSocket-Version"] = "9"
-        self.assert_not_ok(req)
-
-        del req.headers["Sec-WebSocket-Version"]
-        self.assert_not_ok(req)
-
-    def test_protocols(self):
-        req = self.default_req()
-        req.headers["Sec-WebSocket-Protocol"] = "base64"
-        resp, protocols = transact_http(req)
-        self.assertEqual(resp.status, 101)
-        self.assertEqual(protocols, ["base64"])
-        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), "base64")
-
-        req = self.default_req()
-        req.headers["Sec-WebSocket-Protocol"] = "cat"
-        resp, protocols = transact_http(req)
-        self.assertEqual(resp.status, 101)
-        self.assertEqual(protocols, ["cat"])
-        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), None)
-
-        req = self.default_req()
-        req.headers["Sec-WebSocket-Protocol"] = "cat, base64"
-        resp, protocols = transact_http(req)
-        self.assertEqual(resp.status, 101)
-        self.assertEqual(protocols, ["cat", "base64"])
-        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), "base64")
-
-def read_frames(dec):
-    frames = []
-    while True:
-        frame = dec.read_frame()
-        if frame is None:
-            break
-        frames.append((frame.fin, frame.opcode, frame.payload))
-    return frames
-
-def read_messages(dec):
-    messages = []
-    while True:
-        message = dec.read_message()
-        if message is None:
-            break
-        messages.append((message.opcode, message.payload))
-    return messages
-
-class TestWebSocketDecoder(unittest.TestCase):
-    def test_rfc(self):
-        """Test samples from RFC 6455 section 5.7."""
-        TESTS = [
-            ("\x81\x05\x48\x65\x6c\x6c\x6f", False,
-                [(True, 1, "Hello")],
-                [(1, u"Hello")]),
-            ("\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58", True,
-                [(True, 1, "Hello")],
-                [(1, u"Hello")]),
-            ("\x01\x03\x48\x65\x6c\x80\x02\x6c\x6f", False,
-                [(False, 1, "Hel"), (True, 0, "lo")],
-                [(1, u"Hello")]),
-            ("\x89\x05\x48\x65\x6c\x6c\x6f", False,
-                [(True, 9, "Hello")],
-                [(9, u"Hello")]),
-            ("\x8a\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58", True,
-                [(True, 10, "Hello")],
-                [(10, u"Hello")]),
-            ("\x82\x7e\x01\x00" + "\x00" * 256, False,
-                [(True, 2, "\x00" * 256)],
-                [(2, "\x00" * 256)]),
-            ("\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x00" + "\x00" * 65536, False,
-                [(True, 2, "\x00" * 65536)],
-                [(2, "\x00" * 65536)]),
-            ("\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x03" + "ABCD" * 16384 + "XYZ", False,
-                [(True, 2, "ABCD" * 16384 + "XYZ")],
-                [(2, "ABCD" * 16384 + "XYZ")]),
-        ]
-        for data, use_mask, expected_frames, expected_messages in TESTS:
-            dec = WebSocketDecoder(use_mask = use_mask)
-            dec.feed(data)
-            actual_frames = read_frames(dec)
-            self.assertEqual(actual_frames, expected_frames)
-
-            dec = WebSocketDecoder(use_mask = use_mask)
-            dec.feed(data)
-            actual_messages = read_messages(dec)
-            self.assertEqual(actual_messages, expected_messages)
-
-            dec = WebSocketDecoder(use_mask = not use_mask)
-            dec.feed(data)
-            self.assertRaises(WebSocketDecoder.MaskingError, dec.read_frame)
-
-    def test_empty_feed(self):
-        """Test that the decoder can handle a zero-byte feed."""
-        dec = WebSocketDecoder()
-        self.assertEqual(dec.read_frame(), None)
-        dec.feed("")
-        self.assertEqual(dec.read_frame(), None)
-        dec.feed("\x81\x05H")
-        self.assertEqual(dec.read_frame(), None)
-        dec.feed("ello")
-        self.assertEqual(read_frames(dec), [(True, 1, u"Hello")])
-
-    def test_empty_frame(self):
-        """Test that a frame may contain a zero-byte payload."""
-        dec = WebSocketDecoder()
-        dec.feed("\x81\x00")
-        self.assertEqual(read_frames(dec), [(True, 1, u"")])
-        dec.feed("\x82\x00")
-        self.assertEqual(read_frames(dec), [(True, 2, "")])
-
-    def test_empty_message(self):
-        """Test that a message may have a zero-byte payload."""
-        dec = WebSocketDecoder()
-        dec.feed("\x01\x00\x00\x00\x80\x00")
-        self.assertEqual(read_messages(dec), [(1, u"")])
-        dec.feed("\x02\x00\x00\x00\x80\x00")
-        self.assertEqual(read_messages(dec), [(2, "")])
-
-    def test_interleaved_control(self):
-        """Test that control messages interleaved with fragmented messages are
-        returned."""
-        dec = WebSocketDecoder()
-        dec.feed("\x89\x04PING\x01\x03Hel\x8a\x04PONG\x80\x02lo\x89\x04PING")
-        self.assertEqual(read_messages(dec), [(9, "PING"), (10, "PONG"), (1, u"Hello"), (9, "PING")])
-
-    def test_fragmented_control(self):
-        """Test that illegal fragmented control messages cause an error."""
-        dec = WebSocketDecoder()
-        dec.feed("\x09\x04PING")
-        self.assertRaises(ValueError, dec.read_message)
-
-    def test_zero_opcode(self):
-        """Test that it is an error for the first frame in a message to have an
-        opcode of 0."""
-        dec = WebSocketDecoder()
-        dec.feed("\x80\x05Hello")
-        self.assertRaises(ValueError, dec.read_message)
-        dec = WebSocketDecoder()
-        dec.feed("\x00\x05Hello")
-        self.assertRaises(ValueError, dec.read_message)
-
-    def test_nonzero_opcode(self):
-        """Test that every frame after the first must have a zero opcode."""
-        dec = WebSocketDecoder()
-        dec.feed("\x01\x01H\x01\x02el\x80\x02lo")
-        self.assertRaises(ValueError, dec.read_message)
-        dec = WebSocketDecoder()
-        dec.feed("\x01\x01H\x00\x02el\x01\x02lo")
-        self.assertRaises(ValueError, dec.read_message)
-
-    def test_utf8(self):
-        """Test that text frames (opcode 1) are decoded from UTF-8."""
-        text = u"Hello World or Καλημέρα κόσμε or こんにちは 世界 or \U0001f639"
-        utf8_text = text.encode("utf-8")
-        dec = WebSocketDecoder()
-        dec.feed("\x81" + chr(len(utf8_text)) + utf8_text)
-        self.assertEqual(read_messages(dec), [(1, text)])
-
-    def test_wrong_utf8(self):
-        """Test that failed UTF-8 decoding causes an error."""
-        TESTS = [
-            "\xc0\x41", # Non-shortest form.
-            "\xc2", # Unfinished sequence.
-        ]
-        for test in TESTS:
-            dec = WebSocketDecoder()
-            dec.feed("\x81" + chr(len(test)) + test)
-            self.assertRaises(ValueError, dec.read_message)
-
-    def test_overly_large_payload(self):
-        """Test that large payloads are rejected."""
-        dec = WebSocketDecoder()
-        dec.feed("\x82\x7f\x00\x00\x00\x00\x01\x00\x00\x00")
-        self.assertRaises(ValueError, dec.read_frame)
-
-class TestWebSocketEncoder(unittest.TestCase):
-    def test_length(self):
-        """Test that payload lengths are encoded using the smallest number of
-        bytes."""
-        TESTS = [(0, 0), (125, 0), (126, 2), (65535, 2), (65536, 8)]
-        for length, encoded_length in TESTS:
-            enc = WebSocketEncoder(use_mask = False)
-            eframe = enc.encode_frame(2, "\x00" * length)
-            self.assertEqual(len(eframe), 1 + 1 + encoded_length + length)
-            enc = WebSocketEncoder(use_mask = True)
-            eframe = enc.encode_frame(2, "\x00" * length)
-            self.assertEqual(len(eframe), 1 + 1 + encoded_length + 4 + length)
-
-    def test_roundtrip(self):
-        TESTS = [
-            (1, u"Hello world"),
-            (1, u"Hello \N{WHITE SMILING FACE}"),
-        ]
-        for opcode, payload in TESTS:
-            for use_mask in (False, True):
-                enc = WebSocketEncoder(use_mask = use_mask)
-                enc_message = enc.encode_message(opcode, payload)
-                dec = WebSocketDecoder(use_mask = use_mask)
-                dec.feed(enc_message)
-                self.assertEqual(read_messages(dec), [(opcode, payload)])
-
-def format_address(addr):
-    return "%s:%d" % addr
-
-class TestConnectionLimit(unittest.TestCase):
-    def setUp(self):
-        self.p = subprocess.Popen(["./flashproxy-client", format_address(LOCAL_ADDRESS), format_address(REMOTE_ADDRESS)])
-
-    def tearDown(self):
-        self.p.terminate()
-
-#     def test_remote_limit(self):
-#         """Test that the client transport plugin limits the number of remote
-#         connections that it will accept."""
-#         for i in range(5):
-#             s = socket.create_connection(REMOTE_ADDRESS, 2)
-#         self.assertRaises(socket.error, socket.create_connection, REMOTE_ADDRESS)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/flashproxy-client-test.py b/flashproxy-client-test.py
new file mode 100755
index 0000000..0281f42
--- /dev/null
+++ b/flashproxy-client-test.py
@@ -0,0 +1,401 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import base64
+import cStringIO
+import httplib
+import socket
+import subprocess
+import sys
+import unittest
+try:
+    from hashlib import sha1
+except ImportError:
+    # Python 2.4 uses this name.
+    from sha import sha as sha1
+
+# Special tricks to load a module whose filename contains a dash and doesn't end
+# in ".py".
+import imp
+dont_write_bytecode = sys.dont_write_bytecode
+sys.dont_write_bytecode = True
+fp_client = imp.load_source("fp_client", "flashproxy-client")
+parse_socks_request = fp_client.parse_socks_request
+handle_websocket_request = fp_client.handle_websocket_request
+WebSocketDecoder = fp_client.WebSocketDecoder
+WebSocketEncoder = fp_client.WebSocketEncoder
+sys.dont_write_bytecode = dont_write_bytecode
+del dont_write_bytecode
+del fp_client
+
+LOCAL_ADDRESS = ("127.0.0.1", 40000)
+REMOTE_ADDRESS = ("127.0.0.1", 40001)
+
+class TestSocks(unittest.TestCase):
+    def test_parse_socks_request_empty(self):
+        self.assertRaises(ValueError, parse_socks_request, "")
+    def test_parse_socks_request_short(self):
+        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x01\x02\x03\x04")
+    def test_parse_socks_request_ip_userid_missing(self):
+        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04\x00")
+        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04\x00userid")
+        self.assertEqual((dest, port), ("1.2.3.4", 0x9999))
+    def test_parse_socks_request_ip(self):
+        dest, port = parse_socks_request("\x04\x01\x99\x99\x01\x02\x03\x04userid\x00")
+        self.assertEqual((dest, port), ("1.2.3.4", 0x9999))
+    def test_parse_socks_request_hostname_missing(self):
+        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x00\x00\x00\x01userid\x00")
+        self.assertRaises(ValueError, parse_socks_request, "\x04\x01\x99\x99\x00\x00\x00\x01userid\x00abc")
+    def test_parse_socks_request_hostname(self):
+        dest, port = parse_socks_request("\x04\x01\x99\x99\x00\x00\x00\x01userid\x00abc\x00")
+
+class DummySocket(object):
+    def __init__(self, read_fd, write_fd):
+        self.read_fd = read_fd
+        self.write_fd = write_fd
+        self.readp = 0
+
+    def read(self, *args, **kwargs):
+        self.read_fd.seek(self.readp, 0)
+        data = self.read_fd.read(*args, **kwargs)
+        self.readp = self.read_fd.tell()
+        return data
+
+    def readline(self, *args, **kwargs):
+        self.read_fd.seek(self.readp, 0)
+        data = self.read_fd.readline(*args, **kwargs)
+        self.readp = self.read_fd.tell()
+        return data
+
+    def recv(self, size, *args, **kwargs):
+        return self.read(size)
+
+    def write(self, data):
+        self.write_fd.seek(0, 2)
+        self.write_fd.write(data)
+
+    def send(self, data, *args, **kwargs):
+        return self.write(data)
+
+    def sendall(self, data, *args, **kwargs):
+        return self.write(data)
+
+    def makefile(self, *args, **kwargs):
+        return self
+
+def dummy_socketpair():
+    f1 = cStringIO.StringIO()
+    f2 = cStringIO.StringIO()
+    return (DummySocket(f1, f2), DummySocket(f2, f1))
+
+class HTTPRequest(object):
+    def __init__(self):
+        self.method = "GET"
+        self.path = "/"
+        self.headers = {}
+
+def transact_http(req):
+    l, r = dummy_socketpair()
+    r.send("%s %s HTTP/1.0\r\n" % (req.method, req.path))
+    for k, v in req.headers.items():
+        r.send("%s: %s\r\n" % (k, v))
+    r.send("\r\n")
+    protocols = handle_websocket_request(l)
+
+    resp = httplib.HTTPResponse(r)
+    resp.begin()
+    return resp, protocols
+
+class TestHandleWebSocketRequest(unittest.TestCase):
+    DEFAULT_KEY = "0123456789ABCDEF"
+    DEFAULT_KEY_BASE64 = base64.b64encode(DEFAULT_KEY)
+    MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+    @staticmethod
+    def default_req():
+        req = HTTPRequest()
+        req.method = "GET"
+        req.path = "/"
+        req.headers["Upgrade"] = "websocket"
+        req.headers["Connection"] = "Upgrade"
+        req.headers["Sec-WebSocket-Key"] = TestHandleWebSocketRequest.DEFAULT_KEY_BASE64
+        req.headers["Sec-WebSocket-Version"] = "13"
+
+        return req
+
+    def assert_ok(self, req):
+        resp, protocols = transact_http(req)
+        self.assertEqual(resp.status, 101)
+        self.assertEqual(resp.getheader("Upgrade").lower(), "websocket")
+        self.assertEqual(resp.getheader("Connection").lower(), "upgrade")
+        self.assertEqual(resp.getheader("Sec-WebSocket-Accept"), base64.b64encode(sha1(self.DEFAULT_KEY_BASE64 + self.MAGIC_GUID).digest()))
+        self.assertEqual(protocols, [])
+
+    def assert_not_ok(self, req):
+        resp, protocols = transact_http(req)
+        self.assertEqual(resp.status // 100, 4)
+        self.assertEqual(protocols, None)
+
+    def test_default(self):
+        req = self.default_req()
+        self.assert_ok(req)
+
+    def test_missing_upgrade(self):
+        req = self.default_req()
+        del req.headers["Upgrade"]
+        self.assert_not_ok(req)
+
+    def test_missing_connection(self):
+        req = self.default_req()
+        del req.headers["Connection"]
+        self.assert_not_ok(req)
+
+    def test_case_insensitivity(self):
+        """Test that the values of the Upgrade and Connection headers are
+        case-insensitive."""
+        req = self.default_req()
+        req.headers["Upgrade"] = req.headers["Upgrade"].lower()
+        self.assert_ok(req)
+        req.headers["Upgrade"] = req.headers["Upgrade"].upper()
+        self.assert_ok(req)
+        req.headers["Connection"] = req.headers["Connection"].lower()
+        self.assert_ok(req)
+        req.headers["Connection"] = req.headers["Connection"].upper()
+        self.assert_ok(req)
+
+    def test_bogus_key(self):
+        req = self.default_req()
+        req.headers["Sec-WebSocket-Key"] = base64.b64encode(self.DEFAULT_KEY[:-1])
+        self.assert_not_ok(req)
+
+        req.headers["Sec-WebSocket-Key"] = "///"
+        self.assert_not_ok(req)
+
+    def test_versions(self):
+        req = self.default_req()
+        req.headers["Sec-WebSocket-Version"] = "13"
+        self.assert_ok(req)
+        req.headers["Sec-WebSocket-Version"] = "8"
+        self.assert_ok(req)
+
+        req.headers["Sec-WebSocket-Version"] = "7"
+        self.assert_not_ok(req)
+        req.headers["Sec-WebSocket-Version"] = "9"
+        self.assert_not_ok(req)
+
+        del req.headers["Sec-WebSocket-Version"]
+        self.assert_not_ok(req)
+
+    def test_protocols(self):
+        req = self.default_req()
+        req.headers["Sec-WebSocket-Protocol"] = "base64"
+        resp, protocols = transact_http(req)
+        self.assertEqual(resp.status, 101)
+        self.assertEqual(protocols, ["base64"])
+        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), "base64")
+
+        req = self.default_req()
+        req.headers["Sec-WebSocket-Protocol"] = "cat"
+        resp, protocols = transact_http(req)
+        self.assertEqual(resp.status, 101)
+        self.assertEqual(protocols, ["cat"])
+        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), None)
+
+        req = self.default_req()
+        req.headers["Sec-WebSocket-Protocol"] = "cat, base64"
+        resp, protocols = transact_http(req)
+        self.assertEqual(resp.status, 101)
+        self.assertEqual(protocols, ["cat", "base64"])
+        self.assertEqual(resp.getheader("Sec-WebSocket-Protocol"), "base64")
+
+def read_frames(dec):
+    frames = []
+    while True:
+        frame = dec.read_frame()
+        if frame is None:
+            break
+        frames.append((frame.fin, frame.opcode, frame.payload))
+    return frames
+
+def read_messages(dec):
+    messages = []
+    while True:
+        message = dec.read_message()
+        if message is None:
+            break
+        messages.append((message.opcode, message.payload))
+    return messages
+
+class TestWebSocketDecoder(unittest.TestCase):
+    def test_rfc(self):
+        """Test samples from RFC 6455 section 5.7."""
+        TESTS = [
+            ("\x81\x05\x48\x65\x6c\x6c\x6f", False,
+                [(True, 1, "Hello")],
+                [(1, u"Hello")]),
+            ("\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58", True,
+                [(True, 1, "Hello")],
+                [(1, u"Hello")]),
+            ("\x01\x03\x48\x65\x6c\x80\x02\x6c\x6f", False,
+                [(False, 1, "Hel"), (True, 0, "lo")],
+                [(1, u"Hello")]),
+            ("\x89\x05\x48\x65\x6c\x6c\x6f", False,
+                [(True, 9, "Hello")],
+                [(9, u"Hello")]),
+            ("\x8a\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58", True,
+                [(True, 10, "Hello")],
+                [(10, u"Hello")]),
+            ("\x82\x7e\x01\x00" + "\x00" * 256, False,
+                [(True, 2, "\x00" * 256)],
+                [(2, "\x00" * 256)]),
+            ("\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x00" + "\x00" * 65536, False,
+                [(True, 2, "\x00" * 65536)],
+                [(2, "\x00" * 65536)]),
+            ("\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x03" + "ABCD" * 16384 + "XYZ", False,
+                [(True, 2, "ABCD" * 16384 + "XYZ")],
+                [(2, "ABCD" * 16384 + "XYZ")]),
+        ]
+        for data, use_mask, expected_frames, expected_messages in TESTS:
+            dec = WebSocketDecoder(use_mask = use_mask)
+            dec.feed(data)
+            actual_frames = read_frames(dec)
+            self.assertEqual(actual_frames, expected_frames)
+
+            dec = WebSocketDecoder(use_mask = use_mask)
+            dec.feed(data)
+            actual_messages = read_messages(dec)
+            self.assertEqual(actual_messages, expected_messages)
+
+            dec = WebSocketDecoder(use_mask = not use_mask)
+            dec.feed(data)
+            self.assertRaises(WebSocketDecoder.MaskingError, dec.read_frame)
+
+    def test_empty_feed(self):
+        """Test that the decoder can handle a zero-byte feed."""
+        dec = WebSocketDecoder()
+        self.assertEqual(dec.read_frame(), None)
+        dec.feed("")
+        self.assertEqual(dec.read_frame(), None)
+        dec.feed("\x81\x05H")
+        self.assertEqual(dec.read_frame(), None)
+        dec.feed("ello")
+        self.assertEqual(read_frames(dec), [(True, 1, u"Hello")])
+
+    def test_empty_frame(self):
+        """Test that a frame may contain a zero-byte payload."""
+        dec = WebSocketDecoder()
+        dec.feed("\x81\x00")
+        self.assertEqual(read_frames(dec), [(True, 1, u"")])
+        dec.feed("\x82\x00")
+        self.assertEqual(read_frames(dec), [(True, 2, "")])
+
+    def test_empty_message(self):
+        """Test that a message may have a zero-byte payload."""
+        dec = WebSocketDecoder()
+        dec.feed("\x01\x00\x00\x00\x80\x00")
+        self.assertEqual(read_messages(dec), [(1, u"")])
+        dec.feed("\x02\x00\x00\x00\x80\x00")
+        self.assertEqual(read_messages(dec), [(2, "")])
+
+    def test_interleaved_control(self):
+        """Test that control messages interleaved with fragmented messages are
+        returned."""
+        dec = WebSocketDecoder()
+        dec.feed("\x89\x04PING\x01\x03Hel\x8a\x04PONG\x80\x02lo\x89\x04PING")
+        self.assertEqual(read_messages(dec), [(9, "PING"), (10, "PONG"), (1, u"Hello"), (9, "PING")])
+
+    def test_fragmented_control(self):
+        """Test that illegal fragmented control messages cause an error."""
+        dec = WebSocketDecoder()
+        dec.feed("\x09\x04PING")
+        self.assertRaises(ValueError, dec.read_message)
+
+    def test_zero_opcode(self):
+        """Test that it is an error for the first frame in a message to have an
+        opcode of 0."""
+        dec = WebSocketDecoder()
+        dec.feed("\x80\x05Hello")
+        self.assertRaises(ValueError, dec.read_message)
+        dec = WebSocketDecoder()
+        dec.feed("\x00\x05Hello")
+        self.assertRaises(ValueError, dec.read_message)
+
+    def test_nonzero_opcode(self):
+        """Test that every frame after the first must have a zero opcode."""
+        dec = WebSocketDecoder()
+        dec.feed("\x01\x01H\x01\x02el\x80\x02lo")
+        self.assertRaises(ValueError, dec.read_message)
+        dec = WebSocketDecoder()
+        dec.feed("\x01\x01H\x00\x02el\x01\x02lo")
+        self.assertRaises(ValueError, dec.read_message)
+
+    def test_utf8(self):
+        """Test that text frames (opcode 1) are decoded from UTF-8."""
+        text = u"Hello World or Καλημέρα κόσμε or こんにちは 世界 or \U0001f639"
+        utf8_text = text.encode("utf-8")
+        dec = WebSocketDecoder()
+        dec.feed("\x81" + chr(len(utf8_text)) + utf8_text)
+        self.assertEqual(read_messages(dec), [(1, text)])
+
+    def test_wrong_utf8(self):
+        """Test that failed UTF-8 decoding causes an error."""
+        TESTS = [
+            "\xc0\x41", # Non-shortest form.
+            "\xc2", # Unfinished sequence.
+        ]
+        for test in TESTS:
+            dec = WebSocketDecoder()
+            dec.feed("\x81" + chr(len(test)) + test)
+            self.assertRaises(ValueError, dec.read_message)
+
+    def test_overly_large_payload(self):
+        """Test that large payloads are rejected."""
+        dec = WebSocketDecoder()
+        dec.feed("\x82\x7f\x00\x00\x00\x00\x01\x00\x00\x00")
+        self.assertRaises(ValueError, dec.read_frame)
+
+class TestWebSocketEncoder(unittest.TestCase):
+    def test_length(self):
+        """Test that payload lengths are encoded using the smallest number of
+        bytes."""
+        TESTS = [(0, 0), (125, 0), (126, 2), (65535, 2), (65536, 8)]
+        for length, encoded_length in TESTS:
+            enc = WebSocketEncoder(use_mask = False)
+            eframe = enc.encode_frame(2, "\x00" * length)
+            self.assertEqual(len(eframe), 1 + 1 + encoded_length + length)
+            enc = WebSocketEncoder(use_mask = True)
+            eframe = enc.encode_frame(2, "\x00" * length)
+            self.assertEqual(len(eframe), 1 + 1 + encoded_length + 4 + length)
+
+    def test_roundtrip(self):
+        TESTS = [
+            (1, u"Hello world"),
+            (1, u"Hello \N{WHITE SMILING FACE}"),
+        ]
+        for opcode, payload in TESTS:
+            for use_mask in (False, True):
+                enc = WebSocketEncoder(use_mask = use_mask)
+                enc_message = enc.encode_message(opcode, payload)
+                dec = WebSocketDecoder(use_mask = use_mask)
+                dec.feed(enc_message)
+                self.assertEqual(read_messages(dec), [(opcode, payload)])
+
+def format_address(addr):
+    return "%s:%d" % addr
+
+class TestConnectionLimit(unittest.TestCase):
+    def setUp(self):
+        self.p = subprocess.Popen(["./flashproxy-client", format_address(LOCAL_ADDRESS), format_address(REMOTE_ADDRESS)])
+
+    def tearDown(self):
+        self.p.terminate()
+
+#     def test_remote_limit(self):
+#         """Test that the client transport plugin limits the number of remote
+#         connections that it will accept."""
+#         for i in range(5):
+#             s = socket.create_connection(REMOTE_ADDRESS, 2)
+#         self.assertRaises(socket.error, socket.create_connection, REMOTE_ADDRESS)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/setup-client-exe.py b/setup-client-exe.py
index 5baf71d..62b9c87 100755
--- a/setup-client-exe.py
+++ b/setup-client-exe.py
@@ -1,4 +1,5 @@
 #!/usr/bin/python
+"""Setup file for the flashproxy-common python module."""
 from distutils.core import setup
 import os
 import py2exe
diff --git a/setup-common.py b/setup-common.py
index 44c9c3e..85bb325 100755
--- a/setup-common.py
+++ b/setup-common.py
@@ -1,4 +1,24 @@
 #!/usr/bin/env python
+"""Setup file for the flashproxy-common python module.
+
+To build/install a self-contained binary distribution of flashproxy-client
+(which integrates this module within it), see Makefile.
+"""
+# Note to future developers:
+#
+# We place flashproxy-common in the same directory as flashproxy-client for
+# convenience, so that it's possible to run the client programs directly from
+# a source checkout without needing to set PYTHONPATH. This works OK currently
+# because flashproxy-client does not contain python modules, only programs, and
+# therefore doesn't conflict with the flashproxy-common module.
+#
+# If we ever need to have a python module specific to flashproxy-client, the
+# natural thing would be to add a setup.py for it. That is the reason why this
+# file is called setup-common.py instead. However, there are still issues that
+# arise from having two setup*.py files in the same directory, which is an
+# unfortunate limitation of python's setuptools.
+#
+# See discussion on #6810 for more details.
 
 import sys
 






More information about the tor-commits mailing list