[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