tor-commits
  Threads by month 
                
            - ----- 2025 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
September 2012
- 18 participants
- 792 discussions
 
                        
                    
                        
                            
                                
                            
                            r25826: {website} do the other half of r25825	(website/trunk/download/en)
                        
                        
by Roger Dingledine 30 Sep '12
                    by Roger Dingledine 30 Sep '12
30 Sep '12
                    
                        Author: arma
Date: 2012-09-30 20:46:22 +0000 (Sun, 30 Sep 2012)
New Revision: 25826
Modified:
   website/trunk/download/en/download.wml
Log:
do the other half of r25825
Modified: website/trunk/download/en/download.wml
===================================================================
--- website/trunk/download/en/download.wml	2012-09-30 20:15:34 UTC (rev 25825)
+++ website/trunk/download/en/download.wml	2012-09-30 20:46:22 UTC (rev 25826)
@@ -213,7 +213,7 @@
 	  </form>
 
 	  <h2>Tor Browser Bundle</h2>
-	  <em>Version <version-torbrowserbundlelinux32> - Linux, BSD, and Unix</em>
+	  <em>Version <version-torbrowserbundlelinux32> - Linux</em>
 	  <p>Everything you need to safely browse the Internet. This
 package requires no installation. Just extract it and run. <a
 href="<page projects/torbrowser>">Learn more »</a></p>
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            r25825: {website} remove Unix and BSD from OSes tbb	linux runs on (website/trunk/download/en)
                        
                        
by Erinn Clark 30 Sep '12
                    by Erinn Clark 30 Sep '12
30 Sep '12
                    
                        Author: erinn
Date: 2012-09-30 20:15:34 +0000 (Sun, 30 Sep 2012)
New Revision: 25825
Modified:
   website/trunk/download/en/download-easy.wml
Log:
remove Unix and BSD from OSes tbb linux runs on
Modified: website/trunk/download/en/download-easy.wml
===================================================================
--- website/trunk/download/en/download-easy.wml	2012-09-30 18:35:09 UTC (rev 25824)
+++ website/trunk/download/en/download-easy.wml	2012-09-30 20:15:34 UTC (rev 25825)
@@ -110,7 +110,7 @@
 	  </form>
 	  <div class="desc">
 	    <h2>Tor Browser Bundle for GNU/Linux</h2>
-	    <em>Version <version-torbrowserbundlelinux32> - Linux, Unix, BSD</em>
+	    <em>Version <version-torbrowserbundlelinux32> - Linux</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
 	  <p class="alt-dl">Not Using GNU/Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
@@ -131,7 +131,7 @@
 	  </form>
 	  <div class="desc">
 	    <h2>Tor Browser Bundle for 64-Bit GNU/Linux</h2>
-	    <em>Version <version-torbrowserbundlelinux32> - Linux, Unix, BSD (64-Bit)</em>
+	    <em>Version <version-torbrowserbundlelinux32> - Linux (64-Bit)</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
 	  <p class="alt-dl">Not Using GNU/Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    30 Sep '12
                    
                        commit 949ce9dc5f6df6c3192a86ef7f31fde6be137db5
Author: Translation commit bot <translation(a)torproject.org>
Date:   Sun Sep 30 19:45:31 2012 +0000
    Update translations for torcheck
---
 lv/torcheck.po |   57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 57 insertions(+), 0 deletions(-)
diff --git a/lv/torcheck.po b/lv/torcheck.po
new file mode 100644
index 0000000..80eaced
--- /dev/null
+++ b/lv/torcheck.po
@@ -0,0 +1,57 @@
+# TorCheck gettext template
+# Copyright (C) 2008-2012 The Tor Project, Inc
+# 
+# Translators:
+# Ojars Balcers <ojars.balcers(a)gmail.com>, 2012.
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2012-02-16 20:28+PDT\n"
+"PO-Revision-Date: 2012-09-30 19:42+0000\n"
+"Last-Translator: Ojars Balcers <ojars.balcers(a)gmail.com>\n"
+"Language-Team: Tor Translation <tor-translation(a)torproject.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Language: lv\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
+
+msgid "Congratulations. Your browser is configured to use Tor."
+msgstr ""
+
+msgid ""
+"Please refer to the <a href=\"https://www.torproject.org/\">Tor website</a> "
+"for further information about using Tor safely.  You are now free to browse "
+"the Internet anonymously."
+msgstr ""
+
+msgid "There is a security update available for the Tor Browser Bundle."
+msgstr ""
+
+msgid ""
+"<a href=\"https://www.torproject.org/download/download-easy.html\">Click "
+"here to go to the download page</a>"
+msgstr ""
+
+msgid "Sorry. You are not using Tor."
+msgstr "Atvainojiet! Jūs nelietojat Tor."
+
+msgid ""
+"If you are attempting to use a Tor client, please refer to the <a "
+"href=\"https://www.torproject.org/\">Tor website</a> and specifically the <a"
+" href=\"https://www.torproject.org/docs/faq#DoesntWork\">instructions for "
+"configuring your Tor client</a>."
+msgstr ""
+
+msgid "Sorry, your query failed or an unexpected response was received."
+msgstr ""
+
+msgid ""
+"A temporary service outage prevents us from determining if your source IP "
+"address is a <a href=\"https://www.torproject.org/\">Tor</a> node."
+msgstr ""
+
+msgid "Your IP address appears to be: "
+msgstr ""
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            [translation/vidalia_installer] Update translations	for vidalia_installer
                        
                        
by translation@torproject.org 30 Sep '12
                    by translation@torproject.org 30 Sep '12
30 Sep '12
                    
                        commit 0692f5f3c36cdc1b0c9025dcbbae69c3dc1b3e84
Author: Translation commit bot <translation(a)torproject.org>
Date:   Sun Sep 30 19:45:27 2012 +0000
    Update translations for vidalia_installer
---
 lv/vidalia_lv.po |   11 +++++------
 1 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/lv/vidalia_lv.po b/lv/vidalia_lv.po
index bd0008e..fbd01ce 100755
--- a/lv/vidalia_lv.po
+++ b/lv/vidalia_lv.po
@@ -1,18 +1,19 @@
 # 
 # Translators:
+# Ojars Balcers <ojars.balcers(a)gmail.com>, 2012.
 msgid ""
 msgstr ""
 "Project-Id-Version: The Tor Project\n"
 "Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
 "POT-Creation-Date: 2008-07-12 05:29+0000\n"
-"PO-Revision-Date: 2010-12-05 20:21+0000\n"
-"Last-Translator: \n"
+"PO-Revision-Date: 2012-09-30 19:26+0000\n"
+"Last-Translator: Ojars Balcers <ojars.balcers(a)gmail.com>\n"
 "Language-Team: translations(a)vidalia-project.net\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Language: lv\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
 
 msgctxt "BundleSetupCaption"
 msgid "${BUNDLE_NAME} setup"
@@ -39,7 +40,7 @@ msgstr ""
 
 msgctxt "BundleLinkText"
 msgid "${TOR_NAME} installation documentation"
-msgstr ""
+msgstr "${TOR_NAME} instalācijas dokumentācija"
 
 msgctxt "BundleFinishText"
 msgid ""
@@ -237,5 +238,3 @@ msgid ""
 "Or, if you would prefer to install Tor without Firefox, simply\n"
 "press Next to continue."
 msgstr ""
-
-
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            r25824: {website} mention gnu in a few more places,	since rms asked so nicely  (website/trunk/download/en)
                        
                        
by Roger Dingledine 30 Sep '12
                    by Roger Dingledine 30 Sep '12
30 Sep '12
                    
                        Author: arma
Date: 2012-09-30 18:35:09 +0000 (Sun, 30 Sep 2012)
New Revision: 25824
Modified:
   website/trunk/download/en/download-easy.wml
   website/trunk/download/en/download.wml
Log:
mention gnu in a few more places, since rms asked so nicely
Modified: website/trunk/download/en/download-easy.wml
===================================================================
--- website/trunk/download/en/download-easy.wml	2012-09-27 23:26:32 UTC (rev 25823)
+++ website/trunk/download/en/download-easy.wml	2012-09-30 18:35:09 UTC (rev 25824)
@@ -15,7 +15,7 @@
 <define-tag button-osx-tbb32>Tor Browser Bundle</define-tag>
 <define-tag button-osx-tbb64>Mac 64-bit</define-tag>
 <define-tag button-lin-tbb32>Tor Browser Bundle</define-tag>
-<define-tag button-lin-tbb64>Linux 64-bit</define-tag>
+<define-tag button-lin-tbb64>GNU/Linux 64-bit</define-tag>
 
 #include "dlhead.wmi" TITLE="Download Tor" CHARSET="UTF-8"
 
@@ -50,7 +50,7 @@
 	    <em>Version <version-torbrowserbundle> - Windows 7, Vista, and XP</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
-	  <p class="alt-dl">Not Using Windows? Download for <a href="#mac">Mac</a> or <a href="#linux">Linux</a></p>
+	  <p class="alt-dl">Not Using Windows? Download for <a href="#mac">Mac</a> or <a href="#linux">GNU/Linux</a></p>
 	</div>
       </div>
 <!-- START MAC -->
@@ -71,7 +71,7 @@
 	    <em>Version <version-torbrowserbundleosx32> - OS X</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
-	  <p class="alt-dl">Not Using Mac? Download for <a href="#windows">Windows</a> or <a href="#linux">Linux</a></p>
+	  <p class="alt-dl">Not Using Mac? Download for <a href="#windows">Windows</a> or <a href="#linux">GNU/Linux</a></p>
 	</div>
       </div>
 <!-- START MAC 64-->
@@ -92,7 +92,7 @@
 	    <em>Version <version-torbrowserbundleosx64> - OS X (64-Bit)</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
-	  <p class="alt-dl">Not Using Mac? Download for <a href="#windows">Windows</a> or <a href="#linux">Linux</a></p>
+	  <p class="alt-dl">Not Using Mac? Download for <a href="#windows">Windows</a> or <a href="#linux">GNU/Linux</a></p>
 	</div>
       </div>
 <!-- START LINUX -->
@@ -109,11 +109,11 @@
 	    </div>
 	  </form>
 	  <div class="desc">
-	    <h2>Tor Browser Bundle for Linux</h2>
+	    <h2>Tor Browser Bundle for GNU/Linux</h2>
 	    <em>Version <version-torbrowserbundlelinux32> - Linux, Unix, BSD</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
-	  <p class="alt-dl">Not Using Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
+	  <p class="alt-dl">Not Using GNU/Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
 	</div>
       </div>
 <!-- START LINUX 64-Bit -->
@@ -130,11 +130,11 @@
 	    </div>
 	  </form>
 	  <div class="desc">
-	    <h2>Tor Browser Bundle for 64-Bit Linux</h2>
+	    <h2>Tor Browser Bundle for 64-Bit GNU/Linux</h2>
 	    <em>Version <version-torbrowserbundlelinux32> - Linux, Unix, BSD (64-Bit)</em>
 	    <p>Everything you need to safely browse the Internet. This package requires no installation. Just extract it and run. <a href="<page projects/torbrowser>">Learn more »</a></p>
 	  </div>
-	  <p class="alt-dl">Not Using Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
+	  <p class="alt-dl">Not Using GNU/Linux? Download for <a href="#mac">Mac</a> or <a href="#windows">Windows</a></p>
 	</div>
       </div>
       <p class="all-dl">Looking For Something Else? <a href="<page download/download>">View All Downloads</a></p>
Modified: website/trunk/download/en/download.wml
===================================================================
--- website/trunk/download/en/download.wml	2012-09-27 23:26:32 UTC (rev 25823)
+++ website/trunk/download/en/download.wml	2012-09-30 18:35:09 UTC (rev 25824)
@@ -183,7 +183,7 @@
  <!-- END OS X -->
  <!-- START UNIX -->
       <div id="linux" class="accordionButton on">
-	<span class="linux24">Linux, BSD, & Unix</span></div>
+	<span class="linux24">GNU/Linux, BSD, & Unix</span></div>
       <div class="accordionContent">
 	<div class="fauxhead"></div>
   <!-- TOR BROWSER BUNDLE -->
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            [flashproxy/master] Run facilitator and proxy tests	from "make test".
                        
                        
by dcf@torproject.org 29 Sep '12
                    by dcf@torproject.org 29 Sep '12
29 Sep '12
                    
                        commit e863d6e58da4b2222925468314aa1fdcb8daca24
Author: David Fifield <david(a)bamsoftware.com>
Date:   Fri Sep 28 19:47:44 2012 -0700
    Run facilitator and proxy tests from "make test".
---
 Makefile |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
index 55c5859..9339534 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,8 @@ clean:
 
 test:
 	./flashproxy-client-test
+	cd facilitator && ./facilitator-test
+	cd proxy && ./flashproxy-test.js
 
 DISTNAME = flashproxy-client-$(VERSION)
 DISTDIR = dist/$(DISTNAME)
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            [flashproxy/master] Add proxy/Makefile for	convenience of "make test".
                        
                        
by dcf@torproject.org 29 Sep '12
                    by dcf@torproject.org 29 Sep '12
29 Sep '12
                    
                        commit edce6fcb30ebad2ebf3bb9836866fcf21c505e54
Author: David Fifield <david(a)bamsoftware.com>
Date:   Fri Sep 28 19:50:25 2012 -0700
    Add proxy/Makefile for convenience of "make test".
---
 proxy/Makefile |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/proxy/Makefile b/proxy/Makefile
new file mode 100644
index 0000000..e34df0c
--- /dev/null
+++ b/proxy/Makefile
@@ -0,0 +1,7 @@
+all:
+	:
+
+test:
+	./flashproxy-test.js
+
+.PHONY: all test
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            [flashproxy/master] Move flash proxy badge files into	a subdirectory.
                        
                        
by dcf@torproject.org 29 Sep '12
                    by dcf@torproject.org 29 Sep '12
29 Sep '12
                    
                        commit c8bd5fd4fed1907817087ed8f7a6cba6511d1fb5
Author: David Fifield <david(a)bamsoftware.com>
Date:   Fri Sep 28 19:43:32 2012 -0700
    Move flash proxy badge files into a subdirectory.
    
    Now the root contains only files of interest to the most likely end
    user: a client and not a web site owner, relay operator, or facilitator
    operator.
---
 Makefile                 |    1 -
 badge.png                |  Bin 254 -> 0 bytes
 badge.xcf                |  Bin 1441 -> 0 bytes
 embed.html               |   87 -----
 flashproxy-test.js       |  239 ------------
 flashproxy.js            |  898 ----------------------------------------------
 proxy/README             |    4 +
 proxy/badge.png          |  Bin 0 -> 254 bytes
 proxy/badge.xcf          |  Bin 0 -> 1441 bytes
 proxy/embed.html         |   87 +++++
 proxy/flashproxy-test.js |  239 ++++++++++++
 proxy/flashproxy.js      |  898 ++++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 1228 insertions(+), 1225 deletions(-)
diff --git a/Makefile b/Makefile
index 0effce4..55c5859 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,6 @@ clean:
 
 test:
 	./flashproxy-client-test
-	./flashproxy-test.js
 
 DISTNAME = flashproxy-client-$(VERSION)
 DISTDIR = dist/$(DISTNAME)
diff --git a/badge.png b/badge.png
deleted file mode 100644
index 068cf78..0000000
Binary files a/badge.png and /dev/null differ
diff --git a/badge.xcf b/badge.xcf
deleted file mode 100644
index d12ff1f..0000000
Binary files a/badge.xcf and /dev/null differ
diff --git a/embed.html b/embed.html
deleted file mode 100644
index 95821ce..0000000
--- a/embed.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
-<meta http-equiv="refresh" content="86400">
-<style type="text/css">
-html {
-	width: 100%;
-	height: 100%;
-}
-body {
-	margin: 0;
-	padding: 0;
-	width: 100%;
-	height: 100%;
-}
-#flashproxy-badge {
-	width: 100%;
-	height: 100%;
-	margin: 0;
-	padding: 0;
-	border: 0;
-	border-collapse: collapse;
-	line-height: 0;
-}
-#flashproxy-badge.idle {
-	background-color: #227;
-}
-#flashproxy-badge.active {
-	background-color: #28f;
-}
-#flashproxy-badge.disabled {
-	background-color: #777;
-}
-#flashproxy-badge.dead {
-	background-color: #111;
-}
-#flashproxy-badge td {
-	position: relative;
-	margin: 0;
-	padding: 0;
-	vertical-align: middle;
-	text-align: center;
-}
-#flashproxy-badge td .disable-button {
-	position: absolute;
-	margin: 0;
-	padding: 0;
-	border: 1px solid white;
-	color: white;
-	background-color: #B00;
-	top: 1px;
-	right: 1px;
-	width: 11px;
-	height: 11px;
-	text-align: center;
-	line-height: 11px;
-	font-size: 11px;
-	display: none;
-}
-#flashproxy-badge a img {
-	border: 0;
-}
-#flashproxy-badge.debug {
-	position: absolute;
-	margin: 0;
-	left: 0;
-	top: 0;
-	width: 100%;
-	height: 100%;
-	overflow: auto;
-	color: #4c4;
-	background-color: #021;
-	line-height: inherit;
-}
-</style>
-</head>
-<body>
-<script type="text/javascript" src="flashproxy.js"></script>
-<script type="text/javascript">
-flashproxy_badge_insert().start();
-</script>
-<noscript>
-<table id="flashproxy-badge" class="disabled"><tr><td><a href="//crypto.stanford.edu/flashproxy/" target="_parent"><img src="badge.png" alt="Internet freedom"></a></td></tr></table>
-</noscript>
-</body>
-</html>
diff --git a/flashproxy-test.js b/flashproxy-test.js
deleted file mode 100755
index 2dc185a..0000000
--- a/flashproxy-test.js
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/js
-
-/* To run this test program, install the Rhino JavaScript interpreter
-   (apt-get install rhino). */
-
-var VERBOSE = false;
-if ("-v" in arguments)
-    VERBOSE = true;
-
-var num_tests = 0;
-var num_failed = 0;
-
-var window = {location: {search: "?"}};
-
-load("flashproxy.js");
-
-function objects_equal(a, b)
-{
-    if ((a === null) != (b === null))
-        return false;
-    if (typeof a != typeof b)
-        return false;
-    if (typeof a != "object")
-        return a == b;
-
-    for (var k in a) {
-        if (!objects_equal(a[k], b[k]))
-            return false;
-    }
-    for (var k in b) {
-        if (!objects_equal(a[k], b[k]))
-            return false;
-    }
-
-    return true;
-}
-
-var top = true;
-function announce(test_name)
-{
-    if (VERBOSE) {
-        if (!top)
-            print();
-        print(test_name);
-    }
-    top = false;
-}
-
-function pass(test)
-{
-    num_tests++;
-    if (VERBOSE)
-        print("PASS " + repr(test));
-}
-
-function fail(test, expected, actual)
-{
-    num_tests++;
-    num_failed++;
-    print("FAIL " + repr(test) + "  expected: " + repr(expected) + "  actual: " + repr(actual));
-}
-
-function test_build_url()
-{
-    var TESTS = [
-        { args: ["http", "example.com"],
-          expected: "http://example.com" },
-        { args: ["http", "example.com", 80],
-          expected: "http://example.com" },
-        { args: ["http", "example.com", 81],
-          expected: "http://example.com:81" },
-        { args: ["https", "example.com", 443],
-          expected: "https://example.com" },
-        { args: ["https", "example.com", 444],
-          expected: "https://example.com:444" },
-        { args: ["http", "example.com", 80, "/"],
-          expected: "http://example.com/" },
-        { args: ["http", "example.com", 80, "/test?k=%#v"],
-          expected: "http://example.com/test%3Fk%3D%25%23v" },
-        { args: ["http", "example.com", 80, "/test", []],
-          expected: "http://example.com/test?" },
-        { args: ["http", "example.com", 80, "/test", [["k", "%#v"]]],
-          expected: "http://example.com/test?k=%25%23v" },
-        { args: ["http", "example.com", 80, "/test", [["a", "b"], ["c", "d"]]],
-          expected: "http://example.com/test?a=b&c=d" },
-        { args: ["http", "1.2.3.4"],
-          expected: "http://1.2.3.4" },
-        { args: ["http", "1:2::3:4"],
-          expected: "http://[1:2::3:4]" },
-        { args: ["http", "bog][us"],
-          expected: "http://bog%5D%5Bus" },
-        { args: ["http", "bog:u]s"],
-          expected: "http://bog%3Au%5Ds" },
-    ];
-
-    announce("test_build_url");
-    for (var i = 0; i < TESTS.length; i++) {
-        var test = TESTS[i];
-        var actual;
-
-        actual = build_url.apply(undefined, test.args);
-        if (objects_equal(actual, test.expected))
-            pass(test.args);
-        else
-            fail(test.args, test.expected, actual);
-    }
-}
-
-function test_parse_query_string()
-{
-    var TESTS = [
-        { qs: "",
-          expected: { } },
-        { qs: "a=b",
-          expected: { a: "b" } },
-        { qs: "a=b=c",
-          expected: { a: "b=c" } },
-        { qs: "a=b&c=d",
-          expected: { a: "b", c: "d" } },
-        { qs: "client=&relay=1.2.3.4%3A9001",
-          expected: { client: "", relay: "1.2.3.4:9001" } },
-        { qs: "a=b%26c=d",
-          expected: { a: "b&c=d" } },
-        { qs: "a%3db=d",
-          expected: { "a=b": "d" } },
-        { qs: "a=b+c%20d",
-          expected: { "a": "b c d" } },
-        { qs: "a=b+c%2bd",
-          expected: { "a": "b c+d" } },
-        { qs: "a+b=c",
-          expected: { "a b": "c" } },
-        { qs: "a=b+c+d",
-          expected: { a: "b c d" } },
-        /* First appearance wins. */
-        { qs: "a=b&c=d&a=e",
-          expected: { a: "b", c: "d" } },
-        { qs: "a",
-          expected: { a: "" } },
-        { qs: "=b",
-          expected: { "": "b" } },
-        { qs: "&a=b",
-          expected: { "": "", a: "b" } },
-        { qs: "a=b&",
-          expected: { "": "", a: "b" } },
-        { qs: "a=b&&c=d",
-          expected: { "": "", a: "b", c: "d" } },
-    ];
-
-    announce("test_parse_query_string");
-    for (var i = 0; i < TESTS.length; i++) {
-        var test = TESTS[i];
-        var actual;
-
-        actual = parse_query_string(test.qs);
-        if (objects_equal(actual, test.expected))
-            pass(test.qs);
-        else
-            fail(test.qs, test.expected, actual);
-    }
-}
-
-function test_parse_addr_spec()
-{
-    var TESTS = [
-        { spec: "",
-          expected: null },
-        { spec: "3.3.3.3:4444",
-          expected: { host: "3.3.3.3", port: 4444 } },
-        { spec: "3.3.3.3",
-          expected: null },
-        { spec: "3.3.3.3:0x1111",
-          expected: null },
-        { spec: "3.3.3.3:-4444",
-          expected: null },
-        { spec: "3.3.3.3:65536",
-          expected: null },
-        { spec: "[1:2::a:f]:4444",
-          expected: { host: "1:2::a:f", port: 4444 } },
-        { spec: "[1:2::a:f]",
-          expected: null },
-        { spec: "[1:2::a:f]:0x1111",
-          expected: null },
-        { spec: "[1:2::a:f]:-4444",
-          expected: null },
-        { spec: "[1:2::a:f]:65536",
-          expected: null },
-        { spec: "[1:2::ffff:1.2.3.4]:4444",
-          expected: { host: "1:2::ffff:1.2.3.4", port: 4444 } },
-    ];
-
-    announce("test_parse_addr_spec");
-    for (var i = 0; i < TESTS.length; i++) {
-        var test = TESTS[i];
-        var actual;
-
-        actual = parse_addr_spec(test.spec);
-        if (objects_equal(actual, test.expected))
-            pass(test.spec);
-        else
-            fail(test.spec, test.expected, actual);
-    }
-}
-
-function test_get_query_param_addr()
-{
-    var DEFAULT = { host: "1.1.1.1", port: 2222 };
-    var TESTS = [
-        { query: { },
-          expected: DEFAULT },
-        { query: { addr: "3.3.3.3:4444" },
-          expected: { host: "3.3.3.3", port: 4444 } },
-        { query: { x: "3.3.3.3:4444" },
-          expected: DEFAULT },
-        { query: { addr: "---" },
-          expected: null },
-    ];
-
-    announce("test_get_query_param_addr");
-    for (var i = 0; i < TESTS.length; i++) {
-        var test = TESTS[i];
-        var actual;
-
-        actual = get_query_param_addr(test.query, "addr", DEFAULT);
-        if (objects_equal(actual, test.expected))
-            pass(test.query);
-        else
-            fail(test.query, test.expected, actual);
-    }
-}
-
-test_build_url();
-test_parse_query_string();
-test_parse_addr_spec();
-test_get_query_param_addr();
-
-if (num_failed == 0)
-    quit(0);
-else
-    quit(1);
diff --git a/flashproxy.js b/flashproxy.js
deleted file mode 100644
index a2e8863..0000000
--- a/flashproxy.js
+++ /dev/null
@@ -1,898 +0,0 @@
-/* Query string parameters. These change how the program runs from the outside.
- * For example:
- *   http://www.example.com/embed.html?facilitator=127.0.0.1:9002&debug=1
- *
- * client=<HOST>:<PORT>
- * The address of the client to connect to. The proxy normally receives this
- * information from the facilitator. When this option is used, the facilitator
- * query is not done. The "relay" parameter must be given as well.
- *
- * debug=1
- * If set (to any value), show verbose terminal-like output instead of the
- * badge.
- *
- * facilitator=https://host:port/
- * The URL of the facilitator CGI script. By default it is
- * DEFAULT_FACILITATOR_URL.
- *
- * facilitator_poll_interval=<FLOAT>
- * How often to poll the facilitator, in seconds. The default is
- * DEFAULT_FACILITATOR_POLL_INTERVAL. There is a sanity-check minimum of 1.0 s.
- *
- * max_clients=<NUM>
- * How many clients to serve concurrently. The default is
- * DEFAULT_MAX_NUM_PROXY_PAIRS.
- *
- * relay=<HOST>:<PORT>
- * The address of the relay to connect to. The proxy normally receives this
- * information from the facilitator. When this option is used, the facilitator
- * query is not done. The "client" parameter must be given as well.
- *
- * ratelimit=<FLOAT>(<UNIT>)?|off
- * What rate to limit all proxy traffic combined to. The special value "off"
- * disables the limit. The default is DEFAULT_RATE_LIMIT. There is a
- * sanity-check minimum of "10K".
- */
-
-/* WebSocket links.
- *
- * The WebSocket Protocol
- * https://tools.ietf.org/html/rfc6455
- *
- * The WebSocket API
- * http://dev.w3.org/html5/websockets/
- *
- * MDN page with browser compatibility
- * https://developer.mozilla.org/en/WebSockets
- *
- * Implementation tests (including tests of binary messages)
- * http://autobahn.ws/testsuite/reports/clients/index.html
- */
-
-var DEFAULT_FACILITATOR_URL = "https://tor-facilitator.bamsoftware.com/";
-
-var DEFAULT_MAX_NUM_PROXY_PAIRS = 10;
-
-var DEFAULT_FACILITATOR_POLL_INTERVAL = 10.0;
-var MIN_FACILITATOR_POLL_INTERVAL = 1.0;
-
-/* Bytes per second. Set to undefined to disable limit. */
-var DEFAULT_RATE_LIMIT = undefined;
-var MIN_RATE_LIMIT = 10 * 1024;
-var RATE_LIMIT_HISTORY = 5.0;
-
-/* Firefox before version 11.0 uses the name MozWebSocket. Whether the global
-   variable WebSocket is defined indicates whether WebSocket is supported at
-   all. */
-var WebSocket = window.WebSocket || window.MozWebSocket;
-
-var query = parse_query_string(window.location.search.substr(1));
-var debug_div;
-
-if (query.debug) {
-    debug_div = document.createElement("pre");
-    debug_div.className = "debug";
-}
-
-function puts(s) {
-    if (debug_div) {
-        var at_bottom;
-
-        /* http://www.w3.org/TR/cssom-view/#element-scrolling-members */
-        at_bottom = (debug_div.scrollTop + debug_div.clientHeight === debug_div.scrollHeight);
-        debug_div.appendChild(document.createTextNode(s + "\n"));
-        if (at_bottom)
-            debug_div.scrollTop = debug_div.scrollHeight;
-    }
-}
-
-/* Parse a URL query string or application/x-www-form-urlencoded body. The
-   return type is an object mapping string keys to string values. By design,
-   this function doesn't support multiple values for the same named parameter,
-   for example "a=1&a=2&a=3"; the first definition always wins. Returns null on
-   error.
-
-   Always decodes from UTF-8, not any other encoding.
-
-   http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data */
-function parse_query_string(qs) {
-    var strings;
-    var result;
-
-    result = {};
-    if (qs)
-        strings = qs.split("&");
-    else
-        strings = [];
-    for (var i = 0; i < strings.length; i++) {
-        var string = strings[i];
-        var j, name, value;
-
-        j = string.indexOf("=");
-        if (j === -1) {
-            name = string;
-            value = "";
-        } else {
-            name = string.substr(0, j);
-            value = string.substr(j + 1);
-        }
-        name = decodeURIComponent(name.replace(/\+/g, " "));
-        value = decodeURIComponent(value.replace(/\+/g, " "));
-        if (!(name in result))
-             result[name] = value;
-    }
-
-    return result;
-}
-
-var DEFAULT_PORTS = {
-    http: 80,
-    https: 443
-}
-/* Build an escaped URL string from unescaped components. Only scheme and host
-   are required. See RFC 3986, section 3. */
-function build_url(scheme, host, port, path, params) {
-    var parts = []
-
-    parts.push(encodeURIComponent(scheme));
-    parts.push("://");
-
-    /* If it contains a colon but no square brackets, treat it like an IPv6
-       address. */
-    if (host.match(/:/) && !host.match(/[[\]]/)) {
-        parts.push("[");
-        parts.push(host);
-        parts.push("]");
-    } else {
-        parts.push(encodeURIComponent(host));
-    }
-    if (port !== undefined && port !== DEFAULT_PORTS[scheme]) {
-        parts.push(":");
-        parts.push(encodeURIComponent(port.toString()));
-    }
-
-    if (path !== undefined && path !== "") {
-        if (!path.match(/^\//))
-            path = "/" + path;
-        /* Slash is significant so we must protect it from encodeURIComponent,
-           while still encoding question mark and number sign. RFC 3986, section
-           3.3: "The path is terminated by the first question mark ('?') or
-           number sign ('#') character, or by the end of the URI. ... A path
-           consists of a sequence of path segments separated by a slash ('/')
-           character." */
-        path = path.replace(/[^\/]+/, function(m) {
-            return encodeURIComponent(m);
-        });
-        parts.push(path);
-    }
-
-    if (params !== undefined) {
-        parts.push("?");
-        for (var i = 0; i < params.length; i++) {
-            if (i > 0)
-                parts.push("&");
-            parts.push(encodeURIComponent(params[i][0]) + "=" + encodeURIComponent(params[i][1]));
-        }
-    }
-
-    return parts.join("");
-}
-
-/* Get a query string parameter and return it as a string. Returns default_val
-   if param is not defined in the query string. */
-function get_query_param_string(query, param, default_val) {
-    var val;
-
-    val = query[param];
-    if (val === undefined)
-        return default_val;
-    else
-        return val;
-}
-
-/* Get a query string parameter, or the given default, and parse it as an
-   address spec. Returns null on a parsing error. */
-function get_query_param_addr(query, param, default_val) {
-    var val;
-
-    val = query[param];
-    if (val === undefined)
-        return default_val;
-    else
-        return parse_addr_spec(val);
-}
-
-/* Get an integer from the given movie parameter, or the given default. Returns
-   null on error. */
-function get_query_param_integer(query, param, default_val) {
-    var spec;
-    var val;
-
-    spec = query[param];
-    if (spec === undefined) {
-        return default_val;
-    } else if (!spec.match(/^-?[0-9]+/)) {
-        return null;
-    } else {
-        val = parseInt(spec, 10);
-        if (isNaN(val))
-            return null;
-        else
-            return val;
-    }
-}
-
-/* Get a number from the given movie parameter, or the given default. Returns
-   null on error. */
-function get_query_param_number(query, param, default_val) {
-    var spec;
-    var val;
-
-    spec = query[param];
-    if (spec === undefined) {
-        return default_val;
-    } else {
-        val = Number(spec);
-        if (isNaN(val))
-            return null;
-        else
-            return val;
-    }
-}
-
-/* Get a floating-point number of seconds from a time specification. The only
-   time specification format is a decimal number of seconds. Returns null on
-   error. */
-function get_query_param_timespec(query, param, default_val) {
-    return get_query_param_number(query, param, default_val);
-}
-
-/* Parse a count of bytes. A suffix of "k", "m", or "g" (or uppercase)
-   does what you would think. Returns null on error. */
-function parse_byte_count(spec) {
-    var UNITS = {
-        k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024,
-        K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
-    };
-    var count, units;
-    var matches;
-
-    matches = spec.match(/^(\d+(?:\.\d*)?)(\w*)$/);
-    if (matches === null)
-        return null;
-
-    count = Number(matches[1]);
-    if (isNaN(count))
-        return null;
-
-    if (matches[2] === "") {
-        units = 1;
-    } else {
-        units = UNITS[matches[2]];
-        if (units === null)
-            return null;
-    }
-
-    return count * Number(units);
-}
-
-/* Get a count of bytes from a string specification like "100" or "1.3m".
-   Returns null on error. */
-function get_query_param_byte_count(query, param, default_val) {
-    var spec;
-
-    spec = query[param];
-    if (spec === undefined)
-        return default_val;
-    else
-        return parse_byte_count(spec);
-}
-
-/* Parse an address in the form "host:port". Returns an Object with
-   keys "host" (String) and "port" (int). Returns null on error. */
-function parse_addr_spec(spec) {
-    var m, host, port;
-
-    m = null;
-    /* IPv6 syntax. */
-    if (!m)
-        m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/);
-    /* IPv4 syntax. */
-    if (!m)
-        m = spec.match(/^([0-9.]+):([0-9]+)$/);
-    if (!m)
-        return null;
-    host = m[1];
-    port = parseInt(m[2], 10);
-    if (isNaN(port) || port < 0 || port > 65535)
-        return null;
-
-    return { host: host, port: port }
-}
-
-function format_addr(addr) {
-    return addr.host + ":" + addr.port;
-}
-
-/* Does the WebSocket implementation in this browser support binary frames? (RFC
-   6455 section 5.6.) If not, we have to use base64-encoded text frames. It is
-   assumed that the client and relay endpoints always support binary frames. */
-function have_websocket_binary_frames() {
-    var ua, matches;
-
-    ua = window.navigator.userAgent;
-    if (ua === null)
-        return false;
-
-    /* We are cool for Chrome 16 or Safari 6.0. */
-
-    matches = ua.match(/\bchrome\/(\d+)/i);
-    if (matches !== null && Number(matches[1]) >= 16)
-        return true;
-
-    matches = ua.match(/\bversion\/(\d+)/i);
-    if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
-        && Number(matches[1]) >= 6)
-        return true;
-
-    return false;
-}
-
-function make_websocket(addr) {
-    var url;
-    var ws;
-
-    url = build_url("ws", addr.host, addr.port, "/");
-
-    if (have_websocket_binary_frames())
-        ws = new WebSocket(url);
-    else
-        ws = new WebSocket(url, "base64");
-    /* "User agents can use this as a hint for how to handle incoming binary
-       data: if the attribute is set to 'blob', it is safe to spool it to disk,
-       and if it is set to 'arraybuffer', it is likely more efficient to keep
-       the data in memory." */
-    ws.binaryType = "arraybuffer";
-
-    return ws;
-}
-
-function FlashProxy() {
-    if (query.debug) {
-        this.badge_elem = debug_div;
-    } else {
-        this.badge = new Badge();
-        this.badge.elem.onmouseover = function(event) {
-            this.badge.disable_button.style.display = "block";
-        }.bind(this);
-        this.badge.elem.onmouseout = function(event) {
-            this.badge.disable_button.style.display = "none";
-        }.bind(this);
-        /* Click a button to disable the badge. */
-        this.badge.disable_button.onclick = function(event) {
-            this.disable();
-            this.badge.disable_button.parentNode.removeChild(this.badge.disable_button);
-        }.bind(this);
-        this.badge_elem = this.badge.elem;
-    }
-    this.badge_elem.setAttribute("id", "flashproxy-badge");
-
-    this.proxy_pairs = [];
-
-    this.start = function() {
-        var client_addr;
-        var relay_addr;
-        var rate_limit_bytes;
-
-        this.fac_url = get_query_param_string(query, "facilitator", DEFAULT_FACILITATOR_URL);
-
-        this.max_num_proxy_pairs = get_query_param_integer(query, "max_clients", DEFAULT_MAX_NUM_PROXY_PAIRS);
-        if (this.max_num_proxy_pairs === null || this.max_num_proxy_pairs < 0) {
-            puts("Error: max_clients must be a nonnegative integer.");
-            this.die();
-            return;
-        }
-
-        this.facilitator_poll_interval = get_query_param_timespec(query, "facilitator_poll_interval", DEFAULT_FACILITATOR_POLL_INTERVAL);
-        if (this.facilitator_poll_interval === null || this.facilitator_poll_interval < MIN_FACILITATOR_POLL_INTERVAL) {
-            puts("Error: facilitator_poll_interval must be a nonnegative number at least " + MIN_FACILITATOR_POLL_INTERVAL + ".");
-            this.die();
-            return;
-        }
-
-        if (query["ratelimit"] === "off")
-            rate_limit_bytes = undefined;
-        else
-            rate_limit_bytes = get_query_param_byte_count(query, "ratelimit", DEFAULT_RATE_LIMIT);
-        if (rate_limit_bytes === undefined) {
-            this.rate_limit = new DummyRateLimit();
-        } else if (rate_limit_bytes === null || rate_limit_bytes < MIN_FACILITATOR_POLL_INTERVAL) {
-            puts("Error: ratelimit must be a nonnegative number at least " + MIN_RATE_LIMIT + ".");
-            this.die();
-            return;
-        } else {
-            this.rate_limit = new BucketRateLimit(rate_limit_bytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
-        }
-
-        client_addr = get_query_param_addr(query, "client");
-        relay_addr = get_query_param_addr(query, "relay");
-        if (client_addr !== undefined && relay_addr !== undefined) {
-            this.make_proxy_pair(client_addr, relay_addr);
-        } else if (client_addr !== undefined) {
-            puts("Error: the \"client\" parameter requires \"relay\" also.")
-            this.die();
-            return;
-        } else if (relay_addr !== undefined) {
-            puts("Error: the \"relay\" parameter requires \"client\" also.")
-            this.die();
-            return;
-        } else {
-            this.proxy_main();
-        }
-    };
-
-    this.proxy_main = function() {
-        var xhr;
-
-        if (this.proxy_pairs.length >= this.max_num_proxy_pairs) {
-            setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval);
-            return;
-        }
-
-        xhr = new XMLHttpRequest();
-        try {
-            xhr.open("GET", this.fac_url);
-        } catch (err) {
-            /* An exception happens here when, for example, NoScript allows the
-               domain on which the proxy badge runs, but not the domain to which
-               it's trying to make the HTTP request. The exception message is
-               like "Component returned failure code: 0x805e0006
-               [nsIXMLHttpRequest.open]" on Firefox. */
-            puts("Facilitator: exception while connecting: " + repr(err.message) + ".");
-            this.die();
-            return;
-        }
-        xhr.responseType = "text";
-        xhr.onreadystatechange = function() {
-            if (xhr.readyState === xhr.DONE) {
-                if (xhr.status === 200)
-                    this.fac_complete(xhr.responseText);
-                else
-                    puts("Facilitator: can't connect: got status " + repr(xhr.status) + " and status text " + repr(xhr.statusText) + ".");
-            }
-        }.bind(this);
-        puts("Facilitator: connecting to " + this.fac_url + ".");
-        xhr.send(null);
-    };
-
-    this.fac_complete = function(text) {
-        var response;
-        var client_addr;
-        var relay_addr;
-
-        setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval * 1000);
-
-        response = parse_query_string(text);
-
-        if (!response.client) {
-            puts("No clients.");
-            return;
-        }
-        client_addr = parse_addr_spec(response.client);
-        if (client_addr === null) {
-            puts("Error: can't parse client spec " + repr(response.client) + ".");
-            return;
-        }
-        if (!response.relay) {
-            puts("Error: missing relay in response.");
-            return;
-        }
-        relay_addr = parse_addr_spec(response.relay);
-        if (relay_addr === null) {
-            puts("Error: can't parse relay spec " + repr(response.relay) + ".");
-            return;
-        }
-        puts("Facilitator: got client:" + repr(client_addr) + " "
-            + "relay:" + repr(relay_addr) + ".");
-
-        this.make_proxy_pair(client_addr, relay_addr);
-    };
-
-    this.make_proxy_pair = function(client_addr, relay_addr) {
-        var proxy_pair;
-
-        proxy_pair = new ProxyPair(client_addr, relay_addr, this.rate_limit);
-        this.proxy_pairs.push(proxy_pair);
-        proxy_pair.complete_callback = function(event) {
-            puts("Complete.");
-            /* Delete from the list of active proxy pairs. */
-            this.proxy_pairs.splice(this.proxy_pairs.indexOf(proxy_pair), 1);
-            if (this.badge)
-                this.badge.proxy_end();
-        }.bind(this);
-        try {
-            proxy_pair.connect();
-        } catch (err) {
-            puts("ProxyPair: exception while connecting: " + repr(err.message) + ".");
-            this.die();
-            return;
-        }
-
-        if (this.badge)
-            this.badge.proxy_begin();
-    };
-
-    /* Cease all network operations and prevent any future ones. */
-    this.disable = function() {
-        puts("Disabling.");
-        this.proxy_main = function() { };
-        this.make_proxy_pair = function(client_addr, relay_addr) { };
-        while (this.proxy_pairs.length > 0)
-            this.proxy_pairs.pop().close();
-        if (this.badge)
-            this.badge.disable();
-    };
-
-    this.die = function() {
-        puts("Dying.");
-        if (this.badge)
-            this.badge.die();
-    };
-}
-
-/* An instance of a client-relay connection. */
-function ProxyPair(client_addr, relay_addr, rate_limit) {
-    var MAX_BUFFER = 10 * 1024 * 1024;
-
-    function log(s)
-    {
-        puts(s)
-    }
-
-    this.client_addr = client_addr;
-    this.relay_addr = relay_addr;
-    this.rate_limit = rate_limit;
-
-    this.c2r_schedule = [];
-    this.r2c_schedule = [];
-
-    this.running = true;
-    this.flush_timeout_id = null;
-
-    /* This callback function can be overridden by external callers. */
-    this.complete_callback = function() {
-    };
-
-    /* Return a function that shows an error message and closes the other
-       half of a communication pair. */
-    this.make_onerror_callback = function(partner)
-    {
-        return function(event) {
-            var ws = event.target;
-
-            log(ws.label + ": error.");
-            partner.close();
-        }.bind(this);
-    };
-
-    this.onopen_callback = function(event) {
-        var ws = event.target;
-
-        log(ws.label + ": connected.");
-    }.bind(this);
-
-    this.onclose_callback = function(event) {
-        var ws = event.target;
-
-        log(ws.label + ": closed.");
-        this.flush();
-
-        if (this.running && is_closed(this.client_s) && is_closed(this.relay_s)) {
-            this.running = false;
-            this.complete_callback();
-        }
-    }.bind(this);
-
-    this.onmessage_client_to_relay = function(event) {
-        this.c2r_schedule.push(event.data);
-        this.flush();
-    }.bind(this);
-
-    this.onmessage_relay_to_client = function(event) {
-        this.r2c_schedule.push(event.data);
-        this.flush();
-    }.bind(this);
-
-    this.connect = function() {
-        log("Client: connecting.");
-        this.client_s = make_websocket(this.client_addr);
-
-        log("Relay: connecting.");
-        this.relay_s = make_websocket(this.relay_addr);
-
-        this.client_s.label = "Client";
-        this.client_s.onopen = this.onopen_callback;
-        this.client_s.onclose = this.onclose_callback;
-        this.client_s.onerror = this.make_onerror_callback(this.relay_s);
-        this.client_s.onmessage = this.onmessage_client_to_relay;
-
-        this.relay_s.label = "Relay";
-        this.relay_s.onopen = this.onopen_callback;
-        this.relay_s.onclose = this.onclose_callback;
-        this.relay_s.onerror = this.make_onerror_callback(this.client_s);
-        this.relay_s.onmessage = this.onmessage_relay_to_client;
-    };
-
-    function is_open(ws)
-    {
-        return ws.readyState === ws.OPEN;
-    }
-
-    function is_closed(ws)
-    {
-        return ws.readyState === ws.CLOSED;
-    }
-
-    this.close = function() {
-        this.client_s.close();
-        this.relay_s.close();
-    };
-
-    /* Send as much data as the rate limit currently allows. */
-    this.flush = function() {
-        var busy;
-
-        if (this.flush_timeout_id)
-            clearTimeout(this.flush_timeout_id);
-        this.flush_timeout_id = null;
-
-        busy = true;
-        while (busy && !this.rate_limit.is_limited()) {
-            var chunk;
-
-            busy = false;
-            if (is_open(this.client_s) && this.client_s.bufferedAmount < MAX_BUFFER && this.r2c_schedule.length > 0) {
-                chunk = this.r2c_schedule.shift();
-                this.rate_limit.update(chunk.length);
-                this.client_s.send(chunk);
-                busy = true;
-            }
-            if (is_open(this.relay_s) && this.relay_s.bufferedAmount < MAX_BUFFER && this.c2r_schedule.length > 0) {
-                chunk = this.c2r_schedule.shift();
-                this.rate_limit.update(chunk.length);
-                this.relay_s.send(chunk);
-                busy = true;
-            }
-        }
-
-        if (is_closed(this.relay_s) && !is_closed(this.client_s) && this.client_s.bufferedAmount === 0 && this.r2c_schedule.length === 0) {
-            log("Client: closing.");
-            this.client_s.close();
-        }
-        if (is_closed(this.client_s) && !is_closed(this.relay_s) && this.relay_s.bufferedAmount === 0 && this.c2r_schedule.length === 0) {
-            log("Relay: closing.");
-            this.relay_s.close();
-        }
-
-        if (this.r2c_schedule.length > 0 || this.client_s.bufferedAmount > 0
-            || this.c2r_schedule.length > 0 || this.relay_s.bufferedAmount > 0)
-            this.flush_timeout_id = setTimeout(this.flush.bind(this), this.rate_limit.when() * 1000);
-    };
-}
-
-function BucketRateLimit(capacity, time) {
-    this.amount = 0.0;
-    /* capacity / time is the rate we are aiming for. */
-    this.capacity = capacity;
-    this.time = time;
-    this.last_update = new Date();
-
-    this.age = function() {
-        var now;
-        var delta;
-
-        now = new Date();
-        delta = (now - this.last_update) / 1000.0;
-        this.last_update = now;
-
-        this.amount -= delta * this.capacity / this.time;
-        if (this.amount < 0.0)
-            this.amount = 0.0;
-    };
-
-    this.update = function(n) {
-        this.age();
-        this.amount += n;
-
-        return this.amount <= this.capacity;
-    };
-
-    /* How many seconds in the future will the limit expire? */
-    this.when = function() {
-        this.age();
-
-        return (this.amount - this.capacity) / (this.capacity / this.time);
-    }
-
-    this.is_limited = function() {
-        this.age();
-
-        return this.amount > this.capacity;
-    }
-}
-
-/* A rate limiter that never limits. */
-function DummyRateLimit(capacity, time) {
-    this.update = function(n) {
-        return true;
-    };
-
-    this.when = function() {
-        return 0.0;
-    }
-
-    this.is_limited = function() {
-        return false;
-    }
-}
-
-var HTML_ESCAPES = {
-    "&": "amp",
-    "<": "lt",
-    ">": "gt",
-    "'": "apos",
-    "\"": "quot"
-};
-function escape_html(s) {
-    return s.replace(/&<>'"/, function(x) { return HTML_ESCAPES[x] });
-}
-
-/* The usual embedded HTML badge. The "elem" member is a DOM element that can be
-   included elsewhere. */
-function Badge() {
-    /* Number of proxy pairs currently connected. */
-    this.num_proxy_pairs = 0;
-
-    var table, tr, td, div, a, img;
-
-    table = document.createElement("table");
-    tr = document.createElement("tr");
-    table.appendChild(tr);
-    td = document.createElement("td");
-    tr.appendChild(td);
-    a = document.createElement("a");
-    a.setAttribute("href", "http://crypto.stanford.edu/flashproxy/");
-    a.setAttribute("target", "_parent");
-    td.appendChild(a);
-    img = document.createElement("img");
-    img.setAttribute("src", "badge.png");
-    img.setAttribute("alt", "Internet freedom");
-    a.appendChild(img);
-
-    this.elem = table;
-    this.elem.className = "idle";
-
-    a = document.createElement("a");
-    a.setAttribute("href", "#");
-    this.disable_button = document.createElement("div");
-    /* HEAVY MULTIPLICATION X */
-    this.disable_button.innerHTML = "✖";
-    this.disable_button.className = "disable-button";
-    a.appendChild(this.disable_button);
-    td.appendChild(a);
-
-    this.proxy_begin = function() {
-        this.num_proxy_pairs++;
-        this.elem.className = "active";
-    };
-
-    this.proxy_end = function() {
-        this.num_proxy_pairs--;
-        if (this.num_proxy_pairs <= 0) {
-            this.elem.className = "idle";
-        }
-    }
-
-    this.disable = function() {
-        this.elem.className = "disabled";
-        this.disable_button.style.display = "none";
-    }
-
-    this.die = function() {
-        this.elem.className = "dead";
-        this.disable_button.style.display = "none";
-    }
-}
-
-function quote(s) {
-    return "\"" + s.replace(/([\\\"])/, "\\$1") + "\"";
-}
-
-function maybe_quote(s) {
-    if (!/^[a-zA-Z_]\w*$/.test(s))
-        return quote(s);
-    else
-        return s;
-}
-
-function repr(x) {
-    if (x === null) {
-        return "null";
-    } else if (typeof x === "undefined") {
-        return "undefined";
-    } else if (typeof x === "object") {
-        var elems = [];
-        for (var k in x)
-            elems.push(maybe_quote(k) + ": " + repr(x[k]));
-        return "{ " + elems.join(", ") + " }";
-    } else if (typeof x === "string") {
-        return quote(x);
-    } else {
-        return x.toString();
-    }
-}
-
-/* Are circumstances such that we should self-disable and not be a
-   proxy? We take a best-effort guess as to whether this device runs on
-   a battery or the data transfer might be expensive.
-
-   http://www.zytrax.com/tech/web/mobile_ids.html
-   http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect…
-   http://search.cpan.org/~cmanley/Mobile-UserAgent-1.05/lib/Mobile/UserAgent.…
-*/
-function flashproxy_should_disable() {
-    var ua;
-
-    ua = window.navigator.userAgent;
-    if (ua !== null) {
-        var UA_LIST = [
-            /\bmobile\b/i,
-            /\bandroid\b/i,
-            /\bopera mobi\b/i,
-        ];
-
-        for (var i = 0; i < UA_LIST.length; i++) {
-            var re = UA_LIST[i];
-
-            if (ua.match(re)) {
-                puts("Disable because User-Agent matches mobile pattern " + re + ".");
-                return true;
-            }
-        }
-    }
-
-    if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
-        && !ua.match(/\bversion\/[6789]\./i)) {
-        /* Disable before Safari 6.0 because it doesn't have the hybi/RFC type
-           of WebSockets. */
-        puts("Disable because User-Agent is Safari before 6.0.");
-        return true;
-    }
-
-    if (!WebSocket) {
-        /* No WebSocket support. */
-        puts("Disable because of no WebSocket support.");
-        return true;
-    }
-
-    return false;
-}
-
-function flashproxy_badge_insert() {
-    var fp;
-    var e;
-
-    fp = new FlashProxy();
-    if (flashproxy_should_disable())
-        fp.disable();
-
-    /* http://intertwingly.net/blog/2006/11/10/Thats-Not-Write for this trick to
-       insert right after the <script> element in the DOM. */
-    e = document;
-    while (e.lastChild && e.lastChild.nodeType === 1) {
-        e = e.lastChild;
-    }
-    e.parentNode.appendChild(fp.badge_elem);
-
-    return fp;
-}
diff --git a/proxy/README b/proxy/README
new file mode 100644
index 0000000..b9d9003
--- /dev/null
+++ b/proxy/README
@@ -0,0 +1,4 @@
+This directory contains the flash proxy JavaScript proxy program and
+associated HTML and media files. End users don't ahve to do anything
+with these files. They are meant to be installed on a centralized web
+server and then accessed through a browser.
diff --git a/proxy/badge.png b/proxy/badge.png
new file mode 100644
index 0000000..068cf78
Binary files /dev/null and b/proxy/badge.png differ
diff --git a/proxy/badge.xcf b/proxy/badge.xcf
new file mode 100644
index 0000000..d12ff1f
Binary files /dev/null and b/proxy/badge.xcf differ
diff --git a/proxy/embed.html b/proxy/embed.html
new file mode 100644
index 0000000..95821ce
--- /dev/null
+++ b/proxy/embed.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta http-equiv="refresh" content="86400">
+<style type="text/css">
+html {
+	width: 100%;
+	height: 100%;
+}
+body {
+	margin: 0;
+	padding: 0;
+	width: 100%;
+	height: 100%;
+}
+#flashproxy-badge {
+	width: 100%;
+	height: 100%;
+	margin: 0;
+	padding: 0;
+	border: 0;
+	border-collapse: collapse;
+	line-height: 0;
+}
+#flashproxy-badge.idle {
+	background-color: #227;
+}
+#flashproxy-badge.active {
+	background-color: #28f;
+}
+#flashproxy-badge.disabled {
+	background-color: #777;
+}
+#flashproxy-badge.dead {
+	background-color: #111;
+}
+#flashproxy-badge td {
+	position: relative;
+	margin: 0;
+	padding: 0;
+	vertical-align: middle;
+	text-align: center;
+}
+#flashproxy-badge td .disable-button {
+	position: absolute;
+	margin: 0;
+	padding: 0;
+	border: 1px solid white;
+	color: white;
+	background-color: #B00;
+	top: 1px;
+	right: 1px;
+	width: 11px;
+	height: 11px;
+	text-align: center;
+	line-height: 11px;
+	font-size: 11px;
+	display: none;
+}
+#flashproxy-badge a img {
+	border: 0;
+}
+#flashproxy-badge.debug {
+	position: absolute;
+	margin: 0;
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 100%;
+	overflow: auto;
+	color: #4c4;
+	background-color: #021;
+	line-height: inherit;
+}
+</style>
+</head>
+<body>
+<script type="text/javascript" src="flashproxy.js"></script>
+<script type="text/javascript">
+flashproxy_badge_insert().start();
+</script>
+<noscript>
+<table id="flashproxy-badge" class="disabled"><tr><td><a href="//crypto.stanford.edu/flashproxy/" target="_parent"><img src="badge.png" alt="Internet freedom"></a></td></tr></table>
+</noscript>
+</body>
+</html>
diff --git a/proxy/flashproxy-test.js b/proxy/flashproxy-test.js
new file mode 100755
index 0000000..2dc185a
--- /dev/null
+++ b/proxy/flashproxy-test.js
@@ -0,0 +1,239 @@
+#!/usr/bin/js
+
+/* To run this test program, install the Rhino JavaScript interpreter
+   (apt-get install rhino). */
+
+var VERBOSE = false;
+if ("-v" in arguments)
+    VERBOSE = true;
+
+var num_tests = 0;
+var num_failed = 0;
+
+var window = {location: {search: "?"}};
+
+load("flashproxy.js");
+
+function objects_equal(a, b)
+{
+    if ((a === null) != (b === null))
+        return false;
+    if (typeof a != typeof b)
+        return false;
+    if (typeof a != "object")
+        return a == b;
+
+    for (var k in a) {
+        if (!objects_equal(a[k], b[k]))
+            return false;
+    }
+    for (var k in b) {
+        if (!objects_equal(a[k], b[k]))
+            return false;
+    }
+
+    return true;
+}
+
+var top = true;
+function announce(test_name)
+{
+    if (VERBOSE) {
+        if (!top)
+            print();
+        print(test_name);
+    }
+    top = false;
+}
+
+function pass(test)
+{
+    num_tests++;
+    if (VERBOSE)
+        print("PASS " + repr(test));
+}
+
+function fail(test, expected, actual)
+{
+    num_tests++;
+    num_failed++;
+    print("FAIL " + repr(test) + "  expected: " + repr(expected) + "  actual: " + repr(actual));
+}
+
+function test_build_url()
+{
+    var TESTS = [
+        { args: ["http", "example.com"],
+          expected: "http://example.com" },
+        { args: ["http", "example.com", 80],
+          expected: "http://example.com" },
+        { args: ["http", "example.com", 81],
+          expected: "http://example.com:81" },
+        { args: ["https", "example.com", 443],
+          expected: "https://example.com" },
+        { args: ["https", "example.com", 444],
+          expected: "https://example.com:444" },
+        { args: ["http", "example.com", 80, "/"],
+          expected: "http://example.com/" },
+        { args: ["http", "example.com", 80, "/test?k=%#v"],
+          expected: "http://example.com/test%3Fk%3D%25%23v" },
+        { args: ["http", "example.com", 80, "/test", []],
+          expected: "http://example.com/test?" },
+        { args: ["http", "example.com", 80, "/test", [["k", "%#v"]]],
+          expected: "http://example.com/test?k=%25%23v" },
+        { args: ["http", "example.com", 80, "/test", [["a", "b"], ["c", "d"]]],
+          expected: "http://example.com/test?a=b&c=d" },
+        { args: ["http", "1.2.3.4"],
+          expected: "http://1.2.3.4" },
+        { args: ["http", "1:2::3:4"],
+          expected: "http://[1:2::3:4]" },
+        { args: ["http", "bog][us"],
+          expected: "http://bog%5D%5Bus" },
+        { args: ["http", "bog:u]s"],
+          expected: "http://bog%3Au%5Ds" },
+    ];
+
+    announce("test_build_url");
+    for (var i = 0; i < TESTS.length; i++) {
+        var test = TESTS[i];
+        var actual;
+
+        actual = build_url.apply(undefined, test.args);
+        if (objects_equal(actual, test.expected))
+            pass(test.args);
+        else
+            fail(test.args, test.expected, actual);
+    }
+}
+
+function test_parse_query_string()
+{
+    var TESTS = [
+        { qs: "",
+          expected: { } },
+        { qs: "a=b",
+          expected: { a: "b" } },
+        { qs: "a=b=c",
+          expected: { a: "b=c" } },
+        { qs: "a=b&c=d",
+          expected: { a: "b", c: "d" } },
+        { qs: "client=&relay=1.2.3.4%3A9001",
+          expected: { client: "", relay: "1.2.3.4:9001" } },
+        { qs: "a=b%26c=d",
+          expected: { a: "b&c=d" } },
+        { qs: "a%3db=d",
+          expected: { "a=b": "d" } },
+        { qs: "a=b+c%20d",
+          expected: { "a": "b c d" } },
+        { qs: "a=b+c%2bd",
+          expected: { "a": "b c+d" } },
+        { qs: "a+b=c",
+          expected: { "a b": "c" } },
+        { qs: "a=b+c+d",
+          expected: { a: "b c d" } },
+        /* First appearance wins. */
+        { qs: "a=b&c=d&a=e",
+          expected: { a: "b", c: "d" } },
+        { qs: "a",
+          expected: { a: "" } },
+        { qs: "=b",
+          expected: { "": "b" } },
+        { qs: "&a=b",
+          expected: { "": "", a: "b" } },
+        { qs: "a=b&",
+          expected: { "": "", a: "b" } },
+        { qs: "a=b&&c=d",
+          expected: { "": "", a: "b", c: "d" } },
+    ];
+
+    announce("test_parse_query_string");
+    for (var i = 0; i < TESTS.length; i++) {
+        var test = TESTS[i];
+        var actual;
+
+        actual = parse_query_string(test.qs);
+        if (objects_equal(actual, test.expected))
+            pass(test.qs);
+        else
+            fail(test.qs, test.expected, actual);
+    }
+}
+
+function test_parse_addr_spec()
+{
+    var TESTS = [
+        { spec: "",
+          expected: null },
+        { spec: "3.3.3.3:4444",
+          expected: { host: "3.3.3.3", port: 4444 } },
+        { spec: "3.3.3.3",
+          expected: null },
+        { spec: "3.3.3.3:0x1111",
+          expected: null },
+        { spec: "3.3.3.3:-4444",
+          expected: null },
+        { spec: "3.3.3.3:65536",
+          expected: null },
+        { spec: "[1:2::a:f]:4444",
+          expected: { host: "1:2::a:f", port: 4444 } },
+        { spec: "[1:2::a:f]",
+          expected: null },
+        { spec: "[1:2::a:f]:0x1111",
+          expected: null },
+        { spec: "[1:2::a:f]:-4444",
+          expected: null },
+        { spec: "[1:2::a:f]:65536",
+          expected: null },
+        { spec: "[1:2::ffff:1.2.3.4]:4444",
+          expected: { host: "1:2::ffff:1.2.3.4", port: 4444 } },
+    ];
+
+    announce("test_parse_addr_spec");
+    for (var i = 0; i < TESTS.length; i++) {
+        var test = TESTS[i];
+        var actual;
+
+        actual = parse_addr_spec(test.spec);
+        if (objects_equal(actual, test.expected))
+            pass(test.spec);
+        else
+            fail(test.spec, test.expected, actual);
+    }
+}
+
+function test_get_query_param_addr()
+{
+    var DEFAULT = { host: "1.1.1.1", port: 2222 };
+    var TESTS = [
+        { query: { },
+          expected: DEFAULT },
+        { query: { addr: "3.3.3.3:4444" },
+          expected: { host: "3.3.3.3", port: 4444 } },
+        { query: { x: "3.3.3.3:4444" },
+          expected: DEFAULT },
+        { query: { addr: "---" },
+          expected: null },
+    ];
+
+    announce("test_get_query_param_addr");
+    for (var i = 0; i < TESTS.length; i++) {
+        var test = TESTS[i];
+        var actual;
+
+        actual = get_query_param_addr(test.query, "addr", DEFAULT);
+        if (objects_equal(actual, test.expected))
+            pass(test.query);
+        else
+            fail(test.query, test.expected, actual);
+    }
+}
+
+test_build_url();
+test_parse_query_string();
+test_parse_addr_spec();
+test_get_query_param_addr();
+
+if (num_failed == 0)
+    quit(0);
+else
+    quit(1);
diff --git a/proxy/flashproxy.js b/proxy/flashproxy.js
new file mode 100644
index 0000000..a2e8863
--- /dev/null
+++ b/proxy/flashproxy.js
@@ -0,0 +1,898 @@
+/* Query string parameters. These change how the program runs from the outside.
+ * For example:
+ *   http://www.example.com/embed.html?facilitator=127.0.0.1:9002&debug=1
+ *
+ * client=<HOST>:<PORT>
+ * The address of the client to connect to. The proxy normally receives this
+ * information from the facilitator. When this option is used, the facilitator
+ * query is not done. The "relay" parameter must be given as well.
+ *
+ * debug=1
+ * If set (to any value), show verbose terminal-like output instead of the
+ * badge.
+ *
+ * facilitator=https://host:port/
+ * The URL of the facilitator CGI script. By default it is
+ * DEFAULT_FACILITATOR_URL.
+ *
+ * facilitator_poll_interval=<FLOAT>
+ * How often to poll the facilitator, in seconds. The default is
+ * DEFAULT_FACILITATOR_POLL_INTERVAL. There is a sanity-check minimum of 1.0 s.
+ *
+ * max_clients=<NUM>
+ * How many clients to serve concurrently. The default is
+ * DEFAULT_MAX_NUM_PROXY_PAIRS.
+ *
+ * relay=<HOST>:<PORT>
+ * The address of the relay to connect to. The proxy normally receives this
+ * information from the facilitator. When this option is used, the facilitator
+ * query is not done. The "client" parameter must be given as well.
+ *
+ * ratelimit=<FLOAT>(<UNIT>)?|off
+ * What rate to limit all proxy traffic combined to. The special value "off"
+ * disables the limit. The default is DEFAULT_RATE_LIMIT. There is a
+ * sanity-check minimum of "10K".
+ */
+
+/* WebSocket links.
+ *
+ * The WebSocket Protocol
+ * https://tools.ietf.org/html/rfc6455
+ *
+ * The WebSocket API
+ * http://dev.w3.org/html5/websockets/
+ *
+ * MDN page with browser compatibility
+ * https://developer.mozilla.org/en/WebSockets
+ *
+ * Implementation tests (including tests of binary messages)
+ * http://autobahn.ws/testsuite/reports/clients/index.html
+ */
+
+var DEFAULT_FACILITATOR_URL = "https://tor-facilitator.bamsoftware.com/";
+
+var DEFAULT_MAX_NUM_PROXY_PAIRS = 10;
+
+var DEFAULT_FACILITATOR_POLL_INTERVAL = 10.0;
+var MIN_FACILITATOR_POLL_INTERVAL = 1.0;
+
+/* Bytes per second. Set to undefined to disable limit. */
+var DEFAULT_RATE_LIMIT = undefined;
+var MIN_RATE_LIMIT = 10 * 1024;
+var RATE_LIMIT_HISTORY = 5.0;
+
+/* Firefox before version 11.0 uses the name MozWebSocket. Whether the global
+   variable WebSocket is defined indicates whether WebSocket is supported at
+   all. */
+var WebSocket = window.WebSocket || window.MozWebSocket;
+
+var query = parse_query_string(window.location.search.substr(1));
+var debug_div;
+
+if (query.debug) {
+    debug_div = document.createElement("pre");
+    debug_div.className = "debug";
+}
+
+function puts(s) {
+    if (debug_div) {
+        var at_bottom;
+
+        /* http://www.w3.org/TR/cssom-view/#element-scrolling-members */
+        at_bottom = (debug_div.scrollTop + debug_div.clientHeight === debug_div.scrollHeight);
+        debug_div.appendChild(document.createTextNode(s + "\n"));
+        if (at_bottom)
+            debug_div.scrollTop = debug_div.scrollHeight;
+    }
+}
+
+/* Parse a URL query string or application/x-www-form-urlencoded body. The
+   return type is an object mapping string keys to string values. By design,
+   this function doesn't support multiple values for the same named parameter,
+   for example "a=1&a=2&a=3"; the first definition always wins. Returns null on
+   error.
+
+   Always decodes from UTF-8, not any other encoding.
+
+   http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data */
+function parse_query_string(qs) {
+    var strings;
+    var result;
+
+    result = {};
+    if (qs)
+        strings = qs.split("&");
+    else
+        strings = [];
+    for (var i = 0; i < strings.length; i++) {
+        var string = strings[i];
+        var j, name, value;
+
+        j = string.indexOf("=");
+        if (j === -1) {
+            name = string;
+            value = "";
+        } else {
+            name = string.substr(0, j);
+            value = string.substr(j + 1);
+        }
+        name = decodeURIComponent(name.replace(/\+/g, " "));
+        value = decodeURIComponent(value.replace(/\+/g, " "));
+        if (!(name in result))
+             result[name] = value;
+    }
+
+    return result;
+}
+
+var DEFAULT_PORTS = {
+    http: 80,
+    https: 443
+}
+/* Build an escaped URL string from unescaped components. Only scheme and host
+   are required. See RFC 3986, section 3. */
+function build_url(scheme, host, port, path, params) {
+    var parts = []
+
+    parts.push(encodeURIComponent(scheme));
+    parts.push("://");
+
+    /* If it contains a colon but no square brackets, treat it like an IPv6
+       address. */
+    if (host.match(/:/) && !host.match(/[[\]]/)) {
+        parts.push("[");
+        parts.push(host);
+        parts.push("]");
+    } else {
+        parts.push(encodeURIComponent(host));
+    }
+    if (port !== undefined && port !== DEFAULT_PORTS[scheme]) {
+        parts.push(":");
+        parts.push(encodeURIComponent(port.toString()));
+    }
+
+    if (path !== undefined && path !== "") {
+        if (!path.match(/^\//))
+            path = "/" + path;
+        /* Slash is significant so we must protect it from encodeURIComponent,
+           while still encoding question mark and number sign. RFC 3986, section
+           3.3: "The path is terminated by the first question mark ('?') or
+           number sign ('#') character, or by the end of the URI. ... A path
+           consists of a sequence of path segments separated by a slash ('/')
+           character." */
+        path = path.replace(/[^\/]+/, function(m) {
+            return encodeURIComponent(m);
+        });
+        parts.push(path);
+    }
+
+    if (params !== undefined) {
+        parts.push("?");
+        for (var i = 0; i < params.length; i++) {
+            if (i > 0)
+                parts.push("&");
+            parts.push(encodeURIComponent(params[i][0]) + "=" + encodeURIComponent(params[i][1]));
+        }
+    }
+
+    return parts.join("");
+}
+
+/* Get a query string parameter and return it as a string. Returns default_val
+   if param is not defined in the query string. */
+function get_query_param_string(query, param, default_val) {
+    var val;
+
+    val = query[param];
+    if (val === undefined)
+        return default_val;
+    else
+        return val;
+}
+
+/* Get a query string parameter, or the given default, and parse it as an
+   address spec. Returns null on a parsing error. */
+function get_query_param_addr(query, param, default_val) {
+    var val;
+
+    val = query[param];
+    if (val === undefined)
+        return default_val;
+    else
+        return parse_addr_spec(val);
+}
+
+/* Get an integer from the given movie parameter, or the given default. Returns
+   null on error. */
+function get_query_param_integer(query, param, default_val) {
+    var spec;
+    var val;
+
+    spec = query[param];
+    if (spec === undefined) {
+        return default_val;
+    } else if (!spec.match(/^-?[0-9]+/)) {
+        return null;
+    } else {
+        val = parseInt(spec, 10);
+        if (isNaN(val))
+            return null;
+        else
+            return val;
+    }
+}
+
+/* Get a number from the given movie parameter, or the given default. Returns
+   null on error. */
+function get_query_param_number(query, param, default_val) {
+    var spec;
+    var val;
+
+    spec = query[param];
+    if (spec === undefined) {
+        return default_val;
+    } else {
+        val = Number(spec);
+        if (isNaN(val))
+            return null;
+        else
+            return val;
+    }
+}
+
+/* Get a floating-point number of seconds from a time specification. The only
+   time specification format is a decimal number of seconds. Returns null on
+   error. */
+function get_query_param_timespec(query, param, default_val) {
+    return get_query_param_number(query, param, default_val);
+}
+
+/* Parse a count of bytes. A suffix of "k", "m", or "g" (or uppercase)
+   does what you would think. Returns null on error. */
+function parse_byte_count(spec) {
+    var UNITS = {
+        k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024,
+        K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
+    };
+    var count, units;
+    var matches;
+
+    matches = spec.match(/^(\d+(?:\.\d*)?)(\w*)$/);
+    if (matches === null)
+        return null;
+
+    count = Number(matches[1]);
+    if (isNaN(count))
+        return null;
+
+    if (matches[2] === "") {
+        units = 1;
+    } else {
+        units = UNITS[matches[2]];
+        if (units === null)
+            return null;
+    }
+
+    return count * Number(units);
+}
+
+/* Get a count of bytes from a string specification like "100" or "1.3m".
+   Returns null on error. */
+function get_query_param_byte_count(query, param, default_val) {
+    var spec;
+
+    spec = query[param];
+    if (spec === undefined)
+        return default_val;
+    else
+        return parse_byte_count(spec);
+}
+
+/* Parse an address in the form "host:port". Returns an Object with
+   keys "host" (String) and "port" (int). Returns null on error. */
+function parse_addr_spec(spec) {
+    var m, host, port;
+
+    m = null;
+    /* IPv6 syntax. */
+    if (!m)
+        m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/);
+    /* IPv4 syntax. */
+    if (!m)
+        m = spec.match(/^([0-9.]+):([0-9]+)$/);
+    if (!m)
+        return null;
+    host = m[1];
+    port = parseInt(m[2], 10);
+    if (isNaN(port) || port < 0 || port > 65535)
+        return null;
+
+    return { host: host, port: port }
+}
+
+function format_addr(addr) {
+    return addr.host + ":" + addr.port;
+}
+
+/* Does the WebSocket implementation in this browser support binary frames? (RFC
+   6455 section 5.6.) If not, we have to use base64-encoded text frames. It is
+   assumed that the client and relay endpoints always support binary frames. */
+function have_websocket_binary_frames() {
+    var ua, matches;
+
+    ua = window.navigator.userAgent;
+    if (ua === null)
+        return false;
+
+    /* We are cool for Chrome 16 or Safari 6.0. */
+
+    matches = ua.match(/\bchrome\/(\d+)/i);
+    if (matches !== null && Number(matches[1]) >= 16)
+        return true;
+
+    matches = ua.match(/\bversion\/(\d+)/i);
+    if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
+        && Number(matches[1]) >= 6)
+        return true;
+
+    return false;
+}
+
+function make_websocket(addr) {
+    var url;
+    var ws;
+
+    url = build_url("ws", addr.host, addr.port, "/");
+
+    if (have_websocket_binary_frames())
+        ws = new WebSocket(url);
+    else
+        ws = new WebSocket(url, "base64");
+    /* "User agents can use this as a hint for how to handle incoming binary
+       data: if the attribute is set to 'blob', it is safe to spool it to disk,
+       and if it is set to 'arraybuffer', it is likely more efficient to keep
+       the data in memory." */
+    ws.binaryType = "arraybuffer";
+
+    return ws;
+}
+
+function FlashProxy() {
+    if (query.debug) {
+        this.badge_elem = debug_div;
+    } else {
+        this.badge = new Badge();
+        this.badge.elem.onmouseover = function(event) {
+            this.badge.disable_button.style.display = "block";
+        }.bind(this);
+        this.badge.elem.onmouseout = function(event) {
+            this.badge.disable_button.style.display = "none";
+        }.bind(this);
+        /* Click a button to disable the badge. */
+        this.badge.disable_button.onclick = function(event) {
+            this.disable();
+            this.badge.disable_button.parentNode.removeChild(this.badge.disable_button);
+        }.bind(this);
+        this.badge_elem = this.badge.elem;
+    }
+    this.badge_elem.setAttribute("id", "flashproxy-badge");
+
+    this.proxy_pairs = [];
+
+    this.start = function() {
+        var client_addr;
+        var relay_addr;
+        var rate_limit_bytes;
+
+        this.fac_url = get_query_param_string(query, "facilitator", DEFAULT_FACILITATOR_URL);
+
+        this.max_num_proxy_pairs = get_query_param_integer(query, "max_clients", DEFAULT_MAX_NUM_PROXY_PAIRS);
+        if (this.max_num_proxy_pairs === null || this.max_num_proxy_pairs < 0) {
+            puts("Error: max_clients must be a nonnegative integer.");
+            this.die();
+            return;
+        }
+
+        this.facilitator_poll_interval = get_query_param_timespec(query, "facilitator_poll_interval", DEFAULT_FACILITATOR_POLL_INTERVAL);
+        if (this.facilitator_poll_interval === null || this.facilitator_poll_interval < MIN_FACILITATOR_POLL_INTERVAL) {
+            puts("Error: facilitator_poll_interval must be a nonnegative number at least " + MIN_FACILITATOR_POLL_INTERVAL + ".");
+            this.die();
+            return;
+        }
+
+        if (query["ratelimit"] === "off")
+            rate_limit_bytes = undefined;
+        else
+            rate_limit_bytes = get_query_param_byte_count(query, "ratelimit", DEFAULT_RATE_LIMIT);
+        if (rate_limit_bytes === undefined) {
+            this.rate_limit = new DummyRateLimit();
+        } else if (rate_limit_bytes === null || rate_limit_bytes < MIN_FACILITATOR_POLL_INTERVAL) {
+            puts("Error: ratelimit must be a nonnegative number at least " + MIN_RATE_LIMIT + ".");
+            this.die();
+            return;
+        } else {
+            this.rate_limit = new BucketRateLimit(rate_limit_bytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
+        }
+
+        client_addr = get_query_param_addr(query, "client");
+        relay_addr = get_query_param_addr(query, "relay");
+        if (client_addr !== undefined && relay_addr !== undefined) {
+            this.make_proxy_pair(client_addr, relay_addr);
+        } else if (client_addr !== undefined) {
+            puts("Error: the \"client\" parameter requires \"relay\" also.")
+            this.die();
+            return;
+        } else if (relay_addr !== undefined) {
+            puts("Error: the \"relay\" parameter requires \"client\" also.")
+            this.die();
+            return;
+        } else {
+            this.proxy_main();
+        }
+    };
+
+    this.proxy_main = function() {
+        var xhr;
+
+        if (this.proxy_pairs.length >= this.max_num_proxy_pairs) {
+            setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval);
+            return;
+        }
+
+        xhr = new XMLHttpRequest();
+        try {
+            xhr.open("GET", this.fac_url);
+        } catch (err) {
+            /* An exception happens here when, for example, NoScript allows the
+               domain on which the proxy badge runs, but not the domain to which
+               it's trying to make the HTTP request. The exception message is
+               like "Component returned failure code: 0x805e0006
+               [nsIXMLHttpRequest.open]" on Firefox. */
+            puts("Facilitator: exception while connecting: " + repr(err.message) + ".");
+            this.die();
+            return;
+        }
+        xhr.responseType = "text";
+        xhr.onreadystatechange = function() {
+            if (xhr.readyState === xhr.DONE) {
+                if (xhr.status === 200)
+                    this.fac_complete(xhr.responseText);
+                else
+                    puts("Facilitator: can't connect: got status " + repr(xhr.status) + " and status text " + repr(xhr.statusText) + ".");
+            }
+        }.bind(this);
+        puts("Facilitator: connecting to " + this.fac_url + ".");
+        xhr.send(null);
+    };
+
+    this.fac_complete = function(text) {
+        var response;
+        var client_addr;
+        var relay_addr;
+
+        setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval * 1000);
+
+        response = parse_query_string(text);
+
+        if (!response.client) {
+            puts("No clients.");
+            return;
+        }
+        client_addr = parse_addr_spec(response.client);
+        if (client_addr === null) {
+            puts("Error: can't parse client spec " + repr(response.client) + ".");
+            return;
+        }
+        if (!response.relay) {
+            puts("Error: missing relay in response.");
+            return;
+        }
+        relay_addr = parse_addr_spec(response.relay);
+        if (relay_addr === null) {
+            puts("Error: can't parse relay spec " + repr(response.relay) + ".");
+            return;
+        }
+        puts("Facilitator: got client:" + repr(client_addr) + " "
+            + "relay:" + repr(relay_addr) + ".");
+
+        this.make_proxy_pair(client_addr, relay_addr);
+    };
+
+    this.make_proxy_pair = function(client_addr, relay_addr) {
+        var proxy_pair;
+
+        proxy_pair = new ProxyPair(client_addr, relay_addr, this.rate_limit);
+        this.proxy_pairs.push(proxy_pair);
+        proxy_pair.complete_callback = function(event) {
+            puts("Complete.");
+            /* Delete from the list of active proxy pairs. */
+            this.proxy_pairs.splice(this.proxy_pairs.indexOf(proxy_pair), 1);
+            if (this.badge)
+                this.badge.proxy_end();
+        }.bind(this);
+        try {
+            proxy_pair.connect();
+        } catch (err) {
+            puts("ProxyPair: exception while connecting: " + repr(err.message) + ".");
+            this.die();
+            return;
+        }
+
+        if (this.badge)
+            this.badge.proxy_begin();
+    };
+
+    /* Cease all network operations and prevent any future ones. */
+    this.disable = function() {
+        puts("Disabling.");
+        this.proxy_main = function() { };
+        this.make_proxy_pair = function(client_addr, relay_addr) { };
+        while (this.proxy_pairs.length > 0)
+            this.proxy_pairs.pop().close();
+        if (this.badge)
+            this.badge.disable();
+    };
+
+    this.die = function() {
+        puts("Dying.");
+        if (this.badge)
+            this.badge.die();
+    };
+}
+
+/* An instance of a client-relay connection. */
+function ProxyPair(client_addr, relay_addr, rate_limit) {
+    var MAX_BUFFER = 10 * 1024 * 1024;
+
+    function log(s)
+    {
+        puts(s)
+    }
+
+    this.client_addr = client_addr;
+    this.relay_addr = relay_addr;
+    this.rate_limit = rate_limit;
+
+    this.c2r_schedule = [];
+    this.r2c_schedule = [];
+
+    this.running = true;
+    this.flush_timeout_id = null;
+
+    /* This callback function can be overridden by external callers. */
+    this.complete_callback = function() {
+    };
+
+    /* Return a function that shows an error message and closes the other
+       half of a communication pair. */
+    this.make_onerror_callback = function(partner)
+    {
+        return function(event) {
+            var ws = event.target;
+
+            log(ws.label + ": error.");
+            partner.close();
+        }.bind(this);
+    };
+
+    this.onopen_callback = function(event) {
+        var ws = event.target;
+
+        log(ws.label + ": connected.");
+    }.bind(this);
+
+    this.onclose_callback = function(event) {
+        var ws = event.target;
+
+        log(ws.label + ": closed.");
+        this.flush();
+
+        if (this.running && is_closed(this.client_s) && is_closed(this.relay_s)) {
+            this.running = false;
+            this.complete_callback();
+        }
+    }.bind(this);
+
+    this.onmessage_client_to_relay = function(event) {
+        this.c2r_schedule.push(event.data);
+        this.flush();
+    }.bind(this);
+
+    this.onmessage_relay_to_client = function(event) {
+        this.r2c_schedule.push(event.data);
+        this.flush();
+    }.bind(this);
+
+    this.connect = function() {
+        log("Client: connecting.");
+        this.client_s = make_websocket(this.client_addr);
+
+        log("Relay: connecting.");
+        this.relay_s = make_websocket(this.relay_addr);
+
+        this.client_s.label = "Client";
+        this.client_s.onopen = this.onopen_callback;
+        this.client_s.onclose = this.onclose_callback;
+        this.client_s.onerror = this.make_onerror_callback(this.relay_s);
+        this.client_s.onmessage = this.onmessage_client_to_relay;
+
+        this.relay_s.label = "Relay";
+        this.relay_s.onopen = this.onopen_callback;
+        this.relay_s.onclose = this.onclose_callback;
+        this.relay_s.onerror = this.make_onerror_callback(this.client_s);
+        this.relay_s.onmessage = this.onmessage_relay_to_client;
+    };
+
+    function is_open(ws)
+    {
+        return ws.readyState === ws.OPEN;
+    }
+
+    function is_closed(ws)
+    {
+        return ws.readyState === ws.CLOSED;
+    }
+
+    this.close = function() {
+        this.client_s.close();
+        this.relay_s.close();
+    };
+
+    /* Send as much data as the rate limit currently allows. */
+    this.flush = function() {
+        var busy;
+
+        if (this.flush_timeout_id)
+            clearTimeout(this.flush_timeout_id);
+        this.flush_timeout_id = null;
+
+        busy = true;
+        while (busy && !this.rate_limit.is_limited()) {
+            var chunk;
+
+            busy = false;
+            if (is_open(this.client_s) && this.client_s.bufferedAmount < MAX_BUFFER && this.r2c_schedule.length > 0) {
+                chunk = this.r2c_schedule.shift();
+                this.rate_limit.update(chunk.length);
+                this.client_s.send(chunk);
+                busy = true;
+            }
+            if (is_open(this.relay_s) && this.relay_s.bufferedAmount < MAX_BUFFER && this.c2r_schedule.length > 0) {
+                chunk = this.c2r_schedule.shift();
+                this.rate_limit.update(chunk.length);
+                this.relay_s.send(chunk);
+                busy = true;
+            }
+        }
+
+        if (is_closed(this.relay_s) && !is_closed(this.client_s) && this.client_s.bufferedAmount === 0 && this.r2c_schedule.length === 0) {
+            log("Client: closing.");
+            this.client_s.close();
+        }
+        if (is_closed(this.client_s) && !is_closed(this.relay_s) && this.relay_s.bufferedAmount === 0 && this.c2r_schedule.length === 0) {
+            log("Relay: closing.");
+            this.relay_s.close();
+        }
+
+        if (this.r2c_schedule.length > 0 || this.client_s.bufferedAmount > 0
+            || this.c2r_schedule.length > 0 || this.relay_s.bufferedAmount > 0)
+            this.flush_timeout_id = setTimeout(this.flush.bind(this), this.rate_limit.when() * 1000);
+    };
+}
+
+function BucketRateLimit(capacity, time) {
+    this.amount = 0.0;
+    /* capacity / time is the rate we are aiming for. */
+    this.capacity = capacity;
+    this.time = time;
+    this.last_update = new Date();
+
+    this.age = function() {
+        var now;
+        var delta;
+
+        now = new Date();
+        delta = (now - this.last_update) / 1000.0;
+        this.last_update = now;
+
+        this.amount -= delta * this.capacity / this.time;
+        if (this.amount < 0.0)
+            this.amount = 0.0;
+    };
+
+    this.update = function(n) {
+        this.age();
+        this.amount += n;
+
+        return this.amount <= this.capacity;
+    };
+
+    /* How many seconds in the future will the limit expire? */
+    this.when = function() {
+        this.age();
+
+        return (this.amount - this.capacity) / (this.capacity / this.time);
+    }
+
+    this.is_limited = function() {
+        this.age();
+
+        return this.amount > this.capacity;
+    }
+}
+
+/* A rate limiter that never limits. */
+function DummyRateLimit(capacity, time) {
+    this.update = function(n) {
+        return true;
+    };
+
+    this.when = function() {
+        return 0.0;
+    }
+
+    this.is_limited = function() {
+        return false;
+    }
+}
+
+var HTML_ESCAPES = {
+    "&": "amp",
+    "<": "lt",
+    ">": "gt",
+    "'": "apos",
+    "\"": "quot"
+};
+function escape_html(s) {
+    return s.replace(/&<>'"/, function(x) { return HTML_ESCAPES[x] });
+}
+
+/* The usual embedded HTML badge. The "elem" member is a DOM element that can be
+   included elsewhere. */
+function Badge() {
+    /* Number of proxy pairs currently connected. */
+    this.num_proxy_pairs = 0;
+
+    var table, tr, td, div, a, img;
+
+    table = document.createElement("table");
+    tr = document.createElement("tr");
+    table.appendChild(tr);
+    td = document.createElement("td");
+    tr.appendChild(td);
+    a = document.createElement("a");
+    a.setAttribute("href", "http://crypto.stanford.edu/flashproxy/");
+    a.setAttribute("target", "_parent");
+    td.appendChild(a);
+    img = document.createElement("img");
+    img.setAttribute("src", "badge.png");
+    img.setAttribute("alt", "Internet freedom");
+    a.appendChild(img);
+
+    this.elem = table;
+    this.elem.className = "idle";
+
+    a = document.createElement("a");
+    a.setAttribute("href", "#");
+    this.disable_button = document.createElement("div");
+    /* HEAVY MULTIPLICATION X */
+    this.disable_button.innerHTML = "✖";
+    this.disable_button.className = "disable-button";
+    a.appendChild(this.disable_button);
+    td.appendChild(a);
+
+    this.proxy_begin = function() {
+        this.num_proxy_pairs++;
+        this.elem.className = "active";
+    };
+
+    this.proxy_end = function() {
+        this.num_proxy_pairs--;
+        if (this.num_proxy_pairs <= 0) {
+            this.elem.className = "idle";
+        }
+    }
+
+    this.disable = function() {
+        this.elem.className = "disabled";
+        this.disable_button.style.display = "none";
+    }
+
+    this.die = function() {
+        this.elem.className = "dead";
+        this.disable_button.style.display = "none";
+    }
+}
+
+function quote(s) {
+    return "\"" + s.replace(/([\\\"])/, "\\$1") + "\"";
+}
+
+function maybe_quote(s) {
+    if (!/^[a-zA-Z_]\w*$/.test(s))
+        return quote(s);
+    else
+        return s;
+}
+
+function repr(x) {
+    if (x === null) {
+        return "null";
+    } else if (typeof x === "undefined") {
+        return "undefined";
+    } else if (typeof x === "object") {
+        var elems = [];
+        for (var k in x)
+            elems.push(maybe_quote(k) + ": " + repr(x[k]));
+        return "{ " + elems.join(", ") + " }";
+    } else if (typeof x === "string") {
+        return quote(x);
+    } else {
+        return x.toString();
+    }
+}
+
+/* Are circumstances such that we should self-disable and not be a
+   proxy? We take a best-effort guess as to whether this device runs on
+   a battery or the data transfer might be expensive.
+
+   http://www.zytrax.com/tech/web/mobile_ids.html
+   http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect…
+   http://search.cpan.org/~cmanley/Mobile-UserAgent-1.05/lib/Mobile/UserAgent.…
+*/
+function flashproxy_should_disable() {
+    var ua;
+
+    ua = window.navigator.userAgent;
+    if (ua !== null) {
+        var UA_LIST = [
+            /\bmobile\b/i,
+            /\bandroid\b/i,
+            /\bopera mobi\b/i,
+        ];
+
+        for (var i = 0; i < UA_LIST.length; i++) {
+            var re = UA_LIST[i];
+
+            if (ua.match(re)) {
+                puts("Disable because User-Agent matches mobile pattern " + re + ".");
+                return true;
+            }
+        }
+    }
+
+    if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
+        && !ua.match(/\bversion\/[6789]\./i)) {
+        /* Disable before Safari 6.0 because it doesn't have the hybi/RFC type
+           of WebSockets. */
+        puts("Disable because User-Agent is Safari before 6.0.");
+        return true;
+    }
+
+    if (!WebSocket) {
+        /* No WebSocket support. */
+        puts("Disable because of no WebSocket support.");
+        return true;
+    }
+
+    return false;
+}
+
+function flashproxy_badge_insert() {
+    var fp;
+    var e;
+
+    fp = new FlashProxy();
+    if (flashproxy_should_disable())
+        fp.disable();
+
+    /* http://intertwingly.net/blog/2006/11/10/Thats-Not-Write for this trick to
+       insert right after the <script> element in the DOM. */
+    e = document;
+    while (e.lastChild && e.lastChild.nodeType === 1) {
+        e = e.lastChild;
+    }
+    e.parentNode.appendChild(fp.badge_elem);
+
+    return fp;
+}
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            [metrics-web/master] Add option to draw censorship	events w/o expected user ranges.
                        
                        
by karsten@torproject.org 29 Sep '12
                    by karsten@torproject.org 29 Sep '12
29 Sep '12
                    
                        commit 3b41eaaa410252b71d386b5d90a7945acde93b56
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date:   Sat Sep 29 13:37:34 2012 -0400
    Add option to draw censorship events w/o expected user ranges.
---
 rserve/graphs.R                                    |   14 ++++++++------
 .../ernie/web/GraphParameterChecker.java           |    2 +-
 web/WEB-INF/users.jsp                              |    8 +++++---
 3 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/rserve/graphs.R b/rserve/graphs.R
index f5bc089..af186f2 100644
--- a/rserve/graphs.R
+++ b/rserve/graphs.R
@@ -705,20 +705,22 @@ plot_direct_users <- function(start, end, country, events, path, dpi) {
   max_y <- ifelse(length(na.omit(u$users)) == 0, 0,
       max(u$users, na.rm = TRUE))
   plot <- ggplot(u, aes(x = as.Date(date, "%Y-%m-%d"), y = users))
-  if (length(na.omit(u$users)) > 0 & events == "on" & country != "all") {
+  if (length(na.omit(u$users)) > 0 & events != "off" & country != "all") {
     r <- read.csv(
       "/srv/metrics.torproject.org/web/detector/direct-users-ranges.csv",
       stringsAsFactors = FALSE)
     r <- r[r$date >= start & r$date <= end & r$country == country,
         c("date", "minusers", "maxusers")]
-    if (length(r$maxusers) > 0)
-      max_y <- max(max_y, max(r$maxusers, na.rm = TRUE))
     r <- cast(rbind(melt(u, id.vars = "date"), melt(r, id.vars = "date")))
     upturns <- r[r$users > r$maxusers, 1:2]
     downturns <- r[r$users < r$minusers, 1:2]
-    plot <- plot +
-      geom_ribbon(data = r, aes(ymin = minusers, ymax = maxusers),
-          fill = "gray")
+    if (events == "on") {
+      if (length(r$maxusers) > 0)
+        max_y <- max(max_y, max(r$maxusers, na.rm = TRUE))
+      plot <- plot +
+        geom_ribbon(data = r, aes(ymin = minusers, ymax = maxusers),
+            fill = "gray")
+    }
     if (length(upturns$date) > 0)
       plot <- plot +
           geom_point(data = upturns, aes(x = date, y = users), size = 5,
diff --git a/src/org/torproject/ernie/web/GraphParameterChecker.java b/src/org/torproject/ernie/web/GraphParameterChecker.java
index c939035..16d0db4 100644
--- a/src/org/torproject/ernie/web/GraphParameterChecker.java
+++ b/src/org/torproject/ernie/web/GraphParameterChecker.java
@@ -56,7 +56,7 @@ public class GraphParameterChecker {
       sb.append("," + country[0]);
     }
     this.knownParameterValues.put("country", sb.toString());
-    this.knownParameterValues.put("events", "on,off");
+    this.knownParameterValues.put("events", "on,off,points");
     this.knownParameterValues.put("language", "all,en,zh_CN,fa");
     this.knownParameterValues.put("source", "all,siv,moria,torperf");
     this.knownParameterValues.put("filesize", "50kb,1mb,5mb");
diff --git a/web/WEB-INF/users.jsp b/web/WEB-INF/users.jsp
index b1e11a6..f422123 100644
--- a/web/WEB-INF/users.jsp
+++ b/web/WEB-INF/users.jsp
@@ -48,9 +48,11 @@ based on the requests seen by a few dozen directory mirrors.</p>
     </p><p>
       Show possible censorship events if available (<a
       href="http://research.torproject.org/techreports/detector-2011-09-09.pdf">BETA</a>)
-      <input type="checkbox" name="events" value="on"
-        <c:if test="${direct_users_events[0] eq 'on'}"> checked</c:if>
-      ></input>
+      <select name="events">
+        <option value="off">Off</option>
+        <option value="on"<c:if test="${direct_users_events[0] eq 'on'}"> selected</c:if>>On: both points and expected range</option>
+        <option value="points"<c:if test="${direct_users_events[0] eq 'points'}"> selected</c:if>>On: points only, no expected range</option>
+      </select>
     </p><p>
       Resolution: <select name="dpi">
         <option value="72"<c:if test="${direct_users_dpi[0] eq '72'}"> selected</c:if>>Screen - 576x360</option>
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    29 Sep '12
                    
                        commit a18429127f214218ba17370518a04c35d557957b
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date:   Sat Sep 29 13:21:31 2012 -0400
    Revert "Faravahar is actually all lower-case."
    
    This reverts commit 4ef39d487722db99d8889b837aabcb4408c11101.
---
 src/org/torproject/doctor/Checker.java    |    6 +++---
 src/org/torproject/doctor/Downloader.java |    2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/org/torproject/doctor/Checker.java b/src/org/torproject/doctor/Checker.java
index 564195d..3f1276a 100644
--- a/src/org/torproject/doctor/Checker.java
+++ b/src/org/torproject/doctor/Checker.java
@@ -96,7 +96,7 @@ public class Checker {
   private void checkMissingConsensuses() {
     SortedSet<String> missingConsensuses = new TreeSet<String>(
         Arrays.asList(("gabelmoo,tor26,turtles,maatuska,dannenberg,urras,"
-        + "moria1,dizum,faravahar").split(",")));
+        + "moria1,dizum,Faravahar").split(",")));
     missingConsensuses.removeAll(this.downloadedConsensuses.keySet());
     if (!missingConsensuses.isEmpty()) {
       this.warnings.put(Warning.ConsensusDownloadTimeout,
@@ -378,7 +378,7 @@ public class Checker {
   private void checkMissingVotes() {
     SortedSet<String> knownAuthorities = new TreeSet<String>(
         Arrays.asList(("dannenberg,dizum,gabelmoo,turtles,maatuska,"
-        + "moria1,tor26,urras,faravahar").split(",")));
+        + "moria1,tor26,urras,Faravahar").split(",")));
     SortedSet<String> missingVotes =
         new TreeSet<String>(knownAuthorities);
     for (RelayNetworkStatusVote vote : this.downloadedVotes) {
@@ -417,7 +417,7 @@ public class Checker {
   private void checkMissingAuthorities() {
     SortedSet<String> missingAuthorities = new TreeSet<String>(
         Arrays.asList(("gabelmoo,tor26,turtles,maatuska,dannenberg,urras,"
-        + "moria1,dizum,faravahar,Tonga").split(",")));
+        + "moria1,dizum,Faravahar,Tonga").split(",")));
     for (NetworkStatusEntry entry :
         this.downloadedConsensus.getStatusEntries().values()) {
       if (entry.getFlags().contains("Authority")) {
diff --git a/src/org/torproject/doctor/Downloader.java b/src/org/torproject/doctor/Downloader.java
index b060279..eb3e1a3 100644
--- a/src/org/torproject/doctor/Downloader.java
+++ b/src/org/torproject/doctor/Downloader.java
@@ -26,7 +26,7 @@ public class Downloader {
     downloader.addDirectoryAuthority("urras", "208.83.223.34", 443);
     downloader.addDirectoryAuthority("moria1", "128.31.0.34", 9131);
     downloader.addDirectoryAuthority("dizum", "194.109.206.212", 80);
-    downloader.addDirectoryAuthority("faravahar", "154.35.32.5", 80);
+    downloader.addDirectoryAuthority("Faravahar", "154.35.32.5", 80);
 
     /* Instruct the downloader to include the current consensus and all
      * referenced votes in the downloads.  The consensus shall be
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0