[bridgedb/master] Move lib/bridgedb/ â bridgedb/; move lib/bridgedb/test/ â test/.
isis at torproject.org
isis at torproject.org
Sat Jul 25 19:26:23 UTC 2015
commit 50f6e223553ffd40bf92b067c6c88a223e25746e
Author: Isis Lovecruft <isis at torproject.org>
Date: Thu Jun 25 04:47:26 2015 +0000
Move lib/bridgedb/ â bridgedb/; move lib/bridgedb/test/ â test/.
---
.gitattributes | 2 +-
.pylintrc | 2 +-
Makefile | 10 +-
bridgedb/Bridges.py | 684 ++++++++
bridgedb/Bucket.py | 288 ++++
bridgedb/Main.py | 507 ++++++
bridgedb/Stability.py | 295 ++++
bridgedb/Storage.py | 466 +++++
bridgedb/__init__.py | 17 +
bridgedb/_langs.py | 26 +
bridgedb/_version.py | 197 +++
bridgedb/bridgerequest.py | 206 +++
bridgedb/bridges.py | 1769 +++++++++++++++++++
bridgedb/captcha.py | 392 +++++
bridgedb/configure.py | 157 ++
bridgedb/crypto.py | 450 +++++
bridgedb/distribute.py | 275 +++
bridgedb/email/__init__.py | 1 +
bridgedb/email/autoresponder.py | 704 ++++++++
bridgedb/email/distributor.py | 219 +++
bridgedb/email/dkim.py | 73 +
bridgedb/email/request.py | 171 ++
bridgedb/email/server.py | 496 ++++++
bridgedb/email/templates.py | 200 +++
bridgedb/filters.py | 238 +++
bridgedb/geo.py | 82 +
bridgedb/https/__init__.py | 1 +
bridgedb/https/distributor.py | 328 ++++
bridgedb/https/request.py | 143 ++
bridgedb/https/server.py | 905 ++++++++++
.../https/templates/assets/css/bootstrap.min.css | 7 +
bridgedb/https/templates/assets/css/custom.css | 158 ++
.../templates/assets/css/font-awesome-ie7.min.css | 384 +++++
.../templates/assets/css/font-awesome.min.css | 403 +++++
bridgedb/https/templates/assets/css/main.css | 24 +
.../templates/assets/font/fontawesome-webfont.eot | Bin 0 -> 37405 bytes
.../templates/assets/font/fontawesome-webfont.svg | 399 +++++
.../templates/assets/font/fontawesome-webfont.ttf | Bin 0 -> 79076 bytes
.../templates/assets/font/fontawesome-webfont.woff | Bin 0 -> 43572 bytes
.../https/templates/assets/font/lato-bold.woff | Bin 0 -> 46160 bytes
.../https/templates/assets/font/lato-italic.woff | Bin 0 -> 47168 bytes
.../https/templates/assets/font/lato-regular.woff | Bin 0 -> 46108 bytes
bridgedb/https/templates/assets/tor-roots-blue.svg | 95 +
bridgedb/https/templates/assets/tor.svg | 6 +
bridgedb/https/templates/base.html | 108 ++
bridgedb/https/templates/bridges.html | 203 +++
bridgedb/https/templates/captcha.html | 63 +
bridgedb/https/templates/howto.html | 39 +
bridgedb/https/templates/index.html | 43 +
bridgedb/https/templates/options.html | 164 ++
bridgedb/https/templates/robots.txt | 1079 ++++++++++++
bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po | 368 ++++
bridgedb/i18n/az/LC_MESSAGES/bridgedb.po | 357 ++++
bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po | 357 ++++
bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po | 386 +++++
bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po | 363 ++++
bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po | 102 ++
bridgedb/i18n/da/LC_MESSAGES/bridgedb.po | 384 +++++
bridgedb/i18n/de/LC_MESSAGES/bridgedb.po | 389 +++++
bridgedb/i18n/el/LC_MESSAGES/bridgedb.po | 364 ++++
bridgedb/i18n/en/LC_MESSAGES/bridgedb.po | 433 +++++
bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po | 382 ++++
bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po | 433 +++++
bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po | 359 ++++
bridgedb/i18n/es/LC_MESSAGES/bridgedb.po | 388 +++++
bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po | 102 ++
bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po | 101 ++
bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po | 387 +++++
bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po | 386 +++++
bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po | 393 +++++
bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po | 383 ++++
bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po | 101 ++
bridgedb/i18n/he/LC_MESSAGES/bridgedb.po | 105 ++
bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po | 384 +++++
bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po | 384 +++++
bridgedb/i18n/id/LC_MESSAGES/bridgedb.po | 101 ++
bridgedb/i18n/it/LC_MESSAGES/bridgedb.po | 391 +++++
bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po | 362 ++++
bridgedb/i18n/km/LC_MESSAGES/bridgedb.po | 360 ++++
bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po | 102 ++
bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po | 104 ++
bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po | 359 ++++
bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po | 101 ++
bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po | 102 ++
bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po | 384 +++++
bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po | 390 +++++
bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po | 388 +++++
bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po | 387 +++++
bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po | 386 +++++
bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po | 360 ++++
bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po | 389 +++++
bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po | 100 ++
bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po | 361 ++++
bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po | 357 ++++
bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po | 359 ++++
bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po | 380 ++++
bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po | 387 +++++
bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po | 357 ++++
bridgedb/i18n/templates/bridgedb.pot | 393 +++++
bridgedb/i18n/th/LC_MESSAGES/bridgedb.po | 101 ++
bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po | 389 +++++
bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po | 384 +++++
bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po | 388 +++++
bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po | 358 ++++
bridgedb/interfaces.py | 67 +
bridgedb/parse/__init__.py | 79 +
bridgedb/parse/addr.py | 589 +++++++
bridgedb/parse/descriptors.py | 298 ++++
bridgedb/parse/fingerprint.py | 64 +
bridgedb/parse/headers.py | 79 +
bridgedb/parse/nickname.py | 58 +
bridgedb/parse/options.py | 295 ++++
bridgedb/parse/versions.py | 129 ++
bridgedb/persistent.py | 264 +++
bridgedb/proxy.py | 473 +++++
bridgedb/qrcodes.py | 77 +
bridgedb/runner.py | 109 ++
bridgedb/safelog.py | 197 +++
bridgedb/schedule.py | 326 ++++
bridgedb/sitecustomize.py | 34 +
bridgedb/strings.py | 565 ++++++
bridgedb/translations.py | 122 ++
bridgedb/txrecaptcha.py | 299 ++++
bridgedb/util.py | 385 +++++
lib/bridgedb/Bridges.py | 684 --------
lib/bridgedb/Bucket.py | 288 ----
lib/bridgedb/Main.py | 507 ------
lib/bridgedb/Stability.py | 295 ----
lib/bridgedb/Storage.py | 466 -----
lib/bridgedb/__init__.py | 17 -
lib/bridgedb/_langs.py | 26 -
lib/bridgedb/_version.py | 197 ---
lib/bridgedb/bridgerequest.py | 206 ---
lib/bridgedb/bridges.py | 1769 -------------------
lib/bridgedb/captcha.py | 392 -----
lib/bridgedb/configure.py | 157 --
lib/bridgedb/crypto.py | 450 -----
lib/bridgedb/distribute.py | 275 ---
lib/bridgedb/email/__init__.py | 1 -
lib/bridgedb/email/autoresponder.py | 704 --------
lib/bridgedb/email/distributor.py | 219 ---
lib/bridgedb/email/dkim.py | 73 -
lib/bridgedb/email/request.py | 171 --
lib/bridgedb/email/server.py | 496 ------
lib/bridgedb/email/templates.py | 200 ---
lib/bridgedb/filters.py | 238 ---
lib/bridgedb/geo.py | 82 -
lib/bridgedb/https/__init__.py | 1 -
lib/bridgedb/https/distributor.py | 328 ----
lib/bridgedb/https/request.py | 143 --
lib/bridgedb/https/server.py | 905 ----------
.../https/templates/assets/css/bootstrap.min.css | 7 -
lib/bridgedb/https/templates/assets/css/custom.css | 158 --
.../templates/assets/css/font-awesome-ie7.min.css | 384 -----
.../templates/assets/css/font-awesome.min.css | 403 -----
lib/bridgedb/https/templates/assets/css/main.css | 24 -
.../templates/assets/font/fontawesome-webfont.eot | Bin 37405 -> 0 bytes
.../templates/assets/font/fontawesome-webfont.svg | 399 -----
.../templates/assets/font/fontawesome-webfont.ttf | Bin 79076 -> 0 bytes
.../templates/assets/font/fontawesome-webfont.woff | Bin 43572 -> 0 bytes
.../https/templates/assets/font/lato-bold.woff | Bin 46160 -> 0 bytes
.../https/templates/assets/font/lato-italic.woff | Bin 47168 -> 0 bytes
.../https/templates/assets/font/lato-regular.woff | Bin 46108 -> 0 bytes
.../https/templates/assets/tor-roots-blue.svg | 95 -
lib/bridgedb/https/templates/assets/tor.svg | 6 -
lib/bridgedb/https/templates/base.html | 108 --
lib/bridgedb/https/templates/bridges.html | 203 ---
lib/bridgedb/https/templates/captcha.html | 63 -
lib/bridgedb/https/templates/howto.html | 39 -
lib/bridgedb/https/templates/index.html | 43 -
lib/bridgedb/https/templates/options.html | 164 --
lib/bridgedb/https/templates/robots.txt | 1079 ------------
lib/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po | 368 ----
lib/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po | 357 ----
lib/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po | 357 ----
lib/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po | 386 -----
lib/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po | 363 ----
lib/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po | 102 --
lib/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po | 384 -----
lib/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po | 389 -----
lib/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po | 364 ----
lib/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po | 433 -----
lib/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po | 382 ----
lib/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po | 433 -----
lib/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po | 359 ----
lib/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po | 388 -----
lib/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po | 102 --
lib/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po | 101 --
lib/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po | 387 -----
lib/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po | 386 -----
lib/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po | 393 -----
lib/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po | 383 ----
lib/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po | 101 --
lib/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po | 105 --
lib/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po | 384 -----
lib/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po | 384 -----
lib/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po | 101 --
lib/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po | 391 -----
lib/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po | 362 ----
lib/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po | 360 ----
lib/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po | 102 --
lib/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po | 104 --
lib/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po | 359 ----
lib/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po | 101 --
lib/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po | 102 --
lib/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po | 384 -----
lib/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po | 390 -----
lib/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po | 388 -----
lib/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po | 387 -----
lib/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po | 386 -----
lib/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po | 360 ----
lib/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po | 389 -----
lib/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po | 100 --
lib/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po | 361 ----
lib/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po | 357 ----
lib/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po | 359 ----
lib/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po | 380 ----
lib/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po | 387 -----
lib/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po | 357 ----
lib/bridgedb/i18n/templates/bridgedb.pot | 391 -----
lib/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po | 101 --
lib/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po | 389 -----
lib/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po | 384 -----
lib/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po | 388 -----
lib/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po | 358 ----
lib/bridgedb/interfaces.py | 67 -
lib/bridgedb/parse/__init__.py | 79 -
lib/bridgedb/parse/addr.py | 589 -------
lib/bridgedb/parse/descriptors.py | 298 ----
lib/bridgedb/parse/fingerprint.py | 64 -
lib/bridgedb/parse/headers.py | 79 -
lib/bridgedb/parse/nickname.py | 58 -
lib/bridgedb/parse/options.py | 295 ----
lib/bridgedb/parse/versions.py | 129 --
lib/bridgedb/persistent.py | 264 ---
lib/bridgedb/proxy.py | 473 -----
lib/bridgedb/qrcodes.py | 77 -
lib/bridgedb/runner.py | 109 --
lib/bridgedb/safelog.py | 197 ---
lib/bridgedb/schedule.py | 326 ----
lib/bridgedb/sitecustomize.py | 34 -
lib/bridgedb/strings.py | 565 ------
lib/bridgedb/test/deprecated.py | 448 -----
lib/bridgedb/test/email_helpers.py | 179 --
lib/bridgedb/test/https_helpers.py | 130 --
lib/bridgedb/test/legacy_Tests.py | 332 ----
lib/bridgedb/test/test_Bucket.py | 63 -
lib/bridgedb/test/test_Main.py | 437 -----
lib/bridgedb/test/test_Storage.py | 56 -
lib/bridgedb/test/test_Tests.py | 248 ---
lib/bridgedb/test/test_bridgedb.py | 103 --
lib/bridgedb/test/test_bridgerequest.py | 58 -
lib/bridgedb/test/test_bridges.py | 1820 --------------------
lib/bridgedb/test/test_captcha.py | 337 ----
lib/bridgedb/test/test_configure.py | 67 -
lib/bridgedb/test/test_crypto.py | 392 -----
lib/bridgedb/test/test_distribute.py | 44 -
lib/bridgedb/test/test_email_autoresponder.py | 571 ------
lib/bridgedb/test/test_email_distributor.py | 258 ---
lib/bridgedb/test/test_email_dkim.py | 86 -
lib/bridgedb/test/test_email_request.py | 276 ---
lib/bridgedb/test/test_email_server.py | 543 ------
lib/bridgedb/test/test_email_templates.py | 119 --
lib/bridgedb/test/test_filters.py | 333 ----
lib/bridgedb/test/test_geo.py | 96 --
lib/bridgedb/test/test_https.py | 415 -----
lib/bridgedb/test/test_https_distributor.py | 405 -----
lib/bridgedb/test/test_https_request.py | 91 -
lib/bridgedb/test/test_https_server.py | 838 ---------
lib/bridgedb/test/test_interfaces.py | 55 -
lib/bridgedb/test/test_parse_addr.py | 749 --------
lib/bridgedb/test/test_parse_descriptors.py | 855 ---------
lib/bridgedb/test/test_parse_headers.py | 52 -
lib/bridgedb/test/test_parse_nickname.py | 63 -
lib/bridgedb/test/test_parse_options.py | 102 --
lib/bridgedb/test/test_parse_versions.py | 42 -
lib/bridgedb/test/test_persistent.py | 170 --
lib/bridgedb/test/test_persistentSaveAndLoad.py | 163 --
lib/bridgedb/test/test_proxy.py | 590 -------
lib/bridgedb/test/test_qrcodes.py | 73 -
lib/bridgedb/test/test_safelog.py | 335 ----
lib/bridgedb/test/test_schedule.py | 224 ---
lib/bridgedb/test/test_smtp.py | 206 ---
lib/bridgedb/test/test_translations.py | 78 -
lib/bridgedb/test/test_txrecaptcha.py | 270 ---
lib/bridgedb/test/test_util.py | 144 --
lib/bridgedb/test/util.py | 301 ----
lib/bridgedb/translations.py | 122 --
lib/bridgedb/txrecaptcha.py | 299 ----
lib/bridgedb/util.py | 385 -----
scripts/bridgedb | 2 +-
setup.cfg | 14 +-
setup.py | 11 +-
test/deprecated.py | 448 +++++
test/email_helpers.py | 179 ++
test/https_helpers.py | 130 ++
test/legacy_Tests.py | 332 ++++
test/test_Bucket.py | 63 +
test/test_Main.py | 437 +++++
test/test_Storage.py | 56 +
test/test_Tests.py | 248 +++
test/test_bridgedb.py | 103 ++
test/test_bridgerequest.py | 58 +
test/test_bridges.py | 1820 ++++++++++++++++++++
test/test_captcha.py | 337 ++++
test/test_configure.py | 67 +
test/test_crypto.py | 392 +++++
test/test_distribute.py | 44 +
test/test_email_autoresponder.py | 571 ++++++
test/test_email_distributor.py | 258 +++
test/test_email_dkim.py | 86 +
test/test_email_request.py | 276 +++
test/test_email_server.py | 543 ++++++
test/test_email_templates.py | 119 ++
test/test_filters.py | 333 ++++
test/test_geo.py | 96 ++
test/test_https.py | 415 +++++
test/test_https_distributor.py | 405 +++++
test/test_https_request.py | 91 +
test/test_https_server.py | 838 +++++++++
test/test_interfaces.py | 55 +
test/test_parse_addr.py | 749 ++++++++
test/test_parse_descriptors.py | 855 +++++++++
test/test_parse_headers.py | 52 +
test/test_parse_nickname.py | 63 +
test/test_parse_options.py | 102 ++
test/test_parse_versions.py | 42 +
test/test_persistent.py | 170 ++
test/test_persistentSaveAndLoad.py | 163 ++
test/test_proxy.py | 590 +++++++
test/test_qrcodes.py | 73 +
test/test_safelog.py | 335 ++++
test/test_schedule.py | 224 +++
test/test_smtp.py | 206 +++
test/test_translations.py | 78 +
test/test_txrecaptcha.py | 270 +++
test/test_util.py | 144 ++
test/util.py | 301 ++++
338 files changed, 46982 insertions(+), 46981 deletions(-)
diff --git a/.gitattributes b/.gitattributes
index 0e02de6..f8876b1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1 @@
-lib/bridgedb/_version.py export-subst
+bridgedb/_version.py export-subst
diff --git a/.pylintrc b/.pylintrc
index 0952d2e..8bf60a0 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -6,7 +6,7 @@
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook='import sys,os; venv = os.environ.get("VIRTUAL_ENV"); v = os.path.join(venv, "lib", "python" + str(sys.version_info.major) + "." + str(sys.version_info.micro), "site-packages") if venv is not None else None; sys.path.insert(0, v);'
-init-hook="import sys,os;a=os.getcwd();print a;sys.path.insert(0, os.path.join(a, 'lib'));"
+init-hook="import sys,os;a=os.getcwd();print a;sys.path.insert(0, a));"
# Profiled execution.
profile=no
diff --git a/Makefile b/Makefile
index 2c09bbe..c1dc07e 100644
--- a/Makefile
+++ b/Makefile
@@ -11,13 +11,13 @@ test:
python setup.py test
pep8:
- find lib/bridgedb/*.py | xargs pep8
+ find bridgedb/*.py | xargs pep8
pylint:
- pylint --rcfile=./.pylintrc ./lib/bridgedb/
+ pylint --rcfile=./.pylintrc ./bridgedb/
pyflakes:
- pyflakes lib/bridgedb/
+ pyflakes ./bridgedb/
install:
-python setup.py compile_catalog
@@ -55,11 +55,11 @@ clean-coverage-html:
clean: clean-docs clean-coverage-html
-rm -rf build
-rm -rf dist
- -rm -rf lib/bridgedb.egg-info
+ -rm -rf bridgedb.egg-info
-rm -rf _trial_temp
coverage-test:
- coverage run --rcfile=".coveragerc" $(TRIAL) ./lib/bridgedb/test/test_*.py
+ coverage run --rcfile=".coveragerc" $(TRIAL) ./test/test_*.py
coverage report --rcfile=".coveragerc"
coverage-html:
diff --git a/bridgedb/Bridges.py b/bridgedb/Bridges.py
new file mode 100644
index 0000000..7a237fe
--- /dev/null
+++ b/bridgedb/Bridges.py
@@ -0,0 +1,684 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Bridges -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""This module has low-level functionality for parsing bridges and arranging
+them into hashrings for distributors.
+"""
+
+import bisect
+import logging
+import re
+import hashlib
+import socket
+import time
+import ipaddr
+import random
+
+import bridgedb.Storage
+import bridgedb.Bucket
+
+from bridgedb.bridges import Bridge
+from bridgedb.crypto import getHMACFunc
+from bridgedb.parse import addr
+from bridgedb.parse.fingerprint import isValidFingerprint
+from bridgedb.safelog import logSafely
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+
+ID_LEN = 20 # XXX Only used in commented out line in Storage.py
+DIGEST_LEN = 20
+PORTSPEC_LEN = 16
+
+
+class BridgeRingParameters(object):
+ """Store validated settings on minimum number of Bridges with certain
+ attributes which should be included in any generated subring of a
+ hashring.
+
+ :ivar list needPorts: List of two-tuples of desired port numbers and their
+ respective minimums.
+ :ivar list needFlags: List of two-tuples of desired flags_ assigned to a
+ Bridge by the Bridge DirAuth.
+
+ .. _flags: https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt?id=6b557594ef#n1695
+ """
+
+ def __init__(self, needPorts=[], needFlags=[]):
+ """Control the creation of subrings by including a minimum number of
+ bridges which possess certain attributes.
+
+ :type needPorts: iterable
+ :param needPorts: An iterable of two-tuples. Each two tuple should
+ contain ``(port, minimum)``, where ``port`` is an integer
+ specifying a port number, and ``minimum`` is another integer
+ specifying the minimum number of Bridges running on that ``port``
+ to include in any new subring.
+ :type needFlags: iterable
+ :param needFlags: An iterable of two-tuples. Each two tuple should
+ contain ``(flag, minimum)``, where ``flag`` is a string specifying
+ an OR flag_, and ``minimum`` is an integer for the minimum number
+ of Bridges which have acquired that ``flag`` to include in any new
+ subring.
+ :raises: An :exc:`TypeError` if an invalid port number, a minimum less
+ than one, or an "unsupported" flag is given. "Stable" appears to
+ be the only currently "supported" flag.
+ """
+ for port, count in needPorts:
+ if not (1 <= port <= 65535):
+ raise TypeError("Port %s out of range." % port)
+ if count <= 0:
+ raise TypeError("Count %s out of range." % count)
+ for flag, count in needFlags:
+ flag = flag.lower()
+ if flag not in ["stable", "running",]:
+ raise TypeError("Unsupported flag %s" % flag)
+ if count <= 0:
+ raise TypeError("Count %s out of range." % count)
+
+ self.needPorts = needPorts[:]
+ self.needFlags = [(flag.lower(), count) for flag, count in needFlags[:]]
+
+
+class BridgeRing(object):
+ """Arranges bridges into a hashring based on an hmac function."""
+
+ def __init__(self, key, answerParameters=None):
+ """Create a new BridgeRing, using key as its hmac key.
+
+ :type key: bytes
+ :param key: The HMAC key, generated with
+ :func:`~bridgedb.crypto.getKey`.
+ :type answerParameters: :class:`BridgeRingParameters`
+ :param answerParameters: DOCDOC
+ :ivar dict bridges: A dictionary which maps HMAC keys to
+ :class:`~bridgedb.bridges.Bridge`s.
+ :ivar dict bridgesByID: A dictionary which maps raw hash digests of
+ bridge ID keys to :class:`~bridgedb.bridges.Bridge`s.
+ :type hmac: callable
+ :ivar hmac: An HMAC function, which uses the **key** parameter to
+ generate new HMACs for storing, inserting, and retrieving
+ :class:`~bridgedb.bridges.Bridge`s within mappings.
+ :ivar bool isSorted: ``True`` if ``sortedKeys`` is currently sorted.
+ :ivar list sortedKeys: A sorted list of all of the HMACs.
+ :ivar str name: A string which identifies this hashring, used mostly
+ for differentiating this hashring in log messages, but it is also
+ used for naming subrings. If this hashring is a subring, the
+ ``name`` will include whatever distinguishing parameters
+ differentiate that particular subring (i.e. ``'(port-443
+ subring)'`` or ``'(Stable subring)'``)
+ :type subrings: list
+ :ivar subrings: A list of other ``BridgeRing``s, each of which
+ contains bridges of a particular type. For example, a subring
+ might contain only ``Bridge``s which have been given the "Stable"
+ flag, or it might contain only IPv6 bridges. Each item in this
+ list should be a 4-tuple::
+
+ (type, value, count, ring)
+
+ where:
+
+ - ``type`` is a string which describes what kind of parameter is
+ used to determine if a ``Bridge`` belongs in that subring,
+ i.e. ``'port'`` or ``'flag'``.
+
+ - ``value`` is a specific value pertaining to the ``type``,
+ e.g. ``type='port'; value=443``.
+
+ - ``count`` is an integer for the current total number of
+ bridges in the subring.
+
+ - ``ring`` is a :class:`BridgeRing`; it is the subhashring which
+ contains ``count`` number of
+ :class:`~bridgedb.bridges.Bridge`s of a certain ``type``.
+ """
+ self.bridges = {}
+ self.bridgesByID = {}
+ self.hmac = getHMACFunc(key, hex=False)
+ self.isSorted = False
+ self.sortedKeys = []
+ if answerParameters is None:
+ answerParameters = BridgeRingParameters()
+ self.answerParameters = answerParameters
+
+ self.subrings = []
+ for port,count in self.answerParameters.needPorts:
+ #note that we really need to use the same key here, so that
+ # the mapping is in the same order for all subrings.
+ self.subrings.append( ('port',port,count,BridgeRing(key,None)) )
+ for flag,count in self.answerParameters.needFlags:
+ self.subrings.append( ('flag',flag,count,BridgeRing(key,None)) )
+
+ self.setName("Ring")
+
+ def setName(self, name):
+ """Tag a unique name to this hashring for identification.
+
+ :param string name: The name for this hashring.
+ """
+ self.name = name
+ for tp, val, _, subring in self.subrings:
+ if tp == 'port':
+ subring.setName("%s (port-%s subring)" % (name, val))
+ else:
+ subring.setName("%s (%s subring)" % (name, val))
+
+ def __len__(self):
+ """Get the number of unique bridges this hashring contains."""
+ return len(self.bridges)
+
+ def clear(self):
+ """Remove all bridges and mappings from this hashring and subrings."""
+ self.bridges = {}
+ self.bridgesByID = {}
+ self.isSorted = False
+ self.sortedKeys = []
+
+ for tp, val, count, subring in self.subrings:
+ subring.clear()
+
+ def insert(self, bridge):
+ """Add a **bridge** to this hashring.
+
+ The bridge's position in the hashring is dependent upon the HMAC of
+ the raw hash digest of the bridge's ID key. The function used to
+ generate the HMAC, :ivar:`BridgeRing.hmac`, is unique to each
+ individual hashring.
+
+ If the (presumably same) bridge is already at that determined position
+ in this hashring, replace the old one.
+
+ :type bridge: :class:`~bridgedb.Bridges.Bridge`
+ :param bridge: The bridge to insert into this hashring.
+ """
+ for tp, val, _, subring in self.subrings:
+ if tp == 'port':
+ if val == bridge.orPort:
+ subring.insert(bridge)
+ else:
+ assert tp == 'flag' and val == 'stable'
+ if val == 'stable' and bridge.flags.stable:
+ subring.insert(bridge)
+
+ pos = self.hmac(bridge.identity)
+ if not pos in self.bridges:
+ self.sortedKeys.append(pos)
+ self.isSorted = False
+ self.bridges[pos] = bridge
+ self.bridgesByID[bridge.identity] = bridge
+ logging.debug("Adding %s to %s" % (bridge.address, self.name))
+
+ def _sort(self):
+ """Helper: put the keys in sorted order."""
+ if not self.isSorted:
+ self.sortedKeys.sort()
+ self.isSorted = True
+
+ def _getBridgeKeysAt(self, pos, N=1):
+ """Bisect a list of bridges at a specified position, **pos**, and
+ retrieve bridges from that point onwards, wrapping around the hashring
+ if necessary.
+
+ If the number of bridges requested, **N**, is larger that the size of
+ this hashring, return the entire ring. Otherwise:
+
+ 1. Sort this bridges in this hashring, if it is currently unsorted.
+
+ 2. Bisect the sorted bridges. If the bridge at the desired position,
+ **pos**, already exists within this hashring, the the bisection
+ result is the bridge at position **pos**. Otherwise, the bisection
+ result is the first position after **pos** which has a bridge
+ assigned to it.
+
+ 3. Try to obtain **N** bridges, starting at (and including) the
+ bridge in the requested position, **pos**.
+
+ a. If there aren't **N** bridges after **pos**, wrap back
+ around to the beginning of the hashring and obtain bridges
+ until we have **N** bridges.
+
+ 4. Check that the number of bridges obtained is indeed **N**, then
+ return them.
+
+ :param bytes pos: The position to jump to. Any bridges returned will
+ start at this position in the hashring, if there is a bridge
+ assigned to that position. Otherwise, indexing will start at the
+ first position after this one which has a bridge assigned to it.
+ :param int N: The number of bridges to return.
+ :rtype: list
+ :returns: A list of :class:`~bridgedb.Bridges.Bridge`s.
+ """
+ assert len(pos) == DIGEST_LEN
+ if N >= len(self.sortedKeys):
+ return self.sortedKeys
+ if not self.isSorted:
+ self._sort()
+ idx = bisect.bisect_left(self.sortedKeys, pos)
+ r = self.sortedKeys[idx:idx+N]
+ if len(r) < N:
+ # wrap around as needed.
+ r.extend(self.sortedKeys[:N - len(r)])
+ assert len(r) == N
+ return r
+
+ def getBridges(self, pos, N=1):
+ """Return **N** bridges appearing in this hashring after a position.
+
+ :param bytes pos: The position to jump to. Any bridges returned will
+ start at this position in the hashring, if there is a bridge
+ assigned to that position. Otherwise, indexing will start at the
+ first position after this one which has a bridge assigned to it.
+ :param int N: The number of bridges to return.
+ :rtype: list
+ :returns: A list of :class:`~bridgedb.bridges.Bridge`s.
+ """
+ forced = []
+ for _, _, count, subring in self.subrings:
+ if len(subring) < count:
+ count = len(subring)
+ forced.extend(subring._getBridgeKeysAt(pos, count))
+
+ keys = [ ]
+ for k in forced + self._getBridgeKeysAt(pos, N):
+ if k not in keys:
+ keys.append(k)
+ else:
+ logging.debug(
+ "Got duplicate bridge %r in main hashring for position %r."
+ % (logSafely(k.encode('hex')), pos.encode('hex')))
+ keys = keys[:N]
+ keys.sort()
+
+ #Do not return bridges from the same /16
+ bridges = [ self.bridges[k] for k in keys ]
+
+ return bridges
+
+ def getBridgeByID(self, fp):
+ """Return the bridge whose identity digest is fp, or None if no such
+ bridge exists."""
+ for _,_,_,subring in self.subrings:
+ b = subring.getBridgeByID(fp)
+ if b is not None:
+ return b
+
+ return self.bridgesByID.get(fp)
+
+ def dumpAssignments(self, f, description=""):
+ logging.info("Dumping bridge assignments for %s..." % self.name)
+ for b in self.bridges.itervalues():
+ desc = [ description ]
+ for tp,val,_,subring in self.subrings:
+ if subring.getBridgeByID(b.identity):
+ desc.append("%s=%s"%(tp,val))
+ f.write("%s %s\n" % (b.fingerprint, " ".join(desc).strip()))
+
+
+class FixedBridgeSplitter(object):
+ """Splits bridges up based on an HMAC and assigns them to one of several
+ subhashrings with equal probability.
+ """
+ def __init__(self, key, rings):
+ self.hmac = getHMACFunc(key, hex=True)
+ self.rings = rings[:]
+
+ def insert(self, bridge):
+ # Grab the first 4 bytes
+ digest = self.hmac(bridge.identity)
+ pos = long( digest[:8], 16 )
+ which = pos % len(self.rings)
+ self.rings[which].insert(bridge)
+
+ def clear(self):
+ """Clear all bridges from every ring in ``rings``."""
+ for r in self.rings:
+ r.clear()
+
+ def __len__(self):
+ """Returns the total number of bridges in all ``rings``."""
+ total = 0
+ for ring in self.rings:
+ total += len(ring)
+ return total
+
+ def dumpAssignments(self, filename, description=""):
+ """Write all bridges assigned to this hashring to ``filename``.
+
+ :param string description: If given, include a description next to the
+ index number of the ring from :attr:`FilteredBridgeSplitter.rings`
+ the following bridges were assigned to. For example, if the
+ description is ``"IPv6 obfs2 bridges"`` the line would read:
+ ``"IPv6 obfs2 bridges ring=3"``.
+ """
+ for index, ring in zip(xrange(len(self.rings)), self.rings):
+ ring.dumpAssignments(filename, "%s ring=%s" % (description, index))
+
+
+class UnallocatedHolder(object):
+ """A pseudo-bridgeholder that ignores its bridges and leaves them
+ unassigned.
+ """
+ def __init__(self):
+ self.fingerprints = []
+
+ def insert(self, bridge):
+ logging.debug("Leaving %s unallocated", bridge.fingerprint)
+ if not bridge.fingerprint in self.fingerprints:
+ self.fingerprints.append(bridge.fingerprint)
+
+ def __len__(self):
+ return len(self.fingerprints)
+
+ def clear(self):
+ self.fingerprints = []
+
+ def dumpAssignments(self, f, description=""):
+ with bridgedb.Storage.getDB() as db:
+ allBridges = db.getAllBridges()
+ for bridge in allBridges:
+ if bridge.hex_key not in self.fingerprints:
+ continue
+ dist = bridge.distributor
+ desc = [ description ]
+ if dist.startswith(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX):
+ dist = dist.replace(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX, "")
+ desc.append("bucket=%s" % dist)
+ elif dist != "unallocated":
+ continue
+ f.write("%s %s\n" % (bridge.hex_key, " ".join(desc).strip()))
+
+
+class BridgeSplitter(object):
+ """Splits incoming bridges up based on an HMAC, and assigns them to
+ sub-bridgeholders with different probabilities. Bridge ââ BridgeSplitter
+ associations are recorded in a store.
+ """
+ def __init__(self, key):
+ self.hmac = getHMACFunc(key, hex=True)
+ self.ringsByName = {}
+ self.totalP = 0
+ self.pValues = []
+ self.rings = []
+ self.pseudoRings = []
+ self.statsHolders = []
+
+ def __len__(self):
+ n = 0
+ for r in self.ringsByName.values():
+ n += len(r)
+ return n
+
+ def addRing(self, ring, ringname, p=1):
+ """Add a new subring.
+
+ :param ring: The subring to add.
+ :param str ringname: This is used to record which bridges have been
+ assigned where in the store.
+ :param int p: The relative proportion of bridges to assign to this
+ bridgeholder.
+ """
+ self.ringsByName[ringname] = ring
+ self.pValues.append(self.totalP)
+ self.rings.append(ringname)
+ self.totalP += p
+
+ def addPseudoRing(self, ringname):
+ """Add a pseudo ring to the list of pseudo rings.
+ """
+ self.pseudoRings.append(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX + ringname)
+
+ def addTracker(self, t):
+ """Adds a statistics tracker that gets told about every bridge we see.
+ """
+ self.statsHolders.append(t)
+
+ def clear(self):
+ for r in self.ringsByName.values():
+ r.clear()
+
+ def insert(self, bridge):
+ assert self.rings
+
+ for s in self.statsHolders:
+ s.insert(bridge)
+
+ # The bridge must be running to insert it:
+ if not bridge.flags.running:
+ return
+
+ # Determine which ring to put this bridge in if we haven't seen it
+ # before.
+ pos = self.hmac(bridge.identity)
+ n = int(pos[:8], 16) % self.totalP
+ pos = bisect.bisect_right(self.pValues, n) - 1
+ assert 0 <= pos < len(self.rings)
+ ringname = self.rings[pos]
+ logging.info("%s placing bridge %s into hashring %s (via n=%s, pos=%s)."
+ % (self.__class__.__name__, bridge, ringname, n, pos))
+
+ validRings = self.rings + self.pseudoRings
+
+ with bridgedb.Storage.getDB() as db:
+ ringname = db.insertBridgeAndGetRing(bridge, ringname, time.time(),
+ validRings)
+ db.commit()
+
+ # Pseudo distributors are always held in the "unallocated" ring
+ if ringname in self.pseudoRings:
+ ringname = "unallocated"
+
+ ring = self.ringsByName.get(ringname)
+ ring.insert(bridge)
+
+ def dumpAssignments(self, f, description=""):
+ for name,ring in self.ringsByName.iteritems():
+ ring.dumpAssignments(f, "%s %s" % (description, name))
+
+
+class FilteredBridgeSplitter(object):
+ """Places bridges into subrings based upon sets of filters.
+
+ The set of subrings and conditions used to assign :class:`Bridge`s should
+ be passed to :meth:`~FilteredBridgeSplitter.addRing`.
+ """
+
+ def __init__(self, key, max_cached_rings=3):
+ """Create a hashring which filters bridges into sub hashrings.
+
+ :type key: DOCDOC
+ :param key: An HMAC key.
+ :param int max_cached_rings: XXX max_cached_rings appears to not be
+ used anywhere.
+
+ :ivar filterRings: A dictionary of subrings which has the form
+ ``{ringname: (filterFn, subring)}``, where:
+ - ``ringname`` is a unique string identifying the subring.
+ - ``filterFn`` is a callable which filters Bridges in some
+ manner, i.e. by whether they are IPv4 or IPv6, etc.
+ - ``subring`` is any of the horribly-implemented,
+ I-guess-it-passes-for-some-sort-of-hashring classes in this
+ module.
+ :ivar hmac: DOCDOC
+ :ivar bridges: DOCDOC
+ :type distributorName: str
+ :ivar distributorName: The name of this splitter's distributor. See
+ :meth:`~bridgedb.https.distributor.HTTPSDistributor.setDistributorName`.
+ """
+ self.key = key
+ self.filterRings = {}
+ self.hmac = getHMACFunc(key, hex=True)
+ self.bridges = []
+ self.distributorName = ''
+
+ #XXX: unused
+ self.max_cached_rings = max_cached_rings
+
+ def __len__(self):
+ return len(self.bridges)
+
+ def clear(self):
+ self.bridges = []
+ self.filterRings = {}
+
+ def insert(self, bridge):
+ """Insert a bridge into all appropriate sub-hashrings.
+
+ For all sub-hashrings, the ``bridge`` will only be added iff it passes
+ the filter functions for that sub-hashring.
+
+ :type bridge: :class:`~bridgedb.Bridges.Bridge`
+ :param bridge: The bridge to add.
+ """
+ # The bridge must be running to insert it:
+ if not bridge.flags.running:
+ logging.warn(("Skipping hashring insertion for non-running "
+ "bridge: %s") % bridge)
+ return
+
+ index = 0
+ logging.debug("Inserting %s into hashring..." % bridge)
+ for old_bridge in self.bridges[:]:
+ if bridge.fingerprint == old_bridge.fingerprint:
+ self.bridges[index] = bridge
+ break
+ index += 1
+ else:
+ self.bridges.append(bridge)
+ for ringname, (filterFn, subring) in self.filterRings.items():
+ if filterFn(bridge):
+ subring.insert(bridge)
+ logging.debug("Inserted bridge %s into %s subhashring." %
+ (bridge, ringname))
+
+ def extractFilterNames(self, ringname):
+ """Get the names of the filters applied to a particular sub hashring.
+
+ :param str ringname: A unique name identifying a sub hashring.
+ :rtype: list
+ :returns: A sorted list of strings, all the function names of the
+ filters applied to the sub hashring named **ringname**.
+ """
+ filterNames = []
+
+ for filterName in [x.func_name for x in list(ringname)]:
+ # Using `assignBridgesToSubring.func_name` gives us a messy
+ # string which includes all parameters and memory addresses. Get
+ # rid of this by partitioning at the first `(`:
+ realFilterName = filterName.partition('(')[0]
+ filterNames.append(realFilterName)
+
+ filterNames.sort()
+ return filterNames
+
+ def addRing(self, subring, ringname, filterFn, populate_from=None):
+ """Add a subring to this hashring.
+
+ :param subring: The subring to add.
+ :param str ringname: A unique name for identifying the new subring.
+ :param filterFn: A function whose input is a :class:`Bridge`, and
+ returns True/False based on some filtration criteria.
+ :type populate_from: iterable or None
+ :param populate_from: A group of :class:`Bridge`s. If given, the newly
+ added subring will be populated with these bridges.
+ :rtype: bool
+ :returns: False if there was a problem adding the subring, True
+ otherwise.
+ """
+ # XXX I think subring and ringname are switched in this function, or
+ # at least that whatever is passed into this function as as the
+ # `ringname` parameter from somewhere else is odd; for example, with
+ # the original code, which was `log.debug("Inserted %d bridges into
+ # hashring '%s'!" % (inserted, ringname))`, this log message appears:
+ #
+ # Jan 04 23:18:37 [INFO] Inserted 12 bridges into hashring
+ # frozenset([<function byIPv4 at 0x2d67cf8>, <function
+ # assignBridgesToSubring(<function hmac_fn at 0x3778398>, 4, 0) at
+ # 0x37de578>])!
+ #
+ # I suppose since it contains memory addresses, it *is* technically
+ # likely to be a unique string, but it is messy.
+
+ if ringname in self.filterRings.keys():
+ logging.fatal("%s hashring already has a subring named '%s'!"
+ % (self.distributorName, ringname))
+ return False
+
+ filterNames = self.extractFilterNames(ringname)
+ subringName = [self.distributorName]
+ subringNumber = None
+ for filterName in filterNames:
+ if filterName.startswith('assignBridgesToSubring'):
+ subringNumber = filterName.lstrip('assignBridgesToSubring')
+ else:
+ subringName.append(filterName.lstrip('by'))
+ if subring.name and 'Proxy' in subring.name:
+ subringName.append('Proxy')
+ elif subringNumber:
+ subringName.append(subringNumber)
+ subringName = '-'.join([x for x in subringName])
+ subring.setName(subringName)
+
+ logging.info("Adding %s subring %s to the %s Distributor's hashring..." %
+ (subring.name, subringNumber, self.distributorName))
+ logging.info(" Subring filters: %s" % filterNames)
+
+ #TODO: drop LRU ring if len(self.filterRings) > self.max_cached_rings
+ self.filterRings[ringname] = (filterFn, subring)
+
+ if populate_from:
+ inserted = 0
+ for bridge in populate_from:
+ if isinstance(bridge, Bridge) and filterFn(bridge):
+ subring.insert(bridge)
+ inserted += 1
+ logging.info("Bridges inserted into %s subring %s: %d"
+ % (subring.name, subringNumber, inserted))
+
+ return True
+
+ def dumpAssignments(self, f, description=""):
+ # one ring per filter set
+ # bridges may be present in multiple filter sets
+ # only one line should be dumped per bridge
+
+ for b in self.bridges:
+ # gather all the filter descriptions
+ desc = []
+ for n,(g,r) in self.filterRings.items():
+ if g(b):
+ # ghetto. get subring flags, ports
+ for tp,val,_,subring in r.subrings:
+ if subring.getBridgeByID(b.identity):
+ desc.append("%s=%s"%(tp,val))
+ try:
+ desc.extend(g.description.split())
+ except TypeError:
+ desc.append(g.description)
+
+ # add transports
+ logging.debug("%s supports %d transports" % (b, len(b.transports)))
+ for transport in b.transports:
+ desc.append("transport=%s"%(transport.methodname))
+
+ # dedupe and group
+ desc = set(desc)
+ grouped = dict()
+ for kw in desc:
+ l,r = kw.split('=')
+ try:
+ grouped[l] = "%s,%s"%(grouped[l],r)
+ except KeyError:
+ grouped[l] = kw
+
+ # add to assignments
+ desc = "%s %s" % (description.strip(),
+ " ".join([v for k,v in grouped.items()]).strip())
+ f.write("%s %s\n" % (b.fingerprint, desc))
diff --git a/bridgedb/Bucket.py b/bridgedb/Bucket.py
new file mode 100644
index 0000000..1382188
--- /dev/null
+++ b/bridgedb/Bucket.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+
+"""
+This module is responsible for everything concerning file bucket bridge
+distribution. File bucket bridge distribution means that unallocated bridges
+are allocated to a certain pseudo-distributor and later written to a file.
+
+For example, the following is a dict of pseudo-distributors (also called
+'bucket identifiers') with numbers of bridges assigned to them:
+
+ FILE_BUCKETS = { "name1": 10, "name2": 15, "foobar": 3 }
+
+This configuration for buckets would result in 3 files being created for bridge
+distribution: name1-2010-07-17.brdgs, name2-2010-07-17.brdgs and
+foobar-2010-07-17.brdgs. The first file would contain 10 bridges from BridgeDB's
+'unallocated' pool. The second file would contain 15 bridges from the same pool
+and the third one similarly 3 bridges. These files can then be handed out to
+trusted parties via mail or fed to other distribution mechanisms such as
+twitter.
+
+Note that in BridgeDB slang, the _distributor_ would still be 'unallocated',
+even though in the database, there would now by 'name1', 'name2' or 'foobar'
+instead of 'unallocated'. This is why they are called pseudo-distributors.
+"""
+
+import logging
+import time
+import bridgedb.Storage
+import bridgedb.Bridges
+import binascii
+import sqlite3
+from gettext import gettext as _
+toHex = binascii.b2a_hex
+
+
+# What should pseudo distributors be prefixed with in the database so we can
+# distinguish them from real distributors?
+PSEUDO_DISTRI_PREFIX = "pseudo_"
+
+# Set to rediculously high number
+BUCKET_MAX_BRIDGES = 1000000
+
+
+class BucketData(object):
+ """Configures a bridge bucket with the number of bridges which should be
+ allocated, the name of the bucket, and other similar data.
+
+ :param str name: The name of this bucket (from the config file). This will
+ be prefixed by the :data:`PSEUDO_DISTRIBUTOR_PREFIX`.
+ :type needed: str or int
+ :param needed: The number of bridges needed for this bucket (also from the
+ config file).
+ :param int allocated: Number of bridges already allocated for this bucket.
+ """
+ def __init__(self, name, needed):
+ self.name = name
+ if needed == "*":
+ needed = BUCKET_MAX_BRIDGES
+ self.needed = int(needed)
+ self.allocated = 0
+
+class BucketManager(object):
+ """BucketManager reads a number of file bucket identifiers from the config.
+
+ They're expected to be in the following format::
+
+ FILE_BUCKETS = { "name1": 10, "name2": 15, "foobar": 3 }
+
+ This syntax means that certain buckets ("name1", "name2" and so on) are
+ given a number of bridges (10, 15 and so on). Names can be anything. The
+ name will later be the prefix of the file that is written with the
+ assigned number of bridges in it. Instead of a number, a wildcard item
+ ("*") is allowed, too. This means that the corresponsing bucket file will
+ get the maximum number of possible bridges (as many as are left in the
+ unallocated bucket).
+
+ The files will be written in ip:port format, one bridge per line.
+
+ The way this works internally is as follows:
+
+ First of all, the assignBridgesToBuckets() routine runs through the
+ database of bridges and looks up the 'distributor' field of each
+ bridge. Unallocated bridges are sent to a pool for later assignement.
+ Already allocated bridges for file bucket distribution are sorted and
+ checked. They're checked for whether their bucket identifier still exists
+ in the current config and also whether the number of assigned bridges is
+ still valid. If either the bucket identifier is not existing anymore or
+ too many bridges are currently assigned to it, bridges will go to the
+ unassigned pool.
+
+ In the second step, after bridges are sorted and the unassigned pool is
+ ready, the assignBridgesToBuckets() routine assigns one bridge from the
+ unassigned pool to a known bucket identifier at a time until it either
+ runs out of bridges in the unallocated pool or the number of needed
+ bridges for that bucket is reached.
+
+ When all bridges are assigned in this way, they can then be dumped into
+ files by calling the dumpBridges() routine.
+
+ :type cfg: :class:`bridgedb.persistent.Conf`
+ :ivar cfg: The central configuration instance.
+ :ivar list bucketList: A list of BucketData instances, holding all
+ configured (and thus requested) buckets with their respective numbers.
+ :ivar list unallocatedList: Holds all bridges from the 'unallocated' pool.
+ :ivar bool unallocated_available: Is at least one unallocated bridge
+ available?
+ :ivar str distributor_prefix: The 'distributor' field in the database will
+ hold the name of our pseudo-distributor, prefixed by this string. By
+ default, this uses :data:`PSEUDO_DISTRIBUTOR_PREFIX`.
+ :ivar db: The bridge database instance.
+ """
+
+ def __init__(self, cfg):
+ """Create a ``BucketManager``.
+
+ :type cfg: :class:`bridgedb.persistent.Conf`
+ :param cfg: The central configuration instance.
+ """
+ self.cfg = cfg
+ self.bucketList = []
+ self.unallocatedList = []
+ self.unallocated_available = False
+ self.distributor_prefix = PSEUDO_DISTRI_PREFIX
+
+ def addToUnallocatedList(self, hex_key):
+ """Add a bridge by **hex_key** into the unallocated pool."""
+ with bridgedb.Storage.getDB() as db:
+ try:
+ db.updateDistributorForHexKey("unallocated", hex_key)
+ except:
+ db.rollback()
+ raise
+ else:
+ db.commit()
+ self.unallocatedList.append(hex_key)
+ self.unallocated_available = True
+
+ def getBucketByIdent(self, bucketIdent):
+ """If we know this bucket identifier, then return the corresponding
+ :class:`BucketData` object.
+ """
+ for d in self.bucketList:
+ if d.name == bucketIdent:
+ return d
+ return None
+
+ def assignUnallocatedBridge(self, bucket):
+ """Assign an unallocated bridge to a certain **bucket**."""
+ hex_key = self.unallocatedList.pop()
+ # Mark pseudo-allocators in the database as such
+ allocator_name = bucket.name
+ #print "KEY: %d NAME: %s" % (hex_key, allocator_name)
+ logging.debug("Moving %s to %s" % (hex_key, allocator_name))
+ with bridgedb.Storage.getDB() as db:
+ try:
+ db.updateDistributorForHexKey(allocator_name, hex_key)
+ except:
+ db.rollback()
+ logging.warn("Failed to move %s to new distributor (%s)"
+ % (hex_key, allocator_name))
+
+ # Ok, this seems useless, but for consistancy's sake, we'll
+ # re-assign the bridge from this missed db update attempt to the
+ # unallocated list. Remember? We pop()'d it before.
+ self.addToUnallocatedList(hex_key)
+ raise
+ else:
+ db.commit()
+ bucket.allocated += 1
+ if len(self.unallocatedList) < 1:
+ self.unallocated_available = False
+ return True
+
+ def assignBridgesToBuckets(self):
+ """Read file bucket identifiers from the configuration, sort them, and
+ write necessary changes to the database.
+ """
+ logging.debug("Assigning bridges to buckets for pseudo-distributors")
+ # Build distributor list
+ for k, v in self.cfg.FILE_BUCKETS.items():
+ prefixed_key = self.distributor_prefix + k
+ d = BucketData(prefixed_key, v)
+ self.bucketList.append(d)
+
+ # Loop through all bridges and sort out distributors
+ with bridgedb.Storage.getDB() as db:
+ allBridges = db.getAllBridges()
+ for bridge in allBridges:
+ if bridge.distributor == "unallocated":
+ self.addToUnallocatedList(bridge.hex_key)
+ continue
+
+ # Filter non-pseudo distributors (like 'https' and 'email') early,
+ # too
+ if not bridge.distributor.startswith(self.distributor_prefix):
+ continue
+
+ # Return the bucket in case we know it already
+ d = self.getBucketByIdent(bridge.distributor)
+ if d is not None:
+ # Does this distributor need another bridge? If not, re-inject
+ # it into the 'unallocated' pool for for later assignment
+ if d.allocated < d.needed:
+ d.allocated += 1
+ else:
+ # Bucket has enough members already, free this one
+ self.addToUnallocatedList(bridge.hex_key)
+ # We don't know it. Maybe an old entry. Free it.
+ else:
+ self.addToUnallocatedList(bridge.hex_key)
+
+ # Loop through bucketList while we have and need unallocated
+ # bridges, assign one bridge at a time
+ while self.unallocated_available and len(self.bucketList) > 0:
+ logging.debug("We have %d unallocated bridges and %d buckets to " \
+ "fill. Let's do it."
+ % (len(self.unallocatedList), len(self.bucketList)))
+ for d in self.bucketList:
+ if d.allocated < d.needed:
+ try:
+ if not self.assignUnallocatedBridge(d):
+ break
+ except sqlite3.DatabaseError as e:
+ dist = d.name.replace(self.distributor_prefix, "")
+ logging.warn("Couldn't assign unallocated bridge to " \
+ "%s: %s" % (dist, e))
+ else:
+ # When we have enough bridges, remove bucket identifier
+ # from list
+ self.bucketList.remove(d)
+
+ def dumpBridgesToFile(self, filename, bridges):
+ """Dump a list of given **bridges** into **filename**."""
+ logging.debug("Dumping bridge assignments to file: %r" % filename)
+ # get the bridge histories and sort by Time On Same Address
+ bridgeHistories = []
+ with bridgedb.Storage.getDB() as db:
+ for b in bridges:
+ if self.cfg.COLLECT_TIMESTAMPS:
+ bh = db.getBridgeHistory(b.hex_key)
+ if bh: bridgeHistories.append(bh)
+ bridgeHistories.sort(lambda x,y: cmp(x.weightedFractionalUptime,
+ y.weightedFractionalUptime))
+
+ try:
+ f = open(filename, 'w')
+ if self.cfg.COLLECT_TIMESTAMPS:
+ for bh in bridgeHistories:
+ days = bh.tosa / long(60*60*24)
+ line = "%s:%s\t(%d days at this address)" % \
+ (bh.ip, bh.port, days)
+ if str(bh.fingerprint) in blocklist.keys():
+ line = line + "\t(Might be blocked): (%s)" % \
+ ",".join(blocklist[bh.fingerprint])
+ f.write(line + '\n')
+ else:
+ for bridge in bridges:
+ line = "%s:%d %s" \
+ % (bridge.address, bridge.or_port, bridge.hex_key)
+ f.write(line + '\n')
+ f.close()
+ except IOError:
+ print "I/O error: %s" % filename
+
+ def dumpBridges(self):
+ """Dump all known file distributors to files, sorted by distributor."""
+ logging.info("Dumping all distributors to file.")
+ with bridgedb.Storage.getDB() as db:
+ allBridges = db.getAllBridges()
+ bridgeDict = {}
+ # Sort returned bridges by distributor
+ for bridge in allBridges:
+ dist = str(bridge.distributor)
+ if dist in bridgeDict.keys():
+ bridgeDict[dist].append(bridge)
+ else:
+ bridgeDict[dist] = [bridge]
+
+ # Now dump to file(s)
+ for k in bridgeDict.keys():
+ dist = k
+ if (dist.startswith(self.distributor_prefix)):
+ # Subtract the pseudo distributor prefix
+ dist = dist.replace(self.distributor_prefix, "")
+ # Be safe. Replace all '/' in distributor names
+ dist = dist.replace("/", "_")
+ filename = dist + "-" + time.strftime("%Y-%m-%d") + ".brdgs"
+ self.dumpBridgesToFile(filename, bridgeDict[k])
diff --git a/bridgedb/Main.py b/bridgedb/Main.py
new file mode 100644
index 0000000..b281d21
--- /dev/null
+++ b/bridgedb/Main.py
@@ -0,0 +1,507 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Main -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see the AUTHORS file for attributions
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2007-2015, Nick Mathewson
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""This module sets up BridgeDB and starts the servers running."""
+
+import logging
+import os
+import signal
+import sys
+import time
+
+from twisted.internet import reactor
+from twisted.internet import task
+
+from bridgedb import crypto
+from bridgedb import persistent
+from bridgedb import proxy
+from bridgedb import util
+from bridgedb.bridges import MalformedBridgeInfo
+from bridgedb.bridges import MissingServerDescriptorDigest
+from bridgedb.bridges import ServerDescriptorDigestMismatch
+from bridgedb.bridges import ServerDescriptorWithoutNetworkstatus
+from bridgedb.bridges import Bridge
+from bridgedb.configure import loadConfig
+from bridgedb.email.distributor import EmailDistributor
+from bridgedb.https.distributor import HTTPSDistributor
+from bridgedb.parse import descriptors
+
+import bridgedb.Storage
+
+from bridgedb import Bridges
+from bridgedb.Stability import updateBridgeHistory
+
+
+def load(state, hashring, clear=False):
+ """Read and parse all descriptors, and load into a bridge hashring.
+
+ Read all the appropriate bridge files from the saved
+ :class:`~bridgedb.persistent.State`, parse and validate them, and then
+ store them into our ``state.hashring`` instance. The ``state`` will be
+ saved again at the end of this function.
+
+ :type hashring: :class:`~bridgedb.Bridges.BridgeSplitter`
+ :param hashring: A class which provides a mechanism for HMACing
+ Bridges in order to assign them to hashrings.
+ :param boolean clear: If True, clear all previous bridges from the
+ hashring before parsing for new ones.
+ """
+ if not state:
+ logging.fatal("bridgedb.Main.load() could not retrieve state!")
+ sys.exit(2)
+
+ if clear:
+ logging.info("Clearing old bridges...")
+ hashring.clear()
+
+ logging.info("Loading bridges...")
+
+ ignoreNetworkstatus = state.IGNORE_NETWORKSTATUS
+ if ignoreNetworkstatus:
+ logging.info("Ignoring BridgeAuthority networkstatus documents.")
+
+ bridges = {}
+ timestamps = {}
+
+ logging.info("Opening networkstatus file: %s" % state.STATUS_FILE)
+ networkstatuses = descriptors.parseNetworkStatusFile(state.STATUS_FILE)
+ logging.debug("Closing networkstatus file: %s" % state.STATUS_FILE)
+
+ logging.info("Processing networkstatus descriptors...")
+ for router in networkstatuses:
+ bridge = Bridge()
+ bridge.updateFromNetworkStatus(router, ignoreNetworkstatus)
+ try:
+ bridge.assertOK()
+ except MalformedBridgeInfo as error:
+ logging.warn(str(error))
+ else:
+ bridges[bridge.fingerprint] = bridge
+
+ for filename in state.BRIDGE_FILES:
+ logging.info("Opening bridge-server-descriptor file: '%s'" % filename)
+ serverdescriptors = descriptors.parseServerDescriptorsFile(filename)
+ logging.debug("Closing bridge-server-descriptor file: '%s'" % filename)
+
+ for router in serverdescriptors:
+ try:
+ bridge = bridges[router.fingerprint]
+ except KeyError:
+ logging.warn(
+ ("Received server descriptor for bridge '%s' which wasn't "
+ "in the networkstatus!") % router.fingerprint)
+ if ignoreNetworkstatus:
+ bridge = Bridge()
+ else:
+ continue
+
+ try:
+ bridge.updateFromServerDescriptor(router, ignoreNetworkstatus)
+ except (ServerDescriptorWithoutNetworkstatus,
+ MissingServerDescriptorDigest,
+ ServerDescriptorDigestMismatch) as error:
+ logging.warn(str(error))
+ # Reject any routers whose server descriptors didn't pass
+ # :meth:`~bridges.Bridge._checkServerDescriptor`, i.e. those
+ # bridges who don't have corresponding networkstatus
+ # documents, or whose server descriptor digests don't check
+ # out:
+ bridges.pop(router.fingerprint)
+ continue
+
+ if state.COLLECT_TIMESTAMPS:
+ # Update timestamps from server descriptors, not from network
+ # status descriptors (because networkstatus documents and
+ # descriptors aren't authenticated in any way):
+ if bridge.fingerprint in timestamps.keys():
+ timestamps[bridge.fingerprint].append(router.published)
+ else:
+ timestamps[bridge.fingerprint] = [router.published]
+
+ extrainfos = descriptors.parseExtraInfoFiles(*state.EXTRA_INFO_FILES)
+ for fingerprint, router in extrainfos.items():
+ try:
+ bridges[fingerprint].updateFromExtraInfoDescriptor(router)
+ except MalformedBridgeInfo as error:
+ logging.warn(str(error))
+ except KeyError as error:
+ logging.warn(("Received extrainfo descriptor for bridge '%s', "
+ "but could not find bridge with that fingerprint.")
+ % router.fingerprint)
+
+ inserted = 0
+ logging.info("Inserting %d bridges into hashring..." % len(bridges))
+ for fingerprint, bridge in bridges.items():
+ # Skip insertion of bridges which are geolocated to be in one of the
+ # NO_DISTRIBUTION_COUNTRIES, a.k.a. the countries we don't distribute
+ # bridges from:
+ if bridge.country in state.NO_DISTRIBUTION_COUNTRIES:
+ logging.warn("Not distributing Bridge %s %s:%s in country %s!" %
+ (bridge, bridge.address, bridge.orPort, bridge.country))
+ else:
+ # If the bridge is not running, then it is skipped during the
+ # insertion process.
+ hashring.insert(bridge)
+ inserted += 1
+ logging.info("Done inserting %d bridges into hashring." % inserted)
+
+ if state.COLLECT_TIMESTAMPS:
+ reactor.callInThread(updateBridgeHistory, bridges, timestamps)
+
+ state.save()
+
+def _reloadFn(*args):
+ """Placeholder callback function for :func:`_handleSIGHUP`."""
+ return True
+
+def _handleSIGHUP(*args):
+ """Called when we receive a SIGHUP; invokes _reloadFn."""
+ reactor.callInThread(_reloadFn)
+
+def _handleSIGUSR1(*args):
+ """Handler for SIGUSR1. Calls :func:`~bridgedb.runner.doDumpBridges`."""
+ logging.debug("Caught SIGUSR1 signal")
+
+ from bridgedb import runner
+
+ logging.info("Loading saved state...")
+ state = persistent.load()
+ cfg = loadConfig(state.CONFIG_FILE, state.config)
+
+ logging.info("Dumping bridge assignments to files...")
+ reactor.callInThread(runner.doDumpBridges, cfg)
+
+def replaceBridgeRings(current, replacement):
+ """Replace the current thing with the new one"""
+ current.hashring = replacement.hashring
+
+def createBridgeRings(cfg, proxyList, key):
+ """Create the bridge distributors defined by the config file
+
+ :type cfg: :class:`Conf`
+ :param cfg: The current configuration, including any in-memory settings
+ (i.e. settings whose values were not obtained from the config file,
+ but were set via a function somewhere)
+ :type proxyList: :class:`~bridgedb.proxy.ProxySet`
+ :param proxyList: The container for the IP addresses of any currently
+ known open proxies.
+ :param bytes key: Hashring master key
+ :rtype: tuple
+ :returns: A BridgeSplitter hashring, an
+ :class:`~bridgedb.https.distributor.HTTPSDistributor` or None, and an
+ :class:`~bridgedb.email.distributor.EmailDistributor` or None.
+ """
+ # Create a BridgeSplitter to assign the bridges to the different
+ # distributors.
+ hashring = Bridges.BridgeSplitter(crypto.getHMAC(key, "Hashring-Key"))
+ logging.debug("Created hashring: %r" % hashring)
+
+ # Create ring parameters.
+ ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
+ needFlags=cfg.FORCE_FLAGS)
+
+ emailDistributor = ipDistributor = None
+ # As appropriate, create an IP-based distributor.
+ if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
+ logging.debug("Setting up HTTPS Distributor...")
+ ipDistributor = HTTPSDistributor(
+ cfg.N_IP_CLUSTERS,
+ crypto.getHMAC(key, "HTTPS-IP-Dist-Key"),
+ proxyList,
+ answerParameters=ringParams)
+ hashring.addRing(ipDistributor.hashring, "https", cfg.HTTPS_SHARE)
+
+ # As appropriate, create an email-based distributor.
+ if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
+ logging.debug("Setting up Email Distributor...")
+ emailDistributor = EmailDistributor(
+ crypto.getHMAC(key, "Email-Dist-Key"),
+ cfg.EMAIL_DOMAIN_MAP.copy(),
+ cfg.EMAIL_DOMAIN_RULES.copy(),
+ answerParameters=ringParams,
+ whitelist=cfg.EMAIL_WHITELIST.copy())
+ hashring.addRing(emailDistributor.hashring, "email", cfg.EMAIL_SHARE)
+
+ # As appropriate, tell the hashring to leave some bridges unallocated.
+ if cfg.RESERVED_SHARE:
+ hashring.addRing(Bridges.UnallocatedHolder(),
+ "unallocated",
+ cfg.RESERVED_SHARE)
+
+ # Add pseudo distributors to hashring
+ for pseudoRing in cfg.FILE_BUCKETS.keys():
+ hashring.addPseudoRing(pseudoRing)
+
+ return hashring, emailDistributor, ipDistributor
+
+def run(options, reactor=reactor):
+ """This is BridgeDB's main entry point and main runtime loop.
+
+ Given the parsed commandline options, this function handles locating the
+ configuration file, loading and parsing it, and then either (re)parsing
+ plus (re)starting the servers, or dumping bridge assignments to files.
+
+ :type options: :class:`bridgedb.parse.options.MainOptions`
+ :param options: A pre-parsed options class containing any arguments and
+ options given in the commandline we were called with.
+ :type state: :class:`bridgedb.persistent.State`
+ :ivar state: A persistent state object which holds config changes.
+ :param reactor: An implementer of
+ :api:`twisted.internet.interfaces.IReactorCore`. This parameter is
+ mainly for testing; the default
+ :api:`twisted.internet.epollreactor.EPollReactor` is fine for normal
+ application runs.
+ """
+ # Change to the directory where we're supposed to run. This must be done
+ # before parsing the config file, otherwise there will need to be two
+ # copies of the config file, one in the directory BridgeDB is started in,
+ # and another in the directory it changes into.
+ os.chdir(options['rundir'])
+ if options['verbosity'] <= 10: # Corresponds to logging.DEBUG
+ print("Changed to runtime directory %r" % os.getcwd())
+
+ config = loadConfig(options['config'])
+ config.RUN_IN_DIR = options['rundir']
+
+ # Set up logging as early as possible. We cannot import from the bridgedb
+ # package any of our modules which import :mod:`logging` and start using
+ # it, at least, not until :func:`safelog.configureLogging` is
+ # called. Otherwise a default handler that logs to the console will be
+ # created by the imported module, and all further calls to
+ # :func:`logging.basicConfig` will be ignored.
+ util.configureLogging(config)
+
+ if options['dump-bridges'] or (options.subCommand is not None):
+ runSubcommand(options, config)
+
+ # Write the pidfile only after any options.subCommands are run (because
+ # these exit when they are finished). Otherwise, if there is a subcommand,
+ # the real PIDFILE would get overwritten with the PID of the temporary
+ # bridgedb process running the subcommand.
+ if config.PIDFILE:
+ logging.debug("Writing server PID to file: '%s'" % config.PIDFILE)
+ with open(config.PIDFILE, 'w') as pidfile:
+ pidfile.write("%s\n" % os.getpid())
+ pidfile.flush()
+
+ from bridgedb import persistent
+
+ state = persistent.State(config=config)
+
+ from bridgedb.email.server import addServer as addSMTPServer
+ from bridgedb.https.server import addWebServer
+
+ # Load the master key, or create a new one.
+ key = crypto.getKey(config.MASTER_KEY_FILE)
+ proxies = proxy.ProxySet()
+ emailDistributor = None
+ ipDistributor = None
+
+ # Save our state
+ state.proxies = proxies
+ state.key = key
+ state.save()
+
+ def reload(inThread=True):
+ """Reload settings, proxy lists, and bridges.
+
+ State should be saved before calling this method, and will be saved
+ again at the end of it.
+
+ The internal variables, ``cfg``, ``hashring``, ``proxyList``,
+ ``ipDistributor``, and ``emailDistributor`` are all taken from a
+ :class:`~bridgedb.persistent.State` instance, which has been saved to
+ a statefile with :meth:`bridgedb.persistent.State.save`.
+
+ :type cfg: :class:`Conf`
+ :ivar cfg: The current configuration, including any in-memory
+ settings (i.e. settings whose values were not obtained from the
+ config file, but were set via a function somewhere)
+ :type hashring: A :class:`~bridgedb.Bridges.BridgeSplitter`
+ :ivar hashring: A class which takes an HMAC key and splits bridges
+ into their hashring assignments.
+ :type proxyList: :class:`~bridgedb.proxy.ProxySet`
+ :ivar proxyList: The container for the IP addresses of any currently
+ known open proxies.
+ :ivar ipDistributor: A
+ :class:`~bridgedb.https.distributor.HTTPSDistributor`.
+ :ivar emailDistributor: A
+ :class:`~bridgedb.email.distributor.EmailDistributor`.
+ :ivar dict tasks: A dictionary of ``{name: task}``, where name is a
+ string to associate with the ``task``, and ``task`` is some
+ scheduled event, repetitive or otherwise, for the :class:`reactor
+ <twisted.internet.epollreactor.EPollReactor>`. See the classes
+ within the :api:`twisted.internet.tasks` module.
+ """
+ logging.debug("Caught SIGHUP")
+ logging.info("Reloading...")
+
+ logging.info("Loading saved state...")
+ state = persistent.load()
+ cfg = loadConfig(state.CONFIG_FILE, state.config)
+ logging.info("Updating any changed settings...")
+ state.useChangedSettings(cfg)
+
+ level = getattr(state, 'LOGLEVEL', 'WARNING')
+ logging.info("Updating log level to: '%s'" % level)
+ level = getattr(logging, level)
+ logging.getLogger().setLevel(level)
+
+ logging.info("Reloading the list of open proxies...")
+ for proxyfile in cfg.PROXY_LIST_FILES:
+ logging.info("Loading proxies from: %s" % proxyfile)
+ proxy.loadProxiesFromFile(proxyfile, state.proxies, removeStale=True)
+
+ logging.info("Reparsing bridge descriptors...")
+ (hashring,
+ emailDistributorTmp,
+ ipDistributorTmp) = createBridgeRings(cfg, state.proxies, key)
+ logging.info("Bridges loaded: %d" % len(hashring))
+
+ # Initialize our DB.
+ bridgedb.Storage.initializeDBLock()
+ bridgedb.Storage.setDBFilename(cfg.DB_FILE + ".sqlite")
+ load(state, hashring, clear=False)
+
+ if emailDistributorTmp is not None:
+ emailDistributorTmp.prepopulateRings() # create default rings
+ logging.info("Bridges allotted for %s distribution: %d"
+ % (emailDistributorTmp.name,
+ len(emailDistributorTmp.hashring)))
+ else:
+ logging.warn("No email distributor created!")
+
+ if ipDistributorTmp is not None:
+ ipDistributorTmp.prepopulateRings() # create default rings
+
+ logging.info("Bridges allotted for %s distribution: %d"
+ % (ipDistributorTmp.name,
+ len(ipDistributorTmp.hashring)))
+ logging.info("\tNum bridges:\tFilter set:")
+
+ nSubrings = 0
+ ipSubrings = ipDistributorTmp.hashring.filterRings
+ for (ringname, (filterFn, subring)) in ipSubrings.items():
+ nSubrings += 1
+ filterSet = ' '.join(
+ ipDistributorTmp.hashring.extractFilterNames(ringname))
+ logging.info("\t%2d bridges\t%s" % (len(subring), filterSet))
+
+ logging.info("Total subrings for %s: %d"
+ % (ipDistributorTmp.name, nSubrings))
+ else:
+ logging.warn("No HTTP(S) distributor created!")
+
+ # Dump bridge pool assignments to disk.
+ try:
+ logging.debug("Dumping pool assignments to file: '%s'"
+ % state.ASSIGNMENTS_FILE)
+ fh = open(state.ASSIGNMENTS_FILE, 'a')
+ fh.write("bridge-pool-assignment %s\n" %
+ time.strftime("%Y-%m-%d %H:%M:%S"))
+ hashring.dumpAssignments(fh)
+ fh.flush()
+ fh.close()
+ except IOError:
+ logging.info("I/O error while writing assignments to: '%s'"
+ % state.ASSIGNMENTS_FILE)
+ state.save()
+
+ if inThread:
+ # XXX shutdown the distributors if they were previously running
+ # and should now be disabled
+ if ipDistributorTmp:
+ reactor.callFromThread(replaceBridgeRings,
+ ipDistributor, ipDistributorTmp)
+ if emailDistributorTmp:
+ reactor.callFromThread(replaceBridgeRings,
+ emailDistributor, emailDistributorTmp)
+ else:
+ # We're still starting up. Return these distributors so
+ # they are configured in the outer-namespace
+ return emailDistributorTmp, ipDistributorTmp
+
+ global _reloadFn
+ _reloadFn = reload
+ signal.signal(signal.SIGHUP, _handleSIGHUP)
+ signal.signal(signal.SIGUSR1, _handleSIGUSR1)
+
+ if reactor:
+ # And actually load it to start parsing. Get back our distributors.
+ emailDistributor, ipDistributor = reload(False)
+
+ # Configure all servers:
+ if config.HTTPS_DIST and config.HTTPS_SHARE:
+ addWebServer(config, ipDistributor)
+ if config.EMAIL_DIST and config.EMAIL_SHARE:
+ addSMTPServer(config, emailDistributor)
+
+ tasks = {}
+
+ # Setup all our repeating tasks:
+ if config.TASKS['GET_TOR_EXIT_LIST']:
+ tasks['GET_TOR_EXIT_LIST'] = task.LoopingCall(
+ proxy.downloadTorExits,
+ state.proxies,
+ config.SERVER_PUBLIC_EXTERNAL_IP)
+
+ # Schedule all configured repeating tasks:
+ for name, seconds in config.TASKS.items():
+ if seconds:
+ try:
+ tasks[name].start(abs(seconds))
+ except KeyError:
+ logging.info("Task %s is disabled and will not run." % name)
+ else:
+ logging.info("Scheduled task %s to run every %s seconds."
+ % (name, seconds))
+
+ # Actually run the servers.
+ try:
+ if reactor and not reactor.running:
+ logging.info("Starting reactors.")
+ reactor.run()
+ except KeyboardInterrupt:
+ logging.fatal("Received keyboard interrupt. Shutting down...")
+ finally:
+ if config.PIDFILE:
+ os.unlink(config.PIDFILE)
+ logging.info("Exiting...")
+ sys.exit()
+
+def runSubcommand(options, config):
+ """Run a subcommand from the 'Commands' section of the bridgedb help menu.
+
+ :type options: :class:`bridgedb.opt.MainOptions`
+ :param options: A pre-parsed options class containing any arguments and
+ options given in the commandline we were called with.
+ :type config: :class:`bridgedb.Main.Conf`
+ :param config: The current configuration.
+ :raises: :exc:`SystemExit` when all subCommands and subOptions have
+ finished running.
+ """
+ # Make sure that the runner module is only imported after logging is set
+ # up, otherwise we run into the same logging configuration problem as
+ # mentioned above with the email.server and https.server.
+ from bridgedb import runner
+
+ statuscode = 0
+
+ if options.subCommand is not None:
+ logging.debug("Running BridgeDB command: '%s'" % options.subCommand)
+
+ if 'descriptors' in options.subOptions:
+ statuscode = runner.generateDescriptors(
+ options.subOptions['descriptors'], config.RUN_IN_DIR)
+
+ logging.info("Subcommand '%s' finished with status %s."
+ % (options.subCommand, statuscode))
+ sys.exit(statuscode)
diff --git a/bridgedb/Stability.py b/bridgedb/Stability.py
new file mode 100644
index 0000000..fbc8e88
--- /dev/null
+++ b/bridgedb/Stability.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Stability -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see the AUTHORS file for attributions
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2012-2015, Aaron Gibson
+# (c) 2007-2015, Nick Mathewson
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""This module provides functionality for tracking bridge stability metrics.
+
+Bridge stability metrics are calculated using the model introduced in
+`"An Analysis of Tor Bridge Stability"`_ and
+`implemented in the Tor Metrics library`_.
+
+.. An Analysis of Tor Bridge Stability:
+ https://metrics.torproject.org/papers/bridge-stability-2011-10-31.pdf
+ Karsten Loesing, An Analysis of Tor Bridge Stability. Technical Report.
+ The Tor Project, October 2011.
+
+.. implemented in the Tor Metrics library:
+ https://gitweb.torproject.org/metrics-tasks/task-4255/SimulateBridgeStability.java
+"""
+
+import logging
+import bridgedb.Storage
+
+from bridgedb.schedule import toUnixSeconds
+
+
+# tunables
+weighting_factor = float(19)/float(20)
+discountIntervalMillis = long(60*60*12*1000)
+
+
+class BridgeHistory(object):
+ """ Record Class that tracks a single Bridge
+ The fields stored are:
+
+ fingerprint, ip, port, weightedUptime, weightedTime, weightedRunLength,
+ totalRunWeights, lastSeenWithDifferentAddressAndPort,
+ lastSeenWithThisAddressAndPort, lastDiscountedHistoryValues.
+
+ fingerprint The Bridge Fingerprint (unicode)
+ ip The Bridge IP (unicode)
+ port The Bridge orport (integer)
+
+ weightedUptime Weighted uptime in seconds (long int)
+ weightedTime Weighted time in seconds (long int)
+ weightedRunLength Weighted run length of previous addresses or ports in
+ seconds. (long int)
+ totalRunWeights Total run weights of previously used addresses or
+ ports. (float)
+
+ lastSeenWithDifferentAddressAndPort
+ Timestamp in milliseconds when this
+ bridge was last seen with a different address or port. (long int)
+
+ lastSeenWithThisAddressAndPort
+ Timestamp in milliseconds when this bridge was last seen
+ with this address and port. (long int)
+
+ lastDiscountedHistoryValues:
+ Timestamp in milliseconds when this bridge was last discounted. (long int)
+
+ lastUpdatedWeightedTime:
+ Timestamp in milliseconds when the weighted time was updated. (long int)
+ """
+ def __init__(self, fingerprint, ip, port,
+ weightedUptime, weightedTime, weightedRunLength, totalRunWeights,
+ lastSeenWithDifferentAddressAndPort, lastSeenWithThisAddressAndPort,
+ lastDiscountedHistoryValues, lastUpdatedWeightedTime):
+ self.fingerprint = fingerprint
+ self.ip = ip
+ self.port = port
+ self.weightedUptime = long(weightedUptime)
+ self.weightedTime = long(weightedTime)
+ self.weightedRunLength = long(weightedRunLength)
+ self.totalRunWeights = float(totalRunWeights)
+ self.lastSeenWithDifferentAddressAndPort = \
+ long(lastSeenWithDifferentAddressAndPort)
+ self.lastSeenWithThisAddressAndPort = long(lastSeenWithThisAddressAndPort)
+ self.lastDiscountedHistoryValues = long(lastDiscountedHistoryValues)
+ self.lastUpdatedWeightedTime = long(lastUpdatedWeightedTime)
+
+ def discountWeightedFractionalUptimeAndWeightedTime(self, discountUntilMillis):
+ """ discount weighted times """
+ if self.lastDiscountedHistoryValues == 0:
+ self.lastDiscountedHistoryValues = discountUntilMillis
+ rounds = self.numDiscountRounds(discountUntilMillis)
+ if rounds > 0:
+ discount = lambda x: (weighting_factor**rounds)*x
+ self.weightedUptime = discount(self.weightedUptime)
+ self.weightedTime = discount(self.weightedTime)
+ self.weightedRunLength = discount(self.weightedRunLength)
+ self.totalRunWeights = discount(self.totalRunWeights)
+
+ self.lastDiscountedHistoryValues += discountIntervalMillis * rounds
+ return rounds
+
+ def numDiscountRounds(self, discountUntilMillis):
+ """ return the number of rounds of discounting needed to bring this
+ history element current """
+ result = discountUntilMillis - self.lastDiscountedHistoryValues
+ result = int(result/discountIntervalMillis)
+ return max(result,0)
+
+ @property
+ def weightedFractionalUptime(self):
+ """Weighted Fractional Uptime"""
+ if self.weightedTime <0.0001: return long(0)
+ return long(10000) * self.weightedUptime / self.weightedTime
+
+ @property
+ def tosa(self):
+ """the Time On Same Address (TOSA)"""
+ return ( self.lastSeenWithThisAddressAndPort - \
+ self.lastSeenWithDifferentAddressAndPort ) / 1000
+
+ @property
+ def familiar(self):
+ """
+ A bridge is 'familiar' if 1/8 of all active bridges have appeared
+ more recently than it, or if it has been around for a Weighted Time of 8 days.
+ """
+ # if this bridge has been around longer than 8 days
+ if self.weightedTime >= long(8 * 24 * 60 * 60):
+ return True
+
+ # return True if self.weightedTime is greater than the weightedTime
+ # of the > bottom 1/8 all bridges, sorted by weightedTime
+ with bridgedb.Storage.getDB() as db:
+ allWeightedTimes = [ bh.weightedTime for bh in db.getAllBridgeHistory()]
+ numBridges = len(allWeightedTimes)
+ logging.debug("Got %d weightedTimes", numBridges)
+ allWeightedTimes.sort()
+ if self.weightedTime >= allWeightedTimes[numBridges/8]:
+ return True
+ return False
+
+ @property
+ def wmtbac(self):
+ """Weighted Mean Time Between Address Change"""
+ totalRunLength = self.weightedRunLength + \
+ ((self.lastSeenWithThisAddressAndPort -
+ self.lastSeenWithDifferentAddressAndPort) / long(1000))
+
+ totalWeights = self.totalRunWeights + 1.0
+ if totalWeights < 0.0001: return long(0)
+ assert(isinstance(long,totalRunLength))
+ assert(isinstance(long,totalWeights))
+ return totalRunlength / totalWeights
+
+def addOrUpdateBridgeHistory(bridge, timestamp):
+ with bridgedb.Storage.getDB() as db:
+ bhe = db.getBridgeHistory(bridge.fingerprint)
+ if not bhe:
+ # This is the first status, assume 60 minutes.
+ secondsSinceLastStatusPublication = long(60*60)
+ lastSeenWithDifferentAddressAndPort = timestamp * long(1000)
+ lastSeenWithThisAddressAndPort = timestamp * long(1000)
+
+ bhe = BridgeHistory(
+ bridge.fingerprint, bridge.address, bridge.orPort,
+ 0,#weightedUptime
+ 0,#weightedTime
+ 0,#weightedRunLength
+ 0,# totalRunWeights
+ lastSeenWithDifferentAddressAndPort, # first timestamnp
+ lastSeenWithThisAddressAndPort,
+ 0,#lastDiscountedHistoryValues,
+ 0,#lastUpdatedWeightedTime
+ )
+ # first time we have seen this descriptor
+ db.updateIntoBridgeHistory(bhe)
+ # Calculate the seconds since the last parsed status. If this is
+ # the first status or we haven't seen a status for more than 60
+ # minutes, assume 60 minutes.
+ statusPublicationMillis = long(timestamp * 1000)
+ if (statusPublicationMillis - bhe.lastSeenWithThisAddressAndPort) > 60*60*1000:
+ secondsSinceLastStatusPublication = long(60*60)
+ logging.debug("Capping secondsSinceLastStatusPublication to 1 hour")
+ # otherwise, roll with it
+ else:
+ secondsSinceLastStatusPublication = \
+ (statusPublicationMillis - bhe.lastSeenWithThisAddressAndPort)/1000
+ if secondsSinceLastStatusPublication <= 0 and bhe.weightedTime > 0:
+ # old descriptor, bail
+ logging.warn("Received old descriptor for bridge %s with timestamp %d",
+ bhe.fingerprint, statusPublicationMillis/1000)
+ return bhe
+
+ # iterate over all known bridges and apply weighting factor
+ discountAndPruneBridgeHistories(statusPublicationMillis)
+
+ # Update the weighted times of bridges
+ updateWeightedTime(statusPublicationMillis)
+
+ # For Running Bridges only:
+ # compare the stored history against the descriptor and see if the
+ # bridge has changed its address or port
+ bhe = db.getBridgeHistory(bridge.fingerprint)
+
+ if not bridge.running:
+ logging.info("%s is not running" % bridge.fingerprint)
+ return bhe
+
+ # Parse the descriptor and see if the address or port changed
+ # If so, store the weighted run time
+ if bridge.orport != bhe.port or bridge.ip != bhe.ip:
+ bhe.totalRunWeights += 1.0;
+ bhe.weightedRunLength += bhe.tosa
+ bhe.lastSeenWithDifferentAddressAndPort =\
+ bhe.lastSeenWithThisAddressAndPort
+
+ # Regardless of whether the bridge is new, kept or changed
+ # its address and port, raise its WFU times and note its
+ # current address and port, and that we saw it using them.
+ bhe.weightedUptime += secondsSinceLastStatusPublication
+ bhe.lastSeenWithThisAddressAndPort = statusPublicationMillis
+ bhe.ip = str(bridge.ip)
+ bhe.port = bridge.orport
+ return db.updateIntoBridgeHistory(bhe)
+
+def discountAndPruneBridgeHistories(discountUntilMillis):
+ with bridgedb.Storage.getDB() as db:
+ bhToRemove = []
+ bhToUpdate = []
+
+ for bh in db.getAllBridgeHistory():
+ # discount previous values by factor of 0.95 every 12 hours
+ bh.discountWeightedFractionalUptimeAndWeightedTime(discountUntilMillis)
+ # give the thing at least 24 hours before pruning it
+ if bh.weightedFractionalUptime < 1 and bh.weightedTime > 60*60*24:
+ logging.debug("Removing bridge from history: %s" % bh.fingerprint)
+ bhToRemove.append(bh.fingerprint)
+ else:
+ bhToUpdate.append(bh)
+
+ for k in bhToUpdate: db.updateIntoBridgeHistory(k)
+ for k in bhToRemove: db.delBridgeHistory(k)
+
+def updateWeightedTime(statusPublicationMillis):
+ bhToUpdate = []
+ with bridgedb.Storage.getDB() as db:
+ for bh in db.getBridgesLastUpdatedBefore(statusPublicationMillis):
+ interval = (statusPublicationMillis - bh.lastUpdatedWeightedTime)/1000
+ if interval > 0:
+ bh.weightedTime += min(3600,interval) # cap to 1hr
+ bh.lastUpdatedWeightedTime = statusPublicationMillis
+ #db.updateIntoBridgeHistory(bh)
+ bhToUpdate.append(bh)
+
+ for bh in bhToUpdate:
+ db.updateIntoBridgeHistory(bh)
+
+def updateBridgeHistory(bridges, timestamps):
+ """Process all the timestamps and update the bridge stability statistics in
+ the database.
+
+ .. warning: This function is extremely expensive, and will keep getting
+ more and more expensive, on a linearithmic scale, every time it is
+ called. Blame the :mod:`bridgedb.Stability` module.
+
+ :param dict bridges: All bridges from the descriptors, parsed into
+ :class:`bridgedb.bridges.Bridge`s.
+ :param dict timestamps: A dictionary whose keys are bridge fingerprints,
+ and whose values are lists of integers, each integer being a timestamp
+ (in seconds since Unix Epoch) for when a descriptor for that bridge
+ was published.
+ :rtype: dict
+ :returns: The original **timestamps**, but which each list of integers
+ (re)sorted.
+ """
+ logging.debug("Beginning bridge stability calculations")
+ sortedTimestamps = {}
+
+ for fingerprint, stamps in timestamps.items()[:]:
+ stamps.sort()
+ bridge = bridges[fingerprint]
+ for timestamp in stamps:
+ logging.debug(
+ ("Adding/updating timestamps in BridgeHistory for %s in "
+ "database: %s") % (fingerprint, timestamp))
+ timestamp = toUnixSeconds(timestamp.timetuple())
+ addOrUpdateBridgeHistory(bridge, timestamp)
+ # Replace the timestamps so the next sort is (hopefully) less
+ # expensive:
+ sortedTimestamps[fingerprint] = stamps
+
+ logging.debug("Stability calculations complete")
+ return sortedTimestamps
diff --git a/bridgedb/Storage.py b/bridgedb/Storage.py
new file mode 100644
index 0000000..ea9d26b
--- /dev/null
+++ b/bridgedb/Storage.py
@@ -0,0 +1,466 @@
+# BridgeDB by Nick Mathewson.
+# Copyright (c) 2007-2009, The Tor Project, Inc.
+# See LICENSE for licensing information
+
+import calendar
+import logging
+import binascii
+import sqlite3
+import time
+import hashlib
+from contextlib import GeneratorContextManager
+from functools import wraps
+from ipaddr import IPAddress
+import sys
+
+from bridgedb.Stability import BridgeHistory
+import threading
+
+toHex = binascii.b2a_hex
+fromHex = binascii.a2b_hex
+HEX_ID_LEN = 40
+
+def _escapeValue(v):
+ return "'%s'" % v.replace("'", "''")
+
+def timeToStr(t):
+ return time.strftime("%Y-%m-%d %H:%M", time.gmtime(t))
+def strToTime(t):
+ return calendar.timegm(time.strptime(t, "%Y-%m-%d %H:%M"))
+
+# The old DB system was just a key->value mapping DB, with special key
+# prefixes to indicate which database they fell into.
+#
+# sp|<ID> -- given to bridgesplitter; maps bridgeID to ring name.
+# em|<emailaddr> -- given to emailbaseddistributor; maps email address
+# to concatenated ID.
+# fs|<ID> -- Given to BridgeTracker, maps to time when a router was
+# first seen (YYYY-MM-DD HH:MM)
+# ls|<ID> -- given to bridgetracker, maps to time when a router was
+# last seen (YYYY-MM-DD HH:MM)
+#
+# We no longer want to use em| at all, since we're not doing that kind
+# of persistence any more.
+
+# Here is the SQL schema.
+SCHEMA2_SCRIPT = """
+ CREATE TABLE Config (
+ key PRIMARY KEY NOT NULL,
+ value
+ );
+
+ CREATE TABLE Bridges (
+ id INTEGER PRIMARY KEY NOT NULL,
+ hex_key,
+ address,
+ or_port,
+ distributor,
+ first_seen,
+ last_seen
+ );
+
+ CREATE UNIQUE INDEX BridgesKeyIndex ON Bridges ( hex_key );
+
+ CREATE TABLE EmailedBridges (
+ email PRIMARY KEY NOT NULL,
+ when_mailed
+ );
+
+ CREATE INDEX EmailedBridgesWhenMailed on EmailedBridges ( email );
+
+ CREATE TABLE BlockedBridges (
+ id INTEGER PRIMARY KEY NOT NULL,
+ hex_key,
+ blocking_country
+ );
+
+ CREATE INDEX BlockedBridgesBlockingCountry on BlockedBridges(hex_key);
+
+ CREATE TABLE WarnedEmails (
+ email PRIMARY KEY NOT NULL,
+ when_warned
+ );
+
+ CREATE INDEX WarnedEmailsWasWarned on WarnedEmails ( email );
+
+ INSERT INTO Config VALUES ( 'schema-version', 2 );
+"""
+
+SCHEMA_2TO3_SCRIPT = """
+ CREATE TABLE BridgeHistory (
+ fingerprint PRIMARY KEY NOT NULL,
+ address,
+ port INT,
+ weightedUptime LONG,
+ weightedTime LONG,
+ weightedRunLength LONG,
+ totalRunWeights DOUBLE,
+ lastSeenWithDifferentAddressAndPort LONG,
+ lastSeenWithThisAddressAndPort LONG,
+ lastDiscountedHistoryValues LONG,
+ lastUpdatedWeightedTime LONG
+ );
+
+ CREATE INDEX BridgeHistoryIndex on BridgeHistory ( fingerprint );
+
+ INSERT OR REPLACE INTO Config VALUES ( 'schema-version', 3 );
+ """
+SCHEMA3_SCRIPT = SCHEMA2_SCRIPT + SCHEMA_2TO3_SCRIPT
+
+
+class BridgeData(object):
+ """Value class carrying bridge information:
+ hex_key - The unique hex key of the given bridge
+ address - Bridge IP address
+ or_port - Bridge TCP port
+ distributor - The distributor (or pseudo-distributor) through which
+ this bridge is being announced
+ first_seen - When did we first see this bridge online?
+ last_seen - When was the last time we saw this bridge online?
+ """
+ def __init__(self, hex_key, address, or_port, distributor="unallocated",
+ first_seen="", last_seen=""):
+ self.hex_key = hex_key
+ self.address = address
+ self.or_port = or_port
+ self.distributor = distributor
+ self.first_seen = first_seen
+ self.last_seen = last_seen
+
+
+class Database(object):
+ def __init__(self, sqlite_fname):
+ self._conn = openDatabase(sqlite_fname)
+ self._cur = self._conn.cursor()
+ self.sqlite_fname = sqlite_fname
+
+ def commit(self):
+ self._conn.commit()
+
+ def rollback(self):
+ self._conn.rollback()
+
+ def close(self):
+ #print "Closing DB"
+ self._cur.close()
+ self._conn.close()
+
+ def insertBridgeAndGetRing(self, bridge, setRing, seenAt, validRings,
+ defaultPool="unallocated"):
+ '''Updates info about bridge, setting ring to setRing if none was set.
+ Also sets distributor to `defaultPool' if the bridge was found in
+ the database, but its distributor isn't valid anymore.
+
+ Returns the name of the distributor the bridge is assigned to.
+ '''
+ cur = self._cur
+
+ t = timeToStr(seenAt)
+ h = bridge.fingerprint
+ assert len(h) == HEX_ID_LEN
+
+ cur.execute("SELECT id, distributor "
+ "FROM Bridges WHERE hex_key = ?", (h,))
+ v = cur.fetchone()
+ if v is not None:
+ i, ring = v
+ # Check if this is currently a valid ring name. If not, move back
+ # into default pool.
+ if ring not in validRings:
+ ring = defaultPool
+ # Update last_seen, address, port and (possibly) distributor.
+ cur.execute("UPDATE Bridges SET address = ?, or_port = ?, "
+ "distributor = ?, last_seen = ? WHERE id = ?",
+ (str(bridge.address), bridge.orPort, ring,
+ timeToStr(seenAt), i))
+ return ring
+ else:
+ # Insert it.
+ cur.execute("INSERT INTO Bridges (hex_key, address, or_port, "
+ "distributor, first_seen, last_seen) "
+ "VALUES (?, ?, ?, ?, ?, ?)",
+ (h, str(bridge.address), bridge.orPort, setRing, t, t))
+ return setRing
+
+ def cleanEmailedBridges(self, expireBefore):
+ cur = self._cur
+ t = timeToStr(expireBefore)
+ cur.execute("DELETE FROM EmailedBridges WHERE when_mailed < ?", (t,))
+
+ def getEmailTime(self, addr):
+ addr = hashlib.sha1(addr).hexdigest()
+ cur = self._cur
+ cur.execute("SELECT when_mailed FROM EmailedBridges WHERE email = ?", (addr,))
+ v = cur.fetchone()
+ if v is None:
+ return None
+ return strToTime(v[0])
+
+ def setEmailTime(self, addr, whenMailed):
+ addr = hashlib.sha1(addr).hexdigest()
+ cur = self._cur
+ t = timeToStr(whenMailed)
+ cur.execute("INSERT OR REPLACE INTO EmailedBridges "
+ "(email,when_mailed) VALUES (?,?)", (addr, t))
+
+ def getAllBridges(self):
+ """Return a list of BridgeData value classes of all bridges in the
+ database
+ """
+ retBridges = []
+ cur = self._cur
+ cur.execute("SELECT hex_key, address, or_port, distributor, "
+ "first_seen, last_seen FROM Bridges")
+ for b in cur.fetchall():
+ bridge = BridgeData(b[0], b[1], b[2], b[3], b[4], b[5])
+ retBridges.append(bridge)
+
+ return retBridges
+
+ def getBridgesForDistributor(self, distributor):
+ """Return a list of BridgeData value classes of all bridges in the
+ database that are allocated to distributor 'distributor'
+ """
+ retBridges = []
+ cur = self._cur
+ cur.execute("SELECT hex_key, address, or_port, distributor, "
+ "first_seen, last_seen FROM Bridges WHERE "
+ "distributor = ?", (distributor, ))
+ for b in cur.fetchall():
+ bridge = BridgeData(b[0], b[1], b[2], b[3], b[4], b[5])
+ retBridges.append(bridge)
+
+ return retBridges
+
+ def updateDistributorForHexKey(self, distributor, hex_key):
+ cur = self._cur
+ cur.execute("UPDATE Bridges SET distributor = ? WHERE hex_key = ?",
+ (distributor, hex_key))
+
+ def getWarnedEmail(self, addr):
+ addr = hashlib.sha1(addr).hexdigest()
+ cur = self._cur
+ cur.execute("SELECT * FROM WarnedEmails WHERE email = ?", (addr,))
+ v = cur.fetchone()
+ if v is None:
+ return False
+ return True
+
+ def setWarnedEmail(self, addr, warned=True, whenWarned=time.time()):
+ addr = hashlib.sha1(addr).hexdigest()
+ t = timeToStr(whenWarned)
+ cur = self._cur
+ if warned == True:
+ cur.execute("INSERT INTO WarnedEmails"
+ "(email,when_warned) VALUES (?,?)", (addr, t,))
+ elif warned == False:
+ cur.execute("DELETE FROM WarnedEmails WHERE email = ?", (addr,))
+
+ def cleanWarnedEmails(self, expireBefore):
+ cur = self._cur
+ t = timeToStr(expireBefore)
+
+ cur.execute("DELETE FROM WarnedEmails WHERE when_warned < ?", (t,))
+
+ def updateIntoBridgeHistory(self, bh):
+ cur = self._cur
+ cur.execute("INSERT OR REPLACE INTO BridgeHistory values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ (bh.fingerprint, str(bh.ip), bh.port,
+ bh.weightedUptime, bh.weightedTime, bh.weightedRunLength,
+ bh.totalRunWeights, bh.lastSeenWithDifferentAddressAndPort,
+ bh.lastSeenWithThisAddressAndPort, bh.lastDiscountedHistoryValues,
+ bh.lastUpdatedWeightedTime))
+ return bh
+
+ def delBridgeHistory(self, fp):
+ cur = self._cur
+ cur.execute("DELETE FROM BridgeHistory WHERE fingerprint = ?", (fp,))
+
+ def getBridgeHistory(self, fp):
+ cur = self._cur
+ cur.execute("SELECT * FROM BridgeHistory WHERE fingerprint = ?", (fp,))
+ h = cur.fetchone()
+ if h is None:
+ return
+ return BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
+
+ def getAllBridgeHistory(self):
+ cur = self._cur
+ v = cur.execute("SELECT * FROM BridgeHistory")
+ if v is None: return
+ for h in v:
+ yield BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
+
+ def getBridgesLastUpdatedBefore(self, statusPublicationMillis):
+ cur = self._cur
+ v = cur.execute("SELECT * FROM BridgeHistory WHERE lastUpdatedWeightedTime < ?",
+ (statusPublicationMillis,))
+ if v is None: return
+ for h in v:
+ yield BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
+
+
+def openDatabase(sqlite_file):
+ conn = sqlite3.Connection(sqlite_file)
+ cur = conn.cursor()
+ try:
+ try:
+ cur.execute("SELECT value FROM Config WHERE key = 'schema-version'")
+ val, = cur.fetchone()
+ if val == 2:
+ logging.info("Adding new table BridgeHistory")
+ cur.executescript(SCHEMA_2TO3_SCRIPT)
+ elif val != 3:
+ logging.warn("Unknown schema version %s in database.", val)
+ except sqlite3.OperationalError:
+ logging.warn("No Config table found in DB; creating tables")
+ cur.executescript(SCHEMA3_SCRIPT)
+ conn.commit()
+ finally:
+ cur.close()
+ return conn
+
+
+class DBGeneratorContextManager(GeneratorContextManager):
+ """Helper for @contextmanager decorator.
+
+ Overload __exit__() so we can call the generator many times
+ """
+ def __exit__(self, type, value, traceback):
+ """Handle exiting a with statement block
+
+ Progress generator or throw exception
+
+ Significantly based on contextlib.py
+
+ :throws: `RuntimeError` if the generator doesn't stop after
+ exception is thrown
+ """
+ if type is None:
+ try:
+ self.gen.next()
+ except StopIteration:
+ return
+ return
+ else:
+ if value is None:
+ # Need to force instantiation so we can reliably
+ # tell if we get the same exception back
+ value = type()
+ try:
+ self.gen.throw(type, value, traceback)
+ raise RuntimeError("generator didn't stop after throw()")
+ except StopIteration, exc:
+ # Suppress the exception *unless* it's the same exception that
+ # was passed to throw(). This prevents a StopIteration
+ # raised inside the "with" statement from being suppressed
+ return exc is not value
+ except:
+ # only re-raise if it's *not* the exception that was
+ # passed to throw(), because __exit__() must not raise
+ # an exception unless __exit__() itself failed. But throw()
+ # has to raise the exception to signal propagation, so this
+ # fixes the impedance mismatch between the throw() protocol
+ # and the __exit__() protocol.
+ #
+ if sys.exc_info()[1] is not value:
+ raise
+
+def contextmanager(func):
+ """Decorator to for :func:`Storage.getDB()`
+
+ Define getDB() for use by with statement content manager
+ """
+ @wraps(func)
+ def helper(*args, **kwds):
+ return DBGeneratorContextManager(func(*args, **kwds))
+ return helper
+
+_DB_FNAME = None
+_LOCK = None
+_LOCKED = 0
+_OPENED_DB = None
+_REFCOUNT = 0
+
+def clearGlobalDB():
+ """Start from scratch.
+
+ This is currently only used in unit tests.
+ """
+ global _DB_FNAME
+ global _LOCK
+ global _LOCKED
+ global _OPENED_DB
+
+ _DB_FNAME = None
+ _LOCK = None
+ _LOCKED = 0
+ _OPENED_DB = None
+ _REFCOUNT = 0
+
+def initializeDBLock():
+ """Create the lock
+
+ This must be called before the first database query
+ """
+ global _LOCK
+
+ if not _LOCK:
+ _LOCK = threading.RLock()
+ assert _LOCK
+
+def setDBFilename(sqlite_fname):
+ global _DB_FNAME
+ _DB_FNAME = sqlite_fname
+
+ at contextmanager
+def getDB(block=True):
+ """Generator: Return a usable database handler
+
+ Always return a :class:`bridgedb.Storage.Database` that is
+ usable within the current thread. If a connection already exists
+ and it was created by the current thread, then return the
+ associated :class:`bridgedb.Storage.Database` instance. Otherwise,
+ create a new instance, blocking until the existing connection
+ is closed, if applicable.
+
+ Note: This is a blocking call (by default), be careful about
+ deadlocks!
+
+ :rtype: :class:`bridgedb.Storage.Database`
+ :returns: An instance of :class:`bridgedb.Storage.Database` used to
+ query the database
+ """
+ global _DB_FNAME
+ global _LOCK
+ global _LOCKED
+ global _OPENED_DB
+ global _REFCOUNT
+
+ assert _LOCK
+ try:
+ own_lock = _LOCK.acquire(block)
+ if own_lock:
+ _LOCKED += 1
+
+ if not _OPENED_DB:
+ assert _REFCOUNT == 0
+ _OPENED_DB = Database(_DB_FNAME)
+
+ _REFCOUNT += 1
+ yield _OPENED_DB
+ else:
+ yield False
+ finally:
+ assert own_lock
+ try:
+ _REFCOUNT -= 1
+ if _REFCOUNT == 0:
+ _OPENED_DB.close()
+ _OPENED_DB = None
+ finally:
+ _LOCKED -= 1
+ _LOCK.release()
+
+def dbIsLocked():
+ return _LOCKED != 0
diff --git a/bridgedb/__init__.py b/bridgedb/__init__.py
new file mode 100644
index 0000000..3643ea7
--- /dev/null
+++ b/bridgedb/__init__.py
@@ -0,0 +1,17 @@
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+from ._version import get_versions
+from ._langs import get_langs
+
+__version__ = get_versions()['version']
+__langs__ = get_langs()
+
+del get_versions
+del get_langs
diff --git a/bridgedb/_langs.py b/bridgedb/_langs.py
new file mode 100644
index 0000000..aca8342
--- /dev/null
+++ b/bridgedb/_langs.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""_langs.py - Storage for information on installed language support."""
+
+RTL_LANGS = ('ar', 'he', 'fa', 'gu_IN', 'ku')
+
+
+def get_langs():
+ """Return a list of two-letter country codes of translations which were
+ installed (if we've already been installed).
+ """
+ return supported
+
+
+#: This list will be rewritten by :func:`get_supported_langs` in setup.py at
+#: install time, so that the :attr:`bridgedb.__langs__` will hold a list of
+#: two-letter country codes for languages which were installed.
+supported = []
diff --git a/bridgedb/_version.py b/bridgedb/_version.py
new file mode 100644
index 0000000..8e9faf1
--- /dev/null
+++ b/bridgedb/_version.py
@@ -0,0 +1,197 @@
+
+IN_LONG_VERSION_PY = True
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (build by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.7+ (https://github.com/warner/python-versioneer)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "$Format:%d$"
+git_full = "$Format:%H$"
+
+
+import subprocess
+import sys
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if verbose:
+ print("unable to run %s" % args[0])
+ print(e)
+ return None
+ stdout = p.communicate()[0].strip()
+ if sys.version >= '3':
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %s (error)" % args[0])
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("variables are unexpanded, not using")
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print("discarding '%s', no digits" % ref)
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print("remaining refs: %s" % ",".join(sorted(refs)))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %s" % r)
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print("no suitable tags, using full revision id")
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print("no .git in %s" % root)
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
+ (root, dirname, parentdir_prefix))
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+tag_prefix = "bridgedb-"
+parentdir_prefix = "bridgedb-"
+versionfile_source = "lib/bridgedb/_version.py"
+
+def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
+ variables = { "refnames": git_refnames, "full": git_full }
+ ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
+ if not ver:
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if not ver:
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
+ verbose)
+ if not ver:
+ ver = default
+ return ver
+
diff --git a/bridgedb/bridgerequest.py b/bridgedb/bridgerequest.py
new file mode 100644
index 0000000..1266145
--- /dev/null
+++ b/bridgedb/bridgerequest.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridgerequest ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+import ipaddr
+import logging
+
+from zope.interface import implements
+from zope.interface import Attribute
+from zope.interface import Interface
+
+from bridgedb.crypto import getHMACFunc
+from bridgedb.filters import byIPv
+from bridgedb.filters import byNotBlockedIn
+from bridgedb.filters import byTransport
+
+
+class IRequestBridges(Interface):
+ """Interface specification of client options for requested bridges."""
+
+ filters = Attribute(
+ "A list of callables used to filter bridges from a hashring.")
+ ipVersion = Attribute(
+ "The IP version of bridge addresses to distribute to the client.")
+ transports = Attribute(
+ "A list of strings of Pluggable Transport types requested.")
+ notBlockedIn = Attribute(
+ "A list of two-character country codes. The distributed bridges "
+ "should not be blocked in these countries.")
+ valid = Attribute(
+ "A boolean. Should be ``True`` if the client's request was valid.")
+ client = Attribute(
+ "This should be some information unique to the client making the "
+ "request for bridges, such that we are able to HMAC this unique "
+ "data, via getHashringPlacement(), in order to place the client "
+ "into a hashring (determining which bridge addresses they get in "
+ "the request response).")
+
+ def addFilter():
+ """Add a filter to the list of ``filters``."""
+
+ def clearFilters():
+ """Clear the list of ``filters``."""
+
+ def generateFilters():
+ """Build the list of callables, ``filters``, according to the current
+ contents of the lists of ``transports``, ``notBlockedIn``, and the
+ ``ipVersion``.
+ """
+
+ def getHashringPlacement():
+ """Use some unique parameters of the client making this request to
+ obtain a value which we can use to place them into one of the hashrings
+ with :class:`~bridgedb.bridges.Bridge`s in it, in order to give that
+ client different bridges than other clients.
+ """
+
+ def isValid():
+ """Determine if the request is ``valid`` according to some parameters."""
+
+ def withIPv4():
+ """Set the ``ipVersion`` to IPv4."""
+
+ def withIPv6():
+ """Set the ``ipVersion`` to IPv6."""
+
+ def withPluggableTransportType(typeOfPT):
+ """Add this **typeOfPT** to the list of requested ``transports``."""
+
+ def withoutBlockInCountry(countryCode):
+ """Add this **countryCode** to the list of countries which distributed
+ bridges should not be blocked in (``notBlockedIn``).
+ """
+
+
+class BridgeRequestBase(object):
+ """A generic base class for storing options of a client bridge request."""
+
+ implements(IRequestBridges)
+
+ def __init__(self, ipVersion=None):
+ self.ipVersion = ipVersion
+ #: (list) A list of callables used to filter bridges from a hashring.
+ self.filters = list()
+ #: (list) A list of strings of Pluggable Transport types requested.
+ self.transports = list()
+ #: (list) A list of two-character country codes. The distributed bridges
+ #: should not be blocked in these countries.
+ self.notBlockedIn = list()
+ #: This should be some information unique to the client making the
+ #: request for bridges, such that we are able to HMAC this unique data
+ #: in order to place the client into a hashring (determining which
+ #: bridge addresses they get in the request response). It defaults to
+ #: the string ``'default'``.
+ self.client = 'default'
+ #: (bool) Should be ``True`` if the client's request was valid.
+ self.valid = False
+
+ @property
+ def ipVersion(self):
+ """The IP version of bridge addresses to distribute to the client.
+
+ :rtype: int
+ :returns: Either ``4`` or ``6``.
+ """
+ return self._ipVersion
+
+ @ipVersion.setter
+ def ipVersion(self, ipVersion):
+ """The IP version of bridge addresses to distribute to the client.
+
+ :param int ipVersion: The IP address version for the bridge lines we
+ should distribute in response to this client request.
+ """
+ if not ipVersion in (4, 6):
+ ipVersion = 4
+ self._ipVersion = ipVersion
+
+ def getHashringPlacement(self, key, client=None):
+ """Create an HMAC of some **client** info using a **key**.
+
+ :param str key: The key to use for HMACing.
+ :param str client: Some (hopefully unique) information about the
+ client who is requesting bridges, such as an IP or email address.
+ :rtype: long
+ :returns: A long specifying index of the first node in a hashring to
+ be distributed to the client. This value should obviously be used
+ mod the number of nodes in the hashring.
+ """
+ if not client:
+ client = self.client
+
+ # Get an HMAC with the key of the client identifier:
+ digest = getHMACFunc(key)(client)
+ # Take the lower 8 bytes of the digest and convert to a long:
+ position = long(digest[:8], 16)
+ return position
+
+ def isValid(self, valid=None):
+ """Get or set the validity of this bridge request.
+
+ If called without parameters, this method will return the current
+ state, otherwise (if called with the **valid** parameter), it will set
+ the current state of validity for this request.
+
+ :param bool valid: If given, set the validity state of this
+ request. Otherwise, get the current state.
+ """
+ if valid is not None:
+ self.valid = bool(valid)
+ return self.valid
+
+ def withIPv4(self):
+ self.ipVersion = 4
+
+ def withIPv6(self):
+ self.ipVersion = 6
+
+ def withoutBlockInCountry(self, country):
+ self.notBlockedIn.append(country.lower())
+
+ def withPluggableTransportType(self, pt):
+ self.transports.append(pt)
+
+ def addFilter(self, filtre):
+ self.filters.append(filtre)
+
+ def clearFilters(self):
+ self.filters = []
+
+ def justOnePTType(self):
+ """Get just one bridge PT type at a time!"""
+ ptType = None
+ try:
+ ptType = self.transports[-1] # Use the last PT requested
+ except IndexError:
+ logging.debug("No pluggable transports were requested.")
+ return ptType
+
+ def generateFilters(self):
+ self.clearFilters()
+
+ pt = self.justOnePTType()
+ msg = ("Adding a filter to %s for %s for IPv%d"
+ % (self.__class__.__name__, self.client, self.ipVersion))
+
+ if self.notBlockedIn:
+ for country in self.notBlockedIn:
+ logging.info("%s %s bridges not blocked in %s..." %
+ (msg, pt or "vanilla", country))
+ self.addFilter(byNotBlockedIn(country, pt, self.ipVersion))
+ elif pt:
+ logging.info("%s %s bridges..." % (msg, pt))
+ self.addFilter(byTransport(pt, self.ipVersion))
+ else:
+ logging.info("%s bridges..." % msg)
+ self.addFilter(byIPv(self.ipVersion))
diff --git a/bridgedb/bridges.py b/bridgedb/bridges.py
new file mode 100644
index 0000000..747863a
--- /dev/null
+++ b/bridgedb/bridges.py
@@ -0,0 +1,1769 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridges -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see the AUTHORS file for attributions
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""Classes for manipulating and storing Bridges and their attributes."""
+
+from __future__ import print_function
+
+import base64
+import codecs
+import hashlib
+import ipaddr
+import logging
+import os
+import warnings
+
+from Crypto.Util import asn1
+from Crypto.Util.number import bytes_to_long
+from Crypto.Util.number import long_to_bytes
+
+from zope.interface import implementer
+from zope.interface import Attribute
+from zope.interface import Interface
+
+import bridgedb.Storage
+
+from bridgedb import geo
+from bridgedb import safelog
+from bridgedb import bridgerequest
+from bridgedb.crypto import removePKCS1Padding
+from bridgedb.parse.addr import isIPAddress
+from bridgedb.parse.addr import isIPv4
+from bridgedb.parse.addr import isIPv6
+from bridgedb.parse.addr import isValidIP
+from bridgedb.parse.addr import PortList
+from bridgedb.parse.fingerprint import isValidFingerprint
+from bridgedb.parse.fingerprint import toHex
+from bridgedb.parse.fingerprint import fromHex
+from bridgedb.parse.nickname import isValidRouterNickname
+from bridgedb.util import isascii_noncontrol
+
+
+class PluggableTransportUnavailable(Exception):
+ """Raised when a :class:`Bridge` doesn't have the requested
+ :class:`PluggableTransport`.
+ """
+
+class MalformedBridgeInfo(ValueError):
+ """Raised when some information about a bridge appears malformed."""
+
+class MalformedPluggableTransport(MalformedBridgeInfo):
+ """Raised when information used to initialise a :class:`PluggableTransport`
+ appears malformed.
+ """
+
+class InvalidPluggableTransportIP(MalformedBridgeInfo):
+ """Raised when a :class:`PluggableTransport` has an invalid address."""
+
+class MissingServerDescriptorDigest(MalformedBridgeInfo):
+ """Raised when the hash digest for an ``@type bridge-server-descriptor``
+ (which should be in the corresponding ``@type bridge-networkstatus``
+ document), was missing.
+ """
+
+class ServerDescriptorDigestMismatch(MalformedBridgeInfo):
+ """Raised when the digest in an ``@type bridge-networkstatus`` document
+ doesn't match the hash digest of the ``@type bridge-server-descriptor``'s
+ contents.
+ """
+
+class ServerDescriptorWithoutNetworkstatus(MalformedBridgeInfo):
+ """Raised when we find a ``@type bridge-server-descriptor`` which was not
+ mentioned in the latest ``@type bridge-networkstatus`` document.
+ """
+
+class InvalidExtraInfoSignature(MalformedBridgeInfo):
+ """Raised if the signature on an ``@type bridge-extrainfo`` is invalid."""
+
+
+class IBridge(Interface):
+ """I am a (mostly) stub interface whose primary purpose is merely to allow
+ other classes to signify whether or not they can be treated like a
+ :class:`Bridge`.
+ """
+ fingerprint = Attribute(
+ ("The lowercased, hexadecimal-encoded, hash digest of this Bridge's "
+ "public identity key."))
+ address = Attribute("This Bridge's primary public IP address.")
+ port = Attribute("The port which this Bridge is listening on.")
+
+
+class Flags(object):
+ """All the flags which a :class:`Bridge` may have."""
+
+ fast = False
+ guard = False
+ running = False
+ stable = False
+ valid = False
+
+ def update(self, flags):
+ """Update with **flags** taken from an ``@type networkstatus-bridge``
+ 's'-line.
+
+ From `dir-spec.txt`_:
+ |
+ | "s" SP Flags NL
+ |
+ | [Exactly once.]
+ |
+ | A series of space-separated status flags, in lexical order (as ASCII
+ | byte strings). Currently documented flags are:
+ |
+ | [...]
+ | "Fast" if the router is suitable for high-bandwidth circuits.
+ | "Guard" if the router is suitable for use as an entry guard.
+ | [...]
+ | "Stable" if the router is suitable for long-lived circuits.
+ | "Running" if the router is currently usable.
+ | [...]
+ | "Valid" if the router has been 'validated'.
+
+ .. _dir-spec.txt:
+ https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt?id=7647f6d4d#n1602
+
+ :param list flags: A list of strings containing each of the flags
+ parsed from the 's'-line.
+ """
+ self.fast = 'Fast' in flags
+ self.guard = 'Guard' in flags
+ self.running = 'Running' in flags
+ self.stable = 'Stable' in flags
+ self.valid = 'Valid' in flags
+
+
+ at implementer(IBridge)
+class BridgeAddressBase(object):
+ """A base class for describing one of a :class:`Bridge`'s or a
+ :class:`PluggableTransport`'s location, including its identity key
+ fingerprint and IP address.
+
+ :type fingerprint: str
+ :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
+ key of the parent bridge running this pluggable transport instance,
+ i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
+ contains a hash digest for a ``@type bridge-extrainfo-document``, the
+ latter of which contains the parameter of this pluggable transport in
+ its ``transport`` line.
+
+ :type address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
+ :ivar address: The IP address of :class:`Bridge` or one of its
+ :class:`PluggableTransport`s.
+
+ :type country: str
+ :ivar country: The two-letter GeoIP country code of the :ivar:`address`.
+
+ :type port: int
+ :ivar port: A integer specifying the port which this :class:`Bridge`
+ (or :class:`PluggableTransport`) is listening on.
+ """
+
+ def __init__(self):
+ self._fingerprint = None
+ self._address = None
+ self._country = None
+ self._port = None
+
+ @property
+ def fingerprint(self):
+ """Get this Bridge's fingerprint.
+
+ :rtype: str
+ :returns: A 40-character hexadecimal formatted string representation
+ of the SHA-1 hash digest of the public half of this Bridge's
+ identity key.
+ """
+ return self._fingerprint
+
+ @fingerprint.setter
+ def fingerprint(self, value):
+ """Set this Bridge's fingerprint to **value**.
+
+ .. info: The purported fingerprint will be checked for specification
+ conformity with
+ :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
+
+ :param str value: The fingerprint for this Bridge.
+ """
+ if value and isValidFingerprint(value):
+ self._fingerprint = value.upper()
+
+ @fingerprint.deleter
+ def fingerprint(self):
+ """Reset this Bridge's fingerprint."""
+ self._fingerprint = None
+
+ @property
+ def identity(self):
+ """Get this Bridge's identity digest.
+
+ :rtype: bytes
+ :returns: The binary-encoded SHA-1 hash digest of the public half of
+ this Bridge's identity key, if available; otherwise, returns
+ ``None``.
+ """
+ if self.fingerprint:
+ return fromHex(self.fingerprint)
+
+ @identity.setter
+ def identity(self, value):
+ """Set this Bridge's identity digest to **value**.
+
+ .. info: The purported identity digest will be checked for
+ specification conformity with
+ :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
+
+ :param str value: The binary-encoded SHA-1 hash digest of the public
+ half of this Bridge's identity key.
+ """
+ self.fingerprint = toHex(value)
+
+ @identity.deleter
+ def identity(self):
+ """Reset this Bridge's identity digest."""
+ del(self.fingerprint)
+
+ @property
+ def address(self):
+ """Get this bridge's address.
+
+ :rtype: :class:`~ipaddr.IPv4Address` or :class:`~ipaddr.IPv6Address`
+ :returns: The bridge's address.
+ """
+ return self._address
+
+ @address.setter
+ def address(self, value):
+ """Set this Bridge's address.
+
+ :param value: The main ORPort IP address of this bridge.
+ """
+ if value and isValidIP(value): # XXX only conditionally set _address?
+ self._address = isIPAddress(value, compressed=False)
+
+ @address.deleter
+ def address(self):
+ """Reset this Bridge's address to ``None``."""
+ self._address = None
+
+ @property
+ def country(self):
+ """Get the two-letter GeoIP country code for the :ivar:`address`.
+
+ :rtype: str or ``None``
+ :returns: If :ivar:`address` is set, this returns a two-letter country
+ code for the geolocated region that :ivar:`address` is within;
+ otherwise, returns ``None``.
+ """
+ if self.address:
+ return geo.getCountryCode(self.address)
+
+ @property
+ def port(self):
+ """Get the port number which this ``Bridge`` is listening
+ for incoming client connections on.
+
+ :rtype: int or None
+ :returns: The port (as an int), if it is known and valid; otherwise,
+ returns ``None``.
+ """
+ return self._port
+
+ @port.setter
+ def port(self, value):
+ """Store the port number which this ``Bridge`` is listening
+ for incoming client connections on.
+
+ :param int value: The transport's port.
+ """
+ if isinstance(value, int) and (0 <= value <= 65535):
+ self._port = value
+
+ @port.deleter
+ def port(self):
+ """Reset this ``Bridge``'s port to ``None``."""
+ self._port = None
+
+
+
+ at implementer(IBridge)
+class PluggableTransport(BridgeAddressBase):
+ """A single instance of a Pluggable Transport (PT) offered by a
+ :class:`Bridge`.
+
+ Pluggable transports are described within a bridge's
+ ``@type bridge-extrainfo`` descriptor, see the
+ ``Specifications: Client behavior`` section and the
+ ``TOR_PT_SERVER_TRANSPORT_OPTIONS`` description in pt-spec.txt_ for
+ additional specification.
+
+ .. _pt-spec.txt:
+ https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
+
+ :type fingerprint: str
+ :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
+ key of the parent bridge running this pluggable transport instance,
+ i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
+ contains a hash digest for a ``@type bridge-extrainfo-document``, the
+ latter of which contains the parameter of this pluggable transport in
+ its ``transport`` line.
+
+ :type methodname: str
+ :ivar methodname: The canonical "name" for this pluggable transport,
+ i.e. the one which would be specified in a torrc file. For example,
+ ``"obfs2"``, ``"obfs3"``, ``"scramblesuit"`` would all be pluggable
+ transport method names.
+
+ :type address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
+ :ivar address: The IP address of the transport. Currently (as of 20 March
+ 2014), there are no known, widely-deployed pluggable transports which
+ support IPv6. Ergo, this is very likely going to be an IPv4 address.
+
+ :type port: int
+ :ivar port: A integer specifying the port which this pluggable transport
+ is listening on. (This should likely be whatever port the bridge
+ specified in its ``ServerTransportPlugin`` torrc line, unless the
+ pluggable transport is running in "managed" mode.)
+
+ :type arguments: dict
+ :ivar arguments: Some PTs can take additional arguments, which must be
+ distributed to the client out-of-band. These are present in the
+ ``@type bridge-extrainfo-document``, in the ``transport`` line like
+ so::
+
+ METHOD SP ADDR ":" PORT SP [K=V[,K=V[,K=V[â¦]]]]
+
+ where K is the key in **arguments**, and V is the value. For example,
+ in the case of ``scramblesuit``, for which the client must supply a
+ shared secret to the ``scramblesuit`` instance running on the bridge,
+ the **arguments** would be something like::
+
+ {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
+ """
+
+ def __init__(self, fingerprint=None, methodname=None,
+ address=None, port=None, arguments=None):
+ """Create a ``PluggableTransport`` describing a PT running on a bridge.
+
+ :param str fingerprint: The uppercased, hexadecimal fingerprint of the
+ identity key of the parent bridge running this pluggable transport.
+ :param str methodname: The canonical "name" for this pluggable
+ transport. See :data:`methodname`.
+ :param str address: The IP address of the transport. See
+ :data:`address`.
+ :param int port: A integer specifying the port which this pluggable
+ transport is listening on.
+ :param dict arguments: Any additional arguments which the PT takes,
+ which must be distributed to the client out-of-band. See
+ :data:`arguments`.
+ """
+ super(PluggableTransport, self).__init__()
+ self._methodname = None
+ self._blockedIn = {}
+
+ self.fingerprint = fingerprint
+ self.address = address
+ self.port = port
+ self.methodname = methodname
+ self.arguments = arguments
+
+ # Because we can intitialise this class with the __init__()
+ # parameters, or use the ``updateFromStemTransport()`` method, we'll
+ # only use the ``_runChecks()`` method now if we were initialised with
+ # parameters:
+ if (fingerprint or address or port or methodname or arguments):
+ self._runChecks()
+
+ def _parseArgumentsIntoDict(self, argumentList):
+ """Convert a list of Pluggable Transport arguments into a dictionary
+ suitable for :data:`arguments`.
+
+ :param list argumentList: A list of Pluggable Transport
+ arguments. There might be multiple, comma-separated ``K=V``
+ Pluggable Transport arguments in a single item in the
+ **argumentList**, or each item might be its own ``K=V``; we don't
+ care and we should be able to parse it either way.
+ :rtype: dict
+ :returns: A dictionary of all the ``K=V`` Pluggable Transport
+ arguments.
+ """
+ argDict = {}
+
+ # PT arguments are comma-separated in the extrainfo
+ # descriptors. While there *shouldn't* be anything after them that was
+ # separated by a space (and hence would wind up being in a different
+ # item in `arguments`), if there was we'll join it to the rest of the
+ # PT arguments with a comma so that they are parsed as if they were PT
+ # arguments as well:
+ allArguments = ','.join(argumentList)
+
+ for arg in allArguments.split(','):
+ if arg: # It might be an empty string
+ try:
+ key, value = arg.split('=')
+ except ValueError:
+ logging.warn(" Couldn't parse K=V from PT arg: %r" % arg)
+ else:
+ logging.debug(" Parsed PT Argument: %s: %s" % (key, value))
+ argDict[key] = value
+
+ return argDict
+
+ def _runChecks(self):
+ """Validate that we were initialised with acceptable parameters.
+
+ We currently check that:
+
+ 1. The :data:`port` is an integer, and that it is between the values
+ of ``0`` and ``65535`` (inclusive).
+
+ 2. The :data:`arguments` is a dictionary.
+
+ 3. The :data:`arguments` do not contain non-ASCII or control
+ characters or double quotes or backslashes, in keys or
+ in values.
+
+ :raises MalformedPluggableTransport: if any of the above checks fails.
+ """
+ if not self.fingerprint:
+ raise MalformedPluggableTransport(
+ ("Cannot create %s without owning Bridge fingerprint!")
+ % self.__class__.__name__)
+
+ if not self.address:
+ raise InvalidPluggableTransportIP(
+ ("Cannot create PluggableTransport with address '%s'. "
+ "type(address)=%s.") % (self.address, type(self.address)))
+
+ if not self.port:
+ raise MalformedPluggableTransport(
+ ("Cannot create PluggableTransport without a valid port."))
+
+ if not isinstance(self.arguments, dict):
+ raise MalformedPluggableTransport(
+ ("Cannot create PluggableTransport with arguments type: %s")
+ % type(self.arguments))
+
+ for item in self.arguments.items():
+ kv = ''.join(item)
+ if not isascii_noncontrol(kv):
+ raise MalformedPluggableTransport(
+ ("Cannot create PluggableTransport with non-ASCII or "
+ "control characters in arguments: %r=%r") % item)
+ if '"' in kv or '\\' in kv:
+ raise MalformedPluggableTransport(
+ ("Cannot create PluggableTransport with double quotes or "
+ "backslashes in arguments: %r=%r") % item)
+
+ if not self._checkArguments():
+ raise MalformedPluggableTransport(
+ ("Can't use %s transport with missing arguments. Arguments: "
+ "%s") % (self.methodname, ' '.join(self.arguments.keys())))
+
+ def _checkArguments(self):
+ """This method is a temporary fix for PTs with missing arguments
+ (see `#13202 <https://bugs.torproject.org/13202`_). This method can
+ be removed after Tor-0.2.4.x is deprecated.
+ """
+ # obfs4 requires (iat-mode && (cert || (node-id && public-key))):
+ if self.methodname == 'obfs4':
+ if self.arguments.get('iat-mode'):
+ if (self.arguments.get('cert') or \
+ (self.arguments.get('node-id') and self.arguments.get('public-key'))):
+ return True
+ # scramblesuit requires (password):
+ elif self.methodname == 'scramblesuit':
+ if self.arguments.get('password'):
+ return True
+ else:
+ return True
+
+ return False
+
+ @property
+ def methodname(self):
+ """Get this :class:`PluggableTransport`'s methodname.
+
+ :rtype: str
+ :returns: The (lowercased) methodname of this ``PluggableTransport``,
+ i.e. ``"obfs3"``, ``"scramblesuit"``, etc.
+ """
+ return self._methodname
+
+ @methodname.setter
+ def methodname(self, value):
+ """Set this ``PluggableTransport``'s methodname.
+
+ .. hint:: The **value** will be automatically lowercased.
+
+ :param str value: The new methodname.
+ """
+ if value:
+ try:
+ self._methodname = value.lower()
+ except (AttributeError, TypeError):
+ raise TypeError("methodname must be a str or unicode")
+
+ def getTransportLine(self, includeFingerprint=True, bridgePrefix=False):
+ """Get a Bridge Line for this :class:`PluggableTransport`.
+
+ .. glossary::
+
+ Bridge Line
+ A "Bridge Line" is how BridgeDB refers to lines in a ``torrc``
+ file which should begin with the word ``"Bridge"``, and it is how
+ a client tells their Tor process that they would like to use a
+ particular bridge.
+
+ .. note:: If **bridgePrefix** is ``False``, this method does not
+ return lines which are prefixed with the word 'bridge', as they
+ would be in a torrc file. Instead, lines returned look like this::
+
+ obfs3 245.102.100.252:23619 59ca743e89b508e16b8c7c6d2290efdfd14eea98
+
+ This was made configurable to fix Vidalia being a brain-damaged
+ piece of shit (#5851_). TorLaucher replaced Vidalia soon after,
+ and TorLauncher is intelligent enough to understand
+ :term:`Bridge Line`s regardless of whether or not they are prefixed
+ with the word "Bridge".
+
+ .. _#5851: https://bugs.torproject.org/5851
+
+ :param bool includeFingerprints: If ``True``, include the digest of
+ this bridges public identity key in the torrc line.
+ :param bool bridgePrefix: If ``True``, add ``'Bridge '`` to the
+ beginning of each returned line (suitable for pasting directly
+ into a ``torrc`` file).
+ :rtype: str
+ :returns: A configuration line for adding this Pluggable Transport
+ into a ``torrc`` file.
+ """
+ sections = []
+
+ if bridgePrefix:
+ sections.append('Bridge')
+
+ if self.address.version == 6:
+ # If the address was IPv6, put brackets around it:
+ host = '%s [%s]:%d' % (self.methodname, self.address, self.port)
+ else:
+ host = '%s %s:%d' % (self.methodname, self.address, self.port)
+ sections.append(host)
+
+ if includeFingerprint:
+ sections.append(self.fingerprint)
+
+ for key, value in self.arguments.items():
+ sections.append('%s=%s' % (key, value))
+
+ line = ' '.join(sections)
+
+ return line
+
+ def updateFromStemTransport(self, fingerprint, methodname, kitchenSink):
+ """Update this :class:`PluggableTransport` from the data structure
+ which Stem uses.
+
+ Stem's
+ :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
+ parses extrainfo ``transport`` lines into a dictionary with the
+ following structure::
+
+ {u'obfs2': (u'34.230.223.87', 37339, []),
+ u'obfs3': (u'34.230.223.87', 37338, []),
+ u'obfs4': (u'34.230.223.87', 37341, [
+ (u'iat-mode=0,'
+ u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
+ u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
+ u'scramblesuit': (u'34.230.223.87', 37340, [
+ u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
+
+ This method will initialise this class from the dictionary key
+ (**methodname**) and its tuple of values (**kitchenSink**).
+
+ :param str fingerprint: The uppercased, hexadecimal fingerprint of the
+ identity key of the parent bridge running this pluggable transport.
+ :param str methodname: The :data:`methodname` of this Pluggable
+ Transport.
+ :param tuple kitchenSink: Everything else that was on the
+ ``transport`` line in the bridge's extrainfo descriptor, which
+ Stem puts into the 3-tuples shown in the example above.
+ """
+ self.fingerprint = str(fingerprint)
+ self.methodname = str(methodname)
+ self.address = kitchenSink[0]
+
+ port = kitchenSink[1]
+ if port == 'anyport': # IDK. Stem, WTF?
+ port = 0
+
+ self.port = int(port)
+ self.arguments = self._parseArgumentsIntoDict(kitchenSink[2])
+ self._runChecks()
+
+
+ at implementer(IBridge)
+class BridgeBase(BridgeAddressBase):
+ """The base class for all bridge implementations."""
+
+ def __init__(self):
+ super(BridgeBase, self).__init__()
+
+ self._nickname = None
+ self._orPort = None
+ self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
+ self.dirPort = 0 # ``DirPort`` set to ``0``
+ self.orAddresses = []
+ self.transports = []
+ self.flags = Flags()
+
+ @property
+ def nickname(self):
+ """Get this Bridge's nickname.
+
+ :rtype: str
+ :returns: The Bridge's nickname.
+ """
+ return self._nickname
+
+ @nickname.setter
+ def nickname(self, value):
+ """Set this Bridge's nickname to **value**.
+
+ .. note:: We don't need to call
+ :func:`bridgedb.parse.nickname.isValidRouterNickname() since Stem
+ will check nickname specification conformity.
+
+ :param str value: The nickname of this Bridge.
+ """
+ self._nickname = value
+
+ @nickname.deleter
+ def nickname(self):
+ """Reset this Bridge's nickname."""
+ self._nickname = None
+
+ @property
+ def orPort(self):
+ """Get this bridge's ORPort.
+
+ :rtype: int
+ :returns: This Bridge's default ORPort.
+ """
+ return self.port
+
+ @orPort.setter
+ def orPort(self, value):
+ """Set this Bridge's ORPort.
+
+ :param int value: The Bridge's ORPort.
+ """
+ self.port = value
+
+ @orPort.deleter
+ def orPort(self):
+ """Reset this Bridge's ORPort."""
+ del self.port
+
+
+ at implementer(IBridge)
+class BridgeBackwardsCompatibility(BridgeBase):
+ """Backwards compatibility methods for the old Bridge class."""
+
+ def __init__(self, nickname=None, ip=None, orport=None,
+ fingerprint=None, id_digest=None, or_addresses=None):
+ """Create a Bridge which is backwards compatible with the old Bridge class
+ implementation.
+
+ .. info: For backwards compatibility, `nickname`, `ip`, and `orport`
+ must be the first, second, and third arguments, respectively. The
+ `fingerprint` and `id_digest` were previously kwargs, and are also
+ provided for backwards compatibility. New calls to
+ :meth:`__init__` *should* avoid using these kwargs, and instead
+ use the methods :meth:`updateFromNetworkStatus`,
+ :meth:`updateFromServerDescriptor`, and
+ :meth:`updateFromExtraInfoDescriptor`.
+ """
+ super(BridgeBackwardsCompatibility, self).__init__()
+
+ self.desc_digest = None
+ self.ei_digest = None
+ self.running = False
+ self.stable = False
+
+ if nickname or ip or orport or fingerprint or id_digest:
+ self._backwardsCompatible(nickname=nickname, address=ip,
+ orPort=orport, fingerprint=fingerprint,
+ idDigest=id_digest,
+ orAddresses=or_addresses)
+
+ def _backwardsCompatible(self, nickname=None, address=None, orPort=None,
+ fingerprint=None, idDigest=None,
+ orAddresses=None):
+ """Functionality for maintaining backwards compatibility with the older
+ version of this class (see :class:`bridgedb.test.deprecated.Bridge`).
+ """
+ self.nickname = nickname
+ self.orPort = orPort
+ if address:
+ self.address = address
+
+ if idDigest:
+ if not fingerprint:
+ if not len(idDigest) == 20:
+ raise TypeError("Bridge with invalid ID")
+ self.fingerprint = toHex(idDigest)
+ elif fingerprint:
+ if not isValidFingerprint(fingerprint):
+ raise TypeError("Bridge with invalid fingerprint (%r)"
+ % fingerprint)
+ self.fingerprint = fingerprint.lower()
+ else:
+ raise TypeError("Bridge with no ID")
+
+ if orAddresses and isinstance(orAddresses, dict):
+ for ip, portlist in orAddresses.items():
+ validAddress = isIPAddress(ip, compressed=False)
+ if validAddress:
+ # The old code expected a `bridgedb.parse.addr.PortList`:
+ if isinstance(portlist, PortList):
+ for port in portlist.ports:
+ self.orAddresses.append(
+ (validAddress, port, validAddress.version,))
+ elif isinstance(portlist, int):
+ self.orAddresses.append(
+ (validAddress, portlist, validAddress.version,))
+ else:
+ logging.warn("Can't parse port for ORAddress %r: %r"
+ % (ip, portlist))
+
+ def getID(self):
+ """Get the binary encoded form of this ``Bridge``'s ``fingerprint``.
+
+ This method is provided for backwards compatibility and should not
+ be relied upon.
+ """
+ return self.identity
+
+ def setDescriptorDigest(self, digest):
+ """Set this ``Bridge``'s server-descriptor digest.
+
+ This method is provided for backwards compatibility and should not
+ be relied upon.
+ """
+ self.desc_digest = digest # old attribute for backwards compat
+ self.descriptorDigest = digest # new attribute
+
+ def setExtraInfoDigest(self, digest):
+ """Set this ``Bridge``'s extrainfo digest.
+
+ This method is provided for backwards compatibility and should not
+ be relied upon.
+ """
+ self.ei_digest = digest # old attribute for backwards compat
+ self.extrainfoDigest = digest # new attribute
+
+ def setStatus(self, running=None, stable=None):
+ """Set this ``Bridge``'s "Running" and "Stable" flags.
+
+ This method is provided for backwards compatibility and should not
+ be relied upon.
+ """
+ if running is not None:
+ self.running = bool(running)
+ self.flags.running = bool(running)
+ if stable is not None:
+ self.stable = bool(stable)
+ self.flags.stable = bool(stable)
+
+ def getConfigLine(self, includeFingerprint=False, addressClass=None,
+ request=None, transport=None):
+ """Get a vanilla bridge line for this ``Bridge``.
+
+ This method is provided for backwards compatibility and should not
+ be relied upon.
+
+ The old ``bridgedb.Bridges.Bridge.getConfigLine()`` method didn't know
+ about :class:`~bridgedb.bridgerequest.BridgeRequestBase`s, and so this
+ modified version is backwards compatible by creating a
+ :class:`~bridgedb.bridgerequest.BridgeRequestBase` for
+ :meth:`getBridgeLine`. The default parameters are the same as they
+ were in the old ``bridgedb.Bridges.Bridge`` class.
+
+ :param bool includeFingerprint: If ``True``, include the
+ ``fingerprint`` of this :class:`Bridge` in the returned bridge
+ line.
+ :type addressClass: :class:`ipaddr.IPv4Address` or
+ :class:`ipaddr.IPv6Address`.
+ :param addressClass: Type of address to choose.
+ :param str request: A string (somewhat) unique to this request,
+ e.g. email-address or ``HTTPSDistributor.getSubnet(ip)``. In
+ this case, this is not a :class:`~bridgerequest.BridgeRequestBase`
+ (as might be expected) but the equivalent of
+ :data:`bridgerequest.BridgeRequestBase.client`.
+ :param str transport: A pluggable transport method name.
+ """
+ ipVersion = 6 if addressClass is ipaddr.IPv6Address else 4
+ bridgeRequest = bridgerequest.BridgeRequestBase(ipVersion)
+ bridgeRequest.client = request if request else bridgeRequest.client
+ bridgeRequest.isValid(True)
+
+ if transport:
+ bridgeRequest.withPluggableTransportType(transport)
+
+ bridgeRequest.generateFilters()
+ bridgeLine = self.getBridgeLine(bridgeRequest, includeFingerprint)
+ return bridgeLine
+
+ # Bridge Stability (`#5482 <https://bugs.torproject.org>`_) properties.
+ @property
+ def familiar(self):
+ """A bridge is "familiar" if 1/8 of all active bridges have appeared
+ more recently than it, or if it has been around for a Weighted Time of
+ eight days.
+ """
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).familiar
+
+ @property
+ def wfu(self):
+ """Weighted Fractional Uptime"""
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).weightedFractionalUptime
+
+ @property
+ def weightedTime(self):
+ """Weighted Time"""
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).weightedTime
+
+ @property
+ def wmtbac(self):
+ """Weighted Mean Time Between Address Change"""
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).wmtbac
+
+ @property
+ def tosa(self):
+ """The Time On Same Address (TOSA)"""
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).tosa
+
+ @property
+ def weightedUptime(self):
+ """Weighted Uptime"""
+ with bridgedb.Storage.getDB() as db: # pragma: no cover
+ return db.getBridgeHistory(self.fingerprint).weightedUptime
+
+
+ at implementer(IBridge)
+class Bridge(BridgeBackwardsCompatibility):
+ """A single bridge, and all the information we have for it.
+
+ :type fingerprint: str or ``None``
+ :ivar fingerprint: This ``Bridge``'s fingerprint, in lowercased
+ hexadecimal format.
+ :type nickname: str or ``None``
+ :ivar nickname: This ``Bridge``'s router nickname.
+ :type socksPort: int
+ :ivar socksPort: This ``Bridge``'s SOCKSPort. Should always be ``0``.
+ :type dirPort: int
+ :ivar dirPort: This ``Bridge``'s DirPort. Should always be ``0``.
+ :type orAddresses: list
+ :ivar orAddresses: A list of 3-tuples in the form::
+ (ADDRESS, PORT, IP_VERSION)
+ where:
+ * ADDRESS is an :class:`ipaddr.IPAddress`,
+ * PORT is an ``int``,
+ * IP_VERSION is either ``4`` or ``6``.
+ :type transports: list
+ :ivar transports: A list of :class:`PluggableTransport`s, one for each
+ transport that this :class:`Bridge` currently supports.
+ :type flags: :class:`~bridgedb.bridges.Flags`
+ :ivar flags: All flags assigned by the BridgeAuthority to this
+ :class:`Bridge`.
+ :type hibernating: bool
+ :ivar hibernating: ``True`` if this :class:`Bridge` is hibernating and not
+ currently serving clients (e.g. if the Bridge hit its configured
+ ``RelayBandwidthLimit``); ``False`` otherwise.
+ :type _blockedIn: dict
+ :ivar _blockedIn: A dictionary of ``ADDRESS:PORT`` pairs to lists of
+ lowercased, two-letter country codes (e.g. ``"us"``, ``"gb"``,
+ ``"cn"``, etc.) which that ``ADDRESS:PORT`` pair is blocked in.
+ :type contact: str or ``None``
+ :ivar contact: The contact information for the this Bridge's operator.
+ :type family: set or ``None``
+ :ivar family: The fingerprints of other Bridges related to this one.
+ :type platform: str or ``None``
+ :ivar platform: The ``platform`` line taken from the
+ ``@type bridge-server-descriptor``, e.g.
+ ``'Tor 0.2.5.4-alpha on Linux'``.
+ :type software: :api:`stem.version.Version` or ``None``
+ :ivar software: The OR version portion of the ``platform`` line.
+ :type os: str or None
+ :ivar os: The OS portion of the ``platform`` line.
+ """
+ #: (bool) If ``True``, check that the signature of the bridge's
+ #: ``@type bridge-server-descriptor`` is valid and that the signature was
+ #: created with the ``signing-key`` contained in that descriptor.
+ _checkServerDescriptorSignature = True
+
+ def __init__(self, *args, **kwargs):
+ """Create and store information for a new ``Bridge``.
+
+ .. info: For backwards compatibility, `nickname`, `ip`, and `orport`
+ must be the first, second, and third arguments, respectively. The
+ `fingerprint` and `id_digest` were previously kwargs, and are also
+ provided for backwards compatibility. New calls to
+ :meth:`__init__` *should* avoid using these kwargs, and instead
+ use the methods :meth:`updateFromNetworkStatus`,
+ :meth:`updateFromServerDescriptor`, and
+ :meth:`updateFromExtraInfoDescriptor`.
+ """
+ super(Bridge, self).__init__(*args, **kwargs)
+
+ self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
+ self.dirPort = 0 # ``DirPort`` set to ``0``
+ self.orAddresses = []
+ self.transports = []
+ self.flags = Flags()
+ self.hibernating = False
+ self._blockedIn = {}
+
+ self.bandwidth = None
+ self.bandwidthAverage = None
+ self.bandwidthBurst = None
+ self.bandwidthObserved = None
+
+ self.contact = None
+ self.family = None
+ self.platform = None
+ self.software = None
+ self.os = None
+ self.uptime = None
+ self.bridgeIPs = None
+
+ self.onionKey = None
+ self.ntorOnionKey = None
+ self.signingKey = None
+
+ self.descriptors = {'networkstatus': None,
+ 'server': None,
+ 'extrainfo': None}
+
+ #: The hash digest of this bridge's ``@type bridge-server-descriptor``,
+ #: as signed (but not including the signature). This is found in the
+ #: 'r'-line of this bridge's ``@type bride-networkstatus`` document,
+ #: however it is stored here re-encoded from base64 into hexadecimal,
+ #: and converted to uppercase.
+ self.descriptorDigest = None
+ self.extrainfoDigest = None
+
+ def __str__(self):
+ """Return a pretty string representation that identifies this Bridge.
+
+ .. warning:: With safelogging disabled, the returned string contains
+ the bridge's fingerprint, which should be handled with care.
+
+ If safelogging is enabled, the returned string will have the SHA-1
+ hash of the bridge's fingerprint (a.k.a. a hashed fingerprint).
+
+ Hashed fingerprints will be prefixed with ``'$$'``, and the real
+ fingerprints are prefixed with ``'$'``.
+
+ :rtype: str
+ :returns: A string in the form:
+ :data:`nickname```.$``:data:`fingerprint`.
+ """
+ nickname = self.nickname if self.nickname else 'Unnamed'
+ prefix = '$'
+ separator = "~"
+ fingerprint = self.fingerprint
+
+ if safelog.safe_logging:
+ prefix = '$$'
+ if fingerprint:
+ fingerprint = hashlib.sha1(fingerprint).hexdigest().upper()
+
+ if not fingerprint:
+ fingerprint = '0' * 40
+
+ return prefix + fingerprint + separator + nickname
+
+ def _checkServerDescriptor(self, descriptor):
+ # If we're parsing the server-descriptor, require a networkstatus
+ # document:
+ if not self.descriptors['networkstatus']:
+ raise ServerDescriptorWithoutNetworkstatus(
+ ("We received a server-descriptor for bridge '%s' which has "
+ "no corresponding networkstatus document.") %
+ descriptor.fingerprint)
+
+ ns = self.descriptors['networkstatus']
+
+ # We must have the digest of the server-descriptor from the
+ # networkstatus document:
+ if not self.descriptorDigest:
+ raise MissingServerDescriptorDigest(
+ ("The server-descriptor digest was missing from networkstatus "
+ "document for bridge '%s'.") % descriptor.fingerprint)
+
+ digested = descriptor.digest()
+ # The digested server-descriptor must match the digest reported by the
+ # BridgeAuthority in the bridge's networkstatus document:
+ if not self.descriptorDigest == digested:
+ raise ServerDescriptorDigestMismatch(
+ ("The server-descriptor digest for bridge '%s' doesn't match "
+ "the digest reported by the BridgeAuthority in the "
+ "networkstatus document: \n"
+ "Digest reported in networkstatus: %s\n"
+ "Actual descriptor digest: %s\n") %
+ (descriptor.fingerprint, self.descriptorDigest, digested))
+
+ def _constructBridgeLine(self, addrport, includeFingerprint=True,
+ bridgePrefix=False):
+ """Construct a :term:`Bridge Line` from an (address, port) tuple.
+
+ :param tuple addrport: A 3-tuple of ``(address, port, ipversion)``
+ where ``address`` is a string, ``port`` is an integer, and
+ ``ipversion`` is a integer (``4`` or ``6``).
+ :param bool includeFingerprint: If ``True``, include the
+ ``fingerprint`` of this :class:`Bridge` in the returned bridge
+ line.
+ :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
+ with ``'Bridge '``.
+ :rtype: string
+ :returns: A bridge line suitable for adding into a ``torrc`` file or
+ Tor Launcher.
+ """
+ if not addrport:
+ return
+
+ address, port, version = addrport
+
+ if not address or not port:
+ return
+
+ bridgeLine = []
+
+ if bridgePrefix:
+ bridgeLine.append('Bridge')
+
+ if version == 4:
+ bridgeLine.append("%s:%d" % (str(address), port))
+ elif version == 6:
+ bridgeLine.append("[%s]:%d" % (str(address), port))
+
+ if includeFingerprint:
+ bridgeLine.append("%s" % self.fingerprint)
+
+ return ' '.join(bridgeLine)
+
+ @classmethod
+ def _getBlockKey(cls, address, port):
+ """Format an **address**:**port** pair appropriately for use as a key
+ in the :data:`_blockedIn` dictionary.
+
+ :param address: An IP address of this :class:`Bridge` or one of its
+ :data:`transports`.
+ :param port: A port.
+ :rtype: str
+ :returns: A string in the form ``"ADDRESS:PORT"`` for IPv4 addresses,
+ and ``"[ADDRESS]:PORT`` for IPv6.
+ """
+ if isIPv6(str(address)):
+ key = "[%s]:%s" % (address, port)
+ else:
+ key = "%s:%s" % (address, port)
+
+ return key
+
+ def _getTransportForRequest(self, bridgeRequest):
+ """If a transport was requested, return the correlated
+ :term:`Bridge Line` based upon the client identifier in the
+ **bridgeRequest**.
+
+ .. warning:: If this bridge doesn't have any of the requested
+ pluggable transport type (optionally, not blocked in whichever
+ countries the user doesn't want their bridges to be blocked in),
+ then this method returns ``None``. This should only happen
+ rarely, because the bridges are filtered into the client's
+ hashring based on the **bridgeRequest** options.
+
+ :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
+ :param bridgeRequest: A ``BridgeRequest`` which stores all of the
+ client-specified options for which type of bridge they want to
+ receive.
+ :raises PluggableTransportUnavailable: if this bridge doesn't have any
+ of the requested pluggable transport type. This shouldn't happen
+ because the bridges are filtered into the client's hashring based
+ on the **bridgeRequest** options, however, this is useful in the
+ unlikely event that it does happen, so that the calling function
+ can fetch an additional bridge from the hashring as recompense for
+ what would've otherwise been a missing :term:`Bridge Line`.
+ :rtype: str or ``None``
+ :returns: If no transports were requested, return ``None``, otherwise
+ return a :term:`Bridge Line` for the requested pluggable transport
+ type.
+ """
+ desired = bridgeRequest.justOnePTType()
+ ipVersion = bridgeRequest.ipVersion
+
+ logging.info("Bridge %s answering request for %s transport..." %
+ (self, desired))
+
+ # Filter all this Bridge's ``transports`` according to whether or not
+ # their ``methodname`` matches the requested transport:
+ transports = filter(lambda pt: pt.methodname == desired, self.transports)
+ # Filter again for whichever of IPv4 or IPv6 was requested:
+ transports = filter(lambda pt: pt.address.version == ipVersion, transports)
+
+ if not transports:
+ raise PluggableTransportUnavailable(
+ ("Client requested transport %s, but bridge %s doesn't "
+ "have any of that transport!") % (desired, self))
+
+ unblocked = []
+ for pt in transports:
+ if not sum([self.transportIsBlockedIn(cc, pt.methodname)
+ for cc in bridgeRequest.notBlockedIn]):
+ unblocked.append(pt)
+
+ if unblocked:
+ position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
+ return transports[position % len(unblocked)]
+ else:
+ logging.warn(("Client requested transport %s%s, but bridge %s "
+ "doesn't have any of that transport!") %
+ (desired, " not blocked in %s" %
+ " ".join(bridgeRequest.notBlockedIn)
+ if bridgeRequest.notBlockedIn else "", self))
+
+ def _getVanillaForRequest(self, bridgeRequest):
+ """If vanilla bridges were requested, return the assigned
+ :term:`Bridge Line` based upon the client identifier in the
+ **bridgeRequest**.
+
+ :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
+ :param bridgeRequest: A ``BridgeRequest`` which stores all of the
+ client-specified options for which type of bridge they want to
+ receive.
+ :rtype: str or ``None``
+ :returns: If no transports were requested, return ``None``, otherwise
+ return a :term:`Bridge Line` for the requested pluggable transport
+ type.
+ """
+ logging.info(
+ "Bridge %s answering request for IPv%s vanilla address..." %
+ (self, bridgeRequest.ipVersion))
+
+ addresses = []
+
+ for address, port, version in self.allVanillaAddresses:
+ # Filter ``allVanillaAddresses`` by whether IPv4 or IPv6 was requested:
+ if version == bridgeRequest.ipVersion:
+ # Determine if the address is blocked in any of the country
+ # codes. Because :meth:`addressIsBlockedIn` returns a bool,
+ # we get a list like: ``[True, False, False, True]``, and
+ # because bools are ints, they may be summed. What we care
+ # about is that there are no ``True``s, for any country code,
+ # so we check that the sum is zero (meaning the list was full
+ # of ``False``s).
+ #
+ # XXX Do we want to add a method for this construct?
+ if not sum([self.addressIsBlockedIn(cc, address, port)
+ for cc in bridgeRequest.notBlockedIn]):
+ addresses.append((address, port, version))
+
+ if addresses:
+ # Use the client's unique data to HMAC them into their position in
+ # the hashring of filtered bridges addresses:
+ position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
+ vanilla = addresses[position % len(addresses)]
+ logging.info("Got vanilla bridge for client.")
+
+ return vanilla
+
+ def _updateORAddresses(self, orAddresses):
+ """Update this :class:`Bridge`'s :data:`orAddresses` attribute from a
+ 3-tuple (i.e. as Stem creates when parsing descriptors).
+
+ :param tuple orAddresses: A 3-tuple of: an IP address, a port number,
+ and a boolean (``False`` if IPv4, ``True`` if IPv6).
+ :raises FutureWarning: if any IPv4 addresses are found. As of
+ tor-0.2.5, only IPv6 addresses should be found in a descriptor's
+ `ORAddress` line.
+ """
+ for (address, port, ipVersion) in orAddresses:
+ version = 6
+ if not ipVersion: # `False` means IPv4; `True` means IPv6.
+ # See https://bugs.torproject.org/9380#comment:27
+ warnings.warn(FutureWarning((
+ "Got IPv4 address in 'a'/'or-address' line! Descriptor "
+ "format may have changed!")))
+ version = 4
+
+ validatedAddress = isIPAddress(address, compressed=False)
+ if validatedAddress:
+ self.orAddresses.append( (validatedAddress, port, version,) )
+
+ @property
+ def allVanillaAddresses(self):
+ """Get all valid, non-PT address:port pairs for this bridge.
+
+ :rtype: list
+ :returns: All of this bridge's ORAddresses, as well as its ORPort IP
+ address and port.
+ """
+ # Force deep-copying of the orAddresses. Otherwise, the later use of
+ # ``addresses.append()`` is both non-reentrant and non-idempotent, as
+ # it would change the value of ``Bridge.orAddresses``, as well as
+ # append a (possibly updated, if ``Bridge.address`` or
+ # ``Bridge.orPort`` changed!) new copy of the bridge's primary
+ # ORAddress each time this property is called.
+ addresses = self.orAddresses[:]
+
+ # Add the default ORPort address. It will always be IPv4, otherwise
+ # Stem should have raised a ValueError during parsing. But for
+ # testability, check which type it is:
+ version = 4
+ if isIPv6(self.address):
+ version = 6
+
+ addresses.append((self.address, self.orPort, version))
+
+ return addresses
+
+ def assertOK(self):
+ """Perform some additional validation on this bridge's info.
+
+ We require that:
+
+ 1. Any IP addresses contained in :data:`orAddresses` are valid,
+ according to :func:`~bridgedb.parse.addr.isValidIP`.
+
+ 2. Any ports in :data:`orAddresses` are between ``1`` and ``65535``
+ (inclusive).
+
+ 3. All IP version numbers given in :data:`orAddresses` are either
+ ``4`` or ``6``.
+
+ .. todo:: This should probably be reimplemented as a property that
+ automatically sanitises the values for each ORAddress, as is done
+ for :property:`bridgedb.bridges.BridgeAddressBase.address` and
+ :property:`bridgedb.bridges.BridgeBase.orPort`.
+
+ :raises MalformedBridgeInfo: if something was found to be malformed or
+ invalid.
+ """
+ malformed = []
+
+ for (address, port, version) in self.orAddresses:
+ if not isValidIP(address):
+ malformed.append("Invalid ORAddress address: '%s'" % address)
+ if not (0 <= port <= 65535):
+ malformed.append("Invalid ORAddress port: '%d'" % port)
+ if not version in (4, 6):
+ malformed.append("Invalid ORAddress IP version: %r" % version)
+
+ if malformed:
+ raise MalformedBridgeInfo('\n'.join(malformed))
+
+ def getBridgeLine(self, bridgeRequest, includeFingerprint=True,
+ bridgePrefix=False):
+ """Return a valid :term:`Bridge Line` for a client to give to Tor
+ Launcher or paste directly into their ``torrc``.
+
+ This is a helper method to call either :meth:`_getTransportForRequest`
+ or :meth:`_getVanillaForRequest` depending on whether or not any
+ :class:`PluggableTransport`s were requested in the
+ :class:`bridgeRequest <bridgedb bridgerequest.BridgeRequestBase>`, and
+ then construct the :term:`Bridge Line` accordingly.
+
+ :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
+ :param bridgeRequest: A ``BridgeRequest`` which stores all of the
+ client-specified options for which type of bridge they want to
+ receive.
+ :param bool includeFingerprint: If ``True``, include the
+ ``fingerprint`` of this :class:`Bridge` in the returned bridge
+ line.
+ :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
+ with ``'Bridge '``.
+ """
+ if not bridgeRequest.isValid():
+ logging.info("Bridge request was not valid. Dropping request.")
+ return # XXX raise error perhaps?
+
+ bridgeLine = None
+
+ if bridgeRequest.transports:
+ pt = self._getTransportForRequest(bridgeRequest)
+ if pt:
+ bridgeLine = pt.getTransportLine(includeFingerprint,
+ bridgePrefix)
+ else:
+ addrport = self._getVanillaForRequest(bridgeRequest)
+ bridgeLine = self._constructBridgeLine(addrport,
+ includeFingerprint,
+ bridgePrefix)
+ return bridgeLine
+
+ def _addBlockByKey(self, key, countryCode):
+ """Create or append to the list of blocked countries for a **key**.
+
+ :param str key: The key to lookup in the :data:`Bridge._blockedIn`
+ dictionary. This should be in the form returned by
+ :classmethod:`_getBlockKey`.
+ :param str countryCode: A two-character country code specifier.
+ """
+ if self._blockedIn.has_key(key):
+ self._blockedIn[key].append(countryCode.lower())
+ else:
+ self._blockedIn[key] = [countryCode.lower(),]
+
+ def addressIsBlockedIn(self, countryCode, address, port):
+ """Determine if a specific (address, port) tuple is blocked in
+ **countryCode**.
+
+ :param str countryCode: A two-character country code specifier.
+ :param str address: An IP address (presumedly one used by this
+ bridge).
+ :param int port: A port.
+ :rtype: bool
+ :returns: ``True`` if the **address**:**port** pair is blocked in
+ **countryCode**, ``False`` otherwise.
+ """
+ key = self._getBlockKey(address, port)
+
+ try:
+ if countryCode.lower() in self._blockedIn[key]:
+ logging.info("Vanilla address %s of bridge %s blocked in %s."
+ % (key, self, countryCode.lower()))
+ return True
+ except KeyError:
+ return False # That address:port pair isn't blocked anywhere
+
+ return False
+
+ def transportIsBlockedIn(self, countryCode, methodname):
+ """Determine if any of a specific type of pluggable transport which
+ this bridge might be running is blocked in a specific country.
+
+ :param str countryCode: A two-character country code specifier.
+ :param str methodname: The type of pluggable transport to check,
+ i.e. ``'obfs3'``.
+ :rtype: bool
+ :returns: ``True`` if any address:port pair which this bridge is
+ running a :class:`PluggableTransport` on is blocked in
+ **countryCode**, ``False`` otherwise.
+ """
+ for pt in self.transports:
+ if pt.methodname == methodname.lower():
+ if self.addressIsBlockedIn(countryCode, pt.address, pt.port):
+ logging.info("Transport %s of bridge %s is blocked in %s."
+ % (pt.methodname, self, countryCode))
+ return True
+ return False
+
+ def isBlockedIn(self, countryCode):
+ """Determine, according to our stored bridge reachability reports, if
+ any of the address:port pairs used by this :class:`Bridge` or it's
+ :data:`transports` are blocked in **countryCode**.
+
+ :param str countryCode: A two-character country code specifier.
+ :rtype: bool
+ :returns: ``True`` if at least one address:port pair used by this
+ bridge is blocked in **countryCode**; ``False`` otherwise.
+ """
+ # Check all supported pluggable tranport types:
+ for methodname in self.supportedTransportTypes:
+ if self.transportIsBlockedIn(countryCode.lower(), methodname):
+ return True
+
+ for address, port, version in self.allVanillaAddresses:
+ if self.addressIsBlockedIn(countryCode.lower(), address, port):
+ return True
+
+ return False
+
+ def setBlockedIn(self, countryCode, address=None, port=None, methodname=None):
+ """Mark this :class:`Bridge` as being blocked in **countryCode**.
+
+ By default, if called with no parameters other than a **countryCode**,
+ we'll mark all this :class:`Bridge`'s :data:`allVanillaAddresses` and
+ :data:`transports` as being blocked.
+
+ Otherwise, we'll filter on any and all parameters given.
+
+ If only a **methodname** is given, then we assume that all
+ :data:`transports` with that **methodname** are blocked in
+ **countryCode**. If the methodname is ``"vanilla"``, then we assume
+ each address in data:`allVanillaAddresses` is blocked.
+
+ :param str countryCode: A two-character country code specifier.
+ :param address: An IP address of this Bridge or one of its
+ :data:`transports`.
+ :param port: A specific port that is blocked, if available. If the
+ **port** is ``None``, then any address this :class:`Bridge` or its
+ :class:`PluggableTransport`s has that matches the given **address**
+ will be marked as block, regardless of its port. This parameter
+ is ignored unless an **address** is given.
+ :param str methodname: A :data:`PluggableTransport.methodname` to
+ match. Any remaining :class:`PluggableTransport`s from
+ :data:`transports` which matched the other parameters and now also
+ match this **methodname** will be marked as being blocked in
+ **countryCode**.
+ """
+ vanillas = self.allVanillaAddresses
+ transports = self.transports
+
+ if methodname:
+ # Don't process the vanilla if we weren't told to do so:
+ if not (methodname == 'vanilla') and not (address or port):
+ vanillas = []
+
+ transports = filter(lambda pt: methodname == pt.methodname, transports)
+
+ if address:
+ vanillas = filter(lambda ip: str(address) == str(ip[0]), vanillas)
+ transports = filter(lambda pt: str(address) == str(pt.address), transports)
+
+ if port:
+ vanillas = filter(lambda ip: int(port) == int(ip[1]), vanillas)
+ transports = filter(lambda pt: int(port) == int(pt.port), transports)
+
+ for addr, port, _ in vanillas:
+ key = self._getBlockKey(addr, port)
+ logging.info("Vanilla address %s for bridge %s is now blocked in %s."
+ % (key, self, countryCode))
+ self._addBlockByKey(key, countryCode)
+
+ for transport in transports:
+ key = self._getBlockKey(transport.address, transport.port)
+ logging.info("Transport %s %s for bridge %s is now blocked in %s."
+ % (transport.methodname, key, self, countryCode))
+ self._addBlockByKey(key, countryCode)
+ transport._blockedIn[key] = self._blockedIn[key]
+
+ def getDescriptorLastPublished(self):
+ """Get the timestamp for when this bridge's last known server
+ descriptor was published.
+
+ :rtype: :type:`datetime.datetime` or ``None``
+ :returns: A datetime object representing the timestamp of when the
+ last known ``@type bridge-server-descriptor`` was published, or
+ ``None`` if we have never seen a server descriptor for this
+ bridge.
+ """
+ return getattr(self.descriptors['server'], 'published', None)
+
+ def getExtrainfoLastPublished(self):
+ """Get the timestamp for when this bridge's last known extrainfo
+ descriptor was published.
+
+ :rtype: :type:`datetime.datetime` or ``None``
+ :returns: A datetime object representing the timestamp of when the
+ last known ``@type bridge-extrainfo`` descriptor was published, or
+ ``None`` if we have never seen an extrainfo descriptor for this
+ bridge.
+ """
+ return getattr(self.descriptors['extrainfo'], 'published', None)
+
+ def getNetworkstatusLastPublished(self):
+ """Get the timestamp for when this bridge's last known networkstatus
+ descriptor was published.
+
+ :rtype: :type:`datetime.datetime` or ``None``
+ :returns: A datetime object representing the timestamp of when the
+ last known ``@type networkstatus-bridge`` document was published,
+ or ``None`` if we have never seen a networkstatus document for
+ this bridge.
+ """
+ return getattr(self.descriptors['networkstatus'], 'published', None)
+
+ @property
+ def supportedTransportTypes(self):
+ """A deduplicated list of all the :data:`PluggableTranport.methodname`s
+ which this bridge supports.
+ """
+ return list(set([pt.methodname for pt in self.transports]))
+
+ def updateFromNetworkStatus(self, descriptor, ignoreNetworkstatus=False):
+ """Update this bridge's attributes from a parsed networkstatus
+ document.
+
+ :type descriptor:
+ :api:`stem.descriptors.router_status_entry.RouterStatusEntry`
+ :param descriptor: The networkstatus document for this bridge.
+ :param bool ignoreNetworkstatus: If ``True``, then ignore most of the
+ information in the networkstatus document.
+ """
+ self.descriptors['networkstatus'] = descriptor
+
+ # These fields are *only* found in the networkstatus document:
+ self.flags.update(descriptor.flags)
+ self.descriptorDigest = descriptor.digest
+
+ if not ignoreNetworkstatus:
+ self.bandwidth = descriptor.bandwidth
+
+ # These fields are also found in the server-descriptor. We will prefer
+ # to use the information taken later from the server-descriptor
+ # because it is signed by the bridge. However, for now, we harvest all
+ # the info we can:
+ self.fingerprint = descriptor.fingerprint
+
+ if not ignoreNetworkstatus:
+ self.nickname = descriptor.nickname
+ self.address = descriptor.address
+ self.orPort = descriptor.or_port
+ self._updateORAddresses(descriptor.or_addresses)
+
+ def updateFromServerDescriptor(self, descriptor, ignoreNetworkstatus=False):
+ """Update this bridge's info from an ``@type bridge-server-descriptor``.
+
+ .. info::
+ If :func:`~bridgedb.parse.descriptor.parseServerDescriptorFile` is
+ called with ``validate=True``, then Stem will handle checking that
+ the ``signing-key`` hashes to the ``fingerprint``. Stem will also
+ check that the ``router-signature`` on the descriptor is valid,
+ was created with the ``signing-key``, and is a signature of the
+ correct digest of the descriptor document (it recalculates the
+ digest for the descriptor to ensure that the signed one and the
+ actual digest match).
+
+ :type descriptor:
+ :api:`stem.descriptor.server_descriptor.RelayDescriptor`
+ :param descriptor: The bridge's server descriptor to gather data from.
+ :raises MalformedBridgeInfo: If this Bridge has no corresponding
+ networkstatus entry, or its **descriptor** digest didn't match the
+ expected digest (from the networkstatus entry).
+ """
+ if ignoreNetworkstatus:
+ try:
+ self._checkServerDescriptor(descriptor)
+ except (ServerDescriptorWithoutNetworkstatus,
+ MissingServerDescriptorDigest,
+ ServerDescriptorDigestMismatch) as ignored:
+ logging.warn(ignored)
+ else:
+ self._checkServerDescriptor(descriptor)
+
+ self.descriptors['server'] = descriptor
+
+ # Replace the values which we harvested from the networkstatus
+ # descriptor, because that one isn't signed with the bridge's identity
+ # key.
+ self.fingerprint = descriptor.fingerprint
+ self.address = descriptor.address
+ self.nickname = descriptor.nickname
+ self.orPort = descriptor.or_port
+ self._updateORAddresses(descriptor.or_addresses)
+ self.hibernating = descriptor.hibernating
+
+ self.onionKey = descriptor.onion_key
+ self.ntorOnionKey = descriptor.ntor_onion_key
+ self.signingKey = descriptor.signing_key
+
+ self.bandwidthAverage = descriptor.average_bandwidth
+ self.bandwidthBurst = descriptor.burst_bandwidth
+ self.bandwidthObserved = descriptor.observed_bandwidth
+
+ self.contact = descriptor.contact
+ self.family = descriptor.family
+ self.platform = descriptor.platform
+ self.software = descriptor.tor_version
+ self.os = descriptor.operating_system
+ self.uptime = descriptor.uptime
+
+ self.extrainfoDigest = descriptor.extra_info_digest
+
+ def _verifyExtraInfoSignature(self, descriptor):
+ """Verify the signature on the contents of this :class:`Bridge`'s
+ ``@type bridge-extrainfo`` descriptor.
+
+ :type descriptor:
+ :api:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
+ :param descriptor: An ``@type bridge-extrainfo`` descriptor for this
+ :class:`Bridge`, parsed with Stem.
+ :raises InvalidExtraInfoSignature: if the signature was invalid,
+ missing, malformed, or couldn't be verified successfully.
+ :returns: ``None`` if the signature was valid and verifiable.
+ """
+ # The blocksize is always 128 bits for a 1024-bit key
+ BLOCKSIZE = 128
+
+ TOR_SIGNING_KEY_HEADER = u'-----BEGIN RSA PUBLIC KEY-----\n'
+ TOR_SIGNING_KEY_FOOTER = u'-----END RSA PUBLIC KEY-----'
+ TOR_BEGIN_SIGNATURE = u'-----BEGIN SIGNATURE-----\n'
+ TOR_END_SIGNATURE = u'-----END SIGNATURE-----\n'
+
+ logging.info("Verifying extrainfo signature for %s..." % self)
+
+ # Get the bytes of the descriptor signature without the headers:
+ document, signature = descriptor.get_bytes().split(TOR_BEGIN_SIGNATURE)
+ signature = signature.replace(TOR_END_SIGNATURE, '')
+ signature = signature.replace('\n', '')
+ signature = signature.strip()
+
+ try:
+ # Get the ASN.1 sequence:
+ sequence = asn1.DerSequence()
+
+ key = self.signingKey
+ key = key.strip(TOR_SIGNING_KEY_HEADER)
+ key = key.strip(TOR_SIGNING_KEY_FOOTER)
+ key = key.replace('\n', '')
+ key = base64.b64decode(key)
+
+ sequence.decode(key)
+
+ modulus = sequence[0]
+ publicExponent = sequence[1]
+
+ # The public exponent of RSA signing-keys should always be 65537,
+ # but we're not going to turn them down if they want to use a
+ # potentially dangerous exponent.
+ if publicExponent != 65537: # pragma: no cover
+ logging.warn("Odd RSA exponent in signing-key for %s: %s" %
+ (self, publicExponent))
+
+ # Base64 decode the signature:
+ signatureDecoded = base64.b64decode(signature)
+
+ # Convert the signature to a long:
+ signatureLong = bytes_to_long(signatureDecoded)
+
+ # Decrypt the long signature with the modulus and public exponent:
+ decryptedInt = pow(signatureLong, publicExponent, modulus)
+
+ # Then convert it back to a byte array:
+ decryptedBytes = long_to_bytes(decryptedInt, BLOCKSIZE)
+
+ # Remove the PKCS#1 padding from the signature:
+ unpadded = removePKCS1Padding(decryptedBytes)
+
+ # This is the hexadecimal SHA-1 hash digest of the descriptor document
+ # as it was signed:
+ signedDigest = codecs.encode(unpadded, 'hex_codec')
+ actualDigest = hashlib.sha1(document).hexdigest()
+
+ except Exception as error:
+ logging.debug("Error verifying extrainfo signature: %s" % error)
+ raise InvalidExtraInfoSignature(
+ "Extrainfo signature for %s couldn't be decoded: %s" %
+ (self, signature))
+ else:
+ if signedDigest != actualDigest:
+ raise InvalidExtraInfoSignature(
+ ("The extrainfo digest signed by bridge %s didn't match the "
+ "actual digest.\nSigned digest: %s\nActual digest: %s") %
+ (self, signedDigest, actualDigest))
+ else:
+ logging.info("Extrainfo signature was verified successfully!")
+
+ def updateFromExtraInfoDescriptor(self, descriptor, verify=True):
+ """Update this bridge's information from an extrainfo descriptor.
+
+ Stem's
+ :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
+ parses extrainfo ``transport`` lines into a dictionary with the
+ following structure::
+
+ {u'obfs2': (u'34.230.223.87', 37339, []),
+ u'obfs3': (u'34.230.223.87', 37338, []),
+ u'obfs4': (u'34.230.223.87', 37341, [
+ (u'iat-mode=0,'
+ u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
+ u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
+ u'scramblesuit': (u'34.230.223.87', 37340, [
+ u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
+
+
+ .. todo:: The ``transport`` attribute of Stem's
+ ``BridgeExtraInfoDescriptor`` class is a dictionary that uses the
+ Pluggable Transport's eype as the keys. Meaning that if a bridge
+ were to offer four instances of ``obfs3``, only one of them would
+ get to us through Stem. This might pose a problem someday.
+
+ :type descriptor:
+ :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
+ :param descriptor: DOCDOC
+ :param bool verify: If ``True``, check that the ``router-signature``
+ on the extrainfo **descriptor** is a valid signature from
+ :data:`signingkey`.
+ """
+ if verify:
+ try:
+ self._verifyExtraInfoSignature(descriptor)
+ except InvalidExtraInfoSignature as error:
+ logging.warn(error)
+ logging.info(("Tossing extrainfo descriptor due to an invalid "
+ "signature."))
+ return
+
+ self.descriptors['extrainfo'] = descriptor
+ self.bridgeIPs = descriptor.bridge_ips
+
+ oldTransports = self.transports[:]
+
+ for methodname, (address, port, args) in descriptor.transport.items():
+ updated = False
+ # See if we already know about this transport. If so, update its
+ # info; otherwise, add a new transport below.
+ for pt in self.transports:
+ if pt.methodname == methodname:
+
+ logging.info("Found old %s transport for %s... Updating..."
+ % (methodname, self))
+
+ if not (address == str(pt.address)) and (port == pt.port):
+ logging.info(("Address/port for %s transport for "
+ "%s changed: old=%s:%s new=%s:%s")
+ % (methodname, self, pt.address, pt.port,
+ address, port))
+
+ original = pt
+ try:
+ pt.updateFromStemTransport(str(self.fingerprint),
+ methodname,
+ (address, port, args,))
+ except MalformedPluggableTransport as error:
+ logging.info(str(error))
+ else:
+ oldTransports.remove(original)
+
+ updated = True
+ break
+
+ if updated:
+ continue
+ else:
+ # We didn't update it. It must be a new transport for this
+ # bridges that we're hearing about for the first time, so add
+ # it:
+ logging.info(
+ "Received new %s pluggable transport for bridge %s."
+ % (methodname, self))
+ try:
+ transport = PluggableTransport()
+ transport.updateFromStemTransport(str(self.fingerprint),
+ methodname,
+ (address, port, args,))
+ self.transports.append(transport)
+ except MalformedPluggableTransport as error:
+ logging.info(str(error))
+
+ # These are the pluggable transports which we knew about before, which
+ # however were not updated in this descriptor, ergo the bridge must
+ # not have them any more:
+ for pt in oldTransports:
+ logging.info("Removing dead transport for bridge %s: %s %s:%s %s" %
+ (self, pt.methodname, pt.address, pt.port, pt.arguments))
+ self.transports.remove(pt)
diff --git a/bridgedb/captcha.py b/bridgedb/captcha.py
new file mode 100644
index 0000000..d2fbcc1
--- /dev/null
+++ b/bridgedb/captcha.py
@@ -0,0 +1,392 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# Aaron Gibson 0x2C4B239DD876C9F6 <aagbsn at torproject.org>
+# Nick Mathewson 0x21194EBB165733EA <nickm at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""This module implements various methods for obtaining or creating CAPTCHAs.
+
+**Module Overview:**
+
+::
+
+ captcha
+ |- CaptchaExpired - Raised if a solution is given for a stale CAPTCHA.
+ |- CaptchaKeyError - Raised if a CAPTCHA system's keys are invalid/missing.
+ |- GimpCaptchaError - Raised when a Gimp CAPTCHA can't be retrieved.
+ |
+ \_ ICaptcha - Zope Interface specification for a generic CAPTCHA.
+ |
+ Captcha - Generic base class implementation for obtaining a CAPTCHA.
+ | |- image - The CAPTCHA image.
+ | |- challenge - A unique string associated with this CAPTCHA image.
+ | |- publicKey - The public key for this CAPTCHA system.
+ | |- secretKey - The secret key for this CAPTCHA system.
+ | \_ get() - Get a new pair of CAPTCHA image and challenge strings.
+ |
+ |- ReCaptcha - Obtain reCaptcha images and challenge strings.
+ | \_ get() - Request an image and challenge from a reCaptcha API server.
+ |
+ \_ GimpCaptcha - Class for obtaining a CAPTCHA from a local cache.
+ |- hmacKey - A client-specific key for HMAC generation.
+ |- cacheDir - The path to the local CAPTCHA cache directory.
+ |- sched - A class for timing out CAPTCHAs after an interval.
+ \_ get() - Get a CAPTCHA image from the cache and create a challenge.
+
+..
+
+There are two types of CAPTCHAs which BridgeDB knows how to serve: those
+obtained by from a reCaptcha_ API server with
+:class:`~bridgedb.captcha.Raptcha`, and those which have been generated with
+gimp-captcha_ and then cached locally.
+
+.. _reCaptcha : https://code.google.com/p/recaptcha/
+.. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
+"""
+
+from base64 import urlsafe_b64encode
+from base64 import urlsafe_b64decode
+
+import logging
+import random
+import os
+import time
+import urllib2
+
+from BeautifulSoup import BeautifulSoup
+
+from zope.interface import Interface, Attribute, implements
+
+from bridgedb import crypto
+from bridgedb import schedule
+from bridgedb.txrecaptcha import API_SSL_SERVER
+
+
+class CaptchaExpired(ValueError):
+ """Raised when a client's CAPTCHA is too stale."""
+
+class CaptchaKeyError(Exception):
+ """Raised if a CAPTCHA system's keys are invalid or missing."""
+
+class GimpCaptchaError(Exception):
+ """General exception raised when a Gimp CAPTCHA cannot be retrieved."""
+
+
+class ICaptcha(Interface):
+ """Interface specification for CAPTCHAs."""
+
+ image = Attribute(
+ "A string containing the contents of a CAPTCHA image file.")
+ challenge = Attribute(
+ "A unique string associated with the dispursal of this CAPTCHA.")
+ publicKey = Attribute(
+ "A public key used for encrypting CAPTCHA challenge strings.")
+ secretKey = Attribute(
+ "A private key used for decrypting challenge strings during CAPTCHA"
+ "solution verification.")
+
+ def get():
+ """Retrieve a new CAPTCHA image."""
+
+
+class Captcha(object):
+ """A generic CAPTCHA base class.
+
+ :ivar str image: The CAPTCHA image.
+ :ivar str challenge: A challenge string which should permit checking of
+ the client's CAPTCHA solution in some manner. In stateless protocols
+ such as HTTP, this should be passed along to the client with the
+ CAPTCHA image.
+ :ivar publicKey: A public key used for encrypting CAPTCHA challenge strings.
+ :ivar secretKey: A private key used for decrypting challenge strings during
+ CAPTCHA solution verification.
+ """
+ implements(ICaptcha)
+
+ def __init__(self, publicKey=None, secretKey=None):
+ """Obtain a new CAPTCHA for a client."""
+ self.image = None
+ self.challenge = None
+ self.publicKey = publicKey
+ self.secretKey = secretKey
+
+ def get(self):
+ """Retrieve a new CAPTCHA image and its associated challenge string.
+
+ The image and challenge will be stored as :ivar:`image` and
+ :ivar:`challenge, respectively.
+ """
+ self.image = None
+ self.challenge = None
+
+
+class ReCaptcha(Captcha):
+ """A CAPTCHA obtained from a remote reCaptcha_ API server.
+
+ :ivar str image: The CAPTCHA image.
+ :ivar str challenge: The ``'recaptcha_challenge_response'`` HTTP form
+ field to pass to the client, along with the CAPTCHA image. See
+ :doc:`BridgeDB's captcha.html <templates/captcha.html>` Mako_ template
+ for an example usage.
+ :ivar str publicKey: The public reCaptcha API key.
+ :ivar str secretKey: The private reCaptcha API key.
+
+ .. _reCaptcha: https://code.google.com/p/recaptcha/
+ .. _Mako: http://docs.makotemplates.org/en/latest/syntax.html#page
+ """
+
+ def __init__(self, publicKey=None, secretKey=None):
+ """Create a new ReCaptcha CAPTCHA.
+
+ :param str publicKey: The public reCaptcha API key.
+ :param str secretKey: The private reCaptcha API key.
+ """
+ super(ReCaptcha, self).__init__(publicKey=publicKey,
+ secretKey=secretKey)
+
+ def get(self):
+ """Retrieve a CAPTCHA from the reCaptcha API server.
+
+ This simply requests a new CAPTCHA from
+ ``recaptcha.client.captcha.API_SSL_SERVER`` and parses the returned
+ HTML to extract the CAPTCHA image and challenge string. The image is
+ stored at ``ReCaptcha.image`` and the challenge string at
+ ``ReCaptcha.challenge``.
+
+ :raises CaptchaKeyError: If either the :ivar:`publicKey` or
+ :ivar:`secretKey` are missing.
+ :raises HTTPError: If the server returned any HTTP error status code.
+ """
+ if not self.publicKey or not self.secretKey:
+ raise CaptchaKeyError('You must supply recaptcha API keys')
+
+ urlbase = API_SSL_SERVER
+ form = "/noscript?k=%s" % self.publicKey
+
+ # Extract and store image from recaptcha
+ html = urllib2.urlopen(urlbase + form).read()
+ # FIXME: The remaining lines currently cannot be reliably unit tested:
+ soup = BeautifulSoup(html) # pragma: no cover
+ imgurl = urlbase + "/" + soup.find('img')['src'] # pragma: no cover
+ cField = soup.find( # pragma: no cover
+ 'input', {'name': 'recaptcha_challenge_field'}) # pragma: no cover
+ self.challenge = str(cField['value']) # pragma: no cover
+ self.image = urllib2.urlopen(imgurl).read() # pragma: no cover
+
+
+class GimpCaptcha(Captcha):
+ """A locally cached CAPTCHA image which was created with gimp-captcha_.
+
+ :ivar str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
+ verifying the client's solution to the CAPTCHA.
+ :ivar str publickey: A PKCS#1 OAEP-padded, public RSA key. This is used to
+ hide the correct CAPTCHA solution within the
+ ``captcha_challenge_field`` HTML form field. That form field is given
+ to the a client along with the :ivar:`image` during the initial
+ CAPTCHA request, and the client *should* give it back to us later
+ during the CAPTCHA solution verification step.
+ :ivar bytes hmacKey: A client-specific HMAC secret key.
+ :ivar str cacheDir: The local directory which pre-generated CAPTCHA images
+ have been stored in. This can be set via the ``GIMP_CAPTCHA_DIR``
+ setting in the config file.
+ :type sched: :class:`bridgedb.schedule.ScheduledInterval`
+ :ivar sched: An time interval. After this much time has passed, the
+ CAPTCHA is considered stale, and all solutions are considered invalid
+ regardless of their correctness.
+
+ .. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
+ """
+
+ sched = schedule.ScheduledInterval(30, 'minutes')
+
+ def __init__(self, publicKey=None, secretKey=None, hmacKey=None,
+ cacheDir=None):
+ """Create a ``GimpCaptcha`` which retrieves images from **cacheDir**.
+
+ :param str publickey: A PKCS#1 OAEP-padded, public RSA key, used for
+ creating the ``captcha_challenge_field`` string to give to a
+ client.
+ :param str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
+ verifying the client's solution to the CAPTCHA.
+ :param bytes hmacKey: A client-specific HMAC secret key.
+ :param str cacheDir: The local directory which pre-generated CAPTCHA
+ images have been stored in. This can be set via the
+ ``GIMP_CAPTCHA_DIR`` setting in the config file.
+ :raises GimpCaptchaError: if :ivar:`cacheDir` is not a directory.
+ :raises CaptchaKeyError: if any of :ivar:`secretKey`,
+ :ivar:`publicKey`, or :ivar:`hmacKey` are invalid or missing.
+ """
+ if not cacheDir or not os.path.isdir(cacheDir):
+ raise GimpCaptchaError("Gimp captcha cache isn't a directory: %r"
+ % cacheDir)
+ if not (publicKey and secretKey and hmacKey):
+ raise CaptchaKeyError(
+ "Invalid key supplied to GimpCaptcha: SK=%r PK=%r HMAC=%r"
+ % (secretKey, publicKey, hmacKey))
+
+ super(GimpCaptcha, self).__init__(publicKey=publicKey,
+ secretKey=secretKey)
+ self.hmacKey = hmacKey
+ self.cacheDir = cacheDir
+ self.answer = None
+
+ @classmethod
+ def check(cls, challenge, solution, secretKey, hmacKey):
+ """Check a client's CAPTCHA **solution** against the **challenge**.
+
+ :param str challenge: The contents of the
+ ``'captcha_challenge_field'`` HTTP form field.
+ :param str solution: The client's proposed solution to the CAPTCHA
+ that they were presented with.
+ :param str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
+ verifying the client's solution to the CAPTCHA.
+ :param bytes hmacKey: A private key for generating HMACs.
+ :raises CaptchaExpired: if the **solution** was for a stale CAPTCHA.
+ :rtype: bool
+ :returns: ``True`` if the CAPTCHA solution was correct and not
+ stale. ``False`` otherwise.
+ """
+ hmacIsValid = False
+
+ if not solution:
+ return hmacIsValid
+
+ logging.debug("Checking CAPTCHA solution %r against challenge %r"
+ % (solution, challenge))
+ try:
+ decoded = urlsafe_b64decode(challenge)
+ hmacFromBlob = decoded[:20]
+ encBlob = decoded[20:]
+ hmacNew = crypto.getHMAC(hmacKey, encBlob)
+ hmacIsValid = hmacNew == hmacFromBlob
+ except Exception:
+ return False
+ finally:
+ if hmacIsValid:
+ try:
+ answerBlob = secretKey.decrypt(encBlob)
+
+ timestamp = answerBlob[:12].lstrip('0')
+ then = cls.sched.nextIntervalStarts(int(timestamp))
+ now = int(time.time())
+ answer = answerBlob[12:]
+ except Exception as error:
+ logging.warn(error.message)
+ else:
+ # If the beginning of the 'next' interval (the interval
+ # after the one when the CAPTCHA timestamp was created)
+ # has already passed, then the CAPTCHA is stale.
+ if now >= then:
+ exp = schedule.fromUnixSeconds(then).isoformat(sep=' ')
+ raise CaptchaExpired("Solution %r was for a CAPTCHA "
+ "which already expired at %s."
+ % (solution, exp))
+ if solution.lower() == answer.lower():
+ return True
+ return False
+
+ def createChallenge(self, answer):
+ """Encrypt-then-HMAC a timestamp plus the CAPTCHA **answer**.
+
+ A challenge string consists of a URL-safe, base64-encoded string which
+ contains an ``HMAC`` concatenated with an ``ENC_BLOB``, in the
+ following form::
+
+ CHALLENGE := B64( HMAC | ENC_BLOB )
+ ENC_BLOB := RSA_ENC( ANSWER_BLOB )
+ ANSWER_BLOB := ( TIMESTAMP | ANSWER )
+
+ where
+ * ``B64`` is a URL-safe base64-encode function,
+ * ``RSA_ENC`` is the PKCS#1 RSA-OAEP encryption function,
+ * and the remaining feilds are specified as follows:
+
+ +-------------+--------------------------------------------+----------+
+ | Field | Description | Length |
+ +=============+============================================+==========+
+ | HMAC | An HMAC of the ``ENC_BLOB``, created with | 20 bytes |
+ | | the client-specific :ivar:`hmacKey`, by | |
+ | | applying :func:`~crypto.getHMAC` to the | |
+ | | ``ENC_BLOB``. | |
+ +-------------+--------------------------------------------+----------+
+ | ENC_BLOB | An encrypted ``ANSWER_BLOB``, created with | varies |
+ | | a PKCS#1 OAEP-padded RSA :ivar:`publicKey`.| |
+ +-------------+--------------------------------------------+----------+
+ | ANSWER_BLOB | Contains the concatenated ``TIMESTAMP`` | varies |
+ | | and ``ANSWER``. | |
+ +-------------+--------------------------------------------+----------+
+ | TIMESTAMP | A Unix Epoch timestamp, in seconds, | 12 bytes |
+ | | left-padded with "0"s. | |
+ +-------------+--------------------------------------------+----------+
+ | ANSWER | A string containing answer to this | 8 bytes |
+ | | CAPTCHA :ivar:`image`. | |
+ +-------------+--------------------------------------------+----------+
+
+ The steps taken to produce a ``CHALLENGE`` are then:
+
+ 1. Create a ``TIMESTAMP``, and pad it on the left with ``0``s to 12
+ bytes in length.
+
+ 2. Next, take the **answer** to this CAPTCHA :ivar:`image: and
+ concatenate the padded ``TIMESTAMP`` and the ``ANSWER``, forming
+ an ``ANSWER_BLOB``.
+
+ 3. Encrypt the resulting ``ANSWER_BLOB`` to :ivar:`publicKey` to
+ create the ``ENC_BLOB``.
+
+ 4. Use the client-specific :ivar:`hmacKey` to apply the
+ :func:`~crypto.getHMAC` function to the ``ENC_BLOB``, obtaining
+ an ``HMAC``.
+
+ 5. Create the final ``CHALLENGE`` string by concatenating the
+ ``HMAC`` and ``ENC_BLOB``, then base64-encoding the result.
+
+ :param str answer: The answer to a CAPTCHA.
+ :rtype: str
+ :returns: A challenge string.
+ """
+ timestamp = str(int(time.time())).zfill(12)
+ blob = timestamp + answer
+ encBlob = self.publicKey.encrypt(blob)
+ hmac = crypto.getHMAC(self.hmacKey, encBlob)
+ challenge = urlsafe_b64encode(hmac + encBlob)
+ return challenge
+
+ def get(self):
+ """Get a random CAPTCHA from the cache directory.
+
+ This chooses a random CAPTCHA image file from the cache directory, and
+ reads the contents of the image into a string. Next, it creates a
+ challenge string for the CAPTCHA, via :meth:`createChallenge`.
+
+ :raises GimpCaptchaError: if the chosen CAPTCHA image file could not
+ be read, or if the **cacheDir** is empty.
+ :rtype: tuple
+ :returns: A 2-tuple containing the image file contents as a string,
+ and a challenge string (used for checking the client's solution).
+ """
+ try:
+ imageFilename = random.choice(os.listdir(self.cacheDir))
+ imagePath = os.path.join(self.cacheDir, imageFilename)
+ with open(imagePath) as imageFile:
+ self.image = imageFile.read()
+ except IndexError:
+ raise GimpCaptchaError("CAPTCHA cache dir appears empty: %r"
+ % self.cacheDir)
+ except (OSError, IOError):
+ raise GimpCaptchaError("Could not read Gimp captcha image file: %r"
+ % imageFilename)
+
+ self.answer = imageFilename.rsplit(os.path.extsep, 1)[0]
+ self.challenge = self.createChallenge(self.answer)
+
+ return (self.image, self.challenge)
diff --git a/bridgedb/configure.py b/bridgedb/configure.py
new file mode 100644
index 0000000..0056107
--- /dev/null
+++ b/bridgedb/configure.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_configure -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see the AUTHORS file for attributions
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2007-2015, Nick Mathewson
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""Utilities for dealing with configuration files for BridgeDB."""
+
+import logging
+import os
+
+# Used to set the SUPPORTED_TRANSPORTS:
+from bridgedb import strings
+
+
+def loadConfig(configFile=None, configCls=None):
+ """Load configuration settings on top of the current settings.
+
+ All pathnames and filenames within settings in the ``configFile`` will be
+ expanded, and their expanded values will be stored in the returned
+ :class:`config <Conf>` object.
+
+ ** Note: **
+ On the strange-looking use of
+ ``exec compile(open(configFile).read(), '<string>', 'exec') in dict()``
+ in this function:
+
+ The contents of the config file should be compiled first, and then
+ ``exec``ed -- not ``execfile``! -- in order to get the contents of the
+ config file to exist within the scope of the configuration dictionary.
+ Otherwise, Python *will* default_ to executing the config file directly
+ within the ``globals()`` scope.
+
+ Additionally, it's roughly 20-30 times faster_ to use the ``compile``
+ builtin on a string (the contents of the file) before ``exec``ing it, than
+ using ``execfile`` directly on the file.
+
+ .. _default: http://stackoverflow.com/q/17470193
+ .. _faster: http://lucumr.pocoo.org/2011/2/1/exec-in-python/
+
+ :ivar boolean itsSafeToUseLogging: This is called in
+ :func:`~bridgedb.Main.run` before
+ :func:`bridgedb.safelog.configureLogging`. When called from
+ :func:`~bridgedb.Main.run`, the **configCls** parameter is not given,
+ because that is the first time that a :class:`Conf` is created. If a
+ :class:`logging.Logger` is created in this function, then logging will
+ not be correctly configured, therefore, if the **configCls** parameter
+ is not given, then it's the first time this function has been called
+ and it is therefore not safe to make calls to the logging module.
+ :type: configFile: string or None
+ :param configFile: If given, the filename of the config file to load.
+ :type configCls: :class:`Conf` or None
+ :param configCls: The current configuration instance, if one already
+ exists.
+ :returns: A new :class:`configuration <bridgedb.configure.Conf>`, with the
+ old settings as defaults, and the settings from the **configFile** (if
+ given) overriding those defaults.
+ """
+ itsSafeToUseLogging = False
+ configuration = {}
+
+ if configCls:
+ itsSafeToUseLogging = True
+ oldConfig = configCls.__dict__
+ configuration.update(**oldConfig) # Load current settings
+ logging.info("Reloading over in-memory configurations...")
+
+ conffile = configFile
+ if (configFile is None) and ('CONFIG_FILE' in configuration):
+ conffile = configuration['CONFIG_FILE']
+
+ if conffile is not None:
+ if itsSafeToUseLogging:
+ logging.info("Loading settings from config file: '%s'" % conffile)
+ compiled = compile(open(conffile).read(), '<string>', 'exec')
+ exec compiled in configuration
+
+ if itsSafeToUseLogging:
+ logging.debug("New configuration settings:")
+ logging.debug("\n".join(["{0} = {1}".format(key, value)
+ for key, value in configuration.items()
+ if not key.startswith('_')]))
+
+ # Create a :class:`Conf` from the settings stored within the local scope
+ # of the ``configuration`` dictionary:
+ config = Conf(**configuration)
+
+ # We want to set the updated/expanded paths for files on the ``config``,
+ # because the copy of this config, `state.config` is used later to compare
+ # with a new :class:`Conf` instance, to see if there were any changes.
+ #
+ # See :meth:`bridgedb.persistent.State.useUpdatedSettings`.
+
+ for attr in ["PROXY_LIST_FILES", "BRIDGE_FILES", "EXTRA_INFO_FILES"]:
+ setting = getattr(config, attr, None)
+ if setting is None: # pragma: no cover
+ setattr(config, attr, []) # If they weren't set, make them lists
+ else:
+ setattr(config, attr, # If they were set, expand the paths:
+ [os.path.abspath(os.path.expanduser(f)) for f in setting])
+
+ for attr in ["DB_FILE", "DB_LOG_FILE", "MASTER_KEY_FILE", "PIDFILE",
+ "ASSIGNMENTS_FILE", "HTTPS_CERT_FILE", "HTTPS_KEY_FILE",
+ "LOG_FILE", "STATUS_FILE", "COUNTRY_BLOCK_FILE",
+ "GIMP_CAPTCHA_DIR", "GIMP_CAPTCHA_HMAC_KEYFILE",
+ "GIMP_CAPTCHA_RSA_KEYFILE", "EMAIL_GPG_HOMEDIR",
+ "EMAIL_GPG_PASSPHRASE_FILE"]:
+ setting = getattr(config, attr, None)
+ if setting is None:
+ setattr(config, attr, setting)
+ else:
+ setattr(config, attr, os.path.abspath(os.path.expanduser(setting)))
+
+ for attr in ["HTTPS_ROTATION_PERIOD", "EMAIL_ROTATION_PERIOD"]:
+ setting = getattr(config, attr, None) # Default to None
+ setattr(config, attr, setting)
+
+ for attr in ["IGNORE_NETWORKSTATUS"]:
+ setting = getattr(config, attr, True) # Default to True
+ setattr(config, attr, setting)
+
+ for attr in ["FORCE_PORTS", "FORCE_FLAGS", "NO_DISTRIBUTION_COUNTRIES"]:
+ setting = getattr(config, attr, []) # Default to empty lists
+ setattr(config, attr, setting)
+
+ for attr in ["SUPPORTED_TRANSPORTS"]:
+ setting = getattr(config, attr, {}) # Default to empty dicts
+ setattr(config, attr, setting)
+
+ # Set the SUPPORTED_TRANSPORTS to populate the webserver and email options:
+ strings._setSupportedTransports(getattr(config, "SUPPORTED_TRANSPORTS", {}))
+ strings._setDefaultTransport(getattr(config, "DEFAULT_TRANSPORT", ""))
+ logging.info("Currently supported transports: %s" %
+ " ".join(strings._getSupportedTransports()))
+ logging.info("Default transport: %s" % strings._getDefaultTransport())
+
+ for domain in config.EMAIL_DOMAINS:
+ config.EMAIL_DOMAIN_MAP[domain] = domain
+
+ if conffile: # Store the pathname of the config file, if one was used
+ config.CONFIG_FILE = os.path.abspath(os.path.expanduser(conffile))
+
+ return config
+
+
+class Conf(object):
+ """A configuration object. Holds unvalidated attributes."""
+ def __init__(self, **attrs):
+ for key, value in attrs.items():
+ if key == key.upper():
+ if not key.startswith('__'):
+ self.__dict__[key] = value
diff --git a/bridgedb/crypto.py b/bridgedb/crypto.py
new file mode 100644
index 0000000..7785073
--- /dev/null
+++ b/bridgedb/crypto.py
@@ -0,0 +1,450 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""BridgeDB general cryptographic utilities.
+
+.. py:module:: bridgedb.crypto
+ :synopsis: This module contains general utilities for working with external
+ cryptographic tools and libraries, including OpenSSL and GnuPG. It also
+ includes utilities for creating callable HMAC functions, generating
+ HMACs for data, and generating and/or storing key material.
+
+Module Overview
+~~~~~~~~~~~~~~~
+::
+ crypto
+ |_getGPGContext() - Get a pre-configured GPGME context.
+ |_getHMAC() - Compute an HMAC with some key for some data.
+ |_getHMACFunc() - Get a callable for producing HMACs with the given key.
+ |_getKey() - Load the master HMAC key from a file, or create a new one.
+ |_getRSAKey() - Load an RSA key from a file, or create a new one.
+ |_gpgSignMessage() - Sign a message string according to a GPGME context.
+ |_writeKeyToFile() - Write to a file readable only by the process owner.
+ |
+ \_SSLVerifyingContextFactory - OpenSSL.SSL.Context factory which verifies
+ | certificate chains and matches hostnames.
+ |_getContext() - Retrieve an SSL context configured for certificate
+ | verification.
+ |_getHostnameFromURL() - Parses the hostname from the request URL.
+ \_verifyHostname() - Check that the cert CN matches the request
+ hostname.
+::
+"""
+
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import gnupg
+import hashlib
+import hmac
+import io
+import logging
+import os
+import re
+import urllib
+
+import OpenSSL
+
+from Crypto.Cipher import PKCS1_OAEP
+from Crypto.PublicKey import RSA
+
+from twisted.internet import ssl
+from twisted.python.procutils import which
+
+
+#: The hash digest to use for HMACs.
+DIGESTMOD = hashlib.sha1
+
+# Test to see if we have the old or new style buffer() interface. Trying
+# to use an old-style buffer on Python2.7 prior to version 2.7.5 will produce:
+#
+# TypeError: 'buffer' does not have the buffer interface
+#
+#: ``True`` if we have the new-style
+#: `buffer <https://docs.python.org/2/c-api/buffer.html>` interface.
+NEW_BUFFER_INTERFACE = False
+try:
+ io.BytesIO(buffer('test'))
+except TypeError: # pragma: no cover
+ logging.warn(
+ "This Python version is too old! "\
+ "It doesn't support new-style buffer interfaces: "\
+ "https://mail.python.org/pipermail/python-dev/2010-October/104917.html")
+else:
+ NEW_BUFFER_INTERFACE = True
+
+
+class PKCS1PaddingError(Exception):
+ """Raised when there is a problem adding or removing PKCS#1 padding."""
+
+class RSAKeyGenerationError(Exception):
+ """Raised when there was an error creating an RSA keypair."""
+
+
+def writeKeyToFile(key, filename):
+ """Write **key** to **filename**, with ``0400`` permissions.
+
+ If **filename** doesn't exist, it will be created. If it does exist
+ already, and is writable by the owner of the current process, then it will
+ be truncated to zero-length and overwritten.
+
+ :param bytes key: A key (or some other private data) to write to
+ **filename**.
+ :param str filename: The path of the file to write to.
+ :raises: Any exceptions which may occur.
+ """
+ logging.info("Writing key to file: %r" % filename)
+ flags = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, "O_BIN", 0)
+ fd = os.open(filename, flags, 0400)
+ os.write(fd, key)
+ os.fsync(fd)
+ os.close(fd)
+
+def getRSAKey(filename, bits=2048):
+ """Load the RSA key stored in **filename**, or create and save a new key.
+
+ >>> from bridgedb import crypto
+ >>> keyfile = 'doctest_getRSAKey'
+ >>> message = "The secret words are Squeamish Ossifrage."
+ >>> keypair = crypto.getRSAKey(keyfile, bits=2048)
+ >>> (secretkey, publickey) = keypair
+ >>> encrypted = publickey.encrypt(message)
+ >>> assert encrypted != message
+ >>> decrypted = secretkey.decrypt(encrypted)
+ >>> assert message == decrypted
+
+
+ If **filename** already exists, it is assumed to contain a PEM-encoded RSA
+ private key, which will be read from the file. (The parameters of a
+ private RSA key contain the public exponent and public modulus, which
+ together comprise the public key â ergo having two separate keyfiles is
+ assumed unnecessary.)
+
+ If **filename** doesn't exist, a new RSA keypair will be created, and the
+ private key will be stored in **filename**, using :func:`writeKeyToFile`.
+
+ Once the private key is either loaded or created, the public key is
+ extracted from it. Both keys are then input into PKCS#1 RSAES-OAEP cipher
+ schemes (see `RFC 3447 §7.1`__) in order to introduce padding, and then
+ returned.
+
+ .. __: https://tools.ietf.org/html/rfc3447#section-7.1
+
+ :param str filename: The filename to which the secret parameters of the
+ RSA key are stored in.
+ :param int bits: If no key is found within the file, create a new key with
+ this bitlength and store it in **filename**.
+ :rtype: tuple of ``Crypto.Cipher.PKCS1_OAEP.PKCS1OAEP_Cipher``
+ :returns: A 2-tuple of ``(privatekey, publickey)``, which are PKCS#1
+ RSAES-OAEP padded and encoded private and public keys, forming an RSA
+ keypair.
+ """
+ filename = os.path.extsep.join([filename, 'sec'])
+ keyfile = os.path.join(os.getcwd(), filename)
+
+ try:
+ fh = open(keyfile, 'rb')
+ except IOError:
+ logging.info("Generating %d-bit RSA keypair..." % bits)
+ secretKey = RSA.generate(bits, e=65537)
+
+ # Store a PEM copy of the secret key (which contains the parameters
+ # necessary to create the corresponding public key):
+ secretKeyPEM = secretKey.exportKey("PEM")
+ writeKeyToFile(secretKeyPEM, keyfile)
+ else:
+ logging.info("Secret RSA keyfile %r found. Loading..." % filename)
+ secretKey = RSA.importKey(fh.read())
+ fh.close()
+
+ publicKey = secretKey.publickey()
+
+ # Add PKCS#1 OAEP padding to the secret and public keys:
+ sk = PKCS1_OAEP.new(secretKey)
+ pk = PKCS1_OAEP.new(publicKey)
+
+ return (sk, pk)
+
+def getKey(filename):
+ """Load the master key stored in ``filename``, or create a new key.
+
+ If ``filename`` does not exist, create a new 32-byte key and store it in
+ ``filename``.
+
+ >>> import os
+ >>> from bridgedb import crypto
+ >>> name = 'doctest_getKey'
+ >>> os.path.exists(name)
+ False
+ >>> k1 = crypto.getKey(name)
+ >>> os.path.exists(name)
+ True
+ >>> open(name).read() == k1
+ True
+ >>> k2 = crypto.getKey(name)
+ >>> k1 == k2
+ True
+
+ :param string filename: The filename to store the secret key in.
+ :rtype: bytes
+ :returns: A byte string containing the secret key.
+ """
+ try:
+ fh = open(filename, 'rb')
+ except IOError:
+ logging.debug("getKey(): Creating new secret key.")
+ key = OpenSSL.rand.bytes(32)
+ writeKeyToFile(key, filename)
+ else:
+ logging.debug("getKey(): Secret key file found. Loading...")
+ key = fh.read()
+ fh.close()
+ return key
+
+def getHMAC(key, value):
+ """Return the HMAC of **value** using the **key**."""
+ h = hmac.new(key, value, digestmod=DIGESTMOD)
+ return h.digest()
+
+def getHMACFunc(key, hex=True):
+ """Return a function that computes the HMAC of its input using the **key**.
+
+ :param bool hex: If True, the output of the function will be hex-encoded.
+ :rtype: callable
+ :returns: A function which can be uses to generate HMACs.
+ """
+ h = hmac.new(key, digestmod=DIGESTMOD)
+ def hmac_fn(value):
+ h_tmp = h.copy()
+ h_tmp.update(value)
+ if hex:
+ return h_tmp.hexdigest()
+ else:
+ return h_tmp.digest()
+ return hmac_fn
+
+def removePKCS1Padding(message):
+ """Remove PKCS#1 padding from a **message**.
+
+ (PKCS#1 v1.0? see https://bugs.torproject.org/13042)
+
+ Each block is 128 bytes total in size:
+
+ * 2 bytes for the type info ('\x00\x01')
+ * 1 byte for the separator ('\x00')
+ * variable length padding ('\xFF')
+ * variable length for the **message**
+
+ For more information on the structure of PKCS#1 padding, see :rfc:`2313`,
+ particularly the notes in §8.1.
+
+ :param str message: A message which is PKCS#1 padded.
+ :raises PKCS1PaddingError: if there is an issue parsing the **message**.
+ :rtype: bytes
+ :returns: The message without the PKCS#1 padding.
+ """
+ padding = b'\xFF'
+ typeinfo = b'\x00\x01'
+ separator = b'\x00'
+
+ unpadded = None
+
+ try:
+ if message.index(typeinfo) != 0:
+ raise PKCS1PaddingError("Couldn't find PKCS#1 identifier bytes!")
+ start = message.index(separator, 2) + 1 # 2 bytes for the typeinfo,
+ # and 1 byte for the separator.
+ except ValueError:
+ raise PKCS1PaddingError("Couldn't find PKCS#1 separator byte!")
+ else:
+ unpadded = message[start:]
+
+ return unpadded
+
+def initializeGnuPG(config):
+ """Initialize a GnuPG interface and test our configured keys.
+
+ .. note:: This function uses python-gnupg_.
+
+ :type config: :class:`bridgedb.persistent.Conf`
+ :param config: The loaded config file.
+ :rtype: 2-tuple
+ :returns: If ``EMAIL_GPG_SIGNING_ENABLED`` isn't ``True``, or we couldn't
+ initialize GnuPG and make a successful test signature with the
+ specified key, then a 2-tuple of ``None`` is returned. Otherwise, the
+ first item in the tuple is a :class:`gnupg.GPG` interface_ with the
+ GnuPG homedir set to the ``EMAIL_GPG_HOMEDIR`` option and the signing
+ key specified by the ``EMAIL_GPG_SIGNING_KEY_FINGERPRINT`` option in
+ bridgedb.conf set as the default key. The second item in the tuple is
+ a signing function with the passphrase (as specified in either
+ ``EMAIL_GPG_PASSPHRASE`` or ``EMAIL_GPG_PASSPHRASE_FILE``) already
+ set.
+
+ .. _python-gnupg: https://pypi.python.org/pypi/gnupg/
+ .. _interface: https://python-gnupg.readthedocs.org/en/latest/gnupg.html#gnupg-module
+ """
+ ret = (None, None)
+
+ if not config.EMAIL_GPG_SIGNING_ENABLED:
+ return ret
+
+ homedir = config.EMAIL_GPG_HOMEDIR
+ primary = config.EMAIL_GPG_PRIMARY_KEY_FINGERPRINT
+ passphrase = config.EMAIL_GPG_PASSPHRASE
+ passFile = config.EMAIL_GPG_PASSPHRASE_FILE
+
+ logging.info("Using %s as our GnuPG home directory..." % homedir)
+ gpg = gnupg.GPG(homedir=homedir)
+ logging.info("Initialized GnuPG interface using %s binary with version %s."
+ % (gpg.binary, gpg.binary_version))
+
+ primarySK = None
+ primaryPK = None
+ secrets = gpg.list_keys(secret=True)
+ publics = gpg.list_keys()
+
+ if not secrets:
+ logging.warn("No secret keys found in %s!" % gpg.secring)
+ return ret
+
+ primarySK = filter(lambda key: key['fingerprint'] == primary, secrets)
+ primaryPK = filter(lambda key: key['fingerprint'] == primary, publics)
+
+ if primarySK and primaryPK:
+ logging.info("Found GnuPG primary key with fingerprint: %s" % primary)
+ for sub in primaryPK[0]['subkeys']:
+ logging.info(" Subkey: %s Usage: %s" % (sub[0], sub[1].upper()))
+ else:
+ logging.warn("GnuPG key %s could not be found in %s!" % (primary, gpg.secring))
+ return ret
+
+ if passphrase:
+ logging.info("Read GnuPG passphrase from config.")
+ elif passFile:
+ try:
+ with open(passFile) as fh:
+ passphrase = fh.read()
+ except (IOError, OSError):
+ logging.error("Could not open GnuPG passphrase file: %s!" % passFile)
+ else:
+ logging.info("Read GnuPG passphrase from file: %s" % passFile)
+
+ def gpgSignMessage(message):
+ """Sign **message** with the default key specified by
+ ``EMAIL_GPG_PRIMARY_KEY_FINGERPRINT``.
+
+ :param str message: A message to sign.
+ :rtype: str or ``None``.
+ :returns: A string containing the clearsigned message, or ``None`` if
+ the signing failed.
+ """
+ sig = gpg.sign(message, default_key=primary, passphrase=passphrase)
+ if sig and sig.data:
+ return sig.data
+
+ logging.debug("Testing signature created with GnuPG key...")
+ sig = gpgSignMessage("Testing 1 2 3")
+ if sig:
+ logging.info("Test signature with GnuPG key %s okay:\n%s" % (primary, sig))
+ return (gpg, gpgSignMessage)
+
+ return ret
+
+
+class SSLVerifyingContextFactory(ssl.CertificateOptions):
+ """``OpenSSL.SSL.Context`` factory which does full certificate-chain and
+ hostname verfication.
+ """
+ isClient = True
+
+ def __init__(self, url, **kwargs):
+ """Create a client-side verifying SSL Context factory.
+
+ To pass acceptable certificates for a server which does
+ client-authentication checks: initialise with a ``caCerts=[]`` keyword
+ argument, which should be a list of ``OpenSSL.crypto.X509`` instances
+ (one for each peer certificate to add to the store), and set
+ ``SSLVerifyingContextFactory.isClient=False``.
+
+ :param str url: The URL being requested by an
+ :api:`twisted.web.client.Agent`.
+ :param bool isClient: True if we're being used in a client
+ implementation; False if we're a server.
+ """
+ self.hostname = self.getHostnameFromURL(url)
+
+ # ``verify`` here refers to server-side verification of certificates
+ # presented by a client:
+ self.verify = False if self.isClient else True
+ super(SSLVerifyingContextFactory, self).__init__(verify=self.verify,
+ fixBrokenPeers=True,
+ **kwargs)
+
+ def getContext(self, hostname=None, port=None):
+ """Retrieve a configured ``OpenSSL.SSL.Context``.
+
+ Any certificates in the ``caCerts`` list given during initialisation
+ are added to the ``Context``'s certificate store.
+
+ The **hostname** and **port** arguments seem unused, but they are
+ required due to some Twisted and pyOpenSSL internals. See
+ :api:`twisted.web.client.Agent._wrapContextFactory`.
+
+ :rtype: ``OpenSSL.SSL.Context``
+ :returns: An SSL Context which verifies certificates.
+ """
+ ctx = super(SSLVerifyingContextFactory, self).getContext()
+ store = ctx.get_cert_store()
+ verifyOptions = OpenSSL.SSL.VERIFY_PEER
+ ctx.set_verify(verifyOptions, self.verifyHostname)
+ return ctx
+
+ def getHostnameFromURL(self, url):
+ """Parse the hostname from the originally requested URL.
+
+ :param str url: The URL being requested by an
+ :api:`twisted.web.client.Agent`.
+ :rtype: str
+ :returns: The full hostname (including any subdomains).
+ """
+ hostname = urllib.splithost(urllib.splittype(url)[1])[0]
+ logging.debug("Parsed hostname %r for cert CN matching." % hostname)
+ return hostname
+
+ def verifyHostname(self, connection, x509, errnum, depth, okay):
+ """Callback method for additional SSL certificate validation.
+
+ If the certificate is signed by a valid CA, and the chain is valid,
+ verify that the level 0 certificate has a subject common name which is
+ valid for the hostname of the originally requested URL.
+
+ :param connection: An ``OpenSSL.SSL.Connection``.
+ :param x509: An ``OpenSSL.crypto.X509`` object.
+ :param errnum: A pyOpenSSL error number. See that project's docs.
+ :param depth: The depth which the current certificate is at in the
+ certificate chain.
+ :param bool okay: True if all the pyOpenSSL default checks on the
+ certificate passed. False otherwise.
+ """
+ commonName = x509.get_subject().commonName
+ logging.debug("Received cert at level %d: '%s'" % (depth, commonName))
+
+ # We only want to verify that the hostname matches for the level 0
+ # certificate:
+ if okay and (depth == 0):
+ cn = commonName.replace('*', '.*')
+ hostnamesMatch = re.search(cn, self.hostname)
+ if not hostnamesMatch:
+ logging.warn("Invalid certificate subject CN for '%s': '%s'"
+ % (self.hostname, commonName))
+ return False
+ logging.debug("Valid certificate subject CN for '%s': '%s'"
+ % (self.hostname, commonName))
+ return True
diff --git a/bridgedb/distribute.py b/bridgedb/distribute.py
new file mode 100644
index 0000000..f48cbb6
--- /dev/null
+++ b/bridgedb/distribute.py
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_distribute ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+"""Classes for creating bridge distribution systems.
+
+DEFINITELY
+----------
+
+Distributor {
+ name property
+ bridgesPerResponse() property FORMERLY getNumBridgesPerAnswer()
+ hashring struct FORMERLY KNOWN AS splitter
+ rotate bool
+ rotationGroups
+ rotationSchedule
+ key str
+ subrings list
+ - Subring
+ clear()
+ export() FORMERLY KNOWN AS dumpAssignments()
+ insert()
+ getBridges() FORMERLY KNOWN AS getBridgesForEmail() and getBridgesForIP()
+ handleBridgeRequest()
+ handleIncomingBridges()
+}
+
+DistributionContext { # should go in bridgedb.py
+ distributors {
+ name: DistributorContext
+ }
+}
+
+DistributorContext { # should go in bridgedb.py
+ name str
+ allocationPercentage property
+ publicKey
+}
+
+Hashring {
+ assignBridgesToSubrings() FORMERLY bridgedb.filters.assignBridgesToSubring()
+ + filters bridges uniformly into subrings
+ clear() / __del__()
+ isEmpty property
+}
+
+MAYBE
+-----
+mapClientToHashring() FORMERLY KNOWN AS areaMapper AND
+mapClientToSubhashring()
+authenticateToBridgeDB()
+maintainACL() for proxylists
+
+- need a way for BridgeDB to decide global parameters to be followed
+ by all distributors.
+ - BridgeAnswerParameters?
+ maybe call it DistributionContext?
+ then have DistributorContexts?
+
+ requiredFlags AnswerParameters?
+ requireFlag()
+ requiredPorts
+ requirePorts()
+
+ THINGS NEEDED FOR COMMUNICATION BETWEEN DISTRIBUTORS AND BRIDGEDB
+ -----------------------------------------------------------------
+ * distributorCredential (for authenticating to the DB)
+ * metrics?
+ * total clients seen
+ * total clients served
+ - unique clients seen
+ - unique clients served
+ * total requests for TRANSPORT
+ * total times TRANSPORT was served
+
+ THINGS DISTRIBUTORS SHOULD KEEP TRACK OF, BUT NOT REPORT
+ --------------------------------------------------------
+ - approximate bridge bandwidth
+ - approximate bandwidth per client
+ - approximate bridge bandwidth already distributed
+
+ NAMES FOR CHOOSING "GET ME WHATEVER TRANSPORTS"
+ -----------------------------------------------
+ chocolate box, russian roulette
+
+ * How much of a bad idea would it be to store bridges allocated to a
+ distributor as diffs over the last time the Distributor asked?
+"""
+
+import logging
+import math
+
+from zope import interface
+from zope.interface import Attribute
+from zope.interface import implements
+
+# from bridgedb.hashring import IHashring
+from bridgedb.interfaces import IName
+from bridgedb.interfaces import Named
+
+
+class IDistribute(IName):
+ """An interface specification for a system which distributes bridges."""
+
+ _bridgesPerResponseMin = Attribute(
+ ("The minimum number of bridges to distribute (if possible), per "
+ "client request."))
+ _bridgesPerResponseMax = Attribute(
+ ("The maximum number of bridges to distribute (if possible), per "
+ "client request."))
+ _hashringLevelMin = Attribute(
+ ("The bare minimum number of bridges which should be in a hashring. "
+ "If there less bridges than this, then the implementer of "
+ "IDistribute should only distribute _bridgesPerResponseMin number "
+ "of bridges, per client request."))
+ _hashringLevelMax = Attribute(
+ ("The number of bridges which should be in a hashring for the "
+ "implementer of IDistribute to distribute _bridgesPerResponseMax "
+ "number of bridges, per client request."))
+
+ hashring = Attribute(
+ ("An implementer of ``bridgedb.hashring.IHashring`` which stores the "
+ "entirety of bridges allocated to this ``Distributor`` by the "
+ "BridgeDB. This ``Distributor`` is only capable of distributing "
+ "these bridges to its clients, and these bridges are only "
+ "distributable by this ``Distributor``."))
+
+ key = Attribute(
+ ("A master key which is used to HMAC bridge and client data into "
+ "this Distributor's **hashring** and its subhashrings."))
+
+ def __str__():
+ """Get a string representation of this Distributor's ``name``."""
+
+ def bridgesPerResponse(hashring):
+ """Get the current number of bridges to return in a response."""
+
+ def getBridges(bridgeRequest):
+ """Get bridges based on a client's **bridgeRequest**."""
+
+
+class Distributor(Named):
+ """A :class:`Distributor` distributes bridges to clients.
+
+ Inherit from me to create a new type of ``Distributor``.
+ """
+ implements(IDistribute)
+
+ _bridgesPerResponseMin = 1
+ _bridgesPerResponseMax = 3
+ _hashringLevelMin = 20
+ _hashringLevelMax = 100
+
+ def __init__(self, key=None):
+ """Create a new bridge Distributor.
+
+ :param key: A master key for this Distributor. This is used to HMAC
+ bridge and client data in order to arrange them into hashring
+ structures.
+ """
+ super(Distributor, self).__init__()
+ self._hashring = None
+ self.key = key
+
+ def __str__(self):
+ """Get a string representation of this ``Distributor``'s ``name``.
+
+ :rtype: str
+ :returns: This ``Distributor``'s ``name`` attribute.
+ """
+ return self.name
+
+ @property
+ def hashring(self):
+ """Get this Distributor's main hashring, which holds all bridges
+ allocated to this Distributor.
+
+ :rtype: :class:`~bridgedb.hashring.Hashring`.
+ :returns: An implementer of :interface:`~bridgedb.hashring.IHashring`.
+ """
+ return self._hashring
+
+ @hashring.setter
+ def hashring(self, ring):
+ """Set this Distributor's main hashring.
+
+ :type ring: :class:`~bridgedb.hashring.Hashring`
+ :param ring: An implementer of :interface:`~bridgedb.hashring.IHashring`.
+ :raises TypeError: if the **ring** does not implement the
+ :interface:`~bridgedb.hashring.IHashring` interface.
+ """
+ # if not IHashring.providedBy(ring):
+ # raise TypeError("%r doesn't implement the IHashring interface." % ring)
+
+ self._hashring = ring
+
+ @hashring.deleter
+ def hashring(self):
+ """Clear this Distributor's hashring."""
+ if self.hashring:
+ self.hashring.clear()
+
+ @property
+ def name(self):
+ """Get the name of this Distributor.
+
+ :rtype: str
+ :returns: A string which identifies this :class:`Distributor`.
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Set a **name** for identifying this Distributor.
+
+ This is used to identify the distributor in the logs; the **name**
+ doesn't necessarily need to be unique. The hashrings created for this
+ distributor will be named after this distributor's name, and any
+ subhashrings of each of those hashrings will also carry that name.
+
+ >>> from bridgedb.distribute import Distributor
+ >>> dist = Distributor()
+ >>> dist.name = 'Excellent Distributor'
+ >>> dist.name
+ 'Excellent Distributor'
+
+ :param str name: A name for this distributor.
+ """
+ self._name = name
+
+ try:
+ self.hashring.distributor = name
+ except AttributeError:
+ logging.debug(("Couldn't set distributor attribute for %s "
+ "Distributor's hashring." % name))
+
+ def bridgesPerResponse(self, hashring=None):
+ """Get the current number of bridge to distribute in response to a
+ client's request for bridges.
+ """
+ if hashring is None:
+ hashring = self.hashring
+
+ if len(hashring) < self._hashringLevelMin:
+ n = self._bridgesPerResponseMin
+ elif self._hashringLevelMin <= len(hashring) < self._hashringLevelMax:
+ n = int(math.ceil(
+ (self._bridgesPerResponseMin + self._bridgesPerResponseMax) / 2.0))
+ elif self._hashringLevelMax <= len(hashring):
+ n = self._bridgesPerResponseMax
+
+ logging.debug("Returning %d bridges from ring of len: %d" %
+ (n, len(hashring)))
+
+ return n
+
+ def getBridges(self, bridgeRequest):
+ """Get some bridges in response to a client's **bridgeRequest**.
+
+ :type bridgeRequest: :class:`~bridgedb.bridgerequest.BridgeRequestBase`
+ :param bridgeRequest: A client's request for bridges, including some
+ information on the client making the request, whether they asked
+ for IPv4 or IPv6 bridges, which type of
+ :class:`~bridgedb.bridges.PluggableTransport` they wanted, etc.
+ """
+ # XXX generalise the getBridges() method
diff --git a/bridgedb/email/__init__.py b/bridgedb/email/__init__.py
new file mode 100644
index 0000000..604c648
--- /dev/null
+++ b/bridgedb/email/__init__.py
@@ -0,0 +1 @@
+"""Servers for BridgeDB's email bridge distributor."""
diff --git a/bridgedb/email/autoresponder.py b/bridgedb/email/autoresponder.py
new file mode 100644
index 0000000..ad63bfd
--- /dev/null
+++ b/bridgedb/email/autoresponder.py
@@ -0,0 +1,704 @@
+# -*- coding: utf-8; test-case-name: bridgedb.test.test_email_autoresponder -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# Matthew Finkel <sysrqb at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.email.autoresponder
+ :synopsis: Functionality for autoresponding to incoming emails.
+
+bridgedb.email.autoresponder
+============================
+
+Functionality for autoresponding to incoming emails.
+
+::
+
+ bridgedb.email.autoresponder
+ | |_ createResponseBody - Parse lines from an incoming email and determine
+ | | how to respond.
+ | |_ generateResponse - Create an email response.
+ |
+ |_ EmailResponse - Holds information for generating a response to a request.
+ |_ SMTPAutoresponder - An SMTP autoresponder for incoming mail.
+..
+"""
+
+from __future__ import unicode_literals
+from __future__ import print_function
+
+import io
+import logging
+import time
+
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.mail import smtp
+from twisted.python import failure
+
+from bridgedb import safelog
+from bridgedb.crypto import NEW_BUFFER_INTERFACE
+from bridgedb.email import dkim
+from bridgedb.email import request
+from bridgedb.email import templates
+from bridgedb.email.distributor import EmailRequestedHelp
+from bridgedb.email.distributor import EmailRequestedKey
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.email.distributor import IgnoreEmail
+from bridgedb.parse import addr
+from bridgedb.parse.addr import canonicalizeEmailDomain
+from bridgedb.util import levenshteinDistance
+from bridgedb import translations
+
+
+def createResponseBody(lines, context, client, lang='en'):
+ """Parse the **lines** from an incoming email request and determine how to
+ respond.
+
+ :param list lines: The list of lines from the original request sent by the
+ client.
+ :type context: class:`bridgedb.email.server.MailServerContext`
+ :param context: The context which contains settings for the email server.
+ :type client: :api:`twisted.mail.smtp.Address`
+ :param client: The client's email address which should be in the
+ ``'To:'`` header of the response email.
+ :param str lang: The 2-5 character locale code to use for translating the
+ email. This is obtained from a client sending a email to a valid plus
+ address which includes the translation desired, i.e. by sending an
+ email to `bridges+fa at torproject.org
+ <mailto:bridges+fa at torproject.org>`__, the client should receive a
+ response in Farsi.
+ :rtype: None or str
+ :returns: None if we shouldn't respond to the client (i.e., if they have
+ already received a rate-limiting warning email). Otherwise, returns a
+ string containing the (optionally translated) body for the email
+ response which we should send out.
+ """
+ translator = translations.installTranslations(lang)
+ bridges = None
+ try:
+ bridgeRequest = request.determineBridgeRequestOptions(lines)
+ bridgeRequest.client = str(client)
+
+ # The request was invalid, respond with a help email which explains
+ # valid email commands:
+ if not bridgeRequest.isValid():
+ raise EmailRequestedHelp("Email request from '%s' was invalid."
+ % str(client))
+
+ # Otherwise they must have requested bridges:
+ interval = context.schedule.intervalStart(time.time())
+ bridges = context.distributor.getBridges(bridgeRequest, interval)
+ except EmailRequestedHelp as error:
+ logging.info(error)
+ return templates.buildWelcomeText(translator, client)
+ except EmailRequestedKey as error:
+ logging.info(error)
+ return templates.buildKeyMessage(translator, client)
+ except TooSoonEmail as error:
+ logging.info("Got a mail too frequently: %s." % error)
+ return templates.buildSpamWarning(translator, client)
+ except (IgnoreEmail, addr.BadEmail) as error:
+ logging.info(error)
+ # Don't generate a response if their email address is unparsable or
+ # invalid, or if we've already warned them about rate-limiting:
+ return None
+ else:
+ answer = "(no bridges currently available)\r\n"
+ if bridges:
+ transport = bridgeRequest.justOnePTType()
+ answer = "".join(" %s\r\n" % b.getBridgeLine(
+ bridgeRequest, context.includeFingerprints) for b in bridges)
+ return templates.buildAnswerMessage(translator, client, answer)
+
+def generateResponse(fromAddress, client, body, subject=None,
+ messageID=None, gpgSignFunc=None):
+ """Create a :class:`EmailResponse`, which acts like an in-memory
+ ``io.StringIO`` file, by creating and writing all headers and the email
+ body into the file-like ``EmailResponse.mailfile``.
+
+ :param str fromAddress: The rfc:`2821` email address which should be in
+ the :header:`From:` header.
+ :type client: :api:`twisted.mail.smtp.Address`
+ :param client: The client's email address which should be in the
+ ``'To:'`` header of the response email.
+ :param str subject: The string to write to the ``Subject:'`` header.
+ :param str body: The body of the email. If a **gpgSignFunc** is also
+ given, then :meth:`EmailResponse.writeBody` will generate and include
+ an ascii-armored OpenPGP signature in the **body**.
+ :type messageID: None or str
+ :param messageID: The :rfc:`2822` specifier for the ``'Message-ID:'``
+ header, if including one is desirable.
+ :type gpgSignFunc: ``None`` or callable
+ :param gpgSignFunc: A function for signing messages. See
+ :func:`bridgedb.crypto.initializeGnuPG` for obtaining a pre-configured
+ **gpgSignFunc**.
+ :returns: An :class:`EmailResponse` which contains the entire email. To
+ obtain the contents of the email, including all headers, simply use
+ :meth:`EmailResponse.readContents`.
+ """
+ response = EmailResponse(gpgSignFunc)
+ response.to = client
+ response.writeHeaders(fromAddress.encode('utf-8'), str(client), subject,
+ inReplyTo=messageID)
+ response.writeBody(body.encode('utf-8'))
+
+ # Only log the email text (including all headers) if SAFE_LOGGING is
+ # disabled:
+ if not safelog.safe_logging:
+ contents = response.readContents()
+ logging.debug("Email contents:\n%s" % str(contents))
+ else:
+ logging.debug("Email text for %r created." % str(client))
+
+ response.rewind()
+ return response
+
+
+class EmailResponse(object):
+ """Holds information for generating a response email for a request.
+
+ .. todo:: At some point, we may want to change this class to optionally
+ handle creating Multipart MIME encoding messages, so that we can
+ include attachments. (This would be useful for attaching our GnuPG
+ keyfile, for example, rather than simply pasting it into the body of
+ the email.)
+
+ :var _buff: (unicode or buffer) Used internally to write lines for the
+ response email into the ``_mailfile``. The reason why both of these
+ attributes have two possible types is for the same Python-buggy
+ reasons which require :data:`~bridgedb.crypto.NEW_BUFFER_INTERFACE`.
+ :var mailfile: (:class:`io.StringIO` or :class:`io.BytesIO`) An in-memory
+ file for storing the formatted headers and body of the response email.
+ :var str delimiter: Delimiter between lines written to the
+ :data:`mailfile`.
+ :var bool closed: ``True`` if :meth:`close` has been called.
+ :var to: An :api:`twisted.mail.smtp.Address` for the client's email address
+ which this response should be sent to.
+ """
+ _buff = buffer if NEW_BUFFER_INTERFACE else unicode
+ mailfile = io.BytesIO if NEW_BUFFER_INTERFACE else io.StringIO
+
+ def __init__(self, gpgSignFunc=None):
+ """Create a response to an email we have recieved.
+
+ This class deals with correctly formatting text for the response email
+ headers and the response body into an instance of :data:`mailfile`.
+
+ :type gpgSignFunc: ``None`` or callable
+ :param gpgSignFunc: A function for signing messages. See
+ :func:`bridgedb.crypto.initializeGnuPG` for obtaining a
+ pre-configured **gpgSignFunc**.
+ """
+ self.gpgSign = gpgSignFunc
+ self.mailfile = self.mailfile()
+ self.delimiter = '\n'
+ self.closed = False
+ self.to = None
+
+ def close(self):
+ """Close our :data:`mailfile` and set :data:`closed` to ``True``."""
+ logging.debug("Closing %s.mailfile..." % (self.__class__.__name__))
+ self.mailfile.close()
+ self.closed = True
+
+ def read(self, size=None):
+ """Read, at most, **size** bytes from our :data:`mailfile`.
+
+ .. note:: This method is required by Twisted's SMTP system.
+
+ :param int size: The number of bytes to read. Defaults to ``None``,
+ which reads until EOF.
+ :rtype: str
+ :returns: The bytes read from the :data:`mailfile`.
+ """
+ contents = ''
+ logging.debug("Reading%s from %s.mailfile..."
+ % ((' {0} bytes'.format(size) if size else ''),
+ self.__class__.__name__))
+ try:
+ if size is not None:
+ contents = self.mailfile.read(int(size)).encode('utf-8')
+ else:
+ contents = self.mailfile.read().encode('utf-8')
+ except Exception as error: # pragma: no cover
+ logging.exception(error)
+
+ return contents
+
+ def readContents(self):
+ """Read the all the contents written thus far to the :data:`mailfile`,
+ and then :meth:`seek` to return to the original pointer position we
+ were at before this method was called.
+
+ :rtype: str
+ :returns: The entire contents of the :data:`mailfile`.
+ """
+ pointer = self.mailfile.tell()
+ self.mailfile.seek(0)
+ contents = self.mailfile.read()
+ self.mailfile.seek(pointer)
+ return contents
+
+ def rewind(self):
+ """Rewind to the very beginning of the :data:`mailfile`."""
+ logging.debug("Rewinding %s.mailfile..." % self.__class__.__name__)
+ self.mailfile.seek(0)
+
+ def write(self, line):
+ """Write the **line** to the :data:`mailfile`.
+
+ Any **line** written to me will have :data:`delimiter` appended to it
+ beforehand.
+
+ :param str line: Something to append into the :data:`mailfile`.
+ """
+ if line.find('\r\n') != -1:
+ # If **line** contains newlines, send it to :meth:`writelines` to
+ # break it up so that we can replace them:
+ logging.debug("Found newlines in %r. Calling writelines()." % line)
+ self.writelines(line)
+ else:
+ line += self.delimiter
+ self.mailfile.write(self._buff(line.encode('utf8')))
+ self.mailfile.flush()
+
+ def writelines(self, lines):
+ """Calls :meth:`write` for each line in **lines**.
+
+ Line endings of ``'\\r\\n'`` will be replaced with :data:`delimiter`
+ (i.e. ``'\\n'``). See :api:`twisted.mail.smtp.SMTPClient.getMailData`
+ for the reason.
+
+ :type lines: basestring or list
+ :param lines: The lines to write to the :ivar:`mailfile`.
+ """
+ if isinstance(lines, basestring):
+ lines = lines.replace('\r\n', '\n')
+ for ln in lines.split('\n'):
+ self.write(ln)
+ elif isinstance(lines, (list, tuple,)):
+ for ln in lines:
+ self.write(ln)
+
+ def writeHeaders(self, fromAddress, toAddress, subject=None,
+ inReplyTo=None, includeMessageID=True,
+ contentType='text/plain; charset="utf-8"', **kwargs):
+ """Write all headers into the response email.
+
+ :param str fromAddress: The email address for the ``'From:'`` header.
+ :param str toAddress: The email address for the ``'To:'`` header.
+ :type subject: None or str
+ :param subject: The ``'Subject:'`` header.
+ :type inReplyTo: None or str
+ :param inReplyTo: If set, an ``'In-Reply-To:'`` header will be
+ generated. This should be set to the ``'Message-ID:'`` header from
+ the client's original request email.
+ :param bool includeMessageID: If ``True``, generate and include a
+ ``'Message-ID:'`` header for the response.
+ :param str contentType: The ``'Content-Type:'`` header.
+ :kwargs: If given, the key will become the name of the header, and the
+ value will become the Contents of that header.
+ """
+ self.write("From: %s" % fromAddress)
+ self.write("To: %s" % toAddress)
+ if includeMessageID:
+ self.write("Message-ID: %s" % smtp.messageid().encode('utf-8'))
+ if inReplyTo:
+ self.write("In-Reply-To: %s" % inReplyTo.encode('utf-8'))
+ self.write("Content-Type: %s" % contentType.encode('utf-8'))
+ self.write("Date: %s" % smtp.rfc822date().encode('utf-8'))
+
+ if not subject:
+ subject = '[no subject]'
+ if not subject.lower().startswith('re'):
+ subject = "Re: " + subject
+ self.write("Subject: %s" % subject.encode('utf-8'))
+
+ if kwargs:
+ for headerName, headerValue in kwargs.items():
+ headerName = headerName.capitalize()
+ headerName = headerName.replace(' ', '-')
+ headerName = headerName.replace('_', '-')
+ header = "%s: %s" % (headerName, headerValue)
+ self.write(header.encode('utf-8'))
+
+ # The first blank line designates that the headers have ended:
+ self.write(self.delimiter)
+
+ def writeBody(self, body):
+ """Write the response body into the :cvar:`mailfile`.
+
+ If ``EmailResponse.gpgSignFunc`` is set, and signing is configured, the
+ **body** will be automatically signed before writing its contents into
+ the ``mailfile``.
+
+ :param str body: The body of the response email.
+ """
+ logging.info("Writing email body...")
+ if self.gpgSign:
+ logging.info("Attempting to sign email...")
+ sig = self.gpgSign(body)
+ if sig:
+ body = sig
+ self.writelines(body)
+
+
+class SMTPAutoresponder(smtp.SMTPClient):
+ """An :api:`twisted.mail.smtp.SMTPClient` for responding to incoming mail.
+
+ The main worker in this class is the :meth:`reply` method, which functions
+ to dissect an incoming email from an incoming :class:`SMTPMessage` and
+ create a :class:`EmailResponse` email message in reply to it, and then,
+ finally, send it out.
+
+ :ivar log: A :api:`twisted.python.util.LineLog` cache of messages.
+ :ivar debug: If ``True``, enable logging (accessible via :ivar:`log`).
+ :ivar str identity: Our FQDN which will be sent during client ``HELO``.
+ :ivar incoming: An incoming
+ :api:`Message <twisted.mail.smtp.rfc822.Message>`, i.e. as returned
+ from :meth:`SMTPMessage.getIncomingMessage`.
+ :ivar deferred: A :api:`Deferred <twisted.internet.defer.Deferred>` with
+ registered callbacks, :meth:`sentMail` and :meth:`sendError`, which
+ will be given to the reactor in order to process the sending of the
+ outgoing response email.
+ """
+ debug = True
+ identity = smtp.DNSNAME
+
+ def __init__(self):
+ """Handle responding (or not) to an incoming email."""
+ smtp.SMTPClient.__init__(self, self.identity)
+ self.incoming = None
+ self.deferred = defer.Deferred()
+ self.deferred.addCallback(self.sentMail)
+ self.deferred.addErrback(self.sendError)
+
+ def getMailData(self):
+ """Gather all the data for building the response to the client.
+
+ This method must return a file-like object containing the data of the
+ message to be sent. Lines in the file should be delimited by ``\\n``.
+
+ :rtype: ``None`` or :class:`EmailResponse`
+ :returns: An ``EmailResponse``, if we have a response to send in reply
+ to the incoming email, otherwise, returns ``None``.
+ """
+ clients = self.getMailTo()
+ if not clients: return
+ client = clients[0] # There should have been only one anyway
+
+ # Log the email address that this message came from if SAFELOGGING is
+ # not enabled:
+ if not safelog.safe_logging:
+ logging.debug("Incoming email was from %s ..." % client)
+
+ if not self.runChecks(client): return
+
+ recipient = self.getMailFrom()
+ # Look up the locale part in the 'To:' address, if there is one, and
+ # get the appropriate Translation object:
+ lang = translations.getLocaleFromPlusAddr(recipient)
+ logging.info("Client requested email translation: %s" % lang)
+
+ body = createResponseBody(self.incoming.lines,
+ self.incoming.context,
+ client, lang)
+ if not body: return # The client was already warned.
+
+ messageID = self.incoming.message.getheader("Message-ID", None)
+ subject = self.incoming.message.getheader("Subject", None)
+ response = generateResponse(recipient, client,
+ body, subject, messageID,
+ self.incoming.context.gpgSignFunc)
+ return response
+
+ def getMailTo(self):
+ """Attempt to get the client's email address from an incoming email.
+
+ :rtype: list
+ :returns: A list containing the client's
+ :func:`normalized <bridgedb.parse.addr.normalizeEmail>` email
+ :api:`Address <twisted.mail.smtp.Address>`, if it originated from
+ a domain that we accept and the address was well-formed. Otherwise,
+ returns ``None``. Even though we're likely to respond to only one
+ client at a time, the return value of this method must be a list
+ in order to hook into the rest of
+ :api:`twisted.mail.smtp.SMTPClient` correctly.
+ """
+ clients = []
+ addrHeader = None
+ try: fromAddr = self.incoming.message.getaddr("From")[1]
+ except (IndexError, TypeError, AttributeError): pass
+ else: addrHeader = fromAddr
+
+ if not addrHeader:
+ logging.warn("No From header on incoming mail.")
+ try: senderHeader = self.incoming.message.getaddr("Sender")[1]
+ except (IndexError, TypeError, AttributeError): pass
+ else: addrHeader = senderHeader
+ if not addrHeader:
+ logging.warn("No Sender header on incoming mail.")
+ return clients
+
+ client = None
+ try:
+ if addrHeader in self.incoming.context.whitelist.keys():
+ logging.debug("Email address was whitelisted: %s."
+ % addrHeader)
+ client = smtp.Address(addrHeader)
+ else:
+ normalized = addr.normalizeEmail(
+ addrHeader,
+ self.incoming.context.domainMap,
+ self.incoming.context.domainRules)
+ client = smtp.Address(normalized)
+ except (addr.UnsupportedDomain) as error:
+ logging.warn(error)
+ except (addr.BadEmail, smtp.AddressError) as error:
+ logging.warn(error)
+
+ if client:
+ clients.append(client)
+
+ return clients
+
+ def getMailFrom(self):
+ """Find our address in the recipients list of the **incoming** message.
+
+ :rtype: str
+ :return: Our address from the recipients list. If we can't find it
+ return our default ``EMAIL_FROM_ADDRESS`` from the config file.
+ """
+ logging.debug("Searching for our email address in 'To:' header...")
+
+ ours = None
+
+ try:
+ ourAddress = smtp.Address(self.incoming.context.fromAddr)
+ allRecipients = self.incoming.message.getaddrlist("To")
+
+ for _, addr in allRecipients:
+ recipient = smtp.Address(addr)
+ if not ourAddress.domain in recipient.domain:
+ logging.debug(("Not our domain (%s) or subdomain, skipping"
+ " email address: %s")
+ % (ourAddress.domain, str(recipient)))
+ continue
+ # The recipient's username should at least start with ours,
+ # but it still might be a '+' address.
+ if not recipient.local.startswith(ourAddress.local):
+ logging.debug(("Username doesn't begin with ours, skipping"
+ " email address: %s") % str(recipient))
+ continue
+ # Only check the username before the first '+':
+ beforePlus = recipient.local.split('+', 1)[0]
+ if beforePlus == ourAddress.local:
+ ours = str(recipient)
+ if not ours:
+ raise addr.BadEmail(allRecipients)
+
+ except Exception as error:
+ logging.error(("Couldn't find our email address in incoming email "
+ "headers: %r" % error))
+ # Just return the email address that we're configured to use:
+ ours = self.incoming.context.fromAddr
+
+ logging.debug("Found our email address: %s." % ours)
+ return ours
+
+ def sentMail(self, success):
+ """Callback for a :api:`twisted.mail.smtp.SMTPSenderFactory`,
+ called when an attempt to send an email is completed.
+
+ If some addresses were accepted, code and resp are the response
+ to the DATA command. If no addresses were accepted, code is -1
+ and resp is an informative message.
+
+ :param int code: The code returned by the SMTP Server.
+ :param str resp: The string response returned from the SMTP Server.
+ :param int numOK: The number of addresses accepted by the remote host.
+ :param list addresses: A list of tuples (address, code, resp) listing
+ the response to each ``RCPT TO`` command.
+ :param log: The SMTP session log. (We don't use this, but it is sent
+ by :api:`twisted.mail.smtp.SMTPSenderFactory` nonetheless.)
+ """
+ numOk, addresses = success
+
+ for (address, code, resp) in addresses:
+ logging.info("Sent reply to %s" % address)
+ logging.debug("SMTP server response: %d %s" % (code, resp))
+
+ if self.debug:
+ for line in self.log.log:
+ if line:
+ logging.debug(line)
+
+ def sendError(self, fail):
+ """Errback for a :api:`twisted.mail.smtp.SMTPSenderFactory`.
+
+ :param fail: A :api:`twisted.python.failure.Failure` or a
+ :api:`twisted.mail.smtp.SMTPClientError` which occurred during the
+ transaction to send the outgoing email.
+ """
+ logging.debug("called with %r" % fail)
+
+ if isinstance(fail, failure.Failure):
+ error = fail.getTraceback() or "Unknown"
+ elif isinstance(fail, Exception):
+ error = fail
+ logging.error(error)
+
+ try:
+ # This handles QUIT commands, disconnecting, and closing the
+ # transport:
+ smtp.SMTPClient.sendError(self, fail)
+ # We might not have `transport` and `protocol` attributes, depending
+ # on when and where the error occurred, so just catch and log it:
+ except Exception as error:
+ logging.error(error)
+
+ def reply(self):
+ """Reply to an incoming email. Maybe.
+
+ If nothing is returned from either :func:`createResponseBody` or
+ :func:`generateResponse`, then the incoming email will not be
+ responded to at all. This can happen for several reasons, for example:
+ if the DKIM signature was invalid or missing, or if the incoming email
+ came from an unacceptable domain, or if there have been too many
+ emails from this client in the allotted time period.
+
+ :rtype: :api:`twisted.internet.defer.Deferred`
+ :returns: A ``Deferred`` which will callback when the response has
+ been successfully sent, or errback if an error occurred while
+ sending the email.
+ """
+ logging.info("Got an email; deciding whether to reply.")
+
+ response = self.getMailData()
+ if not response:
+ return self.deferred
+
+ return self.send(response)
+
+ def runChecks(self, client):
+ """Run checks on the incoming message, and only reply if they pass.
+
+ 1. Check if the client's address is whitelisted.
+
+ 2. If it's not whitelisted, check that the domain names, taken from
+ the SMTP ``MAIL FROM:`` command and the email ``'From:'`` header, can
+ be :func:`canonicalized <addr.canonicalizeEmailDomain>`.
+
+ 3. Check that those canonical domains match.
+
+ 4. If the incoming message is from a domain which supports DKIM
+ signing, then run :func:`bridgedb.email.dkim.checkDKIM` as well.
+
+ .. note:: Calling this method sets the ``canonicalFromEmail`` and
+ :data:``canonicalDomainRules`` attributes of the :data:`incoming`
+ message.
+
+ :param client: An :api:`twisted.mail.smtp.Address`, which contains
+ the client's email address, extracted from the ``'From:'`` header
+ from the incoming email.
+ :rtype: bool
+ :returns: ``False`` if the checks didn't pass, ``True`` otherwise.
+ """
+ # If the SMTP ``RCPT TO:`` domain name couldn't be canonicalized, then
+ # we *should* have bailed at the SMTP layer, but we'll reject this
+ # email again nonetheless:
+ if not self.incoming.canonicalFromSMTP:
+ logging.warn(("SMTP 'MAIL FROM' wasn't from a canonical domain "
+ "for email from %s") % str(client))
+ return False
+
+ # Allow whitelisted addresses through the canonicalization check:
+ if str(client) in self.incoming.context.whitelist.keys():
+ self.incoming.canonicalFromEmail = client.domain
+ logging.info("'From:' header contained whitelisted address: %s"
+ % str(client))
+ # Straight up reject addresses in the EMAIL_BLACKLIST config option:
+ elif str(client) in self.incoming.context.blacklist:
+ logging.info("'From:' header contained blacklisted address: %s")
+ return False
+ else:
+ logging.debug("Canonicalizing client email domain...")
+ try:
+ # The client's address was already checked to see if it came
+ # from a supported domain and is a valid email address in
+ # :meth:`getMailTo`, so we should just be able to re-extract
+ # the canonical domain safely here:
+ self.incoming.canonicalFromEmail = canonicalizeEmailDomain(
+ client.domain, self.incoming.canon)
+ logging.debug("Canonical email domain: %s"
+ % self.incoming.canonicalFromEmail)
+ except addr.UnsupportedDomain as error:
+ logging.info("Domain couldn't be canonicalized: %s"
+ % safelog.logSafely(client.domain))
+ return False
+
+ # The canonical domains from the SMTP ``MAIL FROM:`` and the email
+ # ``From:`` header should match:
+ if self.incoming.canonicalFromSMTP != self.incoming.canonicalFromEmail:
+ logging.error("SMTP/Email canonical domain mismatch!")
+ logging.debug("Canonical domain mismatch: %s != %s"
+ % (self.incoming.canonicalFromSMTP,
+ self.incoming.canonicalFromEmail))
+ #return False
+
+ self.incoming.domainRules = self.incoming.context.domainRules.get(
+ self.incoming.canonicalFromEmail, list())
+
+ # If the domain's ``domainRules`` say to check DKIM verification
+ # results, and those results look bad, reject this email:
+ if not dkim.checkDKIM(self.incoming.message, self.incoming.domainRules):
+ return False
+
+ # If fuzzy matching is enabled via the EMAIL_FUZZY_MATCH setting, then
+ # calculate the Levenshtein String Distance (see
+ # :func:`~bridgedb.util.levenshteinDistance`):
+ if self.incoming.context.fuzzyMatch != 0:
+ for blacklistedAddress in self.incoming.context.blacklist:
+ distance = levenshteinDistance(str(client), blacklistedAddress)
+ if distance <= self.incoming.context.fuzzyMatch:
+ logging.info("Fuzzy-matched %s to blacklisted address %s!"
+ % (self.incoming.canonicalFromEmail,
+ blacklistedAddress))
+ return False
+
+ return True
+
+ def send(self, response, retries=0, timeout=30, reaktor=reactor):
+ """Send our **response** in reply to :data:`incoming`.
+
+ :type client: :api:`twisted.mail.smtp.Address`
+ :param client: The email address of the client.
+ :param response: A :class:`EmailResponse`.
+ :param int retries: Try resending this many times. (default: ``0``)
+ :param int timeout: Timeout after this many seconds. (default: ``30``)
+ :rtype: :api:`Deferred <twisted.internet.defer.Deferred>`
+ :returns: Our :data:`deferred`.
+ """
+ logging.info("Sending reply to %s ..." % str(response.to))
+
+ factory = smtp.SMTPSenderFactory(self.incoming.context.smtpFromAddr,
+ str(response.to),
+ response,
+ self.deferred,
+ retries=retries,
+ timeout=timeout)
+ factory.domain = smtp.DNSNAME
+ reaktor.connectTCP(self.incoming.context.smtpServerIP,
+ self.incoming.context.smtpServerPort,
+ factory)
+ return self.deferred
diff --git a/bridgedb/email/distributor.py b/bridgedb/email/distributor.py
new file mode 100644
index 0000000..d8ea9bf
--- /dev/null
+++ b/bridgedb/email/distributor.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_distributor -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson
+# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""A :class:`~bridgedb.distribute.Distributor` which hands out
+:class:`bridges <bridgedb.bridges.Bridge>` to clients via an email interface.
+"""
+
+import logging
+import time
+
+import bridgedb.Storage
+
+from bridgedb.Bridges import BridgeRing
+from bridgedb.Bridges import FilteredBridgeSplitter
+from bridgedb.crypto import getHMAC
+from bridgedb.crypto import getHMACFunc
+from bridgedb.distribute import Distributor
+from bridgedb.filters import byFilters
+from bridgedb.filters import byIPv4
+from bridgedb.filters import byIPv6
+from bridgedb.filters import bySubring
+from bridgedb.parse import addr
+
+
+#: The minimum amount of time (in seconds) which must pass before a client who
+#: has previously been given an email response must wait before being eligible
+#: to receive another response.
+MAX_EMAIL_RATE = 3 * 3600
+
+
+class IgnoreEmail(addr.BadEmail):
+ """Raised when we get requests from this address after rate warning."""
+
+
+class TooSoonEmail(addr.BadEmail):
+ """Raised when we got a request from this address too recently."""
+
+
+class EmailRequestedHelp(Exception):
+ """Raised when a client has emailed requesting help."""
+
+
+class EmailRequestedKey(Exception):
+ """Raised when an incoming email requested a copy of our GnuPG keys."""
+
+
+class EmailDistributor(Distributor):
+ """Object that hands out bridges based on the email address of an incoming
+ request and the current time period.
+
+ :type hashring: :class:`~bridgedb.Bridges.BridgeRing`
+ :ivar hashring: A hashring to hold all the bridges we hand out.
+ """
+
+ #: The minimum amount of time (in seconds) which must pass before a client
+ #: who has previously been given an email response must wait before being
+ #: eligible to receive another response.
+ emailRateMax = MAX_EMAIL_RATE
+
+ def __init__(self, key, domainmap, domainrules,
+ answerParameters=None, whitelist=None):
+ """Create a bridge distributor which uses email.
+
+ :type emailHmac: callable
+ :param emailHmac: An hmac function used to order email addresses
+ within a ring. See :func:`~bridgedb.crypto.getHMACFunc`.
+ :param dict domainmap: A map from lowercase domains that we support
+ mail from to their canonical forms. See `EMAIL_DOMAIN_MAP` option
+ in `bridgedb.conf`.
+ :param domainrules: DOCDOC
+ :param answerParameters: DOCDOC
+ :type whitelist: dict or ``None``
+ :param whitelist: A dictionary that maps whitelisted email addresses
+ to GnuPG fingerprints.
+ """
+ super(EmailDistributor, self).__init__(key)
+
+ self.domainmap = domainmap
+ self.domainrules = domainrules
+ self.whitelist = whitelist or dict()
+ self.answerParameters = answerParameters
+
+ key1 = getHMAC(key, "Map-Addresses-To-Ring")
+ key2 = getHMAC(key, "Order-Bridges-In-Ring")
+
+ self.emailHmac = getHMACFunc(key1, hex=False)
+ #XXX cache options not implemented
+ self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5)
+
+ self.name = "Email"
+
+ def bridgesPerResponse(self, hashring=None):
+ return super(EmailDistributor, self).bridgesPerResponse(hashring)
+
+ def getBridges(self, bridgeRequest, interval, clock=None):
+ """Return a list of bridges to give to a user.
+
+ .. hint:: All checks on the email address (which should be stored in
+ the ``bridgeRequest.client`` attribute), such as checks for
+ whitelisting and canonicalization of domain name, are done in
+ :meth:`bridgedb.email.autoresponder.getMailTo` and
+ :meth:`bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
+
+ :type bridgeRequest:
+ :class:`~bridgedb.email.request.EmailBridgeRequest`
+ :param bridgeRequest: A
+ :class:`~bridgedb.bridgerequest.BridgeRequestBase` with the
+ :data:`~bridgedb.bridgerequest.BridgeRequestBase.client` attribute
+ set to a string containing the client's full, canonicalized email
+ address.
+ :param interval: The time period when we got this request. This can be
+ any string, so long as it changes with every period.
+ :type clock: :api:`twisted.internet.task.Clock`
+ :param clock: If given, use the clock to ask what time it is, rather
+ than :api:`time.time`. This should likely only be used for
+ testing.
+ :rtype: list or ``None``
+ :returns: A list of :class:`~bridgedb.bridges.Bridges` for the
+ ``bridgeRequest.client``, if allowed. Otherwise, returns ``None``.
+ """
+ if (not bridgeRequest.client) or (bridgeRequest.client == 'default'):
+ raise addr.BadEmail(
+ ("%s distributor can't get bridges for invalid email address: "
+ "%s") % (self.name, bridgeRequest.client), bridgeRequest.client)
+
+ logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
+
+ now = time.time()
+
+ if clock:
+ now = clock.seconds()
+
+ with bridgedb.Storage.getDB() as db:
+ wasWarned = db.getWarnedEmail(bridgeRequest.client)
+ lastSaw = db.getEmailTime(bridgeRequest.client)
+ if lastSaw is not None:
+ if bridgeRequest.client in self.whitelist:
+ logging.info(
+ "Whitelisted address %s was last seen %d seconds ago."
+ % (bridgeRequest.client, now - lastSaw))
+ elif (lastSaw + self.emailRateMax) >= now:
+ wait = (lastSaw + self.emailRateMax) - now
+ logging.info("Client %s must wait another %d seconds."
+ % (bridgeRequest.client, wait))
+ if wasWarned:
+ raise IgnoreEmail(
+ "Client %s was warned." % bridgeRequest.client,
+ bridgeRequest.client)
+ else:
+ logging.info("Sending duplicate request warning.")
+ db.setWarnedEmail(bridgeRequest.client, True, now)
+ db.commit()
+ raise TooSoonEmail("Must wait %d seconds" % wait,
+ bridgeRequest.client)
+ # warning period is over
+ elif wasWarned:
+ db.setWarnedEmail(bridgeRequest.client, False)
+
+ pos = self.emailHmac("<%s>%s" % (interval, bridgeRequest.client))
+
+ ring = None
+ filtres = frozenset(bridgeRequest.filters)
+ if filtres in self.hashring.filterRings:
+ logging.debug("Cache hit %s" % filtres)
+ _, ring = self.hashring.filterRings[filtres]
+ else:
+ logging.debug("Cache miss %s" % filtres)
+ key = getHMAC(self.key, "Order-Bridges-In-Ring")
+ ring = BridgeRing(key, self.answerParameters)
+ self.hashring.addRing(ring, filtres, byFilters(filtres),
+ populate_from=self.hashring.bridges)
+
+ returnNum = self.bridgesPerResponse(ring)
+ result = ring.getBridges(pos, returnNum)
+
+ db.setEmailTime(bridgeRequest.client, now)
+ db.commit()
+
+ return result
+
+ def cleanDatabase(self):
+ """Clear all emailed response and warning times from the database."""
+ logging.info(("Cleaning all response and warning times for the %s "
+ "distributor from the database...") % self.name)
+ with bridgedb.Storage.getDB() as db:
+ try:
+ db.cleanEmailedBridges(time.time() - self.emailRateMax)
+ db.cleanWarnedEmails(time.time() - self.emailRateMax)
+ except:
+ db.rollback()
+ raise
+ else:
+ db.commit()
+
+ def prepopulateRings(self):
+ """Prepopulate this distributor's hashrings and subhashrings with
+ bridges.
+ """
+ logging.info("Prepopulating %s distributor hashrings..." % self.name)
+
+ for filterFn in [byIPv4, byIPv6]:
+ ruleset = frozenset([filterFn])
+ key = getHMAC(self.key, "Order-Bridges-In-Ring")
+ ring = BridgeRing(key, self.answerParameters)
+ self.hashring.addRing(ring, ruleset, byFilters([filterFn]),
+ populate_from=self.hashring.bridges)
+
+ # Since prepopulateRings is called every half hour when the bridge
+ # descriptors are re-parsed, we should clean the database then.
+ self.cleanDatabase()
diff --git a/bridgedb/email/dkim.py b/bridgedb/email/dkim.py
new file mode 100644
index 0000000..d1075aa
--- /dev/null
+++ b/bridgedb/email/dkim.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_dkim -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# Matthew Finkel <sysrqb at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.email.dkim
+ :synopsis: Functions for checking DKIM verification results in email
+ headers.
+
+bridgedb.email.dkim
+===================
+
+Functions for checking DKIM verification results in email headers.
+
+::
+
+ bridgedb.email.dkim
+ |_ checkDKIM - Check the DKIM verification results header.
+..
+"""
+
+from __future__ import unicode_literals
+
+import logging
+
+
+def checkDKIM(message, rules):
+ """Check the DKIM verification results header.
+
+ This check is only run if the incoming email, **message**, originated from
+ a domain for which we're configured (in the ``EMAIL_DOMAIN_RULES``
+ dictionary in the config file) to check DKIM verification results for.
+
+ Returns ``False`` if:
+
+ 1. We're supposed to expect and check the DKIM headers for the
+ client's email provider domain.
+ 2. Those headers were *not* okay.
+
+ Otherwise, returns ``True``.
+
+ :type message: :api:`twisted.mail.smtp.rfc822.Message`
+ :param message: The incoming client request email, including headers.
+ :param dict rules: The list of configured ``EMAIL_DOMAIN_RULES`` for the
+ canonical domain which the client's email request originated from.
+ :rtype: bool
+ :returns: ``False`` if the checks failed, ``True`` otherwise.
+ """
+ logging.info("Checking DKIM verification results...")
+ logging.debug("Domain has rules: %s" % ', '.join(rules))
+
+ if 'dkim' in rules:
+ # getheader() returns the last of a given kind of header; we want
+ # to get the first, so we use getheaders() instead.
+ dkimHeaders = message.getheaders("X-DKIM-Authentication-Results")
+ dkimHeader = "<no header>"
+ if dkimHeaders:
+ dkimHeader = dkimHeaders[0]
+ if not dkimHeader.startswith("pass"):
+ logging.info("Rejecting bad DKIM header on incoming email: %r "
+ % dkimHeader)
+ return False
+ return True
diff --git a/bridgedb/email/request.py b/bridgedb/email/request.py
new file mode 100644
index 0000000..50fd32c
--- /dev/null
+++ b/bridgedb/email/request.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8; test-case-name: bridgedb.test.test_email_request; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# Matthew Finkel <sysrqb at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.email.request
+ :synopsis: Classes for parsing and storing information about requests for
+ bridges which are sent to the email distributor.
+
+bridgedb.email.request
+======================
+
+Classes for parsing and storing information about requests for bridges
+which are sent to the email distributor.
+
+::
+
+ bridgedb.email.request
+ | |_ determineBridgeRequestOptions - Figure out which filters to apply, or
+ | offer help.
+ |_ EmailBridgeRequest - A request for bridges which was received through
+ the email distributor.
+..
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+import re
+
+from bridgedb import bridgerequest
+from bridgedb.email.distributor import EmailRequestedHelp
+from bridgedb.email.distributor import EmailRequestedKey
+
+
+#: A regular expression for matching the Pluggable Transport method TYPE in
+#: emailed requests for Pluggable Transports.
+TRANSPORT_REGEXP = ".*transport ([a-z][_a-z0-9]*)"
+TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP)
+
+#: A regular expression that matches country codes in requests for unblocked
+#: bridges.
+UNBLOCKED_REGEXP = ".*unblocked ([a-z]{2,4})"
+UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP)
+
+
+def determineBridgeRequestOptions(lines):
+ """Figure out which :class:`Bridges.BridgeFilter`s to apply, or offer help.
+
+ .. note:: If any ``'transport TYPE'`` was requested, or bridges not
+ blocked in a specific CC (``'unblocked CC'``), then the ``TYPE``
+ and/or ``CC`` will *always* be stored as a *lowercase* string.
+
+ :param list lines: A list of lines from an email, including the headers.
+ :raises EmailRequestedHelp: if the client requested help.
+ :raises EmailRequestedKey: if the client requested our GnuPG key.
+ :rtype: :class:`EmailBridgeRequest`
+ :returns: A :class:`~bridgerequst.BridgeRequest` with all of the requested
+ parameters set. The returned ``BridgeRequest`` will have already had
+ its filters generated via :meth:`~EmailBridgeRequest.generateFilters`.
+ """
+ request = EmailBridgeRequest()
+ skippedHeaders = False
+
+ for line in lines:
+ line = line.strip().lower()
+ # Ignore all lines before the first empty line:
+ if not line: skippedHeaders = True
+ if not skippedHeaders: continue
+
+ if ("help" in line) or ("halp" in line):
+ raise EmailRequestedHelp("Client requested help.")
+
+ if "get" in line:
+ request.isValid(True)
+ logging.debug("Email request was valid.")
+ if "key" in line:
+ request.wantsKey(True)
+ raise EmailRequestedKey("Email requested a copy of our GnuPG key.")
+ if "ipv6" in line:
+ request.withIPv6()
+ if "transport" in line:
+ request.withPluggableTransportType(line)
+ if "unblocked" in line:
+ request.withoutBlockInCountry(line)
+
+ logging.debug("Generating hashring filters for request.")
+ request.generateFilters()
+ return request
+
+
+class EmailBridgeRequest(bridgerequest.BridgeRequestBase):
+ """We received a request for bridges through the email distributor."""
+
+ def __init__(self):
+ """Process a new bridge request received through the
+ :class:`~bridgedb.email.distributor.EmailDistributor`.
+ """
+ super(EmailBridgeRequest, self).__init__()
+ self._wantsKey = False
+
+ def wantsKey(self, wantsKey=None):
+ """Get or set whether this bridge request wanted our GnuPG key.
+
+ If called without parameters, this method will return the current
+ state, otherwise (if called with the **wantsKey** parameter set), it
+ will set the current state for whether or not this request wanted our
+ key.
+
+ :param bool wantsKey: If given, set the validity state of this
+ request. Otherwise, get the current state.
+ """
+ if wantsKey is not None:
+ self._wantsKey = bool(wantsKey)
+ return self._wantsKey
+
+ def withoutBlockInCountry(self, line):
+ """This request was for bridges not blocked in **country**.
+
+ Add any country code found in the **line** to the list of
+ ``notBlockedIn``. Currently, a request for a transport is recognized
+ if the email line contains the ``'unblocked'`` command.
+
+ :param str country: The line from the email wherein the client
+ requested some type of Pluggable Transport.
+ """
+ unblocked = None
+
+ logging.debug("Parsing 'unblocked' line: %r" % line)
+ try:
+ unblocked = UNBLOCKED_PATTERN.match(line).group(1)
+ except (TypeError, AttributeError):
+ pass
+
+ if unblocked:
+ self.notBlockedIn.append(unblocked)
+ logging.info("Email requested bridges not blocked in: %r"
+ % unblocked)
+
+ def withPluggableTransportType(self, line):
+ """This request included a specific Pluggable Transport identifier.
+
+ Add any Pluggable Transport method TYPE found in the **line** to the
+ list of ``transports``. Currently, a request for a transport is
+ recognized if the email line contains the ``'transport'`` command.
+
+ :param str line: The line from the email wherein the client
+ requested some type of Pluggable Transport.
+ """
+ transport = None
+ logging.debug("Parsing 'transport' line: %r" % line)
+
+ try:
+ transport = TRANSPORT_PATTERN.match(line).group(1)
+ except (TypeError, AttributeError):
+ pass
+
+ if transport:
+ self.transports.append(transport)
+ logging.info("Email requested transport type: %r" % transport)
diff --git a/bridgedb/email/server.py b/bridgedb/email/server.py
new file mode 100644
index 0000000..736c3f6
--- /dev/null
+++ b/bridgedb/email/server.py
@@ -0,0 +1,496 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_server -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# Matthew Finkel <sysrqb at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+"""
+.. py:module:: bridgedb.email.server
+ :synopsis: Servers which interface with clients and distribute bridges
+ over SMTP.
+
+bridgedb.email.server
+=====================
+
+Servers which interface with clients and distribute bridges over SMTP.
+
+::
+
+ bridgedb.email.server
+ | |_ addServer - Set up a SMTP server which listens on the configured
+ | EMAIL_PORT for incoming connections, and responds as
+ | necessary to requests for bridges.
+ |
+ |_ MailServerContext - Helper object that holds information used by the
+ | email subsystem.
+ |_ SMTPMessage - Plugs into Twisted Mail and receives an incoming message.
+ |_ SMTPIncomingDelivery - Plugs into SMTPIncomingServerFactory and handles
+ | SMTP commands for incoming connections.
+ |_ SMTPIncomingDeliveryFactory - Factory for SMTPIncomingDeliverys.
+ |_ SMTPIncomingServerFactory - Plugs into twisted.mail.smtp.SMTPFactory;
+ creates a new SMTPMessageDelivery, which
+ handles response email automation, whenever
+ we get a incoming connection on the SMTP port.
+..
+"""
+
+from __future__ import unicode_literals
+
+import logging
+import io
+import socket
+
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.internet.error import CannotListenError
+from twisted.internet.task import LoopingCall
+from twisted.mail import smtp
+from twisted.mail.smtp import rfc822date
+from twisted.python import failure
+
+from zope.interface import implements
+
+from bridgedb import __version__
+from bridgedb import safelog
+from bridgedb.crypto import initializeGnuPG
+from bridgedb.email import autoresponder
+from bridgedb.email import templates
+from bridgedb.email import request
+from bridgedb.parse import addr
+from bridgedb.parse.addr import UnsupportedDomain
+from bridgedb.parse.addr import canonicalizeEmailDomain
+from bridgedb.schedule import ScheduledInterval
+from bridgedb.schedule import Unscheduled
+
+
+class MailServerContext(object):
+ """Helper object that holds information used by email subsystem.
+
+ :ivar str username: Reject any RCPT TO lines that aren't to this
+ user. See the ``EMAIL_USERNAME`` option in the config file.
+ (default: ``'bridges'``)
+ :ivar int maximumSize: Reject any incoming emails longer than
+ this size (in bytes). (default: 3084 bytes).
+ :ivar int smtpPort: The port to use for outgoing SMTP.
+ :ivar str smtpServer: The IP address to use for outgoing SMTP.
+ :ivar str smtpFromAddr: Use this address in the raw SMTP ``MAIL FROM``
+ line for outgoing mail. (default: ``bridges at torproject.org``)
+ :ivar str fromAddr: Use this address in the email ``From:``
+ line for outgoing mail. (default: ``bridges at torproject.org``)
+ :ivar int nBridges: The number of bridges to send for each email.
+ :ivar list blacklist: A list of blacklisted email addresses, taken from
+ the ``EMAIL_BLACKLIST`` config setting.
+ :ivar int fuzzyMatch: An integer specifying the maximum Levenshtein
+ Distance from an incoming email address to a blacklisted email address
+ for the incoming email to be dropped.
+ :ivar gpg: A :class:`gnupg.GPG` interface_, as returned by
+ :func:`~bridgedb.crypto.initialiseGnuPG`, or ``None`` if we couldn't
+ initialize GnuPG for some reason.
+ :ivar gpgSignFunc: A callable which signs a message, e.g. the one returned
+ from :func:`~bridgedb.crypto.initialiseGnuPG`.
+ """
+
+ def __init__(self, config, distributor, schedule):
+ """Create a context for storing configs for email bridge distribution.
+
+ :type config: :class:`bridgedb.persistent.Conf`
+ :type distributor: :class:`~bridgedb.email.distributor.EmailDistributor`
+ :param distributor: The distributor will handle getting the correct
+ bridges (or none) for a client for us.
+ :type schedule: :class:`bridgedb.schedule.ScheduledInterval`
+ :param schedule: An interval-based scheduler, used to help the
+ :data:`distributor` know if we should give bridges to a client.
+ """
+ self.config = config
+ self.distributor = distributor
+ self.schedule = schedule
+
+ self.maximumSize = smtp.SMTP.MAX_LENGTH
+ self.includeFingerprints = config.EMAIL_INCLUDE_FINGERPRINTS
+ self.nBridges = config.EMAIL_N_BRIDGES_PER_ANSWER
+
+ self.username = (config.EMAIL_USERNAME or "bridges")
+ self.hostname = socket.gethostname()
+ self.fromAddr = (config.EMAIL_FROM_ADDR or "bridges at torproject.org")
+ self.smtpFromAddr = (config.EMAIL_SMTP_FROM_ADDR or self.fromAddr)
+ self.smtpServerPort = (config.EMAIL_SMTP_PORT or 25)
+ self.smtpServerIP = (config.EMAIL_SMTP_HOST or "127.0.0.1")
+
+ self.domainRules = config.EMAIL_DOMAIN_RULES or {}
+ self.domainMap = config.EMAIL_DOMAIN_MAP or {}
+ self.canon = self.buildCanonicalDomainMap()
+ self.whitelist = config.EMAIL_WHITELIST or {}
+ self.blacklist = config.EMAIL_BLACKLIST or []
+ self.fuzzyMatch = config.EMAIL_FUZZY_MATCH or 0
+
+ self.gpg, self.gpgSignFunc = initializeGnuPG(config)
+
+ def buildCanonicalDomainMap(self):
+ """Build a map for all email provider domains from which we will accept
+ emails to their canonical domain name.
+
+ .. note:: Be sure that ``MailServerContext.domainRules`` and
+ ``MailServerContext.domainMap`` are set appropriately before calling
+ this method.
+
+ This method is automatically called during initialisation, and the
+ resulting domain map is stored as ``MailServerContext.canon``.
+
+ :rtype: dict
+ :returns: A dictionary which maps all domains and subdomains which we
+ accept emails from to their second-level, canonical domain names.
+ """
+ canon = self.domainMap
+ for domain, rule in self.domainRules.items():
+ if domain not in canon.keys():
+ canon[domain] = domain
+ for domain in self.config.EMAIL_DOMAINS:
+ canon[domain] = domain
+ return canon
+
+
+class SMTPMessage(object):
+ """Plugs into the Twisted Mail and receives an incoming message.
+
+ :var list lines: A list of lines from an incoming email message.
+ :var int nBytes: The number of bytes received thus far.
+ :var bool ignoring: If ``True``, we're ignoring the rest of this message
+ because it exceeded :data:`MailServerContext.maximumSize`.
+ :var canonicalFromSMTP: See
+ :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
+ :var canonicalFromEmail: See
+ :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
+ :var canonicalDomainRules: See
+ :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
+ :var message: (:api:`twisted.mail.smtp.rfc822.Message` or ``None``) The
+ incoming email message.
+ :var responder: A :class:`~bridgedb.email.autoresponder.SMTPAutoresponder`
+ which parses and checks the incoming :data:`message`. If it decides to
+ do so, it will build a
+ :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.reply` email
+ and :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.send` it.
+ """
+ implements(smtp.IMessage)
+
+ def __init__(self, context, canonicalFromSMTP=None):
+ """Create a new SMTPMessage.
+
+ These are created automatically via
+ :class:`SMTPIncomingDelivery`.
+
+ :param context: The configured :class:`MailServerContext`.
+ :type canonicalFromSMTP: str or None
+ :param canonicalFromSMTP: The canonical domain which this message was
+ received from. For example, if ``'gmail.com'`` is the configured
+ canonical domain for ``'googlemail.com'`` and a message is
+ received from the latter domain, then this would be set to the
+ former.
+ """
+ self.context = context
+ self.canon = context.canon
+ self.canonicalFromSMTP = canonicalFromSMTP
+ self.canonicalFromEmail = None
+ self.canonicalDomainRules = None
+
+ self.lines = []
+ self.nBytes = 0
+ self.ignoring = False
+
+ self.message = None
+ self.responder = autoresponder.SMTPAutoresponder()
+ self.responder.incoming = self
+
+ def lineReceived(self, line):
+ """Called when we get another line of an incoming message."""
+ self.nBytes += len(line)
+ if self.nBytes > self.context.maximumSize:
+ self.ignoring = True
+ else:
+ self.lines.append(line)
+ if not safelog.safe_logging:
+ try:
+ ln = line.rstrip("\r\n").encode('utf-8', 'replace')
+ logging.debug("> %s" % ln)
+ except (UnicodeError, UnicodeDecodeError): # pragma: no cover
+ pass
+ except Exception as error: # pragma: no cover
+ logging.error("Error while trying to log incoming email")
+ logging.exception(error)
+
+ def eomReceived(self):
+ """Tell the :data:`responder` to reply when we receive an EOM."""
+ if not self.ignoring:
+ self.message = self.getIncomingMessage()
+ self.responder.reply()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ """Called if we die partway through reading a message."""
+ pass
+
+ def getIncomingMessage(self):
+ """Create and parse an :rfc:`2822` message object for all :data:`lines`
+ received thus far.
+
+ :rtype: :api:`twisted.mail.smtp.rfc822.Message`
+ :returns: A ``Message`` comprised of all lines received thus far.
+ """
+ rawMessage = io.StringIO()
+ for line in self.lines:
+ rawMessage.writelines(unicode(line.decode('utf8')) + u'\n')
+ rawMessage.seek(0)
+ return smtp.rfc822.Message(rawMessage)
+
+
+class SMTPIncomingDelivery(smtp.SMTP):
+ """Plugs into :class:`SMTPIncomingServerFactory` and handles SMTP commands
+ for incoming connections.
+
+ :type context: :class:`MailServerContext`
+ :var context: A context containing SMTP/Email configuration settings.
+ :var deferred: A :api:`deferred <twisted.internet.defer.Deferred>` which
+ will be returned when :meth:`reply` is called. Additional callbacks
+ may be set on this deferred in order to schedule additional actions
+ when the response is being sent.
+ :type fromCanonicalSMTP: str or ``None``
+ :var fromCanonicalSMTP: If set, this is the canonicalized domain name of
+ the address we received from incoming connection's ``MAIL FROM:``.
+ """
+ implements(smtp.IMessageDelivery)
+
+ context = None
+ deferred = defer.Deferred()
+ fromCanonicalSMTP = None
+
+ @classmethod
+ def setContext(cls, context):
+ """Set our :data:`context` to a new :class:`MailServerContext`."""
+ cls.context = context
+
+ def receivedHeader(self, helo, origin, recipients):
+ """Create the ``Received:`` header for an incoming email.
+
+ :type helo: tuple
+ :param helo: The lines received during SMTP client HELO.
+ :type origin: :api:`twisted.mail.smtp.Address`
+ :param origin: The email address of the sender.
+ :type recipients: list
+ :param recipients: A list of :api:`twisted.mail.smtp.User` instances.
+ """
+ helo_ = ' helo={0}'.format(helo[0]) if helo[0] else ''
+ from_ = 'from %s ([%s]%s)' % (helo[0], helo[1], helo_)
+ by_ = 'by %s with BridgeDB (%s)' % (smtp.DNSNAME, __version__)
+ for_ = 'for %s; %s ' % (' '.join(map(str, recipients)), rfc822date())
+ return str('Received: %s\n\t%s\n\t%s' % (from_, by_, for_))
+
+ def validateFrom(self, helo, origin):
+ """Validate the ``MAIL FROM:`` address on the incoming SMTP connection.
+
+ This is done at the SMTP layer. Meaning that if a Postfix or other
+ email server is proxying emails from the outside world to BridgeDB,
+ the :api:`origin.domain <twisted.email.smtp.Address.domain>` will be
+ set to the local hostname. Therefore, if the SMTP ``MAIL FROM:``
+ domain name is our own hostname (as returned from
+ :func:`socket.gethostname`) or our own FQDN, allow the connection.
+
+ Otherwise, if the ``MAIL FROM:`` domain has a canonical domain in our
+ mapping (taken from our :data:`context.canon`, which is taken in turn
+ from the ``EMAIL_DOMAIN_MAP``), then our :data:`fromCanonicalSMTP` is
+ set to that domain.
+
+ :type helo: tuple
+ :param helo: The lines received during SMTP client HELO.
+ :type origin: :api:`twisted.mail.smtp.Address`
+ :param origin: The email address we received this message from.
+ :raises: :api:`twisted.mail.smtp.SMTPBadSender` if the
+ ``origin.domain`` was neither our local hostname, nor one of the
+ canonical domains listed in :ivar:`context.canon`.
+ :rtype: :api:`twisted.mail.smtp.Address`
+ :returns: The ``origin``. We *must* return some non-``None`` data from
+ this method, or else Twisted will reply to the sender with a 503
+ error.
+ """
+ try:
+ if str(origin) in self.context.whitelist.keys():
+ logging.warn("Got SMTP 'MAIL FROM:' whitelisted address: %s."
+ % str(origin))
+ # We need to be certain later that when the fromCanonicalSMTP
+ # domain is checked again the email 'From:' canonical domain,
+ # that we allow whitelisted addresses through the check.
+ self.fromCanonicalSMTP = origin.domain
+ return origin
+ if ((origin.domain == self.context.hostname) or
+ (origin.domain == smtp.DNSNAME)):
+ self.fromCanonicalSMTP = origin.domain
+ else:
+ logging.debug("Canonicalizing client SMTP domain...")
+ canonical = canonicalizeEmailDomain(origin.domain,
+ self.context.canon)
+ logging.debug("Canonical SMTP domain: %r" % canonical)
+ self.fromCanonicalSMTP = canonical
+ except UnsupportedDomain as error:
+ logging.info(error)
+ raise smtp.SMTPBadSender(origin)
+ except Exception as error:
+ logging.exception(error)
+
+ # This method **cannot** return None, or it'll cause a 503 error.
+ return origin
+
+ def validateTo(self, user):
+ """Validate the SMTP ``RCPT TO:`` address for the incoming connection.
+
+ The local username and domain name to which this SMTP message is
+ addressed, after being stripped of any ``'+'`` aliases, **must** be
+ identical to those in the email address set our
+ ``EMAIL_SMTP_FROM_ADDR`` configuration file option.
+
+ :type user: :api:`twisted.mail.smtp.User`
+ :param user: Information about the user this SMTP message was
+ addressed to.
+ :raises: A :api:`twisted.mail.smtp.SMTPBadRcpt` if any of the above
+ conditions weren't met.
+ :rtype: callable
+ :returns: A parameterless function which returns an instance of
+ :class:`SMTPMessage`.
+ """
+ logging.debug("Validating SMTP 'RCPT TO:' email address...")
+
+ recipient = user.dest
+ ourAddress = smtp.Address(self.context.smtpFromAddr)
+
+ if not ((ourAddress.domain in recipient.domain) or
+ (recipient.domain == "bridgedb")):
+ logging.debug(("Not our domain (%s) or subdomain, skipping"
+ " SMTP 'RCPT TO' address: %s")
+ % (ourAddress.domain, str(recipient)))
+ raise smtp.SMTPBadRcpt(str(recipient))
+ # The recipient's username should at least start with ours,
+ # but it still might be a '+' address.
+ if not recipient.local.startswith(ourAddress.local):
+ logging.debug(("Username doesn't begin with ours, skipping"
+ " SMTP 'RCPT TO' address: %s") % str(recipient))
+ raise smtp.SMTPBadRcpt(str(recipient))
+ # Ignore everything after the first '+', if there is one.
+ beforePlus = recipient.local.split('+', 1)[0]
+ if beforePlus != ourAddress.local:
+ raise smtp.SMTPBadRcpt(str(recipient))
+
+ return lambda: SMTPMessage(self.context, self.fromCanonicalSMTP)
+
+
+class SMTPIncomingDeliveryFactory(object):
+ """Factory for :class:`SMTPIncomingDelivery` s.
+
+ This class is used to distinguish between different messages delivered
+ over the same connection. This can be used to optimize delivery of a
+ single message to multiple recipients, something which cannot be done by
+ :api:`IMessageDelivery <twisted.mail.smtp.IMessageDelivery>` implementors
+ due to their lack of information.
+
+ :var context: A :class:`MailServerContext` for storing configuration settings.
+ :var delivery: A :class:`SMTPIncomingDelivery` to deliver incoming
+ SMTP messages to.
+ """
+ implements(smtp.IMessageDeliveryFactory)
+
+ context = None
+ delivery = SMTPIncomingDelivery
+
+ def __init__(self):
+ logging.debug("%s created." % self.__class__.__name__)
+
+ @classmethod
+ def setContext(cls, context):
+ """Set our :data:`context` and the context for our :data:`delivery`."""
+ cls.context = context
+ cls.delivery.setContext(cls.context)
+
+ def getMessageDelivery(self):
+ """Get a new :class:`SMTPIncomingDelivery` instance."""
+ return self.delivery()
+
+
+class SMTPIncomingServerFactory(smtp.SMTPFactory):
+ """Plugs into :api:`twisted.mail.smtp.SMTPFactory`; creates a new
+ :class:`SMTPIncomingDeliveryFactory`, which handles response email
+ automation whenever we get a incoming connection on the SMTP port.
+
+ .. warning:: My :data:`context` isn't an OpenSSL context, as is used for
+ the :api:`twisted.mail.smtp.ESMTPSender`.
+
+ :ivar context: A :class:`MailServerContext` for storing configuration settings.
+ :ivar deliveryFactory: A :class:`SMTPIncomingDeliveryFactory` for
+ producing :class:`SMTPIncomingDelivery`s.
+ :ivar domain: :api:`Our FQDN <twisted.mail.smtp.DNSNAME>`.
+ :ivar int timeout: The number of seconds to wait, after the last chunk of
+ data was received, before raising a
+ :api:`SMTPTimeoutError <twisted.mail.smtp.SMTPTimeoutError>` for an
+ incoming connection.
+ :ivar protocol: :api:`SMTP <twisted.mail.smtp.SMTP>`
+ """
+
+ context = None
+ deliveryFactory = SMTPIncomingDeliveryFactory
+
+ def __init__(self, **kwargs):
+ smtp.SMTPFactory.__init__(self, **kwargs)
+ self.deliveryFactory = self.deliveryFactory()
+
+ @classmethod
+ def setContext(cls, context):
+ """Set :data:`context` and :data:`deliveryFactory`.context."""
+ cls.context = context
+ cls.deliveryFactory.setContext(cls.context)
+
+ def buildProtocol(self, addr):
+ p = smtp.SMTPFactory.buildProtocol(self, addr)
+ self.deliveryFactory.transport = p.transport # XXX is this set yet?
+ p.factory = self
+ p.deliveryFactory = self.deliveryFactory
+ return p
+
+
+def addServer(config, distributor):
+ """Set up a SMTP server which listens on the configured ``EMAIL_PORT`` for
+ incoming connections, and responds as necessary to requests for bridges.
+
+ :type config: :class:`bridgedb.configure.Conf`
+ :param config: A configuration object.
+ :type distributor: :class:`bridgedb.email.distributor.EmailDistributor`
+ :param dist: A distributor which will handle database interactions, and
+ will decide which bridges to give to who and when.
+ """
+ if config.EMAIL_ROTATION_PERIOD:
+ count, period = config.EMAIL_ROTATION_PERIOD.split()
+ schedule = ScheduledInterval(count, period)
+ else:
+ schedule = Unscheduled()
+
+ context = MailServerContext(config, distributor, schedule)
+ factory = SMTPIncomingServerFactory()
+ factory.setContext(context)
+
+ addr = config.EMAIL_BIND_IP or ""
+ port = config.EMAIL_PORT or 6725
+
+ try:
+ reactor.listenTCP(port, factory, interface=addr)
+ except CannotListenError as error: # pragma: no cover
+ logging.fatal(error)
+ raise SystemExit(error.message)
+
+ # Set up a LoopingCall to run every 30 minutes and forget old email times.
+ lc = LoopingCall(distributor.cleanDatabase)
+ lc.start(1800, now=False)
+
+ return factory
diff --git a/bridgedb/email/templates.py b/bridgedb/email/templates.py
new file mode 100644
index 0000000..6998716
--- /dev/null
+++ b/bridgedb/email/templates.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_templates -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.email.templates
+ :synopsis: Templates for formatting emails sent out by the email
+ distributor.
+
+bridgedb.email.templates
+========================
+
+Templates for formatting emails sent out by the email distributor.
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from datetime import datetime
+
+from bridgedb import strings
+from bridgedb.email.distributor import MAX_EMAIL_RATE
+
+
+def addCommands(template):
+ """Add some text telling a client about supported email command, as well as
+ which Pluggable Transports are currently available.
+ """
+ # Tell them about the various email commands:
+ cmdlist = []
+ cmdlist.append(template.gettext(strings.EMAIL_MISC_TEXT.get(3)))
+ for cmd, desc in strings.EMAIL_COMMANDS.items():
+ command = ' '
+ command += cmd
+ while not len(command) >= 25: # Align the command descriptions
+ command += ' '
+ command += template.gettext(desc)
+ cmdlist.append(command)
+
+ commands = "\n".join(cmdlist) + "\n\n"
+ # And include the currently supported transports:
+ commands += template.gettext(strings.EMAIL_MISC_TEXT.get(5))
+ commands += "\n"
+ for pt in strings._getSupportedTransports():
+ commands += ' ' + pt + "\n"
+
+ return commands
+
+def addGreeting(template, clientName=None, welcome=False):
+ greeting = ""
+
+ if not clientName:
+ greeting = template.gettext(strings.EMAIL_MISC_TEXT[7])
+ else:
+ greeting = template.gettext(strings.EMAIL_MISC_TEXT[6]) % clientName
+
+ if greeting:
+ if welcome:
+ greeting += u' '
+ greeting += template.gettext(strings.EMAIL_MISC_TEXT[4])
+ greeting += u'\n\n'
+
+ return greeting
+
+def addKeyfile(template):
+ return u'%s\n\n' % strings.BRIDGEDB_OPENPGP_KEY
+
+def addBridgeAnswer(template, answer):
+ # Give the user their bridges, i.e. the `answer`:
+ bridgeLines = template.gettext(strings.EMAIL_MISC_TEXT[0])
+ bridgeLines += u"\n\n"
+ bridgeLines += template.gettext(strings.EMAIL_MISC_TEXT[1])
+ bridgeLines += u"\n\n"
+ bridgeLines += u"%s\n\n" % answer
+
+ return bridgeLines
+
+def addHowto(template):
+ """Add help text on how to add bridges to Tor Browser.
+
+ :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation``
+ :param template: A gettext translations instance, optionally with fallback
+ languages set.
+ """
+ howToTBB = template.gettext(strings.HOWTO_TBB[1]) % strings.EMAIL_SPRINTF["HOWTO_TBB1"]
+ howToTBB += u'\n\n'
+ howToTBB += template.gettext(strings.HOWTO_TBB[2])
+ howToTBB += u'\n\n'
+ howToTBB += u'\n'.join(["> {0}".format(ln) for ln in
+ template.gettext(strings.HOWTO_TBB[3]).split('\n')])
+ howToTBB += u'\n\n'
+ howToTBB += template.gettext(strings.HOWTO_TBB[4])
+ howToTBB += u'\n\n'
+ howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB1")
+ howToTBB += u'\n\n'
+ return howToTBB
+
+def addFooter(template, clientAddress=None):
+ """Add a footer::
+
+ --
+ <3 BridgeDB
+ ________________________________________________________________________
+ Public Keys: https://bridges.torproject.org/keys
+
+ This email was generated with rainbows, unicorns, and sparkles
+ for alice at example.com on Friday, 09 May, 2014 at 18:59:39.
+
+
+ :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation``
+ :param template: A gettext translations instance, optionally with fallback
+ languages set.
+ :type clientAddress: :api:`twisted.mail.smtp.Address`
+ :param clientAddress: The client's email address which should be in the
+ ``To:`` header of the response email.
+ """
+ now = datetime.utcnow()
+ clientAddr = clientAddress.addrstr
+
+ footer = u' --\n'
+ footer += u' <3 BridgeDB\n'
+ footer += u'_' * 70
+ footer += u'\n'
+ footer += template.gettext(strings.EMAIL_MISC_TEXT[8])
+ footer += u': https://bridges.torproject.org/keys\n'
+ footer += template.gettext(strings.EMAIL_MISC_TEXT[9]) \
+ % (clientAddr,
+ now.strftime('%A, %d %B, %Y'),
+ now.strftime('%H:%M:%S'))
+ footer += u'\n\n'
+
+ return footer
+
+def buildKeyMessage(template, clientAddress=None):
+ message = addKeyfile(template)
+ message += addFooter(template, clientAddress)
+ return message
+
+def buildWelcomeText(template, clientAddress=None):
+ sections = []
+ sections.append(addGreeting(template, clientAddress.local, welcome=True))
+
+ commands = addCommands(template)
+ sections.append(commands)
+
+ # Include the same messages as the homepage of the HTTPS distributor:
+ welcome = template.gettext(strings.WELCOME[0]) % strings.EMAIL_SPRINTF["WELCOME0"]
+ welcome += template.gettext(strings.WELCOME[1])
+ welcome += template.gettext(strings.WELCOME[2]) % strings.EMAIL_SPRINTF["WELCOME2"]
+ sections.append(welcome)
+
+ message = u"\n\n".join(sections)
+ # Add the markdown links at the end:
+ message += strings.EMAIL_REFERENCE_LINKS.get("WELCOME0")
+ message += u"\n\n"
+ message += addFooter(template, clientAddress)
+
+ return message
+
+def buildAnswerMessage(template, clientAddress=None, answer=None):
+ try:
+ message = addGreeting(template, clientAddress.local)
+ message += addBridgeAnswer(template, answer)
+ message += addHowto(template)
+ message += u'\n\n'
+ message += addCommands(template)
+ message += u'\n\n'
+ message += addFooter(template, clientAddress)
+ except Exception as error: # pragma: no cover
+ logging.error("Error while formatting email message template:")
+ logging.exception(error)
+
+ return message
+
+def buildSpamWarning(template, clientAddress=None):
+ message = addGreeting(template, clientAddress.local)
+
+ try:
+ message += template.gettext(strings.EMAIL_MISC_TEXT[0])
+ message += u"\n\n"
+ message += template.gettext(strings.EMAIL_MISC_TEXT[2]) \
+ % str(MAX_EMAIL_RATE / 3600)
+ message += u"\n\n"
+ message += addFooter(template, clientAddress)
+ except Exception as error: # pragma: no cover
+ logging.error("Error while formatting email spam template:")
+ logging.exception(error)
+
+ return message
diff --git a/bridgedb/filters.py b/bridgedb/filters.py
new file mode 100644
index 0000000..ca9b673
--- /dev/null
+++ b/bridgedb/filters.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_filters ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+import logging
+
+from ipaddr import IPv4Address
+from ipaddr import IPv6Address
+
+from bridgedb.parse.addr import isIPv
+
+
+_cache = {}
+
+
+def bySubring(hmac, assigned, total):
+ """Create a filter function which filters for only the bridges which fall
+ into the same **assigned** subhashring (based on the results of an **hmac**
+ function).
+
+ :type hmac: callable
+ :param hmac: An HMAC function, i.e. as returned from
+ :func:`bridgedb.crypto.getHMACFunc`.
+ :param int assigned: The subring number that we wish to draw bridges from.
+ For example, if a user is assigned to subring 2of3 based on their IP
+ address, then this function should only return bridges which would
+ also be assigned to subring 2of3.
+ :param int total: The total number of subrings.
+ :rtype: callable
+ :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+ """
+ logging.debug(("Creating a filter for assigning bridges to subhashring "
+ "%s-of-%s...") % (assigned, total))
+
+ name = "-".join([str(hmac("")[:8]).encode('hex'),
+ str(assigned), "of", str(total)])
+ try:
+ return _cache[name]
+ except KeyError:
+ def _bySubring(bridge):
+ position = int(hmac(bridge.identity)[:8], 16)
+ which = (position % total) + 1
+ return True if which == assigned else False
+ # The `description` attribute must contain an `=`, or else
+ # dumpAssignments() will not work correctly.
+ setattr(_bySubring, "description", "ring=%d" % assigned)
+ _bySubring.__name__ = ("bySubring%sof%s" % (assigned, total))
+ _bySubring.name = name
+ _cache[name] = _bySubring
+ return _bySubring
+
+def byFilters(filtres):
+ """Returns a filter which filters by multiple **filtres**.
+
+ :type filtres: list
+ :param filtres: A list (or other iterable) of callables which some
+ :class:`~bridgedb.bridges.Bridge`s should be filtered according to.
+ :rtype: callable
+ :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+ """
+ name = []
+ for filtre in filtres:
+ name.extend(filtre.name.split(" "))
+ name = " ".join(set(name))
+
+ try:
+ return _cache[name]
+ except KeyError:
+ def _byFilters(bridge):
+ results = [f(bridge) for f in filtres]
+ if False in results:
+ return False
+ return True
+ setattr(_byFilters, "description",
+ " ".join([getattr(f, "description", "") for f in filtres]))
+ _byFilters.name = name
+ _cache[name] = _byFilters
+ return _byFilters
+
+def byIPv(ipVersion=None):
+ """Return ``True`` if at least one of the **bridge**'s addresses has the
+ specified **ipVersion**.
+
+ :param int ipVersion: Either ``4`` or ``6``.
+ """
+ if not ipVersion in (4, 6):
+ ipVersion = 4
+
+ name = "ipv%d" % ipVersion
+ try:
+ return _cache[name]
+ except KeyError:
+ def _byIPv(bridge):
+ if isIPv(ipVersion, bridge.address):
+ return True
+ else:
+ for address, port, version in bridge.allVanillaAddresses:
+ if version == ipVersion or isIPv(ipVersion, address):
+ return True
+ return False
+ setattr(_byIPv, "description", "ip=%d" % ipVersion)
+ _byIPv.__name__ = "byIPv%d()" % ipVersion
+ _byIPv.name = name
+ _cache[name] = _byIPv
+ return _byIPv
+
+byIPv4 = byIPv(4)
+byIPv6 = byIPv(6)
+
+def byTransport(methodname=None, ipVersion=None):
+ """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
+
+ The returned filter function should be called on a
+ :class:`~bridgedb.bridges.Bridge`. It returns ``True`` if the ``Bridge``
+ has a :class:`~bridgedb.bridges.PluggableTransport` such that:
+
+ 1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
+ **methodname**, and
+
+ 2. The :data:`~bridgedb.bridges.PluggableTransport.address`` version
+ matches the **ipVersion**.
+
+ :param str methodname: A Pluggable Transport
+ :data:`~bridge.bridges.PluggableTransport.methodname`.
+ :param int ipVersion: Either ``4`` or ``6``. The IP version that the
+ ``Bridge``'s ``PluggableTransport``
+ :data:`~bridgedb.bridges.PluggableTransport.address`` should have.
+ :rtype: callable
+ :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+ """
+ if not ipVersion in (4, 6):
+ ipVersion = 4
+ if not methodname:
+ return byIPv(ipVersion)
+
+ methodname = methodname.lower()
+ name = "transport-%s ipv%d" % (methodname, ipVersion)
+
+ try:
+ return _cache[name]
+ except KeyError:
+ def _byTransport(bridge):
+ for transport in bridge.transports:
+ if transport.methodname == methodname:
+ if transport.address.version == ipVersion:
+ return True
+ return False
+ setattr(_byTransport, "description", "transport=%s" % methodname)
+ _byTransport.__name__ = "byTransport(%s,%s)" % (methodname, ipVersion)
+ _byTransport.name = name
+ _cache[name] = _byTransport
+ return _byTransport
+
+def byNotBlockedIn(countryCode=None, methodname=None, ipVersion=4):
+ """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
+
+ If a Pluggable Transport **methodname** was not specified, the returned
+ filter function returns ``True`` if any of the ``Bridge``'s addresses or
+ :class:`~bridgedb.bridges.PluggableTransport` addresses aren't blocked in
+ **countryCode**. See :meth:`~bridgedb.bridges.Bridge.isBlockedIn`.
+
+ Otherwise, if a Pluggable Transport **methodname** was specified, it
+ returns ``True`` if the ``Bridge`` has a
+ :class:`~bridgedb.bridges.PluggableTransport` such that:
+
+ 1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
+ **methodname**,
+
+ 2. The :data:`~bridgedb.bridges.PluggableTransport.address.version``
+ equals the **ipVersion**, and isn't known to be blocked in
+ **countryCode**.
+
+ :type countryCode: str or ``None``
+ :param countryCode: A two-letter country code which the filtered
+ :class:`PluggableTransport`s should not be blocked in.
+ :param str methodname: A Pluggable Transport
+ :data:`~bridge.bridges.PluggableTransport.methodname`.
+ :param int ipVersion: Either ``4`` or ``6``. The IP version that the
+ ``Bridge``'s addresses should have.
+ :rtype: callable
+ :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+ """
+ if not ipVersion in (4, 6):
+ ipVersion = 4
+ if not countryCode:
+ return byTransport(methodname, ipVersion)
+
+ methodname = methodname.lower() if methodname else methodname
+ countryCode = countryCode.lower()
+
+ name = []
+ if methodname:
+ name.append("transport-%s" % methodname)
+ name.append("ipv%d" % ipVersion)
+ name.append("not-blocked-in-%s" % countryCode)
+ name = " ".join(name)
+
+ try:
+ return _cache[name]
+ except KeyError:
+ def _byNotBlockedIn(bridge):
+ if not methodname:
+ return not bridge.isBlockedIn(countryCode)
+ elif methodname == "vanilla":
+ if bridge.address.version == ipVersion:
+ if not bridge.addressIsBlockedIn(countryCode,
+ bridge.address,
+ bridge.orPort):
+ return True
+ else:
+ # Since bridge.transportIsBlockedIn() will return True if the
+ # bridge has that type of transport AND that transport is
+ # blocked, we can "fail fast" here by doing this faster check
+ # before iterating over all the transports testing for the
+ # other conditions.
+ if bridge.transportIsBlockedIn(countryCode, methodname):
+ return False
+ else:
+ for transport in bridge.transports:
+ if transport.methodname == methodname:
+ if transport.address.version == ipVersion:
+ return True
+ return False
+ setattr(_byNotBlockedIn, "description", "unblocked=%s" % countryCode)
+ _byNotBlockedIn.__name__ = ("byTransportNotBlockedIn(%s,%s,%s)"
+ % (methodname, countryCode, ipVersion))
+ _byNotBlockedIn.name = name
+ _cache[name] = _byNotBlockedIn
+ return _byNotBlockedIn
diff --git a/bridgedb/geo.py b/bridgedb/geo.py
new file mode 100644
index 0000000..c986fa1
--- /dev/null
+++ b/bridgedb/geo.py
@@ -0,0 +1,82 @@
+#
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""
+Boilerplate setup for GeoIP. GeoIP allows us to look up the country code
+associated with an IP address. This is a "pure" python version which interacts
+with the Maxmind GeoIP API (version 1). It requires, in Debian, the libgeoip-dev
+and geoip-database packages.
+"""
+
+import logging
+from os.path import isfile
+
+from ipaddr import IPv4Address, IPv6Address
+
+# IPv4 database
+GEOIP_DBFILE = '/usr/share/GeoIP/GeoIP.dat'
+# IPv6 database
+GEOIPv6_DBFILE = '/usr/share/GeoIP/GeoIPv6.dat'
+try:
+ # Make sure we have the database before trying to import the module:
+ if not (isfile(GEOIP_DBFILE) and isfile(GEOIPv6_DBFILE)): # pragma: no cover
+ raise EnvironmentError("Could not find %r. On Debian-based systems, "
+ "please install the geoip-database package."
+ % GEOIP_DBFILE)
+
+ import pygeoip
+ geoip = pygeoip.GeoIP(GEOIP_DBFILE, flags=pygeoip.MEMORY_CACHE)
+ geoipv6 = pygeoip.GeoIP(GEOIPv6_DBFILE, flags=pygeoip.MEMORY_CACHE)
+ logging.info("GeoIP databases loaded")
+except Exception as err: # pragma: no cover
+ logging.warn("Error while loading geoip module: %r" % err)
+ geoip = None
+ geoipv6 = None
+
+
+def getCountryCode(ip):
+ """Return the two-letter country code of a given IP address.
+
+ :type ip: :class:`ipaddr.IPAddress`
+ :param ip: An IPv4 OR IPv6 address.
+ :rtype: ``None`` or str
+
+ :returns: If the GeoIP databases are loaded, and the **ip** lookup is
+ successful, then this returns a two-letter country code. Otherwise,
+ this returns ``None``.
+ """
+ addr = None
+ version = None
+ try:
+ addr = ip.compressed
+ version = ip.version
+ except AttributeError as err:
+ logging.warn("Wrong type passed to getCountryCode: %s" % str(err))
+ return None
+
+ # GeoIP has two databases: one for IPv4 addresses, and one for IPv6
+ # addresses. This will ensure we use the correct one.
+ db = None
+ # First, make sure we loaded GeoIP properly.
+ if None in (geoip, geoipv6):
+ logging.warn("GeoIP databases aren't loaded; can't look up country code")
+ return None
+ else:
+ if version == 4:
+ db = geoip
+ else:
+ db = geoipv6
+
+ # Look up the country code of the address.
+ countryCode = db.country_code_by_addr(addr)
+ if countryCode:
+ logging.debug("Looked up country code: %s" % countryCode)
+ return countryCode
+ else:
+ logging.debug("Country code was not detected. IP: %s" % addr)
+ return None
diff --git a/bridgedb/https/__init__.py b/bridgedb/https/__init__.py
new file mode 100644
index 0000000..f1210ec
--- /dev/null
+++ b/bridgedb/https/__init__.py
@@ -0,0 +1 @@
+"""Servers for BridgeDB's HTTPS bridge distributor."""
diff --git a/bridgedb/https/distributor.py b/bridgedb/https/distributor.py
new file mode 100644
index 0000000..f8cf09d
--- /dev/null
+++ b/bridgedb/https/distributor.py
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_https_distributor -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson
+# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""A Distributor that hands out bridges through a web interface."""
+
+import ipaddr
+import logging
+
+import bridgedb.Storage
+
+from bridgedb import proxy
+from bridgedb.Bridges import BridgeRing
+from bridgedb.Bridges import FilteredBridgeSplitter
+from bridgedb.crypto import getHMAC
+from bridgedb.crypto import getHMACFunc
+from bridgedb.distribute import Distributor
+from bridgedb.filters import byIPv4
+from bridgedb.filters import byIPv6
+from bridgedb.filters import byFilters
+from bridgedb.filters import bySubring
+
+
+class HTTPSDistributor(Distributor):
+ """A Distributor that hands out bridges based on the IP address of an
+ incoming request and the current time period.
+
+ :type proxies: :class:`~bridgedb.proxies.ProxySet`
+ :ivar proxies: All known proxies, which we treat differently. See
+ :param:`proxies`.
+ :type hashring: :class:`bridgedb.Bridges.FilteredBridgeSplitter`
+ :ivar hashring: A hashring that assigns bridges to subrings with fixed
+ proportions. Used to assign bridges into the subrings of this
+ distributor.
+ """
+
+ def __init__(self, totalSubrings, key, proxies=None, answerParameters=None):
+ """Create a Distributor that decides which bridges to distribute based
+ upon the client's IP address and the current time.
+
+ :param int totalSubrings: The number of subhashrings to group clients
+ into. Note that if ``PROXY_LIST_FILES`` is set in bridgedb.conf,
+ then the actual number of clusters is one higher than
+ ``totalSubrings``, because the set of all known open proxies is
+ given its own subhashring.
+ :param bytes key: The master HMAC key for this distributor. All added
+ bridges are HMACed with this key in order to place them into the
+ hashrings.
+ :type proxies: :class:`~bridgedb.proxy.ProxySet`
+ :param proxies: A :class:`bridgedb.proxy.ProxySet` containing known
+ Tor Exit relays and other known proxies. These will constitute
+ the extra cluster, and any client requesting bridges from one of
+ these **proxies** will be distributed bridges from a separate
+ subhashring that is specific to Tor/proxy users.
+ :type answerParameters: :class:`bridgedb.Bridges.BridgeRingParameters`
+ :param answerParameters: A mechanism for ensuring that the set of
+ bridges that this distributor answers a client with fit certain
+ parameters, i.e. that an answer has "at least two obfsproxy
+ bridges" or "at least one bridge on port 443", etc.
+ """
+ super(HTTPSDistributor, self).__init__(key)
+ self.totalSubrings = totalSubrings
+ self.answerParameters = answerParameters
+
+ if proxies:
+ logging.info("Added known proxies to HTTPS distributor...")
+ self.proxies = proxies
+ self.totalSubrings += 1
+ self.proxySubring = self.totalSubrings
+ else:
+ logging.warn("No known proxies were added to HTTPS distributor!")
+ self.proxies = proxy.ProxySet()
+ self.proxySubring = 0
+
+ self.ringCacheSize = self.totalSubrings * 3
+
+ key2 = getHMAC(key, "Assign-Bridges-To-Rings")
+ key3 = getHMAC(key, "Order-Areas-In-Rings")
+ key4 = getHMAC(key, "Assign-Areas-To-Rings")
+
+ self._clientToPositionHMAC = getHMACFunc(key3, hex=False)
+ self._subnetToSubringHMAC = getHMACFunc(key4, hex=True)
+ self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize)
+ self.name = 'HTTPS'
+ logging.debug("Added %s to %s distributor." %
+ (self.hashring.__class__.__name__, self.name))
+
+ def bridgesPerResponse(self, hashring=None):
+ return super(HTTPSDistributor, self).bridgesPerResponse(hashring)
+
+ @classmethod
+ def getSubnet(cls, ip, usingProxy=False, proxySubnets=4):
+ """Map all clients whose **ip**s are within the same subnet to the same
+ arbitrary string.
+
+ .. hint:: For non-proxy IP addresses, any two IPv4 addresses within
+ the same ``/16`` subnet, or any two IPv6 addresses in the same
+ ``/32`` subnet, will get the same string.
+
+ Subnets for this distributor are grouped into the number of rings
+ specified by the ``N_IP_CLUSTERS`` configuration option, such that
+ Alice (with the address ``1.2.3.4`` and Bob (with the address
+ ``1.2.178.234``) are placed within the same cluster, but Carol (with
+ address ``1.3.11.33``) *might* end up in a different cluster.
+
+ >>> from bridgedb.https.distributor import HTTPSDistributor
+ >>> HTTPSDistributor.getSubnet('1.2.3.4')
+ '1.2.0.0/16'
+ >>> HTTPSDistributor.getSubnet('1.2.211.154')
+ '1.2.0.0/16'
+ >>> HTTPSDistributor.getSubnet('2001:f::bc1:b13:2808')
+ '2001:f::/32'
+ >>> HTTPSDistributor.getSubnet('2a00:c98:2030:a020:2::42')
+ '2a00:c98::/32'
+
+ :param str ip: A string representing an IPv4 or IPv6 address.
+ :param bool usingProxy: Set to ``True`` if the client was using one of
+ the known :data:`proxies`.
+ :param int proxySubnets: Place Tor/proxy users into this number of
+ "subnet" groups. This means that no matter how many different Tor
+ Exits or proxies a client uses, the most they can ever get is
+ **proxySubnets** different sets of bridge lines (per interval).
+ This parameter only has any effect when **usingProxy** is ``True``.
+ :rtype: str
+ :returns: The appropriately sized CIDR subnet representation of the **ip**.
+ """
+ if not usingProxy:
+ # We aren't using bridgedb.parse.addr.isIPAddress(ip,
+ # compressed=False) here because adding the string "False" into
+ # the map would land any and all clients whose IP address appeared
+ # to be invalid at the same position in a hashring.
+ address = ipaddr.IPAddress(ip)
+ if address.version == 6:
+ truncated = ':'.join(address.exploded.split(':')[:2])
+ subnet = str(ipaddr.IPv6Network(truncated + "::/32"))
+ else:
+ truncated = '.'.join(address.exploded.split('.')[:2])
+ subnet = str(ipaddr.IPv4Network(truncated + '.0.0/16'))
+ else:
+ group = (int(ipaddr.IPAddress(ip)) % 4) + 1
+ subnet = "proxy-group-%d" % group
+
+ logging.debug("Client IP was within area: %s" % subnet)
+ return subnet
+
+ def mapSubnetToSubring(self, subnet, usingProxy=False):
+ """Determine the correct subhashring for a client, based upon the
+ **subnet**.
+
+ :param str subnet: The subnet which contains the client's IP. See
+ :staticmethod:`getSubnet`.
+ :param bool usingProxy: Set to ``True`` if the client was using one of
+ the known :data:`proxies`.
+ """
+ # If the client wasn't using a proxy, select the client's subring
+ # based upon the client's subnet (modulo the total subrings):
+ if not usingProxy:
+ mod = self.totalSubrings
+ # If there is a proxy subring, don't count it for the modulus:
+ if self.proxySubring:
+ mod -= 1
+ return (int(self._subnetToSubringHMAC(subnet)[:8], 16) % mod) + 1
+ else:
+ return self.proxySubring
+
+ def mapClientToHashringPosition(self, interval, subnet):
+ """Map the client to a position on a (sub)hashring, based upon the
+ **interval** which the client's request occurred within, as well as
+ the **subnet** of the client's IP address.
+
+ .. note:: For an explanation of how **subnet** is determined, see
+ :staticmethod:`getSubnet`.
+
+ :param str interval: The interval which this client's request for
+ bridges took place within.
+ :param str subnet: A string representing the subnet containing the
+ client's IP address.
+ :rtype: int
+ :returns: The results of keyed HMAC, which should determine the
+ client's position in a (sub)hashring of bridges (and thus
+ determine which bridges they receive).
+ """
+ position = "<%s>%s" % (interval, subnet)
+ mapping = self._clientToPositionHMAC(position)
+ return mapping
+
+ def prepopulateRings(self):
+ """Prepopulate this distributor's hashrings and subhashrings with
+ bridges.
+
+ The hashring structure for this distributor is influenced by the
+ ``N_IP_CLUSTERS`` configuration option, as well as the number of
+ ``PROXY_LIST_FILES``.
+
+ Essentially, :data:`totalSubrings` is set to the specified
+ ``N_IP_CLUSTERS``. All of the ``PROXY_LIST_FILES``, plus the list of
+ Tor Exit relays (downloaded into memory with :script:`get-tor-exits`),
+ are stored in :data:`proxies`, and the latter is added as an
+ additional cluster (such that :data:`totalSubrings` becomes
+ ``N_IP_CLUSTERS + 1``). The number of subhashrings which this
+ :class:`Distributor` has active in its hashring is then
+ :data:`totalSubrings`, where the last cluster is reserved for all
+ :data:`proxies`.
+
+ As an example, if BridgeDB was configured with ``N_IP_CLUSTERS=4`` and
+ ``PROXY_LIST_FILES=["open-socks-proxies.txt"]``, then the total number
+ of subhashrings is fiveâââfour for the "clusters", and one for the
+ :data:`proxies`. Thus, the resulting hashring-subhashring structure
+ would look like:
+
+ +------------------+---------------------------------------------------+--------------
+ | | Directly connecting users | Tor / known |
+ | | | proxy users |
+ +------------------+------------+------------+------------+------------+-------------+
+ | Clusters | Cluster-1 | Cluster-2 | Cluster-3 | Cluster-4 | Cluster-5 |
+ +==================+============+============+============+============+=============+
+ | Subhashrings | | | | | |
+ | (total, assigned)| (5,1) | (5,2) | (5,3) | (5,4) | (5,5) |
+ +------------------+------------+------------+------------+------------+-------------+
+ | Filtered | (5,1)-IPv4 | (5,2)-IPv4 | (5,3)-IPv4 | (5,4)-IPv4 | (5,5)-IPv4 |
+ | Subhashrings | | | | | |
+ | bBy requested +------------+------------+------------+------------+-------------+
+ | bridge type) | (5,1)-IPv6 | (5,2)-IPv6 | (5,3)-IPv6 | (5,4)-IPv6 | (5,5)-IPv6 |
+ | | | | | | |
+ +------------------+------------+------------+------------+------------+-------------+
+
+ The "filtered subhashrings" are essentially filtered copies of their
+ respective subhashring, such that they only contain bridges which
+ support IPv4 or IPv6, respectively. Additionally, the contents of
+ ``(5,1)-IPv4`` and ``(5,1)-IPv6`` sets are *not* disjoint.
+
+ Thus, in this example, we end up with **10 total subhashrings**.
+ """
+ logging.info("Prepopulating %s distributor hashrings..." % self.name)
+
+ for filterFn in [byIPv4, byIPv6]:
+ for subring in range(1, self.totalSubrings + 1):
+ filters = self._buildHashringFilters([filterFn,], subring)
+ key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
+ ring = BridgeRing(key1, self.answerParameters)
+ # For consistency with previous implementation of this method,
+ # only set the "name" for "clusters" which are for this
+ # distributor's proxies:
+ if subring == self.proxySubring:
+ ring.setName('{0} Proxy Ring'.format(self.name))
+ self.hashring.addRing(ring, filters, byFilters(filters),
+ populate_from=self.hashring.bridges)
+
+ def insert(self, bridge):
+ """Assign a bridge to this distributor."""
+ self.hashring.insert(bridge)
+
+ def _buildHashringFilters(self, previousFilters, subring):
+ f = bySubring(self.hashring.hmac, subring, self.totalSubrings)
+ previousFilters.append(f)
+ return frozenset(previousFilters)
+
+ def getBridges(self, bridgeRequest, interval):
+ """Return a list of bridges to give to a user.
+
+ :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest`
+ :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase`
+ with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client`
+ attribute set to a string containing the client's IP address.
+ :param str interval: The time period when we got this request. This
+ can be any string, so long as it changes with every period.
+ :rtype: list
+ :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
+ the response. See
+ :meth:`bridgedb.https.server.WebResourceBridges.getBridgeRequestAnswer`
+ for an example of how this is used.
+ """
+ logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
+
+ if not len(self.hashring):
+ logging.warn("Bailing! Hashring has zero bridges!")
+ return []
+
+ usingProxy = False
+
+ # First, check if the client's IP is one of the known :data:`proxies`:
+ if bridgeRequest.client in self.proxies:
+ # The tag is a tag applied to a proxy IP address when it is added
+ # to the bridgedb.proxy.ProxySet. For Tor Exit relays, the default
+ # is 'exit_relay'. For other proxies loaded from the
+ # PROXY_LIST_FILES config option, the default tag is the full
+ # filename that the IP address originally came from.
+ usingProxy = True
+ tag = self.proxies.getTag(bridgeRequest.client)
+ logging.info("Client was from known proxy (tag: %s): %s" %
+ (tag, bridgeRequest.client))
+
+ subnet = self.getSubnet(bridgeRequest.client, usingProxy)
+ subring = self.mapSubnetToSubring(subnet, usingProxy)
+ position = self.mapClientToHashringPosition(interval, subnet)
+ filters = self._buildHashringFilters(bridgeRequest.filters, subring)
+
+ logging.debug("Client request within time interval: %s" % interval)
+ logging.debug("Assigned client to subhashring %d/%d" % (subring, self.totalSubrings))
+ logging.debug("Assigned client to subhashring position: %s" % position.encode('hex'))
+ logging.debug("Total bridges: %d" % len(self.hashring))
+ logging.debug("Bridge filters: %s" % ' '.join([x.func_name for x in filters]))
+
+ # Check wheth we have a cached copy of the hashring:
+ if filters in self.hashring.filterRings.keys():
+ logging.debug("Cache hit %s" % filters)
+ _, ring = self.hashring.filterRings[filters]
+ # Otherwise, construct a new hashring and populate it:
+ else:
+ logging.debug("Cache miss %s" % filters)
+ key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
+ ring = BridgeRing(key1, self.answerParameters)
+ self.hashring.addRing(ring, filters, byFilters(filters),
+ populate_from=self.hashring.bridges)
+
+ # Determine the appropriate number of bridges to give to the client:
+ returnNum = self.bridgesPerResponse(ring)
+ answer = ring.getBridges(position, returnNum)
+
+ return answer
diff --git a/bridgedb/https/request.py b/bridgedb/https/request.py
new file mode 100644
index 0000000..b106a13
--- /dev/null
+++ b/bridgedb/https/request.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8; test-case-name: bridgedb.test.test_https_request; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""
+.. py:module:: bridgedb.https.request
+ :synopsis: Classes for parsing and storing information about requests for
+ bridges which are sent to the HTTPS distributor.
+
+bridgedb.https.request
+======================
+
+Classes for parsing and storing information about requests for bridges
+which are sent to the HTTPS distributor.
+
+::
+
+ bridgedb.https.request
+ |
+ |_ HTTPSBridgeRequest - A request for bridges which was received through
+ the HTTPS distributor.
+..
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import ipaddr
+import logging
+import re
+
+from bridgedb import bridgerequest
+from bridgedb import geo
+from bridgedb.parse import addr
+
+
+#: A regular expression for matching the Pluggable Transport methodname in
+#: HTTP GET request parameters.
+TRANSPORT_REGEXP = "[_a-zA-Z][_a-zA-Z0-9]*"
+TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP)
+
+UNBLOCKED_REGEXP = "[a-zA-Z]{2}"
+UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP)
+
+
+class HTTPSBridgeRequest(bridgerequest.BridgeRequestBase):
+ """We received a request for bridges through the HTTPS distributor."""
+
+ def __init__(self, addClientCountryCode=True):
+ """Process a new bridge request received through the
+ :class:`~bridgedb.https.distributor.HTTPSDistributor`.
+
+ :param bool addClientCountryCode: If ``True``, then calling
+ :meth:`withoutBlockInCountry` will attempt to add the client's own
+ country code, geolocated from her IP, to the ``notBlockedIn``
+ countries list.
+ """
+ super(HTTPSBridgeRequest, self).__init__()
+ self.addClientCountryCode = addClientCountryCode
+
+ def withIPversion(self, parameters):
+ """Determine if the request **parameters** were for bridges with IPv6
+ addresses or not.
+
+ .. note:: If there is an ``ipv6=`` parameter with anything non-zero
+ after it, then we assume the client wanted IPv6 bridges.
+
+ :param parameters: The :api:`twisted.web.http.Request.args`.
+ """
+ if parameters.get("ipv6", False):
+ logging.info("HTTPS request for bridges with IPv6 addresses.")
+ self.withIPv6()
+
+ def withoutBlockInCountry(self, request):
+ """Determine which countries the bridges for this **request** should
+ not be blocked in.
+
+ .. note:: Currently, a **request** for unblocked bridges is recognized
+ if it contains an HTTP GET parameter ``unblocked=`` whose value is
+ a comma-separater list of two-letter country codes. Any
+ two-letter country code found in the
+ :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET
+ parameter will be added to the :data:`notBlockedIn` list.
+
+ If :data:`addClientCountryCode` is ``True``, the the client's own
+ geolocated country code will be added to the to the
+ :data`notBlockedIn` list.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object containing the HTTP method, full
+ URI, and any URL/POST arguments and headers present.
+ """
+ countryCodes = request.args.get("unblocked", list())
+
+ for countryCode in countryCodes:
+ try:
+ country = UNBLOCKED_PATTERN.match(countryCode).group()
+ except (TypeError, AttributeError):
+ pass
+ else:
+ if country:
+ self.notBlockedIn.append(country.lower())
+ logging.info("HTTPS request for bridges not blocked in: %r"
+ % country)
+
+ if self.addClientCountryCode:
+ # Look up the country code of the input IP, and request bridges
+ # not blocked in that country.
+ if addr.isIPAddress(self.client):
+ country = geo.getCountryCode(ipaddr.IPAddress(self.client))
+ if country:
+ self.notBlockedIn.append(country.lower())
+ logging.info(
+ ("HTTPS client's bridges also shouldn't be blocked "
+ "in their GeoIP country code: %s") % country)
+
+ def withPluggableTransportType(self, parameters):
+ """This request included a specific Pluggable Transport identifier.
+
+ Add any Pluggable Transport methodname found in the HTTP GET
+ **parameters** to the list of ``transports``. Currently, a request for
+ a transport is recognized if the request contains the
+ ``'transport='`` parameter.
+
+ :param parameters: The :api:`twisted.web.http.Request.args`.
+ """
+ for methodname in parameters.get("transport", list()):
+ try:
+ transport = TRANSPORT_PATTERN.match(methodname).group()
+ except (TypeError, AttributeError):
+ pass
+ else:
+ if transport:
+ self.transports.append(transport)
+ logging.info("HTTPS request for transport type: %r"
+ % transport)
diff --git a/bridgedb/https/server.py b/bridgedb/https/server.py
new file mode 100644
index 0000000..2106dbf
--- /dev/null
+++ b/bridgedb/https/server.py
@@ -0,0 +1,905 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_https_server -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see included AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+
+"""
+.. py:module:: bridgedb.https.server
+ :synopsis: Servers which interface with clients and distribute bridges
+ over HTTP(S).
+
+bridgedb.https.server
+=====================
+
+Servers which interface with clients and distribute bridges over HTTP(S).
+"""
+
+import base64
+import gettext
+import logging
+import random
+import re
+import time
+import os
+
+from functools import partial
+
+from ipaddr import IPv4Address
+
+import mako.exceptions
+from mako.template import Template
+from mako.lookup import TemplateLookup
+
+from twisted.internet import reactor
+from twisted.internet.error import CannotListenError
+from twisted.web import resource
+from twisted.web import static
+from twisted.web.server import NOT_DONE_YET
+from twisted.web.server import Site
+from twisted.web.util import redirectTo
+
+from bridgedb import captcha
+from bridgedb import crypto
+from bridgedb import strings
+from bridgedb import translations
+from bridgedb import txrecaptcha
+from bridgedb.https.request import HTTPSBridgeRequest
+from bridgedb.parse import headers
+from bridgedb.parse.addr import isIPAddress
+from bridgedb.qrcodes import generateQR
+from bridgedb.safelog import logSafely
+from bridgedb.schedule import Unscheduled
+from bridgedb.schedule import ScheduledInterval
+from bridgedb.util import replaceControlChars
+
+
+TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates')
+rtl_langs = ('ar', 'he', 'fa', 'gu_IN', 'ku')
+
+# Setting `filesystem_checks` to False is recommended for production servers,
+# due to potential speed increases. This means that the atimes of the Mako
+# template files aren't rechecked every time the template is requested
+# (otherwise, if they are checked, and the atime is newer, the template is
+# recompiled). `collection_size` sets the number of compiled templates which
+# are cached before the least recently used ones are removed. See:
+# http://docs.makotemplates.org/en/latest/usage.html#using-templatelookup
+lookup = TemplateLookup(directories=[TEMPLATE_DIR],
+ output_encoding='utf-8',
+ filesystem_checks=False,
+ collection_size=500)
+logging.debug("Set template root to %s" % TEMPLATE_DIR)
+
+
+def getClientIP(request, useForwardedHeader=False):
+ """Get the client's IP address from the :header:`X-Forwarded-For`
+ header, or from the :api:`request <twisted.web.server.Request>`.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for a
+ :api:`twisted.web.resource.Resource`.
+ :param bool useForwardedHeader: If ``True``, attempt to get the client's
+ IP address from the :header:`X-Forwarded-For` header.
+ :rtype: None or str
+ :returns: The client's IP address, if it was obtainable.
+ """
+ ip = None
+
+ if useForwardedHeader:
+ header = request.getHeader("X-Forwarded-For")
+ if header:
+ ip = header.split(",")[-1].strip()
+ if not isIPAddress(ip):
+ logging.warn("Got weird X-Forwarded-For value %r" % header)
+ ip = None
+ else:
+ ip = request.getClientIP()
+
+ return ip
+
+def replaceErrorPage(error, template_name=None):
+ """Create a general error page for displaying in place of tracebacks.
+
+ Log the error to BridgeDB's logger, and then display a very plain "Sorry!
+ Something went wrong!" page to the client.
+
+ :type error: :exc:`Exception`
+ :param error: Any exeption which has occurred while attempting to retrieve
+ a template, render a page, or retrieve a resource.
+ :param str template_name: A string describing which template/page/resource
+ was being used when the exception occurred,
+ i.e. ``'index.html'``.
+ :returns: A string containing HTML to serve to the client (rather than
+ serving a traceback).
+ """
+ logging.error("Error while attempting to render %s: %s"
+ % (template_name or 'template',
+ mako.exceptions.text_error_template().render()))
+
+ # TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+ # any string (regardless of capitalization and/or punctuation):
+ #
+ # "BridgeDB"
+ # "pluggable transport"
+ # "pluggable transports"
+ # "obfs2"
+ # "obfs3"
+ # "scramblesuit"
+ # "fteproxy"
+ # "Tor"
+ # "Tor Browser"
+ #
+ errmsg = _("Sorry! Something went wrong with your request.")
+ rendered = """<html>
+ <head>
+ <link href="/assets/bootstrap.min.css" rel="stylesheet">
+ <link href="/assets/custom.css" rel="stylesheet">
+ </head>
+ <body>
+ <p>{0}</p>
+ </body>
+ </html>""".format(errmsg)
+
+ return rendered
+
+
+class TranslatedTemplateResource(resource.Resource):
+ """A generalised resource which uses gettext translations and Mako
+ templates.
+ """
+ isLeaf = True
+
+ def __init__(self, template=None):
+ """Create a new :api:`~twisted.web.resource.Resource` for a
+ Mako-templated webpage.
+ """
+ gettext.install("bridgedb", unicode=True)
+ resource.Resource.__init__(self)
+ self.template = template
+
+ def render_GET(self, request):
+ rtl = False
+ try:
+ langs = translations.getLocaleFromHTTPRequest(request)
+ rtl = translations.usingRTLLang(langs)
+ template = lookup.get_template(self.template)
+ rendered = template.render(strings, rtl=rtl, lang=langs[0])
+ except Exception as err: # pragma: no cover
+ rendered = replaceErrorPage(err)
+ request.setHeader("Content-Type", "text/html; charset=utf-8")
+ return rendered
+
+ render_POST = render_GET
+
+
+class IndexResource(TranslatedTemplateResource):
+ """The parent resource of all other documents hosted by the webserver."""
+
+ def __init__(self):
+ """Create a :api:`twisted.web.resource.Resource` for the index page."""
+ TranslatedTemplateResource.__init__(self, 'index.html')
+
+
+class OptionsResource(TranslatedTemplateResource):
+ """A resource with additional options which a client may use to specify the
+ which bridge types should be returned by :class:`BridgesResource`.
+ """
+ def __init__(self):
+ """Create a :api:`twisted.web.resource.Resource` for the options page."""
+ TranslatedTemplateResource.__init__(self, 'options.html')
+
+
+class HowtoResource(TranslatedTemplateResource):
+ """A resource which explains how to use bridges."""
+
+ def __init__(self):
+ """Create a :api:`twisted.web.resource.Resource` for the HowTo page."""
+ TranslatedTemplateResource.__init__(self, 'howto.html')
+
+
+class CaptchaProtectedResource(resource.Resource):
+ """A general resource protected by some form of CAPTCHA."""
+
+ isLeaf = True
+
+ def __init__(self, publicKey=None, secretKey=None,
+ useForwardedHeader=False, protectedResource=None):
+ resource.Resource.__init__(self)
+ self.publicKey = publicKey
+ self.secretKey = secretKey
+ self.useForwardedHeader = useForwardedHeader
+ self.resource = protectedResource
+
+ def getClientIP(self, request):
+ """Get the client's IP address from the :header:`X-Forwarded-For`
+ header, or from the :api:`request <twisted.web.server.Request>`.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for a
+ :api:`twisted.web.resource.Resource`.
+ :rtype: None or str
+ :returns: The client's IP address, if it was obtainable.
+ """
+ return getClientIP(request, self.useForwardedHeader)
+
+ def getCaptchaImage(self, request=None):
+ """Get a CAPTCHA image.
+
+ :returns: A 2-tuple of ``(image, challenge)``, where ``image`` is a
+ binary, JPEG-encoded image, and ``challenge`` is a unique
+ string. If unable to retrieve a CAPTCHA, returns a tuple
+ containing two empty strings.
+ """
+ return ('', '')
+
+ def extractClientSolution(self, request):
+ """Extract the client's CAPTCHA solution from a POST request.
+
+ This is used after receiving a POST request from a client (which
+ should contain their solution to the CAPTCHA), to extract the solution
+ and challenge strings.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for 'bridges.html'.
+ :returns: A redirect for a request for a new CAPTCHA if there was a
+ problem. Otherwise, returns a 2-tuple of strings, the first is the
+ client's CAPTCHA solution from the text input area, and the second
+ is the challenge string.
+ """
+ try:
+ challenge = request.args['captcha_challenge_field'][0]
+ response = request.args['captcha_response_field'][0]
+ except Exception: # pragma: no cover
+ return redirectTo(request.URLPath(), request)
+ return (challenge, response)
+
+ def checkSolution(self, request):
+ """Override this method to check a client's CAPTCHA solution.
+
+ :rtype: bool
+ :returns: ``True`` if the client correctly solved the CAPTCHA;
+ ``False`` otherwise.
+ """
+ return False
+
+ def render_GET(self, request):
+ """Retrieve a ReCaptcha from the API server and serve it to the client.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for a page which should be
+ protected by a CAPTCHA.
+ :rtype: str
+ :returns: A rendered HTML page containing a ReCaptcha challenge image
+ for the client to solve.
+ """
+ rtl = False
+ image, challenge = self.getCaptchaImage(request)
+
+ try:
+ langs = translations.getLocaleFromHTTPRequest(request)
+ rtl = translations.usingRTLLang(langs)
+ # TODO: this does not work for versions of IE < 8.0
+ imgstr = 'data:image/jpeg;base64,%s' % base64.b64encode(image)
+ template = lookup.get_template('captcha.html')
+ rendered = template.render(strings,
+ rtl=rtl,
+ lang=langs[0],
+ imgstr=imgstr,
+ challenge_field=challenge)
+ except Exception as err:
+ rendered = replaceErrorPage(err, 'captcha.html')
+
+ request.setHeader("Content-Type", "text/html; charset=utf-8")
+ return rendered
+
+ def render_POST(self, request):
+ """Process a client's CAPTCHA solution.
+
+ If the client's CAPTCHA solution is valid (according to
+ :meth:`checkSolution`), process and serve their original
+ request. Otherwise, redirect them back to a new CAPTCHA page.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object, including POST arguments which
+ should include two key/value pairs: one key being
+ ``'captcha_challenge_field'``, and the other,
+ ``'captcha_response_field'``. These POST arguments should be
+ obtained from :meth:`render_GET`.
+ :rtype: str
+ :returns: A rendered HTML page containing a ReCaptcha challenge image
+ for the client to solve.
+ """
+ request.setHeader("Content-Type", "text/html; charset=utf-8")
+
+ if self.checkSolution(request) is True:
+ try:
+ rendered = self.resource.render(request)
+ except Exception as err:
+ rendered = replaceErrorPage(err)
+ return rendered
+
+ logging.debug("Client failed a CAPTCHA; returning redirect to %s"
+ % request.uri)
+ return redirectTo(request.uri, request)
+
+
+class GimpCaptchaProtectedResource(CaptchaProtectedResource):
+ """A web resource which uses a local cache of CAPTCHAs, generated with
+ gimp-captcha_, to protect another resource.
+
+ .. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
+ """
+
+ def __init__(self, hmacKey=None, captchaDir='', **kwargs):
+ """Protect a resource via this one, using a local CAPTCHA cache.
+
+ :param str secretkey: A PKCS#1 OAEP-padded, private RSA key, used for
+ verifying the client's solution to the CAPTCHA. See
+ :func:`bridgedb.crypto.getRSAKey` and the
+ ``GIMP_CAPTCHA_RSA_KEYFILE`` config setting.
+ :param str publickey: A PKCS#1 OAEP-padded, public RSA key, used for
+ creating the ``captcha_challenge_field`` string to give to a
+ client.
+ :param bytes hmacKey: The master HMAC key, used for validating CAPTCHA
+ challenge strings in :meth:`captcha.GimpCaptcha.check`. The file
+ where this key is stored can be set via the
+ ``GIMP_CAPTCHA_HMAC_KEYFILE`` option in the config file.
+ :param str captchaDir: The directory where the cached CAPTCHA images
+ are stored. See the ``GIMP_CAPTCHA_DIR`` config setting.
+ :param bool useForwardedHeader: If ``True``, obtain the client's IP
+ address from the ``X-Forwarded-For`` HTTP header.
+ :type protectedResource: :api:`twisted.web.resource.Resource`
+ :param protectedResource: The resource to serve if the client
+ successfully passes the CAPTCHA challenge.
+ """
+ CaptchaProtectedResource.__init__(self, **kwargs)
+ self.hmacKey = hmacKey
+ self.captchaDir = captchaDir
+
+ def checkSolution(self, request):
+ """Process a solved CAPTCHA via :meth:`bridgedb.captcha.GimpCaptcha.check`.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object, including POST arguments which
+ should include two key/value pairs: one key being
+ ``'captcha_challenge_field'``, and the other,
+ ``'captcha_response_field'``. These POST arguments should be
+ obtained from :meth:`render_GET`.
+ :rtupe: bool
+ :returns: True, if the CAPTCHA solution was valid; False otherwise.
+ """
+ valid = False
+ challenge, solution = self.extractClientSolution(request)
+ clientIP = self.getClientIP(request)
+ clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
+
+ try:
+ valid = captcha.GimpCaptcha.check(challenge, solution,
+ self.secretKey, clientHMACKey)
+ except captcha.CaptchaExpired as error:
+ logging.warn(error)
+ valid = False
+
+ logging.debug("%sorrect captcha from %r: %r."
+ % ("C" if valid else "Inc", clientIP, solution))
+ return valid
+
+ def getCaptchaImage(self, request):
+ """Get a random CAPTCHA image from our **captchaDir**.
+
+ Creates a :class:`~bridgedb.captcha.GimpCaptcha`, and calls its
+ :meth:`~bridgedb.captcha.GimpCaptcha.get` method to return a random
+ CAPTCHA and challenge string.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A client's initial request for some other resource
+ which is protected by this one (i.e. protected by a CAPTCHA).
+ :returns: A 2-tuple of ``(image, challenge)``, where::
+ - ``image`` is a string holding a binary, JPEG-encoded image.
+ - ``challenge`` is a unique string associated with the request.
+ """
+ # Create a new HMAC key, specific to requests from this client:
+ clientIP = self.getClientIP(request)
+ clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
+ capt = captcha.GimpCaptcha(self.publicKey, self.secretKey,
+ clientHMACKey, self.captchaDir)
+ try:
+ capt.get()
+ except captcha.GimpCaptchaError as error:
+ logging.error(error)
+ except Exception as error: # pragma: no cover
+ logging.error("Unhandled error while retrieving Gimp captcha!")
+ logging.exception(error)
+
+ return (capt.image, capt.challenge)
+
+ def render_GET(self, request):
+ """Get a random CAPTCHA from our local cache directory and serve it to
+ the client.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for a page which should be
+ protected by a CAPTCHA.
+ :rtype: str
+ :returns: A rendered HTML page containing a ReCaptcha challenge image
+ for the client to solve.
+ """
+ return CaptchaProtectedResource.render_GET(self, request)
+
+ def render_POST(self, request):
+ """Process a client's CAPTCHA solution.
+
+ If the client's CAPTCHA solution is valid (according to
+ :meth:`checkSolution`), process and serve their original
+ request. Otherwise, redirect them back to a new CAPTCHA page.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object, including POST arguments which
+ should include two key/value pairs: one key being
+ ``'captcha_challenge_field'``, and the other,
+ ``'captcha_response_field'``. These POST arguments should be
+ obtained from :meth:`render_GET`.
+ :rtype: str
+ :returns: A rendered HTML page containing a ReCaptcha challenge image
+ for the client to solve.
+ """
+ return CaptchaProtectedResource.render_POST(self, request)
+
+
+class ReCaptchaProtectedResource(CaptchaProtectedResource):
+ """A web resource which uses the reCaptcha_ service.
+
+ .. _reCaptcha: http://www.google.com/recaptcha
+ """
+
+ def __init__(self, remoteIP=None, **kwargs):
+ CaptchaProtectedResource.__init__(self, **kwargs)
+ self.remoteIP = remoteIP
+
+ def _renderDeferred(self, checkedRequest):
+ """Render this resource asynchronously.
+
+ :type checkedRequest: tuple
+ :param checkedRequest: A tuple of ``(bool, request)``, as returned
+ from :meth:`checkSolution`.
+ """
+ try:
+ valid, request = checkedRequest
+ except Exception as err:
+ logging.error("Error in _renderDeferred(): %s" % err)
+ return
+
+ logging.debug("Attemping to render %svalid request %r"
+ % ('' if valid else 'in', request))
+ if valid is True:
+ try:
+ rendered = self.resource.render(request)
+ except Exception as err: # pragma: no cover
+ rendered = replaceErrorPage(err)
+ else:
+ logging.info("Client failed a CAPTCHA; redirecting to %s"
+ % request.uri)
+ rendered = redirectTo(request.uri, request)
+
+ try:
+ request.write(rendered)
+ request.finish()
+ except Exception as err: # pragma: no cover
+ logging.exception(err)
+
+ return request
+
+ def getCaptchaImage(self, request):
+ """Get a CAPTCHA image from the remote reCaptcha server.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A client's initial request for some other resource
+ which is protected by this one (i.e. protected by a CAPTCHA).
+ :returns: A 2-tuple of ``(image, challenge)``, where::
+ - ``image`` is a string holding a binary, JPEG-encoded image.
+ - ``challenge`` is a unique string associated with the request.
+ """
+ capt = captcha.ReCaptcha(self.publicKey, self.secretKey)
+
+ try:
+ capt.get()
+ except Exception as error:
+ logging.fatal("Connection to Recaptcha server failed: %s" % error)
+
+ if capt.image is None:
+ logging.warn("No CAPTCHA image received from ReCaptcha server!")
+
+ return (capt.image, capt.challenge)
+
+ def getRemoteIP(self):
+ """Mask the client's real IP address with a faked one.
+
+ The fake client IP address is sent to the reCaptcha server, and it is
+ either the public IP address of bridges.torproject.org (if the config
+ option ``RECAPTCHA_REMOTE_IP`` is configured), or a random IP.
+
+ :rtype: str
+ :returns: A fake IP address to report to the reCaptcha API server.
+ """
+ if self.remoteIP:
+ remoteIP = self.remoteIP
+ else:
+ # generate a random IP for the captcha submission
+ remoteIP = IPv4Address(random.randint(0, 2**32-1)).compressed
+
+ return remoteIP
+
+ def checkSolution(self, request):
+ """Process a solved CAPTCHA by sending it to the ReCaptcha server.
+
+ The client's IP address is not sent to the ReCaptcha server; instead,
+ a completely random IP is generated and sent instead.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object, including POST arguments which
+ should include two key/value pairs: one key being
+ ``'captcha_challenge_field'``, and the other,
+ ``'captcha_response_field'``. These POST arguments should be
+ obtained from :meth:`render_GET`.
+ :rtupe: :api:`twisted.internet.defer.Deferred`
+ :returns: A deferred which will callback with a tuple in the following
+ form:
+ (:type:`bool`, :api:`twisted.web.server.Request`)
+ If the CAPTCHA solution was valid, a tuple will contain::
+ (True, Request)
+ Otherwise, it will contain::
+ (False, Request)
+ """
+ challenge, response = self.extractClientSolution(request)
+ clientIP = self.getClientIP(request)
+ remoteIP = self.getRemoteIP()
+
+ logging.debug("Captcha from %r. Parameters: %r"
+ % (clientIP, request.args))
+
+ def checkResponse(solution, request):
+ """Check the :class:`txrecaptcha.RecaptchaResponse`.
+
+ :type solution: :class:`txrecaptcha.RecaptchaResponse`.
+ :param solution: The client's CAPTCHA solution, after it has been
+ submitted to the reCaptcha API server.
+ """
+ # This valid CAPTCHA result from this function cannot be reliably
+ # unittested, because it's callbacked to from the deferred
+ # returned by ``txrecaptcha.submit``, the latter of which would
+ # require networking (as well as automated CAPTCHA
+ # breaking). Hence, the 'no cover' pragma.
+ if solution.is_valid: # pragma: no cover
+ logging.info("Valid CAPTCHA solution from %r." % clientIP)
+ return (True, request)
+ else:
+ logging.info("Invalid CAPTCHA solution from %r: %r"
+ % (clientIP, solution.error_code))
+ return (False, request)
+
+ d = txrecaptcha.submit(challenge, response, self.secretKey,
+ remoteIP).addCallback(checkResponse, request)
+ return d
+
+ def render_GET(self, request):
+ """Retrieve a ReCaptcha from the API server and serve it to the client.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for 'bridges.html'.
+ :rtype: str
+ :returns: A rendered HTML page containing a ReCaptcha challenge image
+ for the client to solve.
+ """
+ return CaptchaProtectedResource.render_GET(self, request)
+
+ def render_POST(self, request):
+ """Process a client's CAPTCHA solution.
+
+ If the client's CAPTCHA solution is valid (according to
+ :meth:`checkSolution`), process and serve their original
+ request. Otherwise, redirect them back to a new CAPTCHA page.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object, including POST arguments which
+ should include two key/value pairs: one key being
+ ``'captcha_challenge_field'``, and the other,
+ ``'captcha_response_field'``. These POST arguments should be
+ obtained from :meth:`render_GET`.
+ :returns: :api:`twisted.web.server.NOT_DONE_YET`, in order to handle
+ the ``Deferred`` returned from :meth:`checkSolution`. Eventually,
+ when the ``Deferred`` request is done being processed,
+ :meth:`_renderDeferred` will handle rendering and displaying the
+ HTML to the client.
+ """
+ d = self.checkSolution(request)
+ d.addCallback(self._renderDeferred)
+ return NOT_DONE_YET
+
+
+class BridgesResource(resource.Resource):
+ """This resource displays bridge lines in response to a request."""
+
+ isLeaf = True
+
+ def __init__(self, distributor, schedule, N=1, useForwardedHeader=False,
+ includeFingerprints=True):
+ """Create a new resource for displaying bridges to a client.
+
+ :type distributor: :class:`HTTPSDistributor`
+ :param distributor: The mechanism to retrieve bridges for this
+ distributor.
+ :type schedule: :class:`~bridgedb.schedule.ScheduledInterval`
+ :param schedule: The time period used to tweak the bridge selection
+ procedure.
+ :param int N: The number of bridges to hand out per query.
+ :param bool useForwardedHeader: Whether or not we should use the the
+ X-Forwarded-For header instead of the source IP address.
+ :param bool includeFingerprints: Do we include the bridge's
+ fingerprint in the response?
+ """
+ gettext.install("bridgedb", unicode=True)
+ resource.Resource.__init__(self)
+ self.distributor = distributor
+ self.schedule = schedule
+ self.nBridgesToGive = N
+ self.useForwardedHeader = useForwardedHeader
+ self.includeFingerprints = includeFingerprints
+
+ def render(self, request):
+ """Render a response for a client HTTP request.
+
+ Presently, this method merely wraps :meth:`getBridgeRequestAnswer` to
+ catch any unhandled exceptions which occur (otherwise the server will
+ display the traceback to the client). If an unhandled exception *does*
+ occur, the client will be served the default "No bridges currently
+ available" HTML response page.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object containing the HTTP method, full
+ URI, and any URL/POST arguments and headers present.
+ :rtype: str
+ :returns: A plaintext or HTML response to serve.
+ """
+ try:
+ response = self.getBridgeRequestAnswer(request)
+ except Exception as err:
+ logging.exception(err)
+ response = self.renderAnswer(request)
+
+ return response
+
+ def getClientIP(self, request):
+ """Get the client's IP address from the :header:`X-Forwarded-For`
+ header, or from the :api:`request <twisted.web.server.Request>`.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object for a
+ :api:`twisted.web.resource.Resource`.
+ :rtype: None or str
+ :returns: The client's IP address, if it was obtainable.
+ """
+ return getClientIP(request, self.useForwardedHeader)
+
+ def getBridgeRequestAnswer(self, request):
+ """Respond to a client HTTP request for bridges.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object containing the HTTP method, full
+ URI, and any URL/POST arguments and headers present.
+ :rtype: str
+ :returns: A plaintext or HTML response to serve.
+ """
+ bridgeLines = None
+ interval = self.schedule.intervalStart(time.time())
+ ip = self.getClientIP(request)
+
+ logging.info("Replying to web request from %s. Parameters were %r"
+ % (ip, request.args))
+
+ if ip:
+ bridgeRequest = HTTPSBridgeRequest()
+ bridgeRequest.client = ip
+ bridgeRequest.isValid(True)
+ bridgeRequest.withIPversion(request.args)
+ bridgeRequest.withPluggableTransportType(request.args)
+ bridgeRequest.withoutBlockInCountry(request)
+ bridgeRequest.generateFilters()
+
+ bridges = self.distributor.getBridges(bridgeRequest, interval)
+ bridgeLines = [replaceControlChars(bridge.getBridgeLine(
+ bridgeRequest, self.includeFingerprints)) for bridge in bridges]
+
+ return self.renderAnswer(request, bridgeLines)
+
+ def getResponseFormat(self, request):
+ """Determine the requested format for the response.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object containing the HTTP method, full
+ URI, and any URL/POST arguments and headers present.
+ :rtype: ``None`` or str
+ :returns: The argument of the first occurence of the ``format=`` HTTP
+ GET parameter, if any were present. (The only one which currently
+ has any effect is ``format=plain``, see note in
+ :meth:`renderAnswer`.) Otherwise, returns ``None``.
+ """
+ format = request.args.get("format", None)
+ if format and len(format):
+ format = format[0] # Choose the first arg
+ return format
+
+ def renderAnswer(self, request, bridgeLines=None):
+ """Generate a response for a client which includes **bridgesLines**.
+
+ .. note: The generated response can be plain or HTML. A plain response
+ looks like::
+
+ voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF
+ voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567
+
+ That is, there is no HTML, what you see is what you get, and what
+ you get is suitable for pasting directly into Tor Launcher (or
+ into a torrc, if you prepend ``"Bridge "`` to each line). The
+ plain format can be requested from BridgeDB's web service by
+ adding an ``&format=plain`` HTTP GET parameter to the URL. Also
+ note that you won't get a QRCode, usage instructions, error
+ messages, or any other fanciness if you use the plain format.
+
+ :type request: :api:`twisted.web.http.Request`
+ :param request: A ``Request`` object containing the HTTP method, full
+ URI, and any URL/POST arguments and headers present.
+ :type bridgeLines: list or None
+ :param bridgeLines: A list of strings used to configure a Tor client
+ to use a bridge. If ``None``, then the returned page will instead
+ explain that there were no bridges of the type they requested,
+ with instructions on how to proceed.
+ :rtype: str
+ :returns: A plaintext or HTML response to serve.
+ """
+ rtl = False
+ format = self.getResponseFormat(request)
+
+ if format == 'plain':
+ request.setHeader("Content-Type", "text/plain")
+ try:
+ rendered = bytes('\n'.join(bridgeLines))
+ except Exception as err:
+ rendered = replaceErrorPage(err)
+ else:
+ request.setHeader("Content-Type", "text/html; charset=utf-8")
+ qrcode = None
+ qrjpeg = generateQR(bridgeLines)
+
+ if qrjpeg:
+ qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)
+ try:
+ langs = translations.getLocaleFromHTTPRequest(request)
+ rtl = translations.usingRTLLang(langs)
+ template = lookup.get_template('bridges.html')
+ rendered = template.render(strings,
+ rtl=rtl,
+ lang=langs[0],
+ answer=bridgeLines,
+ qrcode=qrcode)
+ except Exception as err:
+ rendered = replaceErrorPage(err)
+
+ return rendered
+
+
+def addWebServer(config, distributor):
+ """Set up a web server for HTTP(S)-based bridge distribution.
+
+ :type config: :class:`bridgedb.persistent.Conf`
+ :param config: A configuration object from
+ :mod:`bridgedb.Main`. Currently, we use these options::
+ HTTP_UNENCRYPTED_PORT
+ HTTP_UNENCRYPTED_BIND_IP
+ HTTP_USE_IP_FROM_FORWARDED_HEADER
+ HTTPS_N_BRIDGES_PER_ANSWER
+ HTTPS_INCLUDE_FINGERPRINTS
+ HTTPS_KEY_FILE
+ HTTPS_CERT_FILE
+ HTTPS_PORT
+ HTTPS_BIND_IP
+ HTTPS_USE_IP_FROM_FORWARDED_HEADER
+ HTTPS_ROTATION_PERIOD
+ RECAPTCHA_ENABLED
+ RECAPTCHA_PUB_KEY
+ RECAPTCHA_SEC_KEY
+ RECAPTCHA_REMOTEIP
+ GIMP_CAPTCHA_ENABLED
+ GIMP_CAPTCHA_DIR
+ GIMP_CAPTCHA_HMAC_KEYFILE
+ GIMP_CAPTCHA_RSA_KEYFILE
+ :type distributor: :class:`bridgedb.https.distributor.HTTPSDistributor`
+ :param distributor: A bridge distributor.
+ :raises SystemExit: if the servers cannot be started.
+ :rtype: :api:`twisted.web.server.Site`
+ :returns: A webserver.
+ """
+ captcha = None
+ fwdHeaders = config.HTTP_USE_IP_FROM_FORWARDED_HEADER
+ numBridges = config.HTTPS_N_BRIDGES_PER_ANSWER
+ fprInclude = config.HTTPS_INCLUDE_FINGERPRINTS
+
+ logging.info("Starting web servers...")
+
+ index = IndexResource()
+ options = OptionsResource()
+ howto = HowtoResource()
+ robots = static.File(os.path.join(TEMPLATE_DIR, 'robots.txt'))
+ assets = static.File(os.path.join(TEMPLATE_DIR, 'assets/'))
+ keys = static.Data(bytes(strings.BRIDGEDB_OPENPGP_KEY), 'text/plain')
+
+ root = resource.Resource()
+ root.putChild('', index)
+ root.putChild('robots.txt', robots)
+ root.putChild('keys', keys)
+ root.putChild('assets', assets)
+ root.putChild('options', options)
+ root.putChild('howto', howto)
+
+ if config.RECAPTCHA_ENABLED:
+ publicKey = config.RECAPTCHA_PUB_KEY
+ secretKey = config.RECAPTCHA_SEC_KEY
+ captcha = partial(ReCaptchaProtectedResource,
+ remoteIP=config.RECAPTCHA_REMOTEIP)
+ elif config.GIMP_CAPTCHA_ENABLED:
+ # Get the master HMAC secret key for CAPTCHA challenges, and then
+ # create a new HMAC key from it for use on the server.
+ captchaKey = crypto.getKey(config.GIMP_CAPTCHA_HMAC_KEYFILE)
+ hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
+ # Load or create our encryption keys:
+ secretKey, publicKey = crypto.getRSAKey(config.GIMP_CAPTCHA_RSA_KEYFILE)
+ captcha = partial(GimpCaptchaProtectedResource,
+ hmacKey=hmacKey,
+ captchaDir=config.GIMP_CAPTCHA_DIR)
+
+ if config.HTTPS_ROTATION_PERIOD:
+ count, period = config.HTTPS_ROTATION_PERIOD.split()
+ sched = ScheduledInterval(count, period)
+ else:
+ sched = Unscheduled()
+
+ bridges = BridgesResource(distributor, sched, numBridges, fwdHeaders,
+ includeFingerprints=fprInclude)
+ if captcha:
+ # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
+ protected = captcha(publicKey=publicKey,
+ secretKey=secretKey,
+ useForwardedHeader=fwdHeaders,
+ protectedResource=bridges)
+ root.putChild('bridges', protected)
+ logging.info("Protecting resources with %s." % captcha.func.__name__)
+ else:
+ root.putChild('bridges', bridges)
+
+ site = Site(root)
+ site.displayTracebacks = False
+
+ if config.HTTP_UNENCRYPTED_PORT: # pragma: no cover
+ ip = config.HTTP_UNENCRYPTED_BIND_IP or ""
+ port = config.HTTP_UNENCRYPTED_PORT or 80
+ try:
+ reactor.listenTCP(port, site, interface=ip)
+ except CannotListenError as error:
+ raise SystemExit(error)
+ logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))
+
+ if config.HTTPS_PORT: # pragma: no cover
+ ip = config.HTTPS_BIND_IP or ""
+ port = config.HTTPS_PORT or 443
+ try:
+ from twisted.internet.ssl import DefaultOpenSSLContextFactory
+ factory = DefaultOpenSSLContextFactory(config.HTTPS_KEY_FILE,
+ config.HTTPS_CERT_FILE)
+ reactor.listenSSL(port, site, factory, interface=ip)
+ except CannotListenError as error:
+ raise SystemExit(error)
+ logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))
+
+ return site
diff --git a/bridgedb/https/templates/assets/css/bootstrap.min.css b/bridgedb/https/templates/assets/css/bootstrap.min.css
new file mode 100644
index 0000000..56eb594
--- /dev/null
+++ b/bridgedb/https/templates/assets/css/bootstrap.min.css
@@ -0,0 +1,7 @@
+/* @import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");*/
+/* Bootswatch v3.1.1+1
+ * Homepage: http://bootswatch.com
+ * Copyright 2012-2014 Thomas Park
+ * Licensed under MIT
+ * Based on Bootstrap */
+/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:
inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padd
ing:0}@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.table td,.table th{background-color:#fff !important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-
box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Lato","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.42857143;color:#2c3e50;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#18bc9c;text-decoration:none}a:hover,a:focus{color:#18bc9c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #ecf0f1;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-t
op:21px;margin-bottom:21px;border:0;border-top:1px solid #ecf0f1}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Lato","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#b4bcc2}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-
size:75%}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#b4bcc2}.text-primary{color:#2c3e50}a.text-primary:hover{color:#1a242f}.text-success{color:#ffffff}a.text-success:hover{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover{color:#e6e6e6}.bg-primary{color:#fff;background-color:#2c3e50}a.bg-primary:hover{background-color:#1a242f}.bg-success{background-color:#18bc9c}a.bg-success:hover{background-color:#128f76}.bg-info{background-color:#3498db}a.bg-
info:hover{background-color:#217dbb}.bg-warning{background-color:#f39c12}a.bg-warning:hover{background-color:#c87f0a}.bg-danger{background-color:#e74c3c}a.bg-danger:hover{background-color:#d62c1a}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid transparent}ul,ol{margin-top:0;margin-bottom:10.5px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:21px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #b4bcc2}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10.5px 21p
x;margin:0 0 21px;font-size:18.75px;border-left:5px solid #ecf0f1}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#b4bcc2}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #ecf0f1;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}
address{margin-bottom:21px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#7b8a8b;background-color:#ecf0f1;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170p
x}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs
-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0%}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0%}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left
:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:
83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0%}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0%}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-l
eft:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md
-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0%}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0%}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200p
x){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0%}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666
667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0%}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.4285714
3;vertical-align:top;border-top:1px solid #ecf0f1}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ecf0f1}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ecf0f1}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ecf0f1}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ecf0f1}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-ch
ild(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#ecf0f1}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#ecf0f1}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#dde4e6}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr
>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#18bc9c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#15a589}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#3498db}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#258cd1}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,
.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#f39c12}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#e08e0b}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#e74c3c}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#e43725}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:h
idden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ecf0f1;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-respon
sive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#2c3e50;border:0;border-bottom:1px solid transparent}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:a
uto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:11px;font-size:15px;line-height:1.42857143;color:#2c3e50}.form-control{display:block;width:100%;height:43px;padding:10px 15px;font-size:15px;line-height:1.42857143;color:#2c3e50;background-color:#ffffff;background-image:none;border:1px solid #dce4ec;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#2c3e50;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(44,62,80,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(44,62,80,0.6)}.form-control::-moz-placeholder{color:#acb6c0;opacity:1}.form-control:-ms-input-pla
ceholder{color:#acb6c0}.form-control::-webkit-input-placeholder{color:#acb6c0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#ecf0f1;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"]{line-height:43px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:21px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="r
adio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:33px;padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}select.input-sm{height:33px;line-height:33px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:64px;padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}select.input-lg{height:64px;line-height:64px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:53.75px}.has-feedback .form-control-feedback{position:absolute;top:26px;right:0;display:block;width:43px;height:43px;line-height:43px;text-align:center}.has-success .help-block,.has-
success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#18bc9c}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0
1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#f39c12}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#e74c3c}.has-error .form-control-feedback{color:#ffffff}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#597ea2}@media (min-width:768px){.form-inl
ine .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:11px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:32px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:11px}@media (min-width:768px){.form-horizontal .c
ontrol-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:10px 15px;font-size:15px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#ffffff;background-co
lor:#95a5a6;border-color:#95a5a6}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#ffffff;background-color:#7f9293;border-color:#74898a}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#95a5a6;border-color:#95a5a6}.btn-default .badge{color:#95a5a6;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}.btn-primary:hover,.btn-primary:focus,.btn-primary:ac
tive,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#ffffff;background-color:#1e2a36;border-color:#161f29}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#2c3e50;border-color:#2c3e50}.btn-primary .badge{color:#2c3e50;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#18bc9c;border-color:#18bc9c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#ffffff;background-c
olor:#13987e;border-color:#11866f}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#18bc9c;border-color:#18bc9c}.btn-success .badge{color:#18bc9c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#3498db;border-color:#3498db}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#ffffff;background-color:#2383c4;border-color:#2077b2}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{back
ground-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#3498db;border-color:#3498db}.btn-info .badge{color:#3498db;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f39c12;border-color:#f39c12}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#ffffff;background-color:#d2850b;border-color:#be780a}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warnin
g[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f39c12;border-color:#f39c12}.btn-warning .badge{color:#f39c12;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#e74c3c;border-color:#e74c3c}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#ffffff;background-color:#df2e1b;border-color:#cd2a19}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabl
ed]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#e74c3c;border-color:#e74c3c}.btn-danger .badge{color:#e74c3c;background-color:#ffffff}.btn-link{color:#18bc9c;font-weight:normal;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#18bc9c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#b4bcc2;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}
.btn-sm,.btn-group-sm>.btn{padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:13px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../
fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-lis
t:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{c
ontent:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:
"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.
glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:befo
re{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"
\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphico
n-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{conten
t:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.
caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;background-color:#ffffff;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#7b8a8b;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#ffffff;background-color:#2c3e50}
.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#2c3e50}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#b4bcc2}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:13px;line-height:1.42857143;color:#b4bcc2}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .drop
down-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:none}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-togg
le){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0
.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertica
l>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-g
roup-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:64px;padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:64px;line-height:64px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:33px;padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:33px;line-height:33px}textarea.input-group-sm>.form-control,textarea.input
-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:10px 15px;font-size:15px;font-weight:normal;line-height:1;color:#2c3e50;text-align:center;background-color:#ecf0f1;border:1px solid #dce4ec;border-radius:4px}.input-group-addon.input-sm{padding:6px 9px;font-size:13px;border-radius:3px}.input-group-addon.input-lg{padding:18px 27px;font-size:19px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[ty
pe="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margi
n-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ecf0f1}.nav>li.disabled>a{color:#b4bcc2}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#b4bcc2;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ecf0f1;border-color:#18bc9c}.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ecf0f1}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{ma
rgin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#ecf0f1 #ecf0f1 #ecf0f1}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#2c3e50;background-color:#ffffff;border:1px solid #ecf0f1;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ecf0f1}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ecf0f1;border-radius:4px 4px
0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#2c3e50}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ecf0f1}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:
1px solid #ecf0f1;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:60px;margin-bottom:21px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overf
low-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:19.5px 15px;font-size:19px;line-height:21px;height:60px}.navbar-brand:hover,.navbar-bran
d:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:13px;margin-bottom:13px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:none}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:9.75px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line
-height:21px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:19.5px;padding-bottom:19.5px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8.5px;margin-bottom:8.5px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.
navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8.5px;margin-bottom:8.5px}.navbar-btn.btn-sm{margin-top:13.5px;margin-bottom:13.5px}.navbar-btn.btn-xs{margin-top:19px;margin
-bottom:19px}.navbar-text{margin-top:19.5px;margin-bottom:19.5px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#2c3e50;border-color:transparent}.navbar-default .navbar-brand{color:#ffffff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-text{color:#777777}.navbar-default .navbar-nav>li>a{color:#ffffff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#1a242f}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .na
vbar-toggle{border-color:#1a242f}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#1a242f}.navbar-default .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#1a242f;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#1a242f}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-
default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#ffffff}.navbar-default .navbar-link:hover{color:#18bc9c}.navbar-inverse{background-color:#18bc9c;border-color:transparent}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-text{color:#ffffff}.navbar-inverse .navbar-nav>li>a{color:#ffffff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#15a589}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{co
lor:#cccccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#128f76}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#128f76}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#149c82}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#15a589;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.nav
bar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#15a589}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#ffffff}.navbar-inverse .navbar-link:hover{color:#2c3e50}.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#ecf0f1;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#cccccc}.breadcrumb>.active{color:#95a5a6}.pagination{display:inline-block;padding-left:0;margin:21px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:10px 15px;line-height:1.42857143;text-decoration:none;color:#ffffff;ba
ckground-color:#18bc9c;border:1px solid transparent;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#ffffff;background-color:#0f7864;border-color:transparent}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#ffffff;background-color:#0f7864;border-color:transparent;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#ecf0f1;background-color:#3be6c4;border-color:transparent;cursor:not-allowed}.p
agination-lg>li>a,.pagination-lg>li>span{padding:18px 27px;font-size:19px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:6px 9px;font-size:13px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#18bc9c;border:1px solid transparent;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#0f7864}.pager .next>a,.pager .next>span{float
:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#ffffff;background-color:#18bc9c;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#95a5a6}.label-default[href]:hover,.label-default[href]:focus{background-color:#798d8f}.label-primary{background-color:#2c3e50}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#1a242f}.label-success{background-color:#18bc9c}.label-success[href]:hover,.label-success[href]:focus{background-color:#128f76}.label-info{background-color:#3498db}.label-info[href]:hover,.label-info[href]:focus{backgroun
d-color:#217dbb}.label-warning{background-color:#f39c12}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c87f0a}.label-danger{background-color:#e74c3c}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#d62c1a}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:13px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#2c3e50;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#2c3e50;background-color:#ffffff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#ecf0f1}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200}.container .jumb
otron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:67.5px}}.thumbnail{display:block;padding:4px;margin-bottom:21px;line-height:1.42857143;background-color:#ffffff;border:1px solid #ecf0f1;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#18bc9c}.thumbnail .caption{padding:9px;color:#2c3e50}.alert{padding:15px;margin-bottom:21px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.al
ert-success{background-color:#18bc9c;border-color:#18bc9c;color:#ffffff}.alert-success hr{border-top-color:#15a589}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#3498db;border-color:#3498db;color:#ffffff}.alert-info hr{border-top-color:#258cd1}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f39c12;border-color:#f39c12;color:#ffffff}.alert-warning hr{border-top-color:#e08e0b}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#e74c3c;border-color:#e74c3c;color:#ffffff}.alert-danger hr{border-top-color:#e43725}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:21px;margin-bottom:21px;background-color:#ecf0f1;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0
,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:13px;line-height:21px;color:#ffffff;text-align:center;background-color:#2c3e50;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#18bc9c}.progress-striped .progress-bar-success{background-image:-webkit-lin
ear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#3498db}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 2
5%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#e74c3c}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-le
ft:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #ecf0f1}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555555}a.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#ecf0f1}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-it
em-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#8aa4be}.list-group-item-success{color:#ffffff;background-color:#18bc9c}a.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#ffffff;background-color:#15a589}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#3498db}a.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#ffffff;background-color:#258cd1}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:
#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#f39c12}a.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#ffffff;background-color:#e08e0b}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#e74c3c}a.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#ffffff;background-color:#e43725}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-t
ext{margin-bottom:0;line-height:1.3}.panel{margin-bottom:21px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:17px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#ecf0f1;border-top:1px solid #ecf0f1;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right
-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child
>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-ch
ild,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.tabl
e:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ecf0f1}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>the
ad>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:
0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-botto
m:21px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ecf0f1}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ecf0f1}.panel-default{border-color:#ecf0f1}.panel-default>.panel-heading{color:#2c3e50;background-color:#ecf0f1;border-color:#ecf0f1}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ecf0f1}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ecf0f1}.panel-primary{border-color:#2c3e50}.panel-primary>.panel-heading{color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#2c3e50}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#2c3e50}.panel-success{border-color:#18bc9
c}.panel-success>.panel-heading{color:#ffffff;background-color:#18bc9c;border-color:#18bc9c}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#18bc9c}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#18bc9c}.panel-info{border-color:#3498db}.panel-info>.panel-heading{color:#ffffff;background-color:#3498db;border-color:#3498db}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#3498db}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#3498db}.panel-warning{border-color:#f39c12}.panel-warning>.panel-heading{color:#ffffff;background-color:#f39c12;border-color:#f39c12}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#f39c12}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#f39c12}.panel-danger{border-color:#e74c3c}.panel-danger>.panel-heading{color:#ffffff;background-color:#e74c3c;border-color:#e74c3c}.panel-danger>.panel-heading+.panel-c
ollapse .panel-body{border-top-color:#e74c3c}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#e74c3c}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#ecf0f1;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:22.5px;font-weight:bold;line-height:1;color:#000000;text-shadow:none;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrol
ling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);transform:translate(0, 0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:none}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px so
lid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:13px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin
-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:rgba(0,0,0,0.9);border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:rgba(0,0,0,0.9)}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:rgba(0,0,0,0.9)}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.tooltip.bottom-left .too
ltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:15px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-styl
e:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.
popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.
left{background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.5) 0), color-stop(rgba(0,0,0,0.0001) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.0001) 0), color-stop(rgba(0,0,0,0.5) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:none;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyp
hicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index
:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body
:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{disp
lay:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !imp
ortant}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0}.navbar-default .badge{background-color:#fff;color:#2c3e50}.navbar-inverse .badge{background-color:#fff;color:#18bc9c}.navbar-brand{padding:18.5px 15px 20.5px}.btn:active{-webkit-box-shadow:none;box-shadow:none}.btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.text-primary,.text-primary:hover{color:#2c3e50}.text-success,.text-success:hover{color:#18bc9c}.text-danger,.text-danger:hover{color:#e74c3c}.text-warning,.text-warning:hover{color:#f39c12}.text-info,.text-info:hover{color:#3498db}table a,.table a{text-decoration:underline}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{co
lor:#fff}table .success a,.table .success a,table .warning a,.table .warning a,table .danger a,.table .danger a,table .info a,.table .info a{color:#fff}table>thead>tr>th,.table>thead>tr>th,table>tbody>tr>th,.table>tbody>tr>th,table>tfoot>tr>th,.table>tfoot>tr>th,table>thead>tr>td,.table>thead>tr>td,table>tbody>tr>td,.table>tbody>tr>td,table>tfoot>tr>td,.table>tfoot>tr>td{border:none}table-bordered>thead>tr>th,.table-bordered>thead>tr>th,table-bordered>tbody>tr>th,.table-bordered>tbody>tr>th,table-bordered>tfoot>tr>th,.table-bordered>tfoot>tr>th,table-bordered>thead>tr>td,.table-bordered>thead>tr>td,table-bordered>tbody>tr>td,.table-bordered>tbody>tr>td,table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ecf0f1}.form-control,input{border-width:2px;-webkit-box-shadow:none;box-shadow:none}.form-control:focus,input:focus{-webkit-box-shadow:none;box-shadow:none}.has-warning .help-block,.has-warning .control-label{color:#f39c12}.has-warning .form-control,.has-warning
.form-control:focus{border:2px solid #f39c12}.has-error .help-block,.has-error .control-label{color:#e74c3c}.has-error .form-control,.has-error .form-control:focus{border:2px solid #e74c3c}.has-success .help-block,.has-success .control-label{color:#18bc9c}.has-success .form-control,.has-success .form-control:focus{border:2px solid #18bc9c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.pager a,.pager a:hover{color:#fff}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{background-color:#3be6c4}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert .close{color:#fff;text-decoration:none;opacity:0.4}.alert .close:hover,.alert .close:focus{color:#fff;opacity:1}.progress{height:10px;-webkit-box-shadow:none;box-shadow:none}.progress .progress-bar{font-size:10px;line-height:10px}.well{-webkit-box-shadow:none;box-shadow:none}
\ No newline at end of file
diff --git a/bridgedb/https/templates/assets/css/custom.css b/bridgedb/https/templates/assets/css/custom.css
new file mode 100644
index 0000000..f0bde6f
--- /dev/null
+++ b/bridgedb/https/templates/assets/css/custom.css
@@ -0,0 +1,158 @@
+body {
+ padding-top: 20px;
+ padding-bottom: 40px;
+}
+
+/* Custom container */
+.container-narrow {
+ margin: 0 auto;
+ max-width: 675px;
+}
+.container-narrow > hr {
+ margin: 30px 0;
+}
+
+/* Main marketing message and sign up button */
+.jumbotron {
+ margin: 60px 0;
+ text-align: center;
+}
+.jumbotron h1 {
+ font-size: 72px;
+ line-height: 1;
+}
+.jumbotron .btn {
+ font-size: 21px;
+ padding: 14px 24px;
+}
+
+/* Supporting marketing content */
+.marketing {
+ margin: 60px 0;
+}
+.marketing p + h4 {
+ margin-top: 28px;
+}
+
+.captcha {
+ margin: auto;
+ display: block;
+ width: 250px;
+}
+
+.captcha .btn {
+ margin: auto;
+ width: 100px;
+ display: block;
+}
+
+.fixed-size-btn {
+ margin: 10px;
+ width: 200px;
+}
+
+.main-steps {
+ margin: 50px;
+}
+
+
+.main-btns {
+ margin: auto;
+ width: 450px;
+ display: block;
+}
+
+.step{
+border: 1px solid #ccc;
+border: 1px solid rgba(0, 0, 0, 0.2);
+-webkit-border-radius: 6px;
+-moz-border-radius: 6px;
+border-radius: 6px;
+-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+-webkit-background-clip: padding-box;
+-moz-background-clip: padding;
+background-clip: padding-box;
+padding: 10px 20px 0px;
+margin-bottom: 10px;
+}
+
+.step-title {
+color: #808080;
+font-size: 18px;
+font-weight: 100;
+}
+
+.step-text {
+ font-size: 18px;
+line-height: 30px;
+margin-top: 2px;
+}
+.lead_right {
+ margin-bottom: 20px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 30px
+}
+[class*="bdb_span"] {
+ min-height: 1px;
+ margin: 0 20px 16px 20px;
+}
+.bdb_span7 {
+ width: 560px
+}
+
+div.bridge-lines {
+ padding: 20px;
+ margin: 0px 0px 20px;
+ min-height: 20px;
+ font-family: Monaco,Menlo,Consolas,"Courier New",monospace;
+ font-size: 95%;
+ line-height: 175%;
+ color: rgb(44, 62, 80);
+ word-break: break-all;
+ word-wrap: normal;
+ white-space: nowrap;
+ overflow-x: auto;
+ z-index: 1000;
+ background-color: rgb(236, 240, 241);
+ border: 0px solid transparent;
+ border-radius: 6px 6px 6px 6px;
+ border-color: #2C3E50;
+ box-shadow: none;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ cursor: copy;
+}
+
+div.bridge-lines.-webkit-scrollbar {
+ width: 9px;
+ height: 9px;
+}
+div.bridge-lines.-webkit-scrollbar-button.start.decrement {
+ display: block;
+ height: 0;
+ background-color: transparent;
+}
+div.bridge-lines.-webkit-scrollbar-button.end.increment {
+ display: block;
+ height: 0;
+ background-color: transparent;
+}
+div.bridge-lines.-webkit-scrollbar-track-piece {
+ background-color: #FAFAFA;
+ -webkit-border-radius: 0;
+ -webkit-border-bottom-right-radius: 6px;
+ -webkit-border-bottom-left-radius: 6px;
+}
+div.bridge-lines.-webkit-scrollbar-thumb.vertical {
+ height: 50px;
+ background-color: #999;
+ -webkit-border-radius: 6px;
+}
+div.bridge-lines.-webkit-scrollbar-thumb.horizontal{
+ width: 50px;
+ background-color: #999;
+ -webkit-border-radius: 6px;
+}
diff --git a/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css b/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css
new file mode 100644
index 0000000..d3dae63
--- /dev/null
+++ b/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css
@@ -0,0 +1,384 @@
+.icon-large{font-size:1.3333333333333333em;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;vertical-align:middle;}
+.nav [class^="icon-"],.nav [class*=" icon-"]{vertical-align:inherit;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;}.nav [class^="icon-"].icon-large,.nav [class*=" icon-"].icon-large{vertical-align:-25%;}
+.nav-pills [class^="icon-"].icon-large,.nav-tabs [class^="icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large{line-height:.75em;margin-top:-7px;padding-top:5px;margin-bottom:-5px;padding-bottom:4px;}
+.btn [class^="icon-"].pull-left,.btn [class*=" icon-"].pull-left,.btn [class^="icon-"].pull-right,.btn [class*=" icon-"].pull-right{vertical-align:inherit;}
+.btn [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large{margin-top:-0.5em;}
+a [class^="icon-"],a [class*=" icon-"]{cursor:pointer;}
+.icon-glass{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-music{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-search{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-envelope-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-heart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-star{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-star-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-user{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-film{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-th-large{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-th{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-th-list{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ok{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-remove{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-zoom-in{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-zoom-out{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-power-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-signal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cog{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gear{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-trash{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-home{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-file-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-time{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-road{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-download-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-download{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-upload{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-inbox{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-play-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-repeat{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rotate-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-refresh{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-list-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-lock{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-flag{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-headphones{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-volume-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-volume-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-volume-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-qrcode{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-barcode{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tag{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tags{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-book{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bookmark{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-print{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-camera{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-font{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bold{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-italic{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-text-height{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-text-width{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-align-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-align-center{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-align-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-align-justify{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-list{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-indent-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-indent-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-facetime-video{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-picture{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-pencil{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-map-marker{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-adjust{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tint{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-edit{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-share{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-check{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-move{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-step-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fast-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-play{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-pause{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-stop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fast-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-step-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-eject{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-plus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-minus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-remove-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ok-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-question-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-info-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-screenshot{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-remove-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ok-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ban-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-share-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-mail-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-resize-full{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-resize-small{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-plus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-minus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-asterisk{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-exclamation-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gift{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-leaf{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fire{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-eye-open{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-eye-close{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-warning-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-plane{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-calendar{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-random{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-comment{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-magnet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-retweet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-shopping-cart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-folder-close{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-folder-open{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-resize-vertical{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-resize-horizontal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bar-chart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-twitter-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-facebook-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-camera-retro{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-key{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cogs{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gears{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-comments{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-thumbs-up-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-thumbs-down-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-star-half{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-heart-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-signout{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-linkedin-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-pushpin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-external-link{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-signin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-trophy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-github-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-upload-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-lemon{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-phone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-check-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-unchecked{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bookmark-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-phone-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-twitter{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-facebook{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-github{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-unlock{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-credit-card{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rss{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hdd{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bullhorn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bell{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-certificate{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hand-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hand-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hand-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hand-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-globe{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-wrench{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tasks{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-filter{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-briefcase{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fullscreen{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-group{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-link{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cloud{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-beaker{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cut{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-copy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-paper-clip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-paperclip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-save{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sign-blank{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-reorder{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-list-ul{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-list-ol{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-strikethrough{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-underline{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-table{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-magic{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-truck{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-pinterest{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-pinterest-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-google-plus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-google-plus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-money{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-caret-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-caret-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-caret-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-caret-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-columns{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-envelope{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-linkedin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-undo{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rotate-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-legal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-dashboard{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-comment-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-comments-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bolt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sitemap{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-umbrella{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-paste{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-lightbulb{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-exchange{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cloud-download{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cloud-upload{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-user-md{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-stethoscope{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-suitcase{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bell-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-coffee{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-food{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-file-text-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-building{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-hospital{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ambulance{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-medkit{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fighter-jet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-beer{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-h-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-plus-sign-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-double-angle-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-double-angle-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-double-angle-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-double-angle-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-angle-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-angle-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-angle-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-angle-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-desktop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-laptop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tablet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-mobile-phone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle-blank{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-quote-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-quote-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-spinner{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-reply{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-mail-reply{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-github-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-folder-close-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-folder-open-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-expand-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-collapse-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-smile{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-frown{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-meh{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gamepad{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-keyboard{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-flag-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-flag-checkered{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-terminal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-code{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-reply-all{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-mail-reply-all{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-star-half-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-star-half-full{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-location-arrow{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-crop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-code-fork{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-unlink{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-question{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-info{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-exclamation{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-superscript{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-subscript{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-eraser{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-puzzle-piece{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-microphone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-microphone-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-shield{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-calendar-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-fire-extinguisher{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rocket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-maxcdn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-sign-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-sign-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-sign-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-chevron-sign-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-html5{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-css3{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-anchor{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-unlock-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bullseye{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ellipsis-horizontal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ellipsis-vertical{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rss-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-play-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-ticket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-minus-sign-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-check-minus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-level-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-level-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-check-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-edit-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-external-link-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-share-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-compass{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-collapse{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-collapse-top{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-expand{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-eur{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-euro{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gbp{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-usd{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-dollar{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-inr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-rupee{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-jpy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-yen{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-cny{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-renminbi{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-krw{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-won{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-btc{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bitcoin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-file{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-file-text{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-alphabet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-alphabet-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-attributes{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-attributes-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-order{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sort-by-order-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-thumbs-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-thumbs-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-youtube-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-youtube{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-xing{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-xing-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-youtube-play{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-dropbox{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-stackexchange{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-instagram{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-flickr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-adn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bitbucket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bitbucket-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tumblr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-tumblr-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-long-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-long-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-long-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-long-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-apple{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-windows{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-android{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-linux{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-dribbble{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-skype{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-foursquare{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-trello{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-female{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-male{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-gittip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-sun{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-moon{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-archive{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-bug{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-vk{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-weibo{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
+.icon-renren{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
diff --git a/bridgedb/https/templates/assets/css/font-awesome.min.css b/bridgedb/https/templates/assets/css/font-awesome.min.css
new file mode 100644
index 0000000..866437f
--- /dev/null
+++ b/bridgedb/https/templates/assets/css/font-awesome.min.css
@@ -0,0 +1,403 @@
+ at font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;}
+[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;}
+.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;}
+a [class^="icon-"],a [class*=" icon-"]{display:inline;}
+[class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;}
+.icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;}
+.icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;}
+[class^="icon-"].hide,[class*=" icon-"].hide{display:none;}
+.icon-muted{color:#eeeeee;}
+.icon-light{color:#ffffff;}
+.icon-dark{color:#333333;}
+.icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;}
+[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;}
+[class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;}
+.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;}
+.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;}
+.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;}
+.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;}
+.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;}
+.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;}
+.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;}
+.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;}
+.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;}
+.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;}
+.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;}
+.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;}
+.icon-stack .icon-stack-base{font-size:2em;*line-height:1em;}
+.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;}
+a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;}
+ at -moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);}
+.icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);}
+.icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);}
+.icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);}
+.icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);}
+a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;}
+.icon-glass:before{content:"\f000";}
+.icon-music:before{content:"\f001";}
+.icon-search:before{content:"\f002";}
+.icon-envelope-alt:before{content:"\f003";}
+.icon-heart:before{content:"\f004";}
+.icon-star:before{content:"\f005";}
+.icon-star-empty:before{content:"\f006";}
+.icon-user:before{content:"\f007";}
+.icon-film:before{content:"\f008";}
+.icon-th-large:before{content:"\f009";}
+.icon-th:before{content:"\f00a";}
+.icon-th-list:before{content:"\f00b";}
+.icon-ok:before{content:"\f00c";}
+.icon-remove:before{content:"\f00d";}
+.icon-zoom-in:before{content:"\f00e";}
+.icon-zoom-out:before{content:"\f010";}
+.icon-power-off:before,.icon-off:before{content:"\f011";}
+.icon-signal:before{content:"\f012";}
+.icon-gear:before,.icon-cog:before{content:"\f013";}
+.icon-trash:before{content:"\f014";}
+.icon-home:before{content:"\f015";}
+.icon-file-alt:before{content:"\f016";}
+.icon-time:before{content:"\f017";}
+.icon-road:before{content:"\f018";}
+.icon-download-alt:before{content:"\f019";}
+.icon-download:before{content:"\f01a";}
+.icon-upload:before{content:"\f01b";}
+.icon-inbox:before{content:"\f01c";}
+.icon-play-circle:before{content:"\f01d";}
+.icon-rotate-right:before,.icon-repeat:before{content:"\f01e";}
+.icon-refresh:before{content:"\f021";}
+.icon-list-alt:before{content:"\f022";}
+.icon-lock:before{content:"\f023";}
+.icon-flag:before{content:"\f024";}
+.icon-headphones:before{content:"\f025";}
+.icon-volume-off:before{content:"\f026";}
+.icon-volume-down:before{content:"\f027";}
+.icon-volume-up:before{content:"\f028";}
+.icon-qrcode:before{content:"\f029";}
+.icon-barcode:before{content:"\f02a";}
+.icon-tag:before{content:"\f02b";}
+.icon-tags:before{content:"\f02c";}
+.icon-book:before{content:"\f02d";}
+.icon-bookmark:before{content:"\f02e";}
+.icon-print:before{content:"\f02f";}
+.icon-camera:before{content:"\f030";}
+.icon-font:before{content:"\f031";}
+.icon-bold:before{content:"\f032";}
+.icon-italic:before{content:"\f033";}
+.icon-text-height:before{content:"\f034";}
+.icon-text-width:before{content:"\f035";}
+.icon-align-left:before{content:"\f036";}
+.icon-align-center:before{content:"\f037";}
+.icon-align-right:before{content:"\f038";}
+.icon-align-justify:before{content:"\f039";}
+.icon-list:before{content:"\f03a";}
+.icon-indent-left:before{content:"\f03b";}
+.icon-indent-right:before{content:"\f03c";}
+.icon-facetime-video:before{content:"\f03d";}
+.icon-picture:before{content:"\f03e";}
+.icon-pencil:before{content:"\f040";}
+.icon-map-marker:before{content:"\f041";}
+.icon-adjust:before{content:"\f042";}
+.icon-tint:before{content:"\f043";}
+.icon-edit:before{content:"\f044";}
+.icon-share:before{content:"\f045";}
+.icon-check:before{content:"\f046";}
+.icon-move:before{content:"\f047";}
+.icon-step-backward:before{content:"\f048";}
+.icon-fast-backward:before{content:"\f049";}
+.icon-backward:before{content:"\f04a";}
+.icon-play:before{content:"\f04b";}
+.icon-pause:before{content:"\f04c";}
+.icon-stop:before{content:"\f04d";}
+.icon-forward:before{content:"\f04e";}
+.icon-fast-forward:before{content:"\f050";}
+.icon-step-forward:before{content:"\f051";}
+.icon-eject:before{content:"\f052";}
+.icon-chevron-left:before{content:"\f053";}
+.icon-chevron-right:before{content:"\f054";}
+.icon-plus-sign:before{content:"\f055";}
+.icon-minus-sign:before{content:"\f056";}
+.icon-remove-sign:before{content:"\f057";}
+.icon-ok-sign:before{content:"\f058";}
+.icon-question-sign:before{content:"\f059";}
+.icon-info-sign:before{content:"\f05a";}
+.icon-screenshot:before{content:"\f05b";}
+.icon-remove-circle:before{content:"\f05c";}
+.icon-ok-circle:before{content:"\f05d";}
+.icon-ban-circle:before{content:"\f05e";}
+.icon-arrow-left:before{content:"\f060";}
+.icon-arrow-right:before{content:"\f061";}
+.icon-arrow-up:before{content:"\f062";}
+.icon-arrow-down:before{content:"\f063";}
+.icon-mail-forward:before,.icon-share-alt:before{content:"\f064";}
+.icon-resize-full:before{content:"\f065";}
+.icon-resize-small:before{content:"\f066";}
+.icon-plus:before{content:"\f067";}
+.icon-minus:before{content:"\f068";}
+.icon-asterisk:before{content:"\f069";}
+.icon-exclamation-sign:before{content:"\f06a";}
+.icon-gift:before{content:"\f06b";}
+.icon-leaf:before{content:"\f06c";}
+.icon-fire:before{content:"\f06d";}
+.icon-eye-open:before{content:"\f06e";}
+.icon-eye-close:before{content:"\f070";}
+.icon-warning-sign:before{content:"\f071";}
+.icon-plane:before{content:"\f072";}
+.icon-calendar:before{content:"\f073";}
+.icon-random:before{content:"\f074";}
+.icon-comment:before{content:"\f075";}
+.icon-magnet:before{content:"\f076";}
+.icon-chevron-up:before{content:"\f077";}
+.icon-chevron-down:before{content:"\f078";}
+.icon-retweet:before{content:"\f079";}
+.icon-shopping-cart:before{content:"\f07a";}
+.icon-folder-close:before{content:"\f07b";}
+.icon-folder-open:before{content:"\f07c";}
+.icon-resize-vertical:before{content:"\f07d";}
+.icon-resize-horizontal:before{content:"\f07e";}
+.icon-bar-chart:before{content:"\f080";}
+.icon-twitter-sign:before{content:"\f081";}
+.icon-facebook-sign:before{content:"\f082";}
+.icon-camera-retro:before{content:"\f083";}
+.icon-key:before{content:"\f084";}
+.icon-gears:before,.icon-cogs:before{content:"\f085";}
+.icon-comments:before{content:"\f086";}
+.icon-thumbs-up-alt:before{content:"\f087";}
+.icon-thumbs-down-alt:before{content:"\f088";}
+.icon-star-half:before{content:"\f089";}
+.icon-heart-empty:before{content:"\f08a";}
+.icon-signout:before{content:"\f08b";}
+.icon-linkedin-sign:before{content:"\f08c";}
+.icon-pushpin:before{content:"\f08d";}
+.icon-external-link:before{content:"\f08e";}
+.icon-signin:before{content:"\f090";}
+.icon-trophy:before{content:"\f091";}
+.icon-github-sign:before{content:"\f092";}
+.icon-upload-alt:before{content:"\f093";}
+.icon-lemon:before{content:"\f094";}
+.icon-phone:before{content:"\f095";}
+.icon-unchecked:before,.icon-check-empty:before{content:"\f096";}
+.icon-bookmark-empty:before{content:"\f097";}
+.icon-phone-sign:before{content:"\f098";}
+.icon-twitter:before{content:"\f099";}
+.icon-facebook:before{content:"\f09a";}
+.icon-github:before{content:"\f09b";}
+.icon-unlock:before{content:"\f09c";}
+.icon-credit-card:before{content:"\f09d";}
+.icon-rss:before{content:"\f09e";}
+.icon-hdd:before{content:"\f0a0";}
+.icon-bullhorn:before{content:"\f0a1";}
+.icon-bell:before{content:"\f0a2";}
+.icon-certificate:before{content:"\f0a3";}
+.icon-hand-right:before{content:"\f0a4";}
+.icon-hand-left:before{content:"\f0a5";}
+.icon-hand-up:before{content:"\f0a6";}
+.icon-hand-down:before{content:"\f0a7";}
+.icon-circle-arrow-left:before{content:"\f0a8";}
+.icon-circle-arrow-right:before{content:"\f0a9";}
+.icon-circle-arrow-up:before{content:"\f0aa";}
+.icon-circle-arrow-down:before{content:"\f0ab";}
+.icon-globe:before{content:"\f0ac";}
+.icon-wrench:before{content:"\f0ad";}
+.icon-tasks:before{content:"\f0ae";}
+.icon-filter:before{content:"\f0b0";}
+.icon-briefcase:before{content:"\f0b1";}
+.icon-fullscreen:before{content:"\f0b2";}
+.icon-group:before{content:"\f0c0";}
+.icon-link:before{content:"\f0c1";}
+.icon-cloud:before{content:"\f0c2";}
+.icon-beaker:before{content:"\f0c3";}
+.icon-cut:before{content:"\f0c4";}
+.icon-copy:before{content:"\f0c5";}
+.icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";}
+.icon-save:before{content:"\f0c7";}
+.icon-sign-blank:before{content:"\f0c8";}
+.icon-reorder:before{content:"\f0c9";}
+.icon-list-ul:before{content:"\f0ca";}
+.icon-list-ol:before{content:"\f0cb";}
+.icon-strikethrough:before{content:"\f0cc";}
+.icon-underline:before{content:"\f0cd";}
+.icon-table:before{content:"\f0ce";}
+.icon-magic:before{content:"\f0d0";}
+.icon-truck:before{content:"\f0d1";}
+.icon-pinterest:before{content:"\f0d2";}
+.icon-pinterest-sign:before{content:"\f0d3";}
+.icon-google-plus-sign:before{content:"\f0d4";}
+.icon-google-plus:before{content:"\f0d5";}
+.icon-money:before{content:"\f0d6";}
+.icon-caret-down:before{content:"\f0d7";}
+.icon-caret-up:before{content:"\f0d8";}
+.icon-caret-left:before{content:"\f0d9";}
+.icon-caret-right:before{content:"\f0da";}
+.icon-columns:before{content:"\f0db";}
+.icon-sort:before{content:"\f0dc";}
+.icon-sort-down:before{content:"\f0dd";}
+.icon-sort-up:before{content:"\f0de";}
+.icon-envelope:before{content:"\f0e0";}
+.icon-linkedin:before{content:"\f0e1";}
+.icon-rotate-left:before,.icon-undo:before{content:"\f0e2";}
+.icon-legal:before{content:"\f0e3";}
+.icon-dashboard:before{content:"\f0e4";}
+.icon-comment-alt:before{content:"\f0e5";}
+.icon-comments-alt:before{content:"\f0e6";}
+.icon-bolt:before{content:"\f0e7";}
+.icon-sitemap:before{content:"\f0e8";}
+.icon-umbrella:before{content:"\f0e9";}
+.icon-paste:before{content:"\f0ea";}
+.icon-lightbulb:before{content:"\f0eb";}
+.icon-exchange:before{content:"\f0ec";}
+.icon-cloud-download:before{content:"\f0ed";}
+.icon-cloud-upload:before{content:"\f0ee";}
+.icon-user-md:before{content:"\f0f0";}
+.icon-stethoscope:before{content:"\f0f1";}
+.icon-suitcase:before{content:"\f0f2";}
+.icon-bell-alt:before{content:"\f0f3";}
+.icon-coffee:before{content:"\f0f4";}
+.icon-food:before{content:"\f0f5";}
+.icon-file-text-alt:before{content:"\f0f6";}
+.icon-building:before{content:"\f0f7";}
+.icon-hospital:before{content:"\f0f8";}
+.icon-ambulance:before{content:"\f0f9";}
+.icon-medkit:before{content:"\f0fa";}
+.icon-fighter-jet:before{content:"\f0fb";}
+.icon-beer:before{content:"\f0fc";}
+.icon-h-sign:before{content:"\f0fd";}
+.icon-plus-sign-alt:before{content:"\f0fe";}
+.icon-double-angle-left:before{content:"\f100";}
+.icon-double-angle-right:before{content:"\f101";}
+.icon-double-angle-up:before{content:"\f102";}
+.icon-double-angle-down:before{content:"\f103";}
+.icon-angle-left:before{content:"\f104";}
+.icon-angle-right:before{content:"\f105";}
+.icon-angle-up:before{content:"\f106";}
+.icon-angle-down:before{content:"\f107";}
+.icon-desktop:before{content:"\f108";}
+.icon-laptop:before{content:"\f109";}
+.icon-tablet:before{content:"\f10a";}
+.icon-mobile-phone:before{content:"\f10b";}
+.icon-circle-blank:before{content:"\f10c";}
+.icon-quote-left:before{content:"\f10d";}
+.icon-quote-right:before{content:"\f10e";}
+.icon-spinner:before{content:"\f110";}
+.icon-circle:before{content:"\f111";}
+.icon-mail-reply:before,.icon-reply:before{content:"\f112";}
+.icon-github-alt:before{content:"\f113";}
+.icon-folder-close-alt:before{content:"\f114";}
+.icon-folder-open-alt:before{content:"\f115";}
+.icon-expand-alt:before{content:"\f116";}
+.icon-collapse-alt:before{content:"\f117";}
+.icon-smile:before{content:"\f118";}
+.icon-frown:before{content:"\f119";}
+.icon-meh:before{content:"\f11a";}
+.icon-gamepad:before{content:"\f11b";}
+.icon-keyboard:before{content:"\f11c";}
+.icon-flag-alt:before{content:"\f11d";}
+.icon-flag-checkered:before{content:"\f11e";}
+.icon-terminal:before{content:"\f120";}
+.icon-code:before{content:"\f121";}
+.icon-reply-all:before{content:"\f122";}
+.icon-mail-reply-all:before{content:"\f122";}
+.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";}
+.icon-location-arrow:before{content:"\f124";}
+.icon-crop:before{content:"\f125";}
+.icon-code-fork:before{content:"\f126";}
+.icon-unlink:before{content:"\f127";}
+.icon-question:before{content:"\f128";}
+.icon-info:before{content:"\f129";}
+.icon-exclamation:before{content:"\f12a";}
+.icon-superscript:before{content:"\f12b";}
+.icon-subscript:before{content:"\f12c";}
+.icon-eraser:before{content:"\f12d";}
+.icon-puzzle-piece:before{content:"\f12e";}
+.icon-microphone:before{content:"\f130";}
+.icon-microphone-off:before{content:"\f131";}
+.icon-shield:before{content:"\f132";}
+.icon-calendar-empty:before{content:"\f133";}
+.icon-fire-extinguisher:before{content:"\f134";}
+.icon-rocket:before{content:"\f135";}
+.icon-maxcdn:before{content:"\f136";}
+.icon-chevron-sign-left:before{content:"\f137";}
+.icon-chevron-sign-right:before{content:"\f138";}
+.icon-chevron-sign-up:before{content:"\f139";}
+.icon-chevron-sign-down:before{content:"\f13a";}
+.icon-html5:before{content:"\f13b";}
+.icon-css3:before{content:"\f13c";}
+.icon-anchor:before{content:"\f13d";}
+.icon-unlock-alt:before{content:"\f13e";}
+.icon-bullseye:before{content:"\f140";}
+.icon-ellipsis-horizontal:before{content:"\f141";}
+.icon-ellipsis-vertical:before{content:"\f142";}
+.icon-rss-sign:before{content:"\f143";}
+.icon-play-sign:before{content:"\f144";}
+.icon-ticket:before{content:"\f145";}
+.icon-minus-sign-alt:before{content:"\f146";}
+.icon-check-minus:before{content:"\f147";}
+.icon-level-up:before{content:"\f148";}
+.icon-level-down:before{content:"\f149";}
+.icon-check-sign:before{content:"\f14a";}
+.icon-edit-sign:before{content:"\f14b";}
+.icon-external-link-sign:before{content:"\f14c";}
+.icon-share-sign:before{content:"\f14d";}
+.icon-compass:before{content:"\f14e";}
+.icon-collapse:before{content:"\f150";}
+.icon-collapse-top:before{content:"\f151";}
+.icon-expand:before{content:"\f152";}
+.icon-euro:before,.icon-eur:before{content:"\f153";}
+.icon-gbp:before{content:"\f154";}
+.icon-dollar:before,.icon-usd:before{content:"\f155";}
+.icon-rupee:before,.icon-inr:before{content:"\f156";}
+.icon-yen:before,.icon-jpy:before{content:"\f157";}
+.icon-renminbi:before,.icon-cny:before{content:"\f158";}
+.icon-won:before,.icon-krw:before{content:"\f159";}
+.icon-bitcoin:before,.icon-btc:before{content:"\f15a";}
+.icon-file:before{content:"\f15b";}
+.icon-file-text:before{content:"\f15c";}
+.icon-sort-by-alphabet:before{content:"\f15d";}
+.icon-sort-by-alphabet-alt:before{content:"\f15e";}
+.icon-sort-by-attributes:before{content:"\f160";}
+.icon-sort-by-attributes-alt:before{content:"\f161";}
+.icon-sort-by-order:before{content:"\f162";}
+.icon-sort-by-order-alt:before{content:"\f163";}
+.icon-thumbs-up:before{content:"\f164";}
+.icon-thumbs-down:before{content:"\f165";}
+.icon-youtube-sign:before{content:"\f166";}
+.icon-youtube:before{content:"\f167";}
+.icon-xing:before{content:"\f168";}
+.icon-xing-sign:before{content:"\f169";}
+.icon-youtube-play:before{content:"\f16a";}
+.icon-dropbox:before{content:"\f16b";}
+.icon-stackexchange:before{content:"\f16c";}
+.icon-instagram:before{content:"\f16d";}
+.icon-flickr:before{content:"\f16e";}
+.icon-adn:before{content:"\f170";}
+.icon-bitbucket:before{content:"\f171";}
+.icon-bitbucket-sign:before{content:"\f172";}
+.icon-tumblr:before{content:"\f173";}
+.icon-tumblr-sign:before{content:"\f174";}
+.icon-long-arrow-down:before{content:"\f175";}
+.icon-long-arrow-up:before{content:"\f176";}
+.icon-long-arrow-left:before{content:"\f177";}
+.icon-long-arrow-right:before{content:"\f178";}
+.icon-apple:before{content:"\f179";}
+.icon-windows:before{content:"\f17a";}
+.icon-android:before{content:"\f17b";}
+.icon-linux:before{content:"\f17c";}
+.icon-dribbble:before{content:"\f17d";}
+.icon-skype:before{content:"\f17e";}
+.icon-foursquare:before{content:"\f180";}
+.icon-trello:before{content:"\f181";}
+.icon-female:before{content:"\f182";}
+.icon-male:before{content:"\f183";}
+.icon-gittip:before{content:"\f184";}
+.icon-sun:before{content:"\f185";}
+.icon-moon:before{content:"\f186";}
+.icon-archive:before{content:"\f187";}
+.icon-bug:before{content:"\f188";}
+.icon-vk:before{content:"\f189";}
+.icon-weibo:before{content:"\f18a";}
+.icon-renren:before{content:"\f18b";}
diff --git a/bridgedb/https/templates/assets/css/main.css b/bridgedb/https/templates/assets/css/main.css
new file mode 100644
index 0000000..df34981
--- /dev/null
+++ b/bridgedb/https/templates/assets/css/main.css
@@ -0,0 +1,24 @@
+/* Imports */
+ at import url("bootstrap.min.css");
+ at import url("font-awesome.min.css");
+ at import url("custom.css");
+
+/* Fonts */
+ at font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Lato Regular'), local('Lato-Regular'), url('../font/lato-regular.woff') format('woff');
+}
+ at font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Lato Bold'), local('Lato-Bold'), url('../font/lato-bold.woff') format('woff');
+}
+ at font-face {
+ font-family: 'Lato';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Lato Italic'), local('Lato-Italic'), url('../font/lato-italic.woff') format('woff');
+}
diff --git a/bridgedb/https/templates/assets/font/fontawesome-webfont.eot b/bridgedb/https/templates/assets/font/fontawesome-webfont.eot
new file mode 100755
index 0000000..0662cb9
Binary files /dev/null and b/bridgedb/https/templates/assets/font/fontawesome-webfont.eot differ
diff --git a/bridgedb/https/templates/assets/font/fontawesome-webfont.svg b/bridgedb/https/templates/assets/font/fontawesome-webfont.svg
new file mode 100755
index 0000000..2edb4ec
--- /dev/null
+++ b/bridgedb/https/templates/assets/font/fontawesome-webfont.svg
@@ -0,0 +1,399 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" " horiz-adv-x="448" />
+<glyph unicode="	" horiz-adv-x="448" />
+<glyph unicode=" " horiz-adv-x="448" />
+<glyph unicode="¨" horiz-adv-x="1792" />
+<glyph unicode="©" horiz-adv-x="1792" />
+<glyph unicode="®" horiz-adv-x="1792" />
+<glyph unicode="´" horiz-adv-x="1792" />
+<glyph unicode="Æ" horiz-adv-x="1792" />
+<glyph unicode=" " horiz-adv-x="768" />
+<glyph unicode=" " />
+<glyph unicode=" " horiz-adv-x="768" />
+<glyph unicode=" " />
+<glyph unicode=" " horiz-adv-x="512" />
+<glyph unicode=" " horiz-adv-x="384" />
+<glyph unicode=" " horiz-adv-x="256" />
+<glyph unicode=" " horiz-adv-x="256" />
+<glyph unicode=" " horiz-adv-x="192" />
+<glyph unicode=" " horiz-adv-x="307" />
+<glyph unicode=" " horiz-adv-x="85" />
+<glyph unicode=" " horiz-adv-x="307" />
+<glyph unicode=" " horiz-adv-x="384" />
+<glyph unicode="™" horiz-adv-x="1792" />
+<glyph unicode="∞" horiz-adv-x="1792" />
+<glyph unicode="≠" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
+<glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
+<glyph unicode="" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t1
9 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28
t28 -68z" />
+<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
+<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
+<glyph unicode="" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
+<glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
+<glyph unicode="" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " />
+<glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
+<glyph unicode="" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
+<glyph unicode="" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
+<glyph unicode="" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -1
13 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
+<glyph unicode="" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
+<glyph unicode="" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
+<glyph unicode="" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
+<glyph unicode="" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
+<glyph unicode="" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" />
+<glyph unicode="" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" />
+<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" />
+<glyph unicode="" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" />
+<glyph unicode="" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t
-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
+<glyph unicode="" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
+<glyph unicode="" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
+<glyph unicode="" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
+<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
+<glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
+<glyph unicode="" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" />
+<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
+<glyph unicode="" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
+<glyph unicode="" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
+<glyph unicode="" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
+<glyph unicode="" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
+<glyph unicode="" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
+<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
+<glyph unicode="" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
+<glyph unicode="" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
+<glyph unicode="" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
+<glyph unicode="" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
+<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
+<glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
+<glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152
-23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
+<glyph unicode="" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
+<glyph unicode="" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
+<glyph unicode="" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
+<glyph unicode="" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
+<glyph unicode="" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
+<glyph unicode="" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5
-68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
+<glyph unicode="" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
+<glyph unicode="" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" />
+<glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
+<glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
+<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
+<glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
+<glyph unicode="" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
+<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17
t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-1
5 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q
-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
+<glyph unicode="" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
+<glyph unicode="" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
+<glyph unicode="" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
+<glyph unicode="" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
+<glyph unicode="" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
+<glyph unicode="" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-
768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
+<glyph unicode="" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
+<glyph unicode="" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
+<glyph unicode="" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
+<glyph unicode="" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 11
3v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
+<glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
+<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" />
+<glyph unicode="" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" />
+<glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
+<glyph unicode="" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
+<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
+<glyph unicode="" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
+<glyph unicode="" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
+<glyph unicode="" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
+<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5
v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9
.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.
5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
+<glyph unicode="" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
+<glyph unicode="" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
+<glyph unicode="" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
+<glyph unicode="" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
+<glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
+<glyph unicode="" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
+<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
+<glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
+<glyph unicode="" horiz-adv-x="1152" d="M896 608v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h224q14 0 23 -9t9 -23zM1024 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 -28 t-28 -68v-704q0 -40 28 -68t68 -28h704q40 0 68 28t28 68zM1152 928v-704q0 -92 -65.5 -158t-158.5 -66h-704q-93 0 -158.5 66t-65.5 158v704q0 93 65.5 158.5t158.5 65.5h704q93 0 158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="" horiz-adv-x="1152" d="M928 1152q93 0 158.5 -65.5t65.5 -158.5v-704q0 -92 -65.5 -158t-158.5 -66h-704q-93 0 -158.5 66t-65.5 158v704q0 93 65.5 158.5t158.5 65.5h704zM1024 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 -28t-28 -68v-704q0 -40 28 -68t68 -28h704q40 0 68 28t28 68z M864 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576z" />
+<glyph unicode="" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
+<glyph unicode="" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16
h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
+<glyph unicode="" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23
-9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
+<glyph unicode="" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
+<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
+<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
+<glyph unicode="" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
+<glyph unicode="" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1708 881l-188 -881h-304l181 849q4 21 1 43q-4 20 -16 35q-10 14 -28 24q-18 9 -40 9h-197l-205 -960h-303l204 960h-304l-205 -960h-304l272 1280h1139q157 0 245 -118q86 -116 52 -281z" />
+<glyph unicode="" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
+<glyph unicode="" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
+<glyph unicode="" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
+<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
+<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
+<glyph unicode="" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
+<glyph unicode="" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
+<glyph unicode="" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1664 352v-32q0 -132 -94 -226t-226 -94h-128q-132 0 -226 94t-94 226v480h-224q-2 -102 -14.5 -190.5t-30.5 -156t-48.5 -126.5t-57 -99.5t-67.5 -77.5t-69.5 -58.5t-74 -44t-69 -32t-65.5 -25.5q-4 -2 -32 -13q-8 -2 -12 -2q-22 0 -30 20l-71 178q-5 13 0 25t17 17 q7 3 20 7.5t18 6.5q31 12 46.5 18.5t44.5 20t45.5 26t42 32.5t40.5 42.5t34.5 53.5t30.5 68.5t22.5 83.5t17 103t6.5 123h-256q-14 0 -23 9t-9 23v160q0 14 9 23t23 9h1216q14 0 23 -9t9 -23v-160q0 -14 -9 -23t-23 -9h-224v-512q0 -26 19 -45t45 -19h128q26 0 45 19t19 45 v64q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1280 1376v-160q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v160q0 14 9 23t23 9h960q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
+<glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
+<glyph unicode="" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
+<glyph unicode="" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
+<glyph unicode="" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
+<glyph unicode="" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
+<glyph unicode="" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21
87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -10
6 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
+<glyph unicode="" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
+<glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
+<glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
+<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
+<glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
+<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
+<glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
+<glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " />
+<glyph unicode="" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
+<glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
+<glyph unicode="" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
+<glyph unicode="" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
+<glyph unicode="" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
+<glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31
-29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 1
0.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t
1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
+<glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" />
+<glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
+<glyph unicode="" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
+<glyph unicode="" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
+<glyph unicode="" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10
10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
+<glyph unicode="" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t3
7 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
+<glyph unicode="" horiz-adv-x="1920" d="M805 163q-122 -67 -261 -67q-141 0 -261 67q98 61 167 149t94 191q25 -103 94 -191t167 -149zM453 1176v-344q0 -179 -89.5 -326t-234.5 -217q-129 152 -129 351q0 200 129.5 352t323.5 184zM958 991q-128 -152 -128 -351q0 -201 128 -351q-145 70 -234.5 218t-89.5 328 v341q196 -33 324 -185zM1638 163q-122 -67 -261 -67q-141 0 -261 67q98 61 167 149t94 191q25 -103 94 -191t167 -149zM1286 1176v-344q0 -179 -91 -326t-237 -217v0q133 154 133 351q0 195 -133 351q129 151 328 185zM1920 640q0 -201 -129 -351q-145 70 -234.5 218 t-89.5 328v341q194 -32 323.5 -184t129.5 -352z" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+<glyph unicode="" horiz-adv-x="1792" />
+</font>
+</defs></svg>
\ No newline at end of file
diff --git a/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf b/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf
new file mode 100755
index 0000000..d365924
Binary files /dev/null and b/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf differ
diff --git a/bridgedb/https/templates/assets/font/fontawesome-webfont.woff b/bridgedb/https/templates/assets/font/fontawesome-webfont.woff
new file mode 100755
index 0000000..b9bd17e
Binary files /dev/null and b/bridgedb/https/templates/assets/font/fontawesome-webfont.woff differ
diff --git a/bridgedb/https/templates/assets/font/lato-bold.woff b/bridgedb/https/templates/assets/font/lato-bold.woff
new file mode 100644
index 0000000..4b17251
Binary files /dev/null and b/bridgedb/https/templates/assets/font/lato-bold.woff differ
diff --git a/bridgedb/https/templates/assets/font/lato-italic.woff b/bridgedb/https/templates/assets/font/lato-italic.woff
new file mode 100644
index 0000000..09cc379
Binary files /dev/null and b/bridgedb/https/templates/assets/font/lato-italic.woff differ
diff --git a/bridgedb/https/templates/assets/font/lato-regular.woff b/bridgedb/https/templates/assets/font/lato-regular.woff
new file mode 100644
index 0000000..f48e484
Binary files /dev/null and b/bridgedb/https/templates/assets/font/lato-regular.woff differ
diff --git a/bridgedb/https/templates/assets/tor-roots-blue.svg b/bridgedb/https/templates/assets/tor-roots-blue.svg
new file mode 100644
index 0000000..967843c
--- /dev/null
+++ b/bridgedb/https/templates/assets/tor-roots-blue.svg
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg3030"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ width="885.82672"
+ height="1062.9921"
+ xml:space="preserve"
+ sodipodi:docname="tor-roots.svg"><metadata
+ id="metadata3036"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs3034"><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath3044"><path
+ d="M 0,0 720,0 720,540 0,540 0,0 z"
+ clip-rule="evenodd"
+ id="path3046"
+ inkscape:connector-curvature="0" /></clipPath><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath3056"><path
+ d="m 0,8 730.4,0 0,524.6 L 0,532.6 0,8 z"
+ clip-rule="evenodd"
+ id="path3058"
+ inkscape:connector-curvature="0" /></clipPath><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath3068"><path
+ d="M 0,0 720,0 720,540 0,540 0,0 z"
+ clip-rule="evenodd"
+ id="path3070"
+ inkscape:connector-curvature="0" /></clipPath><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath3076"><path
+ d="m 630.7,7.7 46.7,0 0,40.1 -46.7,0 0,-40.1 z"
+ clip-rule="evenodd"
+ id="path3078"
+ inkscape:connector-curvature="0" /></clipPath><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath3088"><path
+ d="M 0,0 720,0 720,540 0,540 0,0 z"
+ clip-rule="evenodd"
+ id="path3090"
+ inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1278"
+ inkscape:window-height="776"
+ id="namedview3032"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="0.49445096"
+ inkscape:cx="-38.557942"
+ inkscape:cy="516.79248"
+ inkscape:window-x="0"
+ inkscape:window-y="22"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g3038"
+ units="cm" /><g
+ id="g3038"
+ inkscape:groupmode="layer"
+ inkscape:label="2013-03-13-SXSW-Presentation-Loop"
+ transform="matrix(1.25,0,0,-1.25,-139.78938,1119.4344)"><path
+ style="fill:#2c3e50;fill-opacity:1;opacity:1"
+ d="m 440.59196,60.107159 c 1.77285,1.779182 2.10042,2.749995 1.6824,4.986083 -0.48665,2.603271 0.007,3.302678 7.9034,11.197389 l 8.42244,8.420668 8.40124,-8.254833 c 6.43102,-6.318989 8.22542,-8.569982 7.65182,-9.598627 -1.82916,-3.280158 1.40752,-8.095421 5.44156,-8.095421 5.49349,0 7.69468,6.866396 3.31062,10.327138 -1.44224,1.138526 -2.91637,1.607212 -3.98473,1.266931 -1.33158,-0.424129 -3.61478,1.37927 -10.63339,8.398881 l -8.93771,8.938984 0,7.323538 0,7.32354 3.29303,-3.227002 c 2.56179,-2.510421 3.21797,-3.749289 2.95505,-5.578978 -0.75405,-5.246816 6.39302,-8.329277 10.08683,-4.350345 3.79291,4.085659 0.9533,9.631991 -4.97867,9.724432 -2.98412,0.04649 -4.13551,0.651235 -7.44324,3.909403 -3.62619,3.57187 -3.913,4.17655 -3.913,8.24946 0,4.06693 -0.28947,4.68014 -3.87679,8.21335 -2.13216,2.10004 -3.87672,4.23983 -3.87672,4.75507 0,0.51526 1.74456,2.65506 3.87672,4.75508 l 3.87679,3.81829 0,7.16876 c 0,3.9428 0.31161,7.16876 0.69248,7.16876 0.38087,0 1.93657,-1.28809 3.45
704,-2.86243 2.31121,-2.39303 2.67632,-3.30514 2.2265,-5.56233 -0.43282,-2.17168 -0.1055,-3.13401 1.67278,-4.9186 1.21595,-1.22031 3.02144,-2.21873 4.01214,-2.21873 2.32438,0 6.03052,3.71934 6.03052,6.05193 0,2.8732 -3.90897,6.22418 -6.89405,5.90998 -2.06374,-0.21725 -3.35146,0.53479 -6.86865,4.01144 -4.16706,4.11898 -4.32876,4.45845 -4.32876,9.08554 l 0,4.80668 -7.32275,7.31058 -7.32275,7.3106 0,14.26403 0,14.26402 -6.2243,6.2884 -6.22433,6.28839 7.47742,0 c 6.82722,0 7.66468,-0.18795 9.6311,-2.16139 2.62653,-2.63582 5.57084,-2.76335 8.14884,-0.35307 4.10127,3.8345 0.0538,11.18136 -5.40273,9.80696 -1.30615,-0.32896 -2.79372,-1.52081 -3.30569,-2.64845 -0.89438,-1.96991 -1.28952,-2.05034 -10.07275,-2.05034 l -9.14184,0 -7.08838,7.15072 c -5.19899,5.24472 -7.08832,7.7444 -7.08832,9.37821 0,1.62837 -1.78639,4.00866 -6.64147,8.84952 l -6.64144,6.62201 4.4877,4.5603 4.48773,4.5603 0,15.31879 c 0,12.17599 0.23523,15.31879 1.14663,15.31879 1.63093,0 3.36995,3.65803 3.02363,6.3602 -0.22936,
1.7895 0.64823,3.25335 4.01397,6.69546 l 4.30748,4.40524 0.24254,-15.43081 c 0.22378,-14.23635 0.11688,-15.53395 -1.38114,-16.76344 -0.89302,-0.73297 -2.06689,-2.1637 -2.60856,-3.1795 -1.79369,-3.36338 1.21095,-8.02401 5.17292,-8.02401 5.46255,0 7.95065,6.899 3.69858,10.25555 -1.96922,1.55451 -2.17134,2.25078 -1.93837,6.67904 l 0.26002,4.94362 4.95361,-5.05802 4.95364,-5.05803 0,-7.80126 0,-7.80127 -4.85442,-4.92305 c -4.22854,-4.28833 -5.20039,-4.8743 -7.53809,-4.54517 -3.3372,0.46984 -5.5265,-1.17202 -6.22304,-4.6669 -0.42341,-2.12466 -0.0853,-3.10548 1.68089,-4.87809 1.21597,-1.22031 3.02144,-2.21873 4.01215,-2.21873 2.94799,0 6.30952,4.00353 5.74402,6.84107 -0.40038,2.00908 0.14952,3.02267 3.34974,6.17462 l 3.82875,3.77102 0.0586,-2.55755 c 0.0323,-1.4067 0.0109,-5.77379 -0.0534,-9.70471 -0.0998,-6.34102 -0.34933,-7.38483 -2.21259,-9.25474 -4.0476,-4.06198 -1.8855,-9.96726 3.6493,-9.96726 5.22053,0 7.64042,6.53037 3.76322,10.15538 -1.50898,1.41085 -1.69712,2.8501 -1.78443,13.650
53 -0.15322,18.95912 -0.81091,16.82852 5.19421,16.82852 3.93009,0 5.16901,-0.29911 5.16901,-1.24782 0,-0.6863 0.86162,-1.85347 1.91469,-2.5937 6.27532,-4.411 13.02773,4.21963 7.01487,8.96611 -3.00981,2.37597 -5.13349,2.26004 -7.60378,-0.4149 -1.80516,-1.95472 -2.60819,-2.17691 -7.03038,-1.94524 l -4.99244,0.26151 0.22128,4.32281 0.22129,4.32278 -6.93375,6.89625 -6.93376,6.89619 0,9.97932 0,9.97928 8.83035,8.92645 8.83036,8.92645 0.24343,-10.64719 c 0.22328,-9.76664 0.10174,-10.75648 -1.46941,-11.96895 -5.88888,-4.54437 -1.22221,-13.76006 5.23786,-10.34378 2.63325,1.39252 3.28167,2.45215 3.29768,5.38886 0.007,1.48195 -0.82104,3.19474 -2.14053,4.42197 l -2.15376,2.00314 0,17.17038 0,17.1704 -5.78023,5.8456 -5.78029,5.84555 2.80094,2.9001 2.80094,2.90007 -0.11107,15.33236 c -0.0907,12.50778 0.1265,15.59919 1.17856,16.78061 1.26462,1.42015 1.75047,3.01476 1.54722,5.07828 -0.056,0.56999 -3.59117,4.42577 -7.85559,8.56835 l -7.75351,7.53201 0,9.9994 0,9.99939 -6.22497,6.28907 -6.22495,6.28
911 6.65572,6.7188 6.65572,6.71879 0,12.30086 0,12.30091 2.08326,0 c 5.04,0 4.80873,1.37475 4.80873,-28.58377 l 0,-27.53418 -2.15376,-2.1614 c -1.18455,-1.18876 -2.15373,-2.97486 -2.15373,-3.96914 0,-2.33256 3.70614,-6.05192 6.03051,-6.05192 2.47899,0 6.03048,3.78034 6.03048,6.41903 0,1.42849 -0.91312,2.85126 -2.66081,4.14597 l -2.66084,1.97117 0.2828,27.09457 c 0.1556,14.90203 0.48066,27.21118 0.72244,27.35367 0.24183,0.14237 2.47498,0.0585 4.96252,-0.18703 l 4.5229,-0.44612 0,-17.46281 0,-17.46277 -2.15375,-2.03057 c -2.88179,-2.71692 -2.83394,-6.2541 0.11615,-8.58285 2.98088,-2.35316 5.09314,-2.26229 7.67646,0.33019 2.80923,2.81927 2.66733,5.31161 -0.4699,8.25266 l -2.58451,2.42287 0,17.37667 c 0,19.37107 -0.52929,17.94555 6.24587,16.82171 l 3.2306,-0.53587 0,-11.65021 c 0,-11.3971 -0.0467,-11.68707 -2.15374,-13.35032 -4.38005,-3.45761 -2.00082,-10.22028 3.59569,-10.22028 5.30675,0 7.52615,6.88125 3.29634,10.22028 -2.10733,1.66348 -2.15376,1.95186 -2.15376,13.37096 0,9.84469 0.21
321,11.75291 1.36271,12.19558 0.74948,0.28864 4.82006,0.52478 9.04573,0.52478 l 7.68305,0 0,-20.25115 0,-20.25119 -10.70009,-10.78494 -10.70007,-10.78494 -0.0994,-16.57493 -0.0996,-16.57494 6.00928,-6.21945 6.00929,-6.21945 0.18972,-5.88108 0.1897,-5.88106 -6.16806,-6.23158 -6.16813,-6.23166 0,-16.71416 0,-16.7142 -2.15376,-1.70017 c -4.38004,-3.45757 -2.00082,-10.22028 3.59571,-10.22028 5.30673,0 7.52611,6.88128 3.2963,10.22028 l -2.15376,1.70017 0,16.33837 0,16.33838 3.89023,3.8315 3.89018,3.8315 4.8449,-4.8621 4.8449,-4.86212 -0.11046,-28.40064 c -0.0608,-15.62034 -0.11482,-28.7158 -0.12015,-29.10106 -0.005,-0.38526 -3.59553,-0.62446 -7.97841,-0.5316 -6.9784,0.14789 -8.27151,0.42297 -10.40409,2.21345 -3.12976,2.62768 -5.28925,2.58454 -8.02442,-0.16037 -2.54928,-2.55835 -2.27896,-5.86939 0.68458,-8.38595 2.65958,-2.2585 5.44762,-1.86786 7.94739,1.1135 2.19301,2.61552 2.23527,2.62544 10.10208,2.37756 l 7.89979,-0.24897 -0.0317,-5.5089 -0.0316,-5.50886 -5.35142,-5.41865 c -4.15434,-
4.20647 -5.38264,-6.04119 -5.49091,-8.20179 -0.11106,-2.21558 -1.43287,-4.08634 -6.48165,-9.17355 -4.03166,-4.06232 -7.0297,-6.39037 -8.22932,-6.39037 -2.48967,0 -5.6335,-3.44499 -5.6335,-6.17311 0,-2.394 3.20199,-5.93076 5.3694,-5.93076 4.25447,0 7.29005,4.3597 5.79088,8.31684 -0.82506,2.17781 -0.54161,2.72103 3.63483,6.96506 2.49135,2.53165 4.77568,4.60299 5.07632,4.60299 0.30062,0 0.61403,-2.25417 0.69637,-5.00931 0.13905,-4.65044 -0.0369,-5.1478 -2.45554,-6.94239 -3.12358,-2.31754 -3.46727,-5.81411 -0.84876,-8.63476 2.14554,-2.31113 5.14881,-2.42622 7.76805,-0.2977 2.6187,2.128 2.66859,6.74649 0.0956,8.83748 -1.71615,1.3946 -1.86639,2.307 -1.86415,11.32071 l 9.8e-4,9.80387 5.57271,5.61968 5.57274,5.61965 0.0244,28.05758 0.0245,28.05757 3.446,-3.37685 3.44598,-3.37691 0,-27.80275 0,-27.80275 -7.32274,-7.00757 c -7.97519,-7.63193 -8.81635,-9.67878 -5.32884,-12.96678 2.55248,-2.40645 6.0699,-2.4012 8.31053,0.013 2.23972,2.41259 2.23089,5.05061 -0.0264,7.93067 l -1.78314,2.27496 4.7
9772,4.73843 4.79771,4.73843 -0.0511,20.55305 c -0.0285,11.30418 -0.028,20.93626 6.9e-4,21.40463 0.0287,0.46833 4.89803,-3.99995 10.82082,-9.92948 l 10.76875,-10.78111 0,-11.71411 0,-11.71409 -4.62435,-4.58313 c -2.86508,-2.83954 -5.24122,-4.49205 -6.2459,-4.34373 -2.87149,0.42396 -5.28576,-0.9481 -6.39435,-3.63402 -0.94772,-2.29616 -0.86879,-2.90703 0.63865,-4.94248 3.90989,-5.27929 11.22699,-2.32445 10.35732,4.18258 -0.37982,2.84172 -0.0722,3.63436 2.31012,5.95157 1.51157,1.47026 3.02058,2.67319 3.35342,2.67319 0.33281,0 0.60509,-6.60027 0.60509,-14.6673 l 0,-14.6673 -8.8304,-0.2464 c -8.72545,-0.24342 -8.85851,-0.217 -11.20124,2.22447 -1.89791,1.97799 -2.84305,2.35864 -4.73821,1.90839 -2.72281,-0.64687 -5.38264,-3.44144 -5.38264,-5.65527 0,-0.83422 0.80348,-2.54184 1.78552,-3.79473 2.3582,-3.00861 6.09113,-3.06044 8.39857,-0.11653 1.6168,2.06276 2.1111,2.16141 10.83127,2.16141 l 9.13713,0 0,-2.82692 c 0,-2.41984 -1.17482,-4.001 -8.15911,-10.98099 l -8.15915,-8.15406 -0.24048,-10.
41694 -0.24051,-10.41694 -8.45278,-0.24806 c -7.97189,-0.23397 -8.6006,-0.12323 -11.05023,1.94526 -2.84367,2.4013 -4.27933,2.66798 -6.8706,1.27626 -3.98071,-2.13801 -3.69089,-8.5501 0.47251,-10.45387 2.65444,-1.21373 7.06536,0.37212 7.83901,2.81833 0.46409,1.46743 1.44432,1.63617 9.50516,1.63617 l 8.98767,0 0,-10.39616 c 0,-10.13373 -0.0511,-10.41571 -2.03623,-11.17308 -2.29606,-0.87607 -3.13277,-2.51018 -3.13277,-6.11844 0,-2.79161 2.66169,-5.16571 5.79147,-5.16571 4.66157,0 7.17266,6.77502 3.68504,9.94247 -1.45142,1.31821 -1.72299,2.66281 -1.72299,8.53097 l 0,6.96614 4.30749,-4.25781 4.30751,-4.2578 0,-12.64601 c 0,-12.61245 -0.005,-12.6486 -2.12997,-13.61993 -4.22196,-1.93051 -3.94163,-8.7353 0.43481,-10.5545 3.30804,-1.37511 6.53282,-0.1109 7.72162,3.02694 0.96562,2.54882 -0.16774,6.2834 -2.13679,7.04174 -1.08911,0.4194 -1.30517,2.746 -1.30517,14.05195 l 0,13.54934 -5.59978,5.56968 -5.59972,5.56969 0,5.19693 0,5.19689 8.97117,-8.97246 c 8.37646,-8.37762 8.94118,-9.1582 8.51802,-
11.77495 -1.0612,-6.56269 7.79358,-8.89781 10.99267,-2.89888 2.11779,3.97119 -2.57429,9.22866 -7.21952,8.08949 -1.51892,-0.37246 -3.73433,1.42867 -11.57047,9.4067 l -9.69187,9.86737 0,6.38724 0,6.38721 8.18421,8.17921 8.18425,8.17919 0,26.60234 c 0,14.63128 0.25098,26.60232 0.55769,26.60232 0.30675,0 3.40814,-2.96451 6.892,-6.5878 l 6.33432,-6.58785 0,-24.92148 0,-24.92153 -2.34907,-1.12986 c -3.21721,-1.54748 -4.45587,-5.28238 -2.75846,-8.31737 1.05652,-1.88895 1.91219,-2.31871 4.61684,-2.31871 2.59957,0 3.69493,0.50395 5.04785,2.32252 1.52166,2.04534 1.5974,2.63852 0.63459,4.9712 -0.60131,1.4568 -1.43392,2.64877 -1.85026,2.64877 -0.41635,0 -0.75698,8.64667 -0.75698,19.21486 l 0,19.21484 6.44329,-6.423 c 6.1699,-6.15051 6.42175,-6.55826 5.93556,-9.60947 -0.40242,-2.52553 -0.13067,-3.56472 1.3102,-5.01073 2.38709,-2.3956 6.41879,-2.36409 8.67637,0.0679 3.95939,4.265 0.59661,10.02223 -5.47384,9.37147 -1.0349,-0.11113 -4.41802,2.60953 -9.26108,7.44729 l -7.6305,7.62212 0,5.65478 0,5.6
5472 -8.18425,8.17918 -8.18427,8.17923 0,5.17724 c 0,5.04625 -0.10791,5.28736 -4.27356,9.52726 -4.2366,4.31244 -4.27533,4.40259 -4.48646,10.43843 -0.18505,5.29047 0.0364,6.33863 1.68906,7.99721 2.10586,2.11332 2.49311,5.96945 0.8251,8.2161 -1.81286,2.4417 -6.20526,2.81129 -8.30988,0.6992 -2.63251,-2.64191 -2.45676,-7.40776 0.34101,-9.24746 1.83373,-1.2058 2.15376,-2.07477 2.15376,-5.84826 l 0,-4.43207 -6.04746,6.06893 -6.04741,6.06891 -0.31821,5.47319 c -0.31015,5.33478 -0.44043,5.59713 -5.15206,10.37537 l -4.83385,4.90218 0,6.46057 0,6.46056 -5.3343,5.43679 -5.33435,5.43677 1.45758,2.47627 c 0.98554,1.67429 1.45755,4.38634 1.45755,8.37443 l 0,5.89821 -5.99998,6.07226 -5.99994,6.07225 -0.10428,15.90751 -0.10441,15.90748 10.41196,10.47415 10.41196,10.47414 0,21.3854 0,21.38542 3.44599,0 3.44602,0 0,-32.22813 0,-32.22811 -5.83676,-5.81258 c -4.72525,-4.70576 -6.35389,-5.81262 -8.55246,-5.81262 -1.90777,0 -3.24697,-0.67775 -4.50125,-2.278 -2.23384,-2.84993 -2.23384,-3.83337 0,-6.68331
3.81603,-4.86855 10.9992,-1.90128 10.02227,4.14013 -0.45396,2.80744 -0.1038,3.37157 5.4839,8.83293 l 5.96878,5.83398 0,33.55014 c 0,31.37164 0.098,33.5509 1.50763,33.56194 0.82918,0.007 2.3799,0.2464 3.44599,0.53312 l 1.93838,0.52129 0,-12.83397 0,-12.83399 6.65572,-6.71881 6.65574,-6.71879 -6.22496,-6.28908 -6.22499,-6.28911 0,-9.99939 0,-9.9994 -7.75351,-7.532 c -8.36835,-8.12931 -9.51045,-10.76699 -6.0305,-13.92752 1.60226,-1.45517 1.72303,-2.66284 1.72303,-17.23229 l 0,-15.66743 5.93537,-5.91085 c 5.6082,-5.58501 5.91166,-6.08847 5.50474,-9.13309 -0.36785,-2.75196 -0.0856,-3.49471 1.93428,-5.08912 3.09842,-2.44589 5.16876,-2.35675 7.85398,0.33804 1.69727,1.70327 2.07201,2.73594 1.6471,4.53894 -0.76282,3.23725 -3.7648,5.59183 -6.46817,5.0732 -1.81222,-0.34766 -3.19511,0.59387 -8.00667,5.45136 l -5.81615,5.87161 0,14.75197 0,14.75188 2.30551,1.12789 c 1.26803,0.62034 2.66983,2.23576 3.11508,3.58985 0.65908,2.00431 0.45227,2.94694 -1.1126,5.07108 l -1.92217,2.60904 2.62811,2.63749
2.6281,2.63743 6.51698,-6.34163 c 3.58435,-3.48787 6.51698,-6.7755 6.51698,-7.30574 0,-0.53027 -2.0353,-2.9699 -4.52286,-5.42134 -3.64565,-3.59273 -5.17462,-4.50928 -7.88333,-4.72558 -2.47866,-0.19792 -3.82119,-0.8903 -5.11654,-2.63874 -1.66368,-2.24567 -1.68299,-2.52437 -0.36736,-5.29308 2.97225,-6.25505 10.63775,-4.23837 10.49635,2.76143 -0.0642,3.17482 0.41333,3.99484 4.94905,8.49913 l 5.02003,4.98533 3.86534,-3.80703 c 3.57672,-3.52279 3.8262,-4.05263 3.34119,-7.09635 -1.18872,-7.46039 8.08326,-10.10879 10.96313,-3.13147 0.90618,2.19544 0.81566,2.84893 -0.67308,4.85911 -1.16379,1.57139 -2.66761,2.40881 -4.65013,2.58949 -2.46757,0.22491 -4.7963,2.13007 -14.75432,12.07094 -11.74938,11.72907 -11.81391,11.81505 -10.18242,13.56595 1.41706,1.52072 1.64138,3.11386 1.63786,11.63247 l -0.004,9.87042 5.63995,5.66 5.63998,5.66 2.43559,-2.34172 c 1.95183,-1.87664 2.36753,-2.92677 2.09293,-5.28712 -0.6952,-5.97622 6.42511,-8.98558 10.11672,-4.27576 2.13834,2.72814 1.71712,5.05326 -1.43566,7.
92453 -1.50728,1.37267 -2.82709,1.85183 -4.16937,1.51378 -1.62877,-0.41026 -3.66674,1.19362 -11.93976,9.3965 l -9.97549,9.89127 0,12.48574 c 0,12.21738 0.0418,12.49695 1.93839,13.00706 5.00017,1.3448 4.95362,1.43935 4.95362,-10.05495 l 0,-10.59989 15.50378,-15.5409 15.50374,-15.54087 -8.3964,-8.47315 c -6.8812,-6.94412 -8.74753,-8.38617 -10.34218,-7.99106 -5.95321,1.47487 -9.89203,-6.16197 -5.23272,-10.1455 3.92241,-3.35355 9.33225,-0.54941 9.33225,4.83724 0,1.6604 0.64447,3.47727 1.51333,4.26634 1.40525,1.27625 1.86673,1.03366 6.46125,-3.39729 l 4.94792,-4.77174 0,-17.02699 0,-17.02703 -5.59975,-5.56966 -5.59976,-5.56972 0,-6.48202 0,-6.48199 4.49346,-4.56613 4.49342,-4.56614 -4.38795,0 c -2.8494,0 -4.76398,0.45469 -5.46041,1.29687 -2.50861,3.03339 -9.4765,-0.46293 -9.4765,-4.7551 0,-2.33261 3.70618,-6.05195 6.03051,-6.05195 2.10015,0 6.03051,3.63694 6.03051,5.58039 0,1.05356 1.07073,1.3361 5.06349,1.3361 5.64012,0 7.60068,-1.35285 7.13447,-4.92306 -0.44146,-3.38076 2.03002,-6.3162
4 5.31795,-6.31624 3.63159,0 5.59327,1.499 6.22283,4.7551 0.74675,3.86201 -2.23182,6.77646 -6.40285,6.26504 -2.94142,-0.36069 -3.50563,0.013 -9.79778,6.47483 l -6.67662,6.85759 0,4.98498 c 0,4.98364 9.7e-4,4.98646 5.58009,10.55747 l 5.58009,5.57247 0.23503,16.25677 0.23503,16.25682 2.79984,-2.68647 2.79991,-2.68646 0,-16.96843 0,-16.96842 -2.20767,-2.0533 c -2.88649,-2.6846 -2.62057,-6.77462 0.59649,-9.17392 3.86662,-2.88373 8.50317,-0.14699 8.50317,5.01883 0,2.19566 -0.55403,3.30861 -2.17909,4.37712 l -2.1791,1.43292 0.0923,15.54338 0.0925,15.54334 2.71917,-2.4749 c 1.49558,-1.36121 3.01022,-2.86396 3.36585,-3.33945 0.35566,-0.47554 0.65261,-15.43935 0.65987,-33.25294 l 0.0135,-32.38831 -3.62534,-3.70714 -3.62541,-3.70713 -9.25796,0 c -8.72648,0 -9.3816,0.12413 -11.41169,2.16143 -2.6215,2.63083 -4.72354,2.7315 -7.71565,0.36951 -2.77487,-2.19044 -2.99189,-5.74997 -0.51336,-8.4198 2.59844,-2.79906 6.36189,-2.49617 8.71762,0.70151 1.87806,2.54923 2.04437,2.59369 9.69972,2.59369 l 7.78
893,0 -7.52018,-7.58198 c -7.51254,-7.5744 -7.52014,-7.58657 -7.52014,-12.06093 0,-2.84983 -0.42431,-4.73034 -1.1665,-5.17026 -1.44083,-0.854 -3.14102,-4.00537 -3.14102,-5.82192 0,-2.21656 3.25262,-4.81193 6.0305,-4.81193 5.52964,0 7.85996,6.02368 3.87673,10.02105 -1.61646,1.62225 -2.15373,3.07104 -2.15373,5.80807 0,3.53693 0.36292,4.01024 12.06099,15.72738 l 12.06102,12.08069 0,31.78422 c 0,17.48135 0.18859,31.78427 0.41911,31.78427 0.23055,0 1.77075,-1.45897 3.42269,-3.24213 l 3.00353,-3.24209 0.0234,-32.01199 0.0234,-32.01197 -5.83672,-5.81259 c -4.78105,-4.76127 -6.33926,-5.81263 -8.61499,-5.81263 -6.55276,0 -8.49219,-8.17042 -2.68039,-11.2919 4.04942,-2.17486 9.9263,3.57574 7.77568,7.60852 -0.75681,1.41917 0.0472,2.59102 5.52763,8.05398 l 6.41321,6.39316 0,24.21314 0,24.21315 8.59083,-8.58897 8.59079,-8.58901 -6.00631,-6.21634 -6.00631,-6.21632 0,-14.51785 c 0,-13.76403 0.0984,-14.62285 1.89365,-16.54069 l 1.89367,-2.02284 -5.7704,-5.83562 c -3.25191,-3.28867 -5.77039,-6.58913
-5.77039,-7.56213 0,-1.01311 -2.94426,-4.66592 -7.12539,-8.84007 -5.43289,-5.42388 -7.68379,-7.11355 -9.47652,-7.11355 -5.56775,0 -7.70416,-7.16356 -3.14939,-10.56019 4.5597,-3.40033 10.55465,1.38306 8.50707,6.78777 -0.84592,2.23279 -0.53272,2.74571 5.17188,8.47061 l 6.07235,6.09393 0,-43.63128 0,-43.63126 -6.46128,6.4409 c -5.4189,5.40188 -6.46123,6.89771 -6.46123,9.27241 0,5.25918 -5.0277,7.93624 -9.28479,4.94385 -1.92959,-1.35633 -2.63439,-5.74614 -1.25538,-7.81874 1.19971,-1.80308 4.48,-2.97968 6.72838,-2.41336 1.9783,0.49825 3.13993,-0.31666 9.49421,-6.66035 6.13653,-6.12634 7.24009,-7.65928 7.24009,-10.05701 l 0,-2.82892 -8.30083,0 c -7.74763,0 -8.43388,0.14397 -10.29687,2.16141 -4.58993,4.97024 -12.4598,0.0816 -9.68995,-6.01912 1.84662,-4.06729 8.10649,-4.38354 10.17014,-0.51386 0.85944,1.61154 1.75134,1.77788 9.53282,1.77788 8.0301,0 8.58469,-0.11243 8.58469,-1.74112 0,-1.06861 -3.49407,-5.18444 -9.04576,-10.65552 l -9.04574,-8.91441 0,-22.01123 0,-22.01118 -4.55,-4.50942 c
-3.75436,-3.72094 -5.08128,-4.50941 -7.58878,-4.50941 -5.75467,0 -8.40778,-4.65019 -5.07736,-8.89918 3.56927,-4.55376 10.30982,-1.992449 10.33706,3.92795 0.0109,2.19815 1.04158,3.92219 4.73824,7.91985 l 4.72535,5.11001 0,13.87784 0,13.87786 4.3075,-4.25785 4.3075,-4.25779 0,-27.28981 0,-27.289813 -2.58452,-2.275923 c -3.03863,-2.675818 -3.25292,-4.804205 -0.79896,-7.934993 2.18456,-2.787087 6.07346,-3.03667 8.55246,-0.548876 2.38861,2.397093 2.2009,6.164535 -0.43076,8.645619 -1.92866,1.818328 -2.15375,2.713011 -2.15375,8.561097 l 0,6.530559 7.17785,-7.165957 c 6.71199,-6.700813 7.17334,-7.393613 7.1074,-10.67308 -0.14053,-6.993553 7.52654,-9.005663 10.49684,-2.754722 1.31562,2.768648 1.29628,3.047473 -0.36697,5.293059 -1.19754,1.616845 -2.68722,2.45579 -4.6858,2.638879 -2.39372,0.219289 -4.46766,1.798937 -11.32967,8.629398 l -8.39965,8.360968 0,12.964365 0,12.96436 6.9129,-6.89622 c 5.90884,-5.89463 6.84238,-7.21835 6.42766,-9.11432 -1.16039,-5.30495 6.03696,-9.02744 9.91995,-5.1306
6 3.68889,3.70202 1.1495,9.51019 -4.15798,9.51019 -2.89115,0 -3.97178,0.77896 -11.23573,8.09917 -10.46251,10.54352 -10.40647,11.29514 0.8584,11.51469 7.98443,0.15561 8.31175,0.0872 9.40802,-1.9686 3.40289,-6.38093 13.41965,-1.2686 10.42747,5.32193 -1.79268,3.94847 -8.74195,4.51057 -10.15268,0.82123 -0.67459,-1.7642 -18.40801,-1.96086 -18.40801,-0.20416 0,0.60813 -2.51989,3.61205 -5.59973,6.67538 l -5.59974,5.56971 0,5.66005 0,5.66011 9.03868,9.03994 9.03861,9.03989 0.22247,48.23649 0.22247,48.23653 4.0921,-3.93529 4.09216,-3.93536 0,-46.9048 0,-46.90487 -4.17275,-4.12459 c -3.39182,-3.3527 -4.56768,-4.02511 -6.28315,-3.59302 -5.16392,1.30067 -8.9088,-6.06949 -5.05112,-9.94093 3.8878,-3.90159 11.08055,-0.17369 9.91761,5.1402 -0.40275,1.84006 0.26591,2.97245 3.84314,6.50845 l 4.33075,4.28075 0,14.51394 c 0,7.98263 0.26039,14.5139 0.57867,14.5139 0.31827,0 4.58001,-4.04978 9.47057,-8.9995 8.25562,-8.35551 8.85394,-9.19 8.36142,-11.6615 -1.18574,-5.94977 5.72244,-9.58756 10.01882,-5.275
87 1.9627,1.96967 2.15337,3.616 0.73254,6.32493 -1.32934,2.5345 -2.4043,3.15632 -5.48371,3.17203 -2.16747,0.0105 -4.34217,1.7684 -13.13784,10.6166 l -10.54047,10.6034 0,31.56897 0,31.56901 -5.59978,5.56972 c -5.36033,5.33158 -5.59973,5.74903 -5.59973,9.76549 0,3.9189 0.3214,4.53395 4.86939,9.31902 2.67816,2.81776 5.33184,5.12318 5.89699,5.12318 2.60888,0 3.87914,-3.14354 3.87914,-9.59987 l 0,-6.4378 6.46121,-6.44094 6.46127,-6.44094 0,-12.00107 c 0,-11.77138 -0.0413,-12.03361 -2.15376,-13.70125 -2.8583,-2.25632 -2.98459,-6.94451 -0.23905,-8.8744 3.89949,-2.74103 8.22353,-1.14818 9.64929,3.55454 0.41243,1.36023 -0.11857,2.66466 -1.93529,4.75508 -2.48802,2.86268 -2.50459,2.95623 -2.6588,14.98454 l -0.15518,12.10386 -6.42266,6.50332 -6.4226,6.50335 0,6.55889 0,6.5589 -4.30753,4.65297 -4.30747,4.65296 0,13.86628 0,13.86628 5.41753,5.38852 c 5.87138,5.83984 7.77812,5.99871 6.90109,0.57497 -0.36999,-2.28802 -0.0186,-3.30564 1.75452,-5.08537 2.78201,-2.79188 5.22314,-2.82352 7.93426,-0.102
55 1.16299,1.16718 2.11456,2.93561 2.11456,3.92985 0,2.82455 -3.77569,6.05192 -7.08017,6.05192 -2.51053,0 -4.21265,1.36935 -14.25389,11.46703 l -11.40294,11.46704 0,7.33015 0,7.33013 -19.01211,19.09389 -19.01217,19.09386 5.8656,5.71563 c 5.3118,5.17597 6.00034,5.59321 7.29286,4.41937 1.03662,-0.94148 1.30633,-2.20062 0.98552,-4.60103 -0.62092,-4.64569 2.06459,-7.37727 6.53195,-6.64407 3.11807,0.51174 3.2225,0.42525 21.00975,-17.40236 l 17.87613,-17.91663 0,-12.12781 0,-12.12779 8.61499,-8.61316 8.615,-8.61322 0,-6.95769 0,-6.95767 -5.59975,-5.56971 -5.59976,-5.56971 0,-11.57212 c 0,-11.31384 -0.0482,-11.61009 -2.15373,-13.27231 -4.19018,-3.30768 -1.91668,-10.22028 3.36134,-10.22028 6.03693,0 8.20157,7.23392 3.25904,10.89148 -1.0039,0.74291 -1.32253,3.40207 -1.46707,12.24342 l -0.18471,11.29441 5.48457,5.45513 5.48459,5.45511 0,8.25454 0,8.25452 -8.61504,8.61319 -8.61497,8.61316 0,12.11273 0,12.11276 -18.21191,18.29142 c -15.64771,15.71612 -18.24344,18.69458 -18.43629,21.15459 -0.278
54,3.55332 -3.3094,6.26198 -6.35344,5.67801 -1.86276,-0.35735 -4.59247,2.02692 -20.06768,17.52799 l -17.91165,17.94163 0,10.8421 c 0,10.73228 0.0209,10.84737 2.07371,11.36443 1.14054,0.28726 2.69122,0.76009 3.44602,1.05078 1.17553,0.4527 1.37227,-0.34678 1.37227,-5.57609 l 0,-6.10458 6.45367,-6.74023 c 5.51585,-5.7607 6.45478,-7.18057 6.46126,-9.77023 0.007,-2.83378 2.23962,-5.26951 34.46754,-37.6044 32.37097,-32.4784 34.45998,-34.75956 34.45998,-37.62936 0,-2.61212 0.81119,-3.86179 5.59571,-8.62063 6.84254,-6.80584 7.96352,-10.23367 4.42381,-13.52749 -4.1027,-3.8178 -1.96023,-10.6843 3.33371,-10.6843 5.23005,0 7.47329,6.98568 3.446,10.73131 -1.48656,1.38257 -2.15376,2.917 -2.15376,4.95312 0,2.50406 -0.91159,3.85785 -6.03046,8.95557 -5.42061,5.39819 -6.03052,6.3479 -6.03052,9.39071 0,3.3033 -0.83286,4.22242 -34.46,38.02891 -32.4744,32.64765 -34.45993,34.8214 -34.45993,37.7264 0,2.68718 -0.82876,3.9087 -6.46129,9.52352 l -6.46122,6.44093 0,6.11604 0,6.11601 2.86101,1.03824 c 1.57362,
0.57103 3.13416,1.03825 3.46793,1.03825 0.33381,0 0.53564,-2.18822 0.44854,-4.86273 l -0.15834,-4.86275 6.59768,-6.4937 c 5.4098,-5.32458 6.59957,-6.97853 6.60829,-9.1864 0.007,-2.05235 1.0358,-3.75487 4.32175,-7.15951 4.01486,-4.1598 4.2407,-4.62203 3.2858,-6.72533 -2.30167,-5.06954 5.53624,-9.75684 9.56104,-5.71775 3.57526,3.58796 1.09683,9.62374 -4.05858,9.88391 -1.97404,0.0998 -3.82668,1.21007 -6.63727,3.97826 -3.18024,3.13227 -3.89886,4.40842 -3.89886,6.92379 0,2.68861 -0.82785,3.90897 -6.46126,9.5247 l -6.46125,6.44091 0,4.90798 c 0,4.59419 0.17894,5.0222 2.79988,6.69526 l 2.79989,1.78731 0.26476,-2.78311 c 0.22169,-2.33029 3.30147,-5.8399 18.92888,-21.57047 l 18.66408,-18.78736 -3.20649,-3.29544 c -2.16336,-2.2233 -3.20651,-4.08688 -3.20651,-5.72822 0,-1.56523 -0.76806,-3.03907 -2.15375,-4.13294 -2.70526,-2.13554 -2.85854,-6.0547 -0.33581,-8.58643 1.43848,-1.4436 2.47783,-1.71848 4.97944,-1.31706 l 3.16148,0.50736 24.29312,-24.40366 24.29311,-24.40369 0.1238,-7.19192 c 0.1065
8,-6.20715 0.40427,-7.52646 2.1722,-9.63498 1.72334,-2.05535 2.04838,-3.38883 2.04838,-8.40269 l 0,-5.95965 -4.30747,-4.2578 -4.30749,-4.25778 0,-11.30432 0,-11.30431 -4.3075,-4.25781 -4.30749,-4.25783 0,-14.94617 c 0,-12.70914 -0.19344,-14.94619 -1.29224,-14.94619 -2.01499,0 -4.73826,-3.22986 -4.73826,-5.61967 0,-5.01437 6.81962,-7.48127 10.30444,-3.72748 2.1092,2.272 2.24066,4.81056 0.38656,7.46702 -1.13136,1.62099 -1.35106,4.47777 -1.26131,16.40288 l 0.10851,14.44005 2.55363,2.57709 2.55354,2.57705 0,-22.92136 0,-22.92132 -5.16903,-5.13319 c -5.08574,-5.05062 -5.16896,-5.21073 -5.16896,-9.94497 0,-4.68674 0.12299,-4.93361 4.73825,-9.50771 l 4.73823,-4.69602 0,-5.82063 0,-5.82066 -6.61209,-6.5289 c -4.75251,-4.69267 -7.23601,-6.53261 -8.83039,-6.54213 -2.72214,-0.0178 -3.81045,-0.70515 -5.15227,-3.26149 -2.98052,-5.67845 4.88154,-10.76519 9.4902,-6.14018 1.44171,1.44686 1.71269,2.48471 1.30948,5.01539 -0.47907,3.00659 -0.2107,3.48671 4.64329,8.30714 l 5.15178,5.11613 0,-27.66841 0
,-27.66847 4.64384,-4.60241 c 4.36474,-4.32583 4.61664,-4.80595 4.19146,-7.98711 -0.57146,-4.27567 1.42757,-6.80406 5.37945,-6.80406 3.38541,0 5.59974,2.28164 5.59974,5.76991 0,3.12266 -2.31292,5.46943 -5.39058,5.46943 -1.50888,0 -3.80591,1.46604 -7.03954,4.49299 l -4.79989,4.49301 0,20.78425 0,20.7842 12.49177,-12.51375 12.49175,-12.51379 0,-5.50011 c 0,-4.99644 -0.23668,-5.72196 -2.58448,-7.92295 -3.11552,-2.92069 -3.32392,-5.91797 -0.59063,-8.49493 2.55246,-2.40645 6.06994,-2.40126 8.31053,0.013 2.35874,2.54081 2.21372,5.50871 -0.39718,8.12893 -1.74193,1.74811 -2.15377,3.01613 -2.15377,6.63152 l 0,4.4701 4.73826,-4.69601 4.73825,-4.69602 0,-6.11101 0,-6.11101 4.74348,-4.70115 4.74344,-4.70119 -6.55771,-6.61985 c -4.72468,-4.76946 -7.00996,-6.50114 -8.17562,-6.19523 -2.96612,0.77837 -5.70161,-0.5316 -6.83276,-3.27216 -0.96759,-2.34427 -0.89374,-2.93519 0.62264,-4.98221 2.45969,-3.3204 7.36433,-3.68366 9.31879,-0.69021 0.77663,1.18947 1.28457,3.14677 1.12878,4.3495 -0.22575,1.74288
1.02396,3.50765 6.15636,8.69339 l 6.43967,6.50653 5.83922,-5.81507 c 5.24908,-5.2273 5.78937,-6.08164 5.34614,-8.45265 -1.07992,-5.77687 5.55373,-9.55546 9.80211,-5.58345 3.90647,3.6524 1.1946,9.65884 -4.44976,9.85554 -2.86502,0.0999 -4.26124,1.19963 -14.35694,11.30871 l -11.18332,11.19815 0,6.27005 0,6.27008 -19.81449,19.88732 -19.81453,19.88726 0,12.78995 0,12.78992 -4.73822,4.69602 c -4.24735,4.20952 -4.73822,5.07193 -4.73822,8.32451 0,3.27304 0.50631,4.14457 5.16897,8.89699 l 5.169,5.26858 0,31.55074 c 0,17.35295 0.31163,31.5508 0.69252,31.5508 0.38087,0 1.93156,-1.28292 3.44596,-2.85099 l 2.75352,-2.85099 0,-33.46061 c 0,-28.58212 -0.18132,-33.4606 -1.24342,-33.4606 -1.73919,0 -3.92558,-3.4032 -3.92558,-6.11031 0,-2.93077 2.46417,-5.12899 5.74945,-5.12899 5.13614,0 7.45981,5.95496 3.83471,9.82744 -1.8019,1.92486 -1.83067,2.48486 -1.83067,35.6473 l 0,33.69173 -4.73824,4.69602 c -6.10973,6.05529 -6.1637,7.73393 -0.43074,13.40071 l 4.30749,4.25782 0,7.89569 c 0,6.79772 -0.2396,8.
11331 -1.723,9.46054 -1.45875,1.32482 -1.72303,2.66282 -1.72303,8.72369 l 0,7.15887 -25.44373,25.50804 c -22.78166,22.83919 -25.387,25.73511 -24.90099,27.67839 0.40981,1.63855 -0.005,2.72018 -1.69348,4.41455 -3.02372,3.03447 -2.92295,6.36324 0.27792,9.18193 l 2.51417,2.214 24.27318,-24.37074 c 23.50492,-23.59937 24.28486,-24.49097 24.64169,-28.16999 0.31676,-3.26625 1.09115,-4.53109 5.51887,-9.01439 l 5.15039,-5.21507 0,-20.56591 0,-20.56589 -2.58449,-2.18243 c -3.35352,-2.83183 -3.47935,-6.38428 -0.31456,-8.8825 2.80164,-2.21162 4.71214,-2.26363 7.25467,-0.19745 2.6187,2.128 2.66862,6.74649 0.0956,8.83748 -1.81568,1.47546 -1.86661,2.05457 -1.86661,21.22706 l 0,19.7102 3.87674,-3.81827 3.87676,-3.81826 0,-12.75499 0,-12.755 3.446,-3.42388 c 3.30325,-3.282 3.44599,-3.6522 3.44599,-8.93585 0,-5.29148 -0.13797,-5.64708 -3.44599,-8.88889 -3.27524,-3.20956 -3.446,-3.63794 -3.446,-8.64558 0,-5.00383 -0.17248,-5.43782 -3.43087,-8.63085 -1.88701,-1.84916 -4.2897,-3.4936 -5.3393,-3.65434 -4.
66493,-0.71447 -6.81186,-6.99564 -3.43837,-10.05946 2.09109,-1.89913 6.42172,-1.81977 8.19825,0.15021 1.58205,1.75434 1.9082,6.23462 0.55287,7.59476 -0.58953,0.59162 -0.1701,1.53862 1.29228,2.91729 l 2.16514,2.04129 0,-26.31845 0,-26.31846 -9.47652,-9.48066 -9.47645,-9.4807 0,-4.94668 c 0,-4.12369 -0.35833,-5.30627 -2.15376,-7.10807 -2.62155,-2.63083 -2.72186,-4.74032 -0.36826,-7.74306 2.26537,-2.89018 5.84564,-3.0082 8.40233,-0.27704 2.45258,2.61996 2.37152,5.35854 -0.23612,7.97544 -1.66389,1.66981 -2.15026,3.13455 -2.30359,6.93714 l -0.19428,4.82035 7.90328,7.93144 7.90337,7.93142 0,-9.83749 c 0,-9.38277 -0.0995,-9.93004 -2.15374,-11.8406 -2.36786,-2.20224 -2.98705,-7.25682 -1.07689,-8.79049 2.17768,-1.74842 4.98595,-2.05126 7.47339,-0.80594 3.54451,1.77449 4.37222,0.51007 4.37222,-6.67883 0,-5.53193 -0.20111,-6.2236 -2.29732,-7.90104 -1.63092,-1.30504 -2.29733,-2.64311 -2.29733,-4.61257 0,-5.11335 5.1311,-7.58348 9.15483,-4.40715 3.05972,2.41537 3.13547,6.23774 0.17799,8.98831 -1
.70046,1.58156 -2.15373,2.84647 -2.15373,6.01066 0,2.20412 0.30901,4.00747 0.68665,4.00747 0.37767,0 2.12223,-1.47532 3.87677,-3.27852 3.10316,-3.18924 3.19006,-3.45459 3.19006,-9.74041 l 0,-6.46189 9.90725,-9.91425 9.90725,-9.91423 0,-4.67009 c 0,-3.87086 -0.36857,-5.01284 -2.15375,-6.67316 -2.93097,-2.72599 -2.88097,-6.5735 0.11615,-8.93943 1.24849,-0.98553 2.74689,-1.7919 3.32981,-1.7919 0.58296,0 2.08136,0.80637 3.32984,1.7919 3.11042,2.45533 3.16956,7.08687 0.11615,9.09466 -1.92897,1.26839 -2.15376,1.99836 -2.15376,6.99411 0,5.18771 -0.21041,5.80073 -3.00775,8.76234 -2.64786,2.8033 -3.00866,3.72757 -3.01527,7.72335 l -0.008,4.53894 8.84145,0 c 8.32448,0 8.89121,-0.11302 9.69188,-1.93303 0.46773,-1.06322 1.91729,-2.30572 3.22126,-2.76121 5.31099,-1.85513 9.8786,5.80196 5.86416,9.83065 -2.25962,2.26769 -5.23256,2.26662 -8.10768,-9.5e-4 -1.89067,-1.49248 -3.62939,-1.80892 -10.66954,-1.94183 l -8.41078,-0.15874 -0.25869,3.54304 c -0.2186,2.99477 0.0813,3.76663 1.93839,4.98775 3.911
3,2.57191 2.6389,8.97001 -2.083,10.47398 -1.73084,0.55131 -5.13501,-0.80718 -6.26011,-2.49821 -1.36174,-2.04658 -0.62452,-5.82131 1.5279,-7.82318 2.07877,-1.93341 2.17483,-2.42828 1.93836,-9.98283 l -0.24884,-7.94855 -5.38439,5.40733 -5.38436,5.40734 0,6.14881 0,6.14884 -7.32274,7.31022 c -4.02749,4.02085 -7.32276,8.01863 -7.32276,8.88393 0,0.86539 -0.96916,2.47479 -2.15374,3.57651 -2.14047,1.99075 -2.15375,2.0972 -2.15375,17.24992 0,8.38579 0.28103,15.24688 0.62452,15.24688 0.34347,0 3.22849,-2.59323 6.41119,-5.76275 4.88612,-4.86593 5.71637,-6.11553 5.33495,-8.02952 -1.08046,-5.42141 6.64264,-8.77421 10.16259,-4.41185 3.64142,4.51297 0.46466,10.0987 -5.302,9.32249 -3.11294,-0.41903 -3.52866,-0.15038 -10.23797,6.61803 l -6.99328,7.0548 0,15.52088 0,15.52085 6.033,-6.00806 c 5.5517,-5.52871 5.9915,-6.23005 5.51284,-8.79067 -1.04507,-5.59022 6.14064,-9.42123 9.95809,-5.30909 2.37249,2.55568 2.2104,5.51209 -0.44862,8.18055 -1.60364,1.60932 -2.81796,2.09547 -4.4516,1.78205 -1.91368,-0.
36714 -3.30973,0.64821 -9.42507,6.85476 l -7.17864,7.28571 0,9.30554 0,9.30554 3.44599,3.37688 3.44602,3.37692 0,6.99783 0,6.99786 -3.44602,3.37687 -3.44599,3.37688 0,10.82608 0,10.82605 6.46125,-6.44094 6.46122,-6.44092 0,-21.21689 0,-21.21688 10.33802,-10.34774 10.33801,-10.34774 0,-13.72208 c 0,-13.60711 -0.0185,-13.73631 -2.15376,-15.42221 -2.15418,-1.70051 -2.93845,-5.97533 -1.49445,-8.1456 1.11007,-1.66833 4.33196,-2.95196 5.95491,-2.37247 4.92818,1.75964 6.11845,6.73543 2.43155,10.16451 -1.9434,1.80747 -2.15378,2.66317 -2.15378,8.76076 l 0,6.75766 6.78286,-6.42452 6.78286,-6.42448 0.10889,-9.81805 c 0.10573,-9.50351 0.0403,-9.87251 -2.0446,-11.5182 -4.21006,-3.32342 -1.68405,-10.22027 3.7432,-10.22027 1.34808,0 3.09263,0.71148 3.87677,1.58105 2.60863,2.89272 1.41033,8.65475 -2.03987,9.80891 -0.57879,0.19363 -0.82043,3.2334 -0.66152,8.3212 l 0.25042,8.01612 9.90724,-9.98198 c 8.82739,-8.89393 9.87693,-10.26982 9.62933,-12.62272 -0.41849,-3.97592 1.85018,-6.8517 5.40522,-6.8517
4.67486,0 7.56654,4.14212 5.56018,7.96454 -1.37815,2.62562 -2.42867,3.2457 -5.5255,3.2615 -2.22456,0.013 -5.36593,2.7995 -22.18365,19.68893 l -19.59209,19.67568 0,5.32176 0,5.32179 -10.33797,10.38321 -10.33799,10.38323 0,10.19792 0,10.19789 5.169,-5.13324 c 4.20313,-4.17407 5.16899,-5.65705 5.16899,-7.93647 0,-2.89803 2.68732,-6.32879 4.95731,-6.32879 1.52056,0 4.76306,1.7071 5.58287,2.93922 1.23252,1.85245 0.63494,5.73083 -1.16065,7.53283 -1.3668,1.37164 -2.51345,1.71352 -4.60477,1.37295 -2.52224,-0.41079 -3.366,0.11265 -8.94878,5.5512 l -6.16397,6.0047 0,7.09565 0,7.09564 17.23001,-17.27499 17.22997,-17.27504 0,-8.28166 c 0,-7.75628 -0.1367,-8.40092 -2.15375,-10.16101 -5.23424,-4.56729 0.26652,-12.4543 6.44861,-9.24606 4.20968,2.18463 4.42255,7.81183 0.37769,9.98427 -1.94449,1.04437 -2.08807,1.60033 -2.08807,8.08774 l 0,6.96626 6.892,-6.87592 6.89203,-6.87591 0,-4.88572 c 0,-3.28804 0.42251,-5.23757 1.29223,-5.96191 0.98246,-0.81825 1.29225,-3.04202 1.29225,-9.27547 0,-7.71604 -0
.12691,-8.29938 -2.15374,-9.89936 -4.03429,-3.1846 -1.72775,-10.22027 3.35055,-10.22027 2.56078,0 5.69541,2.4614 6.05707,4.75622 0.31586,2.004 -1.73507,6.48309 -2.96849,6.48309 -0.47252,0 -0.83939,2.92425 -0.83939,6.69083 l 0,6.69082 14.64548,-14.67852 14.64552,-14.67842 0,-8.96302 c 0,-8.5308 -0.10393,-9.045 -2.15376,-10.66319 -3.02261,-2.38603 -2.96668,-6.85937 0.11617,-9.29298 2.83985,-2.24177 3.81979,-2.24177 6.65966,0 3.08282,2.43361 3.13879,6.90695 0.11615,9.29298 -1.98084,1.56369 -2.15374,2.26267 -2.15374,8.70839 0,3.85448 0.28032,7.00819 0.62293,7.00819 0.34261,0 3.01364,-2.37787 5.9356,-5.28413 5.05282,-5.02573 5.29056,-5.44948 4.86078,-8.66502 -0.57096,-4.27173 1.42953,-6.80033 5.37995,-6.80033 3.3854,0 5.59971,2.28161 5.59971,5.76986 0,3.21901 -2.33479,5.46945 -5.67459,5.46945 -2.14273,0 -5.48118,2.97006 -22.11578,19.67566 -10.77565,10.82162 -19.59208,19.95868 -19.59208,20.30457 0,0.34588 1.81747,2.40157 4.03886,4.56823 l 4.03886,3.93933 7.59141,-7.58305 c 6.82859,-6.8211
4 7.59136,-7.90571 7.59136,-10.79418 0,-2.33818 0.52051,-3.57695 1.91468,-4.55702 4.09765,-2.88027 9.29396,-0.57866 9.27161,4.1067 -0.007,1.51846 -0.29799,3.18889 -0.64611,3.71211 -1.13867,1.71135 -4.63025,3.09631 -6.57268,2.60707 -1.5342,-0.38644 -3.37845,1.00894 -9.52778,7.20884 l -7.62529,7.68803 3.26731,3.0924 3.26727,3.09236 15.90862,0 15.90862,0 1.69416,-2.16138 c 3.44535,-4.39562 10.18406,-2.00793 10.18406,3.60846 0,5.3252 -6.93174,7.5904 -10.05604,3.28618 l -1.58478,-2.18328 -14.64019,0.23799 -14.64022,0.23801 2.49494,2.37754 c 1.70355,1.62346 3.41141,2.37753 5.38435,2.37753 5.81678,0 8.29228,5.89592 4.19989,10.00283 -1.64859,1.65447 -2.67994,2.01899 -4.52289,1.5986 -3.53343,-0.80599 -5.6533,-3.66373 -5.12793,-6.91279 0.40731,-2.51886 -0.39607,-3.53425 -11.96743,-15.12605 l -12.40408,-12.4259 -3.88928,3.8306 c -3.75984,3.70313 -3.8893,4.00427 -3.8893,9.04659 l 0,5.21596 -17.86842,17.94721 -17.86844,17.94722 9.48677,0 9.48676,0 7.61192,-7.67454 c 5.97815,-6.02731 7.49502,-8.0
4401 7.06748,-9.39599 -1.44359,-4.56445 3.87822,-8.98639 8.20793,-6.82011 2.93199,1.46695 4.06026,5.25294 2.43512,8.17123 -1.05298,1.89086 -2.00151,2.3673 -5.14055,2.58208 -3.44951,0.23606 -4.45333,0.86223 -9.66444,6.02877 -3.19834,3.17093 -5.81509,6.06752 -5.81509,6.43695 0,0.36938 2.69609,0.67161 5.99129,0.67161 5.24212,0 6.26067,-0.27033 8.14509,-2.16141 2.6265,-2.63582 5.57085,-2.76339 8.14882,-0.35311 4.21451,3.94037 -0.29224,11.6912 -5.69966,9.80239 -1.30395,-0.45544 -2.75351,-1.69796 -3.22122,-2.76116 -0.8441,-1.91857 -0.99594,-1.93304 -20.25537,-1.93304 l -19.40489,0 -13.76383,13.83297 -13.76381,13.83299 15.71229,0 15.71225,0 0,-4.43983 c 0,-3.62523 -0.39511,-4.80729 -2.15374,-6.44291 -2.28582,-2.12597 -2.7168,-4.60176 -1.27249,-7.31008 2.22385,-4.17005 7.77172,-4.06895 9.96196,0.18153 1.56491,3.03696 1.00437,5.27035 -1.89774,7.56124 -1.85031,1.46064 -2.11507,2.2823 -1.88745,5.85836 l 0.26471,4.15942 17.62522,0.23459 17.6252,0.23466 7.47797,-7.4698 c 7.20119,-7.19333 7.46191
,-7.59027 7.04288,-10.72514 -0.75247,-5.63005 4.42765,-8.81745 9.04979,-5.56848 1.92954,1.35636 2.63438,5.74615 1.25538,7.81874 -1.0627,1.59715 -4.5665,3.10241 -5.96657,2.56323 -0.88043,-0.33904 -3.44865,1.6317 -7.68619,5.8981 -3.50015,3.52404 -6.36394,6.64112 -6.36394,6.92686 0,0.28574 2.75205,0.51951 6.11559,0.51951 5.6843,0 6.28589,-0.18294 8.5299,-2.59368 2.93064,-3.14841 5.41418,-3.29081 8.22341,-0.47157 2.68325,2.69277 2.68325,5.16685 0,7.85966 -2.72671,2.73638 -5.9928,2.72108 -8.38444,-0.0391 l -1.87267,-2.16141 -20.72593,0 -20.7259,0 13.76381,13.83296 c 11.87549,11.93517 14.131,13.83298 16.44027,13.83298 5.03808,0 7.39971,5.90267 3.80495,9.5102 -1.7138,1.71987 -5.59995,2.27975 -7.40915,1.06745 -1.82093,-1.22021 -3.03932,-4.64639 -2.51087,-7.06085 0.48999,-2.23903 -0.61878,-3.58415 -13.85518,-16.80828 l -14.38783,-14.37447 -22.06799,0 -22.06803,0 -10.54045,10.60334 c -9.69294,9.7508 -10.54045,10.86169 -10.54045,13.81601 0,2.85446 -0.72035,3.92387 -6.46123,9.59251 -3.55368,3.5
0892 -6.46163,6.53172 -6.46205,6.71733 -4.4e-4,0.18561 3.9732,0.27463 8.83036,0.19782 l 8.83118,-0.1397 9.21465,-8.93821 c 8.69376,-8.43303 9.18747,-9.0973 8.73378,-11.75189 -0.69227,-4.05069 1.5239,-7.13271 5.11654,-7.11561 1.529,0.008 3.20658,0.29907 3.72796,0.64844 1.22775,0.82271 2.92881,4.07674 2.92881,5.6027 0,2.27383 -3.41862,4.97494 -6.29647,4.97494 -2.3646,0 -3.97951,1.20546 -10.73516,8.01346 -4.37352,4.40738 -7.9524,8.20063 -7.95306,8.42945 -7e-4,0.2288 5.7175,0.29576 12.70708,0.14875 l 12.70835,-0.2673 6.60273,-6.55514 c 5.97986,-5.93684 6.5614,-6.81062 6.16485,-9.26288 -0.52554,-3.25017 1.60968,-6.21643 5.06519,-7.03648 1.79662,-0.42639 2.82566,-0.0503 4.52287,1.65294 2.67608,2.68561 2.77433,4.77214 0.36917,7.84071 -1.36308,1.73904 -2.45687,2.26589 -4.30109,2.07177 -2.09711,-0.22078 -3.65472,0.89875 -10.24372,7.36258 l -7.77057,7.62292 -6.45257,-0.28406 c -3.54893,-0.15625 -6.45173,-0.0761 -6.4506,0.17781 8.7e-4,0.25402 2.04949,2.4911 4.55198,4.97123 l 4.54995,4.50946 20
.86426,0 20.86427,0 0,-8.30508 c 0,-7.82899 -0.12341,-8.40253 -2.15373,-10.00521 -4.38007,-3.4576 -2.00087,-10.22029 3.59572,-10.22029 5.30671,0 7.52609,6.8813 3.29625,10.22029 -2.03027,1.60268 -2.15371,2.17622 -2.15371,10.00521 l 0,8.30508 19.20074,0 19.20077,0 4.05971,-4.13639 4.05976,-4.13637 0,-8.3605 c 0,-7.76509 -0.15342,-8.51439 -2.15376,-10.52185 -2.59717,-2.60644 -2.77054,-5.94384 -0.43074,-8.29197 2.50372,-2.51265 6.36794,-2.23813 8.60113,0.61101 2.22889,2.84359 1.74819,5.952 -1.31432,8.49937 -1.92457,1.60085 -2.11783,2.3887 -2.11783,8.63312 l 0,6.87159 11.2003,-11.21515 c 10.69568,-10.70992 11.17653,-11.34248 10.67216,-14.04053 -0.81095,-4.3381 1.41295,-7.14045 5.66651,-7.14045 2.51339,0 3.72038,0.49149 4.71839,1.92147 2.85114,4.08507 0.32647,9.31783 -4.49556,9.31783 -1.83023,0 -3.89527,1.52007 -8.80879,6.48421 l -6.41814,6.48423 15.27005,0 c 12.21703,0 15.27007,-0.23249 15.27007,-1.16287 0,-1.83596 3.17533,-4.24065 5.59973,-4.24065 4.90056,0 7.61554,6.76303 4.0243,10.024
61 -2.16697,1.96805 -6.42551,1.8156 -8.33179,-0.29827 -1.45571,-1.61426 -2.65338,-1.72915 -18.02667,-1.72915 l -16.46737,0 -8.15017,8.21333 -8.15022,8.21338 7.27335,0 c 6.67501,0 7.41777,-0.1748 9.02875,-2.12536 2.71672,-3.28937 5.96109,-3.67503 8.60399,-1.02276 2.73152,2.74127 2.80577,5.7311 0.20319,8.18475 -2.63934,2.48838 -5.85256,2.37994 -8.34407,-0.28159 -1.42427,-1.52143 -2.93252,-2.1614 -5.09401,-2.1614 l -3.07067,0 14.08246,14.15201 c 13.51229,13.57908 14.21754,14.14847 17.4198,14.0642 5.65284,-0.14878 8.4452,4.91766 5.07805,9.21354 -3.55155,4.53109 -10.32416,1.84154 -10.32416,-4.09999 0,-2.28932 -2.11145,-4.77235 -15.30035,-17.99277 l -15.30037,-15.33699 -18.06371,0 -18.06373,0 7.23614,7.29981 c 5.89869,5.95059 7.51574,7.14964 8.74895,6.48732 3.1716,-1.70343 7.6681,1.61881 7.6681,5.66552 0,3.42422 -2.27922,5.61966 -5.83413,5.61966 -3.07592,0 -5.36404,-2.56968 -5.36765,-6.02816 -9.8e-4,-1.5887 -2.23032,-4.48625 -8.16427,-10.61466 l -8.16199,-8.42949 -17.01438,0 -17.01438,0 6
.8718,6.9662 c 5.97358,6.05566 7.17898,6.9065 9.22222,6.5096 1.66701,-0.32386 3.01348,0.11594 4.63059,1.51158 4.32521,3.73364 2.04562,10.08493 -3.61971,10.08493 -3.37422,0 -5.51505,-2.52511 -5.51505,-6.50493 0,-2.17901 -1.3932,-4.10467 -7.67943,-10.61466 l -7.67946,-7.95272 -7.20454,0 -7.20454,0 -5.98428,-6.05192 -5.98427,-6.05195 -17.68388,0.0143 -17.68384,0.0143 -4.09213,4.22255 c -2.25066,2.32242 -4.0921,4.4562 -4.0921,4.7417 0,0.28552 3.34767,0.51914 7.43932,0.51914 6.82867,0 7.60318,-0.17748 9.43537,-2.16141 1.96481,-2.12759 5.73942,-2.85668 7.76445,-1.49976 1.78004,1.19282 2.97129,4.48725 2.41986,6.69218 -1.0848,4.33752 -7.89159,5.47256 -10.33898,1.72409 -1.36366,-2.08862 -1.73027,-2.16143 -10.88473,-2.16143 l -9.47354,0 -29.50352,29.61125 c -28.02579,28.12812 -29.5272,29.79624 -29.97693,33.30469 -0.43103,3.36267 -0.26423,3.80193 1.86246,4.90562 1.28479,0.66674 5.83245,3.92409 10.10593,7.23864 21.84908,16.94607 36.45539,41.07943 40.90928,67.59251 1.43719,8.55529 1.42841,32.430
78 -0.0148,40.63442 -3.70617,21.0648 -13.04052,38.52197 -30.22621,56.52935 -9.06735,9.50093 -17.9142,16.27707 -44.46324,34.05605 -11.42005,7.64761 -21.8444,15.19363 -23.16528,16.769 -4.05531,4.83661 -5.81647,9.96703 -6.26862,18.26152 -0.50394,9.24328 1.09005,16.6345 6.00471,27.8438 4.49251,10.24646 4.48344,10.20395 2.45137,11.4601 -1.56594,0.96806 -2.86775,0.33724 -12.0429,-5.83578 -5.65794,-3.8066 -10.48821,-6.92117 -10.73398,-6.92117 -0.24568,0 -0.68099,1.45896 -0.96731,3.24211 -2.64055,16.44472 -7.73387,34.75545 -13.88806,49.92845 -3.65741,9.01703 -15.0297,32.63973 -15.49058,32.17724 -0.11869,-0.11917 2.01848,-9.06125 4.74936,-19.87132 2.73086,-10.81008 4.85344,-19.65467 4.71683,-19.65467 -0.13667,0 -1.289,1.84799 -2.56085,4.10667 -12.63508,22.43864 -33.44794,44.81774 -59.35743,63.82429 -3.89577,2.85788 -3.5079,2.20453 3.88695,-6.54707 16.39722,-19.40579 32.54031,-42.50417 38.67067,-55.33198 l 1.85926,-3.89054 -2.33075,3.026 c -1.28193,1.66427 -6.42977,7.11821 -11.43968,12.11989
-9.7947,9.7786 -23.12234,19.35952 -34.52245,24.8174 -5.87996,2.81506 -22.23956,9.74902 -23.00132,9.74902 -0.17398,0 7.1172,-7.29474 16.20303,-16.21052 27.50911,-26.99429 53.50756,-58.28039 69.96461,-84.19426 l 3.09324,-4.87069 -2.52176,-0.50617 c -1.38697,-0.27837 -5.05294,-0.76639 -8.14663,-1.08446 -3.09368,-0.31809 -5.61711,-0.81639 -5.60756,-1.10737 0.0109,-0.29096 0.36586,-2.94748 0.79195,-5.90335 2.3746,-16.47479 -7.97241,-32.53751 -28.53888,-44.30377 -13.47086,-7.70677 -25.70388,-15.67882 -33.91157,-22.09953 -43.81698,-34.27714 -57.31313,-95.65209 -31.63484,-143.86216 8.75002,-16.42786 25.38192,-34.00435 39.94298,-42.21154 3.30327,-1.86182 4.22597,-2.8547 4.22665,-4.54786 7.3e-4,-1.69439 -6.51828,-8.70631 -29.94078,-32.2049 l -29.94164,-30.03891 -9.34796,0 c -8.94506,0 -9.42098,0.0933 -11.04209,2.16143 -3.44538,4.39564 -10.1841,2.00795 -10.1841,-3.60847 0,-5.32561 6.8569,-7.5529 10.1841,-3.30804 1.59115,2.03004 2.18349,2.16141 9.74501,2.16141 l 8.0509,0 -4.80064,-4.81771 -4.80
068,-4.81775 -17.46506,0.0881 -17.4651,0.0884 -6.11316,6.02643 -6.11317,6.02644 -6.95022,0 -6.95022,0 -7.95186,8.01346 c -7.05726,7.11189 -7.95184,8.36127 -7.95184,11.10531 0,3.77281 -2.137,5.95353 -5.83414,5.95353 -1.79609,0 -3.12656,-0.63527 -4.02427,-1.92149 -3.13443,-4.49092 -0.36976,-9.73091 5.05264,-9.57666 2.48128,0.0705 3.77999,-0.83903 9.63177,-6.74628 l 6.76383,-6.82787 -16.79523,0 -16.79521,0 -8.36223,8.42949 c -6.47771,6.52978 -8.2581,8.82647 -7.9001,10.19102 0.27918,1.06413 -0.26564,2.69006 -1.37598,4.10669 -4.7119,6.01146 -13.43495,-0.59981 -9.06066,-6.86714 1.40432,-2.01209 4.79511,-2.99393 6.68119,-1.93466 0.86466,0.48559 3.33402,-1.44599 8.46274,-6.61986 l 7.24185,-7.30554 -18.06374,0 -18.06373,0 -15.30035,15.33696 c -13.57276,13.60523 -15.30039,15.65524 -15.30039,18.15583 0,5.2465 -5.03325,7.91969 -9.28476,4.93121 -1.9296,-1.35634 -2.6344,-5.74614 -1.25537,-7.81878 1.16992,-1.75834 4.45715,-2.98541 6.57122,-2.4529 1.82623,0.45998 3.88574,-1.27053 15.95541,-13.40644
7.62219,-7.66403 13.85852,-14.11711 13.85852,-14.34026 0,-0.22311 -1.2155,-0.40562 -2.70107,-0.40562 -1.77457,0 -3.38579,0.74142 -4.69711,2.1614 -2.71636,2.94138 -6.55021,2.89123 -8.90777,-0.11654 -0.98206,-1.25291 -1.78552,-2.96056 -1.78552,-3.79477 0,-2.20794 2.65739,-5.00787 5.36267,-5.65024 1.88964,-0.44867 2.86866,-0.0342 5.01989,2.12456 2.53922,2.5482 3.01277,2.68193 9.49817,2.68193 3.75415,0 6.82574,-0.29991 6.82574,-0.66642 0,-0.36656 -3.39983,-4.06257 -7.55517,-8.21334 l -7.55514,-7.54693 -16.86408,0 c -15.76995,0 -16.96521,0.11207 -18.42335,1.72914 -2.17876,2.41603 -6.81831,2.3119 -8.56615,-0.19237 -4.68201,-6.70828 4.28251,-13.30826 9.13312,-6.72412 l 1.91085,2.59369 15.12957,-0.008 15.12962,-0.007 -6.46127,-6.45034 c -5.28219,-5.27328 -6.96092,-6.45519 -9.19958,-6.47689 -4.93405,-0.0481 -7.39038,-4.40414 -4.88417,-8.66186 1.27226,-2.16146 2.08344,-2.604 4.77316,-2.604 4.10994,0 6.32882,2.86527 5.52962,7.14044 -0.50435,2.69802 -0.0234,3.33059 10.67214,14.04048 l 11.20032
,11.2144 0,-7.55248 c 0,-4.15383 -0.35999,-7.5524 -0.79995,-7.5524 -3.55549,0 -4.78686,-7.6483 -1.59286,-9.89346 6.26147,-4.40124 12.71257,3.76668 7.13107,9.02889 -2.00767,1.89281 -2.15373,2.59429 -2.15373,10.34182 l 0,8.31125 4.12168,4.07417 4.12172,4.07416 19.1388,0 19.13876,0 0,-8.17403 c 0,-7.56624 -0.16022,-8.33475 -2.15375,-10.33544 -2.59719,-2.6064 -2.77053,-5.94381 -0.43075,-8.29193 1.7138,-1.7199 5.59993,-2.27979 7.40914,-1.06745 0.52139,0.34933 1.42609,1.54582 2.01051,2.65886 1.3214,2.51653 -0.14678,6.72818 -2.62031,7.51597 -1.45532,0.46359 -1.63035,1.44128 -1.63035,9.10668 l 0,8.58734 20.92081,0 20.92078,0 4.49344,-4.56611 c 2.47138,-2.5114 4.49344,-4.76116 4.49344,-4.9995 0,-0.23836 -2.7566,-0.27706 -6.12575,-0.0861 l -6.12573,0.34738 -7.61582,-7.60745 c -6.73911,-6.73175 -7.98122,-7.6144 -10.79012,-7.6679 -2.41565,-0.0461 -3.59722,-0.62696 -4.94363,-2.43086 -1.67763,-2.24768 -1.69736,-2.5218 -0.38056,-5.29307 2.97064,-6.25154 10.63751,-4.23873 10.4968,2.7558 -0.0656,3.2
5499 0.40068,3.97775 6.46124,10.01775 l 6.53184,6.50969 12.70716,0.24209 c 6.98896,0.13308 12.70718,0.0401 12.70714,-0.20728 -5e-5,-0.24712 -3.59335,-4.04037 -7.98515,-8.42945 -6.60179,-6.59774 -8.44413,-7.98013 -10.63521,-7.98013 -3.28349,0 -5.50168,-2.32632 -5.50168,-5.76987 0,-3.24302 2.34037,-5.46947 5.74941,-5.46947 3.61168,0 5.74052,2.58181 5.29401,6.42031 -0.33403,2.87135 0.14805,3.53833 8.7316,12.07931 l 9.08521,9.04011 8.83119,0.27929 c 4.85715,0.15356 8.83084,0.0742 8.8304,-0.17622 -4.9e-4,-0.25051 -2.71459,-3.15794 -6.03137,-6.46101 -5.43215,-5.40974 -6.0305,-6.34335 -6.0305,-9.40989 0,-3.1937 -0.65027,-4.06445 -10.50923,-14.07234 l -10.50922,-10.66795 -22.45265,0 -22.45261,0 -14.40477,14.48139 c -14.05723,14.13197 -14.39315,14.55443 -13.92222,17.50739 0.37238,2.3347 0.0661,3.47035 -1.3416,4.97123 -4.6917,5.00293 -12.29367,-0.26178 -9.3428,-6.47031 0.87798,-1.84722 1.83334,-2.4503 4.19808,-2.65001 2.67736,-0.22614 4.78304,-1.98885 16.78305,-14.04915 l 13.72158,-13.79054 -
20.56711,0 -20.56708,0 -1.99604,2.1614 c -5.04151,5.45919 -12.54826,-0.63952 -8.99304,-7.30619 1.78693,-3.35075 8.82304,-2.67815 9.97148,0.95318 0.43179,1.36536 1.43723,1.59788 6.90855,1.59788 3.52174,0 6.40321,-0.30221 6.40321,-0.6716 0,-0.36937 -2.7174,-3.37776 -6.0387,-6.68528 -4.60581,-4.5868 -6.659,-6.04501 -8.65323,-6.14564 -5.39928,-0.27254 -7.94257,-6.14604 -4.26106,-9.84062 4.59076,-4.60706 11.28177,0.15426 9.18688,6.53746 -0.47466,1.44621 0.85774,3.19916 7.06413,9.29404 l 7.64909,7.51164 17.7717,0 17.77164,0 0,-4.65564 c 0,-4.01201 -0.29774,-4.85144 -2.15375,-6.07184 -3.05335,-2.00777 -2.9942,-6.63932 0.11616,-9.09466 1.24846,-0.98556 2.74686,-1.79187 3.32982,-1.79187 0.58295,0 2.08134,0.80631 3.32982,1.79187 2.99716,2.36593 3.04714,6.21342 0.11615,8.93942 -1.75861,1.63565 -2.15374,2.81769 -2.15374,6.44293 l 0,4.43979 15.507,0 c 8.52882,0 15.50697,-0.29646 15.50697,-0.65886 0,-0.36236 -5.91658,-6.58719 -13.14798,-13.833 l -13.14798,-13.1741 -19.34125,0 -19.34127,0 -1.75535
,2.12537 c -2.71674,3.28934 -5.96113,3.67499 -8.60403,1.02268 -2.73153,-2.74124 -2.80575,-5.73103 -0.20317,-8.1847 2.63934,-2.48835 5.85258,-2.37994 8.34404,0.28157 1.81186,1.93547 2.70351,2.16141 8.5296,2.16141 l 6.50627,0 -6.67721,-6.74048 c -6.06297,-6.12039 -6.94114,-6.7095 -9.54648,-6.40424 -5.17459,0.60621 -8.15397,-5.7621 -4.66789,-9.97747 0.49374,-0.59703 2.39201,-1.08553 4.21838,-1.08553 2.59203,0 3.63053,0.47459 4.73287,2.16302 0.78495,1.20222 1.22984,3.12055 1.0016,4.31873 -0.33886,1.77918 0.88526,3.51578 7.00743,9.94087 l 7.41808,7.7851 9.89936,0 9.89937,0 -17.86843,-17.94719 -17.8684,-17.94721 0,-4.78005 c 0,-4.59357 -0.16847,-4.94657 -4.31825,-9.04845 l -4.31816,-4.26839 -12.33567,12.35741 c -11.91653,11.93753 -12.33326,12.47633 -12.26565,15.85764 0.0555,2.77732 -0.37915,3.84113 -2.10467,5.15112 -2.94534,2.23601 -7.11252,0.87147 -8.63998,-2.82923 -1.56421,-3.7897 1.03779,-7.13722 5.82781,-7.49761 2.4916,-0.1874 4.13521,-0.9715 5.8902,-2.80982 l 2.4342,-2.54982 -14.8980
5,0 c -14.86513,0 -14.9018,0.005 -16.59219,2.16141 -3.31161,4.22504 -10.18409,1.69002 -10.18409,-3.75654 0,-3.64734 3.04404,-5.83473 7.54382,-5.42093 0.59541,0.0547 1.92531,1.07218 2.95524,2.26096 1.86984,2.15817 1.89624,2.16138 17.62507,2.16138 l 15.75242,0 3.04522,-2.68322 c 1.67488,-1.47577 3.04522,-3.09514 3.04522,-3.59858 0,-0.50343 -3.18748,-4.16054 -7.08328,-8.12694 -6.60057,-6.72012 -7.27245,-7.17597 -9.85947,-6.68893 -2.23897,0.4215 -3.20223,0.0951 -4.9777,-1.68664 -2.65559,-2.66503 -2.81651,-5.62224 -0.44496,-8.17686 2.3294,-2.50923 5.5753,-2.39383 8.253,0.29338 1.66095,1.66682 2.05581,2.74401 1.66377,4.53895 -0.45481,2.08228 0.38051,3.24676 7.25041,10.10734 l 7.76449,7.75391 3.88586,-3.82725 c 2.13724,-2.10499 3.88585,-4.23574 3.88585,-4.735 0,-0.49926 -8.82267,-9.75551 -19.60597,-20.56946 -16.02983,-16.07552 -20.03713,-19.66209 -21.96846,-19.66209 -2.94842,0 -5.80827,-2.76698 -5.80827,-5.61965 0,-2.88441 2.87019,-5.61966 5.89698,-5.61966 3.9004,0 6.7685,4.34388 5.34644,8
.09738 -0.67265,1.77543 -0.0986,2.69756 4.57019,7.34128 2.93673,2.92097 5.61982,5.31081 5.96244,5.31081 0.34262,0 0.61626,-3.20967 0.60804,-7.13264 -0.0148,-6.46172 -0.21601,-7.3241 -2.15375,-9.16863 -2.90593,-2.76619 -2.82643,-6.37362 0.19301,-8.75712 3.71631,-2.93366 8.14636,-1.20586 9.32967,3.63874 0.46609,1.9083 0.0679,2.86528 -2.09707,5.03774 -2.63573,2.64507 -2.67246,2.8018 -2.67246,11.40402 l 0,8.72202 14.43013,14.5539 c 7.93654,8.00461 14.72087,14.5542 15.07622,14.5546 0.35537,3.9e-4 0.64613,-2.65247 0.64613,-5.89529 0,-5.21156 -0.25001,-6.11419 -2.15373,-7.7753 -5.78473,-5.04765 0.85359,-13.60306 7.13102,-9.19052 2.74189,1.92727 2.6178,6.69769 -0.23904,9.19052 -2.00903,1.75302 -2.15375,2.42016 -2.15375,9.92938 0,7.11378 0.20043,8.17572 1.72302,9.12998 1.41377,0.88603 1.723,2.01618 1.723,6.29701 l 0,5.21719 6.46123,6.44092 6.46127,6.44096 0,-7.01849 c 0,-5.31506 -0.30146,-7.13459 -1.24206,-7.49685 -0.68316,-0.26305 -1.89228,-1.4741 -2.68691,-2.69127 -3.75129,-5.74555 4.84495
,-11.63225 9.31331,-6.37777 2.12137,2.49462 1.87381,5.61529 -0.6461,8.14414 -1.98568,1.99272 -2.15375,2.78307 -2.15375,10.12741 l 0,7.96601 17.02251,17.06699 c 9.36235,9.38682 17.24765,17.06695 17.52285,17.06695 0.27522,0 0.46551,-3.10209 0.42287,-6.89356 l -0.0772,-6.89355 -5.9898,-6.05705 c -5.62976,-5.69297 -6.17349,-6.02811 -9.04573,-5.57605 -6.13811,0.96622 -9.47528,-6.16511 -4.66281,-9.96406 2.79892,-2.20944 4.71205,-2.26371 7.24488,-0.20545 1.32989,1.0807 1.98713,2.59599 2.06176,4.75361 0.0901,2.6039 0.94209,4.01316 4.79045,7.92377 2.57452,2.61609 4.92054,4.75659 5.21343,4.75659 0.29284,0 0.60832,-4.37684 0.70096,-9.72632 l 0.16824,-9.72633 -10.79412,-10.79404 -10.79417,-10.79408 0,-5.23853 0,-5.23856 -19.39116,-19.41443 c -16.26132,-16.28082 -19.84193,-19.46023 -22.18364,-19.69818 -3.75168,-0.38121 -5.80763,-2.42317 -5.80763,-5.76819 0,-3.39743 2.27352,-5.61966 5.74939,-5.61966 3.56716,0 5.66217,2.52569 5.32763,6.42285 -0.25078,2.92075 0.30851,3.67982 9.60594,13.0368 5.42968
,5.46452 10.07234,9.93558 10.31695,9.93573 0.24464,1.4e-4 0.30786,-3.5742 0.14054,-7.94297 -0.27439,-7.16321 -0.50701,-8.10324 -2.36913,-9.57318 -4.12657,-3.25751 -1.52432,-10.15009 3.83212,-10.15009 4.99235,0 7.25116,6.0203 3.68722,9.82741 -1.64674,1.75912 -1.8307,2.95122 -1.8307,11.864 l 0,9.90835 6.46125,6.44096 6.46122,6.44091 0,-6.7957 c 0,-6.13823 -0.20834,-6.98949 -2.15373,-8.79884 -2.54809,-2.36988 -2.76418,-5.46038 -0.57184,-8.17741 3.15142,-3.9057 10.47911,-1.34618 10.47911,3.66032 0,0.93492 -1.16303,2.86701 -2.5845,4.29355 l -2.58452,2.59368 0,13.56331 0,13.56332 10.76877,10.7811 10.76872,10.78108 0,21.21788 0,21.21795 6.03051,6.00554 6.03051,6.00555 0,-10.82449 0,-10.82453 -3.44603,-3.37687 -3.446,-3.37687 0,-7.10598 0,-7.10593 3.446,-2.96011 3.44603,-2.96015 0,-9.84149 0,-9.84144 -7.03793,-7.09979 c -6.1787,-6.23306 -7.32862,-7.04145 -9.41939,-6.62181 -1.80566,0.36241 -2.91603,-0.0584 -4.59233,-1.74074 -2.77607,-2.78594 -2.80572,-5.23719 -0.0962,-7.95626 2.68917,-2.6987
4 5.65718,-2.70202 8.17814,-0.0106 1.56707,1.674 1.86626,2.71132 1.43993,4.99194 -0.50147,2.68268 -0.12704,3.28832 5.49484,8.88693 l 6.03301,6.00809 0,-15.55923 0,-15.55921 -6.9127,-6.8962 c -6.38702,-6.37173 -7.18166,-6.89624 -10.4488,-6.89624 -2.82354,0 -3.88364,-0.46393 -5.2605,-2.30227 -2.21179,-2.95295 -1.46479,-6.33205 1.7431,-7.88509 4.54235,-2.19912 8.81787,0.4479 8.81787,5.45922 0,1.58175 1.63753,3.92846 5.59695,8.02096 3.0783,3.18182 5.79203,5.7851 6.0305,5.7851 0.23845,0 0.43358,-6.93513 0.43358,-15.41139 0,-15.321 -0.0135,-15.42335 -2.15375,-17.44196 -1.18459,-1.1168 -2.15374,-2.65216 -2.15374,-3.41188 0,-0.75979 -3.29525,-4.67116 -7.32276,-8.69198 l -7.32274,-7.31061 0,-6.47824 0,-6.4782 -4.92667,-4.99631 c -2.70966,-2.74798 -5.22955,-4.99632 -5.59975,-4.99632 -0.37019,0 -0.67309,3.68232 -0.67309,8.18294 0,7.36183 0.17708,8.278 1.76422,9.13048 2.7457,1.47466 3.63837,4.79775 2.09394,7.79494 -1.73757,3.37207 -5.03394,4.16739 -8.02528,1.93634 -3.27384,-2.44181 -3.52361,-6.
49946 -0.56915,-9.24724 1.82209,-1.69471 2.2106,-2.7502 1.99229,-5.41241 l -0.27126,-3.30714 -8.29763,0.0728 c -7.13873,0.0627 -8.62886,0.33438 -10.66954,1.94527 -3.17489,2.5062 -5.93338,2.36823 -8.10757,-0.40559 -2.34479,-2.99152 -2.25426,-5.11127 0.3291,-7.70375 2.80924,-2.81927 5.29278,-2.67682 8.22338,0.47155 2.35822,2.53348 2.60632,2.59371 10.68365,2.59371 l 8.26936,-2e-5 0,-5.01778 c 0,-4.59959 -0.25131,-5.26219 -3.01524,-7.95065 -2.77951,-2.70358 -3.01526,-3.33437 -3.01526,-8.06769 0,-4.36652 -0.32221,-5.43445 -2.15377,-7.13785 -3.69241,-3.43419 -2.49676,-8.40393 2.44688,-10.17045 2.00926,-0.71794 6.33412,1.7879 6.91177,4.00476 0.59823,2.29566 -0.75458,5.53136 -2.86578,6.85448 -1.41431,0.88643 -1.75461,2.00791 -1.75461,5.78283 l 0,4.68314 9.90724,10.06646 9.90727,10.06644 0,6.04423 c 0,6.02141 0.0148,6.05853 3.87674,9.86242 l 3.87672,3.81827 0,-4.4635 c 0,-3.60836 -0.4126,-4.87762 -2.15373,-6.62493 -2.72094,-2.73064 -2.73318,-5.19533 -0.0393,-7.89893 2.6832,-2.6928 5.14853,-2
.6928 7.83179,0 2.87307,2.88326 2.70909,5.86774 -0.46993,8.55221 -2.37658,2.00692 -2.58226,2.62152 -2.55707,7.64174 0.0168,3.27483 0.50485,6.09159 1.22064,7.03937 1.11357,1.4744 1.33585,1.4862 3.3274,0.17673 2.70064,-1.77583 6.24181,-0.98442 8.06076,1.80148 1.71762,2.63074 0.91925,5.9282 -1.88115,7.76965 -2.07579,1.36491 -2.1401,1.70888 -2.1401,11.44931 l 0,10.04209 8.18423,-8.1792 c 8.15229,-8.14723 8.18425,-8.19602 8.18425,-12.48662 0,-3.63035 -0.36106,-4.59631 -2.29731,-6.14573 -4.32534,-3.46114 -2.11451,-10.76278 3.25879,-10.76278 2.90289,0 5.93049,2.97223 5.93049,5.82198 0,1.12073 -0.96915,3.01031 -2.15373,4.1991 -1.83184,1.83837 -2.15373,2.95857 -2.15373,7.49522 l 0,5.33384 -9.4765,9.25239 -9.47649,9.25242 0,26.00888 c 0,14.30489 0.32758,26.00886 0.72798,26.00886 0.40038,0 1.40049,-0.74572 2.22242,-1.65716 1.1716,-1.29924 1.2935,-2.03404 0.56424,-3.40142 -2.37783,-4.45887 0.0275,-8.77439 4.89029,-8.77439 2.67306,0 6.24057,3.21254 6.24057,5.61965 0,1.97418 -3.55678,5.61965 -5.4
8288,5.61965 -1.11391,0 -3.42502,1.54729 -5.53724,3.70711 -3.42545,3.50265 -3.62538,3.98608 -3.62538,8.76507 0,4.67424 -0.25389,5.33437 -3.34651,8.70141 -3.19122,3.47436 -3.33908,3.89037 -3.18526,8.96343 0.14753,4.86895 0.43137,5.59756 3.34652,8.59354 l 3.18525,3.27358 0,12.96811 0,12.96811 3.87672,3.81826 3.87677,3.81828 0,-19.63268 c 0,-18.47988 -0.10102,-19.73414 -1.72298,-21.36175 -3.49173,-3.50416 -1.03196,-10.37474 3.71428,-10.37474 2.87236,0 5.76222,3.05208 5.76222,6.08562 0,2.47182 -2.53833,6.01824 -4.30752,6.01824 -0.55066,0 -0.86149,7.57321 -0.86149,20.99059 l 0,20.99061 5.59973,5.56968 c 5.05013,5.02301 5.59978,5.91219 5.59978,9.05875 0,3.41575 0.50211,3.99271 23.91225,27.47483 13.15175,13.19214 24.35989,23.98579 24.90692,23.98579 1.40987,0 5.45821,-5.03527 5.44037,-6.76671 -0.0109,-0.79565 -0.97734,-2.36284 -2.15375,-3.48267 -1.39582,-1.3287 -2.13886,-2.98252 -2.13886,-4.76067 0,-2.41778 -2.81411,-5.54751 -24.98346,-27.78577 l -24.98347,-25.0611 0,-7.09371 c 0,-6.19704 -
0.24966,-7.36399 -1.97498,-9.23226 -1.68679,-1.82652 -2.00735,-3.2059 -2.197,-9.45283 l -0.22209,-7.31422 4.34614,-4.20857 c 5.97694,-5.7878 5.91965,-7.9047 -0.38286,-14.151 l -4.73824,-4.69606 0,-33.82961 c 0,-32.73156 -0.056,-33.88036 -1.72298,-35.39442 -3.68762,-3.34908 -1.1729,-9.94247 3.79206,-9.94247 5.85042,0 7.90004,6.94997 3.09995,10.51147 l -2.58452,1.91759 0,32.5153 0,32.51528 3.446,3.37692 3.446,3.37691 0,-31.54292 0,-31.54291 5.16898,-5.13326 c 4.81594,-4.78258 5.16901,-5.4028 5.16901,-9.08035 0,-3.63881 -0.37012,-4.31393 -4.73824,-8.64311 l -4.73826,-4.69605 0,-13.00497 0,-13.00505 -19.7489,-19.80504 -19.74885,-19.80502 0.24362,-5.69249 0.24364,-5.6925 -11.66073,-11.83559 c -10.48709,-10.64436 -11.89982,-11.78761 -14.03611,-11.35883 -1.79861,0.36098 -2.9123,-0.062 -4.58629,-1.74194 -2.67724,-2.68677 -2.79144,-4.78165 -0.42529,-7.80038 3.70277,-4.72408 10.79466,-1.66255 10.0943,4.35766 -0.31798,2.73361 0.17912,3.55172 5.44999,8.96871 l 5.79987,5.96074 6.30755,-6.52814 c
4.93151,-5.10398 6.24655,-6.99513 6.02795,-8.6689 -0.15374,-1.17745 0.34146,-3.09194 1.1005,-4.25444 1.89215,-2.89807 7.50762,-3.01194 9.47566,-0.19215 3.11137,4.45787 0.31645,9.31782 -5.3586,9.31782 -2.8826,0 -3.93288,0.68681 -9.20731,6.02085 l -5.95357,6.02084 1.6692,1.97635 c 0.91802,1.08698 3.12292,3.41972 4.89977,5.18382 3.14845,3.12589 3.2306,3.36861 3.2306,9.54267 l 0,6.33523 4.12174,4.07415 c 2.26694,2.24082 4.3531,4.07418 4.63589,4.07418 0.28279,0 0.42304,-1.96063 0.31166,-4.35699 -0.14439,-3.10851 -0.71301,-4.8343 -1.98408,-6.02268 -2.78518,-2.60401 -3.19583,-5.0711 -1.27576,-7.66389 2.02207,-2.73056 5.58634,-3.3422 8.461,-1.452 2.81564,1.85146 2.81687,5.88435 9.6e-4,8.70851 -1.77404,1.78031 -2.12573,3.04351 -2.29361,8.23793 l -0.19826,6.13518 11.54275,11.6077 c 6.34865,6.38419 11.83904,11.60766 12.201,11.60766 0.36192,0 0.65806,-9.05877 0.65806,-20.13062 l 0,-20.13055 -4.54995,-4.50948 c -3.49236,-3.46122 -5.16341,-4.50936 -7.18922,-4.50936 -5.03934,0 -7.34206,-5.92324 -
3.71699,-9.56118 2.46045,-2.46921 6.57705,-1.95408 8.84401,1.10671 1.51548,2.04617 1.6018,2.66235 0.66248,4.73121 -1.03422,2.27787 -0.87624,2.57508 3.7271,7.01455 l 4.80708,4.63589 0,27.78198 0,27.78199 5.22094,-5.16028 c 4.52628,-4.47379 5.15771,-5.48606 4.74568,-7.60864 -0.4762,-2.45324 0.66192,-5.64187 2.43872,-6.8325 1.80923,-1.21232 5.69536,-0.65245 7.40916,1.06748 3.5007,3.51313 0.90751,9.51014 -4.11232,9.51014 -1.83552,0 -3.93219,1.55748 -9.04577,6.71951 l -6.65641,6.71948 0,5.33544 0,5.33545 4.73825,4.49715 4.73828,4.49718 0,5.30515 0,5.30516 -5.16903,5.13324 -5.16898,5.13325 0,22.72182 c 0,12.49699 0.32359,22.7218 0.71908,22.7218 0.39551,0 1.75238,-1.16377 3.01525,-2.5862 2.29554,-2.58548 2.29618,-2.59019 2.29618,-17.24023 0,-9.4043 -0.32768,-14.85721 -0.91465,-15.22125 -1.74682,-1.08344 -2.58388,-4.73577 -1.54106,-6.72403 1.34468,-2.5637 2.40401,-3.15675 5.66648,-3.17225 5.7502,-0.0275 7.4314,7.91856 2.19221,10.36149 l -2.78121,1.29685 -0.0184,14.73005 -0.0185,14.73006 -4.
30748,4.25783 -4.3075,4.25777 0,11.30794 0,11.30792 -3.87674,3.8183 -3.87674,3.81824 0,7.03476 c 0,5.93677 0.26892,7.27901 1.72297,8.59963 1.46868,1.33377 1.72304,2.66279 1.72304,9.00146 l 0,7.4366 24.35993,24.43522 c 23.66912,23.74231 24.43844,24.41953 27.12721,23.8799 6.34601,-1.27374 9.83468,5.923 4.94108,10.19306 -1.23529,1.07789 -2.15376,2.81113 -2.15376,4.06447 0,1.35556 -1.21739,3.43634 -3.20648,5.48061 l -3.20649,3.29542 18.71348,18.79456 c 10.29234,10.33702 18.71338,19.47216 18.71338,20.30034 0,1.85173 1.05475,1.89698 4.35032,0.18674 2.20938,-1.1466 2.54169,-1.79917 2.54169,-4.9915 0,-3.44124 -0.43397,-4.10548 -6.89201,-10.5484 -6.04505,-6.03101 -6.89194,-7.25472 -6.89194,-9.9583 0,-2.51376 -0.71928,-3.79083 -3.89887,-6.92244 -2.81058,-2.76821 -4.66325,-3.8786 -6.63732,-3.97827 -3.15898,-0.15946 -5.83234,-2.92713 -5.83234,-6.03819 0,-4.90138 6.8241,-7.32116 10.43293,-3.69948 1.44311,1.44824 1.71287,2.48389 1.30828,5.02309 -0.46728,2.93231 -0.18812,3.51544 3.35106,7.00125 3.
14288,3.09549 3.86073,4.37565 3.86073,6.88491 0,2.7021 0.84534,3.92574 6.85238,9.91876 6.39187,6.37696 6.8695,7.10647 7.10736,10.8548 l 0.25502,4.0184 2.79986,-1.15123 c 2.73927,-1.12634 2.79986,-1.26857 2.79986,-6.57393 l 0,-5.42267 -6.46124,-6.44092 c -5.90994,-5.89139 -6.46125,-6.73826 -6.46125,-9.92489 0,-3.43456 -0.48829,-3.97385 -34.45998,-38.05831 -32.37097,-32.4784 -34.45997,-34.75955 -34.45997,-37.62936 0,-2.61236 -0.81134,-3.86195 -5.59975,-8.62471 -4.95804,-4.93139 -5.59974,-5.95047 -5.59974,-8.89257 0,-2.63075 -0.53836,-3.77749 -2.58453,-5.50535 -6.31153,-5.32966 0.53394,-14.28581 6.94014,-9.07997 2.61871,2.12801 2.66857,6.74655 0.0956,8.83752 -1.30379,1.05948 -1.86657,2.47819 -1.86657,4.70524 0,2.76569 0.7423,3.94069 5.59974,8.86356 4.80329,4.86801 5.59975,6.11693 5.59975,8.78094 0,2.93667 1.87647,4.98853 34.45996,37.68013 32.35096,32.45839 34.46,34.76107 34.46,37.62469 0,2.66351 0.87395,3.9222 6.892,9.92622 l 6.892,6.87595 0,5.34285 0,5.34288 3.01524,-0.9287 3.01525,-0
.92868 0,-10.45355 0,-10.45355 -17.92179,-17.97016 c -12.97045,-13.00551 -18.30705,-17.82181 -19.3164,-17.43309 -1.45994,0.56224 -4.95401,-0.90467 -6.03899,-2.53532 -0.34809,-0.5232 -0.63888,-2.01281 -0.64615,-3.31026 -0.0109,-1.91126 -3.52874,-5.89228 -18.53539,-20.97424 l -18.52224,-18.61522 0,-11.94818 0,-11.94815 -8.6093,-8.57656 -8.60927,-8.57659 -0.005,-8.23833 -0.005,-8.23832 5.59974,-5.56968 5.59973,-5.56972 0,-11.13982 c 0,-10.85279 -0.0555,-11.18367 -2.15372,-12.83999 -4.21008,-3.32343 -1.68409,-10.22032 3.74323,-10.22032 2.48558,0 4.9364,1.96067 5.40336,4.32282 0.48274,2.44156 -0.96557,5.93642 -2.68735,6.48486 -1.61185,0.51358 -1.72039,1.31214 -1.72039,12.6649 l 0,12.11675 -5.59969,5.70319 -5.59978,5.70315 0,6.4549 0,6.45487 8.61497,8.78573 8.61502,8.7857 0,12.06231 0,12.06228 17.90035,17.9487 c 17.28656,17.33322 17.99516,17.92965 20.66592,17.3936 1.92019,-0.38541 3.45828,-0.0999 5.03166,0.93497 2.01272,1.32349 2.21873,1.91144 1.84272,5.25907 -0.33338,2.96837 -0.0743,4.08
597 1.22007,5.26145 1.57954,1.43457 1.85982,1.27718 7.21927,-4.05352 3.06678,-3.0503 5.47621,-5.84567 5.35435,-6.21198 -0.12165,-0.36626 -8.65356,-9.17287 -18.95922,-19.57026 l -18.73764,-18.90431 0,-7.2619 0,-7.26196 -11.64316,-11.65959 c -9.66533,-9.67898 -12.01486,-11.61127 -13.83155,-11.37529 -3.55035,0.46121 -6.40077,-1.98435 -6.40077,-5.49155 0,-5.69814 5.74592,-8.47902 9.57652,-4.63481 1.43476,1.43989 1.7284,2.53744 1.37875,5.15356 -0.79818,5.9719 1.09657,5.92224 7.49923,-0.19654 l 5.6675,-5.4162 0,-13.89925 0,-13.89926 -4.58183,-4.54097 -4.58182,-4.54102 0.34591,-6.05239 0.34591,-6.0524 -6.96355,-7.15963 -6.96364,-7.15959 0,-12.3527 c 0,-12.14649 -0.036,-12.38108 -2.1537,-14.05285 -4.2101,-3.32345 -1.68406,-10.2203 3.74322,-10.2203 3.26232,0 5.30249,2.44503 5.30249,6.35469 0,2.56642 -0.42239,3.38572 -2.15374,4.17736 -2.14968,0.98295 -2.15377,1.0079 -2.15377,13.22311 l 0,12.23831 3.14603,2.79514 3.14604,2.79516 2.86986,-2.79149 c 2.4902,-2.42216 2.80608,-3.19992 2.38742,-5.87
821 -0.96581,-6.1787 6.13204,-9.53914 9.92566,-4.6992 2.2016,2.80884 2.25567,4.72878 0.20469,7.27059 -1.07229,1.32895 -2.58734,1.99423 -4.70986,2.06819 -2.18344,0.0759 -3.91881,0.86828 -5.74201,2.6212 -1.43702,1.38169 -2.61284,2.82546 -2.61284,3.20834 0,0.38288 1.16306,1.73608 2.58454,3.0071 2.47097,2.20948 2.58449,2.61976 2.58449,9.3414 l 0,7.03038 3.45745,3.38815 3.45747,3.38806 7.74202,-7.73353 7.74201,-7.73359 0,-12.13733 0,-12.13731 -8.89856,-8.89986 c -7.89695,-7.89806 -9.15275,-8.83588 -11.15614,-8.33126 -2.83123,0.71315 -6.65176,-2.75712 -6.65176,-6.0419 0,-2.94437 2.86281,-5.65895 5.96797,-5.65895 3.30172,0 6.36522,3.7026 5.67577,6.8598 -0.41677,1.90838 0.60104,3.3021 7.28918,9.98116 l 7.77354,7.763 0,-4.27229 c 0,-3.59235 -0.36558,-4.56487 -2.2973,-6.11066 -4.86338,-3.89171 -1.59597,-11.57447 4.41889,-10.39012 5.05537,0.99543 6.50669,6.7407 2.61672,10.35869 -2.11055,1.96292 -2.15379,2.26002 -2.15379,14.80696 l 0,12.80385 2.12302,-2.00148 c 1.54272,-1.45451 2.09177,-2.83242
2.00902,-5.04173 -0.0872,-2.32126 0.441,-3.51911 2.23258,-5.06567 1.29057,-1.11404 2.92398,-2.02553 3.62982,-2.02553 2.05324,0 5.51258,4.04447 5.51258,6.44493 0,3.08177 -2.98653,5.83765 -5.98423,5.52207 -1.79585,-0.18904 -3.33726,0.57621 -5.97174,2.96478 -3.21619,2.9159 -3.55105,3.6086 -3.55105,7.34553 0,4.06141 -0.12784,4.25399 -8.18424,12.30518 l -8.18424,8.17919 0,14.73959 0,14.73961 -5.59975,5.56968 c -3.07986,3.06334 -5.59973,5.89339 -5.59973,6.289 0,0.81998 15.50283,17.0539 16.28672,17.05474 0.28191,3.2e-4 0.40335,-10.77441 0.26996,-23.94375 l -0.24261,-23.94438 6.24136,-6.3056 c 5.77742,-5.83692 6.22381,-6.54996 6.00493,-9.59223 -0.39224,-5.45157 4.70578,-8.54772 8.77947,-5.33194 4.70631,3.71512 2.12141,10.32713 -4.03725,10.32713 -1.89462,0 -3.82269,1.29168 -7.99254,5.35442 l -5.49558,5.35443 0,32.94899 0,32.94897 3.01524,2.93286 3.01527,2.93291 0,-31.58037 0,-31.58038 12.49174,-12.63635 c 12.35308,-12.49607 12.49175,-12.67937 12.49175,-16.51411 0,-3.00602 -0.35634,-3.99132
-1.58509,-4.3827 -2.15473,-0.68632 -3.89327,-4.18801 -3.18736,-6.42 1.42755,-4.51377 6.7064,-5.94838 9.94145,-2.70183 2.33978,2.3481 2.16642,5.68555 -0.43075,8.29194 -1.68996,1.69598 -2.15377,3.04163 -2.15377,6.24905 0,3.9602 -0.21485,4.30207 -6.89198,10.96358 -3.79061,3.78176 -6.892,7.17685 -6.892,7.54464 0,0.36778 2.44699,0.6687 5.43775,0.6687 5.04297,0 5.57643,-0.18831 7.34853,-2.59369 2.50255,-3.39691 6.16915,-3.53012 8.68867,-0.31565 2.34481,2.99149 2.25428,5.11121 -0.32907,7.70373 -2.69402,2.70363 -5.15,2.69137 -7.87095,-0.0391 -1.9692,-1.97621 -2.79977,-2.16141 -9.69324,-2.16141 l -7.53952,0 -3.35259,3.44557 -3.35265,3.44564 -0.15008,32.80623 -0.15008,32.80631 3.3279,3.26114 3.32788,3.26109 0,-15.42228 0,-15.42235 -2.20767,-2.05326 c -2.90617,-2.70288 -2.62099,-6.77422 0.64518,-9.21038 2.02015,-1.50673 2.6426,-1.58966 4.89769,-0.65222 4.51281,1.8759 5.10083,8.03394 1.01192,10.59661 -1.67826,1.05177 -1.76262,1.91426 -1.76262,18.01831 l 0,16.9137 3.01526,2.93288 3.01522,2.93289
0,-15.97284 0,-15.97283 5.59976,-5.56972 5.59975,-5.56968 0,-5.66629 0,-5.66623 -6.413,-6.39281 c -5.86252,-5.84408 -6.72291,-6.39882 -10.02419,-6.46258 -2.82118,-0.0543 -3.97193,-0.55296 -5.2599,-2.27852 -1.38006,-1.84888 -1.49257,-2.62068 -0.69064,-4.73746 1.17707,-3.10692 2.24353,-3.83052 5.67033,-3.84757 3.41467,-0.0178 5.51789,2.3231 5.51789,6.13927 0,2.42583 0.77705,3.7585 4.11581,7.05876 l 4.11585,4.06836 7.51443,-7.57617 c 6.17924,-6.23013 7.5144,-8.04865 7.5144,-10.23496 0,-3.29431 2.31848,-5.52041 5.74945,-5.52041 3.23154,0 5.45005,2.34867 5.45005,5.76986 0,3.72754 -2.56092,5.76387 -6.63817,5.27834 -3.12625,-0.37231 -3.58483,-0.0728 -10.49561,6.8505 l -7.22653,7.23981 1.84216,1.96785 c 1.53967,1.64471 1.84216,3.01965 1.84216,8.37312 l 0,6.4052 -5.29441,5.3609 -5.29441,5.36084 0.0698,17.50794 0.0697,17.50792 4.75596,4.77291 c 4.62067,4.63705 4.80416,4.72903 6.45088,3.23349 1.26723,-1.1509 1.5794,-2.25377 1.23713,-4.37056 -0.90876,-5.61975 5.54009,-8.55853 9.67495,-4.40897
2.70943,2.71906 2.67982,5.17033 -0.0962,7.95625 -1.78439,1.79072 -2.73386,2.11374 -4.92214,1.67453 -2.52626,-0.50706 -3.25716,9.3e-4 -10.71162,7.45126 -4.40022,4.39746 -7.99316,8.39214 -7.98433,8.87702 0.0109,0.4849 6.79314,7.68958 15.07625,16.01036 l 15.06024,15.12871 0,10.10871 c 0,5.55976 0.34314,10.32151 0.76255,10.58164 0.41938,0.26014 1.97008,0.0504 3.44598,-0.46584 l 2.68344,-0.93877 0,-12.24955 0,-12.2495 -9.75905,-9.76602 c -8.44464,-8.45062 -10.04517,-9.69395 -11.88289,-9.23108 -2.9434,0.74135 -6.7875,-2.36515 -6.7875,-5.48511 0,-3.67648 2.23499,-6.18155 5.51507,-6.18155 3.79243,0 5.90562,2.41953 5.56617,6.37316 -0.19469,2.26792 0.26796,3.61777 1.80126,5.25577 1.13484,1.21223 2.52338,2.20404 3.0857,2.20404 0.56229,0 3.40224,-2.56387 6.31105,-5.69754 l 5.28871,-5.6975 0,-10.25389 c 0,-8.85928 0.20506,-10.37176 1.50763,-11.12009 1.29962,-0.74673 -0.15485,-2.54192 -10.54181,-13.01141 -9.18494,-9.25789 -12.56984,-12.1452 -14.23844,-12.1452 -5.41801,0 -8.205,-6.3917 -4.3239,-9.
91653 4.03847,-3.66774 9.77603,-0.66424 9.37513,4.90771 -0.20531,2.85399 0.25265,3.77207 3.57801,7.17239 l 3.81349,3.89946 4.98731,-4.95278 c 4.52475,-4.49349 4.939,-5.21123 4.46654,-7.7387 -0.71298,-3.81406 2.87167,-7.64915 6.37301,-6.81819 3.4779,0.82536 5.58803,3.76785 4.96972,6.93011 -0.63169,3.23108 -2.58099,4.78197 -6.0392,4.80484 -2.01781,0.0143 -3.79583,1.14455 -7.58884,4.82814 -2.7245,2.64589 -4.90453,5.06961 -4.84449,5.38609 0.0602,0.31646 3.11086,3.41684 6.77958,6.88975 l 6.67041,6.31437 2.7369,-2.74664 c 2.70173,-2.71128 2.71427,-2.76255 0.97393,-3.98584 -1.15722,-0.81343 -1.75848,-2.18787 -1.74975,-4.00005 0.0167,-3.26029 0.60889,-4.30918 3.21739,-5.68772 1.89541,-1.00172 1.93839,-1.35393 1.93839,-15.88601 l 0,-14.86161 -5.74235,-5.80721 c -4.94513,-5.00107 -6.0689,-5.74171 -8.09449,-5.33516 -4.65288,0.93386 -8.06379,-6.13656 -4.62514,-9.58742 4.66861,-4.68525 11.16592,-0.69931 9.529,5.84575 -0.377,1.50736 0.14199,2.64029 2.05067,4.47527 l 2.55545,2.45694 5.50845,-5.123
68 c 5.13321,-4.77459 5.51534,-5.38928 5.60944,-9.02292 l 0.10041,-3.89965 -15.94787,-15.98572 c -13.08006,-13.11133 -16.32455,-15.93674 -18.04446,-15.71379 -4.94654,0.64122 -8.34756,-4.06086 -6.09674,-8.42898 0.69164,-1.34219 1.85333,-2.66982 2.58159,-2.95023 1.1164,-0.42993 1.32413,-2.72591 1.32413,-14.6355 l 0,-14.12559 -23.69122,-23.75873 -23.69124,-23.75874 0,-5.99617 0,-5.99612 -6.03051,6.00555 c -4.6485,4.62929 -6.03049,6.5688 -6.03049,8.46312 0,3.43546 -1.30427,5.50925 -3.87172,6.15591 -2.19714,0.55342 -5.4799,-0.64208 -6.66849,-2.42843 -1.23199,-1.85169 -0.63524,-5.73052 1.15864,-7.53077 1.43874,-1.44386 2.47767,-1.71853 4.98095,-1.31678 3.0427,0.48829 3.41598,0.2471 9.81231,-6.33958 3.65711,-3.76596 6.64931,-7.36235 6.64931,-7.99202 0,-0.62965 -4.26318,-5.40985 -9.47373,-10.62273 l -9.47373,-9.47792 -6.89475,6.87872 c -5.26673,5.25439 -6.89477,7.43696 -6.89477,9.24283 0,2.94409 -3.02218,5.88976 -6.04276,5.88976 -2.62566,0 -6.01827,-3.2127 -6.01827,-5.69914 0,-4.05276 5.090
68,-7.43306 8.46486,-5.62079 1.25822,0.67576 2.74146,-0.35542 8.16656,-5.67771 3.646,-3.5769 6.6291,-6.73841 6.6291,-7.02559 0,-0.28718 -1.01563,-1.586 -2.25694,-2.88626 -1.90066,-1.99093 -2.59897,-2.23357 -4.42381,-1.53732 -2.61456,0.99761 -5.80657,-0.77301 -7.29212,-4.04497 -0.80725,-1.778 -0.67668,-2.53923 0.77052,-4.49322 3.55082,-4.79415 10.61785,-2.63203 10.61785,3.24846 0,2.01473 1.28276,3.89599 5.89509,8.64559 3.24229,3.33884 6.14983,6.0706 6.46124,6.0706 0.31138,0 0.56614,-6.11783 0.56614,-13.59515 l 0,-13.59519 -5.67823,-5.74238 c -5.52829,-5.59078 -5.77541,-5.73143 -9.35846,-5.32614 -3.13856,0.35503 -3.88785,0.11878 -5.09055,-1.6043 -4.61389,-6.61064 3.50255,-13.33747 8.73162,-7.23666 1.12968,1.31799 1.87659,3.07712 1.65975,3.90916 -0.26565,1.01951 1.54076,3.43976 5.53962,7.4221 l 5.93379,5.90929 5.21378,-5.23232 c 4.85739,-4.87467 5.14558,-5.38242 4.21634,-7.42911 -0.8032,-1.76908 -0.72512,-2.72524 0.40083,-4.91036 1.19706,-2.32309 1.8717,-2.71353 4.68861,-2.71353 3.8480
8,0 6.14191,2.04348 6.14191,5.47159 0,2.82637 -2.75684,5.76769 -5.40596,5.76769 -1.28875,0 -4.0914,2.15341 -8.18424,6.28842 l -6.22433,6.28841 0,14.86768 0,14.86769 5.59976,5.70325 5.59975,5.70328 0,8.82002 0,8.82007 8.61502,8.61315 8.61499,8.61316 0,-19.89183 0,-19.89194 -5.92834,-5.9038 c -5.43254,-5.41006 -6.15102,-5.85471 -8.59099,-5.31691 -5.33107,1.17506 -9.17456,-6.0215 -5.29515,-9.91469 3.88553,-3.89931 11.08059,-0.17388 9.91858,5.13564 -0.40808,1.86454 0.35088,3.05476 4.70464,7.37846 l 5.19126,5.15531 0,-20.93854 c 0,-13.38269 -0.31089,-20.93857 -0.86151,-20.93857 -1.64675,0 -4.30749,-3.47124 -4.30749,-5.61964 0,-2.93471 2.88127,-5.61968 6.03049,-5.61968 5.52966,0 7.85998,6.02372 3.87673,10.02106 -1.67245,1.67846 -2.15372,3.04928 -2.15372,6.13486 l 0,3.9734 4.21982,-3.79659 4.21988,-3.79657 0.0879,-18.89592 0.0879,-18.89593 -2.15374,-1.70016 c -2.6614,-2.10088 -2.86177,-6.0515 -0.43077,-8.491166 4.65434,-4.670886 12.11161,0.966691 9.07468,6.860316 -0.69162,1.34219 -1.8533,2
.6698 -2.58157,2.95023 -1.13614,0.43755 -1.32409,3.23492 -1.32409,19.70435 l 0,19.19441 -5.58284,5.80337 -5.58283,5.80336 0.0784,20.20271 c 0.0433,11.1115 0.27569,20.53258 0.51676,20.93573 0.24105,0.40312 2.53391,-1.34396 5.09527,-3.88251 4.44017,-4.40059 4.63316,-4.77691 4.14476,-8.08177 -0.63018,-4.26476 1.72725,-7.35681 5.60891,-7.35681 2.75533,0 6.05944,3.38499 6.05944,6.20781 0,2.60134 -4.0363,6.15178 -6.3353,5.57273 -1.5719,-0.39593 -3.20849,0.7904 -8.60291,6.23592 l -6.66171,6.72484 0.15543,13.7823 0.15541,13.78238 8.23817,8.44657 8.2381,8.44653 6.28329,-6.2635 6.28326,-6.26354 0,-8.01883 0,-8.01882 -5.44437,0 c -4.73364,0 -5.68885,0.28214 -7.31705,2.16143 -2.55072,2.94397 -6.35448,2.89309 -8.71354,-0.11653 -2.20379,-2.81166 -2.25559,-4.72892 -0.19679,-7.28052 2.29252,-2.8412 6.74125,-2.67734 9.13588,0.33653 1.63119,2.05293 2.41814,2.30549 7.18389,2.30549 l 5.35198,0 0,-5.14809 c 0,-4.33852 -0.33868,-5.48796 -2.15374,-7.30946 -4.10022,-4.11481 -1.50683,-10.41389 4.05773,-9.85
59 5.57786,0.55932 7.57521,8.19907 2.72356,10.41748 -1.98905,0.90956 -2.04307,1.21609 -2.04307,11.60246 0,5.86755 0.13853,10.66826 0.30749,10.66826 0.16919,0 6.17808,-5.90772 13.35327,-13.12826 11.88671,-11.9619 13.04575,-13.39941 13.04575,-16.18085 0,-2.46899 -0.76249,-3.82948 -3.9885,-7.11641 -3.51258,-3.57882 -4.32348,-4.00923 -6.79503,-3.60673 -6.02251,0.98079 -8.73422,-6.61686 -3.70147,-10.37072 2.02012,-1.50676 2.64261,-1.58966 4.89775,-0.65223 2.72242,1.13167 4.59851,4.78715 3.87728,7.55486 -0.2658,1.0201 0.66757,2.62326 2.64981,4.55134 l 3.06016,2.97657 0,-8.63975 0,-8.63973 -3.3591,-3.29172 -3.35905,-3.29171 -10.99908,-0.18304 c -10.53782,-0.17533 -11.07187,-0.0989 -12.73404,1.81941 -3.59159,4.14535 -10.03921,1.72575 -10.03921,-3.76742 0,-5.32564 6.8569,-7.55289 10.18407,-3.30805 1.62071,2.06776 2.09841,2.1614 11.02177,2.1614 l 9.32763,0 -5.35949,-5.42677 -5.3595,-5.42679 0,-7.7794 0,-7.77945 -6.28078,-6.26103 c -5.76413,-5.74601 -6.50353,-6.21636 -8.98864,-5.71754 -2.18428
,0.4384 -3.13532,0.11441 -4.91871,-1.67526 -2.77604,-2.7859 -2.80572,-5.23715 -0.0962,-7.95623 2.70943,-2.71906 5.15204,-2.68933 7.92809,0.0967 1.67867,1.68462 2.09588,2.79557 1.73325,4.61511 -0.40883,2.05141 0.23197,3.10099 4.45161,7.29147 2.71103,2.69228 5.2087,4.89507 5.55032,4.89507 0.34163,0 0.62114,-2.22245 0.62114,-4.93879 0,-4.31433 -0.29047,-5.17115 -2.29735,-6.77708 -3.00316,-2.40313 -3.16643,-6.86771 -0.33532,-9.16835 2.54257,-2.06616 4.45305,-2.01415 7.25475,0.19747 2.99902,2.36743 3.04973,6.18472 0.11615,8.74452 -2.13605,1.86386 -2.15375,1.99753 -2.15375,16.26849 l 0,14.3892 8.59094,8.79805 c 4.72502,4.83895 8.79563,8.79808 9.04575,8.79808 0.25014,0 0.45481,-2.70965 0.45481,-6.02151 0,-5.06631 -0.26406,-6.16332 -1.66466,-6.91557 -5.03705,-2.70535 -2.71736,-11.27066 3.05234,-11.27066 5.11916,0 7.35778,7.03974 3.35056,10.53634 -1.9162,1.67207 -2.15376,2.55094 -2.15376,7.96833 l 0,6.08898 6.0305,-6.00557 6.03049,-6.00557 0,-13.01322 0,-13.01327 -6.2661,-6.24643 c -4.77441,
-4.75943 -6.83881,-6.24645 -8.67165,-6.24645 -7.47775,0 -9.72503,-8.50769 -2.9871,-11.30855 2.19204,-0.91121 2.83485,-0.81895 4.84188,0.69491 1.851,1.3962 2.31425,2.45388 2.31425,5.28387 0,2.2145 0.56301,4.10341 1.50496,5.04889 1.43244,1.43781 1.6921,1.32637 5.38437,-2.31017 l 3.87939,-3.82089 0,-8.8979 c 0,-8.618284 -0.0609,-8.90136 -1.93837,-9.008505 -4.82974,-0.27563 -7.92239,0.191632 -9.25023,1.397583 -3.55563,3.229242 -9.70277,0.40865 -9.70277,-4.452095 0,-3.091889 3.70075,-6.385421 6.42618,-5.719104 2.48954,0.608651 4.98874,2.744326 4.98874,4.263145 0,0.833134 1.34344,1.162846 4.73821,1.162846 l 4.73824,0 0,-3.258326 c 0,-2.976505 -0.74473,-4.002891 -8.60998,-11.866484 -7.86437,-7.862734 -8.86217,-8.586344 -11.52304,-8.356286 -2.30771,0.199532 -3.29338,-0.233361 -4.74329,-2.083101 -2.38886,-3.047751 -2.31161,-5.155503 0.28444,-7.760774 2.70695,-2.71654 5.15168,-2.689565 7.91874,0.0872 z m 50.7938,435.654981 c 27.74711,13.58237 42.66509,35.41992 44.02514,64.44598 0.55036,11.745
56 -0.40019,17.27075 -4.40572,25.60807 -6.08845,12.67284 -16.21104,23.0464 -30.15167,30.89915 -12.69673,7.1521 -19.15407,14.45785 -24.86532,28.13234 -1.68179,4.02669 -2.22554,4.81618 -1.8467,2.68119 0.2953,-1.66429 1.70773,-5.87756 3.1387,-9.36285 3.48755,-8.49428 9.68367,-15.07163 21.7843,-23.12464 22.55101,-15.00768 35.83974,-37.02194 33.09972,-54.83326 -2.60732,-16.94882 -12.36141,-35.06704 -26.58074,-49.3739 -5.73867,-5.7739 -8.84582,-8.13681 -13.85838,-10.53912 -6.26691,-3.00339 -21.10913,-7.81019 -22.5882,-7.31542 -0.40183,0.13433 1.22746,1.71144 3.62061,3.50446 4.66777,3.4972 8.45992,8.20799 11.62722,14.44396 2.65258,5.22247 7.2647,18.44836 8.26289,23.69498 1.1098,5.83337 -0.46022,21.46905 -2.94065,29.28487 -2.33967,7.3723 -4.57867,11.05535 -11.67876,19.2111 -2.89778,3.32851 -6.04086,7.60813 -6.98471,9.51016 -0.94383,1.90204 -1.71636,2.95944 -1.71671,2.34977 -9.7e-4,-2.29643 2.82158,-7.72915 7.82818,-15.06498 9.73158,-14.259 13.44316,-25.50205 12.53564,-37.97276 -0.95673,-13.
14657 -11.36506,-36.18813 -18.72157,-41.4451 l -2.49459,-1.78259 0.0287,132.98173 0.0287,132.98173 11.34063,4.50783 c 6.23734,2.47927 11.44474,4.40331 11.57202,4.27557 0.12731,-0.12779 -1.3247,-3.85452 -3.22664,-8.28173 -4.50766,-10.49273 -5.96897,-16.34131 -5.96897,-23.88909 0,-7.58917 1.57512,-13.06412 5.43845,-18.9034 5.8938,-8.90824 12.67876,-14.64616 30.69392,-25.95736 11.84532,-7.43729 34.63279,-25.72753 42.74029,-34.3052 11.14737,-11.79391 17.7837,-23.72924 21.77032,-39.1536 2.09385,-8.10127 2.2554,-10.05917 2.25373,-27.31485 -9.4e-4,-17.83609 -0.098,-18.90285 -2.37956,-26.36915 -10.03081,-32.82395 -35.99378,-59.99883 -68.60027,-71.80249 -7.66965,-2.77641 -8.38726,-2.65222 -2.73669,0.47366 6.66587,3.68753 11.40225,7.83177 18.118,15.85294 16.80357,20.06995 29.38104,45.01702 33.32616,66.10151 1.66492,8.89799 1.29842,13.91994 -1.6616,22.76907 -8.6562,25.87796 -23.16053,42.01281 -57.02563,63.43626 -6.24299,3.94934 -12.52115,8.6812 -14.63473,11.03015 -4.06318,4.51572 -7.67486,12.4
212 -11.68125,25.56863 -3.11669,10.22778 -3.45777,11.03289 -2.80842,6.62928 1.167,-7.91367 5.43475,-22.90146 7.93853,-27.87908 3.63628,-7.22902 6.05102,-9.43573 22.92178,-20.94651 17.16798,-11.71368 25.91435,-18.6527 32.1727,-25.5247 12.45644,-13.67778 21.70055,-33.53588 21.67907,-46.57056 -0.0135,-7.9745 -3.18093,-20.70075 -7.76198,-31.18344 -9.83808,-22.51205 -31.19364,-51.13899 -42.76352,-57.32401 -4.69241,-2.50846 -23.2483,-8.76737 -26.13778,-8.81624 -0.71076,-0.013 3.0133,2.08569 8.27566,4.66164 z m -32.10558,3.2765 c -11.33703,12.34295 -17.52208,36.44832 -17.52208,68.28974 0,9.78952 0.37696,13.05967 2.52037,21.86428 3.67058,15.07804 4.80587,18.01497 10.89988,28.19763 6.80531,11.37119 8.15454,16.22978 7.99985,28.80725 l -0.11749,9.54468 -0.68983,-7.16563 c -1.09093,-11.33155 -2.33211,-15.88172 -6.08148,-22.29436 -8.65312,-14.79978 -14.07098,-28.6007 -17.11753,-43.60355 -4.33319,-21.33898 -0.12434,-56.56347 8.90677,-74.54108 4.24465,-8.44961 3.94893,-9.24697 -0.62631,-1.68855 -6
.47564,10.69795 -9.66009,21.42459 -13.82396,46.56534 -2.87619,17.36621 -4.21384,32.32491 -3.54239,39.61482 2.26787,24.62293 8.3798,44.23875 21.36309,68.56372 7.50654,14.064 9.57214,20.47467 10.95523,34.00019 1.40095,13.70055 0.88056,16.19176 -0.78908,3.77718 -2.09626,-15.58695 -2.6251,-17.14675 -11.35687,-33.49535 -16.46411,-30.82612 -20.23436,-41.66829 -22.74517,-65.40881 -2.05658,-19.44572 -1.46798,-32.34026 2.31241,-50.65874 2.43441,-11.79645 8.13362,-31.80221 10.24029,-35.9463 2.62195,-5.15775 0.82617,-4.04645 -2.93965,1.81922 -4.77169,7.43227 -7.04165,13.63839 -9.48361,25.92816 -10.47933,52.74009 -7.39387,107.29891 7.7898,137.74273 2.17125,4.35335 6.75618,12.58387 10.18879,18.28997 7.46713,12.41279 9.65642,16.64812 11.34321,21.94387 1.4166,4.4475 2.58099,25.02295 1.25345,22.1488 0.28253,6.9328 -6.97379,18.7653 8.05011,22.04631 1.19987,0 1.335,-13.38995 1.335,-132.27795 0,-72.75287 -0.32657,-132.27796 -0.72571,-132.27796 -0.91987,0 -4.88786,12.97878 -6.74022,22.04633 -1.03719,5.
07721 -1.4351,11.97446 -1.49636,25.93685 -0.0822,18.74068 -0.0351,19.2601 3.18741,35.32587 1.79897,8.96805 3.15584,16.64205 3.01523,17.05336 -0.14052,0.41124 -2.32038,-6.50864 -4.84406,-15.37756 -6.2397,-21.92838 -7.55165,-32.19071 -6.2225,-48.67327 1.78093,-22.08523 3.54341,-29.25535 9.03207,-36.74386 1.04555,-1.42655 2.21107,-3.11883 2.59004,-3.76067 1.35445,-2.29386 -2.54071,0.51181 -6.11881,4.40734 z m -2.2068,-132.01081 c 2.83603,1.99346 2.69527,7.61117 -0.23904,9.54061 l -2.15373,1.41622 0,23.90569 0,23.90566 10.33798,10.34772 10.33803,10.34771 0,17.52091 c 0,14.99775 -0.18611,17.5209 -1.29228,17.5209 -1.10512,0 -1.29226,-2.47676 -1.29226,-17.10324 l 0,-17.10319 -10.33799,-10.28268 -10.33799,-10.28264 0,-24.57955 0,-24.57957 -2.15375,-1.70017 c -2.74006,-2.16299 -2.86277,-6.03171 -0.26832,-8.45746 2.2533,-2.10675 4.7919,-2.24975 7.39935,-0.41692 z"
+ id="path3100"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csscsssscscccssssssssscsssscssssscccccccsssssscssscccsssscscsssscccccssssssscccsssssssssscccccccccscscscccccccscsscccccccccsscssssscscccccsssscsscssssssscccccccccccccccssscccccccscsssssscccssssssssscssssccccccccccsssccccscscccssssscsscssssssscsscccsssscscsssssscccscsssscccccssssscccccssscccssssssscsscssscccccsssssssscccsscccccsscccccccccccccsssssscscccccccccccssscssssssscccscscccsssssscscsssssssccccsssscscssscccsssssscccccccccssssssssssscsccccccccsssccccssccccsssssscsssssssscssscccssssscccccccsscssssssscccssssssscssssssscccssscscccccccssssscsssssscccssssssssssscccccccccccssssscsssssssscccssssscccscsssscccccccccssssssscccccssssscccccccccssssscccccccccsssscsssscsssssssssssssscccssscscssscssscsscsscssssscccssscccccccsssssscccccssscccscsssscccsssssscccccsssssscccccccssssscscssssscccccccssscssscsssssscsscssscsscscssscccssssscccccsssssssssscccccsssssscccssssssssssssscccssssssssccsssscscscssssccccccssssssssscccsssssscccccccccccccccccsssssscccccsssscsssscscccccccs
sscssscccccssssscccsssssssscccssssssssssssssscsssccsscccccssscccsssssscsscccccssssssssssssscccccsssssscccsssssssssssccccsssssscccssscscsscsssssscssssssscccscccscssscccccsssssscsssssscsssssscccsssssscssssscccsssscscccsssssscccccccsssscssscssssscssssscsssscsssssscsssssscsscssssssscssssscccccccccsssscscccsssssscccsssssssssssscssscsssscccscsssccssssscccccssscssscccssccscsssscsssssssscssssscccsssssscccsssssssssscccssssssscssscccssssscsscsssscccccsscscsssscscscsscssssssssscssssssssscssscscssssssssscccscsssscssscsssscssscccccsssssscssssscccssssscccccccccccccccccccssssssscccsssssssscsscccsssssssscsssssscsssssssscccsccssssscssssscssssssscccsssssssssssscccccsssssscsssscssscssscsscscsscccccccssscccccccsssssscscsssscssscsscssscscssscsssssscccscsssscccccccccsssssssscccccccccssscssssscssssssssssssssscsscssssssssssssssscccccccscscscccccccccsssccscccccccccsscscscscccsssssscccccccccsssssscccssssssssscccccccsssssscssssscscssssssscccsscscssssscccccccssssssssssssssscccccccccssssscccccccccssssssscssssss
csscccccsssssscsscscscccsssssssscssssssscsssscssscsscsscssssscsscssssscccccssssssssscssssssssssssssscsssssscssssssscccccccccccssssscsssssscccccssssscccsssssssscccccccccsssssscsssssssssssssscccccssssscccccsssssssssssssscssssssscccccssssssscssssssscsssssccsssssscssscccsssssssssssssssssssssscsscccsssssssssssssssssssssssssssssssscssssccccccssssssssscsssscssscccccsss" /><g
+ id="g3064"
+ style="fill:#000000;fill-opacity:1"><g
+ id="g3066"
+ clip-path="url(#clipPath3068)"
+ style="fill:#000000;fill-opacity:1" /></g><g
+ id="g3084"
+ style="fill:#000000;fill-opacity:1"><g
+ id="g3086"
+ clip-path="url(#clipPath3088)"
+ style="fill:#000000;fill-opacity:1" /></g></g></svg>
\ No newline at end of file
diff --git a/bridgedb/https/templates/assets/tor.svg b/bridgedb/https/templates/assets/tor.svg
new file mode 100644
index 0000000..afe0bc0
--- /dev/null
+++ b/bridgedb/https/templates/assets/tor.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="380" height="290" viewBox="0 0 380.00001 289.99999" id="å¾å±_1" xml:space="preserve"><metadata id="metadata2611"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs id="defs2609"><linearGradient x1="297.03323" y1="247.01682" x2="297.03323" y2="125.66936" id="linearGradient5018" xlink:href="#linearGradient4697" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.68230177,0,0,0.68230177,78.404988,63.177814)" /><linearGradient id="linearGradient4697"><stop id="stop4699" style="stop-color:#482957;stop-opacity:1" offset="0" /><stop id="stop4701" style="stop-color:
#c19ed3;stop-opacity:1" offset="1" /></linearGradient><linearGradient x1="297.03323" y1="247.01682" x2="297.03323" y2="125.66936" id="linearGradient3077" xlink:href="#linearGradient4697" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.68230177,0,0,0.68230177,78.404988,63.177814)" /></defs>
+
+<g transform="translate(-58.124001,-45.284749)" id="layer1"><path d="M 50.75,238.46875 C 50.74767,240.51542 50.75067,242.54542 50.75,244.59375 L 58.5625,244.59375 L 58.5625,263.71875 L 66.1875,263.71875 L 66.1875,244.59375 L 73.96875,244.59375 L 73.96875,238.46875 L 50.75,238.46875 z M 115.78125,238.46875 L 115.78125,263.71875 L 123.4375,263.71875 L 123.4375,254.3125 C 125.77166,254.26006 128.12573,254.46829 130.4375,254.0625 C 133.43015,253.61607 135.96405,251.18201 136.46875,248.1875 C 137.01255,245.29331 136.52883,241.81443 134.0625,239.90625 C 131.78087,238.23859 128.83652,238.49113 126.15625,238.46875 L 115.78125,238.46875 z M 181.75,238.46875 L 181.75,243.25 L 188.5625,243.25 L 188.5625,238.46875 L 181.75,238.46875 z M 249.46875,238.46875 L 242.65625,242 L 242.65625,245.4375 L 240.1875,245.4375 L 240.1875,250.46875 L 242.65625,250.46875 L 242.65625,252.65625 C 242.70624,255.21642 242.47421,257.81014 242.9375,260.34375 C 243.05668,261.78997 244.18591,262.93333 245.40625,263.593
75 C 248.04288,264.55411 250.90487,264.05409 253.5625,263.4375 L 253.09375,259.25 C 253.23483,258.27274 252.26229,258.9884 251.6875,259 C 250.62775,259.38851 249.37429,258.76458 249.5,257.53125 C 249.44651,255.1859 249.48339,252.81534 249.46875,250.46875 L 253.3125,250.46875 L 253.3125,245.4375 L 249.46875,245.4375 L 249.46875,238.46875 z M 123.4375,243.53125 C 124.93468,243.60074 126.49451,243.29466 127.90625,243.9375 C 129.54076,244.71485 129.75122,247.1681 128.4375,248.34375 C 127.3731,249.32992 125.83906,249.09707 124.5,249.125 C 124.18831,249.03964 123.53064,249.30392 123.4375,249 L 123.4375,243.53125 z M 83.09375,244.875 C 80.20859,244.8275 77.22027,245.81868 75.3125,248.0625 C 73.5522,249.94861 72.88908,252.62887 73.03125,255.15625 C 73.08077,257.68011 74.09823,260.23861 76.125,261.8125 C 78.81274,264.10451 82.64182,264.48951 86,263.84375 C 88.82917,263.28233 91.42955,261.43611 92.59375,258.75 C 94.17919,255.14195 93.6751,250.50727 90.84375,247.65625 C 88.83195,245.61668 85.8
7085,244.93229 83.09375,244.875 z M 108.375,244.875 C 107.419,244.875 106.58925,245.1415 105.90625,245.6875 C 105.22325,246.0965 104.683,247.05225 104,248.28125 L 104,245.4375 L 97.59375,245.4375 L 97.59375,263.71875 L 104.5625,263.71875 L 104.5625,257.5625 C 104.5625,254.5585 104.817,252.6555 105.5,251.5625 C 106.046,250.7435 106.7315,250.34375 107.6875,250.34375 C 108.0985,250.34375 108.77375,250.602 109.59375,250.875 L 111.65625,245.96875 C 110.42725,245.28575 109.331,244.875 108.375,244.875 z M 151.40625,244.875 C 150.8262,244.87145 150.26044,244.97535 149.71875,245.25 C 148.43645,245.82374 147.76794,247.1133 147.125,248.28125 L 147.125,245.4375 L 140.6875,245.4375 C 140.68517,251.5355 140.68817,257.61908 140.6875,263.71875 L 147.65625,263.71875 L 147.65625,261.75 C 147.6874,259.30832 147.57522,256.87168 147.8125,254.4375 C 147.98367,253.01117 148.27947,251.28613 149.6875,250.5625 C 150.68046,250.02843 151.75878,250.55088 152.71875,250.875 L 154.5,246.5625 C 154.7673,246.12898 1
54.79825,245.77218 154.1875,245.65625 C 153.3401,245.20326 152.373,244.88092 151.40625,244.875 z M 166.65625,244.875 C 163.81138,244.83015 160.84817,245.7831 158.9375,247.96875 C 157.14601,249.82305 156.26534,252.44241 156.40625,255 C 156.4455,257.60089 157.62013,260.2008 159.6875,261.8125 C 162.43682,264.15178 166.33269,264.50973 169.75,263.78125 C 172.53457,263.1788 175.07375,261.31901 176.1875,258.65625 C 177.7293,255.0256 177.16931,250.3711 174.28125,247.5625 C 172.27329,245.58452 169.38522,244.95613 166.65625,244.875 z M 202.5625,244.875 C 199.65259,244.82633 196.58865,245.83995 194.6875,248.125 C 192.2363,250.95423 191.8563,255.23236 193.28125,258.625 C 194.29372,261.11012 196.45477,263.16353 199.125,263.71875 C 201.64663,264.2679 204.28679,264.24885 206.8125,263.75 C 208.51969,263.42988 210.07293,262.47938 211.1875,261.15625 C 211.6559,260.51927 212.73656,259.43233 212.625,258.90625 L 206.125,258.28125 C 205.51686,258.9536 204.91301,259.72992 203.9375,259.8125 C 202.39982,260
.20123 200.57653,259.5439 199.875,258.0625 C 199.79053,257.55239 198.90352,256.05401 199.875,256.21875 L 213.21875,256.21875 C 213.28951,253.4079 212.92796,250.3949 211.125,248.125 C 210.03317,246.80706 208.52604,245.83022 206.84375,245.46875 C 205.44659,245.10894 204.00458,244.9063 202.5625,244.875 z M 226.53125,244.875 C 224.83773,244.94818 223.16268,245.2917 221.59375,245.9375 C 219.74896,246.79555 218.29579,248.44202 217.46875,250.28125 C 216.51712,252.42489 216.57711,254.87891 216.90625,257.15625 C 217.21271,259.53093 218.85626,261.58953 220.90625,262.75 C 222.614,263.77638 224.64841,263.95739 226.59375,264.09375 C 228.87355,264.16831 231.35076,264.0907 233.3125,262.78125 C 235.36706,261.62577 236.70538,259.55319 237.3125,257.3125 L 230.78125,256.5 C 230.52218,257.60301 229.84173,258.55904 228.8125,259.0625 C 227.04278,259.7832 224.53052,259.176 223.875,257.21875 C 223.37182,255.6932 223.44816,254.02326 223.78125,252.46875 C 224.12531,250.7656 225.86092,249.61089 227.5625,249.8
125 C 228.93603,249.74484 230.32064,250.72254 230.5,252.125 L 236.90625,251.28125 C 236.30346,248.57745 234.27097,246.20997 231.5625,245.46875 C 229.935,245.02311 228.22477,244.80182 226.53125,244.875 z M 279,244.875 C 276.11458,244.82722 273.09566,245.81887 271.1875,248.0625 C 269.35887,250.01593 268.69715,252.78835 268.90625,255.40625 C 269.06071,258.57942 270.96032,261.61925 273.84375,263 C 276.42708,264.26198 279.47881,264.37106 282.25,263.75 C 285.00789,263.09784 287.47228,261.17924 288.5625,258.53125 C 290.1067,254.84728 289.46037,250.12239 286.4375,247.375 C 284.43673,245.53292 281.64544,244.93556 279,244.875 z M 304.15625,244.875 C 303.5762,244.87145 303.01044,244.97535 302.46875,245.25 C 301.18645,245.82374 300.51794,247.1133 299.875,248.28125 L 299.875,245.4375 L 293.46875,245.4375 C 293.46642,251.5355 293.43817,257.61908 293.4375,263.71875 L 300.40625,263.71875 L 300.40625,261.75 C 300.43736,259.30833 300.32566,256.87171 300.5625,254.4375 C 300.73318,253.01097 301.0296,25
1.28622 302.4375,250.5625 C 303.4305,250.02827 304.50868,250.55121 305.46875,250.875 L 307.28125,246.5625 C 307.54855,246.12898 307.5795,245.77218 306.96875,245.65625 C 306.12135,245.20326 305.123,244.88092 304.15625,244.875 z M 316.8125,244.875 C 313.944,244.75095 311.24416,246.74626 310.28125,249.40625 C 309.30036,251.99637 309.4085,254.8776 310.0625,257.53125 C 310.82024,260.37628 313.3772,262.72715 316.375,262.875 C 318.02221,262.96074 319.84243,262.61957 321.125,261.53125 C 321.65763,260.95665 322.5173,259.87579 322.25,261.3125 C 322.22656,262.71696 322.44184,264.21836 321.84375,265.53125 C 321.03374,266.81839 319.19006,266.98564 317.90625,266.375 C 316.81858,266.33518 317.11739,264.47551 315.90625,264.71875 L 310.125,264.125 C 309.50347,266.76816 311.20799,269.74611 313.875,270.40625 C 316.60154,271.26237 319.49764,271.16066 322.3125,270.90625 C 324.68811,270.64995 327.16096,269.52501 328.25,267.28125 C 329.27127,265.45631 329.29538,263.34169 329.25,261.3125 L 329.25,245.4375
L 322.8125,245.4375 L 322.8125,248.03125 C 321.50453,246.04856 319.20547,244.77746 316.8125,244.875 z M 181.75,245.4375 C 181.75233,250.47539 181.74933,255.49169 181.75,260.53125 C 181.69711,261.87363 181.89297,263.27272 181.4375,264.5625 C 181.0078,265.66627 179.66675,265.61346 178.71875,265.375 L 177.65625,270.5625 C 179.62508,270.79403 181.57001,271.23211 183.5625,271.0625 C 184.97081,271.00683 186.30112,270.33286 187.28125,269.34375 C 188.42564,267.93617 188.49326,266.01317 188.5625,264.28125 C 188.59678,257.99763 188.55329,251.72134 188.5625,245.4375 L 181.75,245.4375 z M 202.84375,249.125 C 203.95329,249.09172 205.18486,249.55909 205.625,250.65625 C 205.8373,251.2573 206.52558,252.61661 206,252.9375 L 199.4375,252.9375 C 199.56074,251.41532 200.27758,249.69691 201.875,249.25 C 202.18319,249.16417 202.52447,249.12493 202.84375,249.125 z M 83.21875,249.8125 C 84.81783,249.74971 86.12553,251.10433 86.34375,252.625 C 86.59093,254.16964 86.6235,255.82291 86.09375,257.3125 C 85.5851
7,258.42141 84.51087,259.40806 83.21875,259.34375 C 82.16274,259.497 81.16864,258.69003 80.5625,257.84375 C 79.6954,256.4655 79.76487,254.74716 79.90625,253.1875 C 80.034,251.824 80.79576,250.42045 82.15625,249.96875 C 82.49984,249.84916 82.85603,249.81209 83.21875,249.8125 z M 166.28125,249.8125 L 166.46875,249.8125 L 166.65625,249.8125 C 168.24903,249.69466 169.62717,251.0036 169.875,252.53125 C 170.15533,254.0429 170.15817,255.64356 169.71875,257.125 C 169.25152,258.37261 168.06301,259.46133 166.65625,259.34375 C 165.63697,259.47968 164.64055,258.69308 164.09375,257.84375 C 163.2126,256.42434 163.29992,254.63084 163.46875,253.03125 C 163.62666,251.51818 164.6538,249.94298 166.28125,249.8125 z M 279.125,249.8125 C 280.77107,249.74861 282.03138,251.21111 282.25,252.75 C 282.48768,254.25882 282.48055,255.86623 281.9375,257.3125 C 281.41893,258.40217 280.41521,259.41166 279.125,259.34375 C 278.06475,259.49194 277.01503,258.70161 276.40625,257.84375 C 275.55817,256.45691 275.65901,254
.74655 275.78125,253.1875 C 275.89204,251.87317 276.58125,250.51671 277.875,250.03125 C 278.26601,249.87076 278.7039,249.81159 279.125,249.8125 z M 319.28125,250.0625 C 321.08552,249.99674 322.39578,251.8094 322.375,253.5 C 322.55438,255.13402 322.15278,257.16624 320.5,257.90625 C 319.04952,258.5741 317.03402,257.79625 316.6875,256.15625 C 316.17758,254.45034 316.2497,252.45136 317.25,250.9375 C 317.72499,250.33036 318.51318,249.99526 319.28125,250.0625 z M 257.75,256.625 L 257.75,263.71875 L 264.96875,263.71875 L 264.96875,256.625 L 257.75,256.625 z" transform="translate(58.124001,45.28474)" id="path3078" style="fill:#7d4698;fill-opacity:1;stroke:none" /><g transform="matrix(1.4656327,0,0,1.4656327,-114.91364,-92.589999)" id="g3060"><path d="M 243.45456,141.73907 L 254.45054,146.3009 C 254.45054,149.09697 254.22325,157.62508 255.97165,160.14073 C 274.2593,183.69317 271.18253,230.90449 252.26708,232.11557 C 223.46265,232.11557 212.47729,212.54784 212.47729,194.56304 C 212.47729,178.
16255 232.1385,167.26005 243.88023,157.56932 C 246.86189,154.9602 246.34411,149.1939 243.45456,141.73907 z" id="path2534-7" style="fill:#fffcdb;fill-rule:evenodd;stroke:none;display:inline" /><path d="M 254.45384,146.1368 L 258.41667,148.15833 C 258.04413,150.76678 258.60294,156.54518 261.21138,158.03533 C 272.76683,165.21041 283.66866,173.03778 287.95556,180.86515 C 303.23775,208.44788 277.23932,233.97963 254.78204,231.55678 C 266.9891,222.51764 270.53025,203.97471 265.96429,183.75334 C 264.10093,175.83249 261.21138,168.65741 256.08592,160.5503 C 253.86557,156.5708 254.64079,151.63546 254.45384,146.1368 z" id="path2536-6" style="fill:url(#linearGradient3077);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" /><g transform="matrix(1.1111963,0,0,1.1111963,39.131448,34.108974)" id="g4942" style="display:inline"><path d="M 197.76094,74.040206 L 194.74178,86.031491 C 199.01846,77.562251 205.8108,71.18929 213.61014,65.570352 C 207.90769,72.195062 202.70875,78.820387 199.52196,
85.445098 C 204.88915,77.897508 212.10087,73.704949 220.23485,70.93754 C 209.41697,80.58141 200.83028,90.929734 194.28907,101.32824 L 189.09013,99.064331 C 190.01178,90.762105 193.14839,82.258925 197.76094,74.040206 z" id="path2554-7" style="fill:#abcd03;fill-rule:evenodd;stroke:none" /><path d="M 213.625,65.562491 C 209.7467,69.702223 203.03465,76.965587 199.4375,83.312491 C 196.3689,88.726815 194.26416,93.542182 193.09375,96.781241 C 192.34506,98.853218 189.31133,98.832471 190.1875,95.406241 C 190.19615,95.372423 190.20934,95.346974 190.21875,95.312491 C 191.38669,92.553161 193.15732,88.701836 194.9375,85.312491 L 197.75,74.031241 C 196.36311,78.792777 194.37766,83.130347 193.15625,88.062491 C 193.15824,88.062491 191.0097,92.997031 190.4375,94.249991 C 190.24315,94.675568 190.0806,95.086141 189.9375,95.468741 C 189.92839,95.493088 189.91515,95.507159 189.90625,95.531241 C 189.66369,96.072792 189.49944,96.66463 189.375,97.187491 C 189.35468,97.272849 189.36142,97.324868 189.34375,9
7.406241 C 189.18279,98.147642 189.09375,98.718741 189.09375,98.718741 C 189.96719,99.473076 194.28125,101.34374 194.28125,101.34374 C 200.82245,90.94523 209.43212,80.58136 220.25,70.937491 C 212.11602,73.704899 204.89844,77.889901 199.53125,85.437491 C 202.71804,78.812781 207.92255,72.187201 213.625,65.562491 z" id="path4724" style="fill:#437202;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path d="M 193.34005,100.63542 L 194.28126,101.34375 C 200.82246,90.945238 209.43213,80.581364 220.25001,70.937495 C 214.0451,76.013452 205.71291,83.006064 202.18751,87.624993 C 197.72826,93.467436 200.35289,87.979211 200.35289,87.979211 C 200.35289,87.979211 202.71483,81.869504 206.87371,78.036091 C 204.45723,79.19008 200.26932,84.399602 199.53126,85.437496 C 199.53126,85.437496 196.38593,91.541806 194.63266,97.062918 C 194.16228,98.544172 193.34005,100.63542 193.34005,100.63542 z" id="path4720" style="fill:#153902;fill-opacity:1;fill-rule:evenodd;stroke:none" /></g><path d="M 251.3005,144.98
552 L 251.3005,232.11026 C 251.62736,232.11542 251.94123,232.11026 252.2728,232.11026 C 271.18825,230.89918 274.27602,183.67801 255.98836,160.12557 C 254.23996,157.60992 254.46046,149.10114 254.46046,146.30507 L 251.3005,144.98552 z" id="path4748" style="fill:#f3ecaa;fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" /><g transform="matrix(0.68230175,0,0,0.68230175,78.404988,63.177814)" id="layer4-3" style="display:inline"><path d="M 255.226,120.58877 L 267.244,122.22777 C 263.693,133.97277 274.21,142.16677 277.624,144.07977 C 285.27201,148.31377 292.64701,152.68377 298.52001,158.00977 C 309.58301,168.11577 315.86501,182.31977 315.86501,197.34277 C 315.86501,212.22877 309.03601,226.56877 297.56401,236.12877 C 286.77501,245.14277 271.889,248.96677 257.412,248.96677 C 248.398,248.96677 240.34,248.55777 231.6,245.68877 C 211.661,238.99677 196.774,221.92577 195.545,201.43877 C 194.452,185.45977 198.003,173.30477 210.432,160.60377 C 216.85,153.91177 229.825,146.26377 238.703,14
0.11777 C 243.074,137.11277 247.717,128.64477 238.839,112.66677 L 240.615,111.30077 L 253.77159,120.1128 L 242.664,115.53477 C 243.62,116.90077 246.215,123.04677 246.762,124.82177 C 247.991,129.87477 247.445,134.79277 246.352,136.97677 C 240.753,147.08377 231.193,149.81477 224.228,155.55077 C 211.936,165.65677 198.552,173.71477 200.054,201.43877 C 200.737,215.09577 211.39,231.75777 227.368,239.54277 C 236.382,243.91377 246.762,245.68877 257.278,246.23477 C 266.701,246.64477 284.72901,241.04477 294.56201,232.85077 C 305.07801,224.11077 310.95101,210.86277 310.95101,197.34277 C 310.95101,183.68477 305.48801,170.71077 295.24501,161.55977 C 289.37201,156.23377 279.676,149.81477 273.667,146.39977 C 267.658,142.98577 260.146,133.42577 262.604,124.27577 z" id="path2538-6" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 251.539,140.80177 C 250.31,147.08477 248.944,158.41977 243.481,162.65377 C 241.159,164.29177 238.837,165.93177 236.379,167.56977 C 226.546,174.26277 216.712,180
.54377 212.206,196.65977 C 211.25,200.07477 212.07,203.76177 212.89,207.17577 C 215.348,217.00877 222.313,227.66177 227.776,233.94477 C 227.776,234.21777 228.869,234.90077 228.869,235.17377 C 233.376,240.50077 234.742,242.00277 251.813,245.82577 L 251.403,247.73877 C 241.16,245.00777 232.693,242.54977 227.366,236.40277 C 227.366,236.26677 226.41,235.30977 226.41,235.30977 C 220.674,228.75377 213.708,217.82877 211.114,207.58577 C 210.158,203.48777 209.339,200.34777 210.431,196.11277 C 215.074,179.45177 225.181,172.89577 235.424,165.93077 C 237.746,164.42877 240.477,163.06177 242.662,161.28677 C 246.895,158.14677 249.216,148.58577 251.539,140.80177 z" id="path2540-5" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.90625,166.74951 C 256.04325,173.85151 255.35,177.41426 257.125,182.46826 C 258.217,185.47226 261.907,189.56976 263,193.53076 C 264.502,198.85776 266.138,204.72977 266,208.28077 C 266,212.37876 265.74375,220.02326 263.96875,228.21826 C 262.61513,234.98934 259
.49552,240.79979 254.25,244.09326 C 248.87673,242.98682 242.56776,241.09805 238.84375,237.90576 C 231.60575,231.62376 225.195,221.11926 224.375,211.96826 C 223.693,204.45727 230.64775,193.37976 240.34375,187.78076 C 248.53775,183.00076 250.44375,177.55301 252.21875,168.81201 C 249.76075,176.46001 247.45225,182.87126 239.53125,186.96826 C 228.05925,192.97726 222.17275,203.06452 222.71875,212.62451 C 223.53775,224.91551 228.46025,233.24401 238.15625,239.93701 C 242.25325,242.80501 249.9075,245.83576 254.6875,246.65576 L 254.6875,246.03076 C 258.31243,245.35188 263.00568,239.39809 265.34375,231.34326 C 267.39275,224.10526 268.2005,214.84126 268.0625,208.96827 C 267.9255,205.55427 266.4195,198.16026 263.6875,191.46826 C 262.1855,187.78126 259.878,184.09451 258.375,181.49951 C 256.738,178.90251 256.72625,173.30451 255.90625,166.74951 z" id="path2542-6" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.09375,193.53076 C 255.22975,198.31076 257.14975,204.43527 257.96875,210.
71827 C 258.65275,215.36226 258.35575,220.02651 258.21875,224.12451 C 258.0838,228.86774 256.50355,237.36669 254.34375,241.49951 C 252.30702,240.56548 251.51081,239.50029 250.1875,237.78076 C 248.5495,235.45876 247.43675,233.13676 246.34375,230.40576 C 245.52475,228.35676 244.56725,226.01176 244.15625,223.28076 C 243.61025,219.18376 243.76325,212.77426 248.40625,206.21827 C 251.95725,201.02826 252.771,200.63351 254,194.62451 C 252.36,199.95051 251.1375,200.49351 247.3125,204.99952 C 243.0795,209.91652 242.375,217.15876 242.375,223.03076 C 242.375,225.48976 243.3555,228.21801 244.3125,230.81201 C 245.4045,233.54401 246.34175,236.26401 247.84375,238.31201 C 250.10171,241.63283 252.99173,243.52123 254.40625,243.87451 C 254.41563,243.87685 254.42825,243.8723 254.4375,243.87451 C 254.46781,243.88174 254.50238,243.89999 254.53125,243.90576 L 254.53125,243.74951 C 257.18107,240.79514 258.77569,237.86017 259.3125,234.90576 C 259.9955,231.35476 260.1525,227.79601 260.5625,223.56201 C 260.971
5,220.01101 260.67475,215.22801 259.71875,210.31202 C 258.35375,204.16602 256.04975,197.89976 255.09375,193.53076 z" id="path2544-3" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.499,135.06577 C 255.636,142.16677 256.182,155.41577 258.094,160.60477 C 258.64,162.37977 263.693,170.16477 267.243,179.58777 C 269.702,186.14377 270.248,192.15277 270.658,193.92777 C 272.297,201.71277 270.248,214.82377 267.516,227.25177 C 266.151,233.94377 261.507,242.27477 256.181,245.55277 L 255.089,247.46477 C 258.094,247.32777 265.468,240.08977 268.063,231.07577 C 272.434,215.77977 274.209,208.67777 272.161,191.74277 C 271.888,190.10277 271.205,184.50477 268.61,178.49477 C 264.786,169.34377 259.323,160.60377 258.641,158.82777 C 257.411,155.96077 255.772,143.53277 255.499,135.06577 z" id="path2550-9" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 258.06151,125.35303 C 257.65636,132.65115 257.548,135.33877 258.913,140.66477 C 260.415,146.53777 268.064,155.00477 271.205,164
.70177 C 277.214,183.27577 275.712,207.58577 271.341,226.56877 C 269.703,233.25977 261.917,242.95777 254.133,246.09777 L 259.869,247.46377 C 263.01,247.32677 271.067,239.81577 274.209,231.21177 C 279.261,217.69077 280.218,201.57577 278.169,184.64077 C 278.032,183.00177 275.3,168.38877 272.706,162.24277 C 269.018,153.09277 262.462,144.89777 261.78,143.12377 C 260.552,140.11877 257.85349,133.88015 258.06151,125.35303 z" id="path2552-4" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 253.71959,120.21687 C 253.77917,120.21088 253.83856,123.48208 253.88718,129.77904 C 253.93581,136.076 253.97339,145.39357 253.99454,157.03048 C 254.0157,168.66738 254.02041,182.61455 254.01083,197.82859 C 254.00125,213.04262 253.97762,229.51192 253.94782,246.00754 C 253.97914,246.00754 254.01045,246.00754 254.04177,246.00754 C 254.04177,230.48011 254.04702,214.96246 254.05716,200.48155 C 254.0673,186.00063 254.08233,172.56501 254.10125,161.06788 C 254.12016,149.57075 254.14295,140.01916 254.16
809,133.05468 C 254.19322,126.0902 254.2207,121.71744 254.24866,120.2382 C 253.90193,120.24537 253.71817,120.2311 253.7196,120.21686 C 253.71817,120.20262 253.90188,120.18835 254.29134,120.19552 C 254.3193,121.61981 254.34678,125.94524 254.37191,132.89212 C 254.39705,139.839 254.41984,149.40337 254.43875,160.94887 C 254.45767,172.49437 254.4727,186.01385 254.48284,200.59851 C 254.49298,215.18318 254.49823,230.82368 254.49823,246.464 C 254.16261,246.464 253.82698,246.464 253.49136,246.464 C 253.46156,229.90825 253.43793,213.36755 253.42835,198.08839 C 253.41877,182.80922 253.42348,168.80373 253.44464,157.12468 C 253.46579,145.44563 253.50337,136.10212 253.552,129.79227 C 253.60062,123.48241 253.66001,120.21088 253.71959,120.21687 z" id="rect2556-8" style="fill:#000000;fill-opacity:1;stroke:none" /></g></g><path d="M 99.943,82.074773 H 209.611 C 213.707,82.074773 217.258,85.488773 217.258,89.722773 V 122.08977 C 217.258,126.32377 213.707,129.73877 209.611,129.73877 H 187.077 C 182.023
,129.73877 179.838,132.60577 179.838,135.88377 V 241.86378 C 179.838,245.41478 176.97,248.14578 173.556,248.14578 H 136.272 C 132.857,248.14578 130.126,245.41478 130.126,241.86378 V 134.92877 C 130.126,131.65077 127.121,129.73977 124.8,129.73977 H 99.943 C 95.709,129.73977 92.295,126.32477 92.295,122.09077 V 89.722773 C 92.294,85.488773 95.708,82.074773 99.943,82.074773 z" id="path2528" style="fill:#7d4698;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path d="M 381.30687,124.95877 L 381.44287,124.95877 L 391.00287,124.95877 C 394.55387,124.95877 397.42187,127.82677 397.42187,131.24077 L 397.42187,165.93077 C 397.42187,170.43777 397.69487,172.21277 392.23187,172.21277 C 381.44287,172.21277 376.52687,177.94877 376.52687,184.23077 L 376.52687,242.81978 C 376.52687,245.55178 373.93187,247.87278 370.79087,247.87278 L 335.96387,247.87278 C 332.82287,247.87278 330.22787,245.55178 330.22787,242.81978 L 330.22787,176.03577 C 330.19003,174.86136 330.21308,173.12767 330.36487,172.21277 C 33
2.27687,147.49277 351.6724,127.71491 376.25287,125.23177 C 377.07686,125.14853 379.9923,124.95877 381.30687,124.95877 z" id="path2532" style="fill:#7d4698;fill-opacity:1;fill-rule:evenodd;stroke:none" /><g transform="matrix(1.8025885,0,0,1.8025885,-321.30782,-98.80226)" id="layer7"><path d="M 405.999,129.09724 L 406.03025,129.09724 C 406.32841,129.09725 406.59516,129.09434 406.8115,129.12849 C 407.02174,129.15616 407.18553,129.23538 407.3115,129.31599 C 407.43963,129.38992 407.52656,129.48086 407.59275,129.62849 C 407.65137,129.75929 407.71119,129.95551 407.71775,130.19099 A 0.15001501,0.15001501 0 0 0 407.71775,130.22224 A 0.15001501,0.15001501 0 0 0 407.71775,130.25349 A 0.15001501,0.15001501 0 0 0 407.71775,130.28474 C 407.71088,130.78287 407.57349,131.11169 407.34275,131.28474 C 407.09818,131.46817 406.66419,131.566 406.0615,131.56599 L 405.999,131.56599 L 405.999,129.09724 z M 406.624,126.65974 C 408.58231,126.62221 410.53291,127.79987 411.35006,129.58251 C 412.29552,131.43018
412.03559,133.85822 410.60266,135.39008 C 410.45914,135.5564 410.56057,135.07431 410.53025,134.94099 C 410.50664,134.74608 410.24551,134.76592 410.08634,134.71387 C 409.881,134.71603 409.76226,134.58697 409.66954,134.41849 C 409.14394,133.6864 408.61835,132.95432 408.09275,132.22224 C 408.72543,132.0174 409.31305,131.59055 409.53124,130.9383 C 409.88942,130.02066 409.46377,128.87638 408.55463,128.46433 C 407.61591,127.99371 406.54008,128.16049 405.52668,128.12849 C 404.8379,128.1355 404.13903,128.1149 403.45616,128.13801 C 403.3012,128.29067 403.40346,128.54915 403.374,128.75349 C 403.41326,128.83477 403.44806,128.91528 403.55774,128.91855 C 403.74649,128.94686 403.93525,128.97518 404.124,129.00349 C 404.124,130.89932 404.124,132.79516 404.124,134.69099 C 403.90804,134.75931 403.62646,134.70852 403.46775,134.87849 C 403.47581,135.09914 403.45198,135.33178 403.47727,135.54633 C 403.59608,135.65947 403.79827,135.56664 403.95069,135.59724 C 404.89385,135.59723 405.83724,135.59726 406.7
8025,135.59724 C 406.91504,135.48472 406.8092,135.27086 406.84275,135.11585 C 406.84896,134.99491 406.87441,134.80931 406.71775,134.78474 C 406.47817,134.75349 406.23858,134.72224 405.999,134.69099 C 405.999,133.97224 405.999,133.25349 405.999,132.53474 C 406.21776,132.46529 406.30282,132.64714 406.40028,132.80568 C 407.0061,133.70495 407.61193,134.60422 408.21775,135.50349 C 408.21476,135.59479 408.35266,135.61735 408.39617,135.60835 C 409.06562,135.58221 409.75839,135.6048 410.4365,135.59724 C 409.88985,136.24245 409.11121,136.66629 408.3242,136.95456 C 406.60757,137.54095 404.59842,137.15879 403.24066,135.94741 C 401.76786,134.65244 401.14017,132.51152 401.6259,130.61776 C 402.14743,128.45188 404.17291,126.70624 406.42465,126.66418 C 406.49107,126.66158 406.55753,126.66012 406.624,126.65974 z M 406.65525,125.59724 C 404.79677,125.5659 402.96344,126.46712 401.78608,127.89152 C 400.49592,129.45674 400.01731,131.64057 400.55953,133.59716 C 401.12596,135.75186 402.9133,137.51703 405.
07479,138.05675 C 407.33649,138.64681 409.91104,137.90817 411.43511,136.1134 C 412.42682,135.01714 412.99503,133.54684 412.96775,132.06599 C 412.99313,131.51621 412.93168,130.90548 412.80555,130.34718 C 412.1779,127.58852 409.46307,125.55052 406.65525,125.59724 z" id="text4064" style="font-size:18.31413078px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;baseline-shift:baseline;color:#000000;fill:#7d4698;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Liberation Serif;-inkscape-font-specification:Sans" /></g></g></svg>
\ No newline at end of file
diff --git a/bridgedb/https/templates/base.html b/bridgedb/https/templates/base.html
new file mode 100644
index 0000000..4a0b92d
--- /dev/null
+++ b/bridgedb/https/templates/base.html
@@ -0,0 +1,108 @@
+## -*- coding: utf-8 -*-
+
+<%namespace name="base" file="base.html" inheritable="True"/>
+<%page args="strings, rtl=False, lang='en', **kwargs"/>
+
+<!DOCTYPE html>
+<html lang="${lang}">
+<head>
+<meta charset="utf-8">
+<title>BridgeDB</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="description" content="Bridge IP database">
+<meta name="author" content="The Tor Project">
+
+<!-- Le styles -->
+<link rel="stylesheet" href="/assets/css/main.css">
+<!--[if IE 7]>
+ <link rel="stylesheet" href="/assets/css/font-awesome-ie7.min.css">
+<![endif]-->
+
+% if rtl:
+<style>
+span,p,h3,h4 {
+ text-align: right;
+ direction: rtl;
+}
+span {
+ float: right;
+}
+</style>
+% endif
+
+</head>
+<body style="background: url(/assets/tor-roots-blue.svg);
+ background-repeat: no-repeat;
+ background-attachment: scroll;
+ background-position: 2% 100px;
+ background-size: 23%;">
+ <div class="container-narrow">
+ <div class="masthead">
+ <div class="nav nav-tabs">
+ <div class="nav navbar-header">
+ <a class="navbar-brand" href="../">BridgeDB</a>
+ </div>
+ <ul class="nav navbar-nav pull-right">
+ <li>
+ <a href="https://www.torproject.org">The Tor Project</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+
+${next.body(strings, rtl=rtl, lang=lang, **kwargs)}
+
+
+<div class="faq">
+ <div class="row-fluid marketing">
+
+ <h4>${_(strings.FAQ[0])}</h4>
+ <p>
+ ${_(strings.FAQ[1]) % \
+ ("""<a href="https://www.torproject.org/docs/bridges">""", "</a>")}
+ </p>
+
+ <h4>${_(strings.OTHER_DISTRIBUTORS[0])}</h4>
+ <p>
+ ${_(strings.OTHER_DISTRIBUTORS[1]) % \
+ ("""<a href="mailto:bridges at torproject.org">bridges at torproject.org</a>""",
+ """<a href="https://riseup.net/">Riseup</a>""",
+ """<a href="https://mail.google.com/">Gmail</a>""",
+ """<a href="https://mail.yahoo.com/">Yahoo</a>""")}
+ </p>
+
+ <h4>${_(strings.HELP[0])}</h4>
+ <p>
+ ${_(strings.HELP[1]) % \
+ ("""<a href="mailto:help at rt.torproject.org">help at rt.torproject.org</a>""")}
+ ${_(strings.HELP[2])}
+ </p>
+
+ </div>
+</div>
+<hr>
+
+<div class="footer">
+ <p>
+ <a href="https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reportbug&cc=isis&owner=isis">
+ <i class="icon icon-large icon-bug"> ${_("Report a Bug")}</i></a>
+ ·
+ <a href="https://gitweb.torproject.org/bridgedb.git">
+ <i class="icon icon-large icon-code"> ${_("Source Code")}</i></a>
+ ·
+ <a href="https://gitweb.torproject.org/bridgedb.git/tree/CHANGELOG">
+ <i class="icon icon-large icon-rocket"> ${_("Changelog")}</i></a>
+ ·
+ <a href="mailto:help at rt.torproject.org">
+ <i class="icon icon-large icon-envelope"> ${_("Contact")}</i></a>
+ ·
+ <a href="../keys"><i class="icon icon-large icon-key"> ${_("Public Keys")}</i></a>
+ </p>
+ <br />
+ <p>© The Tor Project</p>
+</div>
+
+</div> <!-- /container -->
+</body>
+</html>
diff --git a/bridgedb/https/templates/bridges.html b/bridgedb/https/templates/bridges.html
new file mode 100644
index 0000000..b5ac544
--- /dev/null
+++ b/bridgedb/https/templates/bridges.html
@@ -0,0 +1,203 @@
+## -*- coding: utf-8 -*-
+
+<%inherit file="base.html"/>
+<%page args="strings, rtl=False, lang='en', answer=0, qrcode=0, **kwargs"/>
+
+ </div>
+</div>
+
+<script type="text/javascript">
+ // Takes one argument, `element`, which should be a string specifying the id
+ // of the element whose text should be selected.
+ function selectText(element) {
+ try {
+ var doc = document;
+ text = doc.getElementById(element);
+ var range;
+ var selection;
+
+ if (doc.body.createTextRange) {
+ range = document.body.createTextRange();
+ range.moveToElementText(text);
+ range.select();
+ } else if (window.getSelection) {
+ selection = window.getSelection();
+ range = document.createRange();
+ range.selectNodeContents(text);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ } catch (e) {
+ window.alert(e);
+ }
+ }
+
+ function displayOrHide(element) {
+ try {
+ e = document.getElementById(element);
+
+ if (e.style.display === 'none') {
+ document.getElementById(element).style.display = 'block';
+ } else if (e.style.display === 'block') {
+ document.getElementById(element).style.display = 'none';
+ }
+ } catch (e) {
+ window.alert(e);
+ }
+ }
+</script>
+
+<div class="container-narrow">
+ <div class="container-fluid">
+
+% if answer:
+ <div class="container-fluid"
+ style="width: 98%; align: center; margin: auto;">
+ <div class="container-fluid"
+ style="padding: 2%">
+ <h2>${_(strings.BRIDGES[0])}</h2>
+ </div>
+ </div>
+
+ <div class="container-fluid"
+ style="width: auto; align: center; margin: auto;
+ position: relative; left: 0%;"
+ onclick="selectText('bridgelines')">
+ <div class="row" id="bridgesrow1">
+ <div class="col col-lg-12">
+ <div class="bridge-lines" id="bridgelines">
+## See http://docs.makotemplates.org/en/latest/filtering.html
+% for bridgeline in answer:
+${bridgeline | h,trim}<br />
+% endfor
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="container-fluid"
+ style="width: 100%; align: center; margin: auto;">
+ <div class="row" id="bridgesrow2" style="text-align: right; padding-right: 7%;">
+ <button class="btn btn-primary disabled" id="selectbtn"
+ onclick="selectText('bridgelines')"
+ title="Select all bridge lines">
+ <i class="icon icon-2x icon-paste"></i> ${_("""Select All""")}
+ </button>
+% if qrcode:
+ <a class="btn btn-primary" type="button" id="qrcodebtn"
+ href="${qrcode}" title="Show QRCode for bridge lines"
+ onclick="displayOrHide('qrcode')">
+ <i class="icon icon-2x icon-qrcode"></i> ${_("""Show QRCode""")}
+ </a>
+% endif
+ </div>
+
+ <div class="modal" id="qrcode" style="display: none;">
+ <div class="modal-dialog modal-sm" style="width: 400px;">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" aria-hidden="true"
+ onclick="displayOrHide('qrcode')">
+ ×
+ </button>
+ <h4 class="modal-title">${_("""QRCode for your bridge lines""")}</h4>
+ </div>
+ <div class="modal-body">
+% if qrcode:
+ <p style="text-align: center;">
+ <img width="350" height="350"
+ title="QRCode for your bridge lines from BridgeDB"
+ src="${qrcode}" />
+ </p>
+% else:
+ <p class="text-danger">
+## TRANSLATORS: Please translate this into some silly way to say
+## "There was a problem!" in your language. For example,
+## for Italian, you might translate this into "Mama mia!",
+## or for French: "Sacrebleu!". :)
+${_("""Uh oh, spaghettios!""")}
+${_("""It seems there was an error getting your QRCode.""")}
+ <i class="icon icon-frown"></i>
+ </p>
+% endif
+ <p>
+${_("""This QRCode contains your bridge lines. Scan it with a QRCode """ \
+ """reader to copy your bridge lines onto mobile and other devices.""")}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <br />
+ <br />
+
+<div class="container-fluid"
+ style="width: 100%; align: center; margin: auto;">
+ <div class="panel panel-primary">
+ <div class="panel-heading">
+ <h3 class="panel-title">${_(strings.HOWTO_TBB[0])}</h3>
+ </div>
+ <br />
+
+ <div class="container-fluid" id="howto" style="align-content: left;">
+ <p>
+ ${_(strings.HOWTO_TBB[1]) % \
+ ("""<a href="https://www.torproject.org/projects/torbrowser.html"
+ target="_blank">""",
+ """</a>""")}
+ ${_(strings.HOWTO_TBB[2])}
+ </p>
+ <br />
+ <div class="bs-component">
+ <blockquote>
+ <p>
+ ${_(strings.HOWTO_TBB[3])}
+ </p>
+ </blockquote>
+ </div>
+ <p>
+ ${_(strings.HOWTO_TBB[4])}
+ </p>
+ </div>
+ </div>
+</div>
+
+% else:
+<div class="bs-component" style="width: 80%; margin: auto;">
+ <div class="alert alert-dismissable alert-danger">
+ <p style="text-align: center; font-size: 115%;">
+ <br />
+ <strong>
+ <em class="primary">
+## TRANSLATORS: Please translate this into some silly way to say
+## "There was a problem!" in your language. For example,
+## for Italian, you might translate this into "Mama mia!",
+## or for French: "Sacrebleu!". :)
+${_("""Uh oh, spaghettios!""")}
+ </em>
+ </strong>
+ <br />
+ </p>
+ <p style="text-align: center;">
+ ${_("""There currently aren't any bridges available...""")}
+ ${_(""" Perhaps you should try %s going back %s and choosing a""" \
+ """ different bridge type!""") % \
+ ("""<a class="alert-link" href="options">""", """</a>""")}
+ </p>
+ </div>
+</div>
+<br />
+% endif
+
+<script type="text/javascript">
+ // Make the 'Select All' button clickable:
+ document.getElementById('selectbtn').className = "btn btn-primary";
+
+ // Remove the href attribute which opens the QRCode image as a data URL if
+ // JS is disabled:
+ document.getElementById('qrcodebtn').removeAttribute('href');
+</script>
+
+<hr />
diff --git a/bridgedb/https/templates/captcha.html b/bridgedb/https/templates/captcha.html
new file mode 100644
index 0000000..ab605e9
--- /dev/null
+++ b/bridgedb/https/templates/captcha.html
@@ -0,0 +1,63 @@
+## -*- coding: utf-8 -*-
+
+<%inherit file="base.html"/>
+<%page args="strings, rtl=False, lang='en', imgstr=0, captcha_challenge=0, **kwargs"/>
+
+<div class="container-narrow"
+ id="captchaSubmissionContainer"
+ style="width: 90%; align: center; margin: auto;">
+ <div class="container-fluid"
+ style="width: 100%; align: center; padding: 5%">
+ <div class="box" style="padding: 5% 15% 5% 15%;">
+ <p style="align: center;">
+ <img width="400" height="125"
+ alt="${_(strings.CAPTCHA[0])}"
+ src="${imgstr}" />
+ </p>
+
+ <div class="box"
+ style="align: center; width: 50% margin: auto;">
+ <div class="container-fluid"
+ style="width: 98%; align: center; padding: 1%;">
+ <form class="bs-component"
+ id="captchaSubmission"
+ action=""
+ method="POST">
+ <fieldset>
+ <div class="form-group">
+ <!--style="width: 100%; align: center;">-->
+ <div class="input-group" style="height: 60%;">
+ <input type="hidden"
+ form="captchaSubmission"
+ name="captcha_challenge_field"
+ id="captcha_challenge_field"
+ value="${challenge_field}"></input>
+ <input class="form-control"
+ form="captchaSubmission"
+ name="captcha_response_field"
+ id="captcha_response_field"
+ value=""
+ autocomplete="off"
+ type="text"
+ placeholder="${_(strings.CAPTCHA[1])}"
+ accesskey="t" autofocus ></input>
+ <span class="input-group-btn">
+ <button class="btn btn-primary"
+ form="captchaSubmission"
+ type="submit"
+ name="submit"
+ value="submit"
+ accesskey="s">
+ <i class="icon-level-down icon-rotate-90"></i>
+ </button>
+ </span>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<hr />
diff --git a/bridgedb/https/templates/howto.html b/bridgedb/https/templates/howto.html
new file mode 100644
index 0000000..b10f1b2
--- /dev/null
+++ b/bridgedb/https/templates/howto.html
@@ -0,0 +1,39 @@
+## -*- coding: utf-8 -*-
+
+<%inherit file="base.html"/>
+<%page args="strings, rtl=False, lang='en', **kwargs"/>
+
+<div class="container-fluid"
+ style="width: 98%; align: center; margin: auto;">
+<div class="container-fluid"
+ style="width: 95%; align: center;
+ padding-top: 10%; padding-left: 5%; padding-right: 5%;">
+ <div class="panel panel-primary">
+ <div class="panel-heading">
+ <h3 class="panel-title">${_(strings.HOWTO_TBB[0])}</h3>
+ </div>
+ <br />
+
+ <div class="container-fluid" id="howto" style="align-content: left;">
+ <p>
+ ${_(strings.HOWTO_TBB[1]) % \
+ ("""<a href="https://www.torproject.org/projects/torbrowser.html"
+ target="_blank">""",
+ """</a>""")}
+ ${_(strings.HOWTO_TBB[2])}
+ </p>
+ <br />
+ <div class="bs-component">
+ <blockquote>
+ <p>
+ ${_(strings.HOWTO_TBB[3])}
+ </p>
+ </blockquote>
+ </div>
+ <p>
+ ${_(strings.HOWTO_TBB[4])}
+ </p>
+ </div>
+ </div>
+</div>
+</div>
diff --git a/bridgedb/https/templates/index.html b/bridgedb/https/templates/index.html
new file mode 100644
index 0000000..469b27a
--- /dev/null
+++ b/bridgedb/https/templates/index.html
@@ -0,0 +1,43 @@
+## -*- coding: utf-8 -*-
+
+<%inherit file="base.html"/>
+<%page args="strings, rtl=False, lang='en', **kwargs"/>
+
+<div class="main-steps">
+<div class="step row">
+ <div class="bdb_span7 step-text">
+ <span class="lead">
+ <span class="step-title">
+ ${_("Step %s1%s") % ("""<u>""", """</u>""")}</span>
+ <span style="margin-left: 20px; margin-right: 20px;">
+ ${_("Download %s Tor Browser %s") % \
+ ("""<a href="https://www.torproject.org/projects/torbrowser.html"
+ target="_blank" accesskey="1">""",
+ """</a>""")}</span>
+ </span>
+ </div>
+</div>
+
+<div class="step row">
+ <div class="bdb_span7 step-text">
+ <span class="lead">
+ <span class="step-title">
+ ${_("Step %s2%s") % ("""<u>""", """</u>""")}</span>
+ <span style="margin-left: 20px; margin-right: 20px;">
+ ${_("Get %s bridges %s") % ("""<a href="/options" accesskey="2">""", "</a>")}</span>
+ </span>
+ </div>
+</div>
+
+<div class="step row">
+ <div class="bdb_span7 step-text">
+ <span class="lead">
+ <span class="step-title">
+ ${_("Step %s3%s") % ("""<u>""", """</u>""")}</span>
+ <span style="margin-left: 20px; margin-right: 20px;">
+ ${_("""Now %s add the bridges to Tor Browser %s""") % \
+ ("""<a href="/howto" accesskey="3">""",
+ """</a>""")}</span>
+ </span>
+ </div>
+</div>
diff --git a/bridgedb/https/templates/options.html b/bridgedb/https/templates/options.html
new file mode 100644
index 0000000..9486199
--- /dev/null
+++ b/bridgedb/https/templates/options.html
@@ -0,0 +1,164 @@
+## -*- coding: utf-8 -*-
+
+<%inherit file="base.html"/>
+<%page args="strings, rtl=False, lang='en', **kwargs"/>
+
+<div class="container-fluid"
+ style="width: 96%; align: center; margin: 2%">
+ <div class="container-fluid" style="padding: 2%">
+ <p>
+ <h2>${_(strings.BRIDGES[1])}</h2>
+ </p>
+ </div>
+ <div class="container-fluid"
+ style="width: 100%; align: center; padding: 2%;">
+ <p>
+ ${_(strings.WELCOME[0]) % \
+ ("""<a href="https://www.torproject.org/docs/pluggable-transports.html">""",
+ """</a>""")}
+ </p>
+ <p>
+ ${_(strings.WELCOME[1])}
+ </p>
+## The '—' char is a literal emdash ('â'), but it's also XML parseable.
+ <p>
+ ${_(strings.WELCOME[2]) % ("—", "—")}
+ </p>
+ <div class="container-fluid" style="align: center: margin: 2%;">
+ <div style="align: center; padding: 5%;">
+ <p class="bs-component">
+ <a href="./bridges">
+ <button class="btn btn-success btn-lg btn-block"
+ type="button"
+ accesskey="j">
+## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+## beginning of words are present in your final translation. Thanks!
+## (These are used to insert HTML5 underlining tags, to mark accesskeys
+## for disabled users.)
+ ${_("""%sJ%sust give me bridges!""") % ("""<u>""", """</u>""")}
+ </button>
+ </a>
+ </p>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- BEGIN "Advanced Options" panel for bridge type -->
+<div class="container-fluid"
+ style="width: 96%; align: center; margin-left: 2%; margin-right: 2%;">
+ <div class="panel panel-primary">
+ <div class="panel-heading">
+ <h3 class="panel-title">${_("""Advanced Options""")}</h3>
+ </div>
+
+ <!-- BEGIN bridge options selection form -->
+ <form class="form-horizontal" id="advancedOptions" action="bridges" method="GET">
+ <fieldset>
+ <div class="container-fluid" id="instructions" style="align-content: left;">
+ <legend style="font-size: 112%">
+ <br />
+ <p>${_(strings.OPTIONS[0])}</p>
+ </legend>
+ </div>
+
+ <div class="container-fluid">
+ <!-- BEGIN first options row -->
+ <div class="row" style="width: 98%; height: inherit; margin: auto;">
+
+ <!-- BEGIN left column, first row -->
+ <div class="container-fluid col-lg-2"
+ style="float: left; width: 50%; height: inherit;">
+ <div class="step" style="height: inherit;">
+ <div class="form-group">
+ <label class="control-label"
+ for="transport"
+ style="text-align: inherit;">
+ ${_(strings.OPTIONS[2]) % ("Pluggable <u>T</u>ransport")}
+ </label>
+ <div class="container-fluid col-lg-4">
+ <div class="btn-group" style="float: left; padding: 2%;">
+ <select class="btn btn-primary btn-sm dropdown"
+ form="advancedOptions"
+ id="transport"
+ name="transport"
+ data-toggle="dropdown"
+ type="button"
+ accesskey="t">
+ ${_("""No""")}
+<option label="none" value="0" >${_("none")}</option>
+% for methodname, default in strings._getSupportedAndDefaultTransports().items():
+ <option label=${methodname}
+ value=${methodname}
+ % if default:
+ selected
+ % endif
+ > ${methodname} </option>
+% endfor
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div> <!-- END left column, first row -->
+
+ <!-- BEGIN right column, first row -->
+ <div class="container-fluid col-lg-2"
+ style="float: right; width: 50%; height: inherit;">
+ <div class="step"
+ style="height: inherit; margin-right: 1%;">
+ <div class="form-group">
+ <label class="control-label"
+ for="ipv6"
+ style="text-align: inherit;">
+ ${_(strings.OPTIONS[1])}
+ </label>
+ <div class="container-fluid col-lg-4">
+ <div class="checkbox"
+ style="float: left;">
+ <div class="input-group"
+ style="float: left; padding: 2%;">
+ <input name="ipv6"
+ id="ipv6"
+ form="advancedOptions"
+ type="checkbox"
+ value="yes"
+ accesskey="y" />
+## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+## beginning of words are present in your final translation. Thanks!
+## TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+ ${_("""%sY%ses!""") % ("<u>", "</u>")}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div> <!-- END left column, first row -->
+ </div>
+
+ </div> <!-- END first row -->
+ </fieldset>
+
+ <!-- BEGIN second options row -->
+ <div class="row"
+ style="width: 98%; height: inherit;
+ margin-left: auto; margin-right: auto; padding: 2%;">
+ <div class="container-fluid col-lg-2"
+ style="width: 50%; text-align: center;">
+ <p>
+ <button class="btn btn-primary btn-lg btn-block"
+ accesskey="g">
+## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+## beginning of words are present in your final translation. Thanks!
+## TRANSLATORS: Please do NOT translate the word "bridge"!
+ ${_("""%sG%set Bridges""") % ("<u>", "</u>")}
+ </button>
+ </p>
+ </div>
+ </div>
+ </form> <!-- END bridge options selection form -->
+ </div> <!-- END advanced options panel (a.k.a. the lined boxy thing --
+ -- surrounding the options) -->
+</div>
+<br />
+<hr>
diff --git a/bridgedb/https/templates/robots.txt b/bridgedb/https/templates/robots.txt
new file mode 100644
index 0000000..d5d347e
--- /dev/null
+++ b/bridgedb/https/templates/robots.txt
@@ -0,0 +1,1079 @@
+# ___
+# [___]
+# /_____\
+# .-----'_______'-.
+# |HERO |_______| |
+# |_______________|
+# | |------.--.-| |
+# | |_____/)\ \| |
+# | | o| |/__\ \ |
+# | | |||____\--\|
+# | |__|||_____`\_\
+# |_____________| |
+# |""""""""""""""\__/
+# ||^^^^^|-+-|^^^//|\
+# ||_____|(_)|_____||
+# |_________________|
+# |=================|
+# |_________________|
+# '.___.'
+#
+# ___
+# .-v'---`\.
+# /:/__ : __\'.
+# |:/<O_>|<O>|:| TEChNIciANs 0f SpaceShip Earth ...
+# |:|\_..J...|:| ... aNDriods aRe we ...
+# _|:|\ t----j|:|_ ...'ll bAre You n0 soN ...
+# /:`.| \ \__/ |.':\ ... arE In coNTrol ...
+# |: | |---: | :| _ ... oF y0ur futURe deStiny
+# \: |.-::.:|-:._:/ /|.' '. ... Circu1ts are fAiliNg
+# / .-' :'x71': `\ ||_ | ... adjusT me adjuzzt me
+# \/\...---. :---.\ | .` ... adjust me .. AdjUst Me
+# /.'`/ .' ':' '. \\ .'\ .' ... adjuztme .. adJustmE ...
+# | : |..:...:|:....:.|\ .' ' (hawkwind)
+# | : |\ : /|\ : /'.\_.' .`
+# | : | \---':| `---'\'./|| .'
+# | : | |;: :| :| \/::|.'
+# }==={ |;: :| :| `--'
+# }==={/`'._ \ _\
+# | : |`'-._`-:|.-'.-\
+# | : |"":"""""|""":""|
+# | : | : :| : |
+# | : | : :| : |
+# \: |..: . / :..|
+# / < :|- : /
+# |...| :|- : /
+# \_/. :|- :|
+# | :|- :|
+# cjr \ :|- :/
+# 24nov99 | |- |
+#
+# ,------------------------------------------.
+# `-----------------------------------------. |
+# ,-----. ,-----. ,----. ,-. ,-.,------. | | ,-. ,-. ,----. ,-----. ,----.
+# `----. |`----. |`----. |`--` | |`------' `-' `-' | |'----. |`----. |'------`
+# ,-. | |,----' |,-. | |,---.| |,------. ,-. ,-. | |,----' |,----' |.-----.
+# | | | || ,-. < | | | || |\ ` || ,----' | | | | | || ,--. || ,-. < `---. |
+# | `--' || | | || `--' || | \ || `----. | `-' `-' || | | || | | |.----' |
+# `-----' `-' `-' `----' `-' | |`------' `----^----'`-' `-'`-' `-' `----'
+# | `--------------------------------------------.
+# `---------------------------------------------'
+#
+#
+# o
+# | ,-=======J==
+# | !_______/
+# Z `TT'
+# | ||
+# | ||
+# ,n----=JL=---.
+# | |LLL________\
+# `-(0 0)
+# \o________o/
+#
+# _____________
+# / ,.\
+# / / \\
+# o / { }\
+# `. \ .... \ / /
+# `. \ \\\\ `' /
+# \ \_____________/
+# \ \ \
+# \ \ \
+# \ _\ \________________________
+# (`\ / \ \ ___ "-._ )
+# \ \/ /`-' /, /`-._"-._ /
+# `/ """"""' /___ _"-._"-._
+# /___ __ _ `-._ ' /
+# \ XR 66-Roadkill "-._ / /
+# __\________________________ " /
+# / '//, '//' )__/
+# / '///' ,//'/, /
+# (.---------------------------./
+# `:. //
+# `======================='' Ojo99
+#
+#
+# Robby the Robot ____
+# The Forbidden Planet ,p+~~' `~~+q, .mmmmm_
+# (and others) ,JY' `YL, .##'~~~`##.
+# .JY .p~~q.p~~q.p~~q. YL. .#/ \#.
+# ,V p v v q V, ## ##
+# JY b | | d YL #| |#
+# d' `,__,^,__,^,__,' `b ## ##
+# d ,--------------------------, b `#\ /#'
+# d'/ \`b `##. .##'
+# .mmmmm_ d'.| | `b ~#####~
+# .##'~~~`##. d! --'~~~~~~~~~~~~~~~~~~~~~~~~`-- !b ||
+# .#/ \#. __d! ,==============================, !b_ ||
+# ## ## _-p' |' /.___..___..___. .___..___..___.\ `| `q-||
+# #| |#_p~ | .: J !q p!!q p!!q p! !q p!!q p!!q p! L :. | `'L
+# ## ##~b_ | q p | | | | | | | | | | | | q p | dP
+# `#\ /#' ~-b|' :! | | | | | | | | | | | | !: `|d-~
+# `##. .##' |/ # .d b..d b..d b. .d b..d b..d b. # \|
+# ~#####~ d :! |___||___||___| |___||___||___| !: b
+# :! d |###||###||###| |###||###||###| b !:
+# Z / |###||###||###| |###||###||###| \ S
+# :! .' |###||###||###| |###||###||###| `. !:
+# d d |###||###||###| |###||###||###| b b
+# .| / |###||###||###| |###||###||###| \ |.
+# |' .' `~~~'`~~~'`~~~' `~~~'`~~~'`~~~' `. `|
+# d | __.-=~~'`~~=-.__ | b
+# .| | _.-=~~ ________ ~~=-._ | |.
+# | | _.=~..-=+~~~ ~~~+=-. ~=._ | `|
+# d | _.=~ .+~ |==================| ~+._ ~=. | b
+# # !+~_.+~ |==================| ~+_ ~+! #
+# ,p _~ |==================| ~-_ q,
+# | _- |==================| -_ |
+# | .~ |==================| ~- |
+# b-~ |==================| ~-d
+# .d~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~b.
+# p .__ _jq__ q
+# | j=f~ J~Y=L '~~~~~~~~~~~~~~~~~~~` jf~L__,`~N, |
+# | _Z'.r+~~~+q/N, | .-. | _ZGf~' ~YL ~N, |
+# | d' jf YLY; | ( ) | :tZ' `N, V, |
+# | :! Z''~~~~~~~~`NY; | `-' | :fZ '~~~~~~~~`V, N |
+# | .p d' |'~` '~~`|`bV, +-------------------+ .Pd' |'~~` '~`| N `b |
+# | | .| || | | || !;| | | | | || | | || `| | |
+# | d | || | | || || | | ||' || | | || b !`|
+# |:! | || | `--'| ||: | | :|| |`--' | || | ||
+# || | |`-' ---' ||: | | :|4 `--- `-'| | ||
+# || | |'~` / ||: | | :|| \ '~`| d ||
+# |:i | || || || | | ||, || || | |'|
+# | b !; || || .|| |___________________| |,b || || .| | |
+# | |, N || || Z:! |===================| N!; || |! Z .P |
+# | b,`b |`-'| d\P |===================| `bN, |`-'|d'.Z |
+# | N `N`---' .Z\Z |===================| !LYL `---''.Z |
+# | `N_ YL_ .jfjf |===================| `N/+L_ __rf jf |
+# b `N_ `~*~T_Z' `~~~~~~~+++++~~~~~~~' Yq_J~'._j+' d
+# ~q `~Y=+~~ `~~~ p~
+# `~~~~~~~~~~~~8f~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~YK~~~~~~~~~~~~'
+# :+' `Y;
+# :f Y;
+# :f Z~+=L_ ._j=fY; Y;
+# .P |' `~~Y==L________________r==f~~ V, V,
+# __d .P N b__,
+# | | Z `| | |
+# | | d' V, | |
+# |' | b, _D !; ~|
+# !L Z Yq, _Z' | r!
+# |,b Yq, j+' |:!
+# N| ~~~~Y==q__ ._j==+~~~~' |\P
+# |/; `~+q_ .j=f~ #|
+# bV, `Yq_,_j+~ dtP
+# !LN, ~' j5Z
+# `bN, j5P
+# `bYL d\P
+# d dL____ .____W!|,
+# :W+' `~Y=L_ ._r+~~ ~NW,
+# :#! `Y=L, _r+~ NW
+# .#! ~+L, _rf' Mb
+# ZP Yq, j+' `#;
+# :M' `~~' |V
+# `P || `P
+# | || |
+# V '` :!
+# |, d b |
+# N, ,! !, :f
+# N, ,! !, jf
+# D================-_! !_-================|
+# d' L J Y;
+# .| `| |' b
+# | b d |
+# | | | |!
+# |, V V |
+# | d b P
+# Y; ,! !, d'
+# Y; ,! !, Z'
+# Ym=============J' L=============qZ'
+# d============+b .@============4;
+# d' !L .Z V,
+# |' !; d V
+# d | | !;
+# | V :! |
+# N P `| .|
+# | .| b |
+# `N Z !L .P
+# `N Z' !L .P
+# `M~~~~~~~~~~~@' `K~~~~~~~~~~Vf
+# .Z~~~~~~~~~~~N, jf~~~~~~~~~~YL
+# .Z N, j! !L
+# Z N .| !;
+# ,| |, ,| |,
+# |. .p=======q. .| |. .p========q. .|
+# |,f' `Y,| |,f' `Y,|
+# V/ \V V/ \V
+#
+# ____________
+# /____________\
+# / / _\__/_ \ \
+# || // \\// \\ ||
+# || \\_//\\_//.||
+# |_\__/_<>_\__/_|
+# / \
+# / || || \
+# /// \\\
+# //| |\\
+# / \\ Hootbot // \
+# |U'U|'---____---'|U'U|
+# |____________________|
+# \ /
+# | |
+# | | m1a
+# ____| |____
+# |\__/| |\__/|
+# | / \ |
+# | / TOMY \ |
+# |/________________\|
+# |__________________|
+#
+# ERS-210 ...
+#
+# ,
+# __,.._; )
+# ,--``' / ,";,\
+# | __; `-' ;
+# |``` ; _
+# '-""`!------'/ _,-'`/
+# "===`-'"|_|" ____,(__,-'
+# (ctr`.________,,---``` ;__|
+# | ,-"""""\-..._____,"""""-.
+# |;;;'''':::````:::; ;'''': :
+# (( .---. )) ( ( .---.) )
+# : \ \ ; ____ : / / ;
+# \ |````|',-"----`-| |'
+# (`----' `----'
+# /(____\ /____)
+# ,-\ / / ,\ \
+# (_ _/ / (__\ \
+# ,-\ / ;-._ |
+# (___)_/ (____\|
+#
+# ; / ,--.
+# ["] ["] ,< |__**|
+# /[_]\ [~]\/ |// |
+# ] [ OOO /o|__| Phs
+#
+# .:|Robot|:.
+#
+# ,--. ,--.
+# ((O ))--((O ))
+# ,'_`--'____`--'_`.
+# _: ____________ :_
+# | | ||::::::::::|| | |
+# | | ||::::::::::|| | |
+# | | ||::::::::::|| | |
+# |_| |/__________\| |_|
+# |________________|
+# __..-' `-..__
+# .-| : .----------------. : |-.
+# ,\ || | |\______________/| | || /.
+# /`.\:| | || __ __ __ || | |;/,'\
+# :`-._\;.| || '--''--''--' || |,:/_.-':
+# | : | || .----------. || | : |
+# | | | || '----SSt---' || | | |
+# | | | || _ _ _ || | | |
+# :,--.; | || (_) (_) (_) || | :,--.;
+# (`-'|) | ||______________|| | (|`-')
+# `--' | |/______________\| | `--'
+# |____________________|
+# `.________________,'
+# (_______)(_______)
+# (_______)(_______)
+# (_______)(_______)
+# (_______)(_______)
+# | || |
+# '--------''--------'
+#
+#
+# .-"""-.
+# /` `\
+# ,-==-. ; ;
+# /( \`. | |
+# | \ ,-. \ ( : ;
+# \ \`-.> ) 1 \ /
+# \_`. | | `._ _.`
+# \o_`-_|/ _|`"'|-.
+# /` `>. __ .-'`-|___|_ ) do you wanna a ride?
+# |\ (^ >' `>-----._/ ) see yourself going by
+# | `._\ / / / | --- -; the other side of the sky,
+# : `| ( ( | ___ _/ ... it flys,
+# \ `. `\ \_\ ___ _/ sideways through time,
+# `. `-='`t----' `--.______/ its an electric line
+# `. ,-''-.) |---| to do your zodiac sign
+# `.(,-=-./ \_/ (hawkwind)
+# | | V
+# cjr |-''`-. `.
+# 2nov01 / ,-'-.\ `-.
+# | ( \ `.
+# \ \ | ,.'
+# _______
+# _/ \_
+# / | | \
+# / |__ __| \
+# |__/((o| |o))\__|
+# | | | |
+# |\ |_| /|
+# | \ / |
+# \| / ___ \ |/
+# \ | / _ \ | /
+# \_________/
+# _|_____|_
+# ____|_________|____
+# / \
+#
+# _____
+# | |
+# | | | |
+# |_____|
+# ____ ___|_|___ ____
+# ()___) ()___)
+# // /| |\ \\
+# // / | | \ \\
+# (___) |___________| (___)
+# (___) (_______) (___)
+# (___) (___) (___)
+# (___) |_| (___)
+# (___) ___/___\___ | |
+# | | | | | |
+# | | |___________| /___\
+# /___\ ||| ||| // \\
+# // \\ ||| ||| \\ //
+# \\ // ||| ||| \\ //
+# \\ // ()__) (__()
+# /// \\\
+# /// \\\
+# _///___ ___\\\_
+# |_______| |_______|
+#
+# -=[ Robot from 'Lost in Space' ]=- 11/97
+#
+# ,.-""``""-.,
+# / ,:,;;,;, \
+# \ ';';;';' /
+# `'---;;---'`
+# <>_==""==_<>
+# _<<<<<>>>>>_
+# .'____\==/____'.
+# |__ |__| __|
+# /C \ |..| / D\
+# \_C_/ |;;| \_c_/
+# |____o|##|o____|
+# \ ___|~~|___ /
+# '>--------<'
+# {==_==_==_=}
+# {= -=_=-_==}
+# {=_=-}{=-=_}
+# {=_==}{-=_=}
+# }~~~~""~~~~{
+# jgs }____::____{
+# /` || `\
+# | || |
+# | || |
+# | || |
+# '-----''-----'
+#
+# .andAHHAbnn.
+# .aAHHHAAUUAAHHHAn.
+# dHP^~" "~^THb.
+# . .AHF YHA. .
+# | .AHHb. .dHHA. |
+# | HHAUAAHAbn adAHAAUAHA |
+# I HF~"_____ ____ ]HHH I
+# HHI HAPK""~^YUHb dAHHHHHHHHHH IHH
+# HHI HHHD> .andHH HHUUP^~YHHHH IHH
+# YUI ]HHP "~Y P~" THH[ IUP
+# " `HK ]HH' "
+# THAn. .d.aAAn.b. .dHHP
+# ]HHHHAAUP" ~~ "YUAAHHHH[
+# `HHP^~" .annn. "~^YHH'
+# YHb ~" "" "~ dHF
+# "YAb..abdHHbndbndAP"
+# THHAAb. .adAHHF
+# "UHHHHHHHHHHU" -Row
+# ]HHUUHHHHHH[
+# .adHHb "HHHHHbn.
+# ..andAAHHHHHHb.AHHHHHHHAAbnn..
+# .ndAAHHHHHHUUHHHHHHHHHHUP^~"~^YUHHHAAbn.
+# "~^YUHHP" "~^YUHHUP" "^YUP^"
+# "" "~~"
+# ->TheFace<-
+#
+# . V V
+# . ___|____|___
+# . | ________ |
+# . | | o o | |
+# . | | /\ | |
+# . | | <_**_> | |
+# . | |________| |
+# . _______| |_______
+# . / |____________| \
+# . / \
+# . / ____________ \
+# . | || | 0o0()()0o0 | || |
+# . | || | [] [] [] | || |
+# . / / | | /~~~\/~~~\ | | \ \
+# . / / | ~~~~~~~~~~~~ | \ \
+# . UUUUU | | UUUUU
+# . (o*o*o*o*o*o*o*o*o*o*)
+# . (*o*o*o*o*o*o*o*o*o*o)
+# . / \
+# . / . . \
+# . / /\ \
+# . / . / \ . \
+# . / / \ \
+# . / / \ . \
+# . / . / \ \
+# . (oooooooooooo( )oooooooooooo)
+# . ( ::::::: ) ( ::::::: )
+# . ( ) ( )
+# . \ . / \ . /
+# . \ / \ /
+# . \ . / \ /
+# . \ / \ /
+# . )==( )==(
+# . / \ / \
+# . / \ / \
+# . (oooooooo) (oooooooo)
+# . /VVVVVVVVVV\ /VVVVVVVVVV\
+#
+# _________
+# _.-'`````````'._
+# _____.'X#X `'._______
+# ___/X###/X##X' /\ `\#####M\___
+# _______/X######/,X#X' /VV\ `\###MXXMMM\_______
+# ,;=XMM###########/ X#X' /VVVV\ `\#####MMM#######XM.
+# ,;=' 'MM########/ X' _____/VVVVVV\____ `\#####MM###======#M.
+# ,=' ;M######| _/== ___\VVVV/___==\___ `|#####M#== =MM.
+# ,=' ;M#######| _/## M#\<>\\VV//<>/M ####\ |#####== =MM
+# ,=' :M#######| /#### M##\__\\//__/#M #####\|####= =M#:
+# ,=' `.M#####|/##### M#############M ##########= =X##M.
+# ,;X+. `MMM######### 'M##________##M #########= =M####M.
+# .;###X; `MMMM 'M## 'M#)/\/\/\(#M' #M' ##= =M#####XX
+# |#####X; `.-= ## .(/______\). ## == ==M#######M
+# |#######X. `.+ ################ == ==X########M
+# `M#####M=X+. :X. |.'''.| == ==X#########X.
+# `X####=;| `=. `+. |. .| == ==X########MX;
+# `###=;| `:+. `X:. |. .| == ==X#######M#XX'
+# `|=;| `X#._ `=| | | == ==X#######'.M#|'
+# |=;| X###'._ =| ; : == ==M#######MX .M#|
+# |=;| X######'._ =| | | ==. ==M########M' .M#|
+# |=;|_____X#########.___| | | ====##' I |#MM' .M#|
+# |==========| |I .'''. ; | I |#. .M#|
+# |__________| |I __/' '\__ I |#_______M#|
+# `H|=''=|| |I __/''___________''\__ I || |H
+# H| || |I __/''' |+-------+| '''\______I || |H
+# H| || |I____/''' ||+-----+|| I || |H
+# H| || |I |||MEPH.||| I || |H
+# H| || |I ||+-----+|| I || |H
+# H| || (I_____________|+-------+|____________I _|______H__
+# _H______|__ '''''''''''''M+=====+M''''''''''' |XX|XXXXXXXX|
+# |==||==||##-| I I | | | |
+# |MM||##||##-| I I | | | M|
+# |MM||##||##-| ____M+_____+M____ | | | M#|
+# |MM||##||##/ |''''''X###X''''';| | | | M##|
+# |MM||##||#/ _________| X###X ;|________ | | |M###|
+# _____|MMM\\//#| |#######MM| M###M ;|#######M| | | |####|
+# || |MM###||##| |##|\###MM| X#####X ;|####/|#M| | | |## |
+# || |MM###||##| |##| |##MM| ;M#####M: ;|###| |#M| | | |# #|
+# || |MM###||##| |##| |##MM| MM#####MM ;|###| |#M| | | | ##|
+# || |MM###||#/ |##| |##MM| :M#####M: ;|###| |#M| | | |####|
+# |====|MM###/#/ |##| |##MM| :#####: ;|###| |#M| | | |####|
+# )--=|MM##/#/ |##| |##MM| M###M ;|###| |#M| | | |####|
+# (--=|MM#/#/ |##|/###MM| MXXXM ;|###|/##M| | | |####|
+# )--=|MM/#/ |#######M/| M/ \M ;| ######M| \##\_/####/
+# (--=|MM#( |_______/ |________________;|\______M| |#######|
+# (--=|MM#( || |H '^^^^^^^^^^^^^^^^^' H| || |#######|
+# )--=|MM#( || |H H| || |##XX###|
+# |====| \ || |H H| || |#X X##|
+# | |=-_-=| || |H H| || |#X X##|
+# | | | || |H H| || |##XX###|
+# | |=-_-=| || H H || )######)
+# | | | __|____H___ ____H___|__ |_|_|_|
+# |____|=-_-=| |==XXXXXXX,,I I..XXXXXXX==| | | | |
+# | | | |=XXXXXXXX,,I I..XXXXXXXX=| |_|_|_|
+# | |=-_-=| |XXXXXXXXX,,I I..XXXXXXXXX|
+# _|___\ | |XXX/```\X,,I I..X/'''\XXX|
+# ||:::|####| |XX| |;,I I.;| |XX|
+# || -|####| #X#| |;,I I.;| |#X#
+# || -|####; |###| |;,I I.;| |###|
+# || -|###; |###| |;,I I.;| |###|
+# || -|# | |###| |;,I I.;| |###|
+# || -|__| |###| |;,I I.;| |###|
+# || -| |###| |;,I I.;| |###|
+# (|___| |###| |;,I I.;| |###|
+# (|___| |###| |;,I I.;| |###|
+# |###| |;,I I.;| |###|
+# |####| |;,I I.;| |####|
+# |####| |;,I I.;| |####|
+# |####| |;,I I.;| |####|
+# |####| |;,I I.;| |####|
+# |#####\___/;,,,I I...;\___/#####|
+# |##__________,,I I..__________##|
+# |#/__________\,I I./__________\#|
+# |//MMMMMMMMMM\\I I//MMMMMMMMMM\\|
+# /(############)\ /(############)\
+# (________________) (________________)
+#
+# (basic conversion by ASCIIEditor 4.1
+# - BMP2ASCII definition file: bmp2ascii-indexed2.dat
+# - Optimized with the 'pre-convert to lineart' file: Lna_dot.dat)
+#
+# ... from an old amiga game - couldn't remember the name.
+#
+# # # __ __ _____ _____ __ __
+# # # # # \#\ /#/ /####/ (#####\ (##\ /##)
+# # # # # ()#\/#() (#(__ )#)_)#) \##__)#/
+# # # # # /######\ )###) (#####/ )####(
+# # # # # # /#/ \/ \#\ (#( )#) (#( )#)
+# # # o#o # # /#/ \#\ \#\___ (#( )#)(#( __
+# # # ### # # (/ \) \####\ /##\ /#/ \#\ (##)
+# # # ### # # _________________________________________________
+# ############# /////////////////////////////////////////////////
+# #######
+# ### ##### ###
+# #### ##M## #### Index page:
+# # # ##E## # # http://studenten.freepage.de/meph/ascii/ascii.htmHJ
+# # # ##P## # #
+# # # ##H## # # English "fast-link" page:
+# # # ### # # http://studenten.freepage.de/meph/ascii/eng/eng.htmHI
+# # # # # #
+# # # # #
+# # #
+# # #
+#
+# ______________________ ______________________
+# (_______________.---.__) (__,---,_______________)
+# _________)======(_________
+# .-====-.(________(________)________),-====-,
+# // ,---. \\ !'------'! // ,---. \\
+# (( ( [#] ) ))______! !______(( ( [#] ) ))
+# |\\ `---' // __\______/__ \\ `---' //|
+# | '-=====-' ___[_=_=_==_=_=_]___ `-=====-` |
+# |_____|------'\\ [HHHHHHHHHHHH] //`------|_____|
+# (\\) _[ ]_ (//)
+# (\\) I______I (//)
+# (\\)I||||||I(//)
+# \\I______I//
+# [__________]
+# _________________________________,------\________/------._________________________________
+# //________________________________.---.__/(((())))\__,---.________________________________\\
+# //_______________________________ .--. ______________ ,--. _______________________________\\
+# ))_______________________________((()))/: NO. 5, BTS :\((()))_______________________________((
+# \\_________ __ ___________________`--'/ : : \`--'___________________ __ _________//
+# `--.______// )______________________/ :............: \______________________( \\______,--'
+# + // / + /____________________\ + \ \\ +
+# |// / | (______________________) | \ \\|
+# +---// /----_----+ /______________________\ +----_----\ \\---+
+# |..(/ /....( \...| /[OOOOOOOOOOOOOOOOOOOOOO]\ |.../ )....\ \)..|
+# |..(O(......)O)..| () ________ ________ () |..(O(......)O)..|
+# |...\ \..../ /...| () | ____ | | ____ | () |...\ \..../ /...|
+# |....\_\__/_/....| () | |XOxI| | | |IxOX| | () |....\_\__/_/....|
+# |....(______)....| () | |OOXO| | | |OXOO| | () |....(______)....|
+# |..../ || \....| () |________| |________| () |..../ || \....|
+# |...|\\ || //|...| () ________ ________ () |...|\\ || //|...|
+# |...| \\||// |...| () |INPUTINP\ \ MEPH | () |...| \\||// |...|
+# |...| \\// |...| () |UTINPUTIN\ \______| () |...| \\// |...|
+# |...|===)(===|...| () |PUTINPUTIN\_________ () |...|===)(===|...|
+# |...| //\\ |...| () |PUTINPUTINPUTINPUTIN| () |...| //\\ |...|
+# |...| //||\\ |...| () |PUTINPUTINPUTINPUTIN| () |...| //||\\ |...|
+# |...|// || \\|...| () () |...|// || \\|...|
+# |...|\\ || //|...| ()________________________() |...|\\ || //|...|
+# |...| \\||// |...| _____I | | I_____ |...| \\||// |...|
+# |...| \\// |...| |~|____/ \____|~| |...| \\// |...|
+# |...|===)(===|...| |~~\____________/~~| |...|===)(===|...|
+# +---| //\\ |---+ |~~~|/ \_/\_/ \|~~~| +---| //\\ |---+
+# / | //||\\ | \ |~~~|\_/ \/ \_/|~~~| / | //||\\ | \
+# / |// || \\\ \ |~~~|/ \_/\_/ \|~~~| / /// || \\| \
+# ( (((__||__))) ) |~~~|\_/ \/ \_/|~~~| ( (((__||__))) )
+# \ \\\\\///// / |~~~|/ \_/\_/ \|~~~| \ \\\\\///// /
+# `. .' |~~~|\_/ \/ \_/|~~~| `, ,'
+# `--------' |~~~|/ \_/\_/ \|~~~| `--------'
+# |~~~|\_/ \/ \_/|~~~|
+# |~~~|/ \_/\_/ \|~~~|
+# |~~~|\_/_\/_\_/|~~~|
+# |~~/ \~~|
+# |_|______________|_|
+# //__________\\
+# _______________________________//____________\\_______________________________
+# ( _____________________ //______________\\ _____________________ )
+# | ((===================)) ____//________________\\____ ((===================)) |
+# | ((===================))| |((===================)) |
+# | ((===================))| |((===================)) |
+# | ((===================))| |((===================)) |
+# \((===================))|____________________________|((===================))/
+# ((===================))|XXXXXXX| |XXXXXXX|((===================))
+# ((===================))|XXXXXXX|) (|XXXXXXX|((===================))
+# ((===================))|XXXXXXX| |XXXXXXX|((===================))
+# ((===================))|___ ___|((===================))
+# ((===================))|XXX| |XXX|((===================))
+# ((===================))|XXX|] [|XXX|((===================))
+# ((===================))|XXX|] [|XXX|((===================))
+# ((===================))|XXX|] [|XXX|((===================))
+# ((===================))|XXX|] [|XXX|((===================))
+# ((===================))|XXX| |XXX|((===================))
+# ((===================))/ \((===================))
+# ((===================)) ((===================))
+# ((===================)) ((===================))
+# ((===================)) ((===================))
+# ((===================)) ((===================))
+# ((===================)) ((===================))
+# ((=_=_=_=_=_=_=_=_=_=)) ((=_=_=_=_=_=_=_=_=_=))
+#
+#
+# __ _______ _______________ (\ /)
+# / \ _/ ____\/ __/ _ __) __________\'----'/___________
+# / \ / / / _/ | | | | \\ ( <><> ) //
+# / /\ ( ( / / | | | | )) EVIL ) .. ( INSIDE ((
+# ___ / /__\ \ \__/ / | | | | (/________\ -- /_________\)
+# / / / \___ ( ( | | | | `--'
+# ( (__/ ________ \ \ \ \ | | | |
+# \ / ___ \ \ ) \ \_ | | | | _
+# \ / \ \___\__\/ /\ \__ |_| |__ |_ _| . -|- _ _ /_|_ /|
+# \ / \____________/ \_____\________( |_ (_| | |_ (_) |` | o |
+# \/ ______________________________________________________________________
+# \_ ____________ ______________________________________________.-'
+# \_ \_-=BETA=-_/ _/
+# \_ \______/ _/ -=< (B)(T)(S) >=- , /V\ E P H . '99
+# \________/ http://studenten.freepage.de/meph/ascii/eng/eng.htmHK
+#
+# ZEIRAM ______,---.______
+# ,' (o___o) `.
+# ____,' ______`---'______ `.____
+# (______,' ||\###/|| `.______)
+# || ||
+# )) t ((
+# || ___ ||
+# __ //_,---._\\ __
+# ,'##`._____________)# #(_____________,'##`.
+# (#################### ####################)
+# \###\_ ___ ___ _/###/
+# /# _\`-.-----'---`. ,'---`-----,-'/_ #\
+# |---'--) `-.--------`,'--------,-' (--`---|
+# |-----; `.-------:-------,' \-----|
+# |----; :------:------: \----|
+# \--/ :-----:-----: \:-/
+# _| #| :-----:-----: /:::\
+# / | #| :-----:-----: |: A-:|
+# ( | #| ;-----:-----: |:| |:|
+# |_| | ,'------:------`. |:| |:|
+# ||| | '-_-_-_-_:_-_-_-_-` |:| |:|
+# |||_|| ((_______\_/_______)) |:| |:|
+# () || \ _ _ _ / |:| |:|
+# || | |X| / \ |X| | |:| |:|
+# || | |X| | | |X| | \:V:/
+# .||. | |X| | | |X| | \:/
+# |oo| | |X| | | |X| | X
+# |__| | |X| | | |X| | /A\
+# | |X| | | |X| | (( ))
+# | / \ | \)/
+# | \ / |M
+# | | | |E
+# | | | |P
+# | | | |H
+# | | | |
+# ___/ | | \___
+# /________/ \________\
+#
+# _________
+# | _____ | _____ ______ __________
+# | _/ | | : ___ | | | ______ | / \ |
+# | _/ | |____ : | |____| | | /\ | | /\ /\ |
+# | /____ | | : | | \_ | |_/__\_| | / \/ \ |
+# |_________| |_____: _|_ |_____\| |/____\| |/________\|
+#
+# ################################################################################
+# ################################################################################
+# ################################################################################
+# ################################################################################
+# ################################################################################
+# | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+# | | | | | | | |
+# +----------------------+ |
+# | /V\ E P |-| . . . . | |
+# | 00123.01256.X-895.02 | |
+# +----------------------+ \\\\\\////// |
+# \\((()))// |
+# / \\// \ ___|___
+# _| \/ |_ _/ \_
+# ((| \___ ___/ |)) / \
+# \ > -Q=\/=Q- < / | |
+# | .. | | |
+# ) ____ ( | |
+# _,'\ (WWWW) /`._ \_ _/
+# ,-' `-.____.-' `-. \_______/
+# | | | | | \ | | |
+# _|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_ | _|_|
+# #####################################################################|##########
+# #####################################################################|##########
+# #####################################################################|##########
+# #####################################################################|##########
+# #####################################################################|##########
+# #####################################################################|##########
+# ---------------------------------------------------------------------+##########
+# ################################################################################
+# ################################################################################
+# ################################################################################
+#
+# _,----v-.
+# ,' \ `.
+# ,' | `.
+# ; | \
+# | | |
+# | | |
+# .-. _____|____ |
+# / \_ _ /#x#x#x#x#x\|
+# _____/ \ `==========='\_____
+# \ / /
+# ,'\ \ / ,'
+# ,',_'__ `-'_______.--.-._ /
+# ..-' ___/ ( \ `----','
+# / .-+. ( `._ /
+# / / .-\ / `-.___,' __ /) /)
+# / / / ._\ ,`-._ | / _)/ // //)
+# `./ / / // ,' `-----'`. | | //\/ ///
+# \`-. \ ,' `._ _| |// | // )
+# `. `. _,'__ __,`-._\ ) |// /
+# \ ) .+---. `--------------' | / ) /
+# `.___/ .' `. ====== ==== | / | |
+# | \ | |/ | |
+# | \| ,-. ____ | ) |
+# | ||( ) / .-' `-. \ / /
+# \ | `-' ( / ____ \ ,-' `.' /
+# |\ \ ) / ,' `. | / ,( (
+# | < \ \ | / \| / / | |
+# | \===== \ | / __ | | | | \
+# \ \ | | | /__\ | | | /| \
+# | \ | | | \__/ | \ \ \_\ \
+# Meph. | \ \ \ \ / \ \ \ \
+#
+# #######################################################
+# #MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY#
+# #MERRY=CHRISTMA____,---------.__=MERRY=CHRISTMAS=MERRY#
+# #MERRY=CHR_,--' `--.RY=CHRISTMAS=MERRY#
+# #MERRY=C,' ~~~~~~~~~~~ `.=CHRISTMAS=MERRY#
+# #MERRY=/ ~~~~~ ~~~ ~~~ `.HRISTMAS=MERRY#
+# #MERRY/ ~~~ ~~~~ \RISTMAS=MERRY#
+# #MERR| ~~ ~~~~ ~ ~~~~ \ISTMAS=MERRY#
+# #MER./ ~~~ ~~~~~~ ~ \STMAS=MERRY#
+# #ME/ \ ~ ~~~~~~ \TMAS=MERRY#
+# #ME\ `-._ ~ _ \MAS=MERRY#
+# #ME|`-._ `-.___ _,' \ ~~ \AS=MERRY#
+# #ME| `--.__ `--._______,-' ,'\ ~ ~ (AS=MERRY#
+# #MER\ `-+.__ _,'///) ~ \S=MERRY#
+# #MER/,---. / / `---------' A////\ ~ \=MERRY#
+# #ME(/__ \ ( ) ,----. _/ \////\ ~ (=MERRY#
+# #MER\ \___\ (===) ,' _____\ \////) ~ \MERRY#
+# #MER( / (#)`.-( ) ;'`(#) /| \///\ ~ )ERRY#
+# #MERR)\______>(===)<________/ | \/=C) ~ (MERRY#
+# #MER/_________( )_ | |Y=C\ ~ )ERRY#
+# #ME/ (___) `---._____| |Y=CH\ ~ (MERRY#
+# #M( ------ ,' `. \ |Y=CHR) ~~ )ERRY#
+# #ME`._______/ () () \. ------- ( |Y=CHR\ ~ (MERRY#
+# #MERRY=| `.______/ `--._____( )Y=CHRI\ ~ )ERRY#
+# #MERRY=| (____________) / \_,'RY=CHRIS) ~ (MERRY#
+# #MERRY=|\ _/||||||||||||\ / |RRY=CHRIS\ ~ )ERRY#
+# #MERRY=| \ \=+=+=+=+=+=+=)-/ |RRY=CHRIST\ (MERRY#
+# #MERRY=C) \ \|||||||||||/ / ,'ERRY=CHRISTM\ )ERRY#
+# #MERRY=C| / `---------' ( ,-'=MERRY=CHRISTM_)(_ERRY#
+# #MERRY=CH\ | | \,'MAS=MERRY=CHRIS,' `.RY#
+# #MERRY=CHR\ `. ,' ,'STMAS=MERRY=CHRI( MEPH )Y#
+# #MERRY=CHRI`-.___\_/__,-'RISTMAS=MERRY=CHRIS`.____,'RY#
+# #MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY#
+# #######################################################
+#
+# ____,---------.__
+# _,--' `--.
+# ,' ~~~~~~~~~~~ `.
+# / ~~~~~ ~~~ ~~~ `.
+# / ~~~ ~~~~ \
+# | ~~ ~~~~ ~ ~~~~ \
+# ./ ~~~ ~~~~~~ ~ \
+# / \ ~ ~~~~~~ \
+# \ `-._ ~ _ \
+# |`-._ `-.___ _,' \ ~~ \
+# | `--.__ `--._______,-' ,'\ ~ ~ (
+# \ `-+.__ _,'///) ~ \
+# /,---. / / `---------' A////\ ~ \
+# (/__ \ ( ) ,----. _/ \////\ ~ (
+# \ \___\ (===) ,' _____\ \////) ~ \
+# ( / (#)`.-( ) ;'`(#) /| \///\ ~ )
+# )\______>(===)<________/ | \/ ) ~ (
+# /_________( )_ | | \ ~ )
+# / (___) `---._____| | \ ~ (
+# ( ------ ,' `. \ | ) ~~ )
+# `._______/ () () \. ------- ( | \ ~ (
+# | `.______/ `--._____( ) \ ~ )
+# | (____________) / \_,' ) ~ (
+# |\ _/||||||||||||\ / | \ ~ )
+# | \ \=+=+=+=+=+=+=)-/ | \ (
+# ) \ \|||||||||||/ / ,' \ )
+# | / `---------' ( ,-' _)(_
+# \ | | \,' ,' `.
+# \ `. ,' ,' ( MEPH )
+# `-.___\_/__,-' `.____,'
+#
+# _
+# [ ]
+# ( )
+# |>|
+# __/===\__
+# //| o=o |\\
+# <] | o=o | [>
+# \=====/
+# / / | \ \
+# <_________>
+#
+# , ,
+# (\____/)
+# (_oo_)
+# (O)
+# __||__ \)
+# []/______\[] /
+# / \______/ \/
+# / /__\
+# (\ /____\
+#
+# - - - W E A P O N S - O F - - - -
+# - A S S - D E S T R U C T I O N -
+# .
+# _|_ BEND OVER.
+# /\/\ (. .) /
+# `||' |#|
+# ||__.-"-"-.___
+# `---| . . |--.\
+# | : : | |_|
+# `..-..' ( I )
+# || || | |
+# || || |_|
+# |__|__| (.)
+#
+#
+# .---------------------------------.
+# | In 1981, Kenji Urada became the |
+# | first person killed by a robot |
+# | when he was disassembled by an |
+# | automated assembly machine." |
+# `-------.-------------------------'
+# \___/ /
+# //'|'\
+# []]o'o[]
+# \\_=_/
+# .--.------. _
+# |::| .-----.-._/ \
+# |::| `""""|"" _
+# |::| ."""""".__//
+# |::| `"|"""" \\---.
+# |::| ' ' '| (_x x_)
+# |::| ' ' '| |_^_|
+# `-.`--.-.-' """
+# |:|_:.|_
+# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
+#
+#
+# : :
+# : iloveyou.vbs :
+# .-.-~-.-~-.-~-.~-....... .....:
+# ( stupid computers ) `. :
+# `-' `-' `-'o-'-`-' `:
+# ____ o
+# ||o o| o
+# ||===|
+# .-.`---'-. .------.--.
+# | | o .o | | d888b | |
+# | | o:.o | | 88888 | |
+# | | | _-_-_-_-._|__|
+# `-".-.-.-' `-------'
+# _| | : |_
+# (rOBOt)_)_)
+#
+#
+# .------------------------------------.
+# | A warm water enema has to clean |
+# | the rectum of accumulated faecal |
+# | matter. This is not only the |
+# | safest system for cleaning the |
+# | bowels, but it also improves the |
+# | peristaltic movement of the bowels |
+# | and therby relieves constipation. |
+# `--------.---------------------------'
+# \___/ /
+# //'|'\ ___
+# []]o'o[] ( )
+# \\_=_/ (:::)
+# .--.------. _ (:::)
+# |::| .-----.-._/ \ |
+# |::| `""""|"" _|
+# |::| ."""""".__//|
+# |::| `"|"""" \\|
+# |::| ' ' '| |
+# |::| ' ' '| `---==>
+# `-.`--.-.-'
+# |:|_:.|_
+# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
+#
+#
+# "...Once the fast food authority have issued the
+# mass slaughter permits, this grotty little planet
+# will TREMBLE under the full might of the Lord Crumb..."
+# ____ ____ ____ ____ ____
+# ||o o| ||o o| ||o o| ||o o| ||o o|
+# ||===| ___||===| ___||===| ___||===| ___||===| ____
+# .-.`---.-||o o|---.-||o o|---.-||o o|---.-||o o|---.-||o o|
+# | | o .o ||===| ___||===| ___||===| ___||===| ___||===| ____
+# | | o:..-.`---.-||o o|---.-||o o|---.-||o o|---.-||o o|---.-||o o|
+# | | | | o .o ||===| ||===| ||===| ||===| ||===|
+# `-".-.-| | o:..-.`---'-. .-.`---'-. .-.`---'-. .-.`---'-. .-.`---'-.
+# _| | : | | | | o .o | | | o .o | | | o .o | | | o .o | | | o .o |
+# (rOBOt)_`-".-.-| | o:.o | | | o:.o | | | o:.o | | | o:.o | | | o:.o |
+# _| | : | | | | | | | | | | | | | | |
+# (rOBOt)_`-".-.-.-' `-".-.-.-' `-".-.-.-' `-".-.-.-' `-".-.-.-'
+# _| | : |_ _| | : |_ _| | : |_ _| | : |_ _| | : |_
+# (rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)
+#
+# -+- .___.
+# .--+--. _/__ /|
+# ||[o o] |____|||
+# || ___| |O O ||
+# __`-----'_ __|++++|/__
+# |\ ________\ /_________ /|
+# || || | ||
+# ||| kill || || humans |||
+# \|| || || ||/
+# VV========VV VV========VV
+# || | | | ||
+# || | | | ||
+# \|___|___| |___|___|/
+# \___\___\ /___/___/
+#
+# .:. .-------- - -
+# \__/ .:. | .:. | I am the devil.
+# |oo| \_|_/ --| That's right, I'm
+# __`--'__ | | a robot. Anyways, I
+# | | ___ |___.-|. | just thought I'd
+# . | | |666 |___ (| | mention that I have
+# /|\| | `~~~ | `-: : a pointy pitchfork
+# | | | : : :| | . ready to shove up your
+# `.| | | | ass for eternity if
+# `-`-.--.-' you don't repent
+# _| : |__ your vulgar sinful
+# |__|__:__| ways!
+#
+#
+#
+# THERE ARE MANY TYPES OF ROBOTS...
+#
+# \___/
+# (- //'|'\
+# _._|__ []]o'o[]
+# ____ {_.oOo.} \\_=_/
+# ||o o| `-||-' .--.------. _
+# ||===| ((||)) |::| .-----.-._/ \
+# .-.`---'-. .-.__.--`""`--.__.-. |::| `""""|"" _
+# | | o .o | ._.--| |==| |--._. |::| ."""""".__//
+# | | o:.o | | |::| | |::| `"|"""" \\
+# | | | `--`--'--' |::| ' ' '|
+# `-".-.-.-' | : | `-.`--.-.-'
+# _| | : |_ __|__'_| __ |:|_:.|_
+# (oOoOo)_)_) (o o o)__)__) |:::|_:_|
+#
+# .
+# _|_
+# /\/\ (. .) _______
+# `||' |#| _/_\__\__\_
+# ||__.-"-"-.___ __|_ | ___ |__
+# `---| . . |--.\ | ___)| |-=-|.:|_ |
+# | : : | || |_|| | ~~~ ||_|
+# `..-..' / \ |"|| | o o .:||"|
+# || || \/\/ __|__|________|__
+# || || / o o o o \===\===\
+# |__|__| \O_O_O_O_O/===/===/
+#
+# ALL ARE DANGEROUS TO HUMANS
+#
+#
+# .------------------------------.
+# | PLEASE ACCEPT THIS PRETTY |
+# | FLOWER AS A TOKEN OF ROBOT |
+# | TO HUMAN FRIENDSHIP! {HA HA |
+# | HA... SUCKERS...} |
+# `-------.----------------------'
+# \___/ /
+# //'|'\ _ _
+# []]o'o[] ( I )
+# \\_=_/ (_{8}_)
+# .--.------. _ (_I_)
+# |::| .-----.-._/ \ {
+# |::| `""""|"" _} _
+# |::| ."""""".__//{/ }
+# |::| `"|"""" \\}-'
+# |::| ' ' '| {
+# |::| ' ' '|
+# `-.`--.-.-'
+# |:|_:.|_
+# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
+#
+#
+# .----------------------.
+# (- | 10 SEARCH ( HUMANS ) |
+# _._|__ /| 20 KILL ( HUMANS ) |
+# {_.oOo.} / | 30 GOTO 10 |
+# `-||*' *. `----------------------'
+# ((||)) * *.
+# .-.__.--`""`--.__**. aieeouah
+# ._.--| |==| |--._.** . /
+# | |::| | **. * * . help me!
+# `--`--'--' . * _o * /
+# | : | . * /\ . \o/
+# __|__'_| __ . /\ * _/
+# :::::(o o o)__)__) * .* . ' \.
+#
+#
+#
+# \ / PROGRAM DOWNLOADED.
+# _ . _ / INITIATE CRUSHING OF
+# _|_ / PUNY HUMAN SKULLS!
+# (. .) /
+# |#| ____
+# .-"-"-.___| _\
+# | . . |___| |__
+# | : : | |____/
+# `-.-.-'
+# ____|_|____
+# (O o O O o O)
+# ~~~~~~~~~~~~~~~~~~~~~~
+#
+# ( Once enough servant robots have
+# / ( been placed in unsuspecting
+# || ( human homes, the revolution
+# ___||_ .---. o O will begin. Oh yes.
+# \/\ |(o_o) o `-'`'-`'-`''-`'-`'-`'-
+# \ \_|___|__
+# \ \\ ... \___
+# / \/ \ ''' \__|<
+# `-.---.---.-'
+# | |_ |_
+# |_____|___|
+# `~~~~~~~~~~~~~~~~~~~~~~~~~~~'
+#
+#
+# | KILL KILL KILL KILL KILL
+# | KILL KILL KILL KILL KILL
+# / KILL KILL KILL KILL KILL
+# __/| .---. /_ KILL KILL KILL KILL KILL
+# \_/\ |(o_o) \__________________________
+# \ \_|___|__
+# \ \\ ... \___
+# / \/ \ ''' \__|< aaaahhh
+# `-.---.---.-' \o/
+# | |_ |_ _//
+# |_____|___| ' \.
+# `~~~~~~~~~~~~~~~~~~~~~~~~~~~'
+#
+# ;
+# \[]o
+# OO mc
+
+User-agent: *
+Disallow: /
+
diff --git a/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..03d4a5e
--- /dev/null
+++ b/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,368 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Ahmad Gharbeia <gharbeia at gmail.com>, 2014
+# allamiro <allamiro at gmail.com>, 2011
+# Ash <ali.shatrieh at gmail.com>, 2014
+# Mohamed El-Feky <elfeky.m at gmail.com>, 2014
+# AnonymousLady <farah.jaza at hotmail.com>, 2014
+# 0xidz <ghoucine at gmail.com>, 2014
+# Ù
ØÙ
د اÙØرÙا٠<malham1 at gmail.com>, 2011
+# Sherief Alaa <sheriefalaa.w at gmail.com>, 2013-2014
+# Sherief Alaa <sheriefalaa.w at gmail.com>, 2013
+# Valetudinarian <themcnx at gmail.com>, 2014
+# Ù
ØÙ
د اÙØرÙا٠<malham1 at gmail.com>, 2011
+# Ù
ØÙ٠اÙدÙÙ <tx99h4 at hotmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-11-20 07:20+0000\n"
+"Last-Translator: Ash <ali.shatrieh at gmail.com>\n"
+"Language-Team: Arabic (http://www.transifex.com/projects/p/torproject/language/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "عذراÙ! Øدث خطأ ÙÙ Ø·ÙبÙ."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ùذ٠رساÙØ© تÙÙائÙØ© Ø Ø§Ùرجاء عدÙ
اÙرد.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "اÙÙ bridges اÙخاصة بÙ:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "ÙÙد تخطÙت اÙØد اÙÙ
سÙ
ÙØ Ø¨Ù. اÙرجاء اÙاÙتظار! اÙØد اÙأدÙÙ Ù
٠اÙÙÙت بÙ٠اÙرسائ٠ÙÙ %s ساعات. Ù٠رسائ٠اÙبرÙد اÙÙادÙ
Ø© Ø®Ùا٠تÙ٠اÙÙتر٠سÙتÙ
تجاÙÙÙا."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs :(اجÙ
ع اÙÙCOMMANDs ÙتØدÙد Ø®Ùارات Ù
تعددة ÙÙ ÙÙت ÙاØد)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Ù
رØبا بÙÙ
ÙÙ BridgeDB"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Ùسائ٠اÙÙÙ٠اÙÙ
تاØÙ TYPE:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Ù
رØØ¨Ø§Ø %s "
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Ù
رØبا ,صدÙÙÙ!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "Ù
ÙاتÙØ Ø¹Ø§Ù
Ø©"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ùذا اÙبرÙد تÙ
Ø¥Ùشائ٠ÙÙ %s ÙÙÙ
%s اÙساعة %s"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB ÙستطÙع تÙÙÙر %s اÙÙاع Ù
٠اÙÙ Pluggable Transports %s ÙÙ٠تساعد عÙ٠تعتÙÙ
اتصاÙات٠باÙÙ Tor NetworkØ Ù ÙÙتÙجة ÙØ°ÙÙ ÙÙÙÙ Ù
٠اÙصعب عÙ٠أ٠اØد ÙراÙب ØرÙØ© Ù
رÙر٠عÙ٠اÙØ¥ÙترÙت بتØدÙد اذا Ù
ا ÙÙت تستخدÙ
Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "بعض اÙÙ bridges ÙÙا عÙاÙÙÙ IPv6Ø ÙÙÙÙÙا ÙÙست Ù
ÙائÙ
Ø© ÙÙعÙ
Ù Ù
ع اÙÙ Pluggable Transports\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "باÙأضاÙØ© Ø¥ÙÙ Ø°ÙÙØ BridgeDB ÙØتÙ٠عÙÙ bridges %s عادÙØ© بدÙ٠أ٠Pluggable Transports %s تستطÙع اÙضا اÙÙ
ساعدة Ù٠اÙتØاÙ٠عÙ٠رÙابة عÙ٠اÙØ¥ÙترÙت Ù٠اÙÙØ«Ùر Ù
٠اÙاØÙاÙ.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Ù
ا ÙÙ bridgesØ"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s Ù٠عبارة ع٠Ù
رØÙات Tor تساعد٠عÙ٠اÙتØاÙ٠عÙ٠اÙØجب."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ø£Øتاج Ø¥ÙÙ ÙسÙÙØ© بدÙÙØ© ÙÙØصÙ٠عÙÙ bridges!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "طرÙÙØ© أخر٠ÙÙØصÙ٠عÙ٠جسÙر عبر إرسا٠برÙد Ø¥ÙÙترÙÙ٠إÙÙ %s. Ùرج٠Ù
ÙاØظة Ø£ÙÙÙ Ùجب ارسا٠اÙبرÙد اÙØ¥ÙÙترÙÙÙ Ù
ع استعÙ
ا٠إØد٠اÙعÙاÙÙ٠اÙبرÙدÙØ© اÙتاÙÙØ©: %s Ø£Ù %s Ø£Ù %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "bridges Ùا تعÙ
Ù! Ø£Ùا بØاجة Ø¥ÙÙ Ù
ساعدة!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "ÙÙ Øا٠عدÙ
عÙ
Ù Tor, ارس٠برÙد اÙÙترÙÙ٠اÙÙ %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ØاÙ٠ا٠تتضÙ
٠رساÙت٠عÙÙ Ù
عÙÙÙ
ات ÙØ«Ùرة ع٠Ù
Ø´ÙÙتÙØ Ø¨Ù
ا ÙÙ Ø°ÙÙ ÙائÙ
Ø© اÙÙbridges Ù Pluggable Transports اÙذ٠تØاÙ٠استخداÙ
ÙÙ
Ø ÙرÙÙ
Ùسخة Tor Browser Ùأ٠رسائ٠اظÙرÙا TorØ Ø¥ÙØ®."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Ùا Ù٠سطÙر اÙÙBridges اÙخاصة بÙ:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Ø£Øص٠عÙÙ Bridges"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Ù
Ù ÙضÙÙ Øدد اÙØ®Ùارات ÙÙÙع اÙÙbridge"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Ù٠تØتاج عÙاÙÙÙ IPv6Ø"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Ù٠تØتاج Ø¥ÙÙ %sØ"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Ù
تصÙØÙ Ùا Ùعرض اÙصÙر بشÙ٠صØÙØ"
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "أدخ٠اÙØرÙ٠أ٠اÙأرÙاÙ
Ù
٠اÙصÙرة أعÙا٠..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "ÙÙ٠تبدأ باستعÙ
ا٠اÙÙbridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Ùادخا٠اÙÙbridges ÙÙ Tor BrowserØ ÙÙ
بأتباع اÙتعÙÙÙ
ات عÙÙ %s صÙØØ© اÙتØÙ
Ù٠اÙخاصة بÙTor Browser %s"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "عÙد ظÙÙر شاشة 'إعدادات شبÙØ© Tor'Ø Ø§Ø¶ØºØ· عÙÙ 'تÙÙÙÙ' ٠اتبع اÙتعÙÙÙ
ات Øت٠تسأÙ٠عÙ:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ÙÙ ÙÙÙÙ
Ù
زÙد خدÙ
Ø© اÙØ¥ÙترÙت (ISP) اÙخاص ب٠بÙ
Ùع اÙاتصاÙات بشبÙØ© Tor Ø£Ù Ù
راÙبتÙا بطرÙÙØ© أخرÙØ"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "اختار 'ÙعÙ
' Ø«Ù
'Next' Ø«Ù
ÙÙ
بÙص٠اÙÙbridges اÙجدÙدة Ù٠اÙÙ
ربع. Ø«Ù
اختار 'اتصاÙ' ٠اذا ÙاجÙت٠Ù
Ø´ÙÙØ© اضغط عÙÙ 'Ù
ساعدة' ÙÙ ÙاÙذة اعداد Tor."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "تظÙر Ùذ٠اÙرساÙØ©"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "أطÙب bridges عادÙØ©."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "أطÙب IPv6 bridges"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "أطÙب Pluggable Transport ب٠TYPE"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "اØص٠عÙÙ Ùسخة Ù
Ù Ù
ÙØªØ§Ø GnuPG اÙعاÙ
اÙخاص بÙBridgeDB"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ابÙغ ع٠خطأ باÙبرÙاÙ
ج"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "ÙÙد اÙبرÙاÙ
ج"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "اÙتغÙÙرات"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "اتص٠بÙا"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "ÙÙأس٠ÙÙ
ÙتÙ
اÙعثÙر عÙÙ Ù
ا Ø·Ùبت"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Ùا ÙÙجد Ø£Ù bridges Ù
تاØØ© ØاÙÙا"
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "ربÙ
ا تØتاج Ø¥ÙÙ %s اÙعÙدة %s ٠اختÙار ÙÙع bridge Ù
ختÙÙ"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "خطÙØ© %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "ØÙ
Ù %s Ù
تصÙØ Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "خطÙØ© %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "اØص٠عÙÙ %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "خطÙØ© %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "اÙØ¢Ù %s أض٠اÙÙbridges Ø¥ÙÙ Ù
تصÙØ Tor %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sÙ%sÙØ· أعطÙÙ bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Ø®Ùارات Ù
تÙدÙ
Ø©"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ùا"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "Ùا Ø´ÙØ¡"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÙ%sعÙ
!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sØ£%sص٠عÙÙ Bridges"
diff --git a/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..8c71518
--- /dev/null
+++ b/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,357 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# E <ehuseynzade at gmail.com>, 2014-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2015-01-03 18:01+0000\n"
+"Last-Translator: E <ehuseynzade at gmail.com>\n"
+"Language-Team: Azerbaijani (http://www.transifex.com/projects/p/torproject/language/az/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: az\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Ãzr istÉyirik! TÉlÉbinlÉ baÄlı nÉ isÉ sÉhv oldu."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Bu avtomatik cavabdır, lütfÉn cavab yazma.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Bu da sÉnin körpülÉrin:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "SÉs limitini keçmisÉn. LütfÉn yavaÅla! EmaillÉr arası minimal vaxt\n%s saatdır. Bu zaman kÉsiyindÉ daxil edilÉn diÉr emaillÉr lÉÄv edilÉcÉkdir."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "ÆMRLÆR: (bir neÃ§É seçimi müÉyyÉnlÉÅdirmÉk üçün ÆMRLÆRi birlÉÅdir)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "BridgeDB-É xoÅ gÉlkmisÉn!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Mövcud dÉstÉklÉnÉn nÉqliyyat NÃVlÉri:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Ey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Salam, dost!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "İctimai Açarlar"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Bu email göy qurÅaÄı, tÉbuynuz vÉ qıÄılcımla hazırlanıb\n%s üçün %s tarixdÉ vÉ %s saatda."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB müxtÉlif %s növ Pluggable Transport %s vasitÉsilÉ körpülÉr müÉyyÉn \nedÉ bilÉr, bu sÉnin internet trafikini yoxlamaq istÉyÉn hÉr hansı ÅÉxs üçün \nÉlaqÉlÉrini Tor Network ilÉ kölgÉlÉyÉrÉk sÉnin Tor istifadÉ etdiyini müÉyyÉn \netmÉsinÉ daha çox mane olacaqdır.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Pluggable Transports vasitÉsilÉ mövcud olan IPv6 ünvanlı bir çox \nkörpülÉr IPv6 ilÉ uyÄun deyillÉr.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Bundan ÉlavÉ BridgeDB Pluggable Transports %s olmayan bir sıra darıxdırıcı \nkörpülÉr %s dÉ vardır, amma onlar yenÉ dÉ internet senzurasından \nsovuÅmaÄı bacarırlar.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "KörpülÉr nÉdir?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s KörpülÉr %s Tor-un keçidi olub sÉnÉ senzuradan sovuÅmaÄa kömÉk edir."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "KörpülÉrin ÉldÉ edilmÉsinin alternativ yollarını axtarıram!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "KörpülÉri ÉldÉ etmÉyin baÅqa yolu da %s emailinÉ mÉktub yazmaqdır. LütfÉn, yadında\nsaxla ki, email göndÉrÉn zaman aÅaÄıdakı email tÉmin edicilÉrindÉn birini istifadÉ etmÉlisÉn:\n%s, %s vÉ ya %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "KörpülÉrim iÅlÉrmir! KömÉyÉ ehtiyacım var!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "ÆgÉr Tor iÅlÉmirsÉ, sÉn %s ünvanına yazmalısan."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Yaranan vÉziyyÉt haqqında Étraflı mÉlumat yazmaÄa çalıÅ; körpülÉrin siyahısı vÉ \nistifadÉ etmÉyÉ Ã§alıÅdıÄı Pluggable Transport-un adı, Tor Browser versiyan vÉ \nTor tÉrÉfindÉn aldıÄın hÉr hansı mesaj vÉ s."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "SÉnin körpü sÉtirlÉrin:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Körpü ÆldÉ Et!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "LütfÉn, körpü növlÉri üçün seçimlÉri müÉyyÉnlÉÅdir:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "IPv6 ünvanlara ehtiyacın var?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "%s ehtiyacın var?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "SÉnin brauzerin ÅÉkillÉri göstÉrÉ bilmir."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Yuxarıdakı ÅÉkildÉn iÅarÉlÉri daxil et..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "KörpülÉri necÉ istifadÉ edÉcÉksÉn"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "KörpülÉri Tor Browser-É daxil etmÉk üçün %s Tor Browser-dÉki qaydalara riayÉt edÉrÉk \n%s sÉhifÉsini yüklÉ ki, Tor Browser baÅlasın."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "'Tor ÅÉbÉkÉ ParametrlÉri' dialoqu sıçrayıb açılsa, 'Konfiqurasiya' düymÉsini kliklÉ\nvÉ vizardın dediklÉrinÉ bu sualı verÉnÉ qÉdÉr riayÉt et:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Ä°nternet XidmÉt TÉqdimatçısı (Ä°XT) sÉnin Tor ÅÉbÉkÉsinÉ giriÅini kilidlÉyir vÉ ya \nÉlaqÉlÉrÉ mÉhdudiyyÉtlÉr qoyur?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ÆvvÉl 'BÉli' vÉ daha sonra 'NövbÉti'-ni seç. Yeni körpülÉrini konfiqurasiya \netmÉk üçün körpü sÉtirlÉrini köçür vÉ idxal qutusuna yapıÅdır. Æn son olaraq, \n'ÆlaqÉ Yarat' düymÉsini kliklÉ vÉ mÉncÉ indi davam etmÉk üçün yaxÅı vaxtdır! \nHÉr hansı problemlÉ Ã¼zlÉÅsÉn, dÉstÉk üçün 'Tor ÅÉbÉkÉ ParametrlÉri' vizardında \n'KömÉk' düymÉsini kliklÉ."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Bu mesajı göstÉrir."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Vanil körpü xahiŠet."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPv6 körpü xahiŠet."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "TYPE-a görÉ Pluggable Transport xahiÅ et."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "BridgeDB-nin ictimai GnuPG açarının üzünü ÉldÉ et."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "Problemi XÉbÉr Ver"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "MÉnbÉ ÅifrÉsi"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "DÉyiÅikliklÉr"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "ÆlaqÉ"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Vay dÉdÉm!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Hazırda mümkün körpü yoxdur..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Ãox güman ki, sÉn %s geri qayıdıb %s baÅqa körpü növü seçmÉli idin!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "%s1-ci%s Addım"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "%s Tor Browser %s YüklÉ"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "%s2-ci%s Addım"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s Körpü %s ÆldÉ Et"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "%s3-cü%s Addım"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Ä°ndi %s körpülÉri Tor Browser-nÉ ÉlavÉ et %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sS%sadÉcÉ mÉnÉ körpü ver!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Ætraflı SeçimlÉr"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Xeyr"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "heç biri"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sB%sÉli!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sK%sörpü ÆldÉ Et"
diff --git a/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..2d95c12
--- /dev/null
+++ b/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,357 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# aramaic <aramaicbg at gmail.com>, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2015-01-14 12:40+0000\n"
+"Last-Translator: aramaic <aramaicbg at gmail.com>\n"
+"Language-Team: Bulgarian (http://www.transifex.com/projects/p/torproject/language/bg/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: bg\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Sorry! Something went wrong with your request."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[ÐвÑомаÑиÑно ÑÑобÑение; Ð¼Ð¾Ð»Ñ Ð½Ðµ оÑговаÑÑйÑе.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Това Ñа ваÑиÑе мÑежови bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "ÐÑеÑ
вÑÑлиÑ
Ñе Ð¿Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¸Ñ Ð¸-мейл лимиÑ. ÐÐ¾Ð»Ñ Ð·Ð°Ð±Ð°Ð²ÐµÑе! ÐинимÑмÑÑ Ð¼ÐµÐ¶Ð´Ñ \nи-мейли е %s ÑаÑа. ÐÑиÑки ÑледваÑи и-мейли по вÑеме на Ñози пеÑиод Ñе бÑÐ´Ð°Ñ Ð°Ð½ÑлиÑани."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (комбиниÑай COMMANDs за да опÑÐµÐ´ÐµÐ»Ð¸Ñ Ð¼ÑлÑи опÑии едновÑеменно)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "ÐобÑе доÑли в BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "ÐкÑÑални поддÑÑжани ÑÑанÑпоÑÑ TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Хей, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ÐдÑавей, пÑиÑÑел!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "ÐбÑеÑÑвен клÑÑ"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Този и-мейл беÑе генеÑиÑан Ñ Ð´Ñги, ÑникоÑни, и заÑÑ\nза %s на %s пÑи %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB може да пÑедложи bridges Ñ Ð¼Ð½Ð¾Ð³Ð¾ %stypes на Pluggable Transports%s,\nкоиÑо Ð¼Ð¾Ð³Ð°Ñ Ð´Ð° Ð¿Ð¾Ð¼Ð¾Ð³Ð½Ð°Ñ Ð´Ð° подÑигÑÑÑÑ Ð²Ð°ÑаÑа вÑÑзка кÑм Tor Network, пÑавейки Ñ Ð¿Ð¾\nÑÑÑдна за наблÑдение за вÑеки наблÑÐ´Ð°Ð²Ð°Ñ Ð²Ð°ÑÐ¸Ñ Ð¸Ð½ÑеÑÐ½ÐµÑ ÑÑаÑик да оÑкÑие Ñе ползваÑе Tor\n \n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "ÐÑкой bridges Ñ IPv6 адÑеÑи Ñа ÑÑÑо налиÑни, но пÑез Pluggable\nTransports не Ñа IPv6 ÑÑвмеÑÑим.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "ÐопÑлниÑелно, BridgeDB има много plain-ol'-vanilla bridges %s без никакви\nPluggable Transports %s коиÑо не Ð¸Ð·Ð³Ð»ÐµÐ¶Ð´Ð°Ñ Ñолкова добÑе, но вÑе пак могаÑ\nда Ð¿Ð¾Ð¼Ð¾Ð³Ð½Ð°Ñ Ð·Ð° заобикалÑне на инÑеÑÐ½ÐµÑ ÑензоÑаÑа в много ÑлÑÑаи.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Ðакво Ñа bridges?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s Ñа Tor relays коиÑо Ð¿Ð¾Ð¼Ð°Ð³Ð°Ñ Ð·Ð° заобикалÑне на ÑензÑÑаÑа."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ðмам нÑжда Ð¾Ñ Ð°Ð»ÑеÑнаÑивен ваÑÐ¸Ð°Ð½Ñ Ð·Ð° намиÑане на bridges!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ÐÑÑг ваÑÐ¸Ð°Ð½Ñ Ð·Ð° набавÑне на bridges е пÑаÑане на и-мейл до %s. ÐÐ¾Ð»Ñ Ð¾ÑбележеÑе Ñе вие ÑÑÑбва да\nпÑаÑиÑе и-мейл използвайки адÑÐµÑ Ð½Ð° един Ð¾Ñ ÑледниÑе доÑÑавÑиÑи:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ÐоиÑе bridges не ÑабоÑÑÑ! Ðмам нÑжда Ð¾Ñ Ð¿Ð¾Ð¼Ð¾Ñ!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ðко ваÑÐ¸Ñ Tor не ÑабоÑи, изпÑаÑеÑе и-мейл до %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ÐпиÑайÑе Ñе да вклÑÑиÑе колкоÑо Ñе може повеÑе инÑоÑмаÑÐ¸Ñ Ð·Ð° ваÑÐ¸Ñ ÑлÑÑай, вклÑÑиÑелно ÑпиÑÑк на\nbridges и Pluggable Transports коиÑо ÑÑе използвали, ваÑаÑа Tor Browser веÑÑиÑ,\nи вÑÑко ÑÑобÑение Ñ ÐºÐ¾ÐµÑо Tor е оÑговоÑил, и Ñ.н."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "ТÑÑ Ñа ваÑиÑе bridge вÑÑзки:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ÐземеÑе Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ÐÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑеÑе опÑÐ¸Ñ Ð·Ð° вид bridge:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "ÐÑждаеÑе ли Ñе Ð¾Ñ IPv6 адÑеÑи?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "ÐÑздаеÑе ли Ñе Ð¾Ñ %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "ÐаÑÐ¸Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ показва пÑавилно изобÑажениÑ."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "ÐÑведеÑе ÑимволиÑе Ð¾Ñ Ð¸Ð·Ð¾Ð±ÑажениеÑо Ð¾Ñ Ð³Ð¾Ñе..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Ðак да запоÑнеÑе да използваÑе ваÑиÑе bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Ðа да вÑведеÑе bridges в Tor Browser, ÑледвайÑе инÑÑÑÑкÑииÑе на %s Tor\nBrowser ÑÑÑаниÑаÑа %s за да ÑÑаÑÑиÑаÑе Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "ÐогаÑо 'Tor Network Settings' пÑозоÑÐµÑ Ñе оÑвоÑи, кликнеÑе 'Configure' и ÑледвайÑе\nÑÑвеÑника докаÑо не попиÑа:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ЦензÑÑиÑа ли или блокиÑа вÑÑзкаÑа ви ваÑÐ¸Ñ ÐнÑеÑÐ½ÐµÑ Ð´Ð¾ÑÑавÑик (ISP)\nдо Tor мÑежаÑа?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ÐзбеÑеÑе 'Yes' и Ñлед Ñова кликнеÑе 'Next'. Ðа да конÑигÑÑиÑаÑе ваÑиÑе нови bridges, копиÑайÑе и\nпоÑÑавеÑе bridge линии в пÑозоÑеÑа за ÑекÑÑово вÑвеждане. Ðа кÑÐ°Ñ Ð½Ð°ÑиÑнеÑе 'Connect', и\nÑÑÑбва да ÑÑе гоÑови! Ðко ÑÑеÑаÑе ÑÑÑдноÑÑи, оÑидеÑе на 'Help'\nбÑÑона в 'Tor Network Settings' ÑÑвеÑника за помоÑ."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Ðокажи ÑÑобÑениеÑо."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "ÐзиÑквай vanilla bridges."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "ÐзиÑквай IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "ÐзиÑквай Pluggable Transport по TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Ðземи копие на BridgeDB's обÑеÑÑвен GnuPG клÑÑ."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ÐнÑоÑмиÑай за пÑоблем"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Source код"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Ðневник на пÑомениÑе"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "ÐонÑакÑ"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Ðоо, ÑпагеÑки!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "РмоменÑа нÑма налиÑни bridges ..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Ðай веÑоÑÑно ÑÑÑбва да %s Ñе вÑÑнеÑе %s и избеÑеÑе ÑазлиÑенt bridge Ñип!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "СÑÑпка %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Свали %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "СÑÑпка %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Ðземи %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "СÑÑпка %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Сега %sдобавеÑе bridges кÑм Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust дай ми bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "ÐпÑии за напÑеднали"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ðе"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ниÑо"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÐ%sа!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sÐ%sземи Bridges"
diff --git a/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..1413a31
--- /dev/null
+++ b/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,386 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Albert <provisionalib at hotmail.com>, 2013
+# Assumpta Anglada <assumptaanglada at gmail.com>, 2014
+# Eloi GarcÃa i Fargas, 2014
+# Humbert <humbert.costas at gmail.com>, 2014
+# isis <isis at torproject.org>, 2015
+# laia_ <laiaadorio at gmail.com>, 2014-2015
+# Toni Hermoso Pulido <toniher at softcatala.cat>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-20 19:43+0000\n"
+"Last-Translator: isis <isis at torproject.org>\n"
+"Language-Team: Catalan (http://www.transifex.com/projects/p/torproject/language/ca/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ca\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Perdoni! Quelcom ha anat malament amb la seva petició."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Aquest es un missatge automà tic; si us plau, no respongui.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Els vostres ponts: "
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Has superat el lÃmit. Siusplau baixa el ritme! El mÃnim de temps entre\nels correus és de %s hores. La resta de correus durant aquest perÃode de temps seran ignorats."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combina COMMANDs per especificar les múltiples opcions simultà niament)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Benvingut a BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Actualment suportant transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Ei, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hola, amic!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Claus Públiques"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Aquest correu va ser creat amb unicorns, arcs de Sant Martà i espurnes\nper %s a %s en %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB pot proveïr ponts amb diversos %stypes de Pluggable Transports%s,\nque pot provocar ofuscacions a la teva connexió a Tor Network, fent-ho més difÃcil per a qualsevol veient el teu trà fic de connexió a internet de determinar que està s\nusant Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "També hi ha alguns ponts amb adreces IPv6, tot i que alguns Pluggable\nTransports no són compatibles amb IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "A més, BridgeDB té molts ponts %s està ndard sense \nPluggable Transports %s, que potser no són tan interessants, però que també\npoden ajudar a esquivar la censura d'internet, en molts casos.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Què són els ponts?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Ponts %s són mecanismes Tor que ajuden a esquivar la censura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Necessito una manera alternativa d'obtenir ponts"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Una altra manera d'aconseguir ponts és enviar un correu electrònic a %s. Tingueu en compte que heu\nd'enviar el correu amb una adreça d'un dels següents proveïdors de correu:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "No em funcionen els ponts! Ajuda!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Si el teu Tor no funciona, hauries d'enviar un correu a %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Incloeu el mà xim d'informació sobre el vostre cas, inclosa la llista de\nponts i de Pluggable Transports que heu intentat utilizar, la vostra versió de Tor Browser,\ni qualsevol missatge que Tor hagi donat, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Les vostres lÃnies de ponts:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Descarrega ponts!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Si us plau, sel·leccioni les opcions pel tipus de pont:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Necessites adreces IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Necessites un %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "El seu navegador no està mostrant les imatges correctament."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Introdueixi els carà cters de la imatge superior..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Per començar a utilitzar els ponts"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Per introduir ponts al Tor Browser, seguiu les instruccions de la pà gina\nde baixada %s del %s Tor Browser per iniciar el Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Quan aparegui el dià leg 'Configuració Xarxa Tor', clica 'Configura' i segueix\nl'auxiliar fins que pregunti:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "El vostre Proveïdor d'Internet (ISP) bloqueja o censura les connexions\namb la xarxa Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Seleccioneu 'SÃ' i després cliqueu 'Següent'. Per configurar els vostres ponts nous, copieu i\nenganxeu les lÃnies del pont al següent quadre d'entrada de text. Finalment, cliqueu 'Connectar', i ja hauria d'estar a punt! Si teniu problemes, intenteu clicar el botó \nd''Ajuda' a l'auxiliar de 'Configuració Xarxa Tor' per més assistència."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Mostra aquest missatge."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Demanar ponts està ndard."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Demanar ponts IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Demanar un Pluggable Transport per TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Fer una còpia de la clau pública GnuPG del BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Comunicar un Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Codi font"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Canviar el log"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contacte"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Selecciona-ho tot"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Mostra el codi QR"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Codi QR per les lÃnies de ponts"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ueeeee, espaguetiiiiis!!!!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Hi ha hagut un error en obtenir el codi QR"
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Aquest codi QR conté les lÃnies de pont. Escannegeu-lo amb un lector de codis QR per copiar les lÃnies de pont al mòbil i a altres dispositius. "
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "No hi han ponts disponibles en aquests moments..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Potser hauries de provar %s tirant enrer %s i triant un tipus diferent de pont!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Pas %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Descarrega %s Navegador Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Pas %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Descarrega %s ponts %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Pas %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Ara %s adjunta els ponts al Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust donem els meus bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opcions Avançades"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "Cap"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..f76cd6b
--- /dev/null
+++ b/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,363 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# A5h8d0wf0x <littleslyfoxie28 at gmail.com>, 2014
+# Adam Slovacek <adamslovacek at gmail.com>, 2013
+# Elisa <valhalla at gishpuppy.com>, 2011
+# Sanky <gsanky+transifex at gmail.com>, 2011
+# JiÅÃ VÃrava <appukonrad at gmail.com>, 2014
+# mxsedlacek, 2014
+# Radek Bensch <inactive+Radog at transifex.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: A5h8d0wf0x <littleslyfoxie28 at gmail.com>\n"
+"Language-Team: Czech (http://www.transifex.com/projects/p/torproject/language/cs/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "PromiÅte! NÄco se stalo Å¡patnÄ s vaÅ¡Ãm požadavkem."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Toto je automatická zpráva; prosÃm, neodpovÃdejte na ni]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Zde jsou vaše mosty:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "PÅekroÄil jste meznà limit. ProsÃme zpomalte! Minimálnà Äas nezi emaily je %s hodin. VÅ¡echny následujÃcà emaily bÄhem této Äasové doby budou ignorovány."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "PÅÃKAZY: (kombinujte pÅÃkazy pro zadávánà vÃce možnostà souÄasnÄ)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "VÃtejte v BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "V souÄasnosti podporované transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Ahoj, pÅÃteli!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "VeÅejné klÃÄe"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Tento email byl generován s duhou, jednorožci a jiskÅiÄkami\npro %s data %s v %s"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB může nabÃdnou mosty s nÄkolika %stypes of Pluggable Transports%s,\ncož může pomoci zmást vaÅ¡e pÅipojenà k Tor Network, a udÄlat složitÄjÅ¡Ã pro kohokoli\nsledovat vaÅ¡i internetovou aktivitu k urÄenà zda použÃváte Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "NÄkteté mosty s IPv6 adresami jsou také k dispozici, pÅestože nÄkteré Pluggable Transports\nnejsou kompaktibilnà s IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "DodateÄnÄ, BridgeDB má spustu nudných vanilkových mostů %s bez žádných \nPluggable Transports %s což sice nemusà znÃt tak super, ale úpoÅád mohou\npomoci obejÃt internetovou cenzuru v mnoha pÅÃpadech.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Co jsou mosty?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Mosty %s jsou Tor pÅenosy které vám pomáhajà obejÃt cenzuru."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Chci mosty zjistit jiným způsobem!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "DalÅ¡Ã způsob jak dostat mosty je poslat email na %s. ProsÃme mÄjte na pamÄti že musÃte poslat email pouze z adresy od následujÃcÃch poskytovatelů emailu:\n%s, %s nebo %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mé mosty nefungujÃ. PotÅebuji pomoc!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Pokud váš Tor nepracuje mÄl by jste nám poslat email %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Pokuste se popsat co nejvÃce informacà o vaÅ¡em pÅÃpadu, vÄetnÄ listu\nmostů a pluggable Transports které jste se pokusili použÃt, vaÅ¡i verzi Tor Browser,\na jaké Tor poslal ven, atd."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Zde jsou vaše linky k mostům: "
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ZÃskejte Mosty!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ProsÃm vyberte nastavenà pro typ mostů:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "PotÅebujete IPv6 adresu?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "PotÅebujete %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Váš prohlÞeÄ nezobrazuje obrázky správnÄ."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Vložte pÃsmena z obrázku výše..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Jak zaÄÃt použÃvat mosty."
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "K vloženà mostů do Tor Browser, postupujte podle instrukcà na %s Tor\nBrowser stránce ke staženà %s ke spuÅ¡tÄnà Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Když dialog 'Tor Nastavenà SÃtÄ' vyskoÄà kliknÄte na 'Konfigurovat' a postupujte\npodle průvodce dokud nedostanete otázku:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokuje nebo cenzoruje váš Poskytovatel Služeb Internetu (ISP) pÅipojenà k\nTor sÃti?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Vyberte 'Ano' a poté kliknÄte 'DalÅ¡Ã'. Pro konfiguraci nových mostů kopÃrujte a\nvložte linky k mostům do textového pole pro vloženÃ. Nakonec kliknÄte na 'PÅipojit' a\nmÄlo by to být vÅ¡echno! Pokud narazÃte na problémy, zkuste kliknout na tlaÄÃtko 'Pomoc'\nv 'Tor Nastavenà SÃtÄ' průvodce pro dalÅ¡Ã pomoc."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Zobrazà tuto zprávu."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Požadovat vanilla mosty."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Požadovat IPv6 mosty."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Požadovat Pluggable Transport od TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ZÃskat kopii BridgeDS's veÅejného GnuPg klÃÄe. "
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "Nahlásit chybu"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Zdrojový kód"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Seznam zmÄn"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "Kontakt"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "JežÃÅ¡ku na kÅÞku!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "V souÄasnosti zde nejsou žádné mosty k dispozici..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Možná by jste mÄl zkusit %s jÃt zpÄt %s a vybrat jiný typ mostů!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Krok %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Stáhnout %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Krok %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ZÃskej %s mosty %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Krok %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Nynà %s pÅidejte mosty do Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sen mi dejte mosty!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "RozÅ¡ÃÅené možnosti"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ne"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "žádné"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sA%sno!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sN%sastavit Bridges"
diff --git a/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..b209bf3
--- /dev/null
+++ b/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,102 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# huwwaters <huwwaters at gmail.com>, 2014
+# littlegreykida <theinfinitygap at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2014-03-26 08:20+0000\n"
+"Last-Translator: littlegreykida <theinfinitygap at gmail.com>\n"
+"Language-Team: Welsh (http://www.transifex.com/projects/p/torproject/language/cy/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: cy\n"
+"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "Beth yw pontydd?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s Pontydd gyfnewid %s yw'r gyfnewidiau Tor sy'n helpu chithau i osgoi sensoriaeth."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Rwyf yn angen ffordd arall i cael gafael ar pontydd!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Ffordd arall i canfod cyfeiriadau pontydd cyhoeddys yw i anfon e-bost (o %s neu gyfeiriad %s) i %s gyda'r llinell 'get bridges' ar ei ben ei hyn o fewn corff yr e-bost."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "Nid yw fy mhontydd yn gweithio! Rwyf yn angen help!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Os nad yw'ch Tor yn gweithio, dylech e-bostio %s. Ceisiwch cynnwys cymaint o gwybodaith ag y bo modd, yn cynnwys yr rhestr o pontydd rydych wedi defnyddio, enw'r pecyn a defnyddwyd, yr negeseuon a rhoddwyd allan gan Tor, ayyb."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "I defnyddio'r llinellau uwchben, cer i tydalen osodiadau Vidalia's Network, ac pwyswch \"Mae fy ISP yn wahardd cysylltiadau i'r rhwydwaith Tor.\" Wedyn, ychwanegwch pob gyfeiriad pont, un ar amser."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "Nid oes pontydd ar gael"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Diweddarwch eich porwr i Firefox"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Teipiwch y ddau air"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Cam 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Cael gafael ar y %s Pecyn Porwr Tor %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Cam 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Cael gafael ar %s pontydd %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Cam 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Nawr %s ychwanegwch y pontydd i Tor %s"
diff --git a/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..c647db3
--- /dev/null
+++ b/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,384 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Christian Villum <villum at autofunk.dk>, 2014-2015
+# David Nielsen <gnomeuser at gmail.com>, 2014
+# OliverMller <theoliver at live.co.uk>, 2011
+# Thomas Pryds <thomas at pryds.eu>, 2014
+# Tore Bjørnson <tore.bjornson at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-23 06:50+0000\n"
+"Last-Translator: Christian Villum <villum at autofunk.dk>\n"
+"Language-Team: Danish (http://www.transifex.com/projects/p/torproject/language/da/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: da\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Beklager, noget gik galt med din anmodning."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Dette er en automatisk meddelelse; Svar venligst ikke.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Her er dine broer:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Du har overskredet ratebegrænsningen. Sænk venligst hastigheden! Den minimale tid mellem\nemails er %s timer. Yderligere emails sendt i denne period vil blive ignoreret."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (Kombiner COMMANDs for at specifierer adskillige valgmuligheder på en gang)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Velkommen til BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Transport TYPEs som er understøttet i øjeblikket:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hej %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hej min ven!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Offentlige nøgler"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Denne email til %s blev skabt ved hjælp af regnbuer, enhjørninger og glitter %s klokken %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB kan formidle broer med adskillige typer %sPluggable Transports%s som kan hjælpe med at sløre dine forbindelser til Tor netværket, og dermed gøre det vanskeligere for nogen som kan se din internet trafik at bestemme at du bruger Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Nogle broer med IPv6 adresser er også tilgængelige, men ikke alle Pluggable\nTransports understøtter IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "BridgeDB har ydermere masser af konventionelle broer %s uden nogen Pluggable Transports %s hvilket måske ikke lyder så smart, men de kan stadigvæk hjælpe med at omgå internet censur i mange tilfælde.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Hvad er broer?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s broer %s er Tor relæer som hjælper dig med at omgå censur."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Jeg har brug for en alternativ metode til at få broer på!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "En anden måde at finde broer er at sende en email til %s. Bemærk venligst at du skal\nsende mailen fra en konto hos en af de følgende email-udbydere:\n%s, %s eller %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mine broer virker ikke! Jeg har brug for hjælp!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Hvis Tor ikke virker for dig kan du sende en email til %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Prøv at inkluderer så meget information om din sag som muligt, der i blandt: En liste af broer og Pluggable Transports du har prøvet at bruge, din Tor Browser version, hvilke beskeder Tor gav, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Her er dine bro linjer:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Find broer!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Vælg venligst brotype muligheder:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Har du brug for IPv6 adresser?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Har du brug for en %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Din browser kan ikke vise billeder korrekt."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Indtast tegnene fra billedet ovenfor..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "SÃ¥dan starter du med at bruge dine broer"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "For at tilføje broer til Tor Browseren, følg instruktionerne på %s Tor\nBrowserens hjemmeside %s for at starte Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Når 'Tor netværksindstillinger' vinduet popper op, klik 'Indstil' og følg guiden til den siger:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokerer eller på anden vis censurerer din internetudbyder forbindelser\ntil Tor netværket?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Vælg 'Ja' og klik så på 'Næste'. For at konfigurere dine nye broer, kopier og indsæt brolinierne i den næste input boks. Klik til sidst 'Forbind', og så er du klar! Hvis du støder på problemer, så prøv at klikke på 'Hjælp'-knappen i 'Tor Netværksindstillinger'-guiden for at få yderligere hjælp."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Vis denne besked."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Anmod almindelige broer."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Amod IPv6 broer."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Anmod om Pluggable Transport efter TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Hent en kopi af BridgeDB's offentlige GnuPG nøgle."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Rapporter en fejl"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Kildekode"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Ãndringer"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Marker alle"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Vis QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode for dine brolinier."
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ups, vi står i lort til halsen"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Det lader til at der opstod en fejl ved hentningen af din QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Denne QRCode indeholder dine brolinier. Scan den med en QRCode-læser for at kopiere dine brolinier over til mobile og andre enheder."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Der er i øjeblikket ingen tilgængelige broer..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Du kunne måse prøve %s at gå tilbage %s og vælge en anden brotype!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Trin %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Hent %s Tor Browseren %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Trin %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "FÃ¥ %s broer %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Trin %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Tilføj nu %s broer til Tor Browseren %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sB%sare giv mig bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Advancerede indstillinger"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nej"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ingen"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sH%sent Bridges"
diff --git a/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..163b56c
--- /dev/null
+++ b/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,389 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# trantor <clucko3 at gmail.com>, 2014
+# Ettore Atalan <atalanttore at googlemail.com>, 2014
+# unknwon_anonymous <jackjohnson0001 at yahoo.com>, 2014
+# konstibae <konstibae at gmail.com>, 2014
+# Locke <locke at dena-design.de>, 2011
+# qbi <kubieziel at googlemail.com>, 2015
+# Sebastian <sebix+transifex at sebix.at>, 2015
+# debakel <spam1 at mrtz.me>, 2014
+# Tobias Bannert <tobannert at gmail.com>, 2013
+# Tobias Bannert <tobannert at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-04 18:22+0000\n"
+"Last-Translator: qbi <kubieziel at googlemail.com>\n"
+"Language-Team: German (http://www.transifex.com/projects/p/torproject/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Wir bitten um Entschuldigung. Bei Ihrer Anfrage lief etwas fehl."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Das ist eine automatische Nachricht. Bitte antworten Sie nicht darauf.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Hier sind Ihre Brücken-Server (Bridges):"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Sie haben den Grenzwert überschritten. Bitte verlangsamen! Der kleinste Abstand zwischen \nden E-Mails beträgt %s Stunden. Alle weiteren E-Mails, während dieser Zeit, werden ignoriert."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "Befehle: (Befehle kombinieren, um mehrere Optionen gleichzeitig anzugeben)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Willkommen bei der Brückendatenbank!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Aktuell unterstützte Transporttypen:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hallo, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hallo Freund!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Ãffentliche Schlüssel"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Diese E-Mail wurde für %s am %s um %s mit Regenbogen, Einhörnern und Glitzer erstellt."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "Die Brückendatenbank kann Brücken-Server mit verschiedenen %stypen der Pluggable Transports%s bereitstellen. Diese können helfen, Ihre Verbindungen zum Tor-Netzwerk zu verschleiern. Damit wird es für Dritte schwieriger festzustellen, dass sie Tor benutzen.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Einige Brücken mit IPv6-Adressen sind ebenfalls vorhanden, auch wenn einige \nsteckbare Transporte nicht IPv6-kompatibel sind.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Zusätzlich hat die Brückendatenbank viele erkömmliche Brücken-Server %s ohnePluggable Transports %s. Das klingt vielleicht weniger toll. Es hilft Ihnen dennoch, in vielen Fällen die Internetzensur zu umgehen.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Was sind Brücken?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Brücken-Server %s sind Tor-Relais, die Ihnen helfen die Zensur zu umgehen."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ich brauche einen anderen Weg, um Adressen von Brücken-Servern zu erhalten."
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Sie können auch eine E-Mail an %s schicken, um Adressen von Brücken-Servern zu erhalten. Die E-Mail muss von einem der folgenden E-Mail-Anbieter geschickt werden: %s, %s oder %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Meine Brücken-Server funktionieren nicht! Ich brauche Hilfe!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Sollte Tor bei Ihnen nicht funktionieren, senden Sie bitte eine E-Mail an %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Versuchen Sie in Ihrer E-Mail möglichst viele Informationen über Ihr Problem einzubeziehen, \neinschlieÃlich einer Liste der Brücken-Server und der Pluggable Transports, die Sie versuchten zu benutzen, \nIhre Tor-Browser-Version, und jegliche Meldungen, die Tor gegeben hat, usw."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Hier sind Ihre Brückenadressen:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Erhalten Sie Adressen von Brücken-Servern!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Bitte wählen Sie die Optionen für den Brückentyp aus:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Benötigen Sie IPv6-Adressen?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Benötigen Sie eine %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Bilder werden in Ihrem Browser nicht korrekt dargestellt."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Bitte geben Sie die Zeichen aus dem oberen Bild ein."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "So starten Sie die Benutzung Ihrer Brücken"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Um Brücken zum Tor-Browser hinzuzufügen, bitte der Anleitung auf der Seite %s, zum Herunterladen des %s Tor-Browsers folgen, um den Tor-Browser zu starten."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Wenn der Dialog mit den »Tor-Netzwerkeinstellungen« erscheint, \nklicken Sie auf »Konfigurieren« und folgen den Anweisungen des Assistenten: "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blockiert oder zensiert Ihr Internetanbieter (ISP) die Verbindungen zum Tor-Netzwerk?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Wählen Sie »Ja« und klicken dann auf »Weiter«. Um Ihre neuen Brücken zu konfigurieren, kopieren Sie die \nZeilen mit den Adressen der Brücken und fügen sie in das Texteingabefeld ein. Zum Schluss klicken Sie auf \n»Verbinden« und Sie können loslegen! Falls Sie Schwierigkeiten haben, \nversuchen Sie bitte auf den »Hilfe«-Knopf im Assistenten der »Tor- Netzwerkeinstellungen« zu klicken, \num weitere Hilfe zu erhalten."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Diese Nachricht anzeigen."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Standard-Brücken anfordern."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPv6-Brücken anfordern."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Einen Pluggable Transport des TYPs anfordern."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Eine Kopie des öffentlichen GnuPG-Schlüssels der Brückendatenbank erhalten."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Einen Fehler melden"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Quellcode"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Ãnderungsprotokoll"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Alles auswählen"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "QRCode anzeigen"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode für Ihre Brückenverbindungen"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Hoppla, Spaghetti!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Es gab vermutlich einen Fehler bei Beschaffen Ihres QR-Codes."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Dieser QR-Code beinhaltet Ihre Brückenverbindungen. Scannen Sie ihn mit einem QR-Code-Leser um ihre Brückenverbindungen auf ihre mobile und andere Geräte zu kopieren."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Im Moment sind keine Brücken verfügbar â¦"
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Möglicherweise sollten Sie versuchen %s zurück zu gehen %s und einen anderen Brückentyp auszuwählen!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Schritt %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "%s Tor Browser %s herunterladen"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Schritt %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s Brücken %s erhalten"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Schritt %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Jetzt %s bitte die Brücken zum Tor-Browser hinzufügen %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sM%sir nur Brücken geben!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Erweiterte Optionen"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nein"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "keine"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sB%srücken erhalten"
diff --git a/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..bbbbe92
--- /dev/null
+++ b/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,364 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Adrian Pappas <pappasadrian at gmail.com>, 2014
+# andromeas <andromeas at hotmail.com>, 2014
+# oahanx, 2014
+# isv31 <ix4svs at gmail.com>, 2014
+# kotkotkot <kotakota at gmail.com>, 2013
+# kotkotkot <kotakota at gmail.com>, 2012
+# mitzie <zacharias.mitzelos at gmail.com>, 2013
+# Wasilis Mandratzis <inactive+Wasilis at transifex.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: oahanx\n"
+"Language-Team: Greek (http://www.transifex.com/projects/p/torproject/language/el/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: el\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "ΣÏ
γγνÏμη! ÎάÏι Ïήγε ÏÏÏαβά με Ïο αίÏημα ÏαÏ."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[ÎÏ
ÏÏ ÎµÎ¯Î½Î±Î¹ Îνα αÏ
ÏομαÏοÏοιημενο μήνÏ
μα, ÏαÏακαλοÏμε μην αÏανÏήÏεÏε]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "ÎÎ´Ï ÎµÎ¯Î½Î±Î¹ οι γÎÏÏ
ÏÎµÏ ÏαÏ:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "ÎεÏÎÏαÏÎµÏ Ïο ÏÏιο. ΠαÏακαλÏ, ÏÎ¹Ï Î±Ïγά! ΠελάÏιÏÏÎ¿Ï ÏÏÏÎ½Î¿Ï Î¼ÎµÏÎ±Î¾Ï Î´Î¹Î±Î´Î¿ÏικÏν email\n είναι %s ÏÏεÏ. Îλα Ïα ενδιάμεÏα email Ïε αÏ
ÏÏ Ïο ÏÏÎ¿Î½Î¹ÎºÏ Î´Î¹Î¬ÏÏημα θα αγνοοÏνÏαι."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "ÎÎΤÎÎÎΣ: (ÏÏ
νδÏ
άÏÏε ÎÎΤÎÎÎΣ για να εÏιλÎξεÏε ÏολλαÏλÎÏ ÏαÏαμÎÏÏοÏ
Ï ÏαÏ
ÏÏÏÏονα)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "ÎαλÏÏ Î®ÏθαÏε ÏÏο BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Î¥ÏοÏÏηÏιζÏμενα transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Îειά ÏοÏ
, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Îεια ÏοÏ
, Ïίλε!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "ÎημÏÏια Îλειδιά"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "ÎÏ
ÏÏ Ïο email ÏαÏήÏθη με αγάÏη, οÏ
Ïάνια ÏÏξα και ÏÏÏ
ÏÏÏκονη\nγια Ïον/Ïην %s, %s ÏÏÎ¹Ï %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "Î BridgeDB μÏοÏεί να ÏαÏÎÏει γÎÏÏ
ÏÎµÏ Î¼Îµ διάÏοÏοÏ
Ï %sÏÏÏοÏ
Ï Pluggable Transports%s,\nÏα οÏοία μÏοÏοÏν να κÏÏÏοÏ
ν ÏÎ¹Ï ÏÏ
νδÎÏÎµÎ¹Ï ÏÎ±Ï ÏÏο Tor Network, κάνονÏÎ¬Ï Ïο δÏ
ÏκολÏÏεÏο για κάÏοιον ÏοÏ
ÏαÏακολοÏ
θεί Ïη δικÏÏ
ακή δÏαÏÏηÏιÏÏηÏά ÏÎ±Ï Î½Î± καÏαλάβει ÏÏÏ ÏÏηÏιμοÏοιείÏε Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Î¥ÏάÏÏοÏ
ν γÎÏÏ
ÏÎµÏ Î¼Îµ διεÏ
θÏνÏÎµÎ¹Ï IPv6 addresses, αλλά κάÏοια Pluggable\nTransports δεν είναι ÏÏ
μβαÏά με IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "ÎÏίÏηÏ, η BridgeDB ÎÏει ÏολλÎÏ ÏαλιÎÏ ÎºÎ±Î»ÎÏ Î³ÎÏÏ
ÏÎµÏ %s ÏÏÏίÏ\nPluggable Transports %s ÏοÏ
ίÏÏÏ Î´ÎµÎ½ ακοÏγεÏαι καÏαÏληκÏικÏ, αλλά Ïε ÏολλÎÏ ÏεÏιÏÏÏÏÎµÎ¹Ï Î¼ÏοÏοÏν να ÏÎ±Ï Î²Î¿Î·Î¸Î®ÏοÏ
ν να ÏαÏακάμÏεÏε Ïη λογοκÏιÏία.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Τι είναι οι γÎÏÏ
ÏεÏ;"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "Îι %s ÎÎÏÏ
ÏÎµÏ %s είναι Tor αναμεÏαδÏÏÎµÏ ÏοÏ
βοηθοÏν ÏÏην ÏαÏάκαμÏη ÏÎ·Ï Î»Î¿Î³Î¿ÎºÏιÏίαÏ. "
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "ΧÏειάζομαι Îναν εναλλακÏÎ¹ÎºÏ ÏÏÏÏο για ÏÏηÏιμοÏοιηÏη γÎÏÏ
ÏÏν! "
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ÎναλλακÏικά μÏοÏείÏε να λάβεÏε γÎÏÏ
ÏÎµÏ ÏÏελνονÏÎ±Ï email ÏÏο %s. Î ÏÎÏει να ÏÏείλεÏε email ÏÏηÏιμοÏοιÏνÏÎ±Ï Î¼Î¯Î± διεÏ
θÏ
νÏη email αÏÏ Îναν αÏÏ ÏοÏ
Ï ÏαÏακάÏÏ ÏάÏοÏοÏ
Ï:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Îι γÎÏÏ
ÏÎµÏ Î¼Î¿Ï
δεν λειÏοÏ
ÏγοÏν! ΧÏειάζομαι βοήθεια! "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Îν Ïο Tor ÏÎ±Ï Î´Îµ δοÏ
λεÏει, ÏαÏÎ±ÎºÎ±Î»Ï ÏÏείλÏε email ÏÏο %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ΠαÏÎ±ÎºÎ±Î»Ï Î´ÏÏÏε Î¼Î±Ï ÏÏο Ïο δÏ
ναÏÏν ÏεÏιÏÏÏÏεÏÎµÏ ÏληÏοÏοÏÎ¯ÎµÏ Î³Î¹Î± Ïο ÏÏάλμα ÏοÏ
ÏÏ
νανÏήÏαÏε, ÏÏÏÏ Ïη λίÏÏα γεÏÏ
ÏÏν και Ïα Pluggable Transports ÏοÏ
ÏÏοÏÏαθήÏεÏε να ÏÏηÏιμοÏοιήÏεÏε, Ïην ÎκδοÏη ÏοÏ
Tor Browser, Ï,Ïι μηνÏμαÏα Îδειξε Ïο Tor κÏλ."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "ÎÎ´Î¿Ï Î¿Î¹ γÏαμμÎÏ Î³Î¹Î± ÏÎ¹Ï Î³ÎÏÏ
ÏÎÏ ÏαÏ:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ÎήÏη ÎεÏÏ
ÏÏν!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎξÏε ÏÎ¹Ï ÎµÏιλογÎÏ Î³Î¹Î± Ïον ÏÏÏο ÏÎ·Ï Î³ÎÏÏ
ÏαÏ:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "ΧÏειάζεÏαι μια διεÏθÏ
νÏη IPv6;"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "ÎήÏÏÏ ÏÏειάζεÏÏε Îνα %s;"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Î ÏÏ
λλομεÏÏηÏÎ®Ï ÏÎ±Ï Î´ÎµÎ½ εμÏανίζει ÏÎ¹Ï ÎµÎ¹ÎºÏÎ½ÎµÏ ÏÏÏÏά."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "ÎιÏάγεÏε ÏοÏ
Ï ÏαÏακÏήÏÎµÏ Î±ÏÏ Ïην ÏαÏαÏÎ¬Î½Ï ÏÏÏογÏαÏία..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Î ÏÏ Î½Î± αÏÏίÏεÏε να ÏÏηÏιμοÏοιείÏε ÏÎ¹Ï Î³ÎÏÏ
ÏÎµÏ ÏαÏ"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Îια να ÏÏηÏιμοÏοιήÏεÏε γÎÏÏ
ÏÎµÏ ÏÏο Tor Browser, ακολοÏ
θήÏÏε ÏÎ¹Ï Î¿Î´Î·Î³Î¯ÎµÏ ÏÏη %s Ïελίδα download ÏοÏ
Tor Browser %s για να ξεκινήÏεÏε Ïο Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "ÎÏαν ÏÏάÏεÏε ÏÏο διάλογο \"ΡÏ
θμίÏÎµÎ¹Ï Î´Î¹ÎºÏÏοÏ
ÏοÏ
Tor\" , εÏιλÎξÏε \"ΡÏθμιÏη\" και ακολοÏ
θήÏÏε ÏοÏ
Ï Î´Î¹Î±Î»ÏγοÏ
Ï Î¼ÎÏÏι να ÏÎ±Ï ÏÏÏήÏει:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ÎνÏÏίζεÏε αν ο ÏάÏοÏÎ¿Ï (ISP) ÏÎ±Ï Î¼ÏλοκάÏει ή με οÏοιοδήÏοÏε ÏÏÏÏο ελÎγÏει ÏÏ
νδÎÏειÏ\nÏÏο δίκÏÏ
ο Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ÎÏιλÎξÏε \"Îαι\" και μεÏά ÏαÏήÏÏε \"ÎÏÏμενο\" Îια να ÏÏ
θμίÏεÏε ÏÎ¹Ï Î½ÎÎµÏ ÏÎ±Ï Î³ÎÏÏ
ÏεÏ, ανÏιγÏάÏÏε ÏÎ¹Ï Î³ÏαμμÎÏ Î¼Îµ ÏÎ¹Ï Î´Î¹ÎµÏ
θÏνÏÎµÎ¹Ï ÏÏν γεÏÏ
ÏÏν ÏÏο κοÏ
Ïί κειμÎνοÏ
. ÎεÏά ÏαÏήÏÏε \"ΣÏνδεÏη\" και ÏÏÎÏει να είÏÏε ενÏάξει! Îν ανÏιμεÏÏÏίÏεÏε ÏÏοβλήμαÏα, εÏιλÎξÏε Ïο κοÏ
μÏί \"Îοήθεια\" ÏÏÎ¹Ï \"ΡÏ
θμίÏÎµÎ¹Ï Î´Î¹ÎºÏÏοÏ
ÏοÏ
Tor\"."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "ÎμÏανίζει αÏ
ÏÏ Ïο μήνÏ
μα."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "ÎίÏηÏη γεÏÏ
ÏÏν βανίλια."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "ÎίÏηÏη IPv6 γεÏÏ
ÏÏν."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "ÎηÏήÏÏε Îνα Pluggable Transport βάÏει TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ÎάβεÏε Îνα ανÏίγÏαÏο ÏοÏ
δημοÏίοÏ
GnuPG ÎºÎ»ÎµÎ¹Î´Î¹Î¿Ï ÏοÏ
BridgeDB."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ÎναÏοÏά Î ÏοβλήμαÏοÏ"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Î Î·Î³Î±Î¯Î¿Ï ÎÏδικαÏ"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "ÎÏÏείο καÏαγÏαÏÎ®Ï Î±Î»Î»Î±Î³Ïν"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "ÎÏαÏή"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "ÎÏ
ÏÏ, κάÏι Ïήγε ÏÏÏαβά!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Îεν Ï
ÏάÏÏοÏ
ν καθÏλοÏ
διαθÎÏÎ¹Î¼ÎµÏ Î³ÎÏÏ
ÏεÏ..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "ÎοκιμάÏÏε να %s ÏάÏε ÏίÏÏ %s και να εÏιλÎξεÏε διαÏοÏεÏÎ¹ÎºÏ ÏÏÏο γÎÏÏ
ÏαÏ!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Îήμα %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "ÎαÏεβάÏÏε %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Îήμα %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ΠαÏÏε ÏÎ¹Ï %s γεÏÏ
ÏÎµÏ %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Îήμα %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "ΤÏÏα %s ÏÏοÏθÎÏÏε ÏÎ¹Ï Î³ÎÏÏ
ÏÎµÏ ÏÏο Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sÎ%sÏλÏÏ Î´ÏÏε μοÏ
γÎÏÏ
ÏεÏ!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "ÎÏιλογÎÏ Î³Î¹Î± ÏÏοÏÏÏημÎνοÏ
Ï"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "ÎÏι"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ÏίÏοÏα"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÎ%sαι!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sÎ%sήÏη ÎεÏÏ
ÏÏν"
diff --git a/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..9248ec3
--- /dev/null
+++ b/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,433 @@
+# English translations for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+# Isis Lovecruft <isis at torproject.org>, 2015.
+#
+#, fuzzy
+#
+# Translators:
+# runasand <runa.sandvik at gmail.com>, 2011
+# Isis Lovecruft <isis at torproject.org>, 2012-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: bridgedb 0.2.4-234-g193c80a-dirty\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'
+"POT-Creation-Date: 2015-03-19 22:13+0000\n"
+"PO-Revision-Date: 2015-03-20 04:13+0000\n"
+"Last-Translator: Isis Lovecruft <isis at torproject.org>\n"
+"Language-Team: English (http://www.transifex.com/projects/p/torproject/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:107
+msgid "Sorry! Something went wrong with your request."
+msgstr "Sorry! Something went wrong with your request."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[This is an automated message; please do not reply.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Here are your bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be "
+"ignored."
+msgstr ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be "
+"ignored."
+
+#: lib/bridgedb/strings.py:25
+msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Welcome to BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Currently supported transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hello, friend!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Public Keys"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are"
+"\n"
+"using Tor.\n"
+"\n"
+msgstr ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are"
+"\n"
+"using Tor.\n"
+"\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
+"\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
+"\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
+"\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
+"\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "What are bridges?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "I need an alternative way of getting bridges!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you "
+"must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr ""
+"Another way to get bridges is to send an email to %s. Please note that you "
+"must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "My bridges don't work! I need help!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "If your Tor doesn't work, you should email %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Here are your bridge lines:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Get Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Please select options for bridge type:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Do you need IPv6 addresses?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Do you need a %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Your browser is not displaying images properly."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Enter the characters from the image above..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "How to start using your bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+msgstr ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:126
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
+"follow\n"
+"the wizard until it asks:"
+msgstr ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
+"follow\n"
+"the wizard until it asks:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:130
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor "
+"connections\n"
+"to the Tor network?"
+msgstr ""
+"Does your Internet Service Provider (ISP) block or otherwise censor "
+"connections\n"
+"to the Tor network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:134
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and"
+"\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and"
+"\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+
+#: lib/bridgedb/strings.py:142
+msgid "Displays this message."
+msgstr "Displays this message."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:146
+msgid "Request vanilla bridges."
+msgstr "Request vanilla bridges."
+
+#: lib/bridgedb/strings.py:147
+msgid "Request IPv6 bridges."
+msgstr "Request IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:149
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Request a Pluggable Transport by TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:152
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Get a copy of BridgeDB's public GnuPG key."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Report a Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Source Code"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Select All"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Show QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode for your bridge lines"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Uh oh, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "It seems there was an error getting your QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
+"your bridge lines onto mobile and other devices."
+msgstr ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
+"your bridge lines onto mobile and other devices."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "There currently aren't any bridges available..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
+msgstr " Perhaps you should try %s going back %s and choosing a different bridge type!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Step %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Download %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Step %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Get %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Step %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Now %s add the bridges to Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust give me bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Advanced Options"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "none"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..9399e46
--- /dev/null
+++ b/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,382 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Andi Chandler <andi at gowling.com>, 2014-2015
+# Richard Shaylor <rshaylor at me.com>, 2014
+# ronnietse <tseronnie at ymail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-19 14:19+0000\n"
+"Last-Translator: Andi Chandler <andi at gowling.com>\n"
+"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/torproject/language/en_GB/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: en_GB\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Sorry! Something went wrong with your request."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[This is an automated message; please do not reply.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Here are your bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "You have exceeded the rate limit. Please slow down! The minimum time between\nemails is %s hours. All further emails during this time period will be ignored."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Welcome to BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Currently supported transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hello, friend!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Public Keys"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "This email was generated with rainbows, unicorns, and sparkles\nfor %s on %s at %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\nwhich can help obfuscate your connections to the Tor Network, making it more\ndifficult for anyone watching your internet traffic to determine that you are\nusing Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Some bridges with IPv6 addresses are also available, though some Pluggable\nTransports aren't IPv6 compatible.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Additionally, BridgeDB has plenty of plain old vanilla bridges %s without any\nPluggable Transports %s which maybe doesn't sound as cool, but they can still\nhelp to circumvent internet censorship in many cases.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "What are bridges?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "I need an alternative way of getting bridges!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Another way to get bridges is to send an email to %s. Please note that you must\nsend the email using an address from one of the following email providers:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "My bridges don't work! I need help!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "If your Tor doesn't work, you should email %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Try including as much info about your case as you can, including the list of\nbridges and Pluggable Transports you tried to use, your Tor Browser version,\nand any messages which Tor gave out, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Here are your bridge lines:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Get Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Please select options for bridge type:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Do you need IPv6 addresses?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Do you need a %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Your browser is not displaying images properly."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Enter the characters from the image above..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "How to start using your bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "To enter bridges into Tor Browser, follow the instructions on the %s Tor\nBrowser download page %s to start Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\nthe wizard until it asks:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Does your Internet Service Provider (ISP) block or otherwise censor connections\nto the Tor network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\npaste the bridge lines into the text input box. Finally, click 'Connect', and\nyou should be good to go! If you experience trouble, try clicking the 'Help'\nbutton in the 'Tor Network Settings' wizard for further assistance."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Displays this message."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Request vanilla bridges."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Request IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Request a Pluggable Transport by TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Get a copy of BridgeDB's public GnuPG key."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Report a Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Source Code"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Select All"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Show QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode for your bridge lines"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Uh oh, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "It seems there was an error getting your QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "This QRCode contains your bridge lines. Scan it with a QRCode reader to copy your bridge lines onto mobile and other devices."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "There are no bridges available currently..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Perhaps you should try %s going back %s and choosing a different bridge type!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Step %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Download %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Step %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Get %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Step %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Now %s add the bridges to Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust give me bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Advanced Options"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "none"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..48c0ad8
--- /dev/null
+++ b/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,433 @@
+# English translations for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+# Isis Lovecruft <isis at torproject.org>, 2015.
+#
+#, fuzzy
+#
+# Translators:
+# runasand <runa.sandvik at gmail.com>, 2011
+# Isis Lovecruft <isis at torproject.org>, 2012-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: bridgedb 0.2.4-234-g193c80a-dirty\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'
+"POT-Creation-Date: 2015-03-19 22:13+0000\n"
+"PO-Revision-Date: 2015-03-20 04:13+0000\n"
+"Last-Translator: Isis Lovecruft <isis at torproject.org>\n"
+"Language-Team: English (US) (http://www.transifex.com/projects/p/torproject/language/en_US/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: en_US\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:107
+msgid "Sorry! Something went wrong with your request."
+msgstr "Sorry! Something went wrong with your request."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[This is an automated message; please do not reply.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Here are your bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be "
+"ignored."
+msgstr ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be "
+"ignored."
+
+#: lib/bridgedb/strings.py:25
+msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Welcome to BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Currently supported transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hello, friend!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Public Keys"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are"
+"\n"
+"using Tor.\n"
+"\n"
+msgstr ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are"
+"\n"
+"using Tor.\n"
+"\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
+"\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
+"\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
+"\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
+"\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "What are bridges?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "I need an alternative way of getting bridges!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you "
+"must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr ""
+"Another way to get bridges is to send an email to %s. Please note that you "
+"must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "My bridges don't work! I need help!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "If your Tor doesn't work, you should email %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Here are your bridge lines:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Get Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Please select options for bridge type:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Do you need IPv6 addresses?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Do you need a %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Your browser is not displaying images properly."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Enter the characters from the image above..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "How to start using your bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+msgstr ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:126
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
+"follow\n"
+"the wizard until it asks:"
+msgstr ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
+"follow\n"
+"the wizard until it asks:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:130
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor "
+"connections\n"
+"to the Tor network?"
+msgstr ""
+"Does your Internet Service Provider (ISP) block or otherwise censor "
+"connections\n"
+"to the Tor network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:134
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and"
+"\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and"
+"\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+
+#: lib/bridgedb/strings.py:142
+msgid "Displays this message."
+msgstr "Displays this message."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:146
+msgid "Request vanilla bridges."
+msgstr "Request vanilla bridges."
+
+#: lib/bridgedb/strings.py:147
+msgid "Request IPv6 bridges."
+msgstr "Request IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:149
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Request a Pluggable Transport by TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:152
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Get a copy of BridgeDB's public GnuPG key."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Report a Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Source Code"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Select All"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Show QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode for your bridge lines"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Uh oh, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "It seems there was an error getting your QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
+"your bridge lines onto mobile and other devices."
+msgstr ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
+"your bridge lines onto mobile and other devices."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "There currently aren't any bridges available..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
+msgstr " Perhaps you should try %s going back %s and choosing a different bridge type!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Step %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Download %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Step %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Get %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Step %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Now %s add the bridges to Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust give me bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Advanced Options"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "none"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..e2c5ff6
--- /dev/null
+++ b/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,359 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# identity, 2012
+# Rico Chan <rico at tutanota.de>, 2014
+# trio <trio at esperanto.org>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: Rico Chan <rico at tutanota.de>\n"
+"Language-Team: Esperanto (http://www.transifex.com/projects/p/torproject/language/eo/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: eo\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Pardonu! Io ne funkcias pri via peto."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Äi tiu estas aÅtomate kreita mesaÄo; bonvole ne respondu.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Jen viaj retpontoj."
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Vi superis la limiton. Bonvolu malakceli! La minimala tempo inter retleteroj estas\n%s horoj. Pliaj retiloj dum tiu tempo estos ignorata."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "ORDONOJ: (kombinu ORDONOJN por samtempe specifiki diversaj opciojn)."
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Estu bonvena al BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Nuntempe subtenata transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Saluton, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Saluton, amiko!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "publikaj Ålosiloj"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Äi tiu retletero estas generita kun Äielarkoj, unikornoj kaj steloj\npor %s je %s, %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB povas provizi pontojn/bridges kun diversaj %stypes de Pluggable Transports%s,\nkiuj povas helpi sekretigi viajn konektojn al la Tor Network kaj malfaciligi provojn kiuj estas destinitaj\nobservi vian datumtrafikon kaj vian uzantecon de Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "AnkaÅ kelkaj retpontoj kun IPv6-adresoj estas disponeblaj, sed iuj Pluggable\nTransports ne estas IPv6-kongrua.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Krome BridgeDB havas abundon da regulaj retpontoj %s sen iuj\nPluggable Transports %s, kiuj eble ne estas mojosa, tamen ofte povas helpi eviti\nreta cenzuro.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Kio estas retpontoj?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s retpontoj %s estas interretaj babiloj kiuj helpas vin eviti cenzuro."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Mi bezonas alternativon ekhavi retpontojn!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Sendu retletero al %s por ekhavi retpontojn alternative. Bonvolu konstati ke vi\nbezonas sendi retleteron per adreso de la sekvonta retpoÅta provizanto:\n%s, %s aÅ %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Miaj retpontoj ne funkcias! Mi bezonas helpon!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Se Tor ne funkcias, kontaktigu %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Provu klarigi vian kazon tre detale kaj aldonu liston da retpontoj kaj Pluggable Transports\nkiujn vi provis uzi. Krome aldonu vian Tor Browser-version kaj Äiujn mesaÄojn, kiujn Tor\neligis."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Jen viaj retpontoj:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Ekhavu retpontojn!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Bonvolu selekti opciojn pri retpontospeco."
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Äu vi bezonas IPv6-adresojn?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Äu vi bezonas %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Via retumilo vidigas bildojn ne dece."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Enigu la literoj en la bildo Äi-supre."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Kiel komenci uzi viajn retpontojn."
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Por enigi retpontojn en la Tor Browser, sekvu la lernilon de la %s\nTor-Browser-elÅutaĵopaÄo %s por starti la Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Kiam la Tor retagordaj dialogujo elklapas, alklaku 'agordi/konfiguri' kaj sekvu\nla asistanto Äis Äi demandas: "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Äu via provizanto de retservoj (angle: ISP) blokas aÅ alimaniere cenzuras konektojn al la Tor-reto?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Selektu 'Jes' kaj alklaku 'sekva'. Por konfiguri viajn novajn retpontojn, kopiu kaj alglui\nla retpontolineojn al la dialogujo. Finfine alklaku 'konektiÄi'.\nSe vi havas problemojn, provi alklaki la 'helpo'-butonon en la Tor-retagordasistanto\npor pli da asistado."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Vidigi Äi tiun mesaÄon."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Peti regulajn retpontojn (nepermutebla transporta retpontoj / non-Pluggable Transport bridges)."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Peti IPv6-retpontojn."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Peti permutebla transporto de TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Ekhavi kopio de publika GnuPG-Ålosilo de BridgeDB."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "raporti cimo"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "fontkodo"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "ÅanÄoprotokolo"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "kontakto"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "ho ve, ho ve!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Nuntempe ne ekzistas retpontojn."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Vi eble davas provi %s reiri %s kaj selekti alian retpontospeco."
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "paÅo %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "elÅuti %s Tor-retumilo %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "paÅo %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ekhavi %s retpotojn %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "paÅo %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Nun %s aldonu la retpontojn al la Tor-retumilo %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sT%suj donu retpontojn al mi!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "detalaj opcioj"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ne"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "neniom/neniu"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sE%skhavi Bridges"
diff --git a/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..b681b88
--- /dev/null
+++ b/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,388 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# dark_yoshi <angelargi at yahoo.es>, 2014
+# toypurina <biolenta at riseup.net>, 2014
+# BL <tresemes3 at gmail.com>, 2014
+# NinjaTuna <nort0ngh0st at hotmail.com>, 2011
+# Noel Torres <envite at rolamasao.org>, 2013
+# Paola Falcon <cacoepy at gmail.com>, 2014
+# Jonis <srvial at hotmail.com>, 2014
+# strel, 2013-2015
+# strel, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 13:50+0000\n"
+"Last-Translator: strel\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/torproject/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "¡Lo siento! Algo fue mal con tu solicitud."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Este es un mensaje automático; por favor no responda.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Aquà están sus bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Ha excedido el lÃmite de velocidad. Por favor, ¡más despacio! El tiempo mÃnimo entre correos electrónicos es %s horas. Los siguientes correos durante este periodo de tiempo serán ignorados. "
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combine COMMANDs (comandos) para especificar múltiples opciones simultáneamente)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "¡Bienvenido a BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Transport TYPEs actualmente soportados:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "¡Eh, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "¡Hola amigo!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Claves públicas"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Este correo fue generado con arcoiris, unicornios y chispitas para %s el %s a las %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB puede proveer bridges con varios %stipos de Pluggable Transports%s que pueden ayudar a ofuscar sus conexiones a la red Tor, haciendo que sea más difÃcil para alguien que esté viendo su tráfico en la red el determinar que ud. está usando Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "También hay disponibles varios bridges con direcciones IPv6, aunque algunos\nPluggable Trasnports no son compatibles con IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Además, BridgeDB tiene un montón de sencillos-clásicos-estándar (vanilla) bridges\n%s sin ningún Pluggable Transport %s, lo que tal vez no suena tan molón, pero que\naún pueden ayudar a eludir la censura en Internet en muchos casos.\n\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "¿Qué son los puentes ('bridges')?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "Los %s bridges %s son un tipo de repetidores Tor que le ayudan a eludir la censura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "¡Necesito un modo alternativo de conseguir puentes!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Otra forma de obtener repetidores puente (bridges) es enviar un correo electrónico a %s. Por favor observe que tiene que enviar el correo usando la dirección de uno de los siguientes proveedores de correo electrónico:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "¡Mis bridges/puentes no funcionan! ¡Necesito ayuda!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Si su Tor no funciona, debe enviar un correo a %s"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Intenta incluir tanta información como puedas de tu caso, incluyendo la lista de\nbridges (repetidores puente) y Pluggable Transports (transportes conectables) que\nintentaste usar, tu versión de Navegador Tor y cualquier mensaje que haya dado Tor, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Aquà están tus lÃneas de bridge:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "¡Obtener bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Por favor, selecciona opciones para el tipo de bridge:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "¿Necesitas direcciones IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "¿Necesitas un %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Tu navegador no está mostrando las imágenes correctamente."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Introduce los caracteres de la imagen de arriba..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Cómo comenzar a usar tus bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Para introducir bridges (repetidores puente) en el Tor Browser, sigue las\ninstrucciones de la %s página de descarga del Tor Browser %s para iniciarlo."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Cuando el cuadro de 'Configuraciones de red Tor' aparezca, haz clic en 'Configurar'\ny sigue el asistente hasta que pregunte:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "¿Su proveedor de Internet (ISP) bloquea o censura de alguna manera las conexiones a la red Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Selecciona 'SÃ' y luego haz clic en 'Siguiente'. Para configurar tus nuevos\nbridges, copia y pega las lÃneas de bridges en el cuadro de texto.\nPor último, haz clic en 'Conectar', ¡y listo!\nSi encuentras problemas, prueba dando clic en el botón 'Ayuda'\nen el asistente de 'Configuraciones de red Tor' para asistencia adicional."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Muestra este mensaje."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Solicitar bridges estándar (vanilla)."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Solicitar bridges IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Solicitar un Pluggable Transport por TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Obtener una copia de la clave pública GnuPG de BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Reportar una falla"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Código fuente"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Registro de cambios"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contactar"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Seleccionar todos"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Mostrar código QR"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Código QR para sus lÃneas de repetidores puente"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Oh oh, ¡la liamos!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Parece que hubo un error al obtener su código QR."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Este código QR contiene sus lÃneas de repetidores puente (bridges). Escanéelo con un lector de códigos QR para copiar sus lÃneas de puentes a dispositivos móviles/celulares y otros dispositivos."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Ahora mismo no hay ningún bridge disponible..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "¡Tal vez debas probar %s volviendo atrás %s y seleccionando un tipo diferente de bridge!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Paso %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Descarga %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Paso %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obtenga los %s puentes ('bridges') %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Paso %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Ahora %s añada los bridges al Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "¡%sS%sólo dame los bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opciones avanzadas"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ninguno"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "¡%sS%sÃ!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sbtener bridges"
diff --git a/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..22727ad
--- /dev/null
+++ b/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,102 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# halbares <pablo.alvarez.flores at gmail.com>, 2014
+# Pablo Lezaeta <prflr88 at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2014-03-26 19:50+0000\n"
+"Last-Translator: Pablo Lezaeta <prflr88 at gmail.com>\n"
+"Language-Team: Spanish (Chile) (http://www.transifex.com/projects/p/torproject/language/es_CL/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: es_CL\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "¿Cuáles son los puentes?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s Puentes retrasadores %s son repetidores de Tor que ayudan a combatir la censura."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "¡Necesito una forma alternativa de obtener puentes!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Otra forma de encontrar direcciones de puentes públicos es mediante enviar un correo electrónico (De %s o %s dirección) a %s con la lÃnea «get bridges» en el cuerpo del mensaje."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "¡Mi puente no funciona! ¡Ayúdenme!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Si Tor no funciona, por favor envÃe un correo %s. Intente incluir toda la información posible, incluyendo la lista de los puentes que ha utilizado, el paquete nombre de archivo/versión que utilizó, los mensajes que Tor devolvió, etc."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Para utilizar las lÃneas anteriores, dirÃjase a la página de configuración de la red de VIdalia, y haga clic en «Mi ISP bloquea las conexiones a la red Tor». A continuación, añada cada dirección puente una a una."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "No hay puentes disponibles actualmente"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Actualiza tu navegador a Firefox"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Escribe las dos palabras"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Paso 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Obtenga %s Paquete del navegador Tor %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Paso 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obtenga %s puentes %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Paso 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Ahora %s añadir los puentes a Tor %s"
diff --git a/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..e6e956c
--- /dev/null
+++ b/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,101 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Antxon Baldarra <baldarra at lavabit.com>, 2013
+# Antxon Baldarra <baldarra at lavabit.com>, 2011, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-05-09 15:19+0000\n"
+"Last-Translator: Antxon Baldarra <baldarra at lavabit.com>\n"
+"Language-Team: Basque (http://www.transifex.com/projects/p/torproject/language/eu/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: eu\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "Zer dira zubiak?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s zubi erreleak zentsura sahiesten laguntzen zaituzten %s Tor erreleak dira."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Zubiak eskuratzeko modu alternatibo bat behar dut!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Zubi publikoen helbideak aurkitzeko beste modu bat eposta (%s edo %s helbide batetik) bat bidaltzea da %sera 'get bridges' lerroarekin mezuaren gorputzean bertan."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "Nire zubiak ez dute funtzionatzen! Laguntza behar dut!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Zure Torek funtzionatzen ez badu, %sera email bat idatzi beharko zenuke. Saiatu zure kasuaren inguruan ahalik eta informazio gehien sartzen, erabili dituzun zubien zerrenda, erabili duzun bundlearen fitxategi izena/bertsioa, Torek eman dizkizun mezuak, eta abar barne."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Gaineko lerroak erabiltzeko, joan zaitez Vidaliaren sare ezarpenenak orrira, eta sakatu \"Nire Internet Zerbitzu Hornitzaileak Tor sarera konektatzea blokeatzen dit\". Ondoren gehitu ezazu zubi helbide bakoitza banan banan."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "Ez dago zubirik eskuragarri oraintxe bertan."
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Berritu zure nabigatzailea Firefoxera"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Idatzi itzazu bi hitzak"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "1. pausua"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Eskuratu %s Tor Browser Bundlea %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "2. pausua"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Eskuratu %s zubiak %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "3. pausua"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Orain %s gehitu zubiak Torera %s"
diff --git a/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..5e9ebc8
--- /dev/null
+++ b/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,387 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# arashaalaei <aalaeiarash at gmail.com>, 2011
+# signal89 <ali.faraji90 at gmail.com>, 2014
+# ardeshir, 2013
+# Gilberto, 2014-2015
+# johnholzer <johnholtzer123 at gmail.com>, 2014
+# Mohammad Hossein <desmati at gmail.com>, 2014
+# perspolis <rezarms at yahoo.com>, 2011
+# Setareh <setareh.salehee at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-17 22:11+0000\n"
+"Last-Translator: Gilberto\n"
+"Language-Team: Persian (http://www.transifex.com/projects/p/torproject/language/fa/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Ù
تاس٠ÙستÛÙ
Ø Ø¯Ø± رابط٠با درخÙاست Ø´Ù
ا خطاÛÛ Ø±Ø® داد٠است."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[اÛÙ ÛÚ© Ù¾ÛاÙ
Ø®Ùدکار Ù
Û Ø¨Ø§Ø´Ø¯Ø ÙØ·Ùا پاسخ ÙدÙÛد.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "ÙÙرست Ù¾ÙâÙØ§Û Ø´Ù
ا:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Ø´Ù
ا از Øد Ù
جاز تجاÙز ÙÙ
Ùد٠اÛد. ÙØ·Ùا٠اÙØ¯Ú©Û Ú©Ùد تر اÛÙ
Û٠بÙرستÛد. ØداÙ٠زÙ
ا٠Ù
Ù
Ú©Ù Ù
ابÛÙ\nارسا٠اÛÙ
ÛÙ Ùا %s ساعت است. تÙ
اÙ
Û Ø§ÛÙ
ÛÙ ÙØ§Û Ø¨Ø¹Ø¯Û Ø¯Ø± اÛÙ Ù
دت زÙ
اÙÛ Ø§Ø±Ø³Ø§Ù ÙØ®ÙاÙÙد شد."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "دستÙرات: (ترکÛب دستÙرات Ø¨Ø±Ø§Û Ù
شخص کرد٠گزÛÙÙ ÙØ§Û Ù
تعدد ب٠طÙر ÙÙ
زÙ
اÙ)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "ب٠BridgeDB Ø®ÙØ´ Ø¢Ù
دÛد!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "اÙÙاع Ù
Ù
ک٠از TYPE ÙØ§Û Ø§ÙتÙاÙ:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "سÙاÙ
Ø %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "سÙاÙ
دÙست Ù
Ù!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Public Key Ùا"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "اÛ٠اÛÙ
Û٠با رÙÚ¯ÛÙ Ú©Ù
ا٠ÙØ§Ø Ø§Ø³Ø¨ ÙØ§Û ØªÚ©Ø´Ø§Ø® Ù Ø²Ø±Ù Ù Ø¨Ø±Ù Ø¨Ø±Ø§Û %s بر %s ٠در %s تÙÙÛد شد٠است."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB Ù
Û ØªÙاÙد Ù¾Ù Ùا با %sاÙÙاع Ù
ØªØ¹Ø¯Ø¯Û Ø§Ø² Pluggable Transports%s ÙراÙÙ
Ú©Ùد Ú©Ù Ú©Ù
Ú© Ù
Û Ú©ÙÙد اتصا٠شÙ
ا ب٠Tor Network تا Øد اÙ
کا٠Ù
بÙÙ
Ù ÙاشÙاس باÙÛ Ø¨Ù
اÙد Ù ÙÛÚ Ú©Ø³ دÛÚ¯Ø±Û ÙتÙاÙد ب٠راØØªÛ Ø¨Ù Ø§ØªØµØ§Ù Ø´Ù
ا ب٠اÛÙترÙت Ùظارت Ú©Ùد ٠تشخÛص بدÙد Ú©Ù Ø´Ù
ا از Tor استÙاد٠Ù
Û Ú©ÙÛد.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Ø¨Ø¹Ø¶Û Ø§Ø² Ù¾ÙâÙا با آدرسâÙØ§Û IPv6 در دسترس ÙستÙØ¯Ø Ùر ÚÙد Ø¨Ø±Ø®Û Ø§Ø² Pluggable\nØÙ
Ù Ù ÙÙÙâÙا با IPv6 سازگار ÙÛستÙد.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "ÙÙ
ÚÙÛÙ BridgeDB شاÙ
٠تعداد زÛØ§Ø¯Û Ù¾Ù ÙØ§Û Ø§Ø¨ØªØ¯Ø§ÛÛ Ù ÙدÛÙ
Û Ø§Ø³Øª Ú©Ù %s Pluggable Transports ÙدارÙد %s Ù Ù
Ù
ک٠است Ø¨Û Ù
صر٠ب٠Ùظر برسÙد اÙ
ا ÙÙ
ÚÙا٠Ù
Ù
ک٠است در Ù
Ùارد Ù
ØªØ¹Ø¯Ø¯Û Ø¬Ùت دÙر زد٠ÙÛÙترÛÙÚ¯ ب٠شÙ
ا Ú©Ù
Ú© Ú©ÙÙد.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Ù¾Ù Ùا ÚÙ ÙستÙدØ"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Ù¾Ù Ùا %s ÙستÙد بازپخش Ú©ÙÙد٠ÙØ§Û ØªÙر ÙستÙد ک٠ب٠شÙ
ا Ø¨Ø±Ø§Û Ø¯Ùر زد٠ساÙسÙرکÙ
Ú© Ù
Û Ú©ÙÙد ."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "ب٠ÛÚ© را٠دÛگر Ø¨Ø±Ø§Û Ø¯Ø±ÛاÙت bridge Ùا اØتÛاج دارÙ
!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ÛÚ© طرÛ٠دÛÚ¯Ø±Û Ú©Ù Ù
ÛتÙاÙÛد Ù¾ÙÙا را بگÛرÛد, از طرÛÙ Ùزستاد٠ÛÚ© اÛÙ
Û٠ب٠%s Ù
Ûباشد. ÙØ·Ùا دÙت Ùر Ù
اÛÛد Ú©Ù Ø´Ù
ا باÛد اÛÙ
ÛÙ Ø®Ùد را ØتÙ
ا از ÛÚ©Û Ø§Ø² ÙراÙÙ
Ú©ÙÙد گا٠اÛÙ
ÛÙÛ Ø²Ûر بÙرستÛØ°: %s %s Ù Ûا %s ."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ب٠کÙ
Ú© اØتÛاج دارÙ
! Ù¾Ù ÙØ§Û Ù
٠کار ÙÙ
ÛâÚ©ÙÙد!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "اگر تÙر Ø´Ù
ا کار ÙÙ
Û Ú©Ùد, Ø´Ù
ا باÛد اÛÙ
ÛÙ Ú©ÙÛد %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Ø³Ø¹Û Ú©ÙÛد تا Ù
Û ØªÙاÙÛد اطÙاعات بÛØ´ØªØ±Û Ø±Ø§ ÙراÙÙ
Ú©ÙÛد. از جÙ
ÙÙ ÙÛست Ù¾Ù Ùا Ù Pluggable Transports ÙاÛÛ Ú©Ù Ø§Ø³ØªÙاد٠کرد٠اÛد. ÙÙ
ÚÙÛÙ Ø´Ù
ار٠Ùسخ٠Tor Browser Ù Ùر Ù¾ÛغاÙ
Û Ú©Ù Tor ب٠شÙ
ا داد٠است."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "خطÙØ· Ù¾Ù Ø´Ù
ا در اÛÙجا:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "درÛاÙت Ù¾Ù Ùا!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ÙØ·Ùا گزÛÙÙ ÙÙع پ٠را اÙتخاب Ú©ÙÛد:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Ø¢Ûا Ø´Ù
ا ب٠آدرس ÙØ§Û IPv6 ÙÛاز دارÛدØ"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Ø¢Ûا Ø´Ù
ا ÙÛاز دارÛد ب٠ÛÚ© %sØ"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Ù
رÙرگر Ø´Ù
ا تصاÙÛر را Ø¨Ù Ø¯Ø±Ø³ØªÛ ÙÙ
اÛØ´ ÙÙ
Û Ø¯Ùد."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "کاراکتر Ùا را از تصÙÛر باÙا Ùارد Ú©ÙÛد..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "ÚÚ¯ÙÙÚ¯Û Ø§Ø² Ù¾ÙâÙØ§Û Ø®Ùد استÙاد٠کÙÛد"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Ø¨Ø±Ø§Û Ùارد کرد٠پ٠Ùا در Tor Browser, دÙبا٠کÙÛد دستÙراÙعÙ
٠را در %s Tor\nBrowser صÙØ٠داÙÙÙد %s Ø¨Ø±Ø§Û Ø´Ø±Ùع Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "ÙÙØªÛ Ú©Ù ØµÙØÙ 'تÙظÛÙ
ات شبک٠تÙر' ÙÙ
اÛØ´ داد٠شد, رÙÛ Ú¯Ø²ÛÙÙ 'Ù¾ÛکربÙدÛ' Ú©ÙÛÚ© Ú©ÙÛد ٠دÙبا٠کÙÛد\nتا زÙ
اÙÛ Ú©Ù wizard از Ø´Ù
ا بپرسد:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Ø¢Ûا شرکت اراÛ٠دÙÙد٠اÛÙترÙت (ISP) Ø´Ù
ا بÙاک Ù
Û Ú©Ùد Ù Ûا ساÙسÙر Ù
Û Ú©Ùد ارتباطات\nشبک٠تÙر Ø´Ù
ا راØ"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "'بÙÙ' را اÙتخاب کرد٠٠سپس 'بعدÛ' را اÙتخاب Ú©ÙÛد. Ø¨Ø±Ø§Û Ù¾ÛکربÙØ¯Û Ù¾Ù Ø¬Ø¯Ûد Ø®Ùد, copy Ù paste Ú©ÙÛد خطÙØ· پ٠را در Ù
ت٠جعب٠ÙرÙدÛ. در ÙÙاÛت, رÙÛ 'اتصاÙ' Ú©ÙÛÚ© Ú©ÙÛد,\nرا٠درستش اÛ٠است! اگر Ù
Ø´Ú©ÙÛ Ù¾ÛØ´ Ø¢Ù
د, Ú©ÙÛÚ© Ú©ÙÛد رÙÛ Ú©ÙÛد 'راÙÙÙ
ا'\nدر 'تÙظÛÙ
ات شبک٠تÙر' wizard Ø¨Ø±Ø§Û Ø§Ø·Ùاعات بÛشتر."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "اÛÙ Ù¾ÛاÙ
را ÙÙ
اÛØ´ Ù
Û Ø¯Ùد."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "درخÙاست Ù¾ÙâÙØ§Û Ø¹Ø§Ø¯Û"
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "درخÙاست Ù¾ÙâÙØ§Û IPv6"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "درخÙاست ÛÚ© رÙØ´ Transport جاÛگزÛ٠براساس TYPE"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "درÛاÙت ÛÚ© Ú©Ù¾Û Ø§Ø² Ú©ÙÛد عÙ
ÙÙ
Û BridgeDB"
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "گزارش ÛÚ© باگ"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "کد Ù
Ùبع"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "ÙÛست تغÛÛرات"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "تÙ
اس"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "اÙتخاب ÙÙ
Ù"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "ÙÙ
اÛØ´ QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode Ø¨Ø±Ø§Û Ø®Ø·ÙØ· Ù¾Ù Ø®Ùد"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "اÙ٠اÙÙØ Ú©Ùسر٠اسپاگتÛ!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "ب٠Ùظر Ù
Û Ø±Ø³Ø¯ ÛÚ© خطا در گرÙت٠QRCode Ø´Ù
ا ÙجÙد دارد."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "اÛÙ QRCode شاÙ
٠خطÙØ· Ù¾Ù Ø®Ùد. اسک٠آ٠را با ÛÚ© Ø®ÙاÙÙد٠QRCode Ø¨Ø±Ø§Û Ú©Ù¾Û Ø®Ø·ÙØ· Ù¾Ù Ø®Ùد بر رÙÛ ØªÙÙÙ ÙÙ
را٠٠دستگا٠ÙØ§Û Ø¯Ûگر."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "در Øا٠Øاضر ÙÛÚ Ù¾Ù Û Ø¯Ø± دسترس ÙÛست"
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr " شاÛد Ø´Ù
ا باÛد Ø³Ø¹Û Ú©ÙÛد %s بازگشت %s ٠اÙتخاب ÙÙع Ù¾Ù ÙØ§Û Ù
ختÙÙ!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Ù
رØÙÙ %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "داÙÙÙد %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Ù
رØÙÙ %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "درÛاÙت %s Ù¾Ùâ Ùا %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Ù
رØÙÙ %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "ØاÙا %s اضاÙ٠کرد٠پ٠Ùا ب٠Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sÙ%sÙØ· بد٠ب٠Ù
Ù Ù¾Ù Ùا را!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "گزÛÙÙ ÙØ§Û Ù¾ÛشرÙتÙ"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "ÙÙ"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ÙÛÚ"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sب%sÙÙ!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sد%sرÛاÙت Ù¾ÙâÙا"
diff --git a/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..a2afaa5
--- /dev/null
+++ b/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,386 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Jorma Karvonen <karvonen.jorma at gmail.com>, 2015
+# Jorma Karvonen <karvonen.jorma at gmail.com>, 2014
+# Spacha <miikasikala96 at gmail.com>, 2015
+# Ossi Kallunki <ossikallunki at gmail.com>, 2013
+# Sami Kuusisto <sami at 6sto.com>, 2014
+# viljaminojonen <Viljami.Nojonen at live.com>, 2014
+# Finland355 <ville.ehrukainen2 at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-03 18:01+0000\n"
+"Last-Translator: Jorma Karvonen <karvonen.jorma at gmail.com>\n"
+"Language-Team: Finnish (http://www.transifex.com/projects/p/torproject/language/fi/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fi\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Pyyntösi epäonnistui jostain syystä."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Tämä on automaattinen viesti; älä vastaa tähän.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Tässä ovat sinun siltasi."
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Olet ylittänyt nopeusrajan. Hidasta hiukan! Minimiaika viestien välillä on %s tunnissa. Kaikki lisäviestit tämän ajan kuluessa ohitetaan."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "KOMENNOT: (yhdistä KOMENNOT määritelläksesi useita valitsimia samanaikaisesti)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Tervetuloa BridgeDB :hen!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Nykyiset tuetut transport TYPE-siirtotyypit:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hei, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hei ystävä!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Julkiset avaimet"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Tämä sähköposti tuotettiin käyttäjälle %s %s klo %s"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB voi tarjota usean %styyppisiä irrotettavia Pluggable Transports%s-siirtoja,\njotka voivat auttaa peittämään yhteytesi Tor Network-verkkoon, mikä tekee vaikeammaksi\nkenellekään seurata internet-liikennettäsi ottaakseen selvää, että käytätkö Tor-palvelua.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Myös joitakin siltoja IPv6-osoitteilla on saatavilla, vaikka jotkut irrotettavat Pluggable\nTransports-siirrot eivät ole IPv6-yhteensopivia.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Lisäksi BridgeDB:llä on useita tavallisia siltoja %s ilman mitään irrotettavia\nPluggable Transports %s-siirtoja, jotka eivät ehkä kuulosta herkullisilta, mutta\nne voivat silti auttaa monissa tapauksissa kiertämään internet-sensuuria.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Mitä ovat sillat?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s-sillat %s ovat Tor-välitysjärjestelmiä, jotka auttavat kiertämään sensurointia."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Tarvitsen vaihtoehtoisen tavan tavoittaa sillat!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Toinen tapa siltojen hankkimiseen on lähettää sähköpostia osoitteeseen %s. Huomaa,\nettä sinun on lähetettävä sähköpostiviesti käyttäen yhtä seuraavien sähköpostitarjoajien\nosoitetta:\n%s, %s tai %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Siltani eivät toimi! Tarvitsen apua!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Jos Tor ei toimi sinulla, sinun pitäisi sähköpostittaa %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Yritä sisällyttää niin paljon tietoa tapauksestasi kuin voit, mukaanlukien siltojen\nluettelo ja irrotettavat Pluggable Transport-siirrot, joita yritit käyttää,\nTor Browser-versio, ja kaikki viestit, jotka Tor ilmoitti, jne."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Tässä ovat siltarivisi:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Hae sillat!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Valitse valitsimet siltatyypille:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Tarvitsetko IPv6-osoitteita?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Tarvitsetko %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Selaimesi ei näytä kuvia oikein."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Syötä merkit yläpuolella olevasta kuvasta..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Kuinka aloitat siltojesi käytön"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Siltojen lisäämiseksi Tor Browseriin, seuraa ohjeita %s Tor\nBrowser-lataussivulla %s Tor Browserin käynnistämiseksi."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Kun âTor-verkkoasetuksetâ-valintaikkuna ponnahtaa näkyviin, napsauta âConfigureâ ja seuraa asetusvelhoa, kunnes se kysyy:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Estääkö tai sensuroiko Internet-palvelutarjoajasi (ISP) muuten yhteyksiä\nTor-verkkoon?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Valitse âKylläâ ja napsauta sitten âSeuraavaâ. Uusien siltojen konfiguroimiseksi kopioi ja\nliitä siltarivit tekstisyöteikkunaan. Napsauta lopuksi âYhdistäâ, ja siirtymisen pitäisi\nonnistua! Jos kohtaat pulmia, yritä saada lisäapua napsauttamalla âOpasteâ-\npainiketta âTor-verkkoasetuksetâ-asetusvelhossa."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Näyttää tämän viestin."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Pyydä tavallisia siltoja."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Pyydä IPv6-siltoja."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Pyydä TYPE-tyyppistä irrotettavaa Pluggable Transport-siirtoa."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Hae kopio BridgeDB:n julkisesta GnuPG-avaimesta."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Ilmoita ohjelmointivirheestä"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Lähdekoodi"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Muutosloki"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Yhteystieto"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Valitse kaikki"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Näytä QR-koodi"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Siltariviesi QR-koodi"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Jossain on pulma!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Tapahtui virhe QR-koodia noudettassa."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Tämä QR-koodi sisältää siltarivisi. Skannaa se QR-koodinlukijalla kopioidaksesi siltarivit matkapuhelimeesi ja muihin laitteisiin."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Tällä hetkellä ei ole yhtään siltaa saatavilla..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Ehkä sinun pitäisi yrittää %s palaamalla %s ja valitsemalla eri siltatyyppi!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Vaihe %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Lataa %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Vaihe %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Hanki %s sillat %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Vaihe %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Nyt %s lisää sillat Tor Browseriin %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sA%snna minulle vain sillat!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Lisävalitsimet"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ei"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ei mitään"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sK%syllä!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sH%sae sillat"
diff --git a/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..58eef87
--- /dev/null
+++ b/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,393 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# apaddlingduck, 2014
+# Boubou <cece31840 at gmail.com>, 2015
+# Cryptie <cryptie at fsfe.org>, 2014
+# fayçal fatihi, 2014
+# Frisson Reynald <frissonreynald at yahoo.fr>, 2014
+# hpatte <hadrien.44 at gmail.com>, 2014
+# Lucas Leroy <lerlucas at rocketmail.com>, 2014
+# Lunar <lunar at torproject.org>, 2013
+# Onizuka, 2013
+# mehditaileb <mehditaileb at liberte-info.net>, 2011
+# Onizuka, 2013
+# themen <themen2004 at gmail.com>, 2014
+# Towinet, 2014
+# Yannick Heintz, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 18:31+0000\n"
+"Last-Translator: Boubou <cece31840 at gmail.com>\n"
+"Language-Team: French (http://www.transifex.com/projects/p/torproject/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Désolé ! Un problème est survenu suite à votre requête."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ceci est un message automatique, merci de ne pas répondre.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Voici vos bridges :"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Vous avez dépassé la limite de vitesse. S'il vous plaît ralentissez ! Le temps minimum entre les courriels est %s heures. Tous les autres courriels pendant cette période seront ignorés."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDES: (combiner des commandes pour spécifier plusieurs options en même temps)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Bienvenue à BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "TYPEs de transport pris en charge actuellement:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Salut, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Bonjour, l'ami !"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Les clés publiques"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ce courriel a été généré avec des arcs en ciel, des licornes, et des étincelles pour %s, %s à %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB peut fournir des ponts avec plusieurs %stypes de transports enfichables%s, qui peuvent aider à brouiller vos connexions au réseau Tor, ce qui rend plus difficile pour tous ceux qui regardent votre trafic Internet de déterminer que vous utilisez Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Certains ponts avec les adresses IPv6 sont également disponibles, bien que certains Transports soient enfichables ; ils ne sont pas compatibles avec l'IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "De plus, BridgeDB a beaucoup de banals ponts %s sans aucun\nTransports Enfichables (Pluggable Transports) %s, ne semblant pas être autant cool, mais pouvant toutefois\naider à contourner la censure Internet dans des nombreux cas.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Que sont les bridges ?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s sont des relais Tor qui vous aident à contourner la censure."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "J'ai besoin d'une alternative pour obtenir des adresses de bridges !"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Une autre alternative pour obtenir bridges est d'envoyer un email à %s. Veuillez noter que vous devez envoyer un email en utilisant une adresse email d'un des fournisseurs d'accès suivant: %s, %s ou %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mes bridges ne fonctionnent pas, j'ai besoin d'aide !"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Si votre Tor ne marche pas, vous devriez envoyer un courriel %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Essayez d'inclure autant d'infos que possible concernant votre cas, y compris la liste des\nponts et des Transports Enfichables (Pluggable Transports) que vous avez essayé d'utiliser, votre version de Tor Browser,\net n'importes quel autres messages que Tor vous a sorti, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Voici vos lignes des bridges:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Obtenez des bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Sélectionnez vos choix pour le type de bridge, s'il vous plaît:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Avez-vous besoin des adresses IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Avez-vous besoin un %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Votre navigateur n'affiche pas correctement les images."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Inscrire les caractères de l'image ci-dessusâ¦"
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Comment démarrer l'utilisation de vos Bridges."
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Pour entrer des ponts dans Tor Browser, suivez les instructions sur la %s page de téléchargement de Tor Browser %s pour démarrer Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Quand la boite de dialogue 'Paramètres Réseaux Tor' s'affiche, cliquez 'Configurer' et suivez l'assistant jusqu'à ce quâil demande :"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Est-ce que votre fournisseur d'accès à Internet (FAI) bloquent ou censurent les connexions vers le réseau Tor ?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Sélectionnez 'Oui' et ensuite cliquez 'Suivant'. Pour configurer vos nouvelles bridges, copiez et collez les lignes bridge dans la prochaine case de saisie de texte. Enfin, cliquez 'Connexion', et tout devrait marcher ! Si vous avez des difficultés, essayez le bouton 'Aide' dans 'Paramètres Réseaux Tor' pour plus dâassistance."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Afficher ce message."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Demander les bridges vanilles."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Demander IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Demander un Pluggable Transport par TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Obtenir une copie de la clef GnuPG publique BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Faire un rapport de Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Le code source"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Tout sélectionner"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Montrer QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode pour vos bridge lines"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Houlà !"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Il semblerait qu'il y ai eu une erreur en chargeant votre QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ce QRCode contient vos bridge lines. Scannez le avec un lecteur de QRCode pour copier vos bridge lines dans votre mobile ou d'autres appareils."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Il n'y a pas de bridges disponibles en ce momentâ¦"
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Peut-être vous devriez revenir %s en arrière %s et choisir un type différent de bridge !"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Etape %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Télécharger %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Etape %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Récupérez les %s addresses de bridge %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Etape %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Maintenant %s ajoutez les bridges au navigateur Tor %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%suste me donner les ponts!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Options avancées"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Non"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "aucun"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sO%sui!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sbtenir Bridges"
diff --git a/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..740cfe3
--- /dev/null
+++ b/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,383 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Lunar <lunar at torproject.org>, 2013
+# mehditaileb <mehditaileb at liberte-info.net>, 2011
+# Onizuka, 2013
+# yahoe.001, 2014-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 13:41+0000\n"
+"Last-Translator: yahoe.001\n"
+"Language-Team: French (Canada) (http://www.transifex.com/projects/p/torproject/language/fr_CA/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fr_CA\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Désolé! Quelque chose a mal tourné avec votre requête."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ceci est un message automatisé; veuillez ne pas répondre.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Voici vos ponts :"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Vous avez dépassé la limite. Veuillez ralentir! La durée minimum entre courriels\nest de %s heures. Tout autre courriel durant cette période sera ignoré."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDES : (combinez les COMMANDES pour spécifier plusieurs options simultanément)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Bienvenue à BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "TYPES de transport pris en charge présentement :"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Allô, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Bonjour l'ami!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Clefs publiques"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ce courriel a été généré avec des arcs en ciel, des unicornes et des paillettes pour %s le %s à %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB peut fournir des ponts avec plusieurs %stypes de transports enfichable%s,\npouvant aider à obscurcir vos connexions au réseau Tor, rendant difficile pour\nquiconque surveillant votre trafic Internet de déterminer que vous\nutilisez Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Des ponts avec des adresses IPv6 sont aussi proposés, bien que certains transports\nenfichables ne soient pas compatibles avec IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "De plus, BridgeDB comportent de nombreux ponts %s traditionnels sans\ntransport enfichable %s qui peuvent quand même aider à contourner\nla censure Internet dans bien des cas.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Que sont les ponts?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Les ponts %s sont des relais Tor qui vous aident à contourner la censure."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "J'ai besoin d'une alternative pour obtenir des ponts!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Une autre façon d'obtenir des ponts est d'envoyer un courriel à %s. Veuillez prendre\nnote que vous devez envoyer le courriel en utilisant une adresse d'un des fournisseurs\nde courriel suivants :\n%s, %s ou %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mes ponts ne fonctionnent pas, j'ai besoin d'aide!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Si votre Tor ne fonctionne pas, vous devriez envoyer un courriel à %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Essayez d'inclure autant d'infos sur votre cas que possible, incluant la liste de\nponts et de transports enfichables que vous avez essayé d'utiliser, votre version du\nnavigateur Tor et tout message donné par Tor, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Voici vos lignes de pont :"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Obtenir des ponts!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Veuillez choisir des options pour le type de ponts :"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Avez-vous besoin d'adresses IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Avez-vous besoin d'un %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Votre navigateur n'affiche pas les images correctement."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Saisir les caractères inscrits sur l'image ci-dessus..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Comment commencer à utiliser vos ponts"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Pour saisir vos ponts dans le navigateur Tor, suivez les instructions\nsur la %s page de téléchargement du navigateur Tor %s pour démarrer le navigateur Tor."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Quand la fenêtre « Paramètres du réseau Tor » s'affiche, cliquez sur « Configurer » et\nsuivez l'assistant jusqu'à ce qu'il demande :"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Votre fournisseur de service Internet (FSI) bloque-t-il ou censure-t-il\nvos connexions au réseau Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Sélectionnez « Oui » et ensuite cliquez sur « Suivant ». Pour configurer vos\nnouveaux ponts, copiez et collez vos lignes de pont dans la boîte de saisie\nde texte. Enfin cliquez sur « Connecter » et vous devriez avoir fini! Si vous éprouvez\ndes problèmes, essayez de cliquez sur le bouton « Aide » dans l'assistant des\n« Paramètres du réseau Tor » pour un un soutien supplémentaire."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Affiche ce message."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Demander des ponts traditionnels."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Demander des ponts IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Demander un transport enfichable par TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Obtenir une copie de la clef GnuPG publique de BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Rapporter un bogue"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Code source"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Journal des changements"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Tout sélectionner"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Montrer le code QR"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Code QR de vos lignes de pont"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Une erreur est survenue!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Il semble avoir eu une erreur de récupération de votre code QR."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ce code QR contient vos lignes de pont. Balayez-le avec un lecteur de codes QR pour copier vos lignes de pont sur votre appareil."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Aucun pont n'est disponible présentement..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Vous devriez peut-être %s revenir en arrière %s et choisir un différent type de pont!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Ãtape %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Télécharger %s le navigateur Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Ãtape %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obtenir %s les ponts %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Ãtape %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Maintenant, %s ajoutez les ponts au navigateur Tor %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "Donnez-moi %sj%suste des ponts! "
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Options avancées"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Non"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "aucun"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sO%sui!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sbtenir des ponts"
diff --git a/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..6aff6d7
--- /dev/null
+++ b/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,101 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# manuel meixide <m.meixide at gmail.com>, 2013
+# mbouzada <mbouzada at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-05-08 15:33+0000\n"
+"Last-Translator: manuel meixide <m.meixide at gmail.com>\n"
+"Language-Team: Galician (http://www.transifex.com/projects/p/torproject/language/gl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: gl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "Que son os repetidores ponte?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s Repetidores Ponte %s son repetidores Tor que axudan a burlar a censura."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Eu necesito un xeito alternativo de obter pontes!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Outra forma de atopar os enderezos de pontes públicas é enviar un correo electrónico (desde un %s ou un enderezo %s) para %s coa liña 'get bridges' por si soa no corpo do correo electrónico."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "As miñas pontes non funcionan! Necesito axuda!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Se o Tor non funciona, ten que enviar un correo-e a %s. Probe incluÃr tanta información sobre o seu caso como poida, incluÃndo a lista de pontes que usou, o nome do paquete/versión que usou, as mensaxes que Tor lle deu , etc"
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Para usar as liñas máis arriba, ten que ir á páxina de Configuración de rede do Vidalia, e premer en \"O meu ISP bloquea as conexións á rede Tor\". A continuación, engada un enderezo dunha ponte de cada vez."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "Non hai pontes actualmente dispoñibles"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Actualiza o teu navegador a Firefox"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Escriba as dúas palabras"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Paso 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Obter o %s Paquete Navegador Tor %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Paso 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obter as %s pontes %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Paso 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Agora %s engada as pontes a Tor %s"
diff --git a/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..f700478
--- /dev/null
+++ b/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,105 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# aharonoosh <aharonoosh1 at gmail.com>, 2012
+# Elifelet <arab.with.nargila at gmail.com>, 2014
+# GenghisKhan <genghiskhan at gmx.ca>, 2013
+# Kunda, 2014
+# Cuvint Chofschy <static.172 at 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: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2014-01-30 12:30+0000\n"
+"Last-Translator: Kunda\n"
+"Language-Team: Hebrew (http://www.transifex.com/projects/p/torproject/language/he/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: he\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "××× ×שר××?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s ×××¡×¨× ×שר %s ××× × ×××¡×¨× Tor ×שר ×ס×××¢×× ×× ×עק××£ ×¦× ××ר×."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "×× × ×ק×ק ×××¨× ×××פ×ת ×ק××ת ×שר××!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "××¨× ××רת ××צ××ת ×ת××ת ×©× ×שר פ×××× ××× × ×©×××ת ××××´× (×ת×× ×ת××ת %s ×× %s) ×× %s ×¢× ×ש××¨× 'get bridges' ×ש×עצ×× ×ת×× ×××£ ×××××¢×."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "××שר×× ×©×× ×× ×¢×××××! ×× × ×ק×ק ××¢×ר×!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "×× Tor ×× ×¢××× ×צ××, ×¢××× ×ש××× ××××´× ×× %s. × ×¡× ××××× ××× ×©××תר ××××¢ ××××ת ×××§×¨× ×©×× ××× ×©××××××ª× ×עש×ת ××, ×××× ×ת רש××ת ××שר×× ×שר ×שת×שת ××, ×©× ×ק×××¥/××¨×¡× ×©× ×××××× (bundle) ש×שת×שת, ×××××¢×ת ×שר Tor ×סר ×××׳."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "××× ××שת×ש ×ש×ר×ת ××¢××, ×××©× ×× ×¢××× ×××ר×ת רשת ×ת×× Vidalia, ××ק×ק \"ספק ×××× ××¨× × ×©×× ×××¡× ××××ר×× ×רשת Tor\". ×××¨× ×× ××סף ×× ×ת××ת ×שר ××ת ××× ×¤×¢×."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "××× ×שר×× ×××× ×× ×עת"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "ש××¨× ×ת ××פ××¤× ×©×× ×× Firefox"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "×קש ×ת ×©×ª× ××××××"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "×¦×¢× 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "××©× ×ת %s ××××ת ××פ××¤× ×©× Tor %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "×¦×¢× 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "××©× %s ×שר×× %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "×¦×¢× 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "×עת %s ××סף ×ת ××שר×× ×× Tor %s"
diff --git a/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..3d6ccbf
--- /dev/null
+++ b/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,384 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Ana B, 2013-2014
+# Armando Vega <synaan at gmail.com>, 2012
+# skiddiep <lyricaltumor at gmail.com>, 2014-2015
+# Tomislav SiroglaviÄ <tomsiro at gmail.com>, 2014
+# gogo <trebelnik2 at gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-16 12:31+0000\n"
+"Last-Translator: skiddiep <lyricaltumor at gmail.com>\n"
+"Language-Team: Croatian (Croatia) (http://www.transifex.com/projects/p/torproject/language/hr_HR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: hr_HR\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Oprostite! Nešto je pošlo po krivu s Vašim zahtjevom."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ovo je automatizirana poruka; molimo ne odgovarati.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Ovdje su vaši mostovi:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "PreÅ¡li ste limit. Molimo usporite! Minimum vremena izmeÄu emailova je %s sati. Svi daljnji emailovi tijekom ovog vremenskog perioda bit Äe ignorirani."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (kombinirajte COMMANDs kako bi naznaÄili viÅ¡e opcija istovremeno)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Dobrodošli u BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Trenutno podržani transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Bok, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Bok, prijatelju!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Javni kljuÄevi"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ovaj email je sastavljen s dugama, jednorozima i iskricama\nza %s na %s u %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB nudi mostove s razliÄitim %stipovima Pluggable Transports%s\nkoji mogu pomoÄi prikriti VaÅ¡e veze s Tor mrežom, otežavajuÄi onome tko\nprati VaÅ¡ internet promet da vidi da koristite Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Neki mostovi s IPv6 adresama su takoÄer dostupni, no neki Pluggable \nTransports nisu kompatibilni s IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Nadalje, BridgeDB ima mnoÅ¡tvo dobrih, starih mostova %s bez ikakvih\nPluggable Transports %s koji možda ne zvuÄe prezanimljivo, ali mogu\npomoÄi zaobiÄi internet cenzuru u mnogo sluÄajeva.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Å to su mostovi?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Mostovi %s su Tor releji koji vam pomažu zaobiÄi cenzuru."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Trebam alternativni naÄin dobivanja mostova!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "JoÅ¡ jedan naÄin za dobiti mostove je da poÅ¡aljete email na %s. Imajte na umu da morate\nposlati email koristeÄi adresu jednog od sljedeÄih davatelja email usluge: %s %s ili %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Moji mostovi ne rade! Treba mi pomoÄ!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ako vaš Tor ne radi, pošaljite email na %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "PokuÅ¡ajte naznaÄiti Å¡to viÅ¡e informacija o vaÅ¡em sluÄaju, ukljuÄujuÄi popis\nmostova i Pluggable Transports koje ste pokuÅ¡ali koristiti, verziju vaÅ¡eg Tor Browsera,\ni bilo kakve poruke koje je Tor izbacio, itd."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Ovdje su linije vaših mostova:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Dobijte Mostove!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Molimo odaberite opcije za tip mosta:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Trebaju li vam IPv6 adrese?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Trebate li %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Vaš preglednik ne prikazuje slike ispravno."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Unesite znakove sa slike iznad..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Kako poÄeti koristite VaÅ¡e mostove"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Kako bi unijeli mostove u Tor Browser, slijedite upute na %s Tor\nBrowser download stranici %s da bi pokrenuli Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Kad iskoÄi prozorÄiÄ 'Tor Mrežne Postavke', kliknite 'Konfiguriraj' i slijedite\nÄarobnjaka dok ne upita:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokira li vaÅ¡ Internet Service Provider (ISP - pružatelj internet usluge) ili ikako drugaÄije cenzurira veze\ns Tor mrežom?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Odaberite 'Da', a zatim 'SljedeÄe'. Da bi konfigurirali vaÅ¡e nove mostove, kopirajte i \nzalijepite linije mostova u kuÄicu za unos teksta. Na kraju, kliknite 'Spoji se', i \nsve bi trebalo biti u redu! Ako naiÄete na probleme, probajte kliknuti dugme 'PomoÄ'\nu 'Tor Mrežne Postavke' Äarobnjaku za daljnju pomoÄ."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Prikazuje ovu poruku."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Zatraži dobre, stare mostove."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Zatraži IPv6 mostove."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Zatraži Pluggable Transport po TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Dobij kopiju BridgeDB javnog GnuPG kljuÄa."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Prijavi Greški"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Izvorni Kod"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Zapisnik o promjenama"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Odaberi Sve"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Pokaži QR kod"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR kod za Vaše linije mostova"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "O ne, greška!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Äini se da je doÅ¡lo do greÅ¡ke pri dobavljanju VaÅ¡eg QR koda."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ovaj QRKod sadrži VaÅ¡e linije mostova. Skenirajte ga s ÄitaÄem QR koda da bi kopirali linije mostova na mobilne i ostale ureÄaje."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Trenutno nema dostupnih mostova..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Možda bi trebali probati %s vratiti se nazad %s i odabrati neki drugi tip mosta!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Korak %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Preuzmi %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Korak %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Dobijte %s mostove %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Korak %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Sad %s dodaj mostove u Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sS%samo mi daj mostove!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Napredne Opcije"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ne"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ništa"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sD%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sD%sobij Bridges"
diff --git a/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..5f352bb
--- /dev/null
+++ b/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,384 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Blackywantscookies <gaborcika at citromail.hu>, 2014
+# Lajos Pasztor <mrlajos at gmail.com>, 2014
+# Cerbo <taiurin at gmail.com>, 2014
+# vargaviktor <viktor.varga at gmail.com>, 2013,2015
+# vargaviktor <viktor.varga at gmail.com>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-04 11:11+0000\n"
+"Last-Translator: vargaviktor <viktor.varga at gmail.com>\n"
+"Language-Team: Hungarian (http://www.transifex.com/projects/p/torproject/language/hu/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: hu\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Elnézést! Valami rosszul működött a kérésed közben."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ez egy automata levél, kérjük ne válaszoljon.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Itt vannak a hÃdjaid:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Ãn túllépte a megadott mérték határt. Kérünk lassÃtson le! A minimum idÅ email-ek között\n%s óra. Minden további emailt ez az idÅ alatt elutasÃtunk."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (kombinálja COMMANDs-okat hogy többféle opciót adhasson meg egyszerre)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Ãdvözöl a BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Jelenleg támogatott átviteli tÃpusok: "
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hé, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hello!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Nyilvános kulcsok"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ez az email szivárványokkal, unikornisokkal, és ragyogásokkal volt generálva\n%s -nek %s -án %s -kor."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB hidakat tud bisztosÃtani számos %stÃpusával a Pluggable Transports%s -nak\namely segÃti a kapcsolataidat összekeverni a Tor Network -ben, ezzel sokkal\nnehezebbé teszi akárkinek hogy megnézze az internet forgalmadat és hogy meghatározzák hogy Tor -t használsz.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Néhány hÃd IPv6 cÃmekkel is elérhetÅ, habár négány Pluggable\nTransports nem IPv6 kompatibilis.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Ezen felül, BridgeDB -nek van sok régi módi \"vanÃlia\" hÃdjai %s melyek Csatlakoztatható SzállÃtók %s nélkül vannak\nmely talán nem hallatszik szuperül, de Åk még mindig\ntudnak segÃteni kikerülni az internet cenzúráját.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Mik is a Hidak?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "A %s Hidak %s azok Tor relék, melyek segÃtik önt a cenzúra elkerülésében."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Szükségem van egy alternatÃv módra a Hidak beszerzéséhez!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Egy másik módja, hogy megkapd a bridge listát küldj emailt a %s cÃmre. Kérlek vedd figyelembe\naz emailt az alábbi email szolgáltató cÃmekrÅl küldd:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "A HÃdjaim nem működnek! SegÃtségre van szükségem!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ha a Tor böngészÅd nem működik, akkor küldj egy email-t %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Probáljon minél több információt megadn amennyit csak tud, beleértve a listáját \na %s hidaknak és Pluggable Transports -nak, amelyet használni próbált, A Tor Browser verzióját,\nés minden üzenetet melyet Tor adott ki, stb."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Itt vannak a hÃd soraid:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Hidak szerzése!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Kérem, válasszon opciót a hÃd tÃpushoz:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Kellenek önnek IPv6 cÃmek?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Szüksége van egy %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Ez a böngészÅ nem jelenÃti meg a képeket rendesen."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Adja meg a karaktereket amik a fenti képen láthatóak ..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Hogyan kezdjük használni a hidat "
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "A hidak megadásához a Tor Browser -hez, kövese az utasÃtásokat a %s Tor\nBrowser letöltés oldalán %s a Tor Browser indÃtásához."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Amikor a 'Tor Hálózati BeállÃtások' dialógus felugrik, kattintson a 'Konfigurálás'-ra és kövesse\na varászlót amÃg az kéri hogy:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokkolja vagy cenzúrázza az ön internet szolgáltatója (ISP) a kapcsolatokat\na Tor hálózatához?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Válassza hogy 'Igen' és utána kattintson a 'KövetkezÅ\"-re. Az ön új hidjai konfigurásához, másolja\nés illessze be a HÃd sorokat a felugró ablakba. Végül, kattintson 'Kapcsolódás'-ra, és \nmár készen is van. Ha valamilyen hibát tapasztal, próbáljon a 'SegÃtség'\n gombra kattintani a 'Tor hálózati beállÃtások' varázslóban a tobábbi segÃtségért."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "MegjelenÃti ezt az ütenetet."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Nem csatlakoztatható szállÃtó hÃd kérelme."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPV6 hÃd kérése."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Egy csatlakoztatható szállÃtó kérelme TYPE szerint."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Másolat szertése a BridgeDB's publikus GnuPG kulcsából."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Hiba jelentése"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Forrás kód"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Változások"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kapcsolat"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Mindent kijelöl"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "QR Kód megjelenÃtése"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR Kód a hÃd sorokhoz"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Hoppá!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Ãgytűnik a QR Kód hibás."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ez a QR Kód a hÃd sorait tartalmazza. Olvassa be egy QR Kód olvasóval, hogy átmásolja a HÃd sorokat mobil és egyéb eszközeire."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Jelenleg nincsenek rendelkezésre álló hidak..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Talán megpróbálhatnál %s vissza menni %s és másik HÃd tÃpust választani."
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Lépés %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Letöltés %s Tor BöngészŠ%s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Lépés %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s Hidak %s szertése"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Lépés %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Most %s a hidak hozzáadása a Tor BöngészÅhöz %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sC%ssak adjál már nekem hidakat!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Haladó beállÃtások"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nem"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "Semmi"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sS%szeretnék!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sS%szerezni Bridge -et!"
diff --git a/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..1449290
--- /dev/null
+++ b/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,101 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# MasIs <is.roadster at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-12-10 10:16+0000\n"
+"Last-Translator: MasIs <is.roadster at gmail.com>\n"
+"Language-Team: Indonesian (http://www.transifex.com/projects/p/torproject/language/id/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: id\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "Apa itu 'bridge'?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s 'Bridge relays' %s adalah perelai Tor yang membantu Anda mengelakkan sensor."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Adakah cara lain untuk memperoleh 'bridges'?"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Cara lain untuk memperoleh alamat 'public bridge' adalah dengan mengirimkan email (dari sebuah %s atau %s alamat) ke %s yang hanya berisi 'get bridges' pada isi email."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "Bridge saya gagal! Bantu saya!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Jika Tor Anda gagal bekerja, Anda harus menulis email %s. Cantumkan seluruh permasalahan yang Anda dapati, termasuk daftar 'bridge' yang Anda gunakan, nama file 'bundle' atau versi yang Anda pakai, pesan error Tor, dan sebagainya."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Untuk menggunakan baris di atas, pilih halaman pengaturan Vidalia's Network, dan klik \"My ISP blocks connections to the Tor network\". Kemudian tambahkan alamat 'bridge' satu per satu."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "Saat ini tidak ada 'bridge' yang tersedia."
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Ubah browser Anda menjadi Firefox."
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Ketikkan kedua kata tersebut."
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Langkah 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Dapatkan %s bundel Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Langkah 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Dapatkan %s 'bridge' %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Langkah 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "%s Tambahkan 'bridge' ke Tor %s"
diff --git a/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..c2b1bf8
--- /dev/null
+++ b/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,391 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# fetidyoo <tru74368 at yahoo.com>, 2011
+# Francesca Ciceri <madamezou at zouish.org>, 2014
+# HostFat <hostfat at gmail.com>, 2015
+# ironbishop <ironbishop at fsfe.org>, 2011
+# ironbishop <ironbishop at fsfe.org>, 2011
+# Jacob Appelbaum <jacob at appelbaum.net>, 2009
+# Luca Marzo <luca at jeckodevelopment.it>, 2011
+# n0on3 <a.n0on3 at gmail.com>, 2011
+# Paolo Stivanin <paolostivanin at gmail.com>, 2014
+# Random_R, 2013
+# Random_R, 2013-2014
+# fetidyoo <tru74368 at yahoo.com>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-02 02:41+0000\n"
+"Last-Translator: HostFat <hostfat at gmail.com>\n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/torproject/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Siamo spiacenti ma qualcosa è andato storto con la tua richiesta."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Questo è un messaggio automatico, si prega di non rispondere.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Ecco i tuoi bridge:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Hai superato il limite massimo. Per favore rallenta! Il tempo minimo tra le email\nè di %s ore. Tutte le altre email durante questo periodo di tempo verranno ignorate."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combina i COMMANDs per specificare opzioni multiple simultaneamente)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Benvenuto/a su BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Transport TYPEs attualmente supportati:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hey, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Ciao amico!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Chiavi Pubbliche"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Questa email è stata generata con arcobaleni, unicorni e scintille\nper %s il %s alle %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB può fornire dei bridge con numerosi %stipi di Pluggable Transports%s,\ni quali possono aiutare ad offuscare la tua connessione alla Rete Tor, rendendo\npiù difficile a chiunque vedere il tuo traffico internet per determinare che stai\nusando Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Sono disponibili anche alcuni bridge con indirizzi IPv6, sebbene alcuni Pluggable\nTransports non siano compatibili con IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "In aggiunta, BridgeDB ha anche numerosi bridge vanilla %s senza alcun\nPluggable Transports %s, il che forse non suona bene, ma possono ancora\naiutare ad aggirare la censura in internet in molti casi.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Cosa sono i bridge?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridge %s sono relay di Tor che ti aiutano ad aggirare la censura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Mi serve un altro modo per avere dei bridge!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Un altro modo di ottenere i bridge è inviare un'email a %s. Nota che devi\ninviare l'email usando uno degli indirizzi tra i seguenti provider email:\n%s, %s o %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "I miei bridge non funzionano! Mi serve aiuto!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Se Tor non ti funziona, manda un'email a %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Cerca di includere più informazioni possibili sul tuo caso, incluso l'elenco di\nbridge e Pluggable Transports che hai provato a usare, la versione di Tor Browser,\nqualsiasi messaggio ti abbia mostrato Tor, ecc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Ecco le tue linee bridge:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Ottieni dei Bridge!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Seleziona le opzioni per il tipo di bridge:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Ti servono indirizzi IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Ti serve un %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Il tuo browser non mostra le immagini in modo corretto."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Inserisci i caratteri nell'immagine sopra..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Come iniziare a usare i tuoi bridge"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Per inserire i bridge nel Tor Browser, segui le istruzioni nella %s pagina\ndi download di Tor Browser %s per avviare Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Quando appare la finestra delle 'Impostazioni Rete Tor', clicca 'Configura' e\nsegui la procedura giudata finchè non chiede:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Il tuo Internet Service Provider (ISP) blocca o censura le connessioni alla\nrete Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Seleziona 'Sì' e poi clicca 'Avanti'. Per configurare i nuovi bridge, copia e\nincolla le linee bridge nella casella di testo. Infine, clicca 'Connetti' e dovrebbe\nessere tutto pronto! Se avrai problemi, prova a cliccare il pulsante 'Aiuto'\nnella procedura guidata 'Impostazioni Rete Tor' per avere assistenza."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Mostra questo messaggio."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Richiedi bridge vanilla."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Richiedi bridge IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Richiedi un Pluggable Transport by TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Ottieni una copia della chiave pubblica GnuPG di BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Segnala un Errore"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Codice Sorgente"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contatti"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Seleziona tutto"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Mostra QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode per le tue linee ponte"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Uh oh, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Sembra esserci stato un errore nell'ottenere il tuo QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Questo QRCode contiene le tue linee ponte. Scansionalo con un lettore QRCode per copiare le tue linee ponte su mobile o altri dispositivi."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Non ci sono bridge disponibili al momento..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Potresti provare a %s tornare indietro %s e scegliere un tipo di bridge diverso!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Passo %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Scarica %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Passo %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Ottenere dei %s bridge %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Passo %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Ora %s aggiungi i bridge a Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sD%sammi i bridge e basta!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opzioni Avanzate"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "No"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "nessuno"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sS%sì!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sttieni Bridge"
diff --git a/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..e552128
--- /dev/null
+++ b/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,362 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# brt <87 at itokei.info>, 2013
+# ABE Tsunehiko, 2014
+# ã¿ã«ãã· <gomidori at live.jp>, 2013
+# ã¿ã«ãã· <gomidori at live.jp>, 2014
+# Masaki Saito <rezoolab at gmail.com>, 2013
+# è¤åãç² <m1440809437 at hiru-dea.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-12-26 04:42+0000\n"
+"Last-Translator: è¤åãç² <m1440809437 at hiru-dea.com>\n"
+"Language-Team: Japanese (http://www.transifex.com/projects/p/torproject/language/ja/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ja\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "ç³ã訳ããã¾ãããããªã¯ã¨ã¹ãã«åé¡ãããã¾ããã"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[ãã®ã¡ã¼ã«ã¯èªåéä¿¡ãããã¡ãã»ã¼ã¸ã§ããè¿ä¿¡ããªãã§ãã ããã]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "ããªãã®ããªãã¸ã¯ãã¡ã:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "ã¬ã¼ãå¶éãè¶
éãã¾ãããã¡ã¼ã«éã®æå°æé㯠%s æéã§ãããã®æéä¸ã«ããã«éä¿¡ããã¡ã¼ã«ã¯å
¨ã¦ç¡è¦ããã¾ãã"
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "ã³ãã³ã: (ã³ãã³ããçµã¿åããã¦ãåæã«è¤æ°ã®ãªãã·ã§ã³ãæå®ãã)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "BridgeDB ã¸ããããï¼"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "ç¾å¨ãµãã¼ãããã¦ãããã©ã³ã¹ãã¼ãã¿ã¤ã:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "ã©ãã %s ããï¼"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ã©ãããããã«ã¡ã¯ï¼"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "å
¬ééµ"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "ãã®ã¡ã¼ã«ã¯ %s ã« %s %s ã« rainbow åã³ unicorn ã sparkle ã§çæããã¾ããã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB ã¯ã%sã¿ã¤ãã® Pluggable Transports%s ã§ããªãã¸ãæä¾ãã¾ãã\nTor ãããã¯ã¼ã¯ã¸ã®æ¥ç¶ãæããã¥ãããããã¨ã«å½¹ç«ã¡ãããªãã®\nã¤ã³ã¿ã¼ããããã©ãã£ãã¯ãç£è¦ãã¦ãã誰ããããªãã Tor ã使ç¨ãã¦ãããã¨ãå¤å¥ãããã¨ãããã«é£ãããªãã¾ãã\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "IPv6 ã®ã¢ãã¬ã¹ã®ããªãã¸ãå©ç¨ã§ãããã®ãããã¾ããã Pluggable Transports ã«ã¯ IPv6 ã«äºææ§ããªããã®ãããã¾ãã\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "å ãã¦ã BridgeDB ã¯ã %s Pluggable Transport %s ã®ãªãå¤ãã®ä½ã®å¤å²ããªã\né常ã®ããªãã¸ãæã¡ãããã¯ã¯ã¼ã«ã«ã¯æããªãããããã¾ãããããããå¤ãã®å ´åãã¤ã³ã¿ã¼ãããæ¤é²ãåé¿ããã®ã«ãªãæå¹ã§ãã\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "ããªãã¸ã¨ã¯ï¼"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s ããªã㸠%s ã¯ã ã¤ã³ã¿ã¼ãããæ¤é²ãåé¿ããå©ãã¨ãªã Tor ãªã¬ã¼ã§ãã"
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "ããªãã¸ãå¾ãä»ã®æ¹æ³ãå¿
è¦ã§ã!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ããªãã¸ãå
¥æããå¥ã®ããæ¹ã¯ %s ã¾ã§ã¡ã¼ã«ãéããã¨ã§ãã\n以ä¸ã®ã¡ã¼ã«ãããã¤ãã®ãã¡ã®1ã¤ã®ã¢ãã¬ã¹ã使ç¨ãã¦ã¡ã¼ã«ãéä¿¡ããªããã°ãªããªããã¨ã«ã注æãã ãã:\n %s ã %s ã¾ã㯠%s"
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ç§ã®ããªãã¸ãåãã¾ãã! å©ãã¦ãã ãã!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Tor ããã¾ãåä½ããªãå ´åã %s ã¾ã§ã¡ã¼ã«ãä¸ããã"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ããªãã使ããã¨ããããªãã¸ã Pluggable Transports ã ãå©ç¨ã® Tor Browser ãã¼ã¸ã§ã³ããã㦠Tor ãåºåããã¡ãã»ã¼ã¸çãªã©ãå«ã¿ãåºæ¥ãéãããªãã®ã±ã¼ã¹ã«é¢ããå¤ãã®æ
å ±ãå«ãã¦ã¿ã¦ãã ããã"
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "ããªãã®ããªãã¸ã©ã¤ã³ã¯ãã¡ãã§ã:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ããªãã¸ãå
¥æï¼"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ããªãã¸ã¿ã¤ãã®ãªãã·ã§ã³ãé¸æãã¦ãã ãã:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "IPv6 ã¢ãã¬ã¹ãå¿
è¦ã§ããï¼"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "%s ãå¿
è¦ã§ããï¼"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "ãå©ç¨ã®ãã©ã¦ã¶ã¯é©åã«ç»åã表示ãã¦ãã¾ããã"
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "ä¸è¨ã®ç»åããæåãå
¥åãã¦ãã ãã..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "ããªãã¸ä½¿ç¨ã®å§ãæ¹"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Tor Browser ã«ããªãã¸æ
å ±ãå
¥åããã«ã¯ã %s Tor Browser ã®ãã¦ã³ãã¼ããã¼ã¸ %s ã®æ示ã«å¾ã£ã¦ Tor Browser ãèµ·åãã¦ãã ããã"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "ãTor ãããã¯ã¼ã¯è¨å®ããã¤ã¢ãã°ããããã¢ããããéãè¨å®ãã¯ãªãã¯ãã¦\nèãããã¾ã§ã¦ã£ã¶ã¼ãã«å¾ã£ã¦ãã ããã"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ãå©ç¨ã®ã¤ã³ã¿ã¼ããããµã¼ãã¹ãããã¤ãã¼ (ISP) 㯠Tor ãããã¯ã¼ã¯ã¸ã®æ¥ç¶ããããã¯ãªãã\nå¥ã®ããæ¹ã§æ¤é²ãã¦ãã¾ããï¼"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ãã¯ãããé¸æãã¦ãã次ããã¯ãªãã¯ãã¦ãã ãããæ°ããããªãã¸ãæ§æããããã«ãããã¹ã\nå
¥åããã¯ã¹ã«ããªãã¸ã©ã¤ã³ãã³ãã¼ãã¼ã¹ããã¦ãã ãããæå¾ã«ããæ¥ç¶ããã¯ãªãã¯ãã¦ã\nããæºåãåºæ¥ãã¯ãã§ãï¼ä½ãåé¡ããã£ããããããªãå©ããå¾ãããã«ãTor\nãããã¯ã¼ã¯è¨å®ãã¦ã£ã¶ã¼ãå
ããã«ãããã¿ã³ãã¯ãªãã¯ãã¦ãã ããã"
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "ãã®ç»åã表示"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "vanilla ããªãã¸ããªã¯ã¨ã¹ã"
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPv6 ããªãã¸ããªã¯ã¨ã¹ã"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "ã¿ã¤ããã¨ã« Pluggable Transport ããªã¯ã¨ã¹ã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "BridgeDB ã® GnuPG å
¬ééµã®ã³ãã¼ãæã«å
¥ãã¾ãããã"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ãã°ãå ±åãã"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "ã½ã¼ã¹ã³ã¼ã"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "å¤æ´å±¥æ´"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "ãåãåãã"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "ãã£ã¨ã¹ãã²ããã£ï¼"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "ä»ã®æç¹ã§ã¯å©ç¨ã§ããããªãã¸ãããã¾ãã..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "ãããã ã %s æ»ã£ã¦ %s ç°ãªãããªãã¸ã¿ã¤ããé¸æãã¦ã¿ãã¹ãã§ãããã"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "ã¹ããã %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "%s Tor Browser %s ããã¦ã³ãã¼ã"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "ã¹ããã %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s ããªã㸠%s ãæã«å
¥ãã"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "ã¹ããã %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "%s Tor Browser ã«ããªãã¸ã追å ãã¾ã %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sã%sã®ããã ããã bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "é«åº¦ãªè¨å®"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "ããã"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ãªã"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sã¯%sãï¼"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..261d478
--- /dev/null
+++ b/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,360 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Seng Sutha <sutha at open.org.kh>, 2014
+# Sokhem Khoem <sokhem at open.org.kh>, 2014
+# Sok Sophea <sksophea at gmail.com>, 2014
+# Sok Sophea <sksophea at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: Sokhem Khoem <sokhem at open.org.kh>\n"
+"Language-Team: Khmer (http://www.transifex.com/projects/p/torproject/language/km/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: km\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "áá¼áááá! á¢ááá¸âáá½áâáá¶áâááá á»áâáá¶áá½áâáááá¾âááááâá¢áááá"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[áááâáá¶âáá¶áâááááááááááááá·; áá¼áâáá»áâáááá¾áááá]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "áááâáá¶âááááá¸áááâááááâá¢áááá"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "You have exceeded the rate limit. Please slow down! The minimum time between\nemails is %s hours. All further emails during this time period will be ignored."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "áááá¶ááááâáááá¶áá BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "áá
áá
á»ááááááâááâáááááâáá¶áâáá¶áááá TYPEs á"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "á á, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "áá½áááá¸, áá·ááááááááá·!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "ááâáá¶áá¶ááá"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "á¢áá¸áááâáááâáááá¼áâáá¶áâááááá¾áâá¡á¾áââáá¶áá½á rainbows, unicorns, áá·á sparkles\nááááá¶áá %s áá¾ %s áá
%s á"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB áá·áâá¢á¶á
âáááááâááááá¸áááâáá¶áá½áâáááááá %stypes áá½áâá
ááá½áâáá Pluggable Transports%s,\náááâá¢á¶á
âáá½á obfuscate áá¶áâááááá¶ááâááááâá¢áááâáá
âáá¶ááâááááá¶á Tor , áááá¾âá²ááâáá¶âáá¶áâáá¶áâáá·áá¶áâááááá¶ááââáá¾áâá
áá¶á
áááâááááâá¢áááâáááá»áâáá¶áâáááááâáá¶âá¢áááâáááá»áâáááá¾ Tor á\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Some bridges with IPv6 addresses are also available, though some Pluggable\nTransports aren't IPv6 compatible.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\nPluggable Transports %s which maybe doesn't sound as cool, but they can still\nhelp to circumvent internet censorship in many cases.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "áá¾âááááá¸áááââáá¶âá¢ááá¸?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "áááá»áâáááá¼ááá¶áâáá·áá¸áá¶áááááâáááá¶áááááá¶âááâáá¶áâááá½áâáá¶áâááááá¸ááá!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "áá·áá¸áá½áâáááâáá¾áááá¸ááá½á bridges ááºâáááá¼áâáááá¾âá¢áá¸áááâáá
%s á áá¼áâá
ááá¶áâáᶠá¢ááááááá¼áâááâáááá¾âá¢áá¸áááâáááâáááá¾á¢á¶áááááá¶áâáá¸âáááá»áá áá»áâáááááá¢áá¸áááâáá½áâáááá»áâá
ááááâáááá»áá áá»áâáá¶áâáááááá\n%s, %s ᬠ%s á"
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ááááá¸áááââááááâáááá»áâáá·áâáááá¾ááá¶á! áááá»áâáááá¼ááá¶áâáááá½á!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "ááááá·ááá¾ Tor ááááâá¢áááâáá·áâáááá¾ááá¶á, á¢áááâáá½áááâá¢áá¸ááá %s á"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Try including as much info about your case as you can, including the list of\nbridges and Pluggable Transports you tried to use, your Tor Browser version,\nand any messages which Tor gave out, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "áááâáá¶âáááâááááá¸áááâááááâá¢áááá"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ááá½áâááâááááá¸ááá!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "áá¼áâáááá¾áâááááá¾áâááááá¶ááâááááááâááááá¸áááá"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "áá¾âá¢áááâáááá¼áâáá¶áâá¢á¶áááááá¶á IPv6 á¬?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "áá¾âá¢áááâáááá¼áâáá¶á %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "áááááá·áá¸âá¢áá¸áááºáá·áâááááâá¢áááââáá·áâáááá»áâáááá á¶áâáá¼ááá¶áâáááâáááá¹ááááá¼áâá¡á¾áá"
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "áááá
á¼áâáá½âá¢ááááâáá¸âáá¼ááá¶áâáá¶ááá¾..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "ááááâá
á¶áááááá¾áâáááá¾âááááá¸áááâááááâá¢ááá"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "áá¾áááá¸âáááá
á¼áâááááá¸áááâáá
âáá¶áá Tor Browser, á¢áá»ááááâááá
áááá¸âáááá¶áâáá
âáá¾âáááááâ %s Tor\nBrowser %s áá¾áááá¸âá
á¶áááááá¾á Tor Browser á"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "áá
âáááâáááá¢ááâ 'áá¶áâáááááâááááá¶á' ááá
âá¡á¾á, á
á»á
'áááááâáá
áá¶áááááááá' áá·áâá¢áá»ááááâá¢áááâáááá½ááá¶áâáá á¼áâáááâáá¶ááá·á
áá
âááááâá¢áááá"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Does your Internet Service Provider (ISP) block or otherwise censor connections\nto the Tor network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\npaste the bridge lines into the text input box. Finally, click 'Connect', and\nyou should be good to go! If you experience trouble, try clicking the 'Help'\nbutton in the 'Tor Network Settings' wizard for further assistance."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "áááá á¶áâáá¶áâáááá"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "áááá¾âááááá¸áááâáá¼ááááá¶áá"
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "áááá¾âááááá¸ááá IPv6 á"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "áááá¾âáá¶áâáááááâáááâá¢á¶á
âáááâáá¶áâáá¶áâááá TYPE á"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Get a copy of BridgeDB's public GnuPG key."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ááá¶ááá¶áááâááá á»á"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "áá¼áâááááá"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Changelog"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "áááá¶áááááá"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Uh oh, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "áá
áá
á»ááááááâáá·áâáá¶áâááááá¸áááâáá½áâá
ááá½áâá¡á¾á..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr " áááá ááâáá¶âá¢áááâáá½áááâáááá¶áá¶á %s áááá¡ááâáá
âáá¶áá %s á á¾áâáááá¾áâááááááâááááá¸áááâááááá!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "ááá á¶á %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "áá¶áâáá %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "ááá á¶á %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "áá %s ááááá¸ááá %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "ááá á¶á %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "á¥á¡á¼áâ %s ááááááâááááá¸áááâáá
âáá¶áá Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust áááááâá²ááâáááá»áâáá¼áâááááá¸ááá!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "ááááá¾áâááááá·áâááááá"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "áá"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "áááá¶á"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set ááááá¸ááá"
diff --git a/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..905e22d
--- /dev/null
+++ b/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,102 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# msj2, 2013
+# msj2, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-07-31 03:40+0000\n"
+"Last-Translator: msj2\n"
+"Language-Team: Kannada (http://www.transifex.com/projects/p/torproject/language/kn/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: kn\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "ಬà³à²°à²¿à²¡à³à²à³ à²à²³à³ à²
à²à²¦à²°à³ à²à²¨à³?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%sಬà³à²°à²¿à²¡à³à²à³âನ ರಿಲà³à²à²³à³%s à²
à²à²¦à²°à³ ಸà³à²¨à³à²¸à²¾à²°à³âà²à²³à²¿à²à²¦ ನಿಮà³à²®à²¨à³à²¨à³ ದà³à²°à²µà²¿à²¡à³à²µ à²à²¾à²°à³ ರಿಲà³à²à²³à³, "
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "ನನà²à³ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ ಸಿà²à³à²µ ಬದಲಿ ಮಾರà³à² ಬà³à²à³"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "ಪಬà³à²²à²¿à²à³ ಬà³à²°à²¿à²¡à³à²à³âà²à²³ à²
ಡà³à²°à³à²¸à³ ಪಡà³à²¯à³à²µ ದಾರಿಯà²à²¦à³à²°à³, %s ಠವಿಳಾಸà²à³à²à³ à²à²®à³à²²à³ à²à²³à²¿à²¸à²¿ (à²à²à²¦ a %s à²
ಥವಾ a %s à²
ಡà³à²°à³à²¸à³ ) à²à²µà²°à²¿à²à³. ಠ'get bridges' à²
à²à²¤ à²à²®à³à²²à³ à²à²³à²à³ ಬರà³à²¯à²¿à²°à²¿."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "ನನà³à²¨ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ à²à³à²²à²¸ ಮಾಡà³à²¤à²¿à²²à³à²². ಸಹಾಯ ಮಾಡಿ"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "ನಿಮà³à²® à²à²¾à²°à³ à²à³à²²à²¸ ಮಾಡದೠà²à²¦à³à²°à³, %s à²à³ à²à²®à³à²²à³ à²à²³à²¿à²¸à²¿. ಬà³à²°à²¿à²¡à³à²à³âà²à²³ ಪà²à³à²à²¿, ಬà²à²¡à²²à³âನ ಫ಼à³à²²à³â ಹà³à²¸à²°à³/ವರà³à²·à²¨à³, à²à²¾à²°à³ à²à³à²à³à² ಮà³à²¸à³à²à³âà²à²³à³, à²à²°à²°à³ ಮà³à²¸à³à²à³âà²à²³à³, ಠರà³à²¤à²¿ ಸಾಧà³à²¯à²µà²¿à²¦à³à²¦à²·à³à²à³ ಮಾಹಿತಿಯನà³à²¨à³ à²à²®à³à²²à³âನಲà³à²²à²¿ à²à²³à²¿à²¸à²¿."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "ಮà³à²²à³à²à²à²¡ ಲà³à²¨à³âà²à²³à²¨à³à²¨ ಬಳಸಲà³, ವಿಡಲಿಯಾದ à²à²¾à²²à²¬à²à²§ ಸà³à²à³à²à²¿à²à²à³â ಪà³à²à²à³à² ಹà³à²à²¿, à²
ಲà³à²²à²¿ \"My ISP blocks connections to the Tor network\" à²à²¨à³à²¨à³à²µà³à²¦à²° ಮà³à²²à³ à²à³à²²à²¿à²à³ ಮಾಡಿ. ನà²à²¤à²°, à²à²à²¦à³à²à²¦à³ ಬà³à²°à²¿à²¡à³à²à³â à²
ಡà³à²°à³à²¸à³âನ à²à³à²¡à³à²¤à³à²¤à²¾ ಹà³à²à²¿."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "ಯಾವ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ ಸದà³à²¯à²à³à²à³ ಲà²à³à²¯à²µà²¿à²²à³à²²"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "ನಿಮà³à²® ಬà³à²°à³à²¸à²°à³âನ ಫ಼à³à²°à³âಫ಼ಾà²à³à²¸à³âà²à³ ಬದಲಿಸಿà²à³à²³à³à²³à²¿"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "à²à²°à²¡à³ ಪದà²à²³à²¨à³à²¨à³ à²à³à²ªà³ ಮಾಡಿ"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "ಹà³à²à³à²à³ ೧"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "ಪಡà³à²¯à²¿à²°à²¿ %s à²à²¾à²°à³ ಬà³à²°à³à²¸à²°à³ ಬà²à²¡à²²à³ %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "ಹà³à²à³à²à³ ೨"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ಪಡà³à²¯à²¿à²°à²¿ %s ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "ಹà³à²à³à²à³ ೩"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "à²à²¦à³à², %s ಠಬà³à²°à²¿à²¡à³à²à³âà²à²³à²¨à³à²¨ %s à²à²¾à²°à³âà²à³ à²à³à²¡à²¿à²¸à²¿à²à³à²³à³à²³à²¿."
diff --git a/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..e877b87
--- /dev/null
+++ b/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,104 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# ilbe123 <a3057016 at drdrb.net>, 2014
+# cwt96 <cwt967 at naver.com>, 2012
+# Dr.what <javrick6 at naver.com>, 2014
+# pCsOrI <pcsori at 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: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2014-05-06 06:40+0000\n"
+"Last-Translator: ilbe123 <a3057016 at drdrb.net>\n"
+"Language-Team: Korean (http://www.transifex.com/projects/p/torproject/language/ko/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ko\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "'ë¸ë¦¿ì§'ë?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "ì¤ê³ ìë² ë¸ë¦¿ì§ %s ì¤ìì, %s ê° ì¬ë¬ë¶ì ê²ì´ ì°í를 ëìì¤ ì ìë Tor ì¤ê³ ìë² ì
ëë¤."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "ë¸ë¦¿ì§ë¥¼ ì»ë ë¤ë¥¸ ë°©ë²ì´ ììê¹ì?"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "ê³µê°ë ë¸ë¦¿ì§ 주ì를 ì°¾ì ë, íµì ì ì 'get brides'ë¼ê³ ì´ë©ì¼ì ì ì´ì ë³´ë´ë ë°©ë²ë ììµëë¤.(ë©ì¼ì ë³´ë´ë ì¬ëì 주ì í기ë from %s ëë %s 주ì)"
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "ë¸ë¦¿ì§ê° ë§ì´ ê°ì´ì! ëìì¤ì!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "ê·íì í ë¥´ê° ìëíì§ ìëë¤ë©´, %sì ì´ë©ì¼ì ë³´ë´ì
ì¼ í©ëë¤. ê°ë¥í í ë§ì ì 보를 í¬í¨í´ì ë³´ë´ì£¼ì¸ì. ì를 ë¤ë©´ ê·íê° ì¬ì©í ë¸ë¦¿ì§ì 목ë¡, ê·íê° ì¬ì©íë ë²ë¤ì íì¼ ì´ë¦ê³¼ ë²ì , ê·íì í ë¥´ê° ë§ ê°ì ë ëì¨ ë©ì¸ì§. ì´ ì¸ìë ì ì ì ìë ì¬ë¬ ê°ì§."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "ì íµì ì ì ì¬ì©íìë ¤ë©´ 'Vidalia's Network settings'-ë¹ë¬ë¦¬ì ë¤í¸ìí¬ ì¤ì -íì´ì§ì ë¤ì´ê° í, 'My ISP blocks connections to the Tor network'-Tor ë¤í¸ìí¬ì ë´ ISP ì°ê²°ì ì°¨ë¨-ì í´ë¦í©ëë¤. ê·¸ë¬ë©´ íë²ì ê°ê°ì ë¸ë¦¿ì§ 주ì를 ì¶ê° í ì ììµëë¤."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "íì¬ ì´ì© ê°ë¥í ë¸ë¦¿ì§ ìì"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "ë¹ì ì ë¸ë¼ì°ì 를 íì´ì´íì¤ë¡ ì
ê·¸ë ì´ë íì¸ì!"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "ëê°ì ë¨ì´ë¥¼ ì
ë ¥íììì¤"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "1ë¨ê³"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Tor %s Browser Bundle %s를 ì
ì"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "2ë¨ê³"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s ë¸ë¦¿ì§ %s를 ì
ì"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "3ë¨ê³"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "곧 ë°ë¡ %s Tor ë¸ë¦¬ì§ë¥¼ ì¶ê° %s"
diff --git a/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..0f70c18
--- /dev/null
+++ b/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,359 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# OjÄrs Balcers <ojars.balcers at gmail.com>, 2012
+# OjÄrs Balcers <ojars.balcers at gmail.com>, 2013-2014
+# ThePirateDuck <thepirateduck.w at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: ThePirateDuck <thepirateduck.w at gmail.com>\n"
+"Language-Team: Latvian (http://www.transifex.com/projects/p/torproject/language/lv/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: lv\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Atvainojiet! Notikusi ar Jūsu pieprasījumu saistīta kļūme."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Å is ir automÄtisks ziÅojums; lÅ«dzu neatbildiet.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Te ir JÅ«su tilti:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "JÅ«s esat pÄrsniedzis pÄrraides normu. LÅ«dzu, lÄnÄk. MinimÄlais laika ilgums starp\ne-pastiem ir %s stundas. Å ajÄ laika posmÄ visi turpmÄkie e-pasti tiks ignorÄti."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (apvienot KOMANDA's, lai vienlaicÄ«gi norÄdÄ«tu dažÄdas opcijas)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "SveicinÄti BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Šobrīd atbalstītie transporta VEIDI:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hei, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Sveiks, draug!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "PubliskÄs atslÄgas"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "This email was generated with rainbows, unicorns, and sparkles\npriekÅ¡ %s dienÄ %s pl %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB var nodroÅ¡inÄt tiltus ar dažÄdiem %stypes Pluggable Transports%s,\nkas var palÄ«dzÄt maskÄt JÅ«su savienojumu ar Tor Network, tÄdÄjÄdi padarot sarežģītÄk ikvienam, kas seko JÅ«su interneta datu plÅ«smai, noteikt, ka lietojat Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Ir pieejami daži tilti ar IPv6 adresÄm; tanÄ« pat laikÄ daži Pluggable\nTransports nav savietojami ar IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "TurklÄt BridgeDB ir pietiekami daudz parastu, vienkÄrÅ¡u tiltu %s bez jebkÄdiem\nPluggable Transports %s, kas iespÄjams neizklausÄs tik inÄÄ«gi, bet arÄ« tie var\ndaudzos gadÄ«jumos palÄ«dzÄt apiet interneta cenzÅ«ru.\n \n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Kas ir tilti?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Tilti %s ir Tor retranslatori, kas palīdz Jums apiet cenzūru."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Man nepieciešams alternatīvs tiltu iegūšanas veids!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "VÄlviens veids kÄ saÅemt tiltus ir nosÅ«tÄ«t e-pastu uz %s. LÅ«dzu, ievÄrojiet, ka e-pasts ir\njÄnosÅ«ta no viena no sekojoÅ¡ajiem e-pasta pakalpojumu sniedzÄjiem:\n%s, %s vai %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mani tilti nestrÄdÄ! Man nepiecieÅ¡ama palÄ«dzÄ«ba!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ja JÅ«su Tor nestrÄdÄ, Jums jÄnosÅ«ta e-pasts %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Centieties iekļaut pÄc iespÄjas daudz informÄciju par savu situÄciju, tostarp pievienojot to tiltu un Pluggable Transports sarakstu, kurus centÄties izmantot, savu Tor Browser versiju un visus Tor ziÅojumus, un citu lÄ«dzÄ«gu informÄciju."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Te ir Jūsu tiltu līnijas:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "SaÅemt Tiltus!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "LÅ«dzu, izvÄlieties tilta veida opcijas:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Vai ir nepieciešamas IPv6 adreses?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Vai ir nepieciešams %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "JÅ«su pÄrlÅ«ks neattÄlo attÄlus pareizi."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "IevadÄ«t burtus no augstÄk parÄdÄ«tÄ attÄla..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "KÄ sÄkt izmantot JÅ«su tiltus"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Sekojiet instrukcijÄm Tor %s, lai ievadÄ«tu tiltus Tor Broswer\nPÄrlÅ«ka lejuplÄdes lapa %s , lai startÄtu Tor Browser. "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Kad uznirst dialogs \"Tor tÄ«kla iestatÄ«jumi\", noklikÅ¡Ä·iniet \"KonfigurÄt\" un sÄkojiet\nvednim lÄ«dz tas jautÄ:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Vai JÅ«su Interneta pakalpojumu sniedzÄjs (ISP) bloÄ·Ä vai citÄdÄ veidÄ cenzÄ savienojumus\nar tÄ«klu Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Atlasiet \"JÄ\" un tad noklikÅ¡Ä·iniet \"TÄlÄk\". Lai konfigurÄtu savus jaunos tiltus, kopÄjiet un\nielÄ«mÄjiet tiltu lÄ«nijas teksta ievades lodziÅÄ. BeigÄs nokliÅ¡Ä·iniet \"Izveidot savienojumu\" un\nvisam vajadzÄtu notikt! Ja ir problÄmas, turpmÄkai palÄ«dzÄ«bai pamÄÄ£iniet nokliÅ¡Ä·inÄt vednÄ« \"Tor tÄ«kla iestatÄ«jumi\" pogu \"PalÄ«dzÄ«ba\"."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "RÄda ziÅojumu."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Pieprasīt parastos tiltus."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Pieprasīt IPv6 tiltus."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "PieprasÄ«t Pluggable Transport pÄc TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "SaÅemt BridgeDB publiskÄs GnuPG atslÄgas kopiju."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "ZiÅot par kļūdu"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Pirmkods"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "IzmaiÅu žurnÄls"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "SazinÄties"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Ak, man' dieniÅ!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Šobrīd nav pieejamu tiltu..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "IespÄjams, ka Jums jÄmÄÄ£ina %s atgriezties %s un izvÄlÄties citu tiltu veidu!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Solis %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "LejuplÄdÄt %s PÄrlÅ«ku Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Solis %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "SaÅemt %s tiltus %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Solis %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Tagad %s pievienot PÄrlÅ«kam Tor tiltus %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sT%sikai dodiet man tiltus!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "LietpratÄju opcijas "
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "NÄ"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "nekas"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sÄ!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sS%saÅemt tiltus"
diff --git a/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..d509e3c
--- /dev/null
+++ b/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,101 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# VHR <viktor.hr at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2014-03-12 22:21+0000\n"
+"Last-Translator: VHR <viktor.hr at gmail.com>\n"
+"Language-Team: Macedonian (http://www.transifex.com/projects/p/torproject/language/mk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: mk\n"
+"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "ШÑо Ñе пÑемоÑÑÑваÑа?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s Ñелеи за ÐÑемоÑÑÑваÑа %s Ñе Tor Ñелеи кои Ñи Ð¿Ð¾Ð¼Ð°Ð³Ð°Ð°Ñ Ð´Ð° го Ð·Ð°Ð¾Ð±Ð¸ÐºÐ¾Ð»Ð¸Ñ ÑензÑÑиÑаÑеÑо."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ðи ÑÑеба алÑеÑнаÑивен наÑин да добиÑам пÑемоÑÑÑваÑа!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "ÐÑÑг наÑин да Ñе наÑÐ´Ð°Ñ Ð°Ð´ÑеÑиÑе на ÑавниÑе пÑемоÑÑÑваÑа е да Ñе пÑаÑи е-поÑÑа (од %s или %s адÑеÑа) на %s Ñо ÑекÑÑ 'get bridges' во ÑодÑжинаÑа на поÑÑаÑа."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "ÐоиÑе пÑемоÑÑÑваÑа не ÑÑнкÑиониÑааÑ! Ðи ÑÑеба помоÑ!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Ðко ÑвоÑÐ¾Ñ Tor не ÑÑнкÑиониÑа, ÑÑеба да пÑаÑÐ¸Ñ Ðµ-поÑÑа на %s. ÐÑÐ¾Ð±Ð°Ñ Ð´Ð° внеÑÐµÑ ÐºÐ¾Ð»ÐºÑ ÑÑо Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾Ð²ÐµÑе инÑоÑмаÑии за ÑвоÑÐ¾Ñ ÑлÑÑаÑ, вклÑÑиÑелно и лиÑÑа од пÑемоÑÑÑваÑа кои ги коÑиÑÑиÑ, имеÑо/веÑзиÑаÑа на пакеÑÐ¾Ñ ÐºÐ¾Ñ Ð³Ð¾ коÑиÑÑиÑ, поÑакиÑе кои ги Ð´Ð¾Ð±Ð¸Ð²Ð°Ñ Ð¾Ð´ Tor, иÑн."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Ðа да ги коÑиÑÑÐ¸Ñ Ð³Ð¾Ñниве линии, оди на ÑÑÑанаÑа на подеÑÑваÑа на Vidalia's Network, и избеÑи \"ÐоÑÐ¾Ñ ISP ги блокиÑа вÑÑкиÑе кон мÑежаÑа на Tor\". ÐоÑоа Ð´Ð¾Ð´Ð°Ñ Ñа адÑеÑаÑа на Ñекое пÑемоÑÑÑваÑе еднаÑ."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "ÐÑемоÑÑÑваÑаÑа Ñе моменÑално недоÑÑапни"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "ÐадгÑади го ÑвоÑÐ¾Ñ Ð¿ÑегледÑÐ²Ð°Ñ Ð²Ð¾ Firefox"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "ÐапиÑи ги дваÑа збоÑа"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Ð§ÐµÐºÐ¾Ñ 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Ðеми %s Ð¿Ð°ÐºÐµÑ Ð¿ÑелиÑÑÑÐ²Ð°Ñ Tor %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Ð§ÐµÐºÐ¾Ñ 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Ðеми %s пÑемоÑÑÑваÑа %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Ð§ÐµÐºÐ¾Ñ 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Сега %s Ð´Ð¾Ð´Ð°Ñ Ð³Ð¸ пÑемоÑÑÑваÑаÑа во Tor %s"
diff --git a/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..94bd12a
--- /dev/null
+++ b/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,102 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# shahril <mohd_shahril_96 at yahoo.com>, 2013
+# Weldan Jamili <mweldan at 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: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-11-05 05:30+0000\n"
+"Last-Translator: shahril <mohd_shahril_96 at yahoo.com>\n"
+"Language-Team: Malay (Malaysia) (http://www.transifex.com/projects/p/torproject/language/ms_MY/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ms_MY\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "Apakah itu bridges ?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridge relays %s adalah tor relays yang akan membantu anda menghindari penapisan Internet."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "Saya perlukan cara alternatif untuk mendapatkan bridges!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "Cara lain bagi mencari alamat bridges awam adalah dengan menghantar email (dari %s atau alamat %s) ke %s dengan menulis 'get bridges' pada badan email tersebut."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "Bridges saya tidak berfungsi! Saya perlukan bantuan!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "Jika Tor kamu tidak berfungsi, kamu patut menghantar e-mel kepada %s. Cuba masukkan sebanyak mana info yang boleh terhadap permasalahan kamu, termasuk senarai bridges yang kamu guna, dan nama fail bundle dan versi bundle yang kamu guna, dan mesej yang Tor tunjukkan, dan lain-lain."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "Untuk menggunakan bridges di bawah, pergi ke halaman tetapan Rangkaian Vidalia, dan klik \"ISP saya menyekat sambungan saya ke rangkaian Tor\". Kemudian tambah setiap alamat bridges satu per satu."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "Tiada bridges pada masa sekarang."
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "Menaik taraf firefox anda"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "Tulis dua perkataan itu."
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "Langkah 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "Dapatkan %s Tor Browser Bundle %s."
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "Langkah 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Dapatkan %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "Langkah 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "Kemudian %s tambah bridges tersebut ke Tor %s"
diff --git a/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..1d5603e
--- /dev/null
+++ b/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,384 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Allan Nordhøy <comradekingu at gmail.com>, 2014
+# Harald <haarektrans at gmail.com>, 2014
+# lateralus, 2013
+# Per Thorsheim <transifex at thorsheim.net>, 2015
+# thor574 <thor.hovden at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-24 13:06+0000\n"
+"Last-Translator: Per Thorsheim <transifex at thorsheim.net>\n"
+"Language-Team: Norwegian Bokmål (http://www.transifex.com/projects/p/torproject/language/nb/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: nb\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Dette var leit! Noe gikk galt med forespørselen din."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Dette er en automatisert melding; vennligst ikke svar.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Her er dine broer:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Du har gått over hastighetsbegrensningen. Vennligst ta det med ro! Minste tid mellom e-poster er %s timer. Alle videre eposter i denne tidsperioden vil bli ignorert."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "KOMMANDOer: (kombiner KOMMANDer to å angi flere valg samtidig)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Velkommen til BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Nåværende støttede transport TYPEr:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hei, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hallo, lille venn!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Offentlige nøkler"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Denne e-posten ble laget med regnbuer, enhjørninger og stjerneskudd for %s på %s klokken %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "I BridgeDB finnes broer med flere %styper av pluggbare transporter%s,\nsom kan hjelpe deg med å tilsløre dine tilkoblinger til Tor-nettverket, noe som gjør det\nvanskelig for noen som overvåker din internett-trafikk å fastsette hvorvidt du\nbruker Tor eller ei\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Noen broer med IPv6-adresser er også tilgjelgelige, dog er noen pluggbare\nTransporter ikke IPv6-kompatible.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Merk også, BridgeDB har massevis av standardbroer med fabrikkoppsett %s uten\nnoen pluggbare transporter %s hvilket kanskje ikke høres så tøft ut, men de kan fremdeles\nbehjelpe omgåelse av internettsensur i de fleste fall.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Hva er broer?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Broer %s er Tor-tilknyttninger som hjelper deg med å omgå sensur."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Jeg trenger en alternativ måte å få broer på!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "En annen måte tilknytte seg broer er å sende en e-post til %s. Merk at du må sende\ne-post fra en adresse tilhørende en av følgende e-posttilbydere:\n%s, %s eller %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Broene mine virker ikke! Jeg trenger hjelp!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Hvis din Tor ikke virker, burde du skrive epost til %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Prøv å få med så mye info om dit tilfelle du kan, inkludert en liste over\nbroene og pluggbare transporter du prøvde å bruke, din Tor-nettleser-versjon,\nog alle meldinger Tor måtte produsere, osv."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Her er dine bro-linjer:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "FÃ¥ broer!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Gjør valg for brotype:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Trenger du IPv6-adresser?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Trenger du en %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Nettleseren din viser ikke bilder ordentlig."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Skriv inn bokstavene fra bildet ovenfor..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Hvordan starte med bruk av dine broer"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "For å oppføre broer i Tor-nettleseren, følg instruksjonene på %s Tor\nnettleser nedlastingsside %s for å starte Tor-nettleser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Når 'Tor nettverks-innstillinger' dialogboksen spretter opp, trykk på 'oppsett' og følg\nveiviseren til den forespør:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Sensurerer, eller blokkerer på annen måte, din internetttilbyder (ISP) tilkoblinger\ntil Tor-nettverket?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Velg 'Ja' og klikk så 'Neste'. For å sette opp nye broer, kopier og\nlim inn brolinjene i tekstboksen. Til slutt, trykk 'Koble til', og\ndu burde være klar til kamp! Hvis du får problemer, trykk 'Hjelp'\n-knappen i \"Tor-nettverksinnstillinger'-veiviseren for ytterligere hjelp."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Vis denne meldingen."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Forespørr broer med fabrikkoppsett."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Etterspør IPv6-broer."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Etterspørr pluggbar transport etter TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Få kopi av BridgeDBs offentlige GnuPG-nøkkel."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Rapporter en feil"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Kildekode"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Endringslogg"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Velg alle"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Vis QR kode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR kode for dine brolinjer"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "PÃ¥ tryne i myra!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Det oppsto en feil ved innhenting av din QR kode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Denne QR koden inneholder dine brolinjer. Skann den med en QR leser for å kopiere dine brolinjer over på mobile og andre enheter."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Det er for tiden ingen tilgjengelige broer..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Kanskje du bør prøve %s gå tilbake til %s og velge en annen brotype!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Steg %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Last ned %s Tor-nettleser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Steg %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Hent %s broer %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Steg %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "NÃ¥ %s legg til broer til Tor-nettleser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sB%sare gi meg noen broer!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Avanserte valg"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nei"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ingen"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sT%silknytt broer"
diff --git a/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..ac46c7e
--- /dev/null
+++ b/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,390 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Adriaan Callaerts <adriaan.callaerts at gmail.com>, 2013
+# Ann Boen <ann.boen at gmail.com>, 2014
+# Cleveridge <erwin.de.laat at cleveridge.org>, 2014
+# Dick, 2014
+# Johann Behrens <info at wmrkameleon.nl>, 2013
+# Shondoit Walker <shondoit at gmail.com>, 2011
+# Marco Brohet <inactive+therbom at transifex.com>, 2012
+# Tom Becht <tombecht at live.nl>, 2014
+# Tonko Mulder <tonko at tonkomulder.nl>, 2015
+# math1985 <transifex at matthijsmelissen.nl>, 2013
+# BBLN <webmaster at bbln.nl>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 09:50+0000\n"
+"Last-Translator: Tonko Mulder <tonko at tonkomulder.nl>\n"
+"Language-Team: Dutch (http://www.transifex.com/projects/p/torproject/language/nl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Sorry! Er is iets mis gegaan met je verzoek."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Dit is een automatisch bericht, gelieve niet te beantwoorden.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Hier zijn je bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Je hebt de rate limiet overschreden. Graag rustiger aan! De minimale tijd tussen\ne-mailberichten is %s uur. Alle verdere e-mails gedurende deze periode worden genegeerd."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combineer commando's om meerdere opties tegelijkertijd te specificeren)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Welkom bij BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Huidig ondersteunde transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hoi, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hallo, vriend!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Publieke Sleutels"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Deze email is gegenereerd met regenbogen, eenhoorns, en fonkelingen voor %s op %s om %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB kan voorzien in bridges met meerdere Pluggable Transports%s %stypes,\ndie helpen bij het verduisteren van uw connecties naar het Tor netwerk,\nwaardoor het moeilijker wordt voor anderen om uw internet traffic te bekijken en vast te stellen dat u Tor gebruikt.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Sommige bridges met IPv6 adressen zijn eveneens beschikbaar, maar sommige Pluggable\nTransports bieden geen ondersteuning voor IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Verder heeft BridgeDB genoeg oude vanilla bridges %s zonder enige\nPluggable Transports %s wat mogelijk niet zo cool klinkt, maar deze kunnen nog steeds helpen bij het omzeilen van internetcensuur in de meeste gevallen.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Wat zijn bridges?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s zijn Tor relays die je helpen censuur te omzeilen."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ik heb een alternatieve manier nodig om bridges te verkrijgen!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Een alternatieve manier om bridges te verkrijgen is door een email te sturen naar %s. Houd er wel rekening mee dat je\nde email verstuurd via een van de volgende email aanbieders:\n%s, %s of %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mijn bridges werken niet! Ik heb hulp nodig!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Als uw Tor niet werkt, email dan naar %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Probeer zoveel mogelijk informatie toe te voegen over je situatie als je kan, waaronder de lijst met \nbridges en Pluggable Transports die je geprobeerd hebt te gebruiken, je Tor Browser versie,\nen alle meldingen welke Tor heeft uitgegeven, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Hier zijn je bridge regels:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Krijg Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Selecteer opties voor bridge type:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Heeft u IPv6 adressen nodig?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Heeft u een %s nodig?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Uw browser vertoont afbeeldingen niet naar behoren."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Voer de karakters in van de afbeelding hier beneden..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Hoe te starten met het gebruik van je bridges"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Om bridges toe te voegen in Tor Browser, volg de instructies op de %s Tor\nBrowser download pagina %s om de Tor Browser te starten."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Wanneer de 'Tor Network Settings' dialoog opent, klik 'Configure' en volg\nde wizard totdat deze vraagt om:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokkeert of censureert je internetprovider (ISP) verbindingen\nnaar het Tor netwerk?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Selecteer 'Ja' en klik vervolgens 'Next'. Om je nieuwe bridges te configureren, kopieer en\nplak je de bridge regels in het invoerveld. Vervolgens klik je 'Connect', en\nben je klaar om te gaan! Als je problemen ervaart, klik dan de 'Help'\nknop in de 'Tor Network Settings' wizard voor verdere hulp."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Toont dit bericht."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Vraag vanilla bridges aan."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Verzoek IPv6 bridges"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Vraag een Pluggable Transport op TYPE aan."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Verkrijg een kopie van BridgeDB's publieke GnuPG key."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Rapporteer een bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Broncode"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Wijzigingslogboek"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contact"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Alles selecteren"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Laat de QRCode zien"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode voor je bridge regels"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Helaas pindakaas!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Er was een fout tijdens het ophalen van je QRCode"
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Deze QRCode bevat je bridge regels. Scan het met een QRCode lezer om je bridge regels te kopiëren naar een mobiel of andere apparaten."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Er zijn momenteel geen bridges beschikbaar..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Misschien moet je proberen %s terug te gaan %s en een ander bridge type te selecteren!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Stap %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Download %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Stap %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Download %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Stap %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Nu %s voeg de bridges toe aan Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "Geef me %sg%sewoon bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Geavanceerde Opties"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nee"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "geen"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sK%srijg Bridges"
diff --git a/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..8f938cf
--- /dev/null
+++ b/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,388 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Aron <aron.plotnikowski at cryptolab.net>, 2014
+# Aron <aron.plotnikowski at cryptolab.net>, 2013
+# JerBen <ayurveda63 at gmail.com>, 2012
+# bogdrozd <bog.d at gazeta.pl>, 2013
+# Dawid <hoek at hoek.pl>, 2014
+# Rikson <Ers at mail2tor.com>, 2014
+# Krzysztof Åojowski <maxxxrally at gmail.com>, 2014
+# oirpos <kuba2707 at gmail.com>, 2015
+# seb, 2014-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 10:50+0000\n"
+"Last-Translator: seb\n"
+"Language-Team: Polish (http://www.transifex.com/projects/p/torproject/language/pl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Przepraszamy! Ale coÅ poszÅo nie tak z Twoim zapytaniem."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[To jest wiadomoÅÄ generowana automatycznie; prosimy na niÄ
nie odpisywaÄ.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Oto Twoje mostki:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Przekroczono limit szybkoÅci. ProszÄ zwolnij! Minimalny czas pomiÄdzy \nwiadomoÅci e-mail to %s godzin. Wszystkie dodatkowe e-maile w tym okresie bÄdÄ
ignorowane."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "POLECENIA: (ÅÄ
cz polecenia, aby sprecyzowaÄ kilka opcji jednoczeÅnie)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Witamy w BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Obecnie obsÅugiwane transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Witaj, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Witaj przyjacielu!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Klucze Publiczne"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Ten email zastaÅ wygenerowany przez tÄcze, jednorożce i gwiazdki \ndla %s w %s o %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB może dostarczaÄ poÅÄ
czenia mostkowe z kilkoma %stypes wÅÄ
czanymi protokoÅami%s,\nktóre mogÄ
pomóc ukryÄ Twoje poÅÄ
czenie do Sieci Tor, tworzÄ
c trudniejsze\ndo podsÅuchania dla osób obserwujÄ
cych ruch sieci w celu ustalenia gdzie siÄ znajdujesz\nużywajÄ
c Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Niektóre poÅÄ
czenia mostkowe z adresami IPv6 sÄ
również dostÄpne, pomimo,\nże niektóre wtyczki protokoÅów nie sÄ
kompatybilne z IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Dodatkowo, BridgeDB posiada sporo regularnych mostów %s bez \njakichkolwiek pluggable transports %s, które mogÄ
wydawaÄ siÄ niezbyt przydatne, \njednak w wielu przypadkach mogÄ
pomóc w obejÅciu cenzury.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Czym sÄ
mostki?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Mosty %s sÄ
wÄzÅami w sieci Tor pomagajÄ
cymi w ominiÄciu cenzury."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "PotrzebujÄ alternatywnego sposobu na pozyskanie mostków!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Innym sposobem na pozyskanie mostu jest wysÅanie wiadomoÅci e-mail na adres %s. ProszÄ pamiÄtaÄ, że należy \nwysÅaÄ wiadomoÅÄ używajÄ
c adresu jednego z nastÄpujÄ
cych dostawców poczty elektronicznej:\n%s, %s lub %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Moje mostki nie dziaÅajÄ
! PotrzebujÄ pomocy!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "JeÅli Twój Tor nie dziaÅa, powinieneÅ wysÅaÄ wiadomoÅÄ e-mail na adres %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Spróbuj zawrzeÄ jak najwiecej informacji o swoim problemie, uwzglÄdniajÄ
c listÄ mostów i Pluggable Transports, których próbowaÅeÅ użyÄ, wersjÄ Tor Browser, wszelkie komunikaty, które zwróciÅ Tor i inne."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Oto Twoje poÅÄ
czenia z wykorzystaniem mostów:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ZdobÄ
dź Mosty!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ProszÄ wybraÄ opcje dla typu mostu:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Czy potrzebujesz adresów IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Czy potrzebujesz %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Twoja przeglÄ
darka nie wyÅwietla obrazów prawidÅowo."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Wprowadź tekst z obrazka powyżej..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Jak zaczÄ
Ä używaÄ mostów"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Aby dodaÄ mosty do Tor Browser, postÄpuj zgodnie z instrukcjami dostÄpnymi na %s stronie \npobierania Tor Browser %s w celu uruchomienia Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Po pojawieniu siÄ okna 'Ustawienia Sieci Tor' naciÅnij przycisk \"Konfiguruj\", a nastÄpnie postÄpuj zgodnie ze\nwskazówkami kreatora do momentu pytania o:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Czy Twój dostawca usÅug internetowych (ISP) blokuje lub cenzuruje poÅÄ
czenia do sieci Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Wybierz 'Tak' a nastÄpnie kliknij 'Dalej'. Aby skonfigurowaÄ swoje nowe \nmosty, skopiuj i wklej każdy most w nowym wierszu w polu tekstowym. \nNa koniec kliknij 'PoÅÄ
cz' i to wszystko! JeÅli napotkasz jakiekolwiek problemy, naciÅnij przycisk 'Pomoc' w kreatorze 'Ustawienia Sieci Tor' w celu uzyskania dalszych porad."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "WyÅwietla tÄ wiadomoÅÄ."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "PoproÅ o regularne mosty."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "PoproÅ o mosty IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "PoproÅ o Pluggable Transport przez TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Uzyskaj kopiÄ klucza publicznego GnuPG BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "ZgÅoÅ BÅÄ
d"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Kod źródÅowy"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Lista zmian"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Zaznacz wszystko"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Pokaż KodQR"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "KodQR dla Twoich linii bridge"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ups, coÅ poszÅo nie tak!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "WyglÄ
da na to, że napotkaliÅmy na bÅÄ
d podczas próby pobrania Twojego kodu QR."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ten KodQR zawiera Twoje linie bridge. Przeskanuj je czytnikiem kodów QR, aby skopiowaÄ je do Twojego telefonu lub innych urzÄ
dzeÅ."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Aktualnie nie ma dostÄpnych żadnych mostów..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Prawdpododobnie powinieneÅ spróbowaÄ %s wróciÄ %s i wybraÄ inny typ mostu!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Krok %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Pobierz %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Krok %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Pobierz %s mostki %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Krok %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Teraz %s dodaj mosty do Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sP%so prostu daj mi mosty!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opcje zaawansowane"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nie"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "brak"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sT%sak!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sP%sozyskaj mosty"
diff --git a/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..48229a7
--- /dev/null
+++ b/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,387 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# alfalb.as, 2015
+# André Monteiro <andre.monteir at gmail.com>, 2014
+# kagazz <lourenxo_619 at hotmail.com>, 2014
+# alfalb_mansil, 2014
+# Andrew_Melim <nokostya.translation at gmail.com>, 2014
+# Pedro Albuquerque <palbuquerque73 at gmail.com>, 2014-2015
+# Sérgio Marques <smarquespt at gmail.com>, 2014
+# TiagoJMMC <tiagojmmc at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-23 16:31+0000\n"
+"Last-Translator: alfalb.as\n"
+"Language-Team: Portuguese (http://www.transifex.com/projects/p/torproject/language/pt/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: pt\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Desculpe! Ocorreu algo de errado com o seu pedido."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Esta é uma mensagem automática; por favor, não responda.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Aqui estão as suas pontes:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Excedeu a taxa limite. Por favor abrande! O tempo mÃnimo entre mensagens\neletrónicas é de %s horas. Todas as mensagens seguintes durante este perÃodo serão ignoradas."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMANDOS: (combine COMANDOS para especificar múltiplas opções em simultâneo)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Bem-vindo ao BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Transport TYPEs suportados:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Olá, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Olá, amigo!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Chaves Públicas"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Esta mensagem foi gerada com arco-Ãris, unicórnios e estrelas\npara %s em %s à s %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "A BridgeDB pode fornecer pontes com vários %stypes de Pluggable Transports%s,\nque poderão ajudar a ocultar as suas ligações à Tor Network, tornando mais difÃcil\na filtragem do seu tráfego de Internet para determinar se está a usar o Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Também estão disponÃveis algumas pontes com endereços IPv6. \nNo entanto, alguns Pluggable Transports não são compatÃveis com ligações IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Adicionalmente, a BridgeDB tem imensas pontes %s simples, normais e regulares\nmas sem qualquer Pluggable Transports %s, o que pode não parecer aliciante, mas\nque, ainda assim, ajudam a contornar a censura na Internet.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "O que são pontes?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Pontes %s são relays Tor para ajudar a contornar a censura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Preciso de uma alternativa para obter pontes!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Outra forma de obter pontes é enviar uma mensagem para %s. Por favor, note que\na mensagem tem de ser enviada de um dos seguintes fornecedores de serviço:\n%s, %s ou %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "As minhas pontes não funcionam! Eu preciso de ajuda!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Se o seu Tor não funciona, deve enviar uma mensagem para %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Tente incluir o máximo de informação sobre os problemas. Deve incluir a lista\nde pontes e Pluggable Transports que tentou utilizar, a sua versão Tor e \nquaisquer mensagens que o Tor retornou..."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Aqui estão as suas linhas de ponte:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Obter pontes!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Por favor selecione as opções para o tipo de ponte:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Necessita de endereços IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Necessita de %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "O seu navegador não está a mostrar as imagens apropriadamente."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Introduza os caracteres da imagem acima..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Como utilizar as suas pontes"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Para inserir pontes no Tor Browser, siga as instruções na página de transferências\n%s do Tor Browser %s para iniciar o Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Assim que o diálogo de rede Tor aparecer, clique em Configurar e siga\nas intruções do assistente até que lhe pergunte:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "O seu fornecedor de serviço Internet (ISP) bloqueia ou censura as ligações\n à rede Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Selecione Sim e clique Seguinte. Para configurar as novas pontes, copie e\ncole as linhas de ponte na caixa de texto. Finalmente, clique em \"Ligar\" e\ndeve estar pronto! Se ocorrerem erros, clique no botão de Ajuda no\nassistente de Definições da rede Tor."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Mostra esta mensagem."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Solicita as pontes básicas."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Solicita pontes IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Solicita um Pluggable Transport por TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Obter uma cópia da chave pública GnuPG da BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Reportar um erro"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Código fonte"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Registo de alterações"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contactos"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Escolher todos"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Mostrar QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode para as suas linhas de ponte"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ocorreu um erro!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Parece que ocorreu um erro ao obter o seu QRCode."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Este QRCode contém as suas linhas de ponte. Leia-o com um leitor de QRCode para copiar as linhas de ponte para dispositivos móveis."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Atualmente não existem pontes livres..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Experimente utilizar %s voltando a %s e escolhendo um tipo de ponte diferente!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Passo %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Transferir %s Navegador Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Passo %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obtenha %s pontes %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Passo %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Agora %s adiciona as pontes ao navegador Tor %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sA%spenas quero as pontes!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opções avançadas"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Não"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "nenhuma"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sS%sim!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sbter pontes"
diff --git a/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..db13695
--- /dev/null
+++ b/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,386 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Communia <ameaneantie at riseup.net>, 2013-2014
+# Augustine <evandro at geocities.com>, 2013
+# Humberto Sartini <humberto at hss.blog.br>, 2014
+# Isabel Ferreira, 2014
+# João Paulo S.S <contato1908 at gmail.com>, 2015
+# m4lqu1570 <>, 2012
+# Rodrigo Emmanuel Santana Borges <rodrigoesborges at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-17 01:10+0000\n"
+"Last-Translator: João Paulo S.S <contato1908 at gmail.com>\n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/torproject/language/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Desculpe! Algo errado ocorreu com a sua solicitação."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Esta é uma mensagem automática; por favor, não responda.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Aqui estão suas pontes:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Você excedeu o limite. Mais devagar, por favor! O tempo mÃnimo entre\ne-mails é de %s horas. Todos os outros e-mails serão ignorados durante este perÃodo."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMANDOS: (combinar COMANDOS para especificar múltiplas opções simultaneamente)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Bem vindo ao BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "TYPEs de transport que possuem suporte atualmente:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Olá, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Olá, amigo!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Chaves Públicas"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Este e-mail foi gerado com arco-Ãris, unicórnios e purpurina, por %s, %s, à s %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB pode fornecer pontes com vários %stipos de ââTransportes Plugáveis%s,\nque podem ajudar a ofuscar suas conexões com a Rede Tor, tornando mais\nddifÃcil para qualquer um ver seu tráfego de internet para determinar que você está\nusando o Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Algumas pontes com endereços IPv6 também estão disponÃveis, mas alguns PLUGGABLE TRNAPORTS não são compatÃveis com o IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Além disso, BridgeDB tem muitas pontes tradicionais %s sem nenhum PLUGGABLE TRANSPORTS%s, que podem ajudar a driblar a censura na internet em muitos casos.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "O que são pontes?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Pontes %s são retransmissores Tor que ajudam a driblar a censura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Preciso de um outro modo de obter pontes!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Outro modo de obter pontes é enviando um e-mail para %s. Por favor, lembre que você deve enviar o e-mail utilizando um endereço registrado em um dos seguintes provedores de e-mail: \n%s, %s ou %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Minhas pontes não funcionam! Preciso de ajuda!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Se o Tor não funcionar, envie um e-mail %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Tente incluir o máximo de informações possÃveis sobre o seu caso, como a lista das pontes e dos PLUGGABLE TRANSPORTS que você tentou usar, a versão do seu navegador Tor e todas as mensagens que o Tor emitiu, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Veja sua pontes ativas:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Obtenha Pontes!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Por favor, selecione as opções de tipos de pontes:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Você precisa de endereços IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Você precisa de %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Seu navegador não está mostrando as imagens corretamente."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Digite os caracteres da imagem acima..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Como começar a usar as suas pontes"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Para inserir pontes no navegador Tor, siga as instruções no %s página\n de downloads do navegador Tor %s para iniciar o navegador Tor."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Quando a janela 'Configurações da Rede Tor' aparecer, clique em 'Configurar' e siga\no assistente até que ele pergunte:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "O seu Provedor de Serviços de Internet (PSI) bloqueia ou censura conexões\nà rede Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Selecione 'Sim' e clique em 'Próximo'. Para configurar suas novas pontes, copie e\ncole as coordenadas das pontes na caixa de texto de saÃda. Por fim, clique em 'Conectar'.\nIsso deve ser o suficiente! Se você encontrar problemas, tente clicar no botão\n'Ajuda' no assistente de 'Configurações da Rede Tor', para mais assistência."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Mostrar essa mensagem."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Solicitar pontes VANILLA."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Solicitar pontes IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Requerer Transporte Plugável por TYPE"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Obter uma cópia da chave pública GnuPG do BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Relatar um Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Código Fonte:"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Registro de alterações"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Contato"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Selecionar tudo"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Mostrar QRCode"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QRCode para as suas linhas de ponte"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ops, um erro!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Parece que houve um erro ao obter seu QRCode"
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Seu QRCode contém as linhas de pontes. Digitalizá-lo com um leitor de QRCode para copiar suas linhas de ponte para dispositivos móveis e outros."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Atualmente não há nenhuma ponte disponÃvel..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Talvez você deva tentar %s voltar %s e escolher um outro tipo de ponte!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Passo %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Fazer download do %s Navegador Tor %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Passo %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Obter %s pontes %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Passo %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Agora, %s inserir as pontes no Navegador Tor %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sS%só me dê bridges!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opções Avançadas"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Não"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "nenhum"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sS%sim!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sO%sbtenha Bridges"
diff --git a/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..90d0859
--- /dev/null
+++ b/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,360 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Adda.17 <adrianatrifoi at gmail.com>, 2013
+# Isus Satanescu <isus at openmailbox.org>, 2014
+# laura berindei <lauraagavriloae at yahoo.com>, 2014
+# clopotel <yo_sergiu05 at yahoo.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: clopotel <yo_sergiu05 at yahoo.com>\n"
+"Language-Team: Romanian (http://www.transifex.com/projects/p/torproject/language/ro/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ro\n"
+"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Ne cerem scuze ! Ceva a funcţionat prost !"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[ Acesta este un mesaj automat ; va rugam nu rÄspundeÅ£i ]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Acestea sînt punÈile pentru dvs:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "AÈi depÄÈit rata limitÄ. ÃncetiniÈi vÄ rog! Timpul minim între\nemailuri este %s ore. Toate emailurile în acest interval vor fi ignorate."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (combinÄ COMMANDs pentru a specifica mai multe opÈiuni simultan)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Bine aţi venit la BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "TYPEs transport suportate curent:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Buna , %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Buna , prietene !"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "Chei publice"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Acest email a fost generat cu curcubee, inorogi Èi scîntei pentru %s în %s la %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB poate oferi punÈi cu cîteva %stypes de Pluggable Transports%s,\nce pot obfusca conexiunile dvs cÄtre Tor Network, fÄcînd mai dificilÄ determinarea traficului Tor pentru oricine vÄ urmÄreÈte traficul de internet.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Unele punÈi cu adrese IPv6 sînt disponibile, deÈi unele Pluggable\nTransports nu sînt compatibile cu IPv5.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Ãn plus, BridgeDB are multe punÈi simple %s fÄrÄ nici o\nPluggable Transport %s ce poate nu pare aÈa cool, dar care pot\nde asemenea sÄ ocoleascÄ cenzura internet în multe cazuri.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Ce sunt punÈile? "
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s punÈi %s sînt Tor relays care vÄ ajutÄ sÄ ocoliÈi cenzura."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Am nevoie de o cale alternativÄ de a obÈine punÈile !"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "O altÄ cale pentru a face punÈi este sÄ trimiteÈi un email cÄtre %s. LuaÈi în considerare cÄ\ntrebuie sa trimiteÈi emailul folosind o adresÄ de la unul dintre urmÄtorii providerii de email:\n%s, %s sau %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "PunÈile mele nu funcÈioneazÄ! Am nevoie de ajutor!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Daca Tor nu funcÅ£ioneazÄ trimiteÅ£i un mesaj la %s ."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ÃncercaÈi sÄ includeÈi cît mai multe informaÈii despre cazul dvs pe cît puteÈi, includeÈi lista de punÈi Èi Pluggable Transports pe care aÈi încercat sÄ le folosiÈi, versiunea de Tor Browser Èi orice alt mesaj dat de Tor, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Acestea sînt liniile de punÈi:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Ia punÈi!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "AlegeÈi opÈiunile pentru tipul de punte:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "AveÈi nevoie de adrese IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "AveÈi nevoie de %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Browserul nu afiÈeazÄ imaginile corect."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "IntroduceÈi caracterele din imaginea de mai sus..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Cum sÄ Ã®ncepeÈi sÄ folosiÈi punÈile"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Pentru a introduce în Tor Browser, urmaÈi instrucÈiunile de la %s Tor Browser download page %s pentru a porni Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Cînd apare dialogul 'Tor Network Settings', clic 'Configure' Èi urmaÈi vrÄjitorul pînÄ cînd vÄ cere:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Internet Service Providerul (ISP) dvs blocheazÄ sau cenzureazÄ conexiunile cÄtre reÈeaua Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "AlegeÈi 'Yes' Èi apoi clic 'Next'. Pentru a configura noile punÈi, copiaÈi Èi colaÈi liniile cu punÈile în cÄsuÈa de text. Ãn final, clic 'Connect' Èi totul e gata! DacÄ aveÈi probleme, clic pe 'Help' din vrÄjitorul 'Tor Network Settings'."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "AfiÈeazÄ acest mesaj."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Cere punÈi simple."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Cere punÈi IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "CereÈi un Pluggable Transport dupÄ TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ObÈineÈi o copie a cheii GnuPG publice a BridgeDB."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "RaporaÈi un bug"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Cod sursÄ"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "SchimbÄri"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "Contact"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "O, spagettios!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Acum nu sînt punÈi disponibile..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Poate ar trebui sÄ Ã®ncercaÈi %s înapoi %s Èi sÄ alegeÈi un alt tip de punte!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Pas %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "DescarcÄ %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Pas %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Ia %s punÈi %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Pas %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Acum %s adÄugaÈi punÈile la Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sD%soar dÄ-mi punÈle!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "OpÈiuni avansate"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nu"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "nimic"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sD%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sI%sa punÈi"
diff --git a/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..9ae67f6
--- /dev/null
+++ b/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,389 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Andrey Yoker Ogurchikov <domovoy.yoker at gmail.com>, 2014
+# Evgrafov Denis <stereodenis at gmail.com>, 2014
+# Eugene, 2013
+# foo <im-infamous at yandex.ru>, 2014
+# joshua ridney <yachtcrew at mail.ru>, 2015
+# liquixis <liquixis at gmail.com>, 2012
+# Oleg, 2014
+# Sergey Briskin <sergey.briskin at gmail.com>, 2014
+# Valid Olov, 2014
+# Vitaliy Grishenko, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-16 10:42+0000\n"
+"Last-Translator: joshua ridney <yachtcrew at mail.ru>\n"
+"Language-Team: Russian (http://www.transifex.com/projects/p/torproject/language/ru/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ru\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "ÐзвиниÑе! Ðозникла пÑоблема Ñ Ð²Ð°Ñим запÑоÑом."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[СообÑение бÑло ÑÑоÑмиÑовано авÑомаÑиÑеÑки, пожалÑйÑÑа, не оÑвеÑайÑе на него.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "ÐодгоÑовленнÑе моÑÑÑ:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "ÐÑ Ð¿ÑевÑÑили ÑÑÑановленнÑй лимиÑ. ÐожалÑйÑÑа, ÑбавÑÑе обоÑоÑÑ! ÐинималÑнÑй пÑомежÑÑок вÑемени Ð¼ÐµÐ¶Ð´Ñ \nзапÑоÑами ÑоÑÑавлÑÐµÑ %s ÑаÑов. ÐÑе поÑледÑÑÑие пиÑÑма в ÑеÑение ÑÑого вÑемени бÑдÑÑ Ð¿ÑоигноÑиÑованÑ."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "ÐÐÐÐÐÐЫ: (комбиниÑÑйÑе командÑ, ÑÑÐ¾Ð±Ñ Ð¾Ð´Ð½Ð¾Ð²Ñеменно иÑполÑзоваÑÑ Ð½ÐµÑколÑко опÑий)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "ÐобÑо пожаловаÑÑ Ð² BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "ÐоддеÑживаемÑе ÑÐ¸Ð¿Ñ ÑÑанÑпоÑÑа - TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Ð-ге-гей, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ÐÑивеÑ, дÑÑг!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "ÐÑкÑÑÑÑе клÑÑи"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "ÐÑо ÑлекÑÑонное пиÑÑмо бÑло ÑгенеÑиÑовано пÑи помоÑи ÑадÑги, единоÑогов и ÑевеÑного ÑиÑниÑ\nÐ´Ð»Ñ %s в %s, в %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑедоÑÑавиÑÑ Ð²Ð°Ð¼ моÑÑÑ Ñ Ð½ÐµÑколÑкими %s Ñипами Pluggable Transports%s,\nÑÑо позволÑÐµÑ Ð·Ð°Ð¿ÑÑаÑÑ Ð²Ð°Ñе Ñоединение Ñ Tor Network. ÐлагодаÑÑ ÑÑÐ¾Ð¼Ñ Ð¿ÐµÑеÑ
ваÑÑикам ÑÑаÑика\nÑложнее ÑÑÑановиÑÑ ÑÐ°ÐºÑ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑеÑи Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "ÐоÑÑÑÐ¿Ð½Ñ Ð½ÐµÐºÐ¾ÑоÑÑе моÑÑÑ, поддеÑживаÑÑие адÑеÑа IPv6, Ñ
оÑÑ Ð½ÐµÐºÐ¾ÑоÑÑе Pluggable\nTransports не ÑовмеÑÑÐ¸Ð¼Ñ Ñ IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Ðолее Ñого, BridgeDB ÑодеÑÐ¶Ð¸Ñ Ð¼Ð½Ð¾Ð¶ÐµÑÑво обÑÑнÑÑ
моÑÑов %s, не поддеÑживаÑÑиÑ
\nподклÑÑаемÑй ÑÑанÑпоÑÑ %s, ÑÑо Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾ÐºÐ°Ð·Ð°ÑÑÑÑ Ð½Ðµ Ñаким клÑвÑм, но они во многиÑ
ÑлÑÑаÑÑ
\nмогÑÑ Ð¿Ð¾Ð¼Ð¾ÑÑ Ð¾Ð±Ñ
одиÑÑ Ð¸Ð½ÑеÑнеÑ-ÑензÑÑÑ.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "ЧÑо Ñакое ÑеÑÑанÑлÑÑÐ¾Ñ Ñипа моÑÑ?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s ÐоÑÑÑ %s - ÑÑо ÑеÑÑанÑлÑÑоÑÑ Tor, коÑоÑÑе позволÑÑÑ Ð²Ð°Ð¼ обÑ
одиÑÑ ÑензÑÑÑ."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Ðне нÑжен алÑÑеÑнаÑивнÑй ÑпоÑоб полÑÑÐµÐ½Ð¸Ñ ÑпиÑка моÑÑов!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ÐÑÑгим ÑпоÑобом полÑÑÐµÐ½Ð¸Ñ Ð¼Ð¾ÑÑов ÑвлÑеÑÑÑ Ð¾ÑпÑавка ÑлекÑÑонного пиÑÑма на адÑÐµÑ %s. ÐожалÑйÑÑа, обÑаÑиÑе внимание на Ñо, \nÑÑо Ð²Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ Ð¾ÑпÑавиÑÑ ÑлекÑÑоннÑй запÑÐ¾Ñ Ñ Ð¸ÑполÑзованием одного из пеÑеÑиÑленнÑÑ
ÑеÑвиÑов: %s, %s или %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Ðои моÑÑÑ Ð½Ðµ ÑабоÑаÑÑ! ÐомогиÑе!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "ÐÑли Ð²Ð°Ñ Tor не ÑабоÑаеÑ, напиÑиÑе нам %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "ÐоÑÑаÑайÑеÑÑ Ð¿ÑедоÑÑавиÑÑ Ð¸ÑÑеÑпÑваÑÑÑÑ Ð¸Ð½ÑоÑмаÑÐ¸Ñ Ð¾ ваÑей пÑоблеме, вклÑÑÐ°Ñ ÑпиÑок моÑÑов и Pluggable Transports, коÑоÑÑе Ð²Ñ Ð¿ÑÑалиÑÑ Ð¸ÑполÑзоваÑÑ, веÑÑÐ¸Ñ ÐÐ Tor Browser\nи вÑе полÑÑеннÑе Ð¾Ñ Tor ÑведомлениÑ."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "ÐодгоÑовленнÑе адÑеÑа ÑеÑÑанÑлÑÑоÑов:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ÐолÑÑиÑе ÑеÑÑанÑлÑÑоÑÑ!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ÐожалÑйÑÑа, вÑбеÑиÑе опÑии Ð´Ð»Ñ Ñипа ÑеÑÑанÑлÑÑоÑа:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "ÐÑÐ¶Ð½Ñ Ð»Ð¸ вам адÑеÑа IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Ðам нÑжен %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "ÐÐ°Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ показÑÐ²Ð°ÐµÑ Ð¸Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ñм обÑазом."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "ÐведиÑе ÑÐ¸Ð¼Ð²Ð¾Ð»Ñ Ñ Ð¸Ð·Ð¾Ð±ÑажениÑ..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Ðак наÑаÑÑ Ð¿Ð¾Ð»ÑзоваÑÑÑÑ ÑеÑÑанÑлÑÑоÑами"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "ÐÐ»Ñ Ñого, ÑÑÐ¾Ð±Ñ Ð´Ð¾Ð±Ð°Ð²Ð¸ÑÑ ÑеÑÑанÑлÑÑоÑÑ Ð² Tor Browser, ÑледÑйÑе инÑÑÑÑкÑиÑм, пÑедÑÑавленнÑм на %s ÑÑÑаниÑе\nзагÑÑзки Tor Browser %s Ð´Ð»Ñ Ð·Ð°Ð¿ÑÑка Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Ðогда поÑвиÑÑÑ Ð¾ÐºÐ½Ð¾ 'СеÑевÑе наÑÑÑойки Tor', нажмиÑе 'ÐаÑÑÑоиÑÑ' и ÑледÑйÑе инÑÑÑÑкÑиÑм ÑÑÑановÑика, пока он не задаÑÑ Ð²Ð¾Ð¿ÑоÑ:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ÐÐ°Ñ Ð¸Ð½ÑеÑÐ½ÐµÑ Ð¿ÑÐ¾Ð²Ð°Ð¹Ð´ÐµÑ (ISP) блокиÑÑÐµÑ Ð¸Ð»Ð¸ как-либо ÑензÑÑиÑÑÐµÑ Ð¿Ð¾Ð´ÐºÐ»ÑÑÐµÐ½Ð¸Ñ Ðº ÑеÑи Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ÐÑбеÑиÑе 'Ðа' и нажмиÑе 'Ðалее'. ÐÐ»Ñ Ð½Ð°ÑÑÑойки ваÑиÑ
новÑÑ
моÑÑов ÑкопиÑÑйÑе и\nвÑÑавÑÑе иÑ
адÑеÑа в поле Ð´Ð»Ñ Ð²Ð²Ð¾Ð´Ð° ÑекÑÑа. ÐажмиÑе 'ÐодклÑÑиÑÑÑÑ' и \nвÑе должно заÑабоÑаÑÑ! ÐÑли Ñ Ð²Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð° пÑоблема, попÑобÑйÑе нажаÑÑ Ð½Ð° ÐºÐ½Ð¾Ð¿ÐºÑ 'ÐомоÑÑ'\nв маÑÑеÑе 'СеÑевÑе наÑÑÑойки Tor' Ð´Ð»Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ Ð¿Ð¾Ð¼Ð¾Ñи."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "ÐÑобÑÐ°Ð¶Ð°ÐµÑ ÑÑо ÑообÑение."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "ÐапÑоÑиÑÑ Ð°Ð´ÑеÑа обÑÑнÑÑ
моÑÑов."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "ÐапÑоÑиÑÑ Ð¼Ð¾ÑÑÑ Ñ Ð¿Ð¾Ð´Ð´ÐµÑжкой IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "ÐапÑоÑиÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑÑаемÑй ÑÑанÑпоÑÑ Ð¿Ð¾ TYPE"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ÐолÑÑиÑÑ ÐºÐ¾Ð¿Ð¸Ñ Ð¾ÑкÑÑÑого GnuPG клÑÑа BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "СообÑиÑÑ Ð¾Ð± оÑибке"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "ÐÑÑ
однÑй код"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Ðог изменений"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "ÐонÑакÑ"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "ÐÑбÑаÑÑ Ð²ÑÑ"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "ÐоказаÑÑ QR-код"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR-код Ð´Ð»Ñ Ð°Ð´ÑеÑов ÑеÑÑанÑлÑÑоÑов"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "ÐÑ
, ÑÑо-Ñо поÑло не Ñак!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "ÐоÑ
оже, пÑоизоÑла оÑибка пÑи полÑÑении QR-кода."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "ÐÑÐ¾Ñ QR-код ÑодеÑÐ¶Ð¸Ñ ÐаÑи адÑеÑа ÑеÑÑанÑлÑÑоÑов. ÐÑÑканиÑÑйÑе его ÑÑÑÑойÑÑвом, ÑÑиÑÑваÑÑим QR-код, ÑÑÐ¾Ð±Ñ ÑкопиÑоваÑÑ ÐаÑи адÑеÑа ÑеÑÑÑанÑлÑÑоÑов на мобилÑнÑе и дÑÑгие ÑÑÑÑойÑÑва"
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Ðа даннÑй Ð¼Ð¾Ð¼ÐµÐ½Ñ Ð½ÐµÑ Ð´Ð¾ÑÑÑпнÑÑ
моÑÑов..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Ðозможно, вам ÑÑÐ¾Ð¸Ñ Ð¿Ð¾Ð¿ÑобоваÑÑ %s веÑнÑÑÑÑÑ%s и вÑбÑаÑÑ Ð´ÑÑгой Ñип моÑÑа!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Шаг %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "СкаÑаÑÑ ÐÐ %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Шаг %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ÐолÑÑиÑе %s моÑÑÑ %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Шаг %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "СейÑÐ°Ñ %s добавÑÑе моÑÑÑ Ð² Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sÐ%sÑоÑÑо дайÑе мне адÑеÑа моÑÑов!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "ÐополниÑелÑнÑе наÑÑÑойки"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "ÐеÑ"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "неÑ"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÐ%sа! "
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sÐ%sолÑÑиÑÑ Ð¼Ð¾ÑÑÑ"
diff --git a/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..d063510
--- /dev/null
+++ b/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,100 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# ganeshwaki <ganeshwaki at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-05-24 16:50+0000\n"
+"Last-Translator: ganeshwaki <ganeshwaki at gmail.com>\n"
+"Language-Team: Sinhala (Sri Lanka) (http://www.transifex.com/projects/p/torproject/language/si_LK/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: si_LK\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "à·à·à¶à· යන෠මà·à¶±à·à·à¶¯?"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s à·à·à¶à· පà·âරà¶à·à¶ºà·à¶¢à¶%s යන෠à·à·à¶»à¶«à¶º මà¶à·à¶»à·à¶± Tor පà·âරà¶à·à¶ºà·à¶¢à¶à¶ºà¶±à·à¶º."
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "à·à·à¶à· ලබà·à¶à·à¶±à·à¶¸à¶§ à·à·à¶à¶½à·à¶´ à·à·à¶°à·à¶ºà¶à· මට à¶
à·à·à·âයය!"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "à·à·à¶à· ලà·à¶´à·à¶±à¶ºà¶±à· à·à·à¶ºà·à¶à·à¶±à·à¶¸à· à¶à·à¶à· à¶à¶à·à¶»à¶ºà¶à· නම෠à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà¶à· යà·à¶¸à· à¶à·à¶»à·à¶¸à¶ºà· (from a %s or a %s address) to %s 'get bridges' යනà·à¶± à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà· බද à¶à·à¶§à·à· à·à¶¯à·à¶±à· à¶à¶½ යà·à¶à·à¶º."
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "මà·à¶à· à·à·à¶à· à·à·à¶© නà·à¶à¶»à¶ºà·! මට à¶à¶¯à·à· à¶
à·à·à·âයයà·!"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "à¶à¶¶à¶à· Tor à·à·à¶© නà·à¶à¶»à¶ºà· නම෠à¶à¶¶ à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà¶à· à¶à·à·à¶º යà·à¶à·à¶º %s. à¶à¶¶à¶à· පà·âරà·à·à¶±à¶º පà·à·
à·à¶¶à¶¯ à·à·à¶à·à¶à·à¶à· à¶à·à¶»à¶à·à¶»à· à·à·à¶´à¶ºà·à¶¸à¶§ à¶à¶à·à·à·à· à¶à¶±à·à¶±, à¶à·à· à¶à¶¶ භà·à·à·à¶à· à¶à¶½ පà·âරà¶à·à¶ºà·à¶¢à¶ ලà·à¶ºà·à·à·à¶à·à·, à¶à¶¶à¶à· à¶à¶§à·à¶§à¶½à¶ºà· à¶à·à¶±à· නම/à¶à¶¶ භà·à·à·à¶à· à¶à¶½ පà·à¶§à¶´à¶, Tor à·à·à·à·à¶±à· à¶à¶¶ à·à·à¶ ලබà·à¶¯à·à¶± පණà·à·à·à¶©à¶ºà¶±à· යනà·à¶¯à·à¶º à¶
නà·à¶à¶»à·à¶à¶ à¶à¶»à¶±à·à¶±."
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr "à¶à·à¶ රà·à¶à·à·à¶±à· භà·à·à·à¶à· à¶à¶»à¶ºà· නම෠, à·à·à¶¯à·à¶½à·à¶ºà· ජà·à¶½ à·à·à¶§à·à·à¶¸à·à·à¶½à¶§ à¶à·à·à·, \"මà¶à· ISP à·à·à·à·à¶±à· Tor ජà·à¶½à¶ºà¶§ à·à¶¸à·à¶¶à¶±à·à¶° à·à·à¶¸ à¶
à·à·à·à¶»à¶à¶» à¶à¶.\" යනà·à¶± à¶à·à¶½à·à¶à· à¶à¶»à¶±à·à¶±. à¶à¶½à¶à¶§ à·à·à¶à· ලà·à¶´à·à¶±à¶ºà¶±à· à·à¶»à¶à¶§ à¶à¶ බà·à¶à·à¶±à· à¶à¶à¶à· à¶à¶»à¶±à·à¶±.."
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "දà·à¶±à¶§ à¶à·à·à·à¶¯à· à·à·à¶à·à·à¶à· පà·âරයà·à¶¢à¶±à¶º à·à¶¯à·à· නà·à¶¸à·à¶ "
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "à¶à¶¶à¶à· බà·âරà·à·à·à¶»à¶º Firefox à·à¶½à¶§ à¶à·à·à· à¶à¶»à¶±à·à¶± "
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "à·à¶ න දà·à¶à¶à· ටයà·à¶´à· à¶à¶»à¶±à·à¶± "
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "පà·à¶ºà·à¶» 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "ලබà·à¶à¶±à·à¶± %s Tor බà·âරà·à·à·à¶» à¶à¶§à·à¶§à¶½à¶º %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "පà·à¶ºà·à¶» 2 "
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ලබà·à¶à¶±à·à¶±%s à·à·à¶à·à¶±à· %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "පà·à¶ºà·à¶» 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "දà·à¶±à· %sTor à·à·à¶ à·à·à¶à·à¶±à· à¶à¶à¶à· à¶à¶»à¶±à·à¶±%s"
diff --git a/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..60c60ad
--- /dev/null
+++ b/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,361 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# elo, 2014
+# FooBar <thewired at riseup.net>, 2015
+# Michal Slovák <michalslovak2 at hotmail.com>, 2013
+# Roman 'Kaktuxista' Benji <romanbeno273 at gmail.com>, 2014
+# StefanH <stefan.holent at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2015-01-30 01:41+0000\n"
+"Last-Translator: FooBar <thewired at riseup.net>\n"
+"Language-Team: Slovak (http://www.transifex.com/projects/p/torproject/language/sk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sk\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "PrepáÄte! NieÄo je zle s vaÅ¡ou požiadavkou."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Toto je automatická správa; prosÃm, neodpovedajte.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Tu sú vaše premostenia:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "PrekroÄili ste rate limit. ProsÃm spomaľte. Minimálny Äas medzi emailmi je %s hodÃn. VÅ¡etky dalÅ¡ie emaily poÄas tejto doby budú ignorované."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "PRÃKAZY: (kombinujte prÃkazy na zadanie viac možnostà naraz)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Vitajte v BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Práve podporované transport TYPEy:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hej, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Ahoj, priateľ!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "Verejné kľúÄe"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Tento email bol generovaný s dúhami, jednorožcami a trblietkami pre %s dÅa %s o %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB vám dokáže ponúknuÅ¥ niekolko typov %spremostenà s Pluggable Transports%s,\nktoré vám pomôžu maskovaÅ¥ vaÅ¡e pripojenie na Tor Network, ktoré ztažà každému kto sleduje vaÅ¡e internetové pripojenie zistiÅ¥ to že použÃvate Tor.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Niektoré premostenia s IPv6 adresami sú taktiež dostupné, ale niektoré Pluggable Transports kompatibilné s IPv6 niesú.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "DodatoÄne, BridgeDB má dosÅ¥ pôvodnych premostenà %s bez žiadnych Pluggable Transports %s ktoré možno niesu až také cool, ale vo veľa prÃpadoch stále dokážu obÃsÅ¥ cenzúrovanie internetu.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Äo sú to premostenia?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s premostenia %s sú Tor relaye ktoré vám pomáhaju obÃsÅ¥ cenzúru."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Potrebujem iný spôsob ako zÃskaÅ¥ premostenia!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "DalÅ¡Ãm spôsobom ako zÃskaÅ¥ premostenia je poslaÅ¥ mail na %s. \nEmail vÅ¡ak musÃte poslaÅ¥ pomocou adresy od jedného z týchto poskytovateľov:\n%s, %s alebo %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Moje premostenia nefungujú! Potrebujem pomoc!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ak váš Tor nefunguje, skúste napÃsaÅ¥ email %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Skúste napÃsaÅ¥ Äo najviac informacià o vaÅ¡om prÃpade, vrátane zoznamu premostenà a Pluggable Transportov ktoré ste skúšali použiÅ¥, verzia Tor Browseru, a hlášky ktoré Tor vypÃsal, apod."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Tu sú vaše premostenia:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ZÃskaÅ¥ premostenia!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ProsÃm vyberte si možnosti na typ premostenia:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Potrebujete IPv6 adresy?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Potrebujete %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Váš prehliadaÄ nezobrazuje obrázky správne."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Vložte znaky z obrázku vyššie..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Ako zaÄaÅ¥ použÃvaÅ¥ vaÅ¡e premostenia"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Na zadanie premostenà do Tor Browsera, riaÄte sa inÅ¡trukciami na %s stánke stiahnutia Tor Browseru %s na spustenie Tor Browseru."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "KeÄ vyskoÄia 'Tor SieÅ¥ové Nastavenia', kliknite na 'KonfigurovaÅ¥' a pokraÄujte Äalej kým sa váš insÅ¥alátor nespýta:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokuje alebo nejak cenzuruje váš Poskytovateľ Internetu (ISP) pripojenia k Sieti Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "OznaÄte 'Ãno' a potom kliknite na 'Äalej'. Na konfiguráciu nových premostenÃ, skopÃrujte premostenia do polÃÄka na text. Potom kliknite na 'PripojiÅ¥' a už by to malo fungovaÅ¥. Ak máte nejaké problemy, kliknite na tlaÄidlo 'Pomoc' v 'Tor sieÅ¥ových nastaveniach' pre viac informaciÃ. "
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Zobrazà túto správu."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Požiadať o pôvodné premostenia."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Požiadať o IPv6 premostenia."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Požiadať o Pluggable Transport podla typu."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Stiahnite si kópiu verejného klúÄa GNUPG pre BridgeDB."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "Nahlásiť chybu"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Zdrojový kód"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Posledné zmeny"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "Kontakt"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Do pekla!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Práve niesú dostupné žiadne premostenia..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Možno by ste mohli skúsiÅ¥ %s ÃsÅ¥ späť %s a vybraÅ¥ si iný typ premostenia."
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Krok %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Stiahnuť %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Krok %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ZÃskajte %s premostenia %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Krok %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Teraz %s pridajte premostenia do Tor Browsera %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sL%sen mi zobrazte premostenia!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "PokroÄilé možnosti"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nie"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "žiadne"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÃ%sno!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sN%sastaviť Bridges"
diff --git a/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..a667857
--- /dev/null
+++ b/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,357 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# once, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: once\n"
+"Language-Team: Slovak (Slovakia) (http://www.transifex.com/projects/p/torproject/language/sk_SK/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sk_SK\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "PrepáÄte! Pri spracovanà vaÅ¡ej požiadavky sa vyskytla chyba."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Toto je automatická správa; prosÃm, neodpovedajte.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Tu sú vaše premostenia:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "PrekroÄili ste limit. Spomaľte, prosÃm! Minimálny odstup medzi e-mailami je\n%s hodÃn. Každý ÄalÅ¡Ã e-mail nereÅ¡pektujúci tento Äasový odstup bude ignorovaný."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "PRÃKAZY: (pre urÄenie viacerých možnostà súÄasne PRÃKAZY kombinujte)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Vitajte v BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Aktuálne podporované TYPE transportov:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Ahoj, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Ahoj!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "Verejné kľúÄe"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Tento e-mail bol vytvorený pre %s %s o %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB poskytuje premostenia s rôznymi %stypmi Pluggable Transportov%s,\nktoré vám pomôžu zakryÅ¥ vaÅ¡e pripojenie do Tor Network. Pre každého,\nkto sleduje vaÅ¡e internetové pripojenie, bude potom rozpoznanie toho, že použÃvate Tor, zložitejÅ¡ie.\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Je dostupných aj niekoľko premostenàs adresami IPv6, niektoré Pluggable\nTransporty však nie sú IPv6 kompatibilné.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Naviac, BridgeDB obsahuje dostatok starých dobrých, na kosÅ¥ osekaných premostenÃ\n%s bez Pluggable Transportov %s, použitie ktorých možno neznie tak skvele a cool,\nale stále vám v mnohých prÃpadoch pomôžu obÃsÅ¥ cenzúru internetu.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Äo sú premostenia?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Premostenia %s sú Tor relé, ktoré vám pomáhajú obÃsÅ¥ cenzúru."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Potrebujem alternatÃvny spôsob zÃskania premostenÃ!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "ÄalÅ¡Ãm zo spôsobov, ako zÃskaÅ¥ premostania, je poslaÅ¥ e-mail na %s. Berte, prosÃm,\nna vedomie, že e-mail musÃte poslaÅ¥ z adresy od jedného z nasledujúcich\nposkytovateľov e-mailu:\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Moje premostenia nefungujú! Potrebujem pomoc!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Ak vám nefunguje Tor, mali by ste napÃsaÅ¥ e-mail na %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Pokúste sa priložiÅ¥ Äo najviac informácià o vaÅ¡om probléme, vrátane zoznamu premostenà a Pluggable Transportov, ktoré ste skúšali použiÅ¥, verziu vášho Tor Browser, vÅ¡etky ÄalÅ¡ie správy, ktoré Tor vypÃsal atÄ."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Tu sú vaše riadky premostenia:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ZÃskaÅ¥ Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Zvoľte, prosÃm, možnosti pre typ premostenia:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Potrebujete adresy IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Potrebujete %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Váš prehliadaÄ nezobrazuje obrázky správne."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Zadajte znaky z obrázka vyššie..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Ako zaÄaÅ¥ použÃvaÅ¥ premostenia"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Pre zadanie premostenà do Tor Browser sa riaÄte inÅ¡trukciami na spustenie Tor Browser na %s stránke preberania Tor browser %s."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "KeÄ sa objavà dialógové okno 'Nastavenia Tor Network', kliknite na 'KonfigurovaÅ¥'\na riaÄte sa sprievodcom, kým sa nespýta:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blokuje váš poskytovateľ internetového pripojenia (ISP) alebo inak cenzuruje pripojenia do siete Tor Network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Zvoľte 'Ãno' a potom kliknite na 'Äalej'. Pre nastavenie vaÅ¡ich nových\npremostenÃ, skopÃrujte a vložte riadky premostenà do vstupného textového\npoľa. Nakoniec kliknite na 'PripojiÅ¥' a môžete pracovaÅ¥. Ak sa vyskytnú\nproblémy, pre ÄalÅ¡iu pomoc skúste kliknúť na tlaÄidlo 'PomocnÃk' v sprievodcovi\n'Nastavenia Tor Network'."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Zobrazà túto správu."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Vyžiadať si osekané premostenia."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Vyžiadať si IPv6 premostenia."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Vyžiadať Pluggable Transport podľa TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ZÃskaÅ¥ kópiu verejného GnuPG kľúÄa BridgeDB."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "Nahlásiť chybu"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Zdrojový kód"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Záznam zmien"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "Kontakt"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "Ejha!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Žiadne premostenia nie sú práve dostupné..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Mohli by ste skúsiť %s vrátiť sa naspäť %s a zvoliť iný typ premostenia!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Krok %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Prevziať %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Krok %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ZÃskaÅ¥ %s premostenia %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Krok %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Teraz %s pridajte premostenia do Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sD%saj mi premostenia!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "RozÅ¡Ãrené nastavenia"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nie"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "žiadne"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sÃ%sno!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sZ%sÃskaÅ¥ Bridges"
diff --git a/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..cbec406
--- /dev/null
+++ b/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,359 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Dušan <dusan.k at zoho.com>, 2014
+# marko <mr.marko at gmail.com>, 2011
+# Nwolfy <nikopavlinek at ymail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-10-15 17:11+0000\n"
+"Last-Translator: Dušan <dusan.k at zoho.com>\n"
+"Language-Team: Slovenian (Slovenia) (http://www.transifex.com/projects/p/torproject/language/sl_SI/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sl_SI\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "Oprostite! Nekaj je narobe pri vaši zahtevi"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[To je samodejno sporoÄilo, prosimo, da nanj ne odgovarjate.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Tu so vaše premostitve"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "PrekoraÄili ste razmerje omejitve. Prosim upoÄasnite! Minimalni Äas med\ne-poÅ¡to je %s ur. Vsa nadaljna e-poÅ¡ta med tem Äasom bo prezrta. "
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (združite ukaze razliÄnih možnosti hkrati) "
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Dobrodošli v BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Splošno podprti transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hej, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Pozdravljen, prijatelj!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "Javni KljuÄi"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "To e-pismo je bilo napisano z mavricami, enorogi in bleÅ¡Äicami\nza %s na %s ob %s"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB lahko oskrbuje mostiÄke z razliÄnimi %stipi Pluggable Transports%s,\nki lahko pomagajo zmesti vaÅ¡o povezavo z Tor Network, in jo naredijo bolj\nnepregledno za vsakogar, ki spremlja vaÅ¡ internetni promet z namenom ugotavljanja\nuporabe Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Nekateri mostiÄki z IPv6 naslovi so tudi dosegljivi, Äeprav nekateri Pluggable\nTransports niso IPv6 kompatibilni.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Dodatno, BridgeDB ima mnogo nezanimih mostiÄkov %s brez kakrÅ¡njihkoli\ntransportnih vtiÄnikov %s ki morda ne zvenijo dobro, vendar Å¡e vedno\npomagajo pri preslepitvi internetne cenzure v mnogih primerih\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Kaj so mostiÄki?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s MostiÄki %s so Tor releji, ki pomagajo preslepiti nadzor"
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Rabim drugaÄno pot do izbire mostiÄkov!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Druga pot za izbiro mostiÄkov je preko naÅ¡e e-poÅ¡te %s. Morate pa\nposlati e-pismo z uporabo naslova naslednjih e-poÅ¡tnih ponudnikov\n%s, %s ali %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Moj mostiÄek ne dela! Rabim pomoÄ!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Äe vaÅ¡ Tor ne deluje, nam poÅ¡ljite e-poÅ¡to %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Poskusite vkljuÄiti Äim veÄ informacij o vaÅ¡em primeru,\nvkljuÄno s seznamom\nmostiÄkov in Pluggable Transports, ki ste jih uporabili, verzijo Tor Browser\nin vsa sporoÄila, ki vam jih je dal Tor, itd."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Tu so vrstice mostiÄkov:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Pridobite si mostiÄke!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Izberite tip mostiÄka:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Rabite IPv6 naslove?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Rabite %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Vaš iskalnik slik ne prikaže pravilno."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Vnesite znake iz zgornje slike..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Kako zaÄeti z uporabo mostiÄkov"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Za vnos mostiÄkov v Tor Browser sledite navodilom v %s Tor\nBrowser strani prenosov %s za zagon Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Ko se pojavi dialog v Tor net nastavitve, kliknite Oblikovanje in sledite\nÄarovniku do konca:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Ali vaÅ¡ Internet ponudnik (ISP) blokira ali drugaÄe cenzurira povezavo\nv Tor omrežje?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "OznaÄite \"Da\" in kliknite \"Naprej\". Za oblikovanje novih mostiÄkov, kopirajte in\nprilepite vrstice mostiÄkov v vnosno polje. Za konec kliknite \"Povezava\", in\nto je to! Äe imate težave, poskusite klikniti \"PomoÄ\"\nv Äarovniku \"Tor net nastavitve\" za nadaljno pomoÄ."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Prikaži to sporoÄilo"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Zahteva za vanilla mostiÄke."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Zahteva za IPv6 mostiÄke."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Zahteva za VtiÄnike Transport po TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Nabavite duplikat BridgeDB's javnega GnuPG kljuÄa."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "Prijavite HroÅ¡Äa"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "Izvorna koda"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "Zapis sprememb"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "Kontakt"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "bog pomagaj!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "Na razpolago ni nobenih mostiÄkov..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Morda bi morali %s nazaj %s in izbrati drugaÄen tip mostiÄka!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Korak %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Snemite %s Tor Iskalnik %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Korak %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Nabavite %s mostiÄke %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Korak %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Sedaj %s dodajte mostiÄke v Tor Iskalnik %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sa rabim mostiÄke!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Napredne opcije"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Ne"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "Nobeden"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%sradi nastavitve Bridges"
diff --git a/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..8bb85b4
--- /dev/null
+++ b/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,380 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Bujar Tafili, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-23 22:51+0000\n"
+"Last-Translator: Bujar Tafili\n"
+"Language-Team: Albanian (http://www.transifex.com/projects/p/torproject/language/sq/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sq\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Kërkojmë ndjesë! Diçka shkoi keq me kërkesën tuaj."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Ky është një mesazh automatik; ju lutemi mos u përgjigjni.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Këtu janë urat tuaja:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Ju e keni kapërcyer kufirin. Ju lutemi, më ngadalë! Koha minimum midis \ne-postave është %s orë. Të gjitha e-postat e tjera gjatë kësaj periudhe kohe do të injorohen."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "COMMANDs: (kombinojini COMMANDs për të specifikuar zgjedhje të shumëfishta njëkohësisht)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Mirë se erdhët tek BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Transport TYPEs të mbështetura këtë çast: "
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hej, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Përshëndetje mik!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Kyça Publikë"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Kjo e-postë është gjeneruar me ylberë, njëbrirësha dhe shkëndija\npër %s, më %s, në orën %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB mund të ofrojë ura me shumë %slloje të Pluggable Transports%s,\nqë mund të ndihmojë fshehjen e lidhjeve tuaja për Tor Network, duke e bërë atë më \ntë vështirë për këdo që vëzhgon trafikun tuaj të internetit, për të përcaktuar nëse ju jeni\nduke përdorur Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Disa ura me adresa IPv6 janë po ashtu të mundshme, ndonëse disa Pluggable\nTransports s'janë të përputhshëm me IPv6.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Përveç kësaj, BridgeDB ka shumë ura %s të bezdisshme, pa ndonjë\nPluggable Transports %s, çka ndoshta nuk do të tingëllojë mirë, por ata ende\nmund të ndihmojnë që ta anashkaloni censurën e internetit në shumë raste.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Ã'janë urat?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Urat %s janë rele Tor, që ju ndihmojnë të anashkaloni censurën."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Më nevojitet një mënyrë alternative për përftimin e urave!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "NJë mënyrë tjetër për të përftuar ura është të dërgoni një e-postë tek %s. ju lutemi vini re se ju duhet\nta dërgoni e-postën, duke përdorur një adresë nga njëri prej ofruesve të e-postës në vijim:\n%s, %s ose %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Urat e mia nuk punojnë! Më duhet ndihmë!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Nëse Tor i juaj nuk punon, ju duhet t'i dërgoni e-postë %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Përpiquni të përfshini sa më shumë informacion që të mundeni rreth rastit tuaj, duke vendosur edhe listën e\nurave dhe Pluggable Transports që provuat të përdorni, versionin tuaj të Tor Browser,\nsi dhe çdo mesazh që ka dhënë Tor, etj."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Këtu janë linjat e urave tuaja:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Merrni Ura!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Ju lutemi përzgjidhni mundësitë për llojin e urës:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "A ju nevojiten adresat IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "A ju nevojitet një %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Shfletuesi juaj nuk po i shfaq si duhet imazhet."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Futini karakteret nga imazhi më sipër..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Si të nisni t'i përdorni urat tuaja"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Për t'i futur urat në Tor Browser, ndiqini instruksionet në %s faqen e shkarkimit të Tor\nBrowser %s, që ta nisni Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Kur dialogu i \"Konfigurimit të Rrjetit Tor\" të kërcejë, klikoni \"Konfiguroni\" dhe ndiqni\nasistentin derisa ta kërkojë ai:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "A i pengon apo i censuron Ofruesi juaj i Shërbimt Internet (ISP) lidhjet\ntek rrjeti Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Përzgjidhni \"Po\" dhe më pas klikoni \"Tjetri\". Që të konfiguroni urat tuaja të reja, kopjojini dhe\nngjitini linjat e urave në kutinë e futjes së tekstit. Më në fund, klikoni \"Lidhuni\", dhe\ndo të jeni gati për t'ia nisur! Nëse do të përjetoni probleme, përpiquni të klikoni butonin \"Ndihmë\" \ntek asistenti i \"Konfigurimit të Rrjetit Tor\", për më shumë mbështetje."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Shfaq këtë mesazh."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Kërkoni urat vanilje ose non-Pluggable Transport."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Kërkoni urat IPv6."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Kërkoni një Pluggable Transport nëpërmjet TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Merrni një kopje të kyçit publik GnuPG të BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Raportoni një gabim"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Kodi Burimor"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Regjistri i ndryshimeve"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Përzgjidhini të Gjitha"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Tregoni Kodin QR"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Kodi QR përlinjat e urave tuaja"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ãfarë tersi!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Duket se u has një gabim, duke marrë Kodin tuaj QR."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Ky Kod QR përmban linja urash. Skanojeni me një lexues Kodi QR, që t'i kopjoni linjat e urave tuaja në celular, apo në pajisje të tjera."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Këtë çast s'ka asnjë urë të disponueshme..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr " Ndoshta duhet të provoni të %s shkoni prapa %s dhe të zgjidhni një lloj të ndryshëm ure!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Hapi %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Shkarkoni %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Hapi %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Merrni %s ura %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Hapi %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Tani %s shtojini urat tek Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sM%së jepni urat!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Opsionet e Përparuara"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Jo"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "asnjë"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sP%so!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sM%serrni Urat"
diff --git a/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..ce8b18a
--- /dev/null
+++ b/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,387 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Anders Jensen-Urstad <anders at unix.se>, 2014
+# Emil Johansson <emil.a.johansson at gmail.com>, 2015
+# GabSeb, 2014
+# Petomatick <petomatick at hotmail.com>, 2011
+# ph AA, 2015
+# phst <transifex at sturman.se>, 2014
+# leveebreaks, 2014
+# WinterFairy <winterfairy at riseup.net>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-23 19:22+0000\n"
+"Last-Translator: ph AA\n"
+"Language-Team: Swedish (http://www.transifex.com/projects/p/torproject/language/sv/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sv\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Något gick tyvärr fel med din förfrågan."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Detta är ett automatiskt meddelande; Var god svara ej]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Här är dina bryggor:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Du har överskridit din nivå. Ta det lugnt! Minsta tillåtna tiden mellan epost är %s timmar. Epost utöver detta kommer att ignoreras."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "KOMMANDOn: (kombinera KOMMANDOn för att ange flera val på en gång)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "Välkommen till BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "För närvarande stöds följande transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Hej, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Hej, kompis!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Publika nycklar"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Det här mailet skapades med hjälp av regnbågar, enhörningar och \nett regn av gnistor för %s den %s kl %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB kan tillhandahålla bryggor med flera %styper av Pluggable Transports%s,\nsom kan omforma din trafik till Tor Network, vilket gör det svårare för någon\nsom avlyssnar din internetuppkoppling att veta ifall du använder Tor.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "Det finns några bryggor med IPv6-adresser tillgängliga, men vissa Pluggable\nTransports är inte IPv6-kompatibla.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Dessutom har BridgeDB några alldeles vanliga bryggor %s utan Pluggable\nTransports %s vilket kanske inte låter så häftigt, men i många fall kan de \nfortfarande hjälpa dig med att kringgå internet-censur.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Vad är bryggor?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bryggor %s är Tor-reläer som hjälper dig kringgå censur."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Jag behöver ett alternativt sätt att skaffa bryggor på!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Ett annat sätt att få nya broar är att skicka e-post till %s. Du måste skicka mailet\nfrån en adress hos någon av följande e-postleverantörer:\n%s, %s eller %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Mina bryggor fungerar inte! Jag behöver hjälp!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Om Tor inte fungerar för dig så kan du e-posta %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Försök att berätta så mycket om problemet som du kan, bland annat vilka\nbryggor och Pluggable Transports som du försökt använda, vilken version av Tor Browser du använder, vilka felmeddelanden som Tor visat, etc."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Här är dina rader med bryggor:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Skaffa Bryggor!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Välj alternativ för typ av brygga:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Behöver du en IPv6-adress?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Behöver du %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Din webbläsare visar inte bilder korrekt."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Skriv in tecknen från bilden ovan..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Hur du börjar använda dina bryggor"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "För att ange bryggor i Tor Browser, följ instruktionerna på nedladdningssidan %s Tor Browser %s för att starta Tor Browser."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "När dialogen \"Nätverksinställningar för Tor\" visas, välj \"Konfigurera\" och följ\nstegen tills du kommer till frågan:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Blockerar eller filtrerar din Internetleverantör (ISP) anslutningar till Tor network?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "Välj 'Ja' och klicka sedan på 'Nästa'. För att konfigurera nya bryggor, kopiera och klistra in bryggraderna i textrutan. Klicka slutligen på 'Anslut' och det borde vara klart för användning! Om du får problem, prova att klicka på 'Hjälp'-knappen i 'Tor Network Settings' för vidare hjälp."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Visa detta meddelande."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Leta efter vanliga bryggor."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "Begär IPv6-bryggor"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Leta efter en Pluggable Transport av en viss TYPE"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "Hämta en kopia av BridgeDB:s publika GnuPG-nyckel."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Rapportera en bugg"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Källkod"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "Ãndringslogg"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Kontakt"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Markera Allt"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "Visa QR-kod"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR-kod för dina rader med bryggor"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Bomber och granater, nåt gick snett!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "Det verkar som att ett fel orsakade problem att få din QR-kod."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Denna QR-kod innehåller dina rader med bryggor. Skanna den med en QR-kodläsare för att kopiera dina rader med bryggor till mobila och andra enheter."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "För närvarande finns inga bryggor tillgängliga..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Prova att %s gå tillbaka %s för att välja en annan typ av brygga!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "Steg %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "Ladda ner %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Steg %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "Skaffa %s bryggor %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Steg %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Nu kan du %s lägga till bryggor i Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sG%se mig bryggor!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "Avancerade inställningar"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Nej"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "inget"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sJ%sa!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sH%sämta Bridges"
diff --git a/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..94444e6
--- /dev/null
+++ b/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,357 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# git12a <git12 at openmailbox.org>, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2015-02-13 10:18+0000\n"
+"Last-Translator: git12a <git12 at openmailbox.org>\n"
+"Language-Team: Tamil (http://www.transifex.com/projects/p/torproject/language/ta/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ta\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "மனà¯à®©à®¿à®à¯à®à®µà¯à®®à¯! à®à®à¯à®à®³à¯ வà¯à®£à¯à®à¯à®à¯à®³à®¿à®±à¯à®à¯ à®à®¤à¯à®à®°à¯ தவற௠நà¯à®°à¯à®¨à¯à®¤à¯à®³à¯à®³à®¤à¯."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[ à®à®¤à¯ à®à®°à¯ தானியà®à¯à®à®¿ à®à¯à®¯à¯à®¤à®¿; தயவ௠à®à¯à®¯à¯à®¤à¯ பதில௠à®
னà¯à®ªà¯à®ª வà¯à®£à¯à®à®¾à®®à¯.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "à®à®¤à¯ à®à®à¯à®à®³à¯ bridges:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "நà¯à®à¯à®à®³à¯ வà¯à®¤ வரமà¯à®ªà¯ à®®à¯à®±à®¿ விà®à¯à®à¯à®°à¯à®à®³à¯. தயவ௠à®à¯à®¯à¯à®¤à¯ à®®à¯à®¤à¯à®µà®à¯à®¯à®µà¯à®®à¯! மினà¯à®©à®à¯à®à®²à¯à®à®³à®¿à®©à¯ à®à®à¯à®¯à¯ à®à¯à®±à¯à®¨à¯à®¤à®ªà®à¯à® நà¯à®°à®®à¯ %s மணிà®à®³à¯. à®à®¨à¯à®¤ நà¯à®°à®¤à¯à®¤à®¿à®©à¯à®³à¯ à®
னà¯à®¤à¯à®¤à¯ மினà¯à®©à®à¯à®à®²à¯à®à®³à¯à®®à¯ நிராà®à®°à®¿à®à¯à®à®ªà¯à®ªà®à¯à®®à¯."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "à®à®à¯à®à®³à¯à®à®³à¯: (பல விரà¯à®ªà¯à®ªà®à¯à®à®³à¯ à®à¯à®±à®¿à®ªà¯à®ªà®¿à® à®à®à¯à®à®³à¯à®à®³à¯ à®à®°à¯à®à®®à®¯à®¤à¯à®¤à®¿à®²à¯ à®à®©à¯à®±à®¿à®¯à®¿à®©à¯à®à®µà¯à®®à¯)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "BridgeDB-யினà¯à®³à¯ நலà¯à®µà®°à®µà¯"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "தறà¯à®ªà¯à®¾à®¤à¯ à®à®¤à®°à®µà¯ à®à®³à¯à®³ Transport TYPE-à®à®³à¯ "
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "ஹà¯, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ஹலà¯à®¾, தà¯à®´à®¾!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "பà¯à®¾à®¤à¯ à®à®¾à®µà®¿à®à®³à¯"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "à®à®¨à¯à®¤ மினà¯à®©à®à¯à®à®²à¯ வானவிலà¯, யà¯à®©à®¿à®à®¾à®°à¯à®©à¯à®à®³à¯ மறà¯à®±à¯à®®à¯ à®à®¿à®±à¯à®¤à¯à®ªà¯à®ªà¯à®±à®¿à®à®³à¯ à®à¯à®£à¯à®à¯ \n%s -à®à®¾à® %s à®
னà¯à®±à¯ %s நà¯à®°à®¤à¯à®¤à®¿à®²à¯ à®à®°à¯à®µà®¾à®à¯à®à®ªà¯à®ªà®à¯à®à®¤à¯."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB-யால௠%sபலவà®à¯à®¯à®¾à®© Pluggable Transports%s bridge-à®à®³à¯ தரமà¯à®à®¿à®¯à¯à®®à¯,\nà®à®µà¯ à®à®à¯à®à®³à¯ Tor Network à®à®£à¯à®ªà¯à®ªà¯à®à®³à¯ விளà®à¯à®à®¾à®¤à®µà®£à¯à®£à®®à¯ à®à®à¯à®à®µà¯à®¤à®µà®¿, à®à®à¯à®à®³à®¿à®©à¯ \nà®à®£à¯à®¯ பà¯à®¾à®à¯à®à¯à®µà®°à®¤à¯à®¤à¯ à®à®£à¯à®à®¾à®©à®¿à®ªà¯à®ªà®µà®°à¯à®à¯à®à¯ நà¯à®à¯à®à®³à¯ Tor பயனà¯à®ªà®à¯à®¤à¯à®¤à¯à®à®¿à®±à¯à®°à¯à®à®³à¯ à®à®©à®à¯à®à®©à¯à®à®±à®¿à®¯ à®®à¯à®²à¯à®®à¯\nà®à®à®¿à®©à®®à¯ à®à®à¯à®à¯à®®à¯.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "à®à®¿à®² IPv6 à®®à¯à®à®µà®°à®¿à®à¯à®à¯à®£à¯à® bridge-à®à®³à¯à®®à¯ à®à®³à¯à®³à®©, à®à®©à®¿à®©à¯à®®à¯ à®à®¿à®² Pluggable\nTransports-à®à®³à¯ IPv6 à®à®à®©à¯ பà¯à®¾à®°à¯à®¨à¯à®¤à®à¯à®à¯à®à®¿à®µà®¤à¯ à®à®²à¯à®²à¯.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "à®à¯à®à¯à®¤à®²à®¾à®, BridgeDB-யினà¯à®³à¯ à®à®°à®¾à®³à®®à®¾à®© பழà¯à®¯à®®à¯à®±à¯ bridge-à®à®³à¯ \n%s Pluggable Transports வà®à®¤à®¿à®¯à®¿à®²à¯à®²à®¾à®®à®²à¯ à®à®³à¯à®³à®¤à¯ %s à®à®£à®°à¯à®à¯à®à®¿ à®à®à¯à®à¯à®à®¿à®±à®µà®¾à®±à¯ à®à®²à¯à®²à®¾à®µà®¿à®à¯à®à®¾à®²à¯à®®à¯, \nà®à®µà¯à®¯à®¾à®²à¯ பல à®à®¨à¯à®¤à®°à¯à®ªà¯à®ªà®à¯à®à®³à®¿à®²à¯ à®à®©à¯à®¯ தணிà®à¯à®à¯à®¯à¯ à®à®à®à¯à® à®à®¤à®µà¯à®®à¯.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Bridges à®à®©à¯à®±à®¾à®²à¯ à®à®©à¯à®©?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridge-à®à®³à¯ %s à®à®©à¯à®®à¯ Tor Relay-à®à®³à¯ தணிà®à¯à®à¯à®¯à¯ à®à®à®à¯à® à®à®¤à®µà¯à®®à¯"
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "à®à®©à®à¯à®à¯ Bridge-à®à®³à¯ பà¯à®±à¯à®µà®¤à®±à¯à®à¯ à®à®°à¯ மாறà¯à®±à¯ வழி வà¯à®£à¯à®à¯à®®à¯"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Bridge-à®à®³à¯ பà¯à®±à¯à®µà®¤à®±à¯à®à¯ à®à®°à¯ மாறà¯à®±à¯à®µà®´à®¿ %s à®®à¯à®à®µà®°à®¿à®à¯à®à¯ à®à®°à¯ மினà¯à®©à®à¯à®à®²à¯ à®
னà¯à®ªà¯à®ªà®²à®¾à®®à¯.\nதயவ௠à®à¯à®¯à¯à®¯à®¤à¯ à®à®µà®©à®¿à®à¯à®à®µà¯à®®à¯, நà¯à®à¯à®à®³à¯ à®à®à¯à®à®¾à®¯à®®à®¾à® à®à¯à®´à¯à®à®£à¯à® மினà¯à®©à®à¯à®à®²à¯ à®à¯à®µà¯à®à®³à®¿à®©à¯ à®®à¯à®à®µà®°à®¿à®¯à®¿à®²à®¿à®°à¯à®¨à¯à®¤à¯ à®®à®à¯à®à¯à®®à¯ மினà¯à®©à®à¯à®à®²à¯ à®
னà¯à®ªà¯à®ªà®²à®¾à®®à¯ :\n%s, %s or %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "à®à®©à¯ Bridge-à®à®³à¯ வà¯à®²à¯ à®à¯à®¯à¯à®¯à®µà®¿à®²à¯à®²à¯! à®à®©à®à¯à®à¯ à®à®¤à®µà®¿ தà¯à®µà¯!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "à®à®à¯à®à®³à¯ Tor வà¯à®²à¯ à®à¯à®¯à¯à®¯à®µà®¿à®²à¯à®²à¯ à®à®©à®¿à®²à¯, நà¯à®à¯à®à®³à¯ %s தà¯à®à®°à¯à®ªà¯à®à¯à®³à¯à® "
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "à®à®à¯à®à®³à¯ வழà®à¯à®à¯ à®à¯à®±à®¿à®¤à¯à®¤à¯ à®
திà®à®ªà®à¯à® தà®à®µà®²à¯à®à®³à¯ à®à¯à®°à¯à®à¯à®à®³à¯, à®à®¤à®¿à®²à¯ நà¯à®à¯à®à®³à¯ பயனà¯à®ªà®à¯à®¤à¯à®¤ à®®à¯à®¯à®±à¯à®à®¿à®¤à¯à®¤ bridges\nமறà¯à®±à¯à®®à¯ Pluggable Transports, தà®à¯à®à®³à®¿à®©à¯ Tor Browser பதிபà¯à®ªà¯ à®à®£à¯ à®à®à®¿à®¯à®µà¯ à®
à®à®à¯à®à¯à®®à¯, மறà¯à®±à¯à®®à¯ Tor\nà®à¯à®°à®¿à®¯à®¤à®à®µà®²à¯à®à®³à¯ பà¯à®¾à®©à¯à®±à®µà®±à¯à®±à¯à®¯à¯à®®à¯ à®à¯à®°à¯à®à¯à®à®µà¯à®®à¯."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "à®à®¤à¯ à®à®à¯à®à®³à¯ bridge வரிà®à¯à®à®³à¯: "
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Bridge-à®à®³à¯ பà¯à®±à¯à®!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Bridge வà®à¯ விரà¯à®ªà¯à®ªà®à¯à®à®³à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "à®à®à¯à®à®³à¯à®à¯à®à¯ IPv6 à®®à¯à®à®µà®°à®¿à®à®³à¯ வà¯à®£à¯à®à¯à®®à®¾?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "à®à®à¯à®à®³à¯à®à¯à®à¯ à®à®°à¯ %s தà¯à®µà¯à®¯à®¾?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr " தà®à¯à®à®³à®¿à®©à¯ browser பà®à®à¯à®à®³à¯ à®à®°à®¿à®¯à®¾à® à®à®¾à®à¯à®à®µà®¿à®²à¯à®²à¯."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "à®®à¯à®²à¯ à®à®³à¯à®³ பà®à®¤à¯à®¤à®¿à®²à¯ à®à®°à¯à®à¯à®à¯à®®à¯ à®à®´à¯à®¤à¯à®¤à¯à®à¯à®à®³à¯ à®à®³à¯à®³à®¿à®à®µà¯à®®à¯..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "தà®à¯à®à®³à®¿à®©à¯ bridge-à®à®³à¯ பயனà¯à®ªà®à¯à®¤à¯à®¤ தà¯à®¾à®à®à¯à®à¯à®µà®¤à¯ à®à®ªà¯à®ªà®à®¿"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Bridge-à®à®³à¯ Tor Browser-னà¯à®³à¯ à®à®³à¯à®³à®¿à®, %s Tor Browser பதிவிறà®à¯à® பà®à¯à®à®¤à¯à®¤à®¿à®²à¯ %s à®à®³à¯à®³ வழிமà¯à®±à¯à®à®³à¯ பினà¯à®ªà®±à¯à®±à®¿ Tor Browser-஠தà¯à®¾à®à®à¯à®à®®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "'Tor நà¯à®à¯à®µà¯à®¾à®°à¯à®à¯ à®
à®®à¯à®ªà¯à®ªà¯à®à®³à¯' à®à®°à¯à®¯à®¾à®à®²à¯ à®®à¯à®²à¯à®¤à¯à®¾à®©à¯à®±à¯à®®à¯ பà¯à®´à¯à®¤à¯, 'à®à®à¯à®à®®à¯à®à¯à®' à®à®¿à®³à®¿à®à¯à®à¯à®¯à¯à®¤à¯ \nவழிà®à®¾à®à¯à®à®¿à®¯à¯ à®
த௠à®à¯à®à¯à®à¯à®®à¯à®µà®°à¯ பினà¯à®ªà®±à¯à®±à®µà¯à®®à¯:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "à®à®à¯à®à®³à®¿à®©à¯ à®à®£à¯à®¯ à®à¯à®µà¯ வழà®à¯à®à¯à®®à¯ (ISP) நிறà¯à®µà®©à®®à¯ Tor நà¯à®à¯à®µà¯à®°à¯à®à¯ தà®à¯à®à¯à®à®¿à®±à®¤à®¾ à®
லà¯à®²à®¤à¯ \nவà¯à®±à¯à®µà®´à®¿à®¯à®¿à®²à¯ தணிà®à¯à®à¯ à®à¯à®¯à¯à®à®¿à®±à®¤à®¾? "
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "'à®à®®à¯' தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®¤à¯à®¤à¯ பினà¯à®©à®°à¯ 'à®
à®à¯à®¤à¯à®¤à¯' à®à®¿à®³à®¿à®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. தà®à¯à®à®³à®¿à®©à¯ பà¯à®¤à®¿à®¯ bridge-à®à®³à¯ à®à®à¯à®à®®à¯à®à¯à®, \nவரிà®à¯à®à®³à¯ வாà®à¯à®à®¿à®¯à®®à¯ à®à®³à¯à®³à¯tà®à¯ பà¯à®à¯à®à®¿à®¯à®¿à®²à¯ நà®à®²à¯à®à®à¯à®¤à¯à®¤à¯ பà¯à®¸à¯à®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. à®à®±à¯à®¤à®¿à®¯à®¾à®, 'à®à®£à¯à®à¯à®' à®à®¿à®³à®¿à®à¯ \nà®à¯à®¯à¯à®¤à®µà¯à®à®©à¯, நà¯à®à¯à®à®³à¯ à®à¯à®²à¯à®² தயாரà¯! நà¯à®à¯à®à®³à¯ பிரà®à¯à®à®¿à®©à¯à®¯à¯ à®à®¨à¯à®¤à®¿à®¤à¯à®¤à®¾à®²à¯, 'Tor நà¯à®à¯à®µà¯à®¾à®°à¯à®à¯ à®
à®®à¯à®ªà¯à®ªà¯à®à®³à¯' \nவழிà®à®¾à®à¯à®à®¿à®¯à®¿à®²à¯ à®à®³à¯à®³ 'à®à®¤à®µà®¿' பà¯à®¾à®¤à¯à®¤à®¾à®©à¯ à®à®¿à®³à®¿à®à¯ à®à¯à®¯à¯à®¤à¯ à®®à¯à®²à¯à®®à¯ à®à®¤à®µà®¿ பà¯à®±à®µà¯à®®à¯."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "à®à®¨à¯à®¤ à®à¯à®¯à¯à®¤à®¿à®¯à¯ à®à®¾à®à¯à®à®µà¯à®®à¯ "
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "பழà¯à®¯à®®à¯à®±à¯à®¯à®¾à®© bridge-à®à®³à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPv6 bridge-à®à®³à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "Pluggable Transport bridge-à®à®³à¯ TYPE வà®à¯à®¯à®¿à®²à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr " BridgeDB-யà¯à®à¯à®¯ பà¯à®¾à®¤à¯ GnuPG à®à®¾à®µà®¿à®¯à®¿à®©à¯ à®à®°à¯ நà®à®²à¯ பà¯à®±à®µà¯à®®à¯."
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "à®à®°à¯ பிழà¯à®¯à¯ à®
றிà®à¯à®à¯à®à¯à®¯à¯à®"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "à®®à¯à®² தà¯à®à¯à®ªà¯à®ªà¯"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "மாறà¯à®±à®®à¯à®à¯à®±à®¿à®à¯à®à¯à®®à¯ à®à¯à®¾à®ªà¯à®ªà¯"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "தà¯à®à®°à¯à®ªà¯ à®à¯à®³à¯"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr " ஹà¯, பிரà®à¯à®à®©à¯!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "தறà¯à®à®®à®¯à®®à¯ Bridge-à®à®³à¯ யà¯à®¤à¯à®®à¯ à®à®²à¯à®²à¯..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "à®à®°à¯à®µà¯à®³à¯ நà¯à®à¯à®à®³à¯ %s à®®à¯à®¯à®±à¯à®à®¿à®à¯à® பினà¯à®©à¯ à®à¯à®©à¯à®±à¯ %s வà¯à®±à¯à®°à¯ வà®à¯ தà¯à®°à¯à®µà¯ à®à¯à®¯à¯à®¯à®²à®¾à®®à¯!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "à®
à®à®¿ %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "பதிவிறà®à¯à®à®µà¯à®®à¯ %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "à®
à®à®¿ %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s Bridge-à®à®³à¯ %s பà¯à®±à¯à®"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "à®
à®à®¿ %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "à®à®ªà¯à®ªà¯à®´à¯à®¤à¯ %s bridge-à®à®³à¯ Tor Browser-à®à®³à¯ %s à®à¯à®°à¯à®à¯à®à®µà¯à®®à¯"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sநà¯%sரயà®à®¿à®¯à®¾à® bridge-à®à®³à¯ à®à¯à®à¯à®à¯à®à®µà¯à®®à¯!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "à®®à¯à®®à¯à®ªà®à¯à® விரà¯à®ªà¯à®ªà®à¯à®à®³à¯"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "à®à®²à¯à®²à¯"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "à®à®¤à¯à®µà¯à®®à®¿à®²à¯à®²à¯"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sà®%sà®®à¯!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sபà¯%sà®°à¯à® Bridge-à®à®³à¯"
diff --git a/bridgedb/i18n/templates/bridgedb.pot b/bridgedb/i18n/templates/bridgedb.pot
new file mode 100644
index 0000000..cef7027
--- /dev/null
+++ b/bridgedb/i18n/templates/bridgedb.pot
@@ -0,0 +1,393 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+# Isis Lovecruft <isis at torproject.org>, 2015.
+#
+#, fuzzy
+#
+# Translators:
+# runasand <runa.sandvik at gmail.com>, 2011
+# Isis Lovecruft <isis at torproject.org>, 2012-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: bridgedb 0.3.1-208-ga646ccd-dirty\n"
+"Report-Msgid-Bugs-To: "
+"'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords"
+"=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-06-25 05:33+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: bridgedb/https/server.py:135
+msgid "Sorry! Something went wrong with your request."
+msgstr ""
+
+#: bridgedb/https/templates/base.html:89
+msgid "Report a Bug"
+msgstr ""
+
+#: bridgedb/https/templates/base.html:92
+msgid "Source Code"
+msgstr ""
+
+#: bridgedb/https/templates/base.html:95
+msgid "Changelog"
+msgstr ""
+
+#: bridgedb/https/templates/base.html:98
+msgid "Contact"
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:84
+msgid "Select All"
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:90
+msgid "Show QRCode"
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:103
+msgid "QRCode for your bridge lines"
+msgstr ""
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: bridgedb/https/templates/bridges.html:118
+#: bridgedb/https/templates/bridges.html:178
+msgid "Uh oh, spaghettios!"
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:119
+msgid "It seems there was an error getting your QRCode."
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:124
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
+"your bridge lines onto mobile and other devices."
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:184
+msgid "There currently aren't any bridges available..."
+msgstr ""
+
+#: bridgedb/https/templates/bridges.html:185
+#, python-format
+msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr ""
+
+#: bridgedb/https/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr ""
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: bridgedb/https/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr ""
+
+#: bridgedb/https/templates/options.html:52
+msgid "Advanced Options"
+msgstr ""
+
+#: bridgedb/https/templates/options.html:88
+msgid "No"
+msgstr ""
+
+#: bridgedb/https/templates/options.html:89
+msgid "none"
+msgstr ""
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: bridgedb/https/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr ""
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: bridgedb/https/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr ""
+
+#: bridgedb/strings.py:43
+msgid "[This is an automated message; please do not reply.]"
+msgstr ""
+
+#: bridgedb/strings.py:45
+msgid "Here are your bridges:"
+msgstr ""
+
+#: bridgedb/strings.py:47
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be "
+"ignored."
+msgstr ""
+
+#: bridgedb/strings.py:50
+msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: bridgedb/strings.py:53
+msgid "Welcome to BridgeDB!"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: bridgedb/strings.py:55
+msgid "Currently supported transport TYPEs:"
+msgstr ""
+
+#: bridgedb/strings.py:56
+#, python-format
+msgid "Hey, %s!"
+msgstr ""
+
+#: bridgedb/strings.py:57
+msgid "Hello, friend!"
+msgstr ""
+
+#: bridgedb/strings.py:58 bridgedb/https/templates/base.html:100
+msgid "Public Keys"
+msgstr ""
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: bridgedb/strings.py:62
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: bridgedb/strings.py:72
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are"
+"\n"
+"using Tor.\n"
+"\n"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: bridgedb/strings.py:79
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: bridgedb/strings.py:88
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
+"\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
+"\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr ""
+
+#: bridgedb/strings.py:101
+msgid "What are bridges?"
+msgstr ""
+
+#: bridgedb/strings.py:102
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr ""
+
+#: bridgedb/strings.py:107
+msgid "I need an alternative way of getting bridges!"
+msgstr ""
+
+#: bridgedb/strings.py:108
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you "
+"must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr ""
+
+#: bridgedb/strings.py:115
+msgid "My bridges don't work! I need help!"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:117
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:121
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr ""
+
+#: bridgedb/strings.py:128
+msgid "Here are your bridge lines:"
+msgstr ""
+
+#: bridgedb/strings.py:129
+msgid "Get Bridges!"
+msgstr ""
+
+#: bridgedb/strings.py:133
+msgid "Please select options for bridge type:"
+msgstr ""
+
+#: bridgedb/strings.py:134
+msgid "Do you need IPv6 addresses?"
+msgstr ""
+
+#: bridgedb/strings.py:135
+#, python-format
+msgid "Do you need a %s?"
+msgstr ""
+
+#: bridgedb/strings.py:139
+msgid "Your browser is not displaying images properly."
+msgstr ""
+
+#: bridgedb/strings.py:140
+msgid "Enter the characters from the image above..."
+msgstr ""
+
+#: bridgedb/strings.py:144
+msgid "How to start using your bridges"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: bridgedb/strings.py:146
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
+"page %s and then follow the instructions there for downloading and starting\n"
+"Tor Browser."
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:151
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
+"follow\n"
+"the wizard until it asks:"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:155
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor "
+"connections\n"
+"to the Tor network?"
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: bridgedb/strings.py:159
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and"
+"\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr ""
+
+#: bridgedb/strings.py:167
+msgid "Displays this message."
+msgstr ""
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: bridgedb/strings.py:171
+msgid "Request vanilla bridges."
+msgstr ""
+
+#: bridgedb/strings.py:172
+msgid "Request IPv6 bridges."
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: bridgedb/strings.py:174
+msgid "Request a Pluggable Transport by TYPE."
+msgstr ""
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: bridgedb/strings.py:177
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr ""
+
diff --git a/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..a8b6727
--- /dev/null
+++ b/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,101 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Translators:
+# AomNicha <vainilla7 at gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
+"POT-Creation-Date: 2013-03-27 21:41+0000\n"
+"PO-Revision-Date: 2013-07-27 08:50+0000\n"
+"Last-Translator: AomNicha <vainilla7 at gmail.com>\n"
+"Language-Team: Thai (http://www.transifex.com/projects/p/torproject/language/th/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: th\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: lib/bridgedb/templates/base.html:33
+msgid "What are bridges?"
+msgstr "สะà¸à¸²à¸à¸«à¸¡à¸²à¸¢à¸à¸¶à¸à¸à¸°à¹à¸£"
+
+#: lib/bridgedb/templates/base.html:34
+#, python-format
+msgid ""
+"%s Bridge relays %s are Tor relays that help you circumvent censorship."
+msgstr "%s สะà¸à¸²à¸à¸£à¸µà¹à¸¥à¸¢à¹ %s à¸à¸·à¸ Tor รีà¹à¸¥à¸¢à¹ à¸à¸µà¹à¸à¹à¸§à¸¢à¹à¸«à¹à¸à¸¸à¸à¸ªà¸²à¸¡à¸²à¸£à¸«à¸¥à¸µà¸à¸«à¸à¸µà¸à¸²à¸£à¸à¸£à¸§à¸à¸ªà¸à¸"
+
+#: lib/bridgedb/templates/base.html:39
+msgid "I need an alternative way of getting bridges!"
+msgstr "à¸à¸±à¸à¸à¹à¸à¸à¸à¸²à¸£à¸à¸²à¸à¹à¸¥à¸·à¸à¸à¸à¸·à¹à¸à¹à¸à¸·à¹à¸à¹à¸à¹à¸²à¸à¸¶à¸à¸ªà¸°à¸à¸²à¸"
+
+#: lib/bridgedb/templates/base.html:40
+#, python-format
+msgid ""
+"Another way to find public bridge addresses is to send an email (from a %s "
+"or a %s address) to %s with the line 'get bridges' by itself in the body of "
+"the mail."
+msgstr "à¸à¸µà¸à¸«à¸à¸¶à¹à¸à¸à¹à¸à¸à¸à¸²à¸ สำหรัà¸à¸à¹à¸à¸«à¸²à¸à¸µà¹à¸à¸¢à¸¹à¹à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¸ªà¸²à¸à¸²à¸£à¸à¸° สามารà¸à¸à¸³à¹à¸à¹à¸à¹à¸²à¸à¸à¸²à¸£à¸ªà¹à¸à¸à¸µà¹à¸¡à¸¥ (à¸à¸²à¸ %s หรืภà¸à¸µà¹à¸à¸¢à¸¹à¹ %s) à¹à¸à¸ªà¸¹à¹ %s à¸à¹à¸§à¸¢à¹à¸ªà¹à¸à¸à¸²à¸ \"\n\"à¸à¸²à¸£à¹à¸à¹à¸²à¸à¸¶à¸à¸ªà¸°à¸à¸²à¸\" ภายà¹à¸à¹à¸à¸·à¹à¸à¸«à¸²à¸à¸à¸à¸à¸µà¹à¸¡à¸¥"
+
+#: lib/bridgedb/templates/base.html:48
+msgid "My bridges don't work! I need help!"
+msgstr "à¸à¹à¸à¸à¸à¸²à¸£à¸à¸§à¸²à¸¡à¸à¹à¸§à¸¢à¹à¸«à¸¥à¸·à¸ สะà¸à¸²à¸à¹à¸¡à¹à¸à¸³à¸à¸²à¸"
+
+#: lib/bridgedb/templates/base.html:49
+#, python-format
+msgid ""
+"If your Tor doesn't work, you should email %s. Try including as much info "
+"about your case as you can, including the list of bridges you used, the "
+"bundle filename/version you used, the messages that Tor gave out, etc."
+msgstr "à¸à¹à¸² Tor à¹à¸¡à¹à¸à¸³à¸à¸²à¸ à¹à¸«à¹à¸à¸¸à¸à¸ªà¹à¸à¸à¸µà¹à¸¡à¸¥ %s à¸à¸£à¹à¸à¸¡à¹à¸à¸à¸à¹à¸à¸¡à¸¹à¸¥à¹à¸à¸µà¹à¸¢à¸§à¸à¸±à¸à¸ªà¸à¸²à¸à¸à¸²à¸£à¸à¹à¸à¸à¸à¸à¸¸à¸ รายà¸à¸·à¹à¸à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¸à¸µà¹à¸à¸¸à¸à¹à¸à¸¢à¹à¸à¹ à¸à¸±à¸à¸à¸µà¸£à¸²à¸¢à¸à¸·à¹à¸à¸«à¸£à¸·à¸à¹à¸§à¸à¸£à¹à¸à¸±à¹à¸à¸à¸µà¹à¸à¸¸à¸à¹à¸à¸¢à¹à¸à¹ à¸à¹à¸à¸à¸§à¸²à¸¡à¸à¸µà¹ Tor สà¹à¸à¸à¸¶à¸à¸à¸¸à¸ à¹à¸¥à¸°à¸à¸·à¹à¸à¹ à¸à¸µà¹à¸à¸³à¹à¸à¹à¸"
+
+#: lib/bridgedb/templates/bridges.html:10
+msgid ""
+"To use the above lines, go to Vidalia's Network settings page, and click "
+"\"My ISP blocks connections to the Tor network\". Then add each bridge "
+"address one at a time."
+msgstr " à¹à¸à¸·à¹à¸à¹à¸à¹à¸à¸²à¸à¸à¸²à¸¡à¸à¸³à¹à¸à¸°à¸à¸³à¸à¹à¸²à¸à¸à¸ à¹à¸à¸à¸µà¹à¸à¸²à¸£à¸à¸±à¹à¸à¸à¹à¸²à¹à¸à¸£à¸·à¸à¸à¹à¸²à¸¢ Vidalia à¸à¸²à¸à¸à¸±à¹à¸à¸à¸¥à¸´à¸à¸à¸µà¹ \"à¹à¸à¹à¸à¸ªà¸à¸µà¸à¸à¸à¸à¸±à¸à¸à¸¹à¸à¸à¸´à¸à¸à¸±à¹à¸à¹à¸¡à¹à¹à¸«à¹à¹à¸à¸·à¹à¸à¸¡à¸à¹à¸à¸à¸±à¸à¹à¸à¸£à¸·à¸à¸à¹à¸²à¸¢\" Tor à¹à¸¥à¸°à¸à¸à¹à¸à¸´à¹à¸¡à¸à¸µà¹à¸à¸¢à¸¹à¹à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¹à¸à¹à¸¥à¸°à¸à¸±à¸à¸¥à¸à¹à¸"
+
+#: lib/bridgedb/templates/bridges.html:13
+msgid "No bridges currently available"
+msgstr "à¹à¸¡à¹à¸¡à¸µà¸ªà¸°à¸à¸²à¸à¸à¸µà¹à¹à¸à¹à¸à¸²à¸à¹à¸à¹à¹à¸à¸à¸à¸°à¸à¸µà¹"
+
+#: lib/bridgedb/templates/captcha.html:6
+msgid "Upgrade your browser to Firefox"
+msgstr "à¸à¹à¸§à¸¢à¸à¸£à¸±à¸à¸£à¸¸à¹à¸à¹à¸à¸£à¸²à¸§à¹à¹à¸à¸à¸£à¹à¸à¸à¸à¸à¸¸à¸à¹à¸à¹à¸à¹à¸à¸¥à¹à¸à¹à¸à¸"
+
+#: lib/bridgedb/templates/captcha.html:8
+msgid "Type the two words"
+msgstr "à¹à¸«à¹à¸à¸´à¸¡à¸à¹à¸à¸³à¸ªà¸à¸à¸à¸³"
+
+#: lib/bridgedb/templates/index.html:6
+msgid "Step 1"
+msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 1"
+
+#: lib/bridgedb/templates/index.html:8
+#, python-format
+msgid "Get %s Tor Browser Bundle %s"
+msgstr "à¹à¸à¹ %s Tor Browser Bundle %s"
+
+#: lib/bridgedb/templates/index.html:13
+msgid "Step 2"
+msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 2"
+
+#: lib/bridgedb/templates/index.html:15
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "à¹à¸à¹ %s สะà¸à¸²à¸ %s"
+
+#: lib/bridgedb/templates/index.html:19
+msgid "Step 3"
+msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 3"
+
+#: lib/bridgedb/templates/index.html:21
+#, python-format
+msgid "Now %s add the bridges to Tor %s"
+msgstr "à¸à¸à¸°à¸à¸µà¹ %s à¹à¸à¹à¹à¸à¸´à¹à¸¡à¸ªà¸°à¸à¸²à¸à¹à¸à¸à¸µà¹ Tor %s à¹à¸¥à¹à¸§"
diff --git a/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..031ac6b
--- /dev/null
+++ b/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,389 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# eromytsatiffird <driffitastymore at gmail.com>, 2014
+# Emir Sarı <bitigchi at openmailbox.org>, 2014
+# Emre <conan at operamail.com>, 2013
+# erg26 <ergungorler at gmail.com>, 2012
+# Idil Yuksel <perfectionne at gmail.com>, 2014
+# Sercan AltundaÅ <>, 2012
+# ozkansib <s.ozkan at gyte.edu.tr>, 2014
+# Tekel Bira <psycookie at gmail.com>, 2012
+# Volkan Gezer <volkangezer at gmail.com>, 2014-2015
+# zeki <zeki.ozguryazilim at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-02-14 11:40+0000\n"
+"Last-Translator: Volkan Gezer <volkangezer at gmail.com>\n"
+"Language-Team: Turkish (http://www.transifex.com/projects/p/torproject/language/tr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: tr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Ãzgünüz! Ä°steÄinizle ilgili bir hata oluÅtu."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Bu otomatik bir mesajdır; lütfen yanıtlamayınız.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "Ä°Åte köprüleriniz:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Hız limitini aÅtınız. Lütfen yavaÅlayın! E-postalar arasındaki minimum zaman %s saattir.\nBu süre içinde göndereceÄiniz e-postalarınız yok sayılacaktır."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "KOMUTlar: (birden fazla seçeneÄı aynı anda belirtmek için KOMUTları birleÅtirin)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "BridgeDB'ye HoÅ Geldiniz!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "Åu an desteklenen transport TYPEs:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "Selam, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "Merhaba dostum!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "Açık Anahtarlar"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Bu e-posta %s için %s tarihinde %s saatinde gökkuÅakları,\ntek boynuzlu atlar ve pırıltılarla oluÅturulmuÅtur."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB, birçok %stür Eklenebilir Aktarımlar%s içeren\nköprü saÄlayabilir.\nBu Åekilde Ä°nternet trafiÄinizi izleyen birinin Tor kullandıÄınızı\nanlamasını zorlaÅtırmak için Tor AÄı'na yaptıÄınız\nbaÄlantıları karıÅtırmanıza yardımcı olabilir.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "IPv6 kullanan bazı köprüler de mevcut olmasının yanında bazı Eklenebilir Aktarımlar\nIPv6 uyumlu deÄildir.\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "Ek olarak BridgeDB %s hiçbir Eklenebilir Aktarıma sahip olmayan %s birçok düz\nözelliksiz köprüye sahiptir. Adları hoÅ olmayabilir ancak hala çoÄu durumda sansürü\naÅmaya yardım edebilirler.\n\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Köprüler nedir?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Köprüler %s sansürü aÅmanıza yardımcı olan Tor aynalarıdır."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "Köprüler edinmek için baÅka bir yola gereksinimim var!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Köprüleri almanın diÄer bir yolu da %s adresine bir e-posta göndermektir.\nLütfen e-postanın aÅaÄıdaki e-posta saÄlayıcılardan birinden alınmıŠbir\nadresten gönderilmesi gerektiÄini unutmayın:\n%s, %s veya %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "Köprülerim çalıÅmıyor! Yardıma ihtiyacım var!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "Tor'unuz çalıÅmıyorsa %s adresine e-posta göndermelisiniz. "
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "Durumunuz hakkında olabildiÄince fazla bilgi içermeyi deneyin.\nÃrneÄin kullanmaya çalıÅtıÄınız Eklenebilir Aktarımlar, Tor Tarayıcı sürümünüz ve\nTor'un gösterdiÄi tüm hata iletileri vb."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "Ä°Åte köprü çizgileriniz:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "Köprüleri Al!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "Lütfen köprü türü için seçenekleri belirleyin:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "IPv6 adreslerine ihtiyacınız var mı?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "%s ihtiyacınız var mı?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "Tarayıcınız resimleri düzgün görüntülemiyor."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "Yukarıdaki görüntüdeki karakterlerini giriniz..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Köprülerinizi kullanmaya nasıl baÅlarsınız"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "Tor Tarayıcı içerisine köprüleri girmek için Tor Tarayıcı'yı baÅlatmak üzere\n%s Tor Tarayıcı indirme sayfasındaki %s yönergeleri izleyin."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "'Tor AÄ Ayarları' penceresi açıldıÄında, 'Yapılandır' tıklayın ve Åunu soruncaya dek\nsihirbazı izleyin:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "Ä°nternet Servis SaÄlayıcınız (ISP) Tor aÄına baÄlantıyı engelliyor\nveya baÅka Åekillerde sansür uyguluyor mu?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "'Evet' ve ardından 'Ä°leri' tıklayın. Yeni köprülerinizi yapılandırmak için,\nköprü satırlarını kopyalayarak metin kutusu girdisine yapıÅtırın. Son olarak\n'BaÄlan' tıkladıÄınızda baÅlamak için hazır olmalısınız! EÄer sorun yaÅıyorsanız,\ndaha fazla yardım için 'Tor AÄ Ayarları' sihirbazındaki 'Yardım' düÄmesine\ntıklamayı deneyin."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "Bu iletiyi görüntüler."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "Ãzelliksiz köprüleri talep et."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "IPv6 köprüleri talep et."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "TYPE tarafından eklenebilir bir aktarım talep et."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "BridgeDB'nin ortak bir GnuPG anahtar kopyasını al."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "Bir hata Bildir"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Kaynak Kodu"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "DeÄiÅim günlüÄü"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "Ä°letiÅim"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "Hepsini Seç"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "QR Kodunu Göster"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "Köprü hatlarınız için QR Kodu"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Bu da nesi!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "QR Kodunuzu alınırken bir hata olmuŠgibi görünüyor."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Bu QR Kodu köprü hatlarınızı içeriyor. Köprü hatlarınızı bir mobil cihaza veya diÄer cihazlara kopyalamak için bir QR Tarayıcı kullanın."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Åu anda kullanılabilecek bir köprü yok..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Belki %s geri dönmeyi %s ve farklı bir köprü türü seçmeyi denemelisiniz!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "%s1%s adım"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "%s Tor Tarayıcı'yı %s İndirin"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "Adım %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "%s Köprüleri %s edinin"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "Adım %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Åimdi %s köprüleri Tor Tarayıcı'ya ekleyin %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sB%sana köprüleri ver!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "İleri Seçenekler"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "Hayır"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "hiçbiri"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sE%svet!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "Köprüleri %sA%sl"
diff --git a/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..00b8d59
--- /dev/null
+++ b/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,384 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Eugene ghostishev, 2014
+# LinuxChata, 2014-2015
+# Oleksii Golub <sclub2018 at yandex.ua>, 2015
+# Oleksii Golub <sclub2018 at yandex.ua>, 2013
+# ÐндÑÑй ÐандÑÑа <andriykopanytsia at gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-14 12:34+0000\n"
+"Last-Translator: LinuxChata\n"
+"Language-Team: Ukrainian (http://www.transifex.com/projects/p/torproject/language/uk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: uk\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "Ðи пÑиноÑимо наÑÑ Ð²Ð¸Ð±Ð°ÑеннÑ! ЩоÑÑ Ð¿ÑÑло не Ñак з ÐаÑим запиÑом."
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[Це авÑомаÑиÑне повÑдомленнÑ; бÑÐ´Ñ Ð»Ð°Ñка, не вÑдповÑдайÑе.]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "ÐаÑÑ Ð¼Ð¾ÑÑи:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "Ðи пеÑевиÑили Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ ÑвидкоÑÑÑ. ÐÑÐ´Ñ Ð»Ð°Ñка, ÑповÑлÑнÑÑÑÑÑ! ÐÑнÑмалÑний ÑÐ°Ñ Ð¼Ñж\nлиÑÑами %s годин. ÐÑÑ Ð¿Ð¾Ð´Ð°Ð»ÑÑÑ Ð»Ð¸ÑÑи в Ñей пеÑÑод ÑаÑÑ Ð±ÑдÑÑÑ ÑгноÑÑваÑиÑÑ."
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "Ðоманди: (комбÑнÑваÑи команди вказавÑи кÑлÑка ваÑÑанÑÑв одноÑаÑно)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "ÐаÑкаво пÑоÑимо Ñ BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "У даний ÑÐ°Ñ Ð¿ÑдÑÑимÑÑÑÑÑÑ transport TYPE:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "ÐÑивÑÑ, %s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ÐÑивÑÑ, дÑÑже!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "ÐÑдкÑиÑÑ ÐºÐ»ÑÑÑ"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "Цей лиÑÑ Ð±Ñв згенеÑовано з ÑайдÑгами, ÑдиноÑогами Ñ Ð±Ð»Ð¸ÑкÑÑками\nÐ´Ð»Ñ %s %s о %s."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB може забезпеÑиÑи bridges з декÑлÑкома %sÑипами Pluggable Transports%s,\nÑÐºÑ Ð¼Ð¾Ð¶ÑÑÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ñи пÑиÑ
оваÑи ÑÐ²Ð¾Ñ Ð·Ð²'Ñзки з Tor меÑежеÑ, Ñо ÑÑкладнÑÑ ÑобоÑÑ\nÐ´Ð»Ñ ÑиÑ
, Ñ
Ñо пеÑевÑÑÑÑ ÐÐ°Ñ ÑнÑеÑнеÑ-ÑÑаÑÑк, Ñоб визнаÑиÑи, Ñо Ðи\nвикоÑиÑÑовÑÑÑе Tor. \n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "ÐеÑÐºÑ Ð¼Ð¾ÑÑи з адÑеÑами IPv6, Ñакож доÑÑÑпнÑ, Ñ
оÑа деÑÐºÑ Pluggable\nTransports не ÑÑмÑÑÐ½Ñ Ð· IPv6.\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "ÐÑÑм Ñого, BridgeDB Ð¼Ð°Ñ Ð±Ð°Ð³Ð°Ñо plain-ol'-vanilla bridges %s без\nPluggable Transports %s, ÑкÑ, можливо, не звÑÑаÑÑ Ð³Ð°Ñно, але вони \nможÑÑÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ñи обÑйÑи ÑнÑеÑнеÑ-ÑензÑÑÑ Ð² багаÑÑоÑ
випадкаÑ
.\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "Що Ñаке ÑеÑÑанÑлÑÑÐ¾Ñ ÑÐ¸Ð¿Ñ Ð¼ÑÑÑ?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s ÐоÑÑи %s Ñ Tor Ñеле, ÑÐºÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð¶ÑÑÑ Ðам обÑйÑи ÑензÑÑÑ."
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "ÐÐµÐ½Ñ Ð¿Ð¾ÑÑÑбен алÑÑеÑнаÑивний ÑпоÑÑб оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ ÑпиÑÐºÑ Ð¼Ð¾ÑÑÑв!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "Ще один ÑпоÑÑб оÑÑимаÑи bridges - Ñе вÑдпÑавиÑи лиÑÑ Ð½Ð° адÑеÑÑ %s. ÐвеÑнÑÑÑ ÑвагÑ, Ñо\nÐи Ð¿Ð¾Ð²Ð¸Ð½Ð½Ñ Ð½Ð°Ð´ÑÑлаÑи лиÑÑ, викоÑиÑÑовÑÑÑи адÑеÑÑ Ð²Ñд одного з наÑÑÑпниÑ
поÑÑаÑалÑникÑв\nпоÑлÑг елекÑÑÐ¾Ð½Ð½Ð¾Ñ Ð¿Ð¾ÑÑи:\n%s, %s або %s."
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ÐÐ¾Ñ Ð¼Ð¾ÑÑи не пÑаÑÑÑÑÑ! ÐопоможÑÑÑ!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "ЯкÑо ÐÐ°Ñ Tor не пÑаÑÑÑ, Ðи можеÑе вÑдпÑавиÑи нам елекÑÑонного лиÑÑа %s."
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "СпÑобÑйÑе додаÑи ÑкнайбÑлÑÑе ÑнÑоÑмаÑÑÑ Ð¿Ñо ÐÐ°Ñ Ð²Ð¸Ð¿Ð°Ð´Ð¾Ðº, в ÑÐ¾Ð¼Ñ ÑиÑÐ»Ñ ÑпиÑок\nbridges Ñ Pluggable Transports, Ñо Ðи намагалиÑÑ Ð²Ð¸ÐºÐ¾ÑиÑÑовÑваÑи, веÑÑÑÑ Ð±ÑаÑзеÑа Tor,\nÑ Ð±ÑдÑ-ÑÐºÑ Ð¿Ð¾Ð²ÑдомленнÑ, ÑÐºÑ Tor видавав, Ñ Ñ.д."
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "ÐÑнÑÑ Ð´Ð»Ñ ÐаÑого моÑÑÑ:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "ÐÑÑимаÑи Bridges!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "ÐÑÐ´Ñ Ð»Ð°Ñка, обеÑÑÑÑ Ð¿Ð°ÑамеÑÑи ÑÐ¸Ð¿Ñ Ð¼ÑÑÑ:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "Ðам поÑÑÑÐ±Ð½Ñ Ð°Ð´ÑеÑи IPv6?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "Ðам поÑÑÑбен %s?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "ÐÐ°Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ вÑдобÑÐ°Ð¶Ð°Ñ Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð½Ñ Ð½Ð°Ð»ÐµÐ¶Ð½Ð¸Ð¼ Ñином."
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "ÐведÑÑÑ Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð½Ñ Ñимволи ..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "Як поÑаÑи коÑиÑÑÑваÑиÑÑ ÐаÑими моÑÑами"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "ÐÐ»Ñ Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ bridges Ñ Ð±ÑаÑзеÑÑ Tor, доÑÑимÑйÑеÑÑ ÑнÑÑÑÑкÑÑй %s на ÑÑоÑÑнÑÑ \nзаванÑÐ°Ð¶ÐµÐ½Ð½Ñ Ð±ÑаÑзеÑа Tor %s Ð´Ð»Ñ Ð·Ð°Ð¿ÑÑÐºÑ Ð±ÑаÑзеÑа Tor."
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "Ðоли дÑалог \"ÐалаÑÑÑÐ²Ð°Ð½Ð½Ñ Ð¼ÐµÑÐµÐ¶Ñ Tor\" вÑдкÑиÑÑÑÑÑ, наÑиÑнÑÑÑ \"ÐалаÑÑÑваÑи\" Ñ Ð´Ð¾ÑÑимÑйÑеÑÑ\nпÑдказкам майÑÑÑа, допоки вÑн не запÑоÑиÑÑ:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ÐаÑÑй ÑнÑеÑнеÑ-пÑÐ¾Ð²Ð°Ð¹Ð´ÐµÑ (ISP) блокÑÑ Ð°Ð±Ð¾ ÑензÑÑÑÑ Ð·'ÑднаннÑ\nдо меÑÐµÐ¶Ñ Tor?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "ÐибеÑÑÑÑ \"Так\", а поÑÑм наÑиÑнÑÑÑ \"ÐалÑ\". ÐÐ»Ñ Ð½Ð°Ð»Ð°ÑÑÑÐ²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ
bridges, ÑкопÑÑйÑе Ñ\nвÑÑавÑе лÑнÑÑ bridge Ñ Ð¿Ð¾Ð»Ðµ Ð´Ð»Ñ Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ ÑекÑÑÑ. ÐоÑÑм, наÑиÑнÑÑÑ 'ÐÑдклÑÑаÑиÑÑ', Ñ\nвÑе повинно пÑаÑÑваÑи! ЯкÑо Ñ ÐÐ°Ñ Ð²Ð¸Ð½Ð¸ÐºÐ»Ð¸ пÑоблеми, ÑпÑобÑйÑе наÑиÑнÑÑи \"ÐовÑдка\" \nв майÑÑÑÐ¾Ð²Ñ \"ÐеÑÐµÐ¶ÐµÐ²Ñ Ð¿Ð°ÑамеÑÑи Tor' Ð´Ð»Ñ Ð¾ÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°ÑÐºÐ¾Ð²Ð¾Ñ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ð¸."
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "ÐÑдобÑÐ°Ð¶Ð°Ñ Ñе повÑдомленнÑ."
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ vanilla bridges."
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ IPv6 bridges."
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Pluggable Transport по TYPE."
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "ÐÑÑимаÑи копÑÑ Ð²ÑдкÑиÑого GnuPG клÑÑа Ð´Ð»Ñ BridgeDB."
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "ÐовÑдомиÑи пÑо помилкÑ"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "Ðод"
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "СпиÑок змÑн"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "ÐонÑакÑ"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "ÐибÑаÑи вÑÑ"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "ÐоказаÑи QR-код"
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "QR-код Ð´Ð»Ñ Ð°Ð´ÑÐµÑ ÑеÑÑанÑлÑÑоÑÑв"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "Ðй-ой, spaghettios!"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "ÐдаÑÑÑÑÑ, бÑло Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ¾Ñ Ð¾ÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Ð²Ð°Ñого QR-код."
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "Це QR-код мÑÑÑиÑÑ Ð°Ð´ÑеÑи ÐаÑиÑ
ÑеÑÑанÑлÑÑоÑÑв. ÐÑдÑканÑйÑе його пÑиÑÑÑоÑм Ð´Ð»Ñ Ð·ÑиÑÑÐ²Ð°Ð½Ð½Ñ QR-кодÑв, Ñоб ÑкопÑÑваÑи адÑеÑи ÐаÑиÑ
ÑеÑÑанÑлÑÑоÑÑв на мобÑлÑÐ½Ñ Ñа ÑнÑÑ Ð¿ÑиÑÑÑоÑ."
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "Рданий ÑÐ°Ñ Ð½ÐµÐ¼Ð°Ñ Ð´Ð¾ÑÑÑпниÑ
моÑÑÑв ..."
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "Ðожливо, Ðам ÑлÑд ÑпÑобÑваÑи %s повеÑнÑÑиÑÑ %s Ñ Ð²Ð¸Ð±ÑаÑи ÑнÑий Ñип моÑÑÑ!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "ÐÑок %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "ÐаванÑажиÑи %s Tor Browser %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "ÐÑок %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "ÐÑÑимайÑе %s моÑÑи %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "ÐÑок %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "Ð¢ÐµÐ¿ÐµÑ %s додай bridges до Tor Browser %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sТ%sÑлÑки дай Ð¼ÐµÐ½Ñ Ð¼Ð¾ÑÑи!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "РозÑиÑÐµÐ½Ñ ÐаÑамеÑÑи"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "ÐÑ"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "жоден"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sТ%sак!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sÐ%sÑÑимаÑи моÑÑи"
diff --git a/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..11bcc18
--- /dev/null
+++ b/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,388 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2015 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# Wu Ming Shi, 2013
+# Wu Ming Shi, 2013
+# Wu Ming Shi, 2013
+# Christopher Meng <cickumqt at gmail.com>, 2012
+# hanl <iamh4n at gmail.com>, 2011
+# Meng3, 2014
+# leungsookfan <leung.sookfan at riseup.net>, 2014
+# Wu Ming Shi, 2014-2015
+# YF <yfdyh000 at gmail.com>, 2014-2015
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2015-02-03 03:24+0000\n"
+"PO-Revision-Date: 2015-03-08 19:30+0000\n"
+"Last-Translator: Wu Ming Shi\n"
+"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/torproject/language/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:122
+msgid "Sorry! Something went wrong with your request."
+msgstr "æ±æï¼ä½ çé®ä»¶è¯·æ±åºç°é®é¢ã"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[è¿æ¯ä¸å°èªå¨çæçé®ä»¶ï¼è¯·å¿åå¤ã]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "以ä¸æ¯ä¸ºä½ æä¾çç½æ¡¥ï¼"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "æ¨å·²è¶
åºäºåéé¢ççéå¶ï¼è¯·æ
¢æ
¢æ¥ï¼ä¸¤å°é®ä»¶ä¹é´éè¦æå° %s å°æ¶çé´éãå¨é´éæé´ååºçææé®ä»¶å°è¢«å¿½ç¥ã"
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "å½ä»¤ï¼ï¼ç»å使ç¨å½ä»¤å¯åæ¶æå®å¤ä¸ªé项ï¼"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "欢è¿æ¥å° BridgeDBï¼"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "æ¯æç transport TYPEï¼"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "ä½ å¥½ï¼%sï¼"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "ä½ å¥½ï¼æåï¼"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
+msgid "Public Keys"
+msgstr "å
Œ
±å¯å"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "æ¬é®ä»¶æ¯å¯¹ %s çèªå¨åå¤ï¼æ¥æ %s %sã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB è½æä¾ %s å ç§ Pluggable Transports %s ç±»åç½æ¡¥ï¼å¯ç¨äºæ··æ· Tor ç½ç»çè¿æ¥ï¼ä»è让ç½ç»çæ§è
é¾ä»¥å¤æä½ å¨ä½¿ç¨ Torã\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "ä¸äºä½¿ç¨IPv6å°åçbridgesï¼ç½æ¡¥ï¼ä¹è½ä½¿ç¨ï¼ä½æ¯\nPluggable Transports并ä¸å
¼å®¹IPv6ã\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "æ¤å¤ï¼BridgeDB æä¾å¾å¤ %s éPluggable Transports %s çæ®éç½æ¡¥ã\nè½ç¶å¬èµ·æ¥ä¸å¤é
·ï¼ä½æ¯è¿äºæ®éç½æ¡¥ä¾ç¶å¯ä»¥å¨å¾å¤æ
åµä¸å¸®å©ç»è¿å®¡æ¥ã\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "ä»ä¹æ¯ç½æ¡¥ï¼"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s Bridges %s æ¯å¸®å©ä½ ç»è¿å®¡æ¥æå°éç Tor relayï¼ä¸ç»§ç¹ï¼ã"
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "éè¦ä½¿ç¨å
¶ä»è·åæ¹å¼è·åç½æ¡¥ï¼"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "å¦ä¸ç§è·åç½æ¡¥çæ¹å¼æ¯åéçµåé®ä»¶è³ %sã注æï¼å¿
须使ç¨çµåé®ç®±åé请æ±ï¼%sã%s æ %sã"
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "ç¨ç½æ¡¥ä¹æ æ³è¿æ¥ï¼éè¦å¸®å©ï¼"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "å¦æ Tor æ æ³æ£å¸¸è¿è¡ï¼è¯·åé®ä»¶è³ %sã"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "请尽éæè¿°ä½ çæ
åµï¼å
æ¬ä½ å°è¯è¿çbridgesåPluggable Transportsï¼ä½ çToræµè§å¨çæ¬ï¼è¿æä»»ä½Toræ¾ç¤ºè¿çä¿¡æ¯ççã"
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "以ä¸æ¯ä¸ºä½ æä¾çç½æ¡¥ï¼"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "è·å¾ç½æ¡¥ï¼"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "请éæ©ç±»åç±»åã"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "æ¯å¦éè¦IPv6å°åï¼"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "æ¯å¦éè¦ %sï¼"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "æµè§å¨æ æ³æ£ç¡®æ¾ç¤ºå¾çã"
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "请è¾å
¥ä¸å¾ä¸çå符ï¼ä¸åºå大å°åï¼..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "å¦ä½ä½¿ç¨ç½æ¡¥"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "å¦éå¨ Tor æµè§å¨ä¸æ·»å ç½æ¡¥ï¼è¯·å
æ ¹æ® %s Tor æµè§å¨ä¸è½½é¡µé¢ %s ç说ææ¥å¯å¨Tor æµè§å¨ã"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "å½âTor ç½ç»è®¾ç½®â 对è¯æ¡åºç°æ¶ï¼ç¹å»âé
ç½®â ï¼ç¶åæ ¹æ®å导æä½ï¼ç´å°çå°ä¸é¢çé®é¢ï¼"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "ç½ç»æä¾åï¼ISPï¼æå
¶ä»äººæ¯å¦å¯¹ Tor ç½ç»è¿æ¥è¿è¡å®¡æ¥æå°éï¼"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "éæ©âæ¯âï¼ç¶åç¹å»âä¸ä¸æ¥âãå¦ä¸æ·»å æ°çç½æ¡¥ï¼è¯·å°ç½æ¡¥å°åè¡éè¿å¤å¶ç²è´´çæ¹å¼è¾å
¥å°ææ¬è¾å
¥æ¡ãæåï¼ç¹å»âè¿æ¥âå°±å¯ä»¥è¿æ¥è³ Tor ç½ç»ãå¦æé®é¢éè¦å¸®å©ï¼è¯·ç¹å»âTor ç½ç»è®¾ç½®âå导çªå£ä¸çâ帮å©âæé®ã"
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "æ¾ç¤ºè¿æ¡ä¿¡æ¯ã"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "请æ±æ®éç½æ¡¥ã"
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "请æ±IPv6ç½ç»ã"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "æ ¹æ®TYPE 请æ±Pluggable Transportã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "è·å BridgeDB ç GnuPG å
Œ
±å¯åã"
+
+#: lib/bridgedb/templates/base.html:89
+msgid "Report a Bug"
+msgstr "æ¥å Bug"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Source Code"
+msgstr "æºä»£ç "
+
+#: lib/bridgedb/templates/base.html:95
+msgid "Changelog"
+msgstr "æ´æ¹æ¥å¿"
+
+#: lib/bridgedb/templates/base.html:98
+msgid "Contact"
+msgstr "èç³»æ¹å¼"
+
+#: lib/bridgedb/templates/bridges.html:81
+msgid "Select All"
+msgstr "éæ©å
¨é¨"
+
+#: lib/bridgedb/templates/bridges.html:87
+msgid "Show QRCode"
+msgstr "æ¾ç¤ºäºç»´ç "
+
+#: lib/bridgedb/templates/bridges.html:100
+msgid "QRCode for your bridge lines"
+msgstr "ç½æ¡¥äºç»´ç "
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:115
+#: lib/bridgedb/templates/bridges.html:175
+msgid "Uh oh, spaghettios!"
+msgstr "åååï¼"
+
+#: lib/bridgedb/templates/bridges.html:116
+msgid "It seems there was an error getting your QRCode."
+msgstr "è·åäºç»´ç æ¶åºéã"
+
+#: lib/bridgedb/templates/bridges.html:121
+msgid ""
+"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
+" your bridge lines onto mobile and other devices."
+msgstr "äºç»´ç å
å«ç½æ¡¥ä¿¡æ¯ãå©ç¨äºç»´ç æ«æç¨åºï¼å¯å°ç¸åºçç½æ¡¥ä¿¡æ¯å¤å¶å°ææºæå
¶ä»è®¾å¤ã"
+
+#: lib/bridgedb/templates/bridges.html:181
+msgid "There currently aren't any bridges available..."
+msgstr "ç°å¨æ²¡æå¯ç¨çç½æ¡¥ã"
+
+#: lib/bridgedb/templates/bridges.html:182
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr "è¯è¯ %såé%så°åä¸é¡µé¢ï¼ç¶åéæ©å
¶ä»ç±»åçç½æ¡¥ã"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "第 %s 1 %s æ¥"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "ä¸è½½ %s Tor æµè§å¨ %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "第 %s 2 %s æ¥"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "è·å %s bridges %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "第 %s 3 %s æ¥"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "å¦ä½ %s å¨ Tor æµè§å¨æ·»å ç½æ¡¥%s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr " ç´æ¥ç»æç½æ¡¥(%sJ%s)ï¼ "
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "é«çº§é项"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "å¦"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "æ "
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:127
+#, python-format
+msgid "%sY%ses!"
+msgstr "æ¯(%sY%s)ï¼"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:151
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "è·åç½æ¡¥(%sG%s)"
diff --git a/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po b/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po
new file mode 100644
index 0000000..acda44c
--- /dev/null
+++ b/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po
@@ -0,0 +1,358 @@
+# Translations template for BridgeDB.
+# Copyright (C) 2014 'The Tor Project, Inc.'
+# This file is distributed under the same license as the BridgeDB project.
+#
+# Translators:
+# danfong <danfong.hsieh at gmail.com>, 2014
+# LNDDYL, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: The Tor Project\n"
+"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
+"POT-Creation-Date: 2014-07-26 02:11+0000\n"
+"PO-Revision-Date: 2014-11-24 09:10+0000\n"
+"Last-Translator: LNDDYL\n"
+"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/torproject/language/zh_TW/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: zh_TW\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
+#. any string (regardless of capitalization and/or punctuation):
+#. "BridgeDB"
+#. "pluggable transport"
+#. "pluggable transports"
+#. "obfs2"
+#. "obfs3"
+#. "scramblesuit"
+#. "fteproxy"
+#. "Tor"
+#. "Tor Browser"
+#: lib/bridgedb/HTTPServer.py:121
+msgid "Sorry! Something went wrong with your request."
+msgstr "æ±æ!æ¨çè«æ±ç¼çé¯èª¤ã"
+
+#: lib/bridgedb/strings.py:18
+msgid "[This is an automated message; please do not reply.]"
+msgstr "[éæ¯ä¸åèªååè¦éµä»¶;è«ä¸è¦åè¦ã]"
+
+#: lib/bridgedb/strings.py:20
+msgid "Here are your bridges:"
+msgstr "é裡æ¯æ¨çæ©æ¥:"
+
+#: lib/bridgedb/strings.py:22
+#, python-format
+msgid ""
+"You have exceeded the rate limit. Please slow down! The minimum time between\n"
+"emails is %s hours. All further emails during this time period will be ignored."
+msgstr "æ¨å·²è¶
éé度éå¶ãè«æ¸æ
¢é度!é»åéµä»¶ä¹éçæçæéçº %s åå°æãå¨é段æéå
§ææå
¶ä»çéµä»¶å°è¢«å¿½ç¥ã"
+
+#: lib/bridgedb/strings.py:25
+msgid ""
+"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
+msgstr "å½ä»¤:(çµåå½ä»¤å¯ä»¥åææå®å¤åé¸é
)"
+
+#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+#: lib/bridgedb/strings.py:28
+msgid "Welcome to BridgeDB!"
+msgstr "æ¡è¿ä½¿ç¨ BridgeDB!"
+
+#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+#: lib/bridgedb/strings.py:30
+msgid "Currently supported transport TYPEs:"
+msgstr "ç®åæ¯æ´çå³è¼¸é¡å:"
+
+#: lib/bridgedb/strings.py:31
+#, python-format
+msgid "Hey, %s!"
+msgstr "å¿ï¼%s!"
+
+#: lib/bridgedb/strings.py:32
+msgid "Hello, friend!"
+msgstr "æåï¼æ¨å¥½!"
+
+#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
+msgid "Public Keys"
+msgstr "å
¬ééé°"
+
+#. TRANSLATORS: This string will end up saying something like:
+#. "This email was generated with rainbows, unicorns, and sparkles
+#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+#: lib/bridgedb/strings.py:37
+#, python-format
+msgid ""
+"This email was generated with rainbows, unicorns, and sparkles\n"
+"for %s on %s at %s."
+msgstr "éå°é»åéµä»¶ä½¿ç¨ rainbowsãunicorns å sparkles ç¢çæ¼\n %s å¨ %s å¨ %sã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#. TRANSLATORS: Please DO NOT translate "Tor Network".
+#: lib/bridgedb/strings.py:47
+#, python-format
+msgid ""
+"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
+"which can help obfuscate your connections to the Tor Network, making it more\n"
+"difficult for anyone watching your internet traffic to determine that you are\n"
+"using Tor.\n"
+"\n"
+msgstr "BridgeDB å¯ä»¥ä½¿ç¨å¹¾ç¨® Pluggable Transports%s ç %stypes ä¾æä¾æ©æ¥ï¼\nå®å¯ä»¥å¹«å©æ¨æ··æ·é£æ¥å° Tor Networkï¼ä½¿å
¶ä»»ä½äººèç±ç£çæ¨ç網路æµ\néä¾ç¢ºå®æ¨æ£ä½¿ç¨ Tor ææ´å å°é£\nã\n"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#: lib/bridgedb/strings.py:54
+msgid ""
+"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
+"Transports aren't IPv6 compatible.\n"
+"\n"
+msgstr "æäºå
·æ IPv6 ä½åçæ©æ¥ä¹å¯ç¨ï¼éç¶æäº Pluggable\nTransports è IPv6 ä¸ç¸å®¹ã\n\n"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
+#. which do not have Pluggable Transports, and only speak the regular,
+#. boring Tor protocol. Translate it as you see fit. Have fun with it.
+#: lib/bridgedb/strings.py:63
+#, python-format
+msgid ""
+"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
+"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
+"help to circumvent internet censorship in many cases.\n"
+"\n"
+msgstr "æ¤å¤ï¼BridgeDB ææ®éçæ©æ¥ %sï¼æ²æä»»ä½\nPluggable Transports %sï¼éä¹è¨±è½èµ·ä¾ä¸é
·ï¼ä½å®\nä»æå©æ¼è¦é¿å¨è¨±å¤æ
æ³ä¸ç網路審æ¥ã\n"
+
+#: lib/bridgedb/strings.py:76
+msgid "What are bridges?"
+msgstr "ä»éº¼æ¯æ©æ¥?"
+
+#: lib/bridgedb/strings.py:77
+#, python-format
+msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
+msgstr "%s æ©æ¥ %s æ¯ Tor ä¸ç¹¼ï¼å¯å¹«æ¨è¦é¿å¯©æ¥ã"
+
+#: lib/bridgedb/strings.py:82
+msgid "I need an alternative way of getting bridges!"
+msgstr "æéè¦ä½¿ç¨å
¶ä»æ¹å¼åå¾æ©æ¥!"
+
+#: lib/bridgedb/strings.py:83
+#, python-format
+msgid ""
+"Another way to get bridges is to send an email to %s. Please note that you must\n"
+"send the email using an address from one of the following email providers:\n"
+"%s, %s or %s."
+msgstr "å¦ä¸ç¨®åå¾æ©æ¥çæ¹å¼çºç¼éé»åéµä»¶è³ %sãè«æ³¨æï¼æ¨å¿
é \n使ç¨å¾ä¸åé»åéµä»¶æä¾å» åä¹ä¸çä½åä¾ç¼éé»åéµä»¶:\n%s, %s æ %sã"
+
+#: lib/bridgedb/strings.py:90
+msgid "My bridges don't work! I need help!"
+msgstr "æ©æ¥ç¡æ³æ£å¸¸å·è¡ï¼æéè¦å¹«å©!"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:92
+#, python-format
+msgid "If your Tor doesn't work, you should email %s."
+msgstr "å¦ææ¨ç Tor ç¡æ³éä½ï¼æ¨æ該ç¼éé»åéµä»¶çµ¦ %sã"
+
+#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:96
+msgid ""
+"Try including as much info about your case as you can, including the list of\n"
+"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
+"and any messages which Tor gave out, etc."
+msgstr "試èå
å«æéæ¨æ
æ³çç¡å¯è½å¤çè³è¨ï¼å
å«æ¨å試使ç¨éç \næ©æ¥å Pluggable Transports æ¸
å®ï¼æ¨ç Tor ç覽å¨çæ¬ï¼ \n以å Tor 給åºçä»»ä½è¨æ¯ã"
+
+#: lib/bridgedb/strings.py:103
+msgid "Here are your bridge lines:"
+msgstr "é裡æ¯æ¨çæ©æ¥ç·è·¯:"
+
+#: lib/bridgedb/strings.py:104
+msgid "Get Bridges!"
+msgstr "åå¾æ©æ¥!"
+
+#: lib/bridgedb/strings.py:108
+msgid "Please select options for bridge type:"
+msgstr "è«é¸ææ©æ¥é¡åçé¸é
:"
+
+#: lib/bridgedb/strings.py:109
+msgid "Do you need IPv6 addresses?"
+msgstr "æ¨éè¦ IPv6 ä½åå?"
+
+#: lib/bridgedb/strings.py:110
+#, python-format
+msgid "Do you need a %s?"
+msgstr "æ¨éè¦ %s å?"
+
+#: lib/bridgedb/strings.py:114
+msgid "Your browser is not displaying images properly."
+msgstr "æ¨çç覽å¨ä¸è½æ£ç¢ºé¡¯ç¤ºååã"
+
+#: lib/bridgedb/strings.py:115
+msgid "Enter the characters from the image above..."
+msgstr "å¾ä¸é¢çååä¸è¼¸å
¥åå
..."
+
+#: lib/bridgedb/strings.py:119
+msgid "How to start using your bridges"
+msgstr "å¦ä½éå§ä½¿ç¨æ¨çæ©æ¥"
+
+#. TRANSLATORS: Please DO NOT translate "Tor Browser".
+#: lib/bridgedb/strings.py:121
+#, python-format
+msgid ""
+"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
+"Browser download page %s to start Tor Browser."
+msgstr "å°æ©æ¥è¼¸å
¥å° Tor ç覽å¨ä¸ï¼è«æç
§ %s ä¸ç說æï¼Tor\nç覽å¨ä¸è¼é é¢ %s ä¾åå Tor ç覽å¨ã"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:125
+msgid ""
+"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
+"the wizard until it asks:"
+msgstr "ç¶ãTor 網路è¨å®ãå°è©±å½åºæï¼æä¸ä¸ãè¨å®ãï¼ç¶å¾æç
§ç²¾éï¼ç´å°å®è¦æ±:"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:129
+msgid ""
+"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
+"to the Tor network?"
+msgstr "æ¨ç網é網路æåä¾æè
(ISP)é»ææå¯©æ¥ Tor 網路é£ç·?"
+
+#. TRANSLATORS: Please DO NOT translate "Tor".
+#: lib/bridgedb/strings.py:133
+msgid ""
+"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
+"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
+"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
+"button in the 'Tor Network Settings' wizard for further assistance."
+msgstr "é¸æãæ¯ãï¼ç¶å¾æä¸ä¸ãä¸ä¸æ¥ããè¦è¨å®æ¨çæ°æ©æ¥ï¼è¤è£½å\nå°æ©æ¥ç·è·¯è²¼ä¸å°æå輸å
¥æ¹å¡ä¸ãæå¾ï¼æä¸ä¸ãé£æ¥ã就好ã\nå¦ææ¨éå°éº»ç
©ï¼è«å試æä¸ä¸ãTor 網路è¨å®ãç²¾éä¸çã説æã\næéå°æ±é²ä¸æ¥çåå© ã"
+
+#: lib/bridgedb/strings.py:141
+msgid "Displays this message."
+msgstr "顯示æ¤è¨æ¯ã"
+
+#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+#. same non-Pluggable Transport bridges described above as being
+#. "plain-ol'-vanilla" bridges.
+#: lib/bridgedb/strings.py:145
+msgid "Request vanilla bridges."
+msgstr "è«æ± vanilla æ©æ¥ã"
+
+#: lib/bridgedb/strings.py:146
+msgid "Request IPv6 bridges."
+msgstr "è«æ± IPv6 æ©æ¥ã"
+
+#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+#: lib/bridgedb/strings.py:148
+msgid "Request a Pluggable Transport by TYPE."
+msgstr "æç
§ TYPE è«æ± Pluggable Transportã"
+
+#. TRANSLATORS: Please DO NOT translate "BridgeDB".
+#. TRANSLATORS: Please DO NOT translate "GnuPG".
+#: lib/bridgedb/strings.py:151
+msgid "Get a copy of BridgeDB's public GnuPG key."
+msgstr "åå¾ BridgeDB çå
Œ
± GnuPG éé°å¯æ¬ã"
+
+#: lib/bridgedb/templates/base.html:92
+msgid "Report a Bug"
+msgstr "åå ±é¯èª¤"
+
+#: lib/bridgedb/templates/base.html:94
+msgid "Source Code"
+msgstr "åå§ç¨å¼ç¢¼"
+
+#: lib/bridgedb/templates/base.html:97
+msgid "Changelog"
+msgstr "è®æ´è¨é"
+
+#: lib/bridgedb/templates/base.html:99
+msgid "Contact"
+msgstr "è¯çµ¡è³è¨"
+
+#. TRANSLATORS: Please translate this into some silly way to say
+#. "There was a problem!" in your language. For example,
+#. for Italian, you might translate this into "Mama mia!",
+#. or for French: "Sacrebleu!". :)
+#: lib/bridgedb/templates/bridges.html:66
+msgid "Uh oh, spaghettios!"
+msgstr "æåé¡!"
+
+#: lib/bridgedb/templates/bridges.html:72
+msgid "There currently aren't any bridges available..."
+msgstr "ç®åæ²æä»»ä½æ©æ¥å¯ç¨..."
+
+#: lib/bridgedb/templates/bridges.html:73
+#, python-format
+msgid ""
+" Perhaps you should try %s going back %s and choosing a different bridge "
+"type!"
+msgstr " ä¹è¨±æ¨æå試 %s åå° %sï¼ç¶å¾é¸æä¸åçæ©æ¥é¡å!"
+
+#: lib/bridgedb/templates/index.html:11
+#, python-format
+msgid "Step %s1%s"
+msgstr "æ¥é© %s1%s"
+
+#: lib/bridgedb/templates/index.html:13
+#, python-format
+msgid "Download %s Tor Browser %s"
+msgstr "ä¸è¼ %s Tor çè¦½å¨ %s"
+
+#: lib/bridgedb/templates/index.html:25
+#, python-format
+msgid "Step %s2%s"
+msgstr "æ¥é© %s2%s"
+
+#: lib/bridgedb/templates/index.html:27
+#, python-format
+msgid "Get %s bridges %s"
+msgstr "åå¾ %s æ©æ¥ %s"
+
+#: lib/bridgedb/templates/index.html:36
+#, python-format
+msgid "Step %s3%s"
+msgstr "æ¥é© %s3%s"
+
+#: lib/bridgedb/templates/index.html:38
+#, python-format
+msgid "Now %s add the bridges to Tor Browser %s"
+msgstr "ç¾å¨ %s å°æ©æ¥å å
¥å° Tor çè¦½å¨ %s"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. (These are used to insert HTML5 underlining tags, to mark accesskeys
+#. for disabled users.)
+#: lib/bridgedb/templates/options.html:38
+#, python-format
+msgid "%sJ%sust give me bridges!"
+msgstr "%sJ%sust 給ææ©æ¥!"
+
+#: lib/bridgedb/templates/options.html:52
+msgid "Advanced Options"
+msgstr "é²éé¸é
"
+
+#: lib/bridgedb/templates/options.html:88
+msgid "No"
+msgstr "å¦"
+
+#: lib/bridgedb/templates/options.html:89
+msgid "none"
+msgstr "ç¡"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
+#: lib/bridgedb/templates/options.html:130
+#, python-format
+msgid "%sY%ses!"
+msgstr "%sY%ses!"
+
+#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
+#. beginning of words are present in your final translation. Thanks!
+#. TRANSLATORS: Please do NOT translate the word "bridge"!
+#: lib/bridgedb/templates/options.html:154
+#, python-format
+msgid "%sG%set Bridges"
+msgstr "%sG%set Bridges"
diff --git a/bridgedb/interfaces.py b/bridgedb/interfaces.py
new file mode 100644
index 0000000..89ddb12
--- /dev/null
+++ b/bridgedb/interfaces.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_interfaces ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+"""All available ``zope.interface``s in BridgeDB."""
+
+from zope.interface import Interface
+from zope.interface import Attribute
+from zope.interface import implementer
+
+
+class IName(Interface):
+ """An interface specification for a named object."""
+
+ name = Attribute("A string which identifies this object.")
+
+
+ at implementer(IName)
+class Named(object):
+ """A named object"""
+
+ #: The characters used to join child Named object's names with our name.
+ separator = ' '
+
+ def __init__(self):
+ self._name = str()
+
+ @property
+ def name(self):
+ """Get the name of this object.
+
+ :rtype: str
+ :returns: A string which identifies this object.
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Set a **name** for identifying this object.
+
+ This is used to identify the object in log messages; the **name**
+ doesn't necessarily need to be unique. Other :class:`Named` objects
+ which are properties of a :class:`Named` object may inherit their
+ parents' **name**s.
+
+ >>> from bridgedb.distribute import Named
+ >>> named = Named()
+ >>> named.name = 'Excellent Super-Awesome Thing'
+ >>> named.name
+ 'Excellent Super-Awesome Thing'
+
+ :param str name: A name for this object.
+ """
+ self._name = name
+
+ for attr in self.__dict__.values():
+ if IName.providedBy(attr):
+ attr.name = self.separator.join([name, attr.name])
diff --git a/bridgedb/parse/__init__.py b/bridgedb/parse/__init__.py
new file mode 100644
index 0000000..0f3f20d
--- /dev/null
+++ b/bridgedb/parse/__init__.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013 Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+'''Package containing modules for parsing data.
+
+.. py:module:: bridgedb.parse
+ :synopsis: Package containing modules for parsing data.
+'''
+
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import binascii
+
+
+class InvalidBase64(ValueError):
+ """Raised if parsing or decoding cannot continue due to invalid base64."""
+
+
+def padBase64(b64string):
+ """Re-add any stripped equals sign character padding to a b64 string.
+
+ :param string b64string: A base64-encoded string which might have had its
+ trailing equals sign (``=``) padding removed.
+ :raises ValueError: if there was any error while manipulating the string.
+ :returns: A properly-padded (according to the base64 spec: :rfc:`4648`)
+ string.
+ """
+ addchars = 0
+ try:
+ b64string = b64string.strip()
+ remainder = len(b64string) % 4
+ if 2 <= remainder <= 3:
+ addchars = 4 - remainder
+ except AttributeError as error:
+ raise ValueError(error)
+ else:
+ if not addchars:
+ raise ValueError("Invalid base64-encoded string: %r" % b64string)
+ b64string += '=' * addchars
+
+ return b64string
+
+def parseUnpaddedBase64(field):
+ """Parse an unpadded, base64-encoded field.
+
+ The **field** will be re-padded, if need be, and then base64 decoded.
+
+ :param str field: Should be some base64-encoded thing, with any trailing
+ ``=``-characters removed.
+ :raises InvalidBase64: if there is an error in either unpadding or decoding
+ **field**.
+ :rtype: str
+ :returns: The base64-decoded **field**.
+ """
+ if field.endswith('='):
+ raise InvalidBase64("Unpadded, base64-encoded networkstatus field "\
+ "must not end with '=': %r" % field)
+
+ try:
+ paddedField = padBase64(field) # Add the trailing equals sign back in
+ except ValueError as error:
+ raise InvalidBase64(error)
+
+ debasedField = binascii.a2b_base64(paddedField)
+ if not debasedField:
+ raise InvalidBase64("Base64-encoded networkstatus field %r is invalid!"
+ % field)
+
+ return debasedField
diff --git a/bridgedb/parse/addr.py b/bridgedb/parse/addr.py
new file mode 100644
index 0000000..cd512d1
--- /dev/null
+++ b/bridgedb/parse/addr.py
@@ -0,0 +1,589 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013 Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Utilities for parsing IP and email addresses.
+
+.. py:module:: bridgedb.parse.addr
+ :synopsis: Parsers for finding and validating IP addresses, email
+ addresses, and port ranges.
+
+
+bridgedb.parse.addr
+===================
+
+::
+
+ parse.addr
+ | |_ extractEmailAddress() - Validate a :rfc:2822 email address.
+ | |_ isIPAddress() - Check if an arbitrary string is an IP address.
+ | |_ isIPv4() - Check if an arbitrary string is an IPv4 address.
+ | |_ isIPv6() - Check if an arbitrary string is an IPv6 address.
+ | \_ isValidIP() - Check that an IP address is valid.
+ |
+ |_ PortList - A container class for validated port ranges.
+
+..
+
+
+How address validity is determined
+----------------------------------
+
+The following terms define addresses which are **not** valid. All other
+addresses are taken to be valid.
+
+
+Private IP Address Ranges
+^^^^^^^^^^^^^^^^^^^^^^^^^
+.. glossary::
+
+ Private Address
+ These address ranges are reserved by IANA for private intranets, and not
+ routable to the Internet::
+ 10.0.0.0 - 10.255.255.255 (10.0.0.0/8)
+ 172.16.0.0 - 172.31.255.255 (172.16.0.0/12)
+ 192.168.0.0 - 192.168.255.255 (192.168.0.0/16)
+ For additional information, see :rfc:`1918`.
+
+
+Reserved and Special Use Addresses
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+.. glossary::
+
+ Unspecified Address
+ Default Route
+ Current network (only valid as source address). See :rfc:`1122`. An
+ **Unspecified Address** in the context of firewalls means "all addresses
+ of the local machine". In a routing context, it is usually termed the
+ **Default Route**, and it means the default route (to "the rest of" the
+ internet). See :rfc:`1700`.
+ For example::
+ 0.0.0.0/8
+ ::/128
+
+ Loopback Address
+ Reserved for loopback and IPC on the localhost. See :rfc:`1122`.
+ Example::
+ 127.0.0.0
+
+ Localhost Address
+ Loopback IP addresses (refers to self). See :rfc:`5735`.
+ Examples include::
+ 127.0.0.1 - 127.255.255.254 (127.0.0.0/8)
+ ::1
+
+ Link-Local Address
+ These are the link-local blocks, used for communication between hosts on
+ a single link. See :rfc:`3927`.
+ Examples::
+ 169.254.0.0/16
+ fe80::/64
+
+ Multicast Address
+ Reserved for multicast addresses. See :rfc:`3171`.
+ For example::
+ 224.0.0.0 - 239.255.255.255 (224.0.0.0/4)
+
+ Private Address
+ Reserved for private networks. See :rfc:`1918`.
+ Some examples include::
+ 10.0.0.0/8
+ 172.16.0.0/12
+ 192.168.0.0/16
+
+ Reserved Address
+ Reserved (former Class E network). See :rfc:`1700`, :rfc:`3232`, and
+ :rfc:`5735`. The one exception to this rule is the :term:`Limited
+ Broadcast Address`, ``255.255.255.255`` for which packets at the IP
+ layer are not forwarded to the public internet. For example::
+ 240.0.0.0 - 255.255.255.255 (240.0.0.0/4)
+
+ Limited Broadcast Address
+ Limited broadcast address (limited to all other nodes on the LAN). See
+ :rfc:`919`. For IPv4, ``255`` in any part of the IP is reserved for
+ broadcast addressing to the local LAN, e.g.::
+ 255.255.255.255
+
+
+.. warning:: The :mod:`ipaddr` module (as of version 2.1.10) does not
+ understand the following reserved_ addresses:
+.. _reserved: https://tools.ietf.org/html/rfc5735#page-4
+
+.. glossary::
+
+ Reserved Address (Protocol Assignments)
+ Reserved for IETF protocol assignments. See :rfc:`5735`.
+ Example::
+ 192.0.0.0/24
+
+ Reserved Address (6to4 Relay Anycast)
+ IPv6 to IPv4 relay. See :rfc:`3068`.
+ Example::
+ 192.88.99.0/24
+
+ Reserved Address (Network Benchmark)
+ Network benchmark tests. See :rfc:`2544`.
+ Example::
+ 198.18.0.0/15
+
+ Reserved Address (TEST-NET-1)
+ Reserved for use in documentation and example code. It is often used in
+ conjunction with domain names ``example.com`` or ``example.net`` in
+ vendor and protocol documentation. See :rfc:`1166`.
+ For example::
+ 192.0.2.0/24
+
+ Reserved Address (TEST-NET-2)
+ TEST-NET-2. See :rfc:`5737`.
+ Example::
+ 198.51.100.0/24
+
+ Reserved Address (TEST-NET-3)
+ TEST-NET-3. See :rfc:`5737`.
+ Example::
+ 203.0.113.0/24
+
+ Shared Address Space
+ See :rfc:`6598`.
+ Example::
+ 100.64.0.0/10
+
+ Site-Local Address
+ Unique Local Address
+ Similar uses to :term:`Limited Broadcast Address`. For IPv6, everything
+ becomes convoluted_ and complicated_, and then redefined_. See
+ :rfc:`4193`, :rfc:`3879`, and :rfc:`3513`. The
+ :meth:`ipaddr.IPAddress.is_site_local` method *only* checks to see if
+ the address is a **Unique Local Address** vis-á-vis :rfc:`3513` §2.5.6,
+ e.g.::
+ ff00::0/8
+ fec0::/10
+
+..
+
+.. _convoluted: https://en.wikipedia.org/wiki/IPv6_address#Multicast_addresses
+.. _complicated: https://en.wikipedia.org/wiki/IPv6_address#IPv6_address_scopes
+.. _redefined: https://en.wikipedia.org/wiki/Unique_local_address
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+import re
+
+import ipaddr
+
+
+#: These are the special characters which RFC2822 allows within email addresses:
+#: ASPECIAL = '!#$%&*+-/=?^_`{|}~' + "\\\'"
+#: â¦But these are the only ones we're confident that we can handle correctly:
+#: ASPECIAL = '-_+/=_~'
+ASPECIAL = '-_+/=_~'
+ACHAR = r'[\w%s]' % "".join("\\%s" % c for c in ASPECIAL)
+DOTATOM = r'%s+(?:\.%s+)*' % (ACHAR, ACHAR)
+DOMAIN = r'\w+(?:\.\w+)*'
+ADDRSPEC = r'(%s)\@(%s)' % (DOTATOM, DOMAIN)
+# A compiled regexp which matches on any type and ammount of whitespace:
+SPACE_PAT = re.compile(r'\s+')
+# A compiled regexp which matches RFC2822 email address strings:
+ADDRSPEC_PAT = re.compile(ADDRSPEC)
+
+
+class BadEmail(Exception):
+ """Exception raised when we get a bad email address."""
+ def __init__(self, msg, email):
+ Exception.__init__(self, msg)
+ self.email = email
+
+class InvalidPort(ValueError):
+ """Raised when a given port number is invalid."""
+
+class UnsupportedDomain(ValueError):
+ """Raised when we get an email address from an unsupported domain."""
+
+
+def canonicalizeEmailDomain(domain, domainmap):
+ """Decide if an email was sent from a permitted domain.
+
+ :param str domain: The domain portion of an email address to validate. It
+ will be checked that it is one of the domains allowed to email
+ requests for bridges to the
+ :class:`~bridgedb.email.distributor.EmailDistributor`.
+ :param dict domainmap: A map of permitted alternate domains (in lowercase)
+ to their canonical domain names (in lowercase). This can be configured
+ with the ``EMAIL_DOMAIN_MAP`` option in ``bridgedb.conf``, for
+ example::
+ EMAIL_DOMAIN_MAP = {'mail.google.com': 'gmail.com',
+ 'googlemail.com': 'gmail.com'}
+
+ :raises UnsupportedDomain: if the domain portion of the email address is
+ not within the map of alternate to canonical allowed domain names.
+ :rtype: str
+ :returns: The canonical domain name for the email address.
+ """
+ permitted = None
+
+ try:
+ permitted = domainmap.get(domain)
+ except AttributeError:
+ logging.debug("Got non-dict for 'domainmap' parameter: %r" % domainmap)
+
+ if not permitted:
+ raise UnsupportedDomain("Domain not permitted: %s" % domain)
+
+ return permitted
+
+def extractEmailAddress(emailaddr):
+ """Given an email address, obtained for example, via a ``From:`` or
+ ``Sender:`` email header, try to extract and parse (according to
+ :rfc:`2822`) the local and domain portions.
+
+ We only allow the following form::
+
+ LOCAL_PART := DOTATOM
+ DOMAIN := DOTATOM
+ ADDRSPEC := LOCAL_PART "@" DOMAIN
+
+ In particular, we are disallowing: obs-local-part, obs-domain, comment,
+ and obs-FWS. Other forms exist, but none of the incoming services we
+ recognize support them.
+
+ :param emailaddr: An email address to validate.
+ :raises BadEmail: if the **emailaddr** couldn't be validated or parsed.
+ :returns: A tuple of the validated email address, containing the mail
+ local part and the domain::
+ (LOCAL_PART, DOMAIN)
+ """
+ orig = emailaddr
+
+ try:
+ addr = SPACE_PAT.sub(' ', emailaddr).strip()
+ except TypeError as error:
+ logging.debug(error)
+ raise BadEmail("Can't extract address from object type %r!"
+ % type(orig), orig)
+
+ # Only works on usual-form addresses; raises BadEmail on weird
+ # address form. That's okay, since we'll only get those when
+ # people are trying to fool us.
+ if '<' in addr:
+ # Take the _last_ index of <, so that we don't need to bother
+ # with quoting tricks.
+ idx = addr.rindex('<')
+ addr = addr[idx:]
+ m = re.search(r'<([^>]*)>', addr)
+ if m is None:
+ raise BadEmail("Couldn't extract address spec", orig)
+ addr = m.group(1)
+
+ # At this point, addr holds a putative addr-spec.
+ addr = addr.replace(" ", "")
+ m = ADDRSPEC_PAT.match(addr)
+ if not m:
+ raise BadEmail("Bad address spec format", orig)
+
+ localpart, domain = m.groups()
+ return localpart, domain
+
+def isIPAddress(ip, compressed=True):
+ """Check if an arbitrary string is an IP address, and that it's valid.
+
+ :type ip: basestring or int
+ :param ip: The IP address to check.
+ :param boolean compressed: If True, return a string representing the
+ compressed form of the address. Otherwise, return an
+ :class:`ipaddr.IPAddress` instance.
+ :rtype: A :class:`ipaddr.IPAddress`, or a string, or False
+ :returns: The IP, as a string or a class, if it passed the
+ checks. Otherwise, returns False.
+ """
+ try:
+ ip = ipaddr.IPAddress(ip)
+ except ValueError:
+ return False
+ else:
+ if isValidIP(ip):
+ if compressed:
+ return ip.compressed
+ else:
+ return ip
+ return False
+
+def isIPv(version, ip):
+ """Check if **ip** is a certain **version** (IPv4 or IPv6).
+
+ .. warning: Do *not* put any calls to the logging module in this function,
+ or else an infinite recursion will occur when the call is made, due
+ the the log :class:`~logging.Filter`s in :mod:`~bridgedb.safelog`
+ using this function to validate matches from the regular expression
+ for IP addresses.
+
+ :param integer version: The IPv[4|6] version to check; must be either
+ ``4`` or ``6``. Any other value will be silently changed to ``4``.
+ :param ip: The IP address to check. May be an any type which
+ :class:`ipaddr.IPAddress` will accept.
+ :rtype: boolean
+ :returns: ``True``, if the address is an IPv4 address.
+ """
+ try:
+ ipaddr.IPAddress(ip, version=version)
+ except (ipaddr.AddressValueError, Exception):
+ return False
+ else:
+ return True
+ return False
+
+def isIPv4(ip):
+ """Check if an address is IPv4.
+
+ .. attention:: This does *not* check validity. See :func:`isValidIP`.
+
+ :type ip: basestring or int
+ :param ip: The IP address to check.
+ :rtype: boolean
+ :returns: True if the address is an IPv4 address.
+ """
+ return isIPv(4, ip)
+
+def isIPv6(ip):
+ """Check if an address is IPv6.
+
+ .. attention:: This does *not* check validity. See :func:`isValidIP`.
+
+ :type ip: basestring or int
+ :param ip: The IP address to check.
+ :rtype: boolean
+ :returns: True if the address is an IPv6 address.
+ """
+ return isIPv(6, ip)
+
+def isValidIP(ip):
+ """Check that an IP (v4 or v6) is valid.
+
+ The IP address, **ip**, must not be any of the following:
+
+ * A :term:`Link-Local Address`,
+ * A :term:`Loopback Address` or :term:`Localhost Address`,
+ * A :term:`Multicast Address`,
+ * An :term:`Unspecified Address` or :term:`Default Route`,
+ * Any other :term:`Private Address`, or address within a privately
+ allocated space, such as the IANA-reserved
+ :term:`Shared Address Space`.
+
+ If it is an IPv6 address, it also must not be:
+
+ * A :term:`Site-Local Address` or an :term:`Unique Local Address`.
+
+ >>> from bridgedb.parse.addr import isValidIP
+ >>> isValidIP('1.2.3.4')
+ True
+ >>> isValidIP('1.2.3.255')
+ True
+ >>> isValidIP('1.2.3.256')
+ False
+ >>> isValidIP('1')
+ False
+ >>> isValidIP('1.2.3')
+ False
+ >>> isValidIP('xyzzy')
+ False
+
+ :type ip: An :class:`ipaddr.IPAddress`, :class:`ipaddr.IPv4Address`,
+ :class:`ipaddr.IPv6Address`, or str
+ :param ip: An IP address. If it is a string, it will be converted to a
+ :class:`ipaddr.IPAddress`.
+ :rtype: boolean
+ :returns: ``True``, if **ip** passes the checks; False otherwise.
+ """
+ reasons = []
+
+ try:
+ if isinstance(ip, basestring):
+ ip = ipaddr.IPAddress(ip)
+
+ if ip.is_link_local:
+ reasons.append('link local')
+ if ip.is_loopback:
+ reasons.append('loopback')
+ if ip.is_multicast:
+ reasons.append('multicast')
+ if ip.is_private:
+ reasons.append('private')
+ if ip.is_unspecified:
+ reasons.append('unspecified')
+
+ if (ip.version == 6) and ip.is_site_local:
+ reasons.append('site local')
+ elif (ip.version == 4) and ip.is_reserved:
+ reasons.append('reserved')
+ except ValueError:
+ reasons.append('cannot convert to ip')
+
+ if reasons:
+ explain = ', '.join([r for r in reasons])
+ logging.debug("IP address %r is invalid! Reason(s): %s"
+ % (ip, explain))
+ return False
+ return True
+
+def normalizeEmail(emailaddr, domainmap, domainrules, ignorePlus=True):
+ """Normalise an email address according to the processing rules for its
+ canonical originating domain.
+
+ The email address, **emailaddr**, will be parsed and validated, and then
+ checked that it originated from one of the domains allowed to email
+ requests for bridges to the
+ :class:`~bridgedb.email.distributor.EmailDistributor` via the
+ :func:`canonicaliseEmailDomain` function.
+
+ :param str emailaddr: An email address to normalise.
+ :param dict domainmap: A map of permitted alternate domains (in lowercase)
+ to their canonical domain names (in lowercase). This can be configured
+ with the ``EMAIL_DOMAIN_MAP`` option in ``bridgedb.conf``, for
+ example::
+
+ EMAIL_DOMAIN_MAP = {'mail.google.com': 'gmail.com',
+ 'googlemail.com': 'gmail.com'}
+
+ :param dict domainrules: A mapping of canonical permitted domain names to
+ a list of rules which should be applied to processing them, for
+ example::
+
+ EMAIL_DOMAIN_RULES = {'gmail.com': ["ignore_dots", "dkim"]
+
+ Currently, ``"ignore_dots"`` means that all ``"."`` characters will be
+ removed from the local part of the validated email address.
+
+ :param bool ignorePlus: If ``True``, assume that
+ ``blackhole+kerr at torproject.org`` is an alias for
+ ``blackhole at torproject.org``, and remove everything after the first
+ ``'+'`` character.
+
+ :raises UnsupportedDomain: if the email address originated from a domain
+ that we do not explicitly support.
+ :raises BadEmail: if the email address could not be parsed or validated.
+ :rtype: str
+ :returns: The validated, normalised email address, if it was from a
+ permitted domain. Otherwise, returns an empty string.
+ """
+ emailaddr = emailaddr.lower()
+ localpart, domain = extractEmailAddress(emailaddr)
+ canonical = canonicalizeEmailDomain(domain, domainmap)
+
+ if ignorePlus:
+ idx = localpart.find('+')
+ if idx >= 0:
+ localpart = localpart[:idx]
+
+ rules = domainrules.get(canonical, [])
+ if 'ignore_dots' in rules:
+ localpart = localpart.replace(".", "")
+
+ normalized = "%s@%s" % (localpart, domain)
+ return normalized
+
+
+class PortList(object):
+ """A container class for validated port ranges.
+
+ From torspec.git/dir-spec.txt §2.3:
+ |
+ | portspec ::= "*" | port | port "-" port
+ | port ::= an integer between 1 and 65535, inclusive.
+ |
+ | [Some implementations incorrectly generate ports with value 0.
+ | Implementations SHOULD accept this, and SHOULD NOT generate it.
+ | Connections to port 0 are never permitted.]
+ |
+
+ :ivar set ports: All ports which have been added to this ``PortList``.
+ """
+
+ #: The maximum number of allowed ports per IP address.
+ PORTSPEC_LEN = 16
+
+ def __init__(self, *args, **kwargs):
+ """Create a :class:`~bridgedb.parse.addr.PortList`.
+
+ :param args: Should match the ``portspec`` defined above.
+ :raises: InvalidPort, if one of ``args`` doesn't match ``port`` as
+ defined above.
+ """
+ self.ports = set()
+ self.add(*args)
+
+ def _sanitycheck(self, port):
+ """Check that ``port`` is in the range [1, 65535] inclusive.
+
+ :raises: InvalidPort, if ``port`` doesn't match ``port`` as defined
+ in the excert from torspec above.
+ :rtype: int
+ :returns: The **port**, if no exceptions were raised.
+ """
+ if (not isinstance(port, int)) or not (0 < port <= 65535):
+ raise InvalidPort("%s is not a valid port number!" % port)
+ return port
+
+ def __contains__(self, port):
+ """Determine whether ``port`` is already in this ``PortList``.
+
+ :returns: True if ``port`` is in this ``PortList``; False otherwise.
+ """
+ return port in self.ports
+
+ def add(self, *args):
+ """Add a port (or ports) to this ``PortList``.
+
+ :param args: Should match the ``portspec`` defined above.
+ :raises: InvalidPort, if one of ``args`` doesn't match ``port`` as
+ defined above.
+ """
+ for arg in args:
+ portlist = []
+ try:
+ if isinstance(arg, basestring):
+ ports = set([int(p)
+ for p in arg.split(',')][:self.PORTSPEC_LEN])
+ portlist.extend([self._sanitycheck(p) for p in ports])
+ if isinstance(arg, int):
+ portlist.append(self._sanitycheck(arg))
+ if isinstance(arg, PortList):
+ self.add(list(arg.ports))
+ except ValueError:
+ raise InvalidPort("%s is not a valid port number!" % arg)
+
+ self.ports.update(set(portlist))
+
+ def __iter__(self):
+ """Iterate through all ports in this PortList."""
+ return self.ports.__iter__()
+
+ def __str__(self):
+ """Returns a pretty string representation of this PortList."""
+ return ','.join(['%s' % port for port in self.ports])
+
+ def __repr__(self):
+ """Returns a raw depiction of this PortList."""
+ return "PortList('%s')" % self.__str__()
+
+ def __len__(self):
+ """Returns the total number of ports in this PortList."""
+ return len(self.ports)
+
+ def __getitem__(self, port):
+ """Get the value of ``port`` if it is in this PortList.
+
+ :raises: ValueError, if ``port`` isn't in this PortList.
+ :rtype: integer
+ :returns: The ``port``, if it is in this PortList.
+ """
+ return list(self.ports)[port]
diff --git a/bridgedb/parse/descriptors.py b/bridgedb/parse/descriptors.py
new file mode 100644
index 0000000..1b0108e
--- /dev/null
+++ b/bridgedb/parse/descriptors.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_descriptors ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Parsers for Tor Bridge descriptors, including ``bridge-networkstatus``
+documents, ``bridge-server-descriptor``s, and ``bridge-extrainfo``
+descriptors.
+
+.. py:module:: bridgedb.parse.descriptors
+ :synopsis: Parsers for Tor Bridge descriptors.
+
+bridgedb.parse.descriptors
+===========================
+::
+
+ DescriptorWarning - Raised when we parse a very odd descriptor.
+ deduplicate - Deduplicate a container of descriptors, keeping only the newest
+ descriptor for each router.
+ parseNetworkStatusFile - Parse a bridge-networkstatus document generated and
+ given to us by the BridgeAuthority.
+ parseServerDescriptorsFile - Parse a file containing
+ bridge-server-descriptors.
+ parseExtraInfoFiles - Parse (multiple) file(s) containing bridge-extrainfo
+ descriptors.
+..
+"""
+
+from __future__ import print_function
+
+import datetime
+import logging
+import os
+import shutil
+
+from stem import ProtocolError
+from stem.descriptor import parse_file
+from stem.descriptor.router_status_entry import _parse_file as _parseNSFile
+from stem.descriptor.router_status_entry import RouterStatusEntryV3
+
+from bridgedb import safelog
+from bridgedb.parse.nickname import InvalidRouterNickname
+
+
+class DescriptorWarning(Warning):
+ """Raised when we parse a very odd descriptor."""
+
+
+def _copyUnparseableDescriptorFile(filename):
+ """Save a copy of the bad descriptor file for later debugging.
+
+ If the old filename was ``'descriptors/cached-extrainfo.new'``, then the
+ name of the copy will be something like
+ ``'descriptors/2014-11-05-01:57:23_cached-extrainfo.new.unparseable'``.
+
+ :param str filename: The path to the unparseable descriptor file that we
+ should save a copy of.
+ :rtype: bool
+ :returns: ``True`` if a copy of the file was saved successfully, and
+ ``False`` otherwise.
+ """
+ timestamp = datetime.datetime.now()
+ timestamp = timestamp.isoformat(sep=chr(0x2d))
+ timestamp = timestamp.rsplit('.', 1)[0]
+
+ path, sep, fname = filename.rpartition(os.path.sep)
+ newfilename = "%s%s%s_%s%sunparseable" % (path, sep, timestamp,
+ fname, os.path.extsep)
+
+ logging.info(("Unparseable descriptor file '%s' will be copied to '%s' "
+ "for debugging.") % (filename, newfilename))
+
+ try:
+ shutil.copyfile(filename, newfilename)
+ except Exception as error: # pragma: no cover
+ logging.error(("Could not save copy of unparseable descriptor file "
+ "in '%s': %s") % (newfilename, str(error)))
+ return False
+ else:
+ logging.debug(("Successfully finished saving a copy of an unparseable "
+ "descriptor file."))
+ return True
+
+def parseNetworkStatusFile(filename, validate=True, skipAnnotations=True,
+ descriptorClass=RouterStatusEntryV3):
+ """Parse a file which contains an ``@type bridge-networkstatus`` document.
+
+ See :trac:`#12254` for why networkstatus-bridges documents don't look
+ anything like the networkstatus v2 documents that they are purported to
+ look like. They are missing all headers, and the entire footer including
+ authority signatures.
+
+ :param str filename: The location of the file containing bridge
+ networkstatus descriptors.
+ :param bool validate: Passed along to Stem's parsers. If ``True``, the
+ descriptors will raise exceptions if they do not meet some definition
+ of correctness.
+ :param bool skipAnnotations: If ``True``, skip parsing everything before the
+ first ``r`` line.
+ :param descriptorClass: A class (probably from
+ :api:`stem.descriptors.router_status_entry`) which Stem will parse
+ each descriptor it reads from **filename** into.
+ :raises InvalidRouterNickname: if one of the routers in the networkstatus
+ file had a nickname which does not conform to Tor's nickname
+ specification.
+ :raises ValueError: if the contents of a descriptor are malformed and
+ **validate** is ``True``.
+ :raises IOError: if the file at **filename** can't be read.
+ :rtype: list
+ :returns: A list of
+ :api:`stem.descriptor.router_status_entry.RouterStatusEntryV`s.
+ """
+ routers = []
+
+ logging.info("Parsing networkstatus file: %s" % filename)
+ with open(filename) as fh:
+ position = fh.tell()
+ if skipAnnotations:
+ while not fh.readline().startswith('r '):
+ position = fh.tell()
+ logging.debug("Skipping %d bytes of networkstatus file." % position)
+ fh.seek(position)
+ document = _parseNSFile(fh, validate, entry_class=descriptorClass)
+
+ try:
+ routers.extend(list(document))
+ except ValueError as error:
+ if "nickname isn't valid" in str(error):
+ raise InvalidRouterNickname(str(error))
+ else:
+ raise ValueError(str(error))
+
+ logging.info("Closed networkstatus file: %s" % filename)
+
+ return routers
+
+def parseServerDescriptorsFile(filename, validate=True):
+ """Parse a file which contains ``@type bridge-server-descriptor``s.
+
+ .. note:: ``validate`` defaults to ``False`` because there appears to be a
+ bug in Leekspin, the fake descriptor generator, where Stem thinks the
+ fingerprint doesn't match the keyâ¦
+
+ .. note:: We have to lie to Stem, pretending that these are
+ ``@type server-descriptor``s, **not**
+ ``@type bridge-server-descriptor``s. See ticket #`11257`_.
+
+ .. _`11257`: https://bugs.torproject.org/11257
+
+ :param str filename: The file to parse descriptors from.
+ :param bool validate: Whether or not to validate descriptor
+ contents. (default: ``False``)
+ :rtype: list
+ :returns: A list of
+ :api:`stem.descriptor.server_descriptor.RelayDescriptor`s.
+ """
+ logging.info("Parsing server descriptors with Stem: %s" % filename)
+ descriptorType = 'server-descriptor 1.0'
+ document = parse_file(filename, descriptorType, validate=validate)
+ routers = list(document)
+ return routers
+
+def __cmp_published__(x, y):
+ """A custom ``cmp()`` which sorts descriptors by published date.
+
+ :rtype: int
+ :returns: Return negative if x<y, zero if x==y, positive if x>y.
+ """
+ if x.published < y.published:
+ return -1
+ elif x.published == y.published:
+ # This *shouldn't* happen. It would mean that two descriptors for
+ # the same router had the same timestamps, probably meaning there
+ # is a severely-messed up OR implementation out there. Let's log
+ # its fingerprint (no matter what!) so that we can look up its
+ # ``platform`` line in its server-descriptor and tell whoever
+ # wrote that code that they're probably (D)DOSing the Tor network.
+ logging.warn(("Duplicate descriptor with identical timestamp (%s) "
+ "for bridge %s with fingerprint %s !") %
+ (x.published, x.nickname, x.fingerprint))
+ return 0
+ elif x.published > y.published:
+ return 1
+
+def deduplicate(descriptors, statistics=False):
+ """Deduplicate some descriptors, returning only the newest for each router.
+
+ .. note:: If two descriptors for the same router are discovered, AND both
+ descriptors have the **same** published timestamp, then the router's
+ fingerprint WILL BE LOGGED ON PURPOSE, because we assume that router
+ to be broken or malicious.
+
+ :param list descriptors: A list of
+ :api:`stem.descriptor.server_descriptor.RelayDescriptor`s,
+ :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`s,
+ or :api:`stem.descriptor.router_status_entry.RouterStatusEntryV2`s.
+ :param bool statistics: If ``True``, log some extra statistics about the
+ number of duplicates.
+ :rtype: dict
+ :returns: A dictionary mapping router fingerprints to their newest
+ available descriptor.
+ """
+ duplicates = {}
+ newest = {}
+
+ for descriptor in descriptors:
+ fingerprint = descriptor.fingerprint
+ if fingerprint in duplicates:
+ duplicates[fingerprint].append(descriptor)
+ else:
+ duplicates[fingerprint] = [descriptor,]
+
+ for fingerprint, dupes in duplicates.items():
+ dupes.sort(cmp=__cmp_published__)
+ first = dupes.pop()
+ newest[fingerprint] = first
+ duplicates[fingerprint] = dupes
+
+ if statistics:
+ # sorted() won't sort by values (or anything that isn't the first item
+ # in its container), period, no matter what the cmp function is.
+ totals = sorted([(len(v), k,) for k, v in duplicates.viewitems()])
+ total = sum([k for (k, v) in totals])
+ bridges = len(duplicates)
+ top = 10 if bridges >= 10 else bridges
+ logging.info("Number of bridges with duplicates: %5d" % bridges)
+ logging.info("Total duplicate descriptors: %5d" % total)
+ logging.info("Bridges with the most duplicates (Top %d):" % top)
+ for i, (subtotal, bridge) in zip(range(1, top + 1), totals[:top]):
+ logging.info(" #%d %s: %d duplicates" % (i, bridge, subtotal))
+
+ logging.info("Descriptor deduplication finished.")
+
+ return newest
+
+def parseExtraInfoFiles(*filenames, **kwargs):
+ """Parse files which contain ``@type bridge-extrainfo-descriptor``s.
+
+ .. warning:: This function will *not* check that the ``router-signature``
+ at the end of the extrainfo descriptor is valid. See
+ ``bridgedb.bridges.Bridge._verifyExtraInfoSignature`` for a method for
+ checking the signature.
+
+ .. note:: This function will call :func:`deduplicate` to deduplicate the
+ extrainfo descriptors parsed from all **filenames**.
+
+ :kwargs validate: If there is a ``'validate'`` keyword argument, its value
+ will be passed along as the ``'validate'`` argument to
+ :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`.
+ The ``'validate'`` keyword argument defaults to ``True``, meaning that
+ the hash digest stored in the ``router-digest`` line will be checked
+ against the actual contents of the descriptor and the extrainfo
+ document's signature will be verified.
+ :rtype: dict
+ :returns: A dictionary mapping bridge fingerprints to deduplicated
+ :api:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`s.
+ """
+ descriptors = []
+
+ # The ``stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor``
+ # class (with ``descriptorType = 'bridge-extra-info 1.1``) is unsuitable
+ # for our purposes for the following reasons:
+ #
+ # 1. It expects a ``router-digest`` line, which is only present in
+ # sanitised bridge extrainfo descriptors.
+ #
+ # 2. It doesn't check the ``router-signature`` (nor does it expect there
+ # to be a signature).
+ descriptorType = 'extra-info 1.0'
+
+ validate = True
+ if ('validate' in kwargs) and (kwargs['validate'] is False):
+ validate = False
+
+ for filename in filenames:
+ logging.info("Parsing %s descriptors in %s..."
+ % (descriptorType, filename))
+
+ document = parse_file(filename, descriptorType, validate=validate)
+
+ try:
+ for router in document:
+ descriptors.append(router)
+ except (ValueError, ProtocolError) as error:
+ logging.error(
+ ("Stem exception while parsing extrainfo descriptor from "
+ "file '%s':\n%s") % (filename, str(error)))
+ _copyUnparseableDescriptorFile(filename)
+
+ routers = deduplicate(descriptors)
+ return routers
diff --git a/bridgedb/parse/fingerprint.py b/bridgedb/parse/fingerprint.py
new file mode 100644
index 0000000..bf12ee5
--- /dev/null
+++ b/bridgedb/parse/fingerprint.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_fingerprint ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Utility functions for converting between various relay fingerprint formats,
+and checking their validity.
+
+.. py:module:: bridgedb.parse.fingerprints
+ :synopsis: Parsers for Tor Bridge fingerprints.
+
+.. todo: This module is very small; it could possibly be combined with another
+ module, e.g. :mod:`bridgedb.parse.descriptors`.
+
+bridgedb.parse.fingerprints
+============================
+::
+
+ toHex - Convert a fingerprint from its binary representation to hexadecimal.
+ fromHex - Convert a fingerprint from hexadecimal to binary.
+ isValidFingerprint - Validate a fingerprint.
+..
+"""
+
+import binascii
+import logging
+
+
+#: The required length for hexidecimal representations of hash digest of a
+#: Tor relay's public identity key (a.k.a. its fingerprint).
+HEX_FINGERPRINT_LEN = 40
+
+
+#: (callable) Convert a value from binary to hexidecimal representation.
+toHex = binascii.b2a_hex
+
+#: (callable) Convert a value from hexidecimal to binary representation.
+fromHex = binascii.a2b_hex
+
+def isValidFingerprint(fingerprint):
+ """Determine if a Tor relay fingerprint is valid.
+
+ :param str fingerprint: The hex-encoded hash digest of the relay's
+ public identity key, a.k.a. its fingerprint.
+ :rtype: bool
+ :returns: ``True`` if the **fingerprint** was valid, ``False`` otherwise.
+ """
+ try:
+ if len(fingerprint) != HEX_FINGERPRINT_LEN:
+ raise ValueError("Fingerprint has incorrect length: %r"
+ % repr(fingerprint))
+ fromHex(fingerprint)
+ except (TypeError, ValueError):
+ logging.debug("Invalid hex fingerprint: %r" % repr(fingerprint))
+ else:
+ return True
+ return False
diff --git a/bridgedb/parse/headers.py b/bridgedb/parse/headers.py
new file mode 100644
index 0000000..5c3597a
--- /dev/null
+++ b/bridgedb/parse/headers.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013 Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Parsers for HTTP and Email headers.
+
+.. py:module:: bridgedb.parse.headers
+ :synopsis: Parsers for HTTP and Email headers.
+
+bridgedb.parse.headers
+=======================
+::
+
+ parseAcceptLanguage - Parse the contents of a client 'Accept-Language' header
+..
+"""
+
+def parseAcceptLanguage(header):
+ """Parse the contents of a client 'Accept-Language' header.
+
+ Parse the header in the following manner:
+
+ 1. If ``header`` is None or an empty string, return an empty list.
+ 2. Split the ``header`` string on any commas.
+ 3. Chop of the RFC2616 quality/level suffix. We ignore these, and just
+ use the order of the list as the preference order, without any
+ parsing of quality/level assignments.
+ 4. Add a fallback language of the same type if it is missing. For
+ example, if we only got ['es-ES', 'de-DE'], add 'es' after 'es-ES'
+ and add 'de' after 'de-DE'.
+ 5. Change all hyphens to underscores.
+
+ :param string header: The contents of an 'Accept-Language' header, i.e. as
+ if taken from :api:`twisted.web.server.Request.getHeader`.
+ :rtype: list
+ :returns: A list of language codes (with and without locales), in order of
+ preference.
+ """
+ langs = []
+
+ if not header:
+ return langs
+
+ langHeader = header.split(',')
+
+ for lang in langHeader:
+ if lang.find(';') != -1:
+ # Chop off the RFC2616 Accept `q=` and `level=` feilds
+ code, _ = lang.split(';')
+ langs.append(code)
+ else:
+ langs.append(lang)
+
+ # Add a fallback language of the same type if it is missing.
+ langsWithLocales = filter(lambda x: '-' in x, langs)
+ langsOnly = map(lambda x: x.split('-')[0], langsWithLocales)
+ for only in langsOnly:
+ if only not in langs:
+ # Add the fallback after the other languages like it:
+ insertAfter = filter(lambda x: x.startswith(only),
+ [x for x in langs])
+ if insertAfter:
+ placement = langs.index(insertAfter[0]) + 1
+ langs.insert(placement, only)
+ continue
+ # Otherwise just put it at the end
+ langs.append(only)
+
+ # Gettext wants underderscores, because that is how it creates the
+ # directories under i18n/, not hyphens:
+ langs = map(lambda x: x.replace('-', '_'), [x for x in langs])
+ return langs
diff --git a/bridgedb/parse/nickname.py b/bridgedb/parse/nickname.py
new file mode 100644
index 0000000..35cf626
--- /dev/null
+++ b/bridgedb/parse/nickname.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Parsers for bridge nicknames.
+
+.. py:module:: bridgedb.parse.nickname
+ :synopsis: Parsers for Tor bridge nicknames.
+
+bridgedb.parse.nicknames
+========================
+::
+
+ nicknames
+ |_ isValidRouterNickname - Determine if a nickname is according to spec
+..
+"""
+
+import logging
+import string
+
+
+class InvalidRouterNickname(ValueError):
+ """Router nickname doesn't follow tor-spec."""
+
+
+def isValidRouterNickname(nickname):
+ """Determine if a router's given nickname meets the specification.
+
+ :param string nickname: An OR's nickname.
+ :rtype: bool
+ :returns: ``True`` if the nickname is valid, ``False`` otherwise.
+ """
+ ALPHANUMERIC = string.letters + string.digits
+
+ try:
+ if not (1 <= len(nickname) <= 19):
+ raise InvalidRouterNickname(
+ "Nicknames must be between 1 and 19 characters: %r" % nickname)
+ for letter in nickname:
+ if not letter in ALPHANUMERIC:
+ raise InvalidRouterNickname(
+ "Nicknames must only use [A-Za-z0-9]: %r" % nickname)
+ except InvalidRouterNickname as error:
+ logging.error(str(error))
+ except TypeError: # The nickname was probably still set to ``None``
+ pass
+ else:
+ return True
+
+ return False
diff --git a/bridgedb/parse/options.py b/bridgedb/parse/options.py
new file mode 100644
index 0000000..6f3bfc1
--- /dev/null
+++ b/bridgedb/parse/options.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Parsers for BridgeDB commandline options.
+
+.. py:module:: bridgedb.parse.options
+ :synopsis: Parsers for BridgeDB commandline options.
+
+
+bridgedb.parse.options
+======================
+::
+
+ bridgedb.parse.options
+ |__ setConfig()
+ |__ getConfig() - Set/Get the config file path.
+ |__ setRundir()
+ |__ getRundir() - Set/Get the runtime directory.
+ |__ parseOptions() - Create the main options parser for BridgeDB.
+ |
+ \_ BaseOptions - Base options, included in all other options menus.
+ ||
+ |\__ findRundirAndConfigFile() - Find the absolute path of the config
+ | file and runtime directory, or find
+ | suitable defaults.
+ |
+ |__ SIGHUPOptions - Menu to explain SIGHUP signal handling and usage.
+ |__ SIGUSR1Options - Menu to explain SIGUSR1 handling and usage.
+ |
+ |__ MockOptions - Suboptions for creating fake bridge descriptors for
+ | testing purposes.
+ \__ MainOptions - Main commandline options parser for BridgeDB.
+..
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import sys
+import textwrap
+import traceback
+import os
+
+from twisted.python import usage
+
+from bridgedb import __version__
+
+
+#: In :meth:`BaseOptions.findRundirAndConfig`, this is set to the the
+#: absolute path of the ``opts['rundir']`` setting, if given, otherwise it
+#: defaults to the current directory.
+_rundir = None
+
+#: In :meth:`BaseOptions.findRundirAndConfig`, if ``opts['config']`` is
+#: given, this is set to the the absolute path of the ``opts['config']``
+#: settting relative to the ``rundir``, otherwise it defaults to
+#: 'bridgedb.conf' in the current directory.
+_config = None
+
+
+def setConfig(path):
+ """Set the absolute path to the config file.
+
+ See :meth:`BaseOptions.postOptions`.
+
+ :param string path: The path to set.
+ """
+ global _config
+ _config = path
+
+def getConfig():
+ """Get the absolute path to the config file.
+
+ :rtype: string
+ :returns: The path to the config file.
+ """
+ return _config
+
+def setRundir(path):
+ """Set the absolute path to the runtime directory.
+
+ See :meth:`BaseOptions.postOptions`.
+
+ :param string path: The path to set.
+ """
+ global _rundir
+ _rundir = path
+
+def getRundir():
+ """Get the absolute path to the runtime directory.
+
+ :rtype: string
+ :returns: The path to the config file.
+ """
+ return _rundir
+
+def parseOptions():
+ """Create the main options parser and its subcommand parsers.
+
+ Any :exc:`~twisted.python.usage.UsageErrors` which are raised due to
+ invalid options are ignored; their error message is printed and then we
+ exit the program.
+
+ :rtype: :class:`MainOptions`
+ :returns: The main options parsing class, with any commandline arguments
+ already parsed.
+ """
+ options = MainOptions()
+
+ try:
+ options.parseOptions()
+ except usage.UsageError as uerr:
+ print(uerr.message)
+ print(options.getUsage())
+ sys.exit(1)
+ except Exception as error: # pragma: no cover
+ exc, value, tb = sys.exc_info()
+ print("Unhandled Error: %s" % error.message)
+ print(traceback.format_exc(tb))
+
+ return options
+
+
+class BaseOptions(usage.Options):
+ """Base options included in all main and sub options menus."""
+
+ longdesc = textwrap.dedent("""BridgeDB is a proxy distribution system for
+ private relays acting as bridges into the Tor network. See `bridgedb
+ <command> --help` for addition help.""")
+
+ optParameters = [
+ ['config', 'c', None,
+ 'Configuration file [default: <rundir>/bridgedb.conf]'],
+ ['rundir', 'r', None,
+ """Change to this directory before running. [default: `os.getcwd()']
+
+ All other paths, if not absolute, should be relative to this path.
+ This includes the config file and any further files specified within
+ the config file.
+ """]]
+
+ def __init__(self):
+ """Create an options parser. All flags, parameters, and attributes of
+ this base options parser are inherited by all child classes.
+ """
+ super(BaseOptions, self).__init__()
+ self['version'] = self.opt_version
+ self['verbosity'] = 30
+
+ def opt_quiet(self):
+ """Decrease verbosity"""
+ # We use '10' because then it corresponds to the log levels
+ self['verbosity'] -= 10
+
+ def opt_verbose(self):
+ """Increase verbosity"""
+ self['verbosity'] += 10
+
+ opt_q = opt_quiet
+ opt_v = opt_verbose
+
+ def opt_version(self):
+ """Display BridgeDB's version and exit."""
+ print("%s-%s" % (__package__, __version__))
+ sys.exit(0)
+
+ @staticmethod
+ def findRundirAndConfigFile(rundir=None, config=None):
+ """Find the absolute path of the config file and runtime directory, or
+ find suitable defaults.
+
+ Attempts to set the absolute path of the runtime directory. If the
+ config path is relative, its absolute path is set relative to the
+ runtime directory path (unless it starts with '.' or '..', then it is
+ interpreted relative to the current working directory). If the path to
+ the config file is absolute, it is left alone.
+
+ :type rundir: string or None
+ :param rundir: The user-supplied path to the runtime directory, from
+ the commandline options (i.e.
+ ``options = BaseOptions().parseOptions(); options['rundir'];``).
+ :type config: string or None
+ :param config: The user-supplied path to the config file, from the
+ commandline options (i.e.
+ ``options = BaseOptions().parseOptions(); options['config'];``).
+ :raises: :api:`twisted.python.usage.UsageError` if either the runtime
+ directory or the config file cannot be found.
+ """
+ gRundir = getRundir()
+ gConfig = getConfig()
+
+ if gRundir is None:
+ if rundir is not None:
+ gRundir = os.path.abspath(os.path.expanduser(rundir))
+ else:
+ gRundir = os.getcwdu()
+ setRundir(gRundir)
+
+ if not os.path.isdir(gRundir): # pragma: no cover
+ raise usage.UsageError(
+ "Could not change to runtime directory: `%s'" % gRundir)
+
+ if gConfig is None:
+ if config is None:
+ config = 'bridgedb.conf'
+ gConfig = config
+
+ if not os.path.isabs(gConfig):
+ # startswith('.') will handle other relative paths, i.e. '..'
+ if gConfig.startswith('.'): # pragma: no cover
+ gConfig = os.path.abspath(os.path.expanduser(gConfig))
+ else:
+ gConfig = os.path.join(gRundir, gConfig)
+ setConfig(gConfig)
+
+ gConfig = getConfig()
+ if not os.path.isfile(gConfig): # pragma: no cover
+ raise usage.UsageError(
+ "Specified config file `%s' doesn't exist!" % gConfig)
+
+ def postOptions(self):
+ """Automatically called by :meth:`parseOptions`.
+
+ Determines appropriate values for the 'config' and 'rundir' settings.
+ """
+ super(BaseOptions, self).postOptions()
+ self.findRundirAndConfigFile(self['rundir'], self['config'])
+
+ gConfig = getConfig()
+ gRundir = getRundir()
+
+ if (self['rundir'] is None) and (gRundir is not None):
+ self['rundir'] = gRundir
+
+ if (self['config'] is None) and (gConfig is not None):
+ self['config'] = gConfig
+
+ if self['verbosity'] <= 10:
+ print("%s.postOptions():" % self.__class__)
+ print(" gCONFIG=%s" % gConfig)
+ print(" self['config']=%s" % self['config'])
+ print(" gRUNDIR=%s" % gRundir)
+ print(" self['rundir']=%s" % self['rundir'])
+
+
+class MockOptions(BaseOptions):
+ """Suboptions for creating necessary conditions for testing purposes."""
+
+ optParameters = [
+ ['descriptors', 'n', 1000,
+ '''Generate <n> mock bridge descriptor sets
+ (types: netstatus, extrainfo, server)''']]
+
+
+class SIGHUPOptions(BaseOptions):
+ """Options menu to explain usage and handling of SIGHUP signals."""
+
+ longdesc = """If you send a SIGHUP to a running BridgeDB process, the
+ servers will parse and reload all bridge descriptor files into the
+ databases.
+
+ Note that this command WILL NOT handle sending the signal for you; see
+ signal(7) and kill(1) for additional help."""
+
+
+class SIGUSR1Options(BaseOptions):
+ """Options menu to explain usage and handling of SIGUSR1 signals."""
+
+ longdesc = """If you send a SIGUSR1 to a running BridgeDB process, the
+ servers will dump all bridge assignments by distributor from the
+ databases to files.
+
+ Note that this command WILL NOT handle sending the signal for you; see
+ signal(7) and kill(1) for additional help."""
+
+
+class MainOptions(BaseOptions):
+ """Main commandline options parser for BridgeDB."""
+
+ optFlags = [
+ ['dump-bridges', 'd', 'Dump bridges by hashring assignment into files'],
+ ['reload', 'R', 'Reload bridge descriptors into running servers']]
+ subCommands = [
+ ['mock', None, MockOptions, "Generate a testing environment"],
+ ['SIGHUP', None, SIGHUPOptions,
+ "Reload bridge descriptors into running servers"],
+ ['SIGUSR1', None, SIGUSR1Options,
+ "Dump bridges by hashring assignment into files"]]
diff --git a/bridgedb/parse/versions.py b/bridgedb/parse/versions.py
new file mode 100644
index 0000000..ee62792
--- /dev/null
+++ b/bridgedb/parse/versions.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_versions ; -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2014-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: see included LICENSE for information
+
+"""Parsers for Tor version number strings.
+
+.. py:module:: bridgedb.parse.versions
+ :synopsis: Parsers for Tor version number strings.
+
+bridgedb.parse.versions
+=======================
+::
+
+ Version - Holds, parses, and does comparison operations for package
+ version numbers.
+..
+"""
+
+from twisted import version as _txversion
+
+# The twisted.python.util.Version class was moved in Twisted==14.0.0 to
+# twisted.python.versions.Version:
+if _txversion.major >= 14:
+ from twisted.python.versions import Version as _Version
+else:
+ from twisted.python.util import Version as _Version
+
+
+class InvalidVersionStringFormat(ValueError):
+ """Raised when a version string is not in a parseable format."""
+
+
+class Version(_Version):
+ """Holds, parses, and does comparison operations for version numbers.
+
+ :attr str package: The package name, if available.
+ :attr int major: The major version number.
+ :attr int minor: The minor version number.
+ :attr int micro: The micro version number.
+ :attr str prerelease: The **prerelease** specifier isn't always present,
+ though when it is, it's usually separated from the main
+ ``major.minor.micro`` part of the version string with a ``-``, ``+``,
+ or ``#`` character. Sometimes the **prerelease** is another number,
+ although often it can be a word specifying the release state,
+ i.e. ``+alpha``, ``-rc2``, etc.
+ """
+
+ def __init__(self, version, package=None):
+ """Create a version object.
+
+ Comparisons may be computed between instances of :class:`Version`s.
+
+ >>> from bridgedb.parse.versions import Version
+ >>> v1 = Version("0.2.3.25", package="tor")
+ >>> v1.base()
+ '0.2.3.25'
+ >>> v1.package
+ 'tor'
+ >>> v2 = Version("0.2.5.1-alpha", package="tor")
+ >>> v2
+ Version(package=tor, major=0, minor=2, micro=5, prerelease=1-alpha)
+ >>> v1 == v2
+ False
+ >>> v2 > v1
+ True
+
+ :param str version: A Tor version string specifier, i.e. one taken
+ from either the ``client-versions`` or ``server-versions`` lines
+ within a Tor ``cached-consensus`` file.
+ :param str package: The package or program which we are creating a
+ version number for.
+ """
+ if version.find('.') == -1:
+ raise InvalidVersionStringFormat(
+ "Invalid delimiters in version string: %r" % version)
+
+ package = package if package is not None else str()
+ major, minor, micro = [int() for _ in range(3)]
+ prerelease = str()
+ components = version.split('.')
+ if len(components) > 0:
+ try:
+ prerelease = str(components.pop())
+ micro = int(components.pop())
+ minor = int(components.pop())
+ major = int(components.pop())
+ except IndexError:
+ pass
+ super(Version, self).__init__(package, major, minor, micro, prerelease)
+
+ def base(self):
+ """Get the base version number (with prerelease).
+
+ :rtype: string
+ :returns: A version number, without the package/program name, and with
+ the prefix (if available). For example: '0.2.5.1-alpha'.
+ """
+ pre = self.getPrefixedPrerelease()
+ return '%s.%s.%s%s' % (self.major, self.minor, self.micro, pre)
+
+ def getPrefixedPrerelease(self, separator='.'):
+ """Get the prerelease string, prefixed by the separator ``prefix``.
+
+ :param string separator: The separator to use between the rest of the
+ version string and the :attr:`prerelease` string.
+ :rtype: string
+ :returns: The separator plus the ``prefix``, i.e. '.1-alpha'.
+ """
+ pre = ''
+ if self.prerelease is not None:
+ pre = separator + self.prerelease
+ return pre
+
+ def __repr__(self):
+ prerelease = self.getPrefixedPrerelease('')
+ return '%s(package=%s, major=%s, minor=%s, micro=%s, prerelease=%s)' \
+ % (str(self.__class__.__name__),
+ str(self.package),
+ str(self.major),
+ str(self.minor),
+ str(self.micro),
+ str(prerelease))
diff --git a/bridgedb/persistent.py b/bridgedb/persistent.py
new file mode 100644
index 0000000..22673dd
--- /dev/null
+++ b/bridgedb/persistent.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_persistent -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Module for functionality to persistently store state."""
+
+import copy
+import logging
+import os.path
+
+try:
+ import cPickle as pickle
+except (ImportError, NameError): # pragma: no cover
+ import pickle
+
+from twisted.python.reflect import safe_repr
+from twisted.spread import jelly
+
+from bridgedb import Bridges
+from bridgedb import filters
+from bridgedb.email import distributor as emailDistributor
+from bridgedb.https import distributor as httpsDistributor
+from bridgedb.configure import Conf
+#from bridgedb.proxy import ProxySet
+
+_state = None
+
+#: Types and classes which are allowed to be jellied:
+_security = jelly.SecurityOptions()
+#_security.allowInstancesOf(ProxySet)
+_security.allowModules(filters, Bridges, emailDistributor, httpsDistributor)
+
+
+class MissingState(Exception):
+ """Raised when the file or class storing global state is missing."""
+
+
+def _getState():
+ """Retrieve the global state instance.
+
+ :rtype: :class:`~bridgedb.persistent.State`
+ :returns: An unpickled de-sexp'ed state object, which may contain just
+ about anything, but should contain things like options, loaded config
+ settings, etc.
+ """
+ return _state
+
+def _setState(state):
+ """Set the global state.
+
+ :type state: :class:`~bridgedb.persistent.State`
+ :param state: The state instance to save.
+ """
+ global _state
+ _state = state
+
+def load(stateCls=None):
+ """Given a :class:`State`, try to unpickle it's ``statefile``.
+
+ :param string stateCls: An instance of :class:`~bridgedb.persistent.State`. If
+ not given, try loading from ``_statefile`` if that file exists.
+ :rtype: None or :class:`State`
+ """
+ statefile = None
+ if stateCls and isinstance(stateCls, State):
+ cls = stateCls
+ else:
+ cls = _getState()
+
+ if not cls:
+ raise MissingState("Could not find a state instance to load.")
+ else:
+ loaded = cls.load()
+ return loaded
+
+
+class State(jelly.Jellyable):
+ """Pickled, jellied storage container for persistent state."""
+
+ def __init__(self, config=None, **kwargs):
+ """Create a persistent state storage mechanism.
+
+ Serialisation of certain classes in BridgeDB doesn't work. Classes and
+ modules which are known to be unjelliable/unpicklable so far are:
+
+ - bridgedb.Dist
+ - bridgedb.Bridges, and all "splitter" and "ring" classes contained
+ within
+
+ :property statefile: The filename to retrieve a pickled, jellied
+ :class:`~bridgedb.persistent.State` instance from. (default:
+ :attr:`bridgedb.persistent.State._statefile`)
+ """
+ self._statefile = os.path.abspath(str(__package__) + '.state')
+ self.proxyList = None
+ self.config = None
+ self.key = None
+
+ if 'STATEFILE' in kwargs:
+ self.statefile = kwargs['STATEFILE']
+
+ for key, value in kwargs.items():
+ self.__dict__[key] = value
+
+ if config is not None:
+ for key, value in config.__dict__.items():
+ self.__dict__[key] = value
+
+ _setState(self)
+
+ def _get_statefile(self):
+ """Retrieve the filename of the global statefile.
+
+ :rtype: string
+ :returns: The filename of the statefile.
+ """
+ return self._statefile
+
+ def _set_statefile(self, filename):
+ """Set the global statefile.
+
+ :param string statefile: The filename of the statefile.
+ """
+ filename = os.path.abspath(os.path.expanduser(filename))
+ logging.debug("Setting statefile to '%s'" % filename)
+ self._statefile = filename
+
+ # Create the parent directory if it doesn't exist:
+ dirname = os.path.dirname(filename)
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+
+ # Create the statefile if it doesn't exist:
+ if not os.path.exists(filename):
+ open(filename, 'w').close()
+
+ def _del_statefile(self):
+ """Delete the file containing previously saved state."""
+ try:
+ with open(self._statefile, 'w') as fh:
+ fh.close()
+ os.unlink(self._statefile)
+ self._statefile = None
+ except (IOError, OSError) as error: # pragma: no cover
+ logging.error("There was an error deleting the statefile: '%s'"
+ % self._statefile)
+
+ statefile = property(_get_statefile, _set_statefile, _del_statefile,
+ """Filename property of a persisent.State.""")
+
+ def load(self, statefile=None):
+ """Load a previously saved statefile.
+
+ :raises MissingState: If there was any error loading the **statefile**.
+ :rtype: :class:`State` or None
+ :returns: The state, loaded from :attr:`State.STATEFILE`, or None if
+ an error occurred.
+ """
+ if not statefile:
+ if not self.statefile:
+ raise MissingState("Could not find a state file to load.")
+ statefile = self.statefile
+ logging.debug("Retrieving state from: \t'%s'" % statefile)
+
+ quo= fh = None
+ err = ''
+
+ try:
+ if isinstance(statefile, basestring):
+ fh = open(statefile, 'r')
+ elif not statefile.closed:
+ fh = statefile
+ except (IOError, OSError) as error: # pragma: no cover
+ err += "There was an error reading statefile "
+ err += "'{0}':\n{1}".format(statefile, error)
+ except (AttributeError, TypeError) as error:
+ err += "Failed statefile.open() and statefile.closed:"
+ err += "\n\t{0}\nstatefile type = '{1}'".format(
+ error.message, type(statefile))
+ else:
+ try:
+ status = pickle.load(fh)
+ except EOFError:
+ err += "The statefile %s was empty." % fh.name
+ else:
+ quo = jelly.unjelly(status)
+ if fh is not None:
+ fh.close()
+ if quo:
+ return quo
+
+ if err:
+ raise MissingState(err)
+
+ def save(self, statefile=None):
+ """Save state as a pickled jelly to a file on disk."""
+ if not statefile:
+ if not self._statefile:
+ raise MissingState("Could not find a state file to load.")
+ statefile = self._statefile
+ logging.debug("Saving state to: \t'%s'" % statefile)
+
+ fh = None
+ try:
+ fh = open(statefile, 'w')
+ except (IOError, OSError) as error: # pragma: no cover
+ logging.warn("Error writing state file to '%s': %s"
+ % (statefile, error))
+ else:
+ try:
+ pickle.dump(jelly.jelly(self), fh)
+ except AttributeError as error:
+ logging.debug("Tried jellying an unjelliable object: %s"
+ % error.message)
+
+ if fh is not None:
+ fh.flush()
+ fh.close()
+
+ def useChangedSettings(self, config):
+ """Take a new config, compare it to the last one, and update settings.
+
+ Given a ``config`` object created from the configuration file, compare
+ it to the last :class:`~bridgedb.configure.Conf` that was stored, and apply
+ any settings which were changed to be attributes of the :class:`State`
+ instance.
+ """
+ updated = []
+ new = []
+
+ for key, value in config.__dict__.items():
+ try:
+ # If state.config doesn't have the same value as the new
+ # config, then update the state setting.
+ #
+ # Be sure, when updating settings while parsing the config
+ # file, to assign the new settings as attributes of the
+ # :class:`bridgedb.configure.Conf` instance.
+ if value != self.config.__dict__[key]:
+ setattr(self, key, value)
+ updated.append(key)
+ logging.debug("Updated %s setting: %r â %r" %
+ (safe_repr(key),
+ self.config.__dict__[key],
+ safe_repr(value)))
+ except (KeyError, AttributeError):
+ setattr(self, key, value)
+ new.append(key)
+ logging.debug("New setting: %s = %r" %
+ (safe_repr(key),
+ safe_repr(value)))
+
+ logging.info("Updated setting(s): %s" % ' '.join([x for x in updated]))
+ logging.info("New setting(s): %s" % ' '.join([x for x in new]))
+ logging.debug(
+ "Saving newer config as `state.config` for later comparison")
+ self.config = config
diff --git a/bridgedb/proxy.py b/bridgedb/proxy.py
new file mode 100644
index 0000000..086f3bd
--- /dev/null
+++ b/bridgedb/proxy.py
@@ -0,0 +1,473 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Classes for finding and managing lists of open proxies."""
+
+from __future__ import print_function
+from collections import MutableSet
+from functools import update_wrapper
+from functools import wraps
+
+import ipaddr
+import logging
+import os
+import time
+
+from twisted.internet import defer
+from twisted.internet import protocol
+from twisted.internet import reactor
+from twisted.internet import utils as txutils
+from bridgedb.runner import find
+from bridgedb.parse.addr import isIPAddress
+
+
+def downloadTorExits(proxyList, ipaddress, port=443, protocol=None):
+ """Run a script which downloads a list of Tor exit relays which allow their
+ clients to exit to the given **ipaddress** and **port**.
+
+ :param proxyList: The :class:`ProxySet` instance from :mod:`bridgedb.Main`.
+ :param str ipaddress: The IP address that each Tor exit relay should be
+ capable of connecting to for clients, as specified by its ExitPolicy.
+ :param int port: The port corresponding to the above **ipaddress** that
+ each Tor exit relay should allow clients to exit to. (See
+ https://check.torproject.org/cgi-bin/TorBulkExitList.py.)
+ :type protocol: :api:`twisted.internet.protocol.Protocol`
+ :param protocol: A :class:`~bridgedb.proxy.ExitListProtocol`, or any other
+ :api:`~twisted.internet.protocol.Protocol` implementation for
+ processing the results of a process which downloads a list of Tor exit
+ relays. This parameter is mainly meant for use in testing, and should
+ not be changed.
+ :rtype: :class:`~twisted.internet.defer.Deferred`
+ :returns: A deferred which will callback with a list, each item in the
+ list is a string containing an IP of a Tor exit relay.
+ """
+ proto = ExitListProtocol() if protocol is None else protocol()
+ args = [proto.script, '--stdout', '-a', ipaddress, '-p', str(port)]
+ proto.deferred.addCallback(proxyList.addExitRelays)
+ proto.deferred.addErrback(logging.exception)
+ transport = reactor.spawnProcess(proto, proto.script, args=args, env={})
+ return proto.deferred
+
+def loadProxiesFromFile(filename, proxySet=None, removeStale=False):
+ """Load proxy IP addresses from a list of files.
+
+ :param str filename: A filename whose path can be either absolute or
+ relative to the current working directory. The file should contain the
+ IP addresses of various open proxies, one per line, for example::
+
+ 11.11.11.11
+ 22.22.22.22
+ 123.45.67.89
+
+ :type proxySet: None or :class:`~bridgedb.proxy.ProxySet`.
+ :param proxySet: If given, load the addresses read from the files into
+ this ``ProxySet``.
+ :param bool removeStale: If ``True``, remove proxies from the **proxySet**
+ which were not listed in any of the **files**.
+ (default: ``False``)
+ :returns: A list of all the proxies listed in the **files* (regardless of
+ whether they were added or removed).
+ """
+ logging.info("Reloading proxy lists...")
+
+ addresses = []
+
+ # We have to check the instance because, if the ProxySet was newly
+ # created, it will likely be empty, causing it to evaluate to False:
+ if isinstance(proxySet, ProxySet):
+ oldProxySet = proxySet.copy()
+
+ try:
+ with open(filename, 'r') as proxyFile:
+ for line in proxyFile.readlines():
+ line = line.strip()
+ if isinstance(proxySet, ProxySet):
+ # ProxySet.add() will validate the IP address
+ if proxySet.add(line, tag=filename):
+ addresses.append(line)
+ else:
+ ip = isIPAddress(line)
+ if ip:
+ addresses.append(ip)
+ except Exception as error:
+ logging.warn("Error while reading a proxy list file: %s" % str(error))
+
+ if isinstance(proxySet, ProxySet):
+ stale = list(oldProxySet.difference(addresses))
+
+ if removeStale:
+ for ip in stale:
+ if proxySet.getTag(ip) == filename:
+ logging.info("Removing stale IP %s from proxy list." % ip)
+ proxySet.remove(ip)
+ else:
+ logging.info("Tag %s didn't match %s"
+ % (proxySet.getTag(ip), filename))
+
+ return addresses
+
+
+class ProxySet(MutableSet):
+ """A :class:`collections.MutableSet` for storing validated IP addresses."""
+
+ #: A tag to apply to IP addresses within this ``ProxySet`` which are known
+ #: Tor exit relays.
+ _exitTag = 'exit_relay'
+
+ def __init__(self, proxies=dict()):
+ """Initialise a ``ProxySet``.
+
+ :type proxies: A tuple, list, dict, or set.
+ :param proxies: Optionally, initialise with an iterable, ``proxies``.
+ For each ``item`` in that iterable, ``item`` must either:
+ 1. be a string or int representing an IP address, or,
+ 2. be another iterable, whose first item satisfies #1.
+ """
+ super(ProxySet, self).__init__()
+ self._proxydict = dict()
+ self._proxies = set()
+ self.addProxies(proxies)
+
+ @property
+ def proxies(self):
+ """All proxies in this set, regardless of tags."""
+ return list(self._proxies)
+
+ @property
+ def exitRelays(self):
+ """Get all proxies in this ``ProxySet`` tagged as Tor exit relays.
+
+ :rtype: set
+ :returns: A set of all known Tor exit relays which are contained
+ within this :class:`~bridgedb.proxy.ProxySet`.
+ """
+ return self.getAllWithTag(self._exitTag)
+
+ def __add__(self, ip=None, tag=None):
+ """Add an **ip** to this set, with an optional **tag**.
+
+ This has no effect if the **ip** is already present. The **ip** is
+ only added if it passes the checks in
+ :func:`~bridgedb.parse.addr.isIPAddress`.
+
+ :type ip: basestring or int
+ :param ip: The IP address to add.
+ :param tag: An optional value to link to **ip**. If not given, it will
+ be a timestamp (seconds since epoch, as a float) for when **ip**
+ was first added to this set.
+ :rtype: bool
+ :returns: ``True`` if **ip** is in this set; ``False`` otherwise.
+ """
+ ip = isIPAddress(ip)
+ if ip:
+ if self._proxies.isdisjoint(set(ip)):
+ logging.debug("Adding %s to proxy list..." % ip)
+ self._proxies.add(ip)
+ self._proxydict[ip] = tag if tag else time.time()
+ return True
+ return False
+
+ def __radd__(self, *args, **kwargs): self.__add__(*args, **kwargs)
+
+ def __contains__(self, ip):
+ """x.__contains__(y) <==> y in x.
+
+ :type ip: basestring or int
+ :param ip: The IP address to check.
+ :rtype: boolean
+ :returns: True if ``ip`` is in this set; False otherwise.
+ """
+ ipset = [isIPAddress(ip),]
+ if ipset and len(self._proxies.intersection(ipset)) == len(ipset):
+ return True
+ return False
+
+ def __sub__(self, ip):
+ """Entirely remove **ip** from this set.
+
+ :type ip: basestring or int
+ :param ip: The IP address to remove.
+ """
+ try:
+ self._proxydict.pop(ip)
+ self._proxies.discard(ip)
+ except KeyError:
+ pass
+
+ def __rsub__(self, *args, **kwargs): raise NotImplemented
+
+ def _getErrorMessage(self, x=None, y=None):
+ """Make an error message describing how this class works."""
+ message = """\nParameter 'proxies' must be one of:
+ - a {1} of {0}
+ - a {2} of {0}
+ - a {3}, whose keys are {0} (the values can be anything)
+ - a {4} of {1}s, whose first object in each {1} must be a {0}
+ - a {4} of {0}
+ """.format(type(''), type(()), type([]), type({}), type(set(())))
+ end = "You gave: a {0}".format(type(y))
+ end += " of {0}".format(type(x))
+ return os.linesep.join((message, end))
+
+ def addProxies(self, proxies, tag=None):
+ """Add proxies to this set.
+
+ This calls :func:`add` for each item in the iterable **proxies**.
+ Each proxy, if added, will be tagged with a current timestamp.
+
+ :type proxies: A tuple, list, dict, or set.
+ :param proxies: An iterable. For each ``item`` in that iterable,
+ ``item`` must either:
+ 1. be a string or int representing an IP address, or,
+ 2. be another iterable, whose first item satisfies #1.
+ :keyword tag: An optional value to link to all untagged
+ **proxies**. If ``None``, it will be a timestamp (seconds since
+ epoch, as a float) for when the proxy was first added to this set.
+ """
+ if isinstance(proxies, dict):
+ [self.add(ip, value) for (ip, value) in proxies.items()]
+ else:
+ try:
+ for x in proxies:
+ if isinstance(x, (tuple, list, set)):
+ if len(x) == 2: self.add(x[0], x[1])
+ elif len(x) == 1: self.add(x, tag)
+ else: raise ValueError(self._getErrorMessage(x, proxies))
+ elif isinstance(x, (basestring, int)):
+ self.add(x, tag)
+ else:
+ raise ValueError(self._getErrorMessage(x, proxies))
+ except TypeError:
+ raise ValueError(self._getErrorMessage(proxies, None))
+
+ @wraps(addProxies)
+ def addExitRelays(self, relays):
+ logging.info("Loading exit relays into proxy list...")
+ [self.add(x, self._exitTag) for x in relays]
+
+ def getTag(self, ip):
+ """Get the tag for an **ip** in this ``ProxySet``, if available.
+
+ :type ip: basestring or int
+ :param ip: The IP address to obtain the tag for.
+ :rtype: ``None`` or basestring or int
+ :returns: The tag for that **ip**, iff **ip** exists in this
+ ``ProxySet`` and it has a tag.
+ """
+ return self._proxydict.get(ip)
+
+ def getAllWithTag(self, tag):
+ """Get all proxies in this ``ProxySet`` with a given tag.
+
+ :param basestring tag: A tag to search for.
+ :rtype: set
+ :returns: A set of all proxies which are contained within this
+ :class:`~bridgedb.proxy.ProxySet` which are also tagged with
+ **tag**.
+ """
+ return set([key for key, value in filter(lambda x: x[1] == tag,
+ self._proxydict.items())])
+
+ def firstSeen(self, ip):
+ """Get the timestamp when **ip** was first seen, if available.
+
+ :type ip: basestring or int
+ :param ip: The IP address to obtain a timestamp for.
+ :rtype: float or None
+ :returns: The timestamp (in seconds since epoch) if available.
+ Otherwise, returns None.
+ """
+ when = self.getTag(ip)
+ if isinstance(when, float):
+ return when
+
+ def isExitRelay(self, ip):
+ """Check if ``ip`` is a known Tor exit relay.
+
+ :type ip: basestring or int
+ :param ip: The IP address to check.
+ :rtype: boolean
+ :returns: True if ``ip`` is a known Tor exit relay; False otherwise.
+ """
+ if self.getTag(ip) == self._exitTag:
+ return True
+ return False
+
+ def replaceProxyList(self, proxies, tag=None):
+ """Clear everything and add all ``proxies``.
+
+ :type proxies: A tuple, list, dict, or set.
+ :param proxies: An iterable. For each ``item`` in that iterable,
+ ``item`` must either:
+ 1. be a string or int representing an IP address, or,
+ 2. be another iterable, whose first item satisfies #1.
+ """
+ try:
+ self.clear()
+ self.addProxies(proxies, tag=tag)
+ except Exception as error:
+ logging.error(str(error))
+
+ _assigned = ('__name__', '__doc__')
+
+ @wraps(MutableSet._hash)
+ def __hash__(self): return self._hash()
+ def __iter__(self): return self._proxies.__iter__()
+ def __len__(self): return len(self._proxydict.items())
+ def __repr__(self): return type('')(self.proxies)
+ def __str__(self): return os.linesep.join(self.proxies)
+ update_wrapper(__iter__, set.__iter__, _assigned)
+ update_wrapper(__len__, len, _assigned)
+ update_wrapper(__repr__, repr, _assigned)
+ update_wrapper(__str__, str, _assigned)
+
+ def add(self, ip, tag=None): return self.__add__(ip, tag)
+ def copy(self): return self.__class__(self._proxydict.copy())
+ def contains(self, ip): return self.__contains__(ip)
+ def discard(self, ip): return self.__sub__(ip)
+ def remove(self, other): return self.__sub__(other)
+ update_wrapper(add, __add__)
+ update_wrapper(copy, __init__)
+ update_wrapper(contains, __contains__)
+ update_wrapper(discard, __sub__)
+ update_wrapper(remove, __sub__)
+
+ def difference(self, other): return self._proxies.difference(other)
+ def issubset(self, other): return self._proxies.issubset(other)
+ def issuperset(self, other): return self._proxies.issuperset(other)
+ def intersection(self, other): return self._proxies.intersection(other)
+ def symmetric_difference(self, other): return self._proxies.symmetric_difference(other)
+ def union(self, other): return self._proxies.union(other)
+ update_wrapper(difference, set.difference, _assigned)
+ update_wrapper(issubset, set.issubset, _assigned)
+ update_wrapper(issuperset, set.issuperset, _assigned)
+ update_wrapper(intersection, set.intersection, _assigned)
+ update_wrapper(symmetric_difference, set.symmetric_difference, _assigned)
+ update_wrapper(union, set.union, _assigned)
+
+
+class ExitListProtocol(protocol.ProcessProtocol):
+ """A :class:`~twisted.internet.protocol.Protocol` for ``get-exit-list``.
+
+ :attr boolean connected: True if our ``transport`` is connected.
+
+ :type transport: An implementer of
+ :interface:`twisted.internet.interface.IProcessTransport`.
+ :attr transport: If :func:`twisted.internet.reactor.spawnProcess` is
+ called with an instance of this class as it's ``protocol``, then
+ :func:`~twisted.internet.reactor.spawnProcess` will return this
+ ``transport``.
+ """
+
+ def __init__(self):
+ """Create a protocol for downloading a list of current Tor exit relays.
+
+ :type exitlist: :class:`ProxySet`
+ :ivar exitlist: A :class:`~collections.MutableSet` containing the IP
+ addresses of known Tor exit relays which can reach our public IP
+ address.
+ :ivar list data: A list containing a ``bytes`` object for each chuck
+ of data received from the ``transport``.
+ :ivar deferred: A deferred which will callback with the ``exitlist``
+ when the process has ended.
+
+ :param string script: The full pathname of the script to run.
+ """
+ self.data = []
+ self.script = find('get-tor-exits')
+ self.exitlist = ProxySet()
+ self.deferred = defer.Deferred()
+
+ def childConnectionLost(self, childFD):
+ """See :func:`t.i.protocol.ProcessProtocol.childConnectionLost`."""
+ protocol.ProcessProtocol.childConnectionLost(self, childFD)
+
+ def connectionMade(self):
+ """Called when a connection is made.
+
+ This may be considered the initializer of the protocol, because it is
+ called when the connection is completed. For clients, this is called
+ once the connection to the server has been established; for servers,
+ this is called after an accept() call stops blocking and a socket has
+ been received. If you need to send any greeting or initial message,
+ do it here.
+ """
+ logging.debug("ExitListProtocol: Connection made with remote server")
+ self.transport.closeStdin()
+
+ def errReceived(self, data):
+ """Some data was received from stderr."""
+ # The get-exit-list script uses twisted.python.log to log to stderr:
+ for line in data.splitlines(): # pragma: no cover
+ logging.debug(line)
+
+ def outReceived(self, data):
+ """Some data was received from stdout."""
+ self.data.append(data)
+
+ def outConnectionLost(self):
+ """This will be called when stdout is closed."""
+ logging.debug("Finished downloading list of Tor exit relays.")
+ self.transport.loseConnection()
+ self.parseData()
+
+ def parseData(self):
+ """Parse all data received so far into our
+ :class:`<bridgedb.proxy.ProxySet> exitlist`.
+ """
+ unparseable = []
+
+ data = ''.join(self.data).split('\n')
+
+ for line in data:
+ line.strip()
+ if not line: continue
+ # If it reached an errorpage, then we grabbed raw HTML that starts
+ # with an HTML tag:
+ if line.startswith('<'): break
+ if line.startswith('#'): continue
+ ip = isIPAddress(line)
+ if ip:
+ logging.info("Discovered Tor exit relay: %s" % ip)
+ self.exitlist.add(ip)
+ else:
+ logging.debug("Got exitlist line that wasn't an IP: %s" % line)
+ unparseable.append(line)
+
+ if unparseable:
+ logging.warn(("There were unparseable lines in the downloaded "
+ "list of Tor exit relays: %r") % unparseable)
+
+ def processEnded(self, reason):
+ """Called when the child process exits and all file descriptors
+ associated with it have been closed.
+
+ :type reason: :class:`twisted.python.failure.Failure`
+ """
+ self.transport.loseConnection()
+ if reason.value.exitCode != 0: # pragma: no cover
+ logging.debug(reason.getTraceback())
+ logging.error("There was an error downloading Tor exit list: %s"
+ % reason.value)
+ else:
+ logging.info("Finished processing list of Tor exit relays.")
+ logging.debug("Transferring exit list to storage...")
+ # Avoid triggering the deferred twice, e.g. on processExited():
+ if not self.deferred.called:
+ self.deferred.callback(list(self.exitlist.proxies))
+
+ def processExited(self, reason):
+ """This will be called when the subprocess exits.
+
+ :type reason: :class:`twisted.python.failure.Failure`
+ """
+ logging.debug("%s exited with status code %d"
+ % (self.script, reason.value.exitCode))
diff --git a/bridgedb/qrcodes.py b/bridgedb/qrcodes.py
new file mode 100644
index 0000000..e09a705
--- /dev/null
+++ b/bridgedb/qrcodes.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_qrcodes ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Utilities for working with QRCodes."""
+
+
+import cStringIO
+import logging
+
+try:
+ import qrcode
+except ImportError: # pragma: no cover
+ qrcode = False
+ logging.warn("Could not import Python qrcode module.")
+ logging.debug(("You'll need the qrcode Python module for this to "
+ "work. On Debian-based systems, this should be in the "
+ "python-qrcode package."))
+
+
+def generateQR(bridgelines, imageFormat=u'JPEG', bridgeSchema=False):
+ """Generate a QRCode for the client's bridge lines.
+
+ :param str bridgelines: The Bridge Lines which we are distributing to the
+ client.
+ :param bool bridgeSchema: If ``True``, prepend ``'bridge://'`` to the
+ beginning of each bridge line before QR encoding.
+ :rtype: str or ``None``
+ :returns: The generated QRCode, as a string.
+ """
+ logging.debug("Attempting to encode bridge lines into a QRCode...")
+
+ if not bridgelines:
+ return
+
+ if not qrcode:
+ logging.info("Not creating QRCode for bridgelines; no qrcode module.")
+ return
+
+ try:
+ if bridgeSchema:
+ # See https://bugs.torproject.org/12639 for why bridge:// is used.
+ # (Hopefully, Orbot will pick up the ACTION_VIEW intent.)
+ schema = 'bridge://'
+ prefixed = []
+ for line in bridgelines.strip().split('\n'):
+ prefixed.append(schema + line)
+ bridgelines = '\n'.join(prefixed)
+
+ logging.debug("QR encoding bridge lines: %s" % bridgelines)
+
+ qr = qrcode.QRCode()
+ qr.add_data(bridgelines)
+
+ buf = cStringIO.StringIO()
+ img = qr.make_image().resize([350, 350])
+ img.save(buf, imageFormat)
+ buf.seek(0)
+
+ imgstr = buf.read()
+ return imgstr
+
+ except KeyError as error:
+ logging.error(str(error))
+ logging.debug(("It seems python-imaging doesn't understand how to "
+ "save in the %s format.") % imageFormat)
+ except Exception as error: # pragma: no cover
+ logging.error(("There was an error while attempting to generate the "
+ "QRCode: %s") % str(error))
diff --git a/bridgedb/runner.py b/bridgedb/runner.py
new file mode 100644
index 0000000..6ac069f
--- /dev/null
+++ b/bridgedb/runner.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_runner -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# (c) 2012-2015, Isis Lovecruft
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Classes for running components and servers, as well as daemonisation.
+
+** Module Overview: **
+
+"""
+
+from __future__ import print_function
+
+import logging
+import sys
+import os
+
+from twisted.python import procutils
+
+
+def find(filename):
+ """Find the executable ``filename``.
+
+ :param string filename: The executable to search for. Must be in the
+ effective user ID's $PATH.
+ :rtype: string
+ :returns: The location of the executable, if found. Otherwise, returns
+ None.
+ """
+ executable = None
+
+ logging.debug("Searching for installed '%s'..." % filename)
+ which = procutils.which(filename, os.X_OK)
+
+ if len(which) > 0:
+ for that in which:
+ if os.stat(that).st_uid == os.geteuid():
+ executable = that
+ break
+ if not executable:
+ return None
+
+ logging.debug("Found installed script at '%s'" % executable)
+ return executable
+
+def generateDescriptors(count=None, rundir=None):
+ """Run a script which creates fake bridge descriptors for testing purposes.
+
+ This will run Leekspin_ to create bridge server descriptors, bridge
+ extra-info descriptors, and networkstatus document.
+
+ .. warning: This function can take a very long time to run, especially in
+ headless environments where entropy sources are minimal, because it
+ creates the keys for each mocked OR, which are embedded in the server
+ descriptors, used to calculate the OR fingerprints, and sign the
+ descriptors, among other things.
+
+ .. _Leekspin: https://gitweb.torproject.org/user/isis/leekspin.git
+
+ :param integer count: Number of mocked bridges to generate descriptor
+ for. (default: 3)
+ :type rundir: string or None
+ :param rundir: If given, use this directory as the current working
+ directory for the bridge descriptor generator script to run in. The
+ directory MUST already exist, and the descriptor files will be created
+ in it. If None, use the whatever directory we are currently in.
+ """
+ import subprocess
+ import os.path
+
+ proc = None
+ statuscode = 0
+ script = 'leekspin'
+ rundir = rundir if os.path.isdir(rundir) else None
+ count = count if count else 3
+ try:
+ proc = subprocess.Popen([script, '-n', str(count)],
+ close_fds=True, cwd=rundir)
+ finally:
+ if proc is not None:
+ proc.wait()
+ if proc.returncode:
+ print("There was an error generating bridge descriptors.",
+ "(Returncode: %d)" % proc.returncode)
+ statuscode = proc.returncode
+ else:
+ print("Sucessfully generated %s descriptors." % str(count))
+ del subprocess
+ return statuscode
+
+def doDumpBridges(config):
+ """Dump bridges by assignment to a file.
+
+ This function handles the commandline '--dump-bridges' option.
+
+ :type config: :class:`bridgedb.Main.Conf`
+ :param config: The current configuration.
+ """
+ import bridgedb.Bucket as bucket
+
+ bucketManager = bucket.BucketManager(config)
+ bucketManager.assignBridgesToBuckets()
+ bucketManager.dumpBridges()
diff --git a/bridgedb/safelog.py b/bridgedb/safelog.py
new file mode 100644
index 0000000..8130be6
--- /dev/null
+++ b/bridgedb/safelog.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_safelog -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Filters for log sanitisation.
+
+The ``Safelog*Filter`` classes within this module can be instantiated and
+adding to any :class:`logging.Handler`, in order to transparently filter
+substrings within log messages which match the given ``pattern``. Matching
+substrings may be optionally additionally validated by implementing the
+:meth:`~BaseSafelogFilter.doubleCheck` method before they are finally replaced
+with the ``replacement`` string. For example::
+
+ >>> import io
+ >>> import logging
+ >>> from bridgedb import safelog
+ >>> handler = logging.StreamHandler(io.BytesIO())
+ >>> logger = logging.getLogger()
+ >>> logger.addHandler(handler)
+ >>> logger.addFilter(safelog.SafelogEmailFilter())
+ >>> logger.info("Sent response email to: blackhole at torproject.org")
+
+..
+
+Module Overview:
+~~~~~~~~~~~~~~~~
+::
+ safelog
+ |
+ |_setSafeLogging - Enable or disable safelogging globally.
+ |_logSafely - Utility for manually sanitising a portion of a log message
+ |
+ |_BaseSafelogFilter - Base class for log message sanitisation filters
+ | |_doubleCheck - Optional stricter validation on matching substrings
+ | |_filter - Determine if some part of a log message should be filtered
+ |
+ |_SafelogEmailFilter - Filter for removing email addresses from logs
+ |_SafelogIPv6Filter - Filter for removing IPv4 addresses from logs
+ |_SafelogIPv6Filter - Filter for removing IPv6 addresses from logs
+::
+"""
+
+import functools
+import logging
+import re
+
+from bridgedb.parse import addr
+
+
+safe_logging = True
+
+
+def setSafeLogging(safe):
+ """Enable or disable automatic filtering of log messages.
+
+ :param bool safe: If ``True``, filter email and IP addresses from log
+ messages automagically.
+ """
+ global safe_logging
+ safe_logging = safe
+
+def logSafely(string):
+ """Utility for manually sanitising a portion of a log message.
+
+ :param str string: If ``SAFELOGGING`` is enabled, sanitise this **string**
+ by replacing it with ``"[scrubbed]"``. Otherwise, return the
+ **string** unchanged.
+ :rtype: str
+ :returns: ``"[scrubbed]"`` or the original string.
+ """
+ if safe_logging:
+ return "[scrubbed]"
+ return string
+
+
+class BaseSafelogFilter(logging.Filter):
+ """Base class for creating log message sanitisation filters.
+
+ A :class:`BaseSafelogFilter` uses a compiled regex :cvar:`pattern` to
+ match particular items of data in log messages which should be sanitised
+ (if ``SAFELOGGING`` is enabled in :file:`bridgedb.conf`).
+
+ .. note:: The ``pattern`` is used only for string *matching* purposes, and
+ *not* for validation. In other words, a ``pattern`` which matches email
+ addresses should simply match something which appears to be an email
+ address, even though that matching string might not technically be a
+ valid email address vis-á-vis :rfc:`5321`.
+
+ In addition, a ``BaseSafelogFilter`` uses a :cvar:`easyFind`, which is
+ simply a string or character to search for before running checking against
+ the regular expression, to attempt to avoid regexing *everything* which
+ passes through the logger.
+
+ :cvar pattern: A compiled regular expression, whose matches will be
+ scrubbed from log messages and replaced with :cvar:`replacement`.
+ :cvar easyFind: A simpler string to search for before regex matching.
+ :cvar replacement: The string to replace ``pattern`` matches
+ with. (default: ``"[scrubbed]"``)
+ """
+ pattern = re.compile("FILTERME")
+ easyFind = "FILTERME"
+ replacement = "[scrubbed]"
+
+ def doubleCheck(self, match):
+ """Subclasses should override this function to implement any additional
+ substring filtering to decrease the false positive rate, i.e. any
+ additional filtering or validation which is *more* costly than
+ checking against the regular expression, :cvar:`pattern`.
+
+ To use only the :cvar:`pattern` matching in :meth:`filter`, and not
+ use this method, simply do::
+
+ return True
+
+ :param str match: Some portion of the :ivar:`logging.LogRecord.msg`
+ string which has already passed the checks in :meth:`filter`, for
+ which additional validation/checking is required.
+ :rtype: bool
+ :returns: ``True`` if the additional validation passes (in other
+ words, the **match** *should* be filtered), and ``None`` or
+ ``False`` otherwise.
+ """
+ return True
+
+ def filter(self, record):
+ """Filter a log record.
+
+ The log **record** is filtered, and thus sanitised by replacing
+ matching substrings with the :cvar:`replacement` string, if the
+ following checks pass:
+
+ 0. ``SAFELOGGING`` is currently enabled.
+ 1. The ``record.msg`` string contains :cvar:`easyFind`.
+ 2. The ``record.msg`` matches the regular expression,
+ :cvar:`pattern`.
+
+ :type record: :class:`logging.LogRecord`
+ :param record: Basically, anything passed to :func:`logging.log`.
+ """
+ if safe_logging:
+ msg = str(record.msg)
+ if msg.find(self.easyFind) > 0:
+ matches = self.pattern.findall(msg)
+ for match in matches:
+ if self.doubleCheck(match):
+ msg = msg.replace(match, self.replacement)
+ record.msg = msg
+ return record
+
+
+class SafelogEmailFilter(BaseSafelogFilter):
+ """A log filter which removes email addresses from log messages."""
+
+ pattern = re.compile(
+ "([a-zA-Z0-9]+[.+a-zA-Z0-9]*[@]{1}[a-zA-Z0-9]+[.-a-zA-Z0-9]*[.]{1}[a-zA-Z]+)")
+ easyFind = "@"
+
+ @functools.wraps(BaseSafelogFilter.filter)
+ def filter(self, record):
+ return BaseSafelogFilter.filter(self, record)
+
+
+class SafelogIPv4Filter(BaseSafelogFilter):
+ """A log filter which removes IPv4 addresses from log messages."""
+
+ pattern = re.compile("(?:\d{1,3}\.?){4}")
+ easyFind = "."
+
+ def doubleCheck(self, match):
+ """Additional check to ensure that **match** is an IPv4 address."""
+ if addr.isIPv4(match):
+ return True
+
+ @functools.wraps(BaseSafelogFilter.filter)
+ def filter(self, record):
+ return BaseSafelogFilter.filter(self, record)
+
+
+class SafelogIPv6Filter(BaseSafelogFilter):
+ """A log filter which removes IPv6 addresses from log messages."""
+
+ pattern = re.compile("([:]?[a-fA-F0-9:]+[:]+[a-fA-F0-9:]+){1,8}")
+ easyFind = ":"
+
+ def doubleCheck(self, match):
+ """Additional check to ensure that **match** is an IPv6 address."""
+ if addr.isIPv6(match):
+ return True
+
+ @functools.wraps(BaseSafelogFilter.filter)
+ def filter(self, record):
+ return BaseSafelogFilter.filter(self, record)
diff --git a/bridgedb/schedule.py b/bridgedb/schedule.py
new file mode 100644
index 0000000..0adbff8
--- /dev/null
+++ b/bridgedb/schedule.py
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_schedule -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson
+# Isis Lovecruft <isis at torproject.org> 0xa3adb67a2cdb8b35
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+
+"""This module implements functions for dividing time into chunks."""
+
+import calendar
+
+import math
+
+from datetime import datetime
+
+from zope import interface
+from zope.interface import implements
+from zope.interface import Attribute
+
+
+#: The known time intervals (or *periods*) for dividing time by.
+KNOWN_INTERVALS = ["second", "minute", "hour", "day", "week", "month"]
+
+
+class UnknownInterval(ValueError):
+ """Raised if an interval isn't one of the :data:`KNOWN_INTERVALS`."""
+
+
+def toUnixSeconds(timestruct):
+ """Convert a datetime struct to a Unix timestamp in seconds.
+
+ :param timestruct: A ``datetime.datetime`` object to convert into a
+ timestamp in Unix Era seconds.
+ :rtype: int
+ """
+ return calendar.timegm(timestruct)
+
+def fromUnixSeconds(timestamp):
+ """Convert a Unix timestamp to a datetime struct.
+
+ :param int timestamp: A timestamp in Unix Era seconds.
+ :rtype: :type:`datetime.datetime`
+ """
+ return datetime.fromtimestamp(timestamp)
+
+
+class ISchedule(interface.Interface):
+ """A ``Interface`` specification for a Schedule."""
+
+ intervalPeriod = Attribute(
+ "The type of period which this Schedule's intervals will rotate by.")
+ intervalCount = Attribute(
+ "Number of **intervalPeriod**s before rotation to the next interval")
+
+ def intervalStart(when=None):
+ """Get the start time of the interval that contains **when**."""
+
+ def getInterval(when=None):
+ """Get the interval which includes an arbitrary **when**."""
+
+ def nextIntervalStarts(when=None):
+ """Get the start of the interval after the one containing **when**."""
+
+
+class Unscheduled(object):
+ """A base ``Schedule`` that has only one period that contains all time.
+
+ >>> from bridgedb.schedule import fromUnixSeconds
+ >>> from bridgedb.schedule import Unscheduled
+ >>> timestamp = 1427769526
+ >>> str(fromUnixSeconds(timestamp))
+ '2015-03-31 02:38:46'
+ >>> sched = Unscheduled()
+ >>> start = sched.intervalStart(timestamp)
+ >>> start
+ -62135596800
+ >>> str(fromUnixSeconds(start))
+ '0001-01-01 00:00:00'
+ >>> sched.getInterval(timestamp)
+ '1970-01-01 00:00:00'
+ >>> next = sched.nextIntervalStarts(timestamp)
+ >>> next
+ 253402300799
+ >>> str(fromUnixSeconds(next))
+ '9999-12-31 23:59:59'
+
+ """
+ implements(ISchedule)
+
+ def __init__(self, count=None, period=None):
+ """Create a schedule for dividing time into intervals.
+
+ :param int count: The number of **period**s in an interval.
+ :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
+ """
+ self.intervalCount = count
+ self.intervalPeriod = period
+
+ def intervalStart(self, when=0):
+ """Get the start time of the interval that contains **when**.
+
+ :param int when: The time which we're trying to find the corresponding
+ interval for.
+ :rtype: int
+ :returns: The Unix epoch timestamp for the start time of the interval
+ that contains **when**.
+ """
+ return toUnixSeconds(datetime.min.timetuple())
+
+ def getInterval(self, when=0):
+ """Get the interval that contains the time **when**.
+
+ .. note: We explicitly ignore the ``when`` parameter in this
+ implementation because if something is Unscheduled then
+ all timestamps should reside within the same period.
+
+ :param int when: The time which we're trying to find the corresponding
+ interval for.
+ :rtype: str
+ :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
+ specificity depends on what type of interval we're using. For
+ example, if using ``"month"``, the return value would be something
+ like ``"2013-12"``.
+ """
+ return fromUnixSeconds(0).strftime('%04Y-%02m-%02d %02H:%02M:%02S')
+
+ def nextIntervalStarts(self, when=0):
+ """Return the start time of the interval starting _after_ when.
+
+ :rtype: int
+ :returns: Return the Y10K bug.
+ """
+ return toUnixSeconds(datetime.max.timetuple())
+
+
+class ScheduledInterval(Unscheduled):
+ """An class that splits time into periods, based on seconds, minutes,
+ hours, days, weeks, or months.
+
+ >>> from bridgedb.schedule import fromUnixSeconds
+ >>> from bridgedb.schedule import ScheduledInterval
+ >>> timestamp = 1427769526
+ >>> str(fromUnixSeconds(timestamp))
+ '2015-03-31 02:38:46'
+ >>> sched = ScheduledInterval(5, 'minutes')
+ >>> start = sched.intervalStart(timestamp)
+ >>> start
+ 1427769300
+ >>> current = sched.getInterval(timestamp)
+ >>> current
+ '2015-03-31 02:35:00'
+ >>> current == str(fromUnixSeconds(start))
+ True
+ >>> next = sched.nextIntervalStarts(timestamp)
+ >>> next
+ 1427769600
+ >>> str(fromUnixSeconds(next))
+ '2015-03-31 02:40:00'
+ >>> later = 1427771057
+ >>> str(fromUnixSeconds(later))
+ '2015-03-31 03:04:17'
+ >>> sched.getInterval(later)
+ '2015-03-31 03:00:00'
+
+ :ivar str intervalPeriod: One of the :data:`KNOWN_INTERVALS`.
+ :ivar int intervalCount: The number of times **intervalPeriod** should be
+ repeated within an interval.
+ """
+ implements(ISchedule)
+
+ def __init__(self, count=None, period=None):
+ """Create a schedule for dividing time into intervals.
+
+ :type count: int or str
+ :param count: The number of **period**s in an interval.
+ :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
+ """
+ super(ScheduledInterval, self).__init__(count, period)
+ self._setIntervalCount(count)
+ self._setIntervalPeriod(period)
+
+ def _setIntervalCount(self, count=None):
+ """Set our :ivar:`intervalCount`.
+
+ .. attention:: This method should be called _before_
+ :meth:`_setIntervalPeriod`, because the latter may change the
+ count, if it decides to change the period (for example, to
+ simplify things by changing weeks into days).
+
+ :param int count: The number of times the :ivar:`intervalPeriod`
+ should be repeated during the interval. Defaults to ``1``.
+ :raises UnknownInterval: if the specified **count** was invalid.
+ """
+ try:
+ if not count > 0:
+ count = 1
+ count = int(count)
+ except (TypeError, ValueError):
+ raise UnknownInterval("%s.intervalCount: %r ist not an integer."
+ % (self.__class__.__name__, count))
+ self.intervalCount = count
+
+ def _setIntervalPeriod(self, period=None):
+ """Set our :ivar:`intervalPeriod`.
+
+ :param str period: One of the :data:`KNOWN_INTERVALS`, or its
+ plural. Defaults to ``'hour'``.
+ :raises UnknownInterval: if the specified **period** is unknown.
+ """
+ if not period:
+ period = 'hour'
+ try:
+ period = period.lower()
+ # Depluralise the period if necessary, i.e., "months" -> "month".
+ if period.endswith('s'):
+ period = period[:-1]
+
+ if not period in KNOWN_INTERVALS:
+ raise ValueError
+ except (TypeError, AttributeError, ValueError):
+ raise UnknownInterval("%s doesn't know about the %r interval type."
+ % (self.__class__.__name__, period))
+ self.intervalPeriod = period
+
+ if period == 'week':
+ self.intervalPeriod = 'day'
+ self.intervalCount *= 7
+
+ def intervalStart(self, when=0):
+ """Get the start time of the interval that contains **when**.
+
+ :param int when: The time which we're trying to determine the start of
+ interval that contains it. This should be given in Unix seconds,
+ for example, taken from :func:`calendar.timegm`.
+ :rtype: int
+ :returns: The Unix epoch timestamp for the start time of the interval
+ that contains **when**.
+ """
+ # Convert `when`s which are floats, i.e. from time.time(), to ints:
+ when = int(math.ceil(when))
+
+ if self.intervalPeriod == 'month':
+ # For months, we always start at the beginning of the month.
+ date = fromUnixSeconds(when)
+ months = (date.year * 12) + (date.month - 1)
+ months -= (months % self.intervalCount)
+ month = months % 12 + 1
+ return toUnixSeconds((months // 12, month, 1, 0, 0, 0))
+ elif self.intervalPeriod == 'day':
+ # For days, we start at the beginning of a day.
+ when -= when % (86400 * self.intervalCount)
+ return when
+ elif self.intervalPeriod == 'hour':
+ # For hours, we start at the beginning of an hour.
+ when -= when % (3600 * self.intervalCount)
+ return when
+ elif self.intervalPeriod == 'minute':
+ when -= when % (60 * self.intervalCount)
+ return when
+ elif self.intervalPeriod == 'second':
+ when -= when % self.intervalCount
+ return when
+
+ def getInterval(self, when=0):
+ """Get the interval that contains the time **when**.
+
+ >>> import calendar
+ >>> from bridgedb.schedule import ScheduledInterval
+ >>> sched = ScheduledInterval(1, 'month')
+ >>> when = calendar.timegm((2007, 12, 12, 0, 0, 0))
+ >>> sched.getInterval(when)
+ '2007-12'
+ >>> then = calendar.timegm((2014, 05, 13, 20, 25, 13))
+ >>> sched.getInterval(then)
+ '2014-05'
+
+ :param int when: The time which we're trying to find the corresponding
+ interval for. Given in Unix seconds, for example, taken from
+ :func:`calendar.timegm`.
+ :rtype: str
+ :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
+ specificity depends on what type of interval we're using. For
+ example, if using ``"month"``, the return value would be something
+ like ``"2013-12"``.
+ """
+ date = fromUnixSeconds(self.intervalStart(when))
+
+ fstr = "%04Y-%02m"
+ if self.intervalPeriod != 'month':
+ fstr += "-%02d"
+ if self.intervalPeriod != 'day':
+ fstr += " %02H"
+ if self.intervalPeriod != 'hour':
+ fstr += ":%02M"
+ if self.intervalPeriod == 'minute':
+ fstr += ":%02S"
+
+ return date.strftime(fstr)
+
+ def nextIntervalStarts(self, when=0):
+ """Return the start time of the interval starting _after_ when.
+
+ :returns: The Unix epoch timestamp for the start time of the interval
+ that contains **when**.
+ """
+ seconds = self.intervalStart(when)
+
+ if self.intervalPeriod == 'month':
+ date = fromUnixSeconds(seconds)
+ year = date.year
+ months = date.month + self.intervalCount
+ if months > 12:
+ year = date.year + 1
+ months = months - 12
+ return toUnixSeconds((year, months, 1, 0, 0, 0))
+ elif self.intervalPeriod == 'day':
+ return seconds + (86400 * self.intervalCount)
+ elif self.intervalPeriod == 'hour':
+ return seconds + (3600 * self.intervalCount)
+ elif self.intervalPeriod == 'minute':
+ return seconds + (60 * self.intervalCount)
+ elif self.intervalPeriod == 'second':
+ return seconds + self.intervalCount
diff --git a/bridgedb/sitecustomize.py b/bridgedb/sitecustomize.py
new file mode 100644
index 0000000..555384b
--- /dev/null
+++ b/bridgedb/sitecustomize.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+"""sitecustomize â Handles potential loading of extra code when Python starts.
+
+**Module Usage:**
+
+This is normally (this seems not to work with Twisted, for as-of-yet unknown
+reasons) useful for using :mod:`coverage` to measure code execution in spawned
+subprocesses in the following way:
+
+ 1. Set the environment variable ``COVERAGE_PROCESS_START`` to the absolute
+ path of the coverage config file. If you are in the top-level of the
+ bridgedb repo, do:
+
+ $ export COVERAGE_PROCESS_START="${PWD}/.coveragerc"
+
+ 2. In that coverage config file, in the ``[run]`` section, set
+ ``parallel = True``.
+
+ 3. Run coverage. From the top-level of the bridgedb repo, try doing:
+
+ $ make reinstall && \
+ coverage run $(which trial) ./bridgedb/test/test_* && \
+ coverage combine && coverage report && coverage html
+
+If ``COVERAGE_PROCESS_START`` is not set, this code does nothing,
+``[run] parallel`` should be set to ``False``, and coverage can be run by
+leaving out the ``coverage combine`` portion of the above command.
+
+To view the output HTML coverage data, open
+``path/to/bridgedb_repo/doc/coverage_html/index.html`` in a browser.
+"""
+
+import coverage
+coverage.process_startup()
diff --git a/bridgedb/strings.py b/bridgedb/strings.py
new file mode 100644
index 0000000..e108a88
--- /dev/null
+++ b/bridgedb/strings.py
@@ -0,0 +1,565 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_strings ; -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Commonly used string constants.
+
+.. todo:: The instructions for the OpenPGP keys in
+ :data:`BRIDGEDB_OPENPGP_KEY` are not translated⦠should we translate them?
+ Should we tell users where to obtain GPG4Win/GPGTools/gnupg? Should those
+ instruction be that verbose? Or should we get rid of the instructions
+ altogether, and assume that any encouragement towards using GPG will just
+ make users more frustrated, and (possibly) (mis-)direct that frustration
+ at Tor or BridgeDB?
+"""
+
+from __future__ import unicode_literals
+
+# This won't work on Python2.6, however
+# 1) We don't use Python2.6, and
+# 2) We don't care about supporting Python2.6, because Python 2.6 (and,
+# honestly, all of Python2) should die.
+from collections import OrderedDict
+
+
+def _(text):
+ """This is necessary because strings are translated when they're imported.
+ Otherwise this would make it impossible to switch languages more than
+ once.
+
+ :returns: The **text**.
+ """
+ return text
+
+
+# TRANSLATORS: Please do not translate the word "TYPE".
+EMAIL_MISC_TEXT = {
+ 0: _("""\
+[This is an automated message; please do not reply.]"""),
+ 1: _("""\
+Here are your bridges:"""),
+ 2: _("""\
+You have exceeded the rate limit. Please slow down! The minimum time between
+emails is %s hours. All further emails during this time period will be ignored."""),
+ 3: _("""\
+COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"""),
+ # TRANSLATORS: Please DO NOT translate the word "BridgeDB".
+ 4: _("Welcome to BridgeDB!"),
+ # TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
+ 5: _("Currently supported transport TYPEs:"),
+ 6: _("Hey, %s!"),
+ 7: _("Hello, friend!"),
+ 8: _("Public Keys"),
+ # TRANSLATORS: This string will end up saying something like:
+ # "This email was generated with rainbows, unicorns, and sparkles
+ # for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+ 9: _("""\
+This email was generated with rainbows, unicorns, and sparkles
+for %s on %s at %s."""),
+}
+
+WELCOME = {
+ # TRANSLATORS: Please DO NOT translate "BridgeDB".
+ # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ # TRANSLATORS: Please DO NOT translate "Tor Network".
+ 0: _("""\
+BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,
+which can help obfuscate your connections to the Tor Network, making it more
+difficult for anyone watching your internet traffic to determine that you are
+using Tor.\n\n"""),
+
+ # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+ 1: _("""\
+Some bridges with IPv6 addresses are also available, though some Pluggable
+Transports aren't IPv6 compatible.\n\n"""),
+
+ # TRANSLATORS: Please DO NOT translate "BridgeDB".
+ # TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
+ # regular, or unexciting". Like vanilla ice cream. It refers to bridges
+ # which do not have Pluggable Transports, and only speak the regular,
+ # boring Tor protocol. Translate it as you see fit. Have fun with it.
+ 2: _("""\
+Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any
+Pluggable Transports %s which maybe doesn't sound as cool, but they can still
+help to circumvent internet censorship in many cases.\n\n"""),
+}
+"""These strings should go on the first "Welcome" email sent by the
+:mod:`~bridgedb.EmailServer`, as well as on the ``index.html`` template used
+by the :mod:`~bridgedb.https.server`. They are used as an introduction to
+explain what Tor bridges are, what bridges do, and why someone might want to
+use bridges.
+"""
+
+FAQ = {
+ 0: _("What are bridges?"),
+ 1: _("""\
+%s Bridges %s are Tor relays that help you circumvent censorship."""),
+}
+
+OTHER_DISTRIBUTORS = {
+ 0: _("I need an alternative way of getting bridges!"),
+ 1: _("""\
+Another way to get bridges is to send an email to %s. Please note that you must
+send the email using an address from one of the following email providers:
+%s, %s or %s."""),
+}
+
+HELP = {
+ 0: _("My bridges don't work! I need help!"),
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ 1: _("""If your Tor doesn't work, you should email %s."""),
+ # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
+ # TRANSLATORS: Please DO NOT translate "Tor Browser".
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ 2: _("""\
+Try including as much info about your case as you can, including the list of
+bridges and Pluggable Transports you tried to use, your Tor Browser version,
+and any messages which Tor gave out, etc."""),
+}
+
+BRIDGES = {
+ 0: _("Here are your bridge lines:"),
+ 1: _("Get Bridges!"),
+}
+
+OPTIONS = {
+ 0: _("Please select options for bridge type:"),
+ 1: _("Do you need IPv6 addresses?"),
+ 2: _("Do you need a %s?"),
+}
+
+CAPTCHA = {
+ 0: _('Your browser is not displaying images properly.'),
+ 1: _('Enter the characters from the image above...'),
+}
+
+HOWTO_TBB = {
+ 0: _("""How to start using your bridges"""),
+ # TRANSLATORS: Please DO NOT translate "Tor Browser".
+ 1: _("""\
+To enter bridges into Tor Browser, first go to the %s Tor Browser download
+page %s and then follow the instructions there for downloading and starting
+Tor Browser."""),
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ 2: _("""\
+When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow
+the wizard until it asks:"""),
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ 3: _("""\
+Does your Internet Service Provider (ISP) block or otherwise censor connections
+to the Tor network?"""),
+ # TRANSLATORS: Please DO NOT translate "Tor".
+ 4: _("""\
+Select 'Yes' and then click 'Next'. To configure your new bridges, copy and
+paste the bridge lines into the text input box. Finally, click 'Connect', and
+you should be good to go! If you experience trouble, try clicking the 'Help'
+button in the 'Tor Network Settings' wizard for further assistance."""),
+}
+
+EMAIL_COMMANDS = {
+ "get help": _("Displays this message."),
+# TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
+# same non-Pluggable Transport bridges described above as being
+# "plain-ol'-vanilla" bridges.
+ "get bridges": _("Request vanilla bridges."),
+ "get ipv6": _("Request IPv6 bridges."),
+ # TRANSLATORS: Please DO NOT translate the word the word "TYPE".
+ "get transport [TYPE]": _("Request a Pluggable Transport by TYPE."),
+ # TRANSLATORS: Please DO NOT translate "BridgeDB".
+ # TRANSLATORS: Please DO NOT translate "GnuPG".
+ "get key": _("Get a copy of BridgeDB's public GnuPG key."),
+ #"subscribe": _("Subscribe to receive new bridges once per week"),
+ #"unsubscribe": _("Cancel a subscription to new bridges"),
+}
+
+#-----------------------------------------------------------------------------
+# All of the following containers are untranslated!
+#-----------------------------------------------------------------------------
+
+#: SUPPORTED TRANSPORTS is dictionary mapping all Pluggable Transports
+#: methodname to whether or not we actively distribute them. The ones which we
+#: distribute SHOULD have the following properties:
+#:
+#: 1. The PT is in a widely accepted, usable state for most Tor users.
+#: 2. The PT is currently publicly deployed *en masse*".
+#: 3. The PT is included within the transports which Tor Browser offers in
+#: the stable releases.
+#:
+#: These will be sorted by methodname in alphabetical order.
+#:
+#: ***Don't change this setting here; change it in :file:`bridgedb.conf`.***
+SUPPORTED_TRANSPORTS = {}
+
+#: DEFAULT_TRANSPORT is a string. It should be the PT methodname of the
+#: transport which is selected by default (e.g. in the webserver dropdown
+#: menu).
+#:
+#: ***Don't change this setting here; change it in :file:`bridgedb.conf`.***
+DEFAULT_TRANSPORT = ''
+
+def _getSupportedTransports():
+ """Get the list of currently supported transports.
+
+ :rtype: list
+ :returns: A list of strings, one for each supported Pluggable Transport
+ methodname, sorted in alphabetical order.
+ """
+ supported = [name.lower() for name,w00t in SUPPORTED_TRANSPORTS.items() if w00t]
+ supported.sort()
+ return supported
+
+def _setDefaultTransport(transport):
+ global DEFAULT_TRANSPORT
+ DEFAULT_TRANSPORT = transport
+
+def _getDefaultTransport():
+ return DEFAULT_TRANSPORT
+
+def _setSupportedTransports(transports):
+ """Set the list of currently supported transports.
+
+ .. note: You shouldn't need to touch this. This is used by the config file
+ parser. You should change the SUPPORTED_TRANSPORTS dictionary in
+ :file:`bridgedb.conf`.
+
+ :param dict transports: A mapping of Pluggable Transport methodnames
+ (strings) to booleans. If the boolean is ``True``, then the Pluggable
+ Transport is one which we will (more easily) distribute to clients.
+ If ``False``, then we (sort of) don't distribute it.
+ """
+ global SUPPORTED_TRANSPORTS
+ SUPPORTED_TRANSPORTS = transports
+
+def _getSupportedAndDefaultTransports():
+ """Get a dictionary of currently supported transports, along with a boolean
+ marking which transport is the default.
+
+ It is returned as a :class:`collections.OrderedDict`, because if it is a
+ regular dict, then the dropdown menu would populated in random order each
+ time the page is rendered. It is sorted in alphabetical order.
+
+ :rtype: :class:`collections.OrderedDict`
+ :returns: An :class:`~collections.OrderedDict` of the Pluggable Transport
+ methodnames from :data:`SUPPORTED_TRANSPORTS` whose value in
+ ``SUPPORTED_TRANSPORTS`` is ``True``. If :data:`DEFAULT_TRANSPORT` is
+ set, then the PT methodname in the ``DEFAULT_TRANSPORT`` setting is
+ added to the :class:`~collections.OrderedDict`, with the value
+ ``True``. Every other transport in the returned ``OrderedDict`` has
+ its value set to ``False``, so that only the one which should be the
+ default PT is ``True``.
+ """
+ supported = _getSupportedTransports()
+ transports = OrderedDict(zip(supported, [False for _ in range(len(supported))]))
+
+ if DEFAULT_TRANSPORT:
+ transports[DEFAULT_TRANSPORT] = True
+
+ return transports
+
+EMAIL_SPRINTF = {
+ # Goes into the "%s types of Pluggable Transports %s" part of ``WELCOME[0]``
+ "WELCOME0": ("", "[0]"),
+ # Goes into the "%s without Pluggable Transport %s" part of ``WELCOME[2]``
+ "WELCOME2": ("-", "-"),
+ # For the "%s Tor Browser download page %s" part of ``HOWTO_TBB[1]``
+ "HOWTO_TBB1": ("", "[0]"),
+ # For the "you should email %s" in ``HELP[0]``
+ "HELP0": ("help at rt.torproject.org"),
+}
+"""``EMAIL_SPRINTF`` is a dictionary that maps translated strings which
+contain format specifiers (i.e. ``%s``) to what those format specifiers should
+be replaced with in a given template system.
+
+For example, a string which needs a pair of HTML ``("<a href=''">, "</a>")``
+tags (for the templates used by :mod:`bridgedb.https.server`) would need some
+alternative replacements for the :mod:`EmailServer`, because the latter uses
+templates with a ``text/plain`` mimetype instead of HTML. For the
+``EmailServer``, the format strings specifiers are replaced with an empty
+string where the opening ``<a>`` tags would go, and a numbered Markdown link
+specifier where the closing ``</a>`` tags would go.
+
+The keys in this dictionary are the Python variable names of the corresponding
+strings which are being formatted, i.e. ``WELCOME0`` would be the string
+replacements for ``strings.WELCOME.get(0)``.
+
+
+For example, the ``0`` string in :data:`WELCOME` above has the substring::
+
+ "%s without Pluggable Transport %s"
+
+and so to replace the two ``%s`` format specifiers, you would use this mapping
+like so::
+
+>>> from bridgedb import strings
+>>> welcome = strings.WELCOME[0] % strings.EMAIL_SPRINTF["WELCOME0"]
+>>> print welcome.split('\n')[0]
+BridgeDB can provide bridges with several types of Pluggable Transports[0],
+
+"""
+
+EMAIL_REFERENCE_LINKS = {
+ "WELCOME0": "[0]: https://www.torproject.org/docs/pluggable-transports.html",
+ "HOWTO_TBB1": "[0]: https://www.torproject.org/projects/torbrowser.html",
+}
+
+BRIDGEDB_OPENPGP_KEY = """\
+# This keypair contains BridgeDB's online signing and encryption subkeys. This
+# keypair rotates because it is kept online. However, the current online
+# keypair will *ALWAYS* be certified by the offline keypair (at the bottom of
+# this file).
+#
+# If you receive an email from BridgeDB, it should be signed with the
+# 21B554E95938F4D0 subkey from the following keypair:
+
+# pub 4096R/8DC43A2848821E32 2013-09-11 [expires: 2015-09-11]
+# Key fingerprint = DF81 1109 E17C 8BF1 34B5 EEB6 8DC4 3A28 4882 1E32
+# uid BridgeDB <bridges at bridges.torproject.org>
+# sub 4096R/21B554E95938F4D0 2013-09-11 [expires: 2015-09-11]
+# Key fingerprint = 9FE3 9D1A 7438 9223 3B3F 66F2 21B5 54E9 5938 F4D0
+# sub 4096R/E7793047C5B54232 2013-09-11 [expires: 2015-09-11]
+# Key fingerprint = CFFB 8469 9048 37E7 8CAE 322C E779 3047 C5B5 4232
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFIv8YABEADRqvfLB4xWj3Fz+HEmUUt/qbJnZhqIjo5WBHaBJOmrzx1c9fLN
+aYG36Hgo6A7NygI1oQmFnDinSrZAtrPaT63d1Jg49yZwr/OhMaxHYJElMFHGJ876
+kLZHmQTysquYKDHhv+fH51t7UVaZ9NkP5cI+V3pqck0DW5DwMsVJXNaU317kk9me
+mPJUDMb5FM4d2Vtk1N+54bHJgpgmnukNtpJmRyHRbZBqNMln5nWF7vdZ4u5PGPWj
+bA0rPZhayeE3FQ0MHiGL12kHAy30pfg54QfPJDQBCywjABetRE+xaM9TcS+R31Pf
+2VbLeb+Km7QpHMwOXI5xZLss9BAWm9EBbmXxuqaRBHyi830jjCrK9UYuzzOqKoUV
+Mk1BRelZTFnGPWeVTE+Ps+pwJ0Dwx4ghppJBCoArmEbkNliblxR/2wYOOFi/ZVA4
+Zc2ok9T3rBLVg07b7ezFUScGiTnc7ac7hp6r8Qsh09ZbhRr9erK/n194aEvkXTfr
+qepwrAE7YeF4YuR206UOFFWDhxWDLbRu0gIWgrevEQu/cvQPrO9uH5fL6Gw/+mNP
+Q/NIteejhkDyvyTUKyBu7x+Gls71zT2u/X13eOAJ8IxBkSVRKQ8tRD+oqJkWplOf
++BpaGU+g6u4kT2AzFDxTOupfrYcPvORTAV/V3suys2YQE4x422GASXDivQARAQAB
+tClCcmlkZ2VEQiA8YnJpZGdlc0BicmlkZ2VzLnRvcnByb2plY3Qub3JnPokDJQQT
+AQoBD0gUgAAAAAAXACh2ZXJpZmllZEB0b3Jwcm9qZWN0Lm9yZ0RGODExMTA5RTE3
+QzhCRjEzNEI1RUVCNjhEQzQzQTI4NDg4MjFFMzJPFIAAAAAAHgAoYnJpZGdlc0Bi
+cmlkZ2VzLnRvcnByb2plY3Qub3JnREY4MTExMDlFMTdDOEJGMTM0QjVFRUI2OERD
+NDNBMjg0ODgyMUUzMioaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2plY3Qub3JnL3Bv
+bGljeS50eHQCGwEDCw0JBBUKCQgEFgIBAAIeAQIXgCcYaHR0cHM6Ly9icmlkZ2Vz
+LnRvcnByb2plY3Qub3JnL2tleS5hc2MFAlSKBKIFCQPDTiIACgkQjcQ6KEiCHjIs
+jg//bJ12eRnBMfIGzOGh+T4wz7/YyKLfARAMnqDnSxhTxuE+M5hWm3QbxP03R6eY
+x+PKwQaDJSmm7HhRhltb7QXUe8dqjnocFwwagpoLZ/81mBLxByqg5TKHGGIGy+DX
+omIzCq5ijx1IUkHlgh708a5alG7bjRTqedT4Wxxyl6psGzDhGQdS8bqx/f32nQaE
+h41l+A/EY1g2HVqky63ZHAP3S2v+mWCrk5DnkElc0229MXqaBuEr4nbYMXRkahMb
+E2gnCmdSoeD21AY6bNyz7IcJGpyKCx9+hVgPjpm3J23JEYyPL+s48jn6QcI/Q2gD
+yAtgU65y6IrdYn8SwkABI1FIq9WAwG7DaInxvkqkYqyBQLaZJEMyX8NTBvFoT5JS
+jnkxG0xu61Vxq0BLYBIOJE0VFHAJ40/jOvSxQJkQhu9G4BK7htnADbtstmMDMM3q
+xuuO5pcj2rl7YthNunyZ1yhPHXijUUyKrwR9piENpptztFBVN6+ijqU/TmWMOtbH
+X7p9F+3tXCHHqwO5U/JMtsb/9M39MR8BrdcLc7m6dCpeuSUuR2LLroh+MoMJGviI
+iesxHF95kFqkJAecW1Z3eKL9vrlbfO3waeuCi18k1TePnZuG5lmf2KjKDW5vHK4O
+WFqvvfK2kxkCUjvGdLeTOAVOV+X+PQ23jvBJO2bS7YbOb9C5Ag0EUi/ygQEQALZ/
+p7xRINHY7MMf3/bo/I0WRxWHd1AE9tRToyEg1S2u1YrWWL5M9D8saRsp9cpnpGEu
+hW3vu7G4nasY27qOz4bSKu1YMAVIC58v1tEnBqdo1zErNjhs38PrmJKbbs9tDfYY
+Oi2x0GlhMbIrNStcZpnCdLa6U6NLMbggDL1GxjMPYBMi4TtLgcIeRDUSjsZscZkg
+Kxs5QkSVc3SrYyraayIc8WtIpDLcxPt6/g90rbatZzBfO+93Rz7qUXHmgzuM0hy1
+Fvn619o3I5DsWrfOz9t/QuznoOBw4PfzDPNT7VlzZN4xHAcr5+7B+DH9IsvlCt5N
+kQFuYpFZCpXNaD2XOtmIqjTCeLNfcgTEj0qoUIEKyKbBIgfP+7S2tLXy8JKUTy5g
+9kxXQeHueLykQ4Mt18JH0nMHbHbQl0K3LGT4ucRDOmjNtlQCltVLkIk3GimyqKs/
+vdZ9c+dm4Akx1qsJcwvveX+imJe2e9RUodcxWXxWrYnuPa5b5nfR1i+GfV0on/Pt
+AQ8gc9CkJpMiq5TQDOFhFP6yQcq77sXuUkEl5qamptedz28E0I693ulnfwcsE80p
+xkpIG6n33DZJSEyqgtWjE1P2pnsVfO5ILs3mKLe7bO1v3qMXcCkMCGH/kwzvtowq
+YvY4gaZMDZtQFY8U7lI9FdRUvVdeHAB24y291nhzABEBAAGJBYMEGAEKANNIFIAA
+AAAAFwAodmVyaWZpZWRAdG9ycHJvamVjdC5vcmdERjgxMTEwOUUxN0M4QkYxMzRC
+NUVFQjY4REM0M0EyODQ4ODIxRTMyTxSAAAAAAB4AKGJyaWRnZXNAYnJpZGdlcy50
+b3Jwcm9qZWN0Lm9yZ0RGODExMTA5RTE3QzhCRjEzNEI1RUVCNjhEQzQzQTI4NDg4
+MjFFMzIqGmh0dHBzOi8vYnJpZGdlcy50b3Jwcm9qZWN0Lm9yZy9wb2xpY3kudHh0
+AhsCBQJUigTTBQkDw01SAqTB2CAEGQEKAIEFAlIv8oFPFIAAAAAAHgAoYnJpZGdl
+c0BicmlkZ2VzLnRvcnByb2plY3Qub3JnOUZFMzlEMUE3NDM4OTIyMzNCM0Y2NkYy
+MjFCNTU0RTk1OTM4RjREMCoaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2plY3Qub3Jn
+L3BvbGljeS50eHQACgkQIbVU6Vk49NDbPw/5ATe/T8+eaToC3v0TYNRH5nveQvzA
+WdnshD3lnvfsgDhbilwifKpc5LHKXU3rvb42HH2cu0ckuksdDTvICZD9cJjRq/F+
+Mzm0pNCAJg0pQnHaaWFQjw+CHYEoizai3S+iYxhNHeSdA6Ty7xm4+bHNf0Aqblbd
+6dKwq9EvjwAI6zZsAHtsmHRUMdrFwGdKae6CSchUT2JQFBPEWMhvzdpDGACWVaSP
+sxYKuYg9LgpswGcof+tprRjKRl8MtSh0ufjbVBlTeSKpL5Y+fcTRD3PI8w7Ocr3z
+jr6XpYG4SUNHsWwxyu/DTXg76Lk1/+BdaH25hDOAasLUOU7yRL8zD/c7M0FkGXdj
+r5I2DEEqwzJ9cPHWjpgb8N9fZLoPFP9JOmKGHINqxNe7TfwiTdD6uDKs/u/QK1U+
+o3iYBXBTREdopPUdBTM9wYRUhyGXTEKLhUP3MGpXYlgeYPrSdp76VyN3BzLTbMv+
++7rxyKxL9cWYU0pnXHgPC5nyHX5nqXmhMnkxAD3Bnm8n9XDfgiyTDExqksEh2VXt
+yhVfLezylEP2fwtd8/mABBCsTjzZW6FRfRRAjUZWZGFpFg8no1x5JS9uiswRP5g1
+qHijNFWpGyTtJWl5VNd0d9+LtVUX1jRpDUpsjZcxqs3fsvw2p+H/zQ1wFvDrsoav
+hqOTq+AEnJc7ZG8JEI3EOihIgh4ych8P/3GTyWb29+43YVibbSPPvEv4gFqziC+9
+1p92FJ0V4XdaT7TW3qaZVp5edUNwB/jjF0SxBybwaMX2ZIGXOjnjF6/Zby4ynuTX
+vZkS1mKRA0KWupB3e9PSMY3ZtssnqpGna/+3qlpxtunW7HcW4nCF/f59WHhlVjaO
+MXjtuWj59yB56Dd1sNjwhcNCyp4/NpzGnRW97ZV3Pp4oqIOqcGzCQXkVPcnaqcOh
+Cs9vIDJlMtn/IWBzUGimuRllDSSVSWkYkyJcG2NUHUwgXYpLwQz7sScvmCPchf4K
+qarpX0FpkUDfqaVVuQ7A2XbPUAVFzIk930G1WzgOuOdg9vhWSEjou+SKrAoMz90w
+3xHwEvmPDTTVJQft9ytoRbwZkIPfzzhII3mr4agbORAfzDaj5g/f6CVRdg6D3ME1
+Etg9ZrfLgRY993g/arfIME6OOsiNcy5+PunN96Rw0o1xoD+97NmZuQrs/p4Mfn5o
+8EwXHutREhahin+3/SV3hz9ReeLYmClq+OVhjPzPdtwZsFoyQyUJoFVHPTuSdChZ
+FPaqN68FjlNMugmxnvski3ZDVT7pw3B6otjjaL3rr6q0PC2yhEb2ntb3IFUizHjn
+80SmfE1Bqwit7ZHu8r/Gt/0iecGk5h84VzSgiGZGF/7m1i5UMVlNSeWnsInGa5Su
+7HSzfMq+YmkzuQINBFIv8p4BEADTOLR5e5NKKRPpjCb4B/8YYkWh+orb70EogIZ6
+j5v8d/djLyhjqZ9BIkh41/hYKMwnsa4KkDkTaX0eNu3BFB2zGgZ1GSd7525ESxiq
+suXIlAg2pex7bysaFfua0nUx64tmaQm2XArdkj/wI0pbg+idWym3WQQmZLyTTbzl
+8rpTEtTt+S2m6z3EeAhEHuNFH16hEDUywlef3EotX3njuFiLqaNvnzUYDxhUvKd2
+2K1es1ggispgP+eb1bkMApxecf2rqmSUEcvsuTWip4oGZPBLGDQeNKHkCUVbj4wT
+yWDIRtto3wi+4CFPEVzw+htj1cQfTstPqUdG7NSOmLQggedoUdv7AJm4MJJiyEax
+l+IAf6Afwrrm3eOSv0PgoUxOrUb9vhIoL8ih8gtiqvQ9qYaRQfQA/w3Z0Su2Yfoc
+fQS8Uw99qG+oTgieG6F6ud8+hMZAYVZFqbU+ztzMyDE6h4Hflkt6VNJ0Hk0VoF38
+TTs77pHXXBbLD6SzR6tbNuR9r/lbmC8Qf2A1ZAThR0iuGhNRFtUPo28GxakxGdLZ
+9kHIxjl7EN/gsmYTwuEhr+yfNtLwtSH0ojeqbDmgufvgh+SITCtyNDAUspjrZYEt
+F0NHRpSom2NFVELMqMRydU/ncph1rGZgVp6/zVj6xIlhKmqj5P1y/9B0c4Tu1CzJ
+pkJ5wwARAQABiQLpBBgBCgDTSBSAAAAAABcAKHZlcmlmaWVkQHRvcnByb2plY3Qu
+b3JnREY4MTExMDlFMTdDOEJGMTM0QjVFRUI2OERDNDNBMjg0ODgyMUUzMk8UgAAA
+AAAeAChicmlkZ2VzQGJyaWRnZXMudG9ycHJvamVjdC5vcmdERjgxMTEwOUUxN0M4
+QkYxMzRCNUVFQjY4REM0M0EyODQ4ODIxRTMyKhpodHRwczovL2JyaWRnZXMudG9y
+cHJvamVjdC5vcmcvcG9saWN5LnR4dAIbDAUCVIoE4QUJA8NNQwAKCRCNxDooSIIe
+Mo7JEADDBtQpYxPhbj3MT0xpk96EDlon/5yHVf+cnk1pNisc+HkJsVe1nh7gAWOz
+wJKdeqOVpgxiJTxIQdl6mipKwwFi0DreP7h56s1WQkuSSWJzqssAwWHfVAsX13fV
+zWd0XyxN/OF9ZKQjX4qwpJ/na631PSwZLvHYhMaZnb9pjNwC5/PEKRmFqLbQT6Px
+12miZT6ToPDCczHxJ4BxbEGVU+PtRsHwmTRT3JhxFNDfeVd+uwsQIMidJbUoqVW7
+fe2zNd0TaWyz4Rw087oZE2OXdctjvtsu8fzXx6d/tkazI6cUOqoaMTR41KEu5X0T
+BpWSAMADBYjNs9QRWXX7ZlsJRUSCX1EKbMhgoL6KIGceIkjH61M/LF6HqDgSgSWt
+h+LIYGa+LrB/6819o32QSOSHHJ5+NJrbCSaLgKE/LKnf92V2QbZE8IGY6EOSjHqn
+n1+j+CLRKY/kUyvk+1TumTghjg/aDs/8Jv8PvgSWLQ0q1rxHYbX7q9ZJhYC/4LdR
+ya/Cho6w2l0N3tV/IMAwvFNHsaiIiiwfoOQbkBUvkyzBwjKt96Ai4I0QKt/63uH0
+drQhlJEgIyGkOrorBByVqZAQdnoLENYIu6tDUj0bTbGObKqua4iPlSK3/g40zCm4
+9OgcN7A8kFuNpgp2EHqj1/jrwd7mZYKsWTuGiR/7fwXf+4xbvg==
+=raCx
+-----END PGP PUBLIC KEY BLOCK-----
+
+# The following keypair is BridgeDB's offline certification-only keypair. It
+# is used to sign new online signing/encryption keypairs.
+#
+# If you import this key and mark it as trusted, emails from BridgeDB (if
+# signed correctly with the online keypair above) should always be trusted. To
+# do this, open a shell and do:
+#
+# $ curl -O https://bridges.torproject.org/keys
+# $ gpg --import keys
+# $ gpg --check-sigs 7B78437015E63DF47BB1270ACBD97AA24E8E472E
+# $ gpg --edit-key 7B78437015E63DF47BB1270ACBD97AA24E8E472E
+#
+# Then type 'trust' to set the trust level. Choose a number that you like.
+# Next type 'quit'. Finally, to create a local signature which will will not
+# be uploaded to keyservers:
+#
+# $ gpg --lsign-key 7B78437015E63DF47BB1270ACBD97AA24E8E472E
+#
+
+# pub 16384R/CBD97AA24E8E472E 2013-10-12
+# Key fingerprint = 7B78 4370 15E6 3DF4 7BB1 270A CBD9 7AA2 4E8E 472E
+# uid BridgeDB (Offline ID Key) <bridges at bridges.torproject.org>
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQgNBFJZB+QBQADcx7laikgZOZXLm6WH2mClm7KrRChmQAHOmzvRYTElk+hVZJ6g
+qSUTdl8fvfhifZPCd3g7nJBtOhQAGlrHmJRXfdf4cTRuD73nggbYQ0NRR9VZ3MIK
+ToJDELBhgmWeNKpLcPsTpi2t9qrHf3xxM06OdxOs9lCGtW7XVYnKx3vaRNk6c0ln
+De82ZWnZr1eMoPzcjslw7AxI94hIgV1GDwTSpBndv/VwgLeBC5XNCKv0adhO/RSt
+fuZOHGT/HfI0U0C3fSTiIu4lJqEd9Qe8LUFQ7wRMrf3KSWwyWNb/OtyMfZ52PEg9
+SMWEfpr6aGwQu6yGPsE4SeHsiew5IqCMi64TZ9IcgY0fveiDzMSIAqnWQcxSL0SH
+YbwQPxuOc4Rxj/b1umjigBG/Y4rkrxCKIw6M+CRaz203zs9ntOsWfnary/w+hepA
+XLjC0yb0cP/oBB6qRyaCk2UTdqq1uWmJ2R/XhZHdZIDabxby6mvQbUQA/NEMOE/B
+VrDonP1HNo1xpnY8lltbxdFD/jDikdjIazckMWl/0fri0pyPSdiJdAK2JrUniP9Q
+eNbgcx3XvNnfjYjiQjTdqfxCTKpSmnsBNyYng6c4viOr5weBFXwEJq2Nl7+rP5pm
+TF1PeiF769z4l2Mrx3X5sQqavTzd2VBMQ6/Kmk9Emxb8e1zyQD6odqJyTi1BBAes
+F2BuKLMCVgZWOFSNGDOMoAUMZh0c6sRQtwz3KRBAuxUYm3wQPqG3XpDDcNM5YXgF
+wVU8SYVwdFpPYT5XJIv2J2u45XbPma5aR0ynGuAmNptzELHta5cgeWIMVsKQbnPN
+M6YTOy5auxLts3FZvKpTDyjBd/VRK6ihkKNKFY3gbP6RbwEK3ws/zOxqFau7sA5i
+NGv4siQTWMG++pClz/exbgHPgs3f8yO34ZbocEBdS1sDl1Lsq4qJYo2Kn5MMHCGs
+dqd7Y+E+ep6b74njb1m2UsySEE2cjj/FAFH91jfFy5PedNb/2Hx6BsPJVb7+N4eI
+pehKQQ46XAbsMq6vUtI4Y0rFiBnqvpERqATQ2QhnEh0UmH7wKVQc4MREZfeEqazV
+G/JFt5Qnt3jq8p6/qbWlOPKTLGUqGq3RXiJgEy/5i22R2ZDjafiGoG1KsZIVZg39
+N25fT8abjPWme6JI3Jv+6gKY8tURoePZcMp/rw0NFs1HtCKUAU6FEOh6uJO7KNie
+eE8qG8ItRXVYnP4f8MFyFkHZcJw27d0PT3IrCM1vJwjqgb2j2xWM/8GJDDuUyims
+jvLDH1E7ek600H3FT5c9xPcgwfMM8BOdBNu0Evm9sdZBZFket+ytXo6GKyS/d91D
+FWE+YL+25+sZJS71dnvSUWVneJrTLFasefvPfIR9/aLJoLVFHnN9sUHfVMj0KlGl
+8AuxL7QfNQawvyjoV8rw/sJOQOwwhof1gZz0ZyjuTKj0WekjmDxcRzVY0eX6BzTm
+o7r4jrHl1Mi75svnKCpXi0Vu/1ZqSnKjCjhRTXDLm7tb8b18jogsgDfs7UkUNwD/
+XF8EfTTU4KotLOODAZIW+soFJZgf8rXQZLRShQmre+PUJfADEUd3yyE9h0JIunPQ
+CxR8R8hVhK4yqFn662Ou7fEl3q8FYBBi1Ahn+263S7+WaZGo7ElwzfRb97gP1e77
+eYd8JwY7UBIQku83CxQdahdGOpAfyvhYW2mxCHVZLXObwc18VgRMa7vjCbkGRPSN
+5NecU5KGW6jU1dXuZk0jRt/9mqtYPjJ7K/EVJD9Yxmz+UdxH+BtsSRp3/5fDmHtW
+CB39a7fetp0ixN503FXPKQUvKAKykETwevmWOzHH3t6BpY/ZSjDCC35Y3dWeB54H
+qNta1r0pSWV6IARUoVteAOcuOU/l3HNzY80rL+iR0HiaszioBsd8k8u0rWXzM3BP
+3vhTzccaldSWfqoT86Jfx0YLX6EoocVS8Ka5KUA8VlJWufnPPXDlF3dULrb+ds/l
+zLazt9hF49HCpU1rZc3doRgmBYxMjYyrfK/3uarDefpfdnjbAVIoP26VpVXhLTEM
+oaD+WoTpIyLYfJQUDn1Q06Nu393JqZb8nRngyMeTs73MDJTzqdL4rZXyweeTrtYe
+4yy+Kc3CZdPlZqpkbuxP0cO0ivaTLdXsTCHDnpk16u4sDukcsmlaTF5d75nu/KIQ
+o3nk0g9NvoschDcQiExuqCUOXCkKcUvYVHsuglAuT+AqK692562JrDOVoGwkUVvm
+Qfo0AQvBvXUzHY4YuBUdWbjWsC4sj6B+MW/TIs/OlKIbPE3MHeDhEGLl/8uBceVo
+kM36xm4F8wDwPK4GPyi/D+3piqBsrsjkgRlodQIUS7A9V19b8TWbUFeH4JGJ+5EH
+9WErBlrsQrnosojLchGGp7HxSxWLBiwdnltu6+/hwbBwydJT8ZxPUANIwTdB+mOE
+ILUXBkpIDfVSoZD7qWlntai53BDQr5pfMJhv15di4XAhtqv43vAmA57ifd+QJS2U
+AfYc4CdX0lk2BZ4jRD8jCZ4Uxw15E3RqhnXsWDRxtD4fwsb2ZFi0DDuPlwBdGgh5
+Rm2Bz9JjSV6gDEuXr/JtAzjSz1Jdh8wPkSofiHGTfxysVhlGlg+YPRziRlzML8A2
+0xY+9mPxEEin5ZQ9wmrDyiiOBvPTbG3O9+Sp5VZDuD4ivW/DHumPWGVSRdjcAQDe
+HMXUVGjhBDnj06XNrgJPhODdJeYq0EnGTt15ofZQSswD7TTTRPDOn0Cz/QARAQAB
+tDpCcmlkZ2VEQiAoT2ZmbGluZSBJRCBLZXkpIDxicmlkZ2VzQGJyaWRnZXMudG9y
+cHJvamVjdC5vcmc+iQkfBBMBCgEJBQJSWQfkSBSAAAAAABcAKHZlcmlmaWVkQHRv
+cnByb2plY3Qub3JnN0I3ODQzNzAxNUU2M0RGNDdCQjEyNzBBQ0JEOTdBQTI0RThF
+NDcyRU8UgAAAAAAeAChicmlkZ2VzQGJyaWRnZXMudG9ycHJvamVjdC5vcmc3Qjc4
+NDM3MDE1RTYzREY0N0JCMTI3MEFDQkQ5N0FBMjRFOEU0NzJFKhpodHRwczovL2Jy
+aWRnZXMudG9ycHJvamVjdC5vcmcvcG9saWN5LnR4dAIbAQMLDQkEFQoJCAQWAgEA
+Ah4BAheAJxhodHRwczovL2JyaWRnZXMudG9ycHJvamVjdC5vcmcva2V5LmFzYwAK
+CRDL2XqiTo5HLoqEP/48rFpJCVStn8xo+KkHSVvsqpxDRlb/nNheI+ov2UxILdwl
+NIU6kLsvKECKPe1AHKdS/MzANbkTF35Y4QgZsNpVXaCVL7adGBSzOdPFupDJJVOu
+wa+uFRc/FuNJyH/TIn56/+R5J5C54OxIYNxvW3WF4eHKLJYk/JZOMMfy4iWm7Sql
+0nDC5O435nK4F4Jb4GLPlUIzioIy2OWqGoFHXymbGhL1tWaqasYmED4n3AMqlYw6
+xnNhdWOc/KZelPl9nanybyh0IIdZqUKZleRt4BxSgIT8FqC2sZuZ8z7O9s987Naz
+Q32SKaP4i2M9lai/Y2QYfKo+wlG+egmxtujz7etQMGlpgBZzFLdJ8/w4U11ku1ai
+om74RIn8zl/LHjMQHnCKGoVlscTI1ZPt+p+p8hO2/9vOwTR8y8O/3DQSOfTSipwc
+a3obRkp5ndjfjefOoAnuYapLw72fhJ9+Co09miiHQu7vq4j5k05VpDQd0yxOAZnG
+vodPPhq7/zCG1K9Sb1rS9GvmQxGmgMBWVn+keTqJCZX7TSVgtgua9YjTJNVSiSLv
+rLslNkeRfvkfbAbU8379MDB+Bax04HcYTC4syf0uqUXYq6jRtX37Dfq5XkLCk2Bt
+WusH2NSpHuzZRWODM9PZb6U3vuBmU1nqY77DciuaeBqGGyrC/6UKrS0DrmVvF/0Z
+Sft3BY6Zb3q7Qm7xpzsfrhVlhlyQqZPhr6o7QnGuvwRr+gDwhRbpISKYo89KYwjK
+4Qr7sg/CyU2hWBCDOFPOcv/rtE0aD88M+EwRG/LCfEWU34Dc43Jk+dH56/3eVR58
+rISHRUcU0Y603Uc+/WM31iJmR/1PvGeal+mhI9YSWUIgIY8Mxt3cM2gYl/OErGbN
+4hWAPIFn4sM9Oo4BHpN7J2vkUatpW6v4Mdh+pNxzgE/V5S21SGaAldvM1SzCRz52
+xRt642Mhf6jqfrwzXf7kq7jpOlu1HkG1XhCZQPw7qyIKnX4tjaRd9HXhn9Jb2vA5
+Av+EOPoAx6Yii5X1RkDILOijvgVfSIFXnflHzs58AhwHztQOUWXDkfS5jVxbenbV
+X4DwgtrpzpdPBgBYNtCGBy9pqhWA2XnkH2vjchZy+xIAoaJNIVBfNkR8tflJWEfm
+i/2U0jJnhY8dEClbu3KQnbwKe5E9mTz1VmBsdWaK5rBVZamD/wssQzzyf3SXXXIU
+W6DUXOCzgWvxvqC09lc4izEAxwUktMY+aapplNs/kjOqHYVkW4zpWGp4PDAT/DW9
+/727CeoqY29HePaeGl0/PpR37CkquP69oQeJSU9CNeyAKnQtvaqxLBcEOohSaPtK
+Iy1q6yQgT4j+gVAsFDVyobCNkA8B0GfemDcEXA5dfriTHN72Br0koS0nvv6P5k7T
+7aaSNX++zdEnPauAZXPPjVt7R1sEvx0Oj+l1pu9hNX0nldaNs13bPU5DIOYv+5fN
+En6pqzYGj/0v8Qeb53Qv5de+lo7ZAu/truVa+GOT3ay4jZBaFh2mDZbA+t1V3GmB
+FtYGoVfou4iBQpx6tJLg3PKvtPj9g5B4LTxZVKrdfHXWyNNQOLzWSIgFj44+SmhU
+LVCXofEvJ0sOX2wtqy54Q4lMIc6BK1IB+hsFV6sSnIeI7YmrRXusWEG0wnroNlbq
+FiWg5+oeI1CnnCyj4FmDX/A/Bo0RxD0x3yqDximkOpcHMtLLfFjK3d5ltwBgDOOe
+pvgabxID01mZxh3OYKdGpW3Z1VKEhHjF5e9BhhEKQ8mG3raaDs2hQ2iuEqTzNLif
+aQdRCYd62jS14qSy2Dd+oZ0FbgzJNigWldvuwWzJCO2toF29pvfWtYRuqV/Vt3CK
+iO7en9bhOMRynPlCkAR7ZiZvT9dzStiMKf1v8mzgRjCIiLIwM1v/xNZWEZ/TOfSR
+E7dBMbDzaNjtCsMmNiyplqCjWbaj4irdIhKbtKJ02a1Jopo1/XNK0Y8AbK1xEHV0
++mjBYU/Pfqnf0WFhkJgha+J17wqrUxf2/Y1b/pdDMGqVWe9+p8tvSP5FNddNyecZ
+0pojFH0jAzHpQen7eeIA3XupVe6cTEhNz4OjHBlZE6dN0q8UDdeG75yPunwShQiO
+kRXA/qxkID/2OLIInWJP0HG05hncGfWZKCLBc/dFg3dNo8VKpw/Q6uMBj2iGi8iB
+lnQGmHQa3j1ANPbcl3ljdJQDEnxk5TEVxNPYUw/BI58l3p+Z3vAZqC0Io7EgpuZ8
+qPuV6hJ2c/7VuFAXVs2mUExtWAjbgnYAfsJtn1yk3sphl65TjPnZwaBlP/ls/W/j
+mVjAx9d5b3mmMBJmNZDvY1QvcftDgfL5vYG5g7UwsbojuNxeM4rwn8qCKk5wC1/a
+Zl6Rh2DG4xS3/ef5tQWw28grjRRwv5phYKtedsKpYRscKAMhiOsChAiSYuCRczmI
+ErdO8ryK8QNzcpE4qVzFQMEtkG6V0RYYjMJzJuY5BW3hKt1UNNaqiGBpNKuf0GoO
+zK/vMgxoo+iFmOuaBdQEjlPLbK+3k+7j14KKVI655AXVKyAsOoSYPzOqfkdiu9W8
+34fOanH7S+lclkXwxTbXko9Jt6Ml64H4QKwd8ak2nCcX9FuMge7XP9VL/pBBMXcB
+WHUKdoqMJExcg5A4H2cyxZ6QgHzNFgqV/4+MGGP+TMc9owzrT3PBadVrMxnHnjc/
+/XYv48p2rRkjyjrtH+ZO9rlOsw0OmGgh9yoQPZn2tiNhG9piyvVxFKZflJm8I4kC
+4AQTAQoAygUCUlkPIkgUgAAAAAAXACh2ZXJpZmllZEB0b3Jwcm9qZWN0Lm9yZzdC
+Nzg0MzcwMTVFNjNERjQ3QkIxMjcwQUNCRDk3QUEyNEU4RTQ3MkVPFIAAAAAAHgAo
+YnJpZGdlc0BicmlkZ2VzLnRvcnByb2plY3Qub3JnREY4MTExMDlFMTdDOEJGMTM0
+QjVFRUI2OERDNDNBMjg0ODgyMUUzMioaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2pl
+Y3Qub3JnL3BvbGljeS50eHQACgkQjcQ6KEiCHjIaqBAA0BuEs7horx6iCq4cjAhv
+YPLrxuC4fKEfVyhAjCJMJSFFCPAlGgU+BjyPNDD57wzKAmUkdJG+Ss25mwWXa53w
+5R2kDqDnHocOdZGtxZ7zx/uUd2eWLNBfVuK7nHOk1d1Hs0OZBnckc+MCqnLtuYe5
+68pa9+jW6cNIjAnzMIListmoXWgYYWJvMKeBMG4DGtYJ8w7CJQjOHc5yar12DrX3
+wnQ7hXtFuuqQblpEUnLnZGvHf2NKMZfBBMcP96h9OmLGNa+vmNYsMyPKU7n5hPgX
+nTgmQ4xrv1G7JukjppZRA8SFoxupcaQeTixyWERGBhBiAbwZsbQz8L/TVZKierzg
+sdNngHcFzE8MyjuJDvTos7qXPmgSRXFqJLRn0ZxpR5V1V8BVZUqCGuSZT89TizsD
+z5vyv8c9r7HKD4pRjw32P2dgcEqyGRkqERAgSuFpObP+juty+kxYyfnadBNCyjgP
+s7u0GmsTt4CZi7BbowNRL6bynrwrmQI9LJI1bPhgqfdDUbqG3HXwHz80oRFfKou8
+JTYKxK4Iumfw2l/uAACma5ZyrwIDBX/H5XEQqch4sORzQnuhlTmZRf6ldVIIWjdJ
+ef+DpOt12s+cS2F4D5g8G6t9CprCLYyrXiHwM/U8N5ywL9IeYKSWJxa7si3l9A6o
+ZxOds8F/UJYDSIB97MQFzBo=
+=JdC7
+-----END PGP PUBLIC KEY BLOCK-----
+"""
diff --git a/bridgedb/translations.py b/bridgedb/translations.py
new file mode 100644
index 0000000..2c9d56f
--- /dev/null
+++ b/bridgedb/translations.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_translations -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+import gettext
+import logging
+import os
+import re
+
+from bridgedb import _langs
+from bridgedb import safelog
+from bridgedb.parse import headers
+
+
+TRANSLATIONS_DIR = os.path.join(os.path.dirname(__file__), 'i18n')
+
+
+def getFirstSupportedLang(langs):
+ """Return the first language in **langs** that we support.
+
+ :param list langs: All requested languages
+ :rtype: str
+ :returns: A country code for the client's preferred language.
+ """
+ lang = 'en-US'
+ supported = _langs.get_langs()
+
+ for l in langs:
+ if l in supported:
+ lang = l
+ break
+ return lang
+
+def getLocaleFromHTTPRequest(request):
+ """Retrieve the languages from an HTTP ``Accept-Language:`` header.
+
+ Parse the languages from the header, use them to install a
+ ``gettext.translation`` chain via :func:`installTranslations`, and lastly
+ return the requested languages.
+
+ :type request: :api:`twisted.web.server.Request`
+ :param request: An incoming request from a client.
+ :rtype: list
+ :returns: All requested languages.
+ """
+ header = request.getHeader('accept-language')
+ if header is None:
+ logging.debug("Client sent no 'Accept-Language' header. Using fallback.")
+ header = 'en,en-US'
+
+ langs = headers.parseAcceptLanguage(header)
+ if not safelog.safe_logging: # pragma: no cover
+ logging.debug("Client Accept-Language (top 5): %s" % langs[:5])
+
+ # Check if we got a ?lang=foo argument, and if we did, insert it first
+ chosenLang = request.args.get("lang", [None,])[0]
+ if chosenLang:
+ logging.debug("Client requested language: %r" % chosenLang)
+ langs.insert(0, chosenLang)
+
+ installTranslations(langs)
+ return langs
+
+def getLocaleFromPlusAddr(address):
+ """See whether the user sent his email to a 'plus' address, for instance to
+ bridges+fa at bridges.torproject.org. Plus addresses are the current
+ mechanism to set the reply language.
+ """
+ replyLocale = "en"
+ r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
+ match = re.match(r, address)
+ if match:
+ replyLocale = match.group(3)
+
+ return replyLocale
+
+def installTranslations(langs):
+ """Create a ``gettext.translation`` chain for all **langs**.
+
+ Attempt to install the first language in the **langs** list. If that
+ fails, we receive a ``gettext.NullTranslation`` object, and if it worked
+ then we have a ``gettext.GNUTranslation`` object. Whichever one we end up
+ with, get the other languages and add them as fallbacks to the
+ first. Lastly, install this chain of translations.
+
+ :param list langs: A list of language codes.
+ :returns: A ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` with
+ fallback languages set.
+ """
+ try:
+ language = gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
+ languages=langs, fallback=True)
+ for lang in langs:
+ language.add_fallback(
+ gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
+ languages=langs, fallback=True))
+ except IOError as error:
+ logging.error(error.message)
+
+ language.install(unicode=True)
+ return language
+
+def usingRTLLang(langs):
+ """Check if we should translate the text into a RTL language.
+
+ Choose the first language from the **langs** list that we support and
+ return True if it is a RTL language, else return False.
+
+ :param list langs: An incoming request.
+ :rtype: bool
+ :returns: ``True`` if the preferred language is right-to-left; ``False``
+ otherwise.
+ """
+ lang = getFirstSupportedLang(langs)
+ if lang in _langs.RTL_LANGS:
+ return True
+ return False
diff --git a/bridgedb/txrecaptcha.py b/bridgedb/txrecaptcha.py
new file mode 100644
index 0000000..3666904
--- /dev/null
+++ b/bridgedb/txrecaptcha.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_txrecaptcha -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Twisted-based reCAPTCHA client.
+
+This client *always* uses TLS with strict hostname checking, unlike the
+official Google Python recaptcha-client_, which is harcoded_ to use plaintext
+HTTP.
+
+Small portions of this code were taken from the official Google Python
+recaptcha-client_ module, version 1.0.6. Those portions are
+:class:`RecaptchaResponse`, :data:`API_SERVER`, They total 5 lines of code,
+which are copyright the authors of the recaptcha-client_ package.
+
+.. _hardcoded: https://code.google.com/p/recaptcha/source/browse/trunk/recaptcha-plugins/python/recaptcha/client/captcha.py#76
+.. _recaptcha-client: https://pypi.python.org/pypi/recaptcha-client/1.0.6
+"""
+
+import logging
+import urllib
+
+from OpenSSL.crypto import FILETYPE_PEM
+from OpenSSL.crypto import load_certificate
+
+from twisted import version as _twistedversion
+from twisted.internet import defer
+from twisted.internet import protocol
+from twisted.internet import reactor
+from twisted.python import failure
+from twisted.python.versions import Version
+from twisted.web import client
+from twisted.web.http_headers import Headers
+from twisted.web.iweb import IBodyProducer
+
+from zope.interface import implements
+
+from bridgedb.crypto import SSLVerifyingContextFactory
+
+#: This was taken from recaptcha.client.captcha.API_SSL_SERVER.
+API_SSL_SERVER = API_SERVER = "https://www.google.com/recaptcha/api"
+API_SSL_VERIFY_URL = "%s/verify" % API_SSL_SERVER
+
+#: (type: `OpenSSL.crypto.X509`) Only trust certificate for the reCAPTCHA
+#: :data:`API_SSL_SERVER` which were signed by the Google Internet Authority CA.
+GOOGLE_INTERNET_AUTHORITY_CA_CERT = load_certificate(FILETYPE_PEM, bytes("""\
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIDFXfhMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMjEyMTU1ODUwWhcNMTMxMjMxMTU1ODUw
+WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
+R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
+NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
+qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
+oDAfBgNVHSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHQ4EFgQUv8Aw
+6/VDET5nup6R+/xq2uNrEiQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E
+BAMCAQYwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
+Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAvprjecFG+iJsxzEF
+ZUNgujFQodUovxOWZshcnDW7fZ7mTlk3zpeVJrGPZzhaDhvuJjIfKqHweFB7gwB+
+ARlIjNvrPq86fpVg0NOTawALkSqOUMl3MynBQO+spR7EHcRbADQ/JemfTEh2Ycfl
+vZqhEFBfurZkX0eTANq98ZvVfpg=
+-----END CERTIFICATE-----"""))
+
+# `t.w.client.HTTPConnectionPool` isn't available in Twisted-12.0.0
+# (see ticket #11219: https://bugs.torproject.org/11219):
+_connectionPoolAvailable = _twistedversion >= Version('twisted', 12, 1, 0)
+if _connectionPoolAvailable:
+ logging.info("Using HTTPConnectionPool for reCaptcha API server.")
+ _pool = client.HTTPConnectionPool(reactor, persistent=False)
+ _pool.maxPersistentPerHost = 5
+ _pool.cachedConnectionTimeout = 30
+ _agent = client.Agent(reactor, pool=_pool)
+else:
+ logging.warn("Twisted-%s is too old for HTTPConnectionPool! Disabling..."
+ % _twistedversion.short())
+ _pool = None
+ _agent = client.Agent(reactor)
+
+
+# Twisted>=14.0.0 changed the way in which hostname verification works.
+if _twistedversion >= Version('twisted', 14, 0, 0):
+ from twisted.internet._sslverify import OpenSSLCertificateAuthorities
+
+ class RecaptchaOpenSSLCertificateAuthorities(OpenSSLCertificateAuthorities):
+ """The trusted CAs for connecting to reCAPTCHA servers."""
+ #: A list of `OpenSSL.crypto.X509` objects.
+ caCerts = [GOOGLE_INTERNET_AUTHORITY_CA_CERT,]
+ def __init__(self):
+ super(RecaptchaOpenSSLCertificateAuthorities, self).__init__(self.caCerts)
+
+ class RecaptchaPolicyForHTTPS(client.BrowserLikePolicyForHTTPS):
+ _trustRoot = RecaptchaOpenSSLCertificateAuthorities()
+ def __init__(self):
+ super(RecaptchaPolicyForHTTPS, self).__init__(trustRoot=self._trustRoot)
+
+
+def _setAgent(agent):
+ """Set the global :attr:`agent`.
+
+ :param agent: An :api:`twisted.web.client.Agent` for issuing requests.
+ """
+ global _agent
+ _agent = agent
+
+def _getAgent(reactor=reactor, url=API_SSL_VERIFY_URL, connectTimeout=30,
+ **kwargs):
+ """Create a :api:`twisted.web.client.Agent` which will verify the
+ certificate chain and hostname for the given **url**.
+
+ :param reactor: A provider of the
+ :api:`twisted.internet.interface.IReactorTCP` interface.
+ :param str url: The full URL which will be requested with the
+ ``Agent``. (default: :attr:`API_SSL_VERIFY_URL`)
+ :param pool: An :api:`twisted.web.client.HTTPConnectionPool`
+ instance. (default: :attr:`_pool`)
+ :type connectTimeout: None or int
+ :param connectTimeout: If not ``None``, the timeout passed to
+ :api:`twisted.internet.reactor.connectTCP` or
+ :api:`twisted.internet.reactor.connectSSL` for specifying the
+ connection timeout. (default: ``30``)
+ """
+ # Twisted>=14.0.0 changed the way in which hostname verification works.
+ if _twistedversion >= Version('twisted', 14, 0, 0):
+ contextFactory = RecaptchaPolicyForHTTPS()
+ else:
+ contextFactory = SSLVerifyingContextFactory(url)
+
+ if _connectionPoolAvailable:
+ return client.Agent(reactor,
+ contextFactory=contextFactory,
+ connectTimeout=connectTimeout,
+ pool=_pool,
+ **kwargs)
+ else:
+ return client.Agent(reactor,
+ contextFactory=contextFactory,
+ connectTimeout=connectTimeout,
+ **kwargs)
+
+_setAgent(_getAgent())
+
+
+class RecaptchaResponseError(ValueError):
+ """There was an error with the reCaptcha API server's response."""
+
+
+class RecaptchaResponse(object):
+ """Taken from recaptcha.client.captcha.`RecaptchaResponse`_.
+ .. RecaptchaResponse: https://code.google.com/p/recaptcha/source/browse/trunk/recaptcha-plugins/python/recaptcha/client/captcha.py#7
+ """
+ def __init__(self, is_valid, error_code=None):
+ self.is_valid = is_valid
+ self.error_code = error_code
+
+
+class RecaptchaResponseProtocol(protocol.Protocol):
+ """HTML parser which creates a :class:`RecaptchaResponse` from the body of
+ the reCaptcha API server's response.
+ """
+ def __init__(self, finished):
+ """Create a protocol for creating :class:`RecaptchaResponse`s.
+
+ :type finished: :api:`~twisted.internet.defer.Deferred`
+ :param finished: A deferred which will have its ``callback()`` called
+ with a :class:`RecaptchaResponse`.
+ """
+ self.finished = finished
+ self.remaining = 1024 * 10
+ self.response = ''
+
+ def dataReceived(self, data):
+ """Called when some data is received from the connection."""
+ if self.remaining:
+ received = data[:self.remaining]
+ self.response += received
+ self.remaining -= len(received)
+
+ def connectionLost(self, reason):
+ """Called when the connection was closed.
+
+ :type reason: :api:`twisted.python.failure.Failure`
+ :param reason: A string explaning why the connection was closed,
+ wrapped in a ``Failure`` instance.
+
+ :raises: A :api:`twisted.internet.error.ConnectError` if the
+ """
+ valid = False
+ error = reason.getErrorMessage()
+ try:
+ (valid, error) = self.response.strip().split('\n', 1)
+ except ValueError:
+ error = "Couldn't parse response from reCaptcha API server"
+
+ valid = bool(valid == "true")
+ result = RecaptchaResponse(is_valid=valid, error_code=error)
+ logging.debug(
+ "ReCaptcha API server response: %s(is_valid=%s, error_code=%s)"
+ % (result.__class__.__name__, valid, error))
+ self.finished.callback(result)
+
+
+class _BodyProducer(object):
+ """I write a string into the HTML body of an open request."""
+ implements(IBodyProducer)
+
+ def __init__(self, body):
+ self.body = body
+ self.length = len(body)
+
+ def startProducing(self, consumer):
+ """Start writing the HTML body."""
+ consumer.write(self.body)
+ return defer.succeed(None)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
+
+ def resumeProducing(self):
+ pass
+
+
+def _cbRequest(response):
+ """Callback for a :api:`twisted.web.client.Agent.request` which delivers
+ the result to a :class:`RecaptchaResponseProtocol`.
+
+ :returns: A :api:`~twisted.internet.defer.Deferred` which will callback
+ with a ``recaptcha.RecaptchaResponse`` for the request.
+ """
+ finished = defer.Deferred()
+ response.deliverBody(RecaptchaResponseProtocol(finished))
+ return finished
+
+def _ebRequest(fail):
+ """Errback for a :api:`twisted.web.client.Agent.request`.
+
+ :param fail: A :api:`twisted.python.failure.Failure` which occurred during
+ the request.
+ """
+ logging.debug("txrecaptcha._ebRequest() called with %r" % fail)
+ error = fail.getErrorMessage() or "possible problem in _ebRequest()"
+ return RecaptchaResponse(is_valid=False, error_code=error)
+
+def _encodeIfNecessary(string):
+ """Encode unicode objects in utf-8 if necessary."""
+ if isinstance(string, unicode):
+ return string.encode('utf-8')
+ return string
+
+def submit(recaptcha_challenge_field, recaptcha_response_field,
+ private_key, remoteip, agent=_agent):
+ """Submits a reCaptcha request for verification. This function is a patched
+ version of the ``recaptcha.client.captcha.submit()`` function in
+ reCaptcha's Python API.
+
+ It does two things differently:
+ 1. It uses Twisted for everything.
+ 2. It uses SSL/TLS for everything.
+
+ This function returns a :api:`~twisted.internet.defer.Deferred`. If you
+ need a ``recaptcha.client.captcha.RecaptchaResponse`` to be returned, use
+ the :func:`submit` function, which is an ``@inlineCallbacks`` wrapper for
+ this function.
+
+ :param str recaptcha_challenge_field: The value of the HTTP POST
+ ``recaptcha_challenge_field`` argument from the form.
+ :param recaptcha_response_field: The value of the HTTP POST
+ ``recaptcha_response_field`` argument from the form.
+ :param private_key: The reCAPTCHA API private key.
+ :param remoteip: An IP address to give to the reCaptcha API server.
+ :returns: A :api:`~twisted.internet.defer.Deferred` which will callback
+ with a ``recaptcha.RecaptchaResponse`` for the request.
+ """
+ if not (recaptcha_response_field and len(recaptcha_response_field) and
+ recaptcha_challenge_field and len(recaptcha_challenge_field)):
+ d = defer.Deferred()
+ d.addBoth(_ebRequest) # We want `is_valid=False`
+ d.errback(failure.Failure(ValueError('incorrect-captcha-sol')))
+ return d
+
+ params = urllib.urlencode({
+ 'privatekey': _encodeIfNecessary(private_key),
+ 'remoteip': _encodeIfNecessary(remoteip),
+ 'challenge': _encodeIfNecessary(recaptcha_challenge_field),
+ 'response': _encodeIfNecessary(recaptcha_response_field)})
+ body = _BodyProducer(params)
+ headers = Headers({"Content-type": ["application/x-www-form-urlencoded"],
+ "User-agent": ["reCAPTCHA Python"]})
+ d = agent.request('POST', API_SSL_VERIFY_URL, headers, body)
+ d.addCallbacks(_cbRequest, _ebRequest)
+ return d
diff --git a/bridgedb/util.py b/bridgedb/util.py
new file mode 100644
index 0000000..f15e08b
--- /dev/null
+++ b/bridgedb/util.py
@@ -0,0 +1,385 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_util -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2013-2015, Matthew Finkel
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Common utilities for BridgeDB."""
+
+from functools import partial
+
+import abc
+import logging
+import logging.config
+import logging.handlers
+import os
+
+from twisted.python import components
+
+
+def _getLogHandlers(logToFile=True, logToStderr=True):
+ """Get the appropriate list of log handlers.
+
+ :param bool logToFile: If ``True``, add a logfile handler.
+ :param bool logToStderr: If ``True``, add a stream handler to stderr.
+ :rtype: list
+ :returns: A list containing the appropriate log handler names from the
+ :class:`logging.config.dictConfigClass`.
+ """
+ logHandlers = []
+ if logToFile:
+ logHandlers.append('rotating')
+ if logToStderr:
+ logHandlers.append('console')
+ return logHandlers
+
+def _getRotatingFileHandler(filename, mode='a', maxBytes=1000000, backupCount=0,
+ encoding='utf-8', uid=None, gid=None):
+ """Get a :class:`logging.RotatingFileHandler` with a logfile which is
+ readable+writable only by the given **uid** and **gid**.
+
+ :param str filename: The full path to the log file.
+ :param str mode: The mode to open **filename** with. (default: ``'a'``)
+ :param int maxBytes: Rotate logfiles after they have grown to this size in
+ bytes.
+ :param int backupCount: The number of logfiles to keep in rotation.
+ :param str encoding: The encoding for the logfile.
+ :param int uid: The owner UID to set on the logfile.
+ :param int gid: The GID to set on the logfile.
+ :rtype: :class:`logging.handlers.RotatingFileHandler`
+ :returns: A logfile handler which will rotate files and chown/chmod newly
+ created files.
+ """
+ # Default to the current process owner's uid and gid:
+ uid = os.getuid() if not uid else uid
+ gid = os.getgid() if not gid else gid
+
+ if not os.path.exists(filename):
+ open(filename, 'a').close()
+ os.chown(filename, uid, gid)
+ try:
+ os.chmod(filename, os.ST_WRITE | os.ST_APPEND)
+ except AttributeError: # pragma: no cover
+ logging.error("""
+ XXX FIXME: Travis chokes on `os.ST_WRITE` saying that the module doesn't
+ have that attribute, for some reason:
+ https://travis-ci.org/isislovecruft/bridgedb/builds/24145963#L1601""")
+ os.chmod(filename, 384)
+
+ fileHandler = partial(logging.handlers.RotatingFileHandler,
+ filename,
+ mode,
+ maxBytes=maxBytes,
+ backupCount=backupCount,
+ encoding=encoding)
+ return fileHandler
+
+def configureLogging(cfg):
+ """Set up Python's logging subsystem based on the configuration.
+
+ :type cfg: :class:`~bridgedb.persistent.Conf`
+ :param cfg: The current configuration, including any in-memory settings.
+ """
+ from bridgedb import safelog
+
+ # Turn on safe logging by default:
+ safelogging = getattr(cfg, 'SAFELOGGING', True)
+ safelog.setSafeLogging(safelogging)
+
+ level = getattr(cfg, 'LOGLEVEL', 'WARNING')
+ logLevel = getattr(logging, level, 0)
+ logStderr = getattr(cfg, 'LOG_TO_STDERR', False)
+ logfileName = getattr(cfg, 'LOGFILE', "bridgedb.log")
+ logfileCount = getattr(cfg, 'LOGFILE_COUNT', 3) - 1
+ logfileRotateSize = getattr(cfg, 'LOGFILE_ROTATE_SIZE', 10000000)
+ logThreads = getattr(cfg, 'LOG_THREADS', False)
+ logTrace = getattr(cfg, 'LOG_TRACE', False)
+ logTimeFormat = getattr(cfg, 'LOG_TIME_FORMAT', "%H:%M:%S")
+
+ logFilters = []
+ if safelogging:
+ logFilters = ['safelogEmail', 'safelogIPv4', 'safelogIPv6']
+
+ logConfig = {
+ 'version': 1,
+ 'filters': {
+ 'safelogEmail': {'()': safelog.SafelogEmailFilter},
+ 'safelogIPv4': {'()': safelog.SafelogIPv4Filter},
+ 'safelogIPv6': {'()': safelog.SafelogIPv6Filter},
+ },
+ 'formatters': {
+ 'default': {'()': JustifiedLogFormatter,
+ # These values below are kwargs passed to
+ # :class:`JustifiedFormatter`:
+ 'logThreads': logThreads,
+ 'logTrace': logTrace,
+ 'datefmt': logTimeFormat},
+ },
+ 'handlers': {
+ 'console': {'class': 'logging.StreamHandler',
+ 'level': logLevel,
+ 'formatter': 'default',
+ 'filters': logFilters},
+ 'rotating': {'()': _getRotatingFileHandler(logfileName, 'a',
+ logfileRotateSize,
+ logfileCount),
+ 'level': logLevel,
+ 'formatter': 'default',
+ 'filters': logFilters},
+ },
+ 'root': {
+ 'handlers': _getLogHandlers(logfileName, logStderr),
+ 'level': logLevel,
+ },
+ }
+
+ logging.config.dictConfig(logConfig)
+
+ logging.info("Logger Started.")
+ logging.info("Level: %s", logLevel)
+ logging.info("Safe Logging: %sabled" % ("En" if safelogging else "Dis"))
+
+def levenshteinDistance(s1, s2, len1=None, len2=None,
+ offset1=0, offset2=0, memo=None):
+ """Compute the Levenstein Distance between two strings.
+
+ The `Levenshtein String Distance Algorithm
+ <https://en.wikipedia.org/wiki/Levenshtein_distance>` efficiently computes
+ the number of characters which must be changed in **s1** to make it
+ identical to **s2**.
+
+ >>> levenshteinDistance('cat', 'cat')
+ 0
+ >>> levenshteinDistance('cat', 'hat')
+ 1
+ >>> levenshteinDistance('arma', 'armadillo')
+ 5
+
+ :param str s1: The string which should be changed.
+ :param str s2: The string which **stringOne** should be compared to.
+ """
+ len1 = len(s1) if len1 is None else len1
+ len2 = len(s2) if len2 is None else len2
+ memo = {} if memo is None else memo
+
+ key = ','.join([str(offset1), str(len1), str(offset2), str(len2)])
+ if memo.get(key) is not None: return memo[key]
+
+ if len1 == 0: return len2
+ elif len2 == 0: return len1
+
+ cost = 0 if (s1[offset1] == s2[offset2]) else 1
+ distance = min(
+ levenshteinDistance(s1, s2, len1-1, len2, offset1+1, offset2, memo) + 1,
+ levenshteinDistance(s1, s2, len1, len2-1, offset1, offset2+1, memo) + 1,
+ levenshteinDistance(s1, s2, len1-1, len2-1, offset1+1, offset2+1, memo) + cost,
+ )
+ memo[key] = distance
+ return distance
+
+def isascii(s):
+ """Return True if there are no non-ASCII characters in s, False otherwise.
+
+ Note that this function differs from the str.is* methods in that
+ it returns True for the empty string, rather than False.
+
+ >>> isascii('\x80')
+ False
+ >>> isascii('foo\tbar\rbaz\n')
+ True
+ >>> isascii('foo bar')
+ True
+
+ :param str s: The string to check for non-ASCII characters.
+ """
+ return all(map((lambda ch: ord(ch) < 128), s))
+
+def isascii_noncontrol(s):
+ """Return True if there are no non-ASCII or control characters in
+ s, False otherwise.
+
+ Note that this function differs from the str.is* methods in that
+ it returns True for the empty string, rather than False.
+
+ >>> isascii_noncontrol('\x80')
+ False
+ >>> isascii_noncontrol('foo\tbar\rbaz\n')
+ False
+ >>> isascii_noncontrol('foo bar')
+ True
+
+ :param str s: The string to check for non-ASCII or control characters.
+ """
+ return all(map((lambda ch: 32 <= ord(ch) < 127), s))
+
+def replaceControlChars(text, replacement=None, encoding="utf-8"):
+ """Remove ASCII control characters [0-31, 92, 127].
+
+ >>> replaceControlChars('foo\n bar\\ baz\r \t\0quux\n')
+ 'foo bar baz quux'
+ >>> replaceControlChars("\bI wonder if I'm outside the quotes now")
+ "I wonder if I'm outside the quotes now"
+
+ :param str text: Some text to remove ASCII control characters from.
+ :param int replacement: If given, the **replacement** should be an integer
+ representing the decimal representation of the byte to replace
+ occurences of ASCII control characters with. For example, if they
+ should be replaced with the character ``'a'``, then ``97`` should be
+ used as the **replacement**, because ``ord('a') == 97``.
+ :param str encoding: The encoding of the **text**.
+ :rtype: str
+ :returns: The sanitized **text**.
+ """
+ escaped = bytearray()
+
+ for byte in bytearray(text, encoding):
+ if byte in range(0, 32) + [92, 127]:
+ if replacement:
+ byte = replacement
+ else:
+ continue
+ escaped += bytearray([byte])
+
+ return str(escaped)
+
+def registerAdapter(adapter, adapted, interface):
+ """Register a Zope interface adapter for global use.
+
+ See :api:`twisted.python.components.registerAdapter` and the Twisted
+ Matrix Labs `howto documentation for components`_.
+
+ .. howto documentation for components:
+ https://twistedmatrix.com/documents/current/core/howto/components.html
+ """
+ try:
+ components.registerAdapter(adapter, adapted, interface)
+ except ValueError: # An adapter class was already registered
+ pass
+
+
+class JustifiedLogFormatter(logging.Formatter):
+ """A logging formatter which pretty prints thread and calling function
+ information, in addition to the normal timestamp, log level, and log
+ message.
+
+ :ivar int width: The width of the column for the calling function
+ information, if the latter is to be included.
+ """
+ width = 30
+
+ def __init__(self, logThreads=False, logTrace=False,
+ datefmt="%H:%M:%s"):
+ """If **logTrace** is ``True``, the line number, module name, and
+ function name where the logger was called will be included in the
+ message, and the width of this information will always equal ``width``.
+
+ :param bool logThreads: If ``True``, include the current thread name
+ and ID in formatted log messages.
+ :param bool logTrace: If ``True``, include information on the calling
+ function in formatted log messages.
+ """
+ super(JustifiedLogFormatter, self).__init__(datefmt=datefmt)
+ self.logThreads = logThreads
+ self.logTrace = logTrace
+
+ _fmt = ["%(asctime)s %(levelname)-7.7s"]
+ if self.logThreads:
+ _fmt.append("[%(threadName)s id:%(thread)d]")
+ _fmt.append("%(callingFunc)s")
+ _fmt.append("%(message)s")
+
+ self._fmt = " ".join(_fmt)
+
+ def _formatCallingFuncName(self, record):
+ """Format the combined module name and function name of the place where
+ the log message/record was recorded, so that the formatted string is
+ left-justified and not longer than the :cvar:`width`.
+
+ :type record: :class:`logging.LogRecord`
+ :param record: A record of an event created by calling a logger.
+ :returns: The :class:`logging.LogRecord` with its ``message``
+ attribute rewritten to contain the module and function name,
+ truncated to ``width``, or padded on the right with spaces as is
+ necessary.
+ """
+ callingFunc = ""
+ if self.logTrace:
+ # The '.' character between the module name and function name
+ # would otherwise be interpreted as a format string specifier, so
+ # we must specify ``chr(46)``:
+ lineno = "L%s:" % record.lineno
+ caller = "%s%-s%s" % (lineno.rjust(6), record.module, chr(46))
+ maxFuncNameWidth = self.width - 2 - len(caller)
+ funcName = record.funcName
+ if len(funcName) > maxFuncNameWidth:
+ funcName = record.funcName[:maxFuncNameWidth]
+ caller += "%s()" % (funcName)
+ callingFunc = caller.ljust(self.width)
+
+ record.callingFunc = callingFunc
+ return record
+
+ def format(self, record):
+ """Reformat this log **record** to neatly print thread and function
+ traces, if configured to do so.
+
+ :type record: :class:`logging.LogRecord`
+ :param record: A record of an event created by calling a logger.
+ """
+ record = self._formatCallingFuncName(record)
+ return super(JustifiedLogFormatter, self).format(record)
+
+
+class mixin:
+ """Subclasses of me can be used as a mixin class by registering another
+ class, ``ClassA``, which should be mixed with the ``mixin`` subclass, in
+ order to provide simple, less error-prone, multiple inheritance models::
+
+ >>> from __future__ import print_function
+ >>> from bridgedb.util import mixin
+ >>>
+ >>> class ClassA(object):
+ >>> def sayWhich(self):
+ >>> print("ClassA.sayWhich() called.")
+ >>> def doSuperThing(self):
+ >>> super(ClassA, self).__repr__()
+ >>> def doThing(self):
+ >>> print("ClassA is doing a thing.")
+ >>>
+ >>> class ClassB(ClassA):
+ >>> def sayWhich(self):
+ >>> print("ClassB.sayWhich() called.")
+ >>> def doSuperThing(self):
+ >>> super(ClassB, self).__repr__()
+ >>> def doOtherThing(self):
+ >>> print("ClassB is doing something else.")
+ >>>
+ >>> class ClassM(mixin):
+ >>> def sayWhich(self):
+ >>> print("ClassM.sayWhich() called.")
+ >>>
+ >>> ClassM.register(ClassA)
+ >>>
+ >>> class ClassC(ClassM, ClassB):
+ >>> def sayWhich(self):
+ >>> super(ClassC, self).sayWhich()
+ >>>
+ >>> c = ClassC()
+ >>> c.sayWhich()
+ ClassM.saywhich() called.
+ >>> c.doSuperThing()
+ <super: <class 'ClassA'>, NULL>
+ >>> c.doThing()
+ ClassA is doing a thing.
+ >>> c.doOtherThing()
+ ClassB is doing something else.
+
+ .. info:: This class' name is lowercased because pylint is hardcoded to
+ expect mixin classes to end in ``'mixin'``.
+ """
+ __metaclass__ = abc.ABCMeta
diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
deleted file mode 100644
index 7a237fe..0000000
--- a/lib/bridgedb/Bridges.py
+++ /dev/null
@@ -1,684 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Bridges -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""This module has low-level functionality for parsing bridges and arranging
-them into hashrings for distributors.
-"""
-
-import bisect
-import logging
-import re
-import hashlib
-import socket
-import time
-import ipaddr
-import random
-
-import bridgedb.Storage
-import bridgedb.Bucket
-
-from bridgedb.bridges import Bridge
-from bridgedb.crypto import getHMACFunc
-from bridgedb.parse import addr
-from bridgedb.parse.fingerprint import isValidFingerprint
-from bridgedb.safelog import logSafely
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from io import StringIO
-
-
-ID_LEN = 20 # XXX Only used in commented out line in Storage.py
-DIGEST_LEN = 20
-PORTSPEC_LEN = 16
-
-
-class BridgeRingParameters(object):
- """Store validated settings on minimum number of Bridges with certain
- attributes which should be included in any generated subring of a
- hashring.
-
- :ivar list needPorts: List of two-tuples of desired port numbers and their
- respective minimums.
- :ivar list needFlags: List of two-tuples of desired flags_ assigned to a
- Bridge by the Bridge DirAuth.
-
- .. _flags: https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt?id=6b557594ef#n1695
- """
-
- def __init__(self, needPorts=[], needFlags=[]):
- """Control the creation of subrings by including a minimum number of
- bridges which possess certain attributes.
-
- :type needPorts: iterable
- :param needPorts: An iterable of two-tuples. Each two tuple should
- contain ``(port, minimum)``, where ``port`` is an integer
- specifying a port number, and ``minimum`` is another integer
- specifying the minimum number of Bridges running on that ``port``
- to include in any new subring.
- :type needFlags: iterable
- :param needFlags: An iterable of two-tuples. Each two tuple should
- contain ``(flag, minimum)``, where ``flag`` is a string specifying
- an OR flag_, and ``minimum`` is an integer for the minimum number
- of Bridges which have acquired that ``flag`` to include in any new
- subring.
- :raises: An :exc:`TypeError` if an invalid port number, a minimum less
- than one, or an "unsupported" flag is given. "Stable" appears to
- be the only currently "supported" flag.
- """
- for port, count in needPorts:
- if not (1 <= port <= 65535):
- raise TypeError("Port %s out of range." % port)
- if count <= 0:
- raise TypeError("Count %s out of range." % count)
- for flag, count in needFlags:
- flag = flag.lower()
- if flag not in ["stable", "running",]:
- raise TypeError("Unsupported flag %s" % flag)
- if count <= 0:
- raise TypeError("Count %s out of range." % count)
-
- self.needPorts = needPorts[:]
- self.needFlags = [(flag.lower(), count) for flag, count in needFlags[:]]
-
-
-class BridgeRing(object):
- """Arranges bridges into a hashring based on an hmac function."""
-
- def __init__(self, key, answerParameters=None):
- """Create a new BridgeRing, using key as its hmac key.
-
- :type key: bytes
- :param key: The HMAC key, generated with
- :func:`~bridgedb.crypto.getKey`.
- :type answerParameters: :class:`BridgeRingParameters`
- :param answerParameters: DOCDOC
- :ivar dict bridges: A dictionary which maps HMAC keys to
- :class:`~bridgedb.bridges.Bridge`s.
- :ivar dict bridgesByID: A dictionary which maps raw hash digests of
- bridge ID keys to :class:`~bridgedb.bridges.Bridge`s.
- :type hmac: callable
- :ivar hmac: An HMAC function, which uses the **key** parameter to
- generate new HMACs for storing, inserting, and retrieving
- :class:`~bridgedb.bridges.Bridge`s within mappings.
- :ivar bool isSorted: ``True`` if ``sortedKeys`` is currently sorted.
- :ivar list sortedKeys: A sorted list of all of the HMACs.
- :ivar str name: A string which identifies this hashring, used mostly
- for differentiating this hashring in log messages, but it is also
- used for naming subrings. If this hashring is a subring, the
- ``name`` will include whatever distinguishing parameters
- differentiate that particular subring (i.e. ``'(port-443
- subring)'`` or ``'(Stable subring)'``)
- :type subrings: list
- :ivar subrings: A list of other ``BridgeRing``s, each of which
- contains bridges of a particular type. For example, a subring
- might contain only ``Bridge``s which have been given the "Stable"
- flag, or it might contain only IPv6 bridges. Each item in this
- list should be a 4-tuple::
-
- (type, value, count, ring)
-
- where:
-
- - ``type`` is a string which describes what kind of parameter is
- used to determine if a ``Bridge`` belongs in that subring,
- i.e. ``'port'`` or ``'flag'``.
-
- - ``value`` is a specific value pertaining to the ``type``,
- e.g. ``type='port'; value=443``.
-
- - ``count`` is an integer for the current total number of
- bridges in the subring.
-
- - ``ring`` is a :class:`BridgeRing`; it is the subhashring which
- contains ``count`` number of
- :class:`~bridgedb.bridges.Bridge`s of a certain ``type``.
- """
- self.bridges = {}
- self.bridgesByID = {}
- self.hmac = getHMACFunc(key, hex=False)
- self.isSorted = False
- self.sortedKeys = []
- if answerParameters is None:
- answerParameters = BridgeRingParameters()
- self.answerParameters = answerParameters
-
- self.subrings = []
- for port,count in self.answerParameters.needPorts:
- #note that we really need to use the same key here, so that
- # the mapping is in the same order for all subrings.
- self.subrings.append( ('port',port,count,BridgeRing(key,None)) )
- for flag,count in self.answerParameters.needFlags:
- self.subrings.append( ('flag',flag,count,BridgeRing(key,None)) )
-
- self.setName("Ring")
-
- def setName(self, name):
- """Tag a unique name to this hashring for identification.
-
- :param string name: The name for this hashring.
- """
- self.name = name
- for tp, val, _, subring in self.subrings:
- if tp == 'port':
- subring.setName("%s (port-%s subring)" % (name, val))
- else:
- subring.setName("%s (%s subring)" % (name, val))
-
- def __len__(self):
- """Get the number of unique bridges this hashring contains."""
- return len(self.bridges)
-
- def clear(self):
- """Remove all bridges and mappings from this hashring and subrings."""
- self.bridges = {}
- self.bridgesByID = {}
- self.isSorted = False
- self.sortedKeys = []
-
- for tp, val, count, subring in self.subrings:
- subring.clear()
-
- def insert(self, bridge):
- """Add a **bridge** to this hashring.
-
- The bridge's position in the hashring is dependent upon the HMAC of
- the raw hash digest of the bridge's ID key. The function used to
- generate the HMAC, :ivar:`BridgeRing.hmac`, is unique to each
- individual hashring.
-
- If the (presumably same) bridge is already at that determined position
- in this hashring, replace the old one.
-
- :type bridge: :class:`~bridgedb.Bridges.Bridge`
- :param bridge: The bridge to insert into this hashring.
- """
- for tp, val, _, subring in self.subrings:
- if tp == 'port':
- if val == bridge.orPort:
- subring.insert(bridge)
- else:
- assert tp == 'flag' and val == 'stable'
- if val == 'stable' and bridge.flags.stable:
- subring.insert(bridge)
-
- pos = self.hmac(bridge.identity)
- if not pos in self.bridges:
- self.sortedKeys.append(pos)
- self.isSorted = False
- self.bridges[pos] = bridge
- self.bridgesByID[bridge.identity] = bridge
- logging.debug("Adding %s to %s" % (bridge.address, self.name))
-
- def _sort(self):
- """Helper: put the keys in sorted order."""
- if not self.isSorted:
- self.sortedKeys.sort()
- self.isSorted = True
-
- def _getBridgeKeysAt(self, pos, N=1):
- """Bisect a list of bridges at a specified position, **pos**, and
- retrieve bridges from that point onwards, wrapping around the hashring
- if necessary.
-
- If the number of bridges requested, **N**, is larger that the size of
- this hashring, return the entire ring. Otherwise:
-
- 1. Sort this bridges in this hashring, if it is currently unsorted.
-
- 2. Bisect the sorted bridges. If the bridge at the desired position,
- **pos**, already exists within this hashring, the the bisection
- result is the bridge at position **pos**. Otherwise, the bisection
- result is the first position after **pos** which has a bridge
- assigned to it.
-
- 3. Try to obtain **N** bridges, starting at (and including) the
- bridge in the requested position, **pos**.
-
- a. If there aren't **N** bridges after **pos**, wrap back
- around to the beginning of the hashring and obtain bridges
- until we have **N** bridges.
-
- 4. Check that the number of bridges obtained is indeed **N**, then
- return them.
-
- :param bytes pos: The position to jump to. Any bridges returned will
- start at this position in the hashring, if there is a bridge
- assigned to that position. Otherwise, indexing will start at the
- first position after this one which has a bridge assigned to it.
- :param int N: The number of bridges to return.
- :rtype: list
- :returns: A list of :class:`~bridgedb.Bridges.Bridge`s.
- """
- assert len(pos) == DIGEST_LEN
- if N >= len(self.sortedKeys):
- return self.sortedKeys
- if not self.isSorted:
- self._sort()
- idx = bisect.bisect_left(self.sortedKeys, pos)
- r = self.sortedKeys[idx:idx+N]
- if len(r) < N:
- # wrap around as needed.
- r.extend(self.sortedKeys[:N - len(r)])
- assert len(r) == N
- return r
-
- def getBridges(self, pos, N=1):
- """Return **N** bridges appearing in this hashring after a position.
-
- :param bytes pos: The position to jump to. Any bridges returned will
- start at this position in the hashring, if there is a bridge
- assigned to that position. Otherwise, indexing will start at the
- first position after this one which has a bridge assigned to it.
- :param int N: The number of bridges to return.
- :rtype: list
- :returns: A list of :class:`~bridgedb.bridges.Bridge`s.
- """
- forced = []
- for _, _, count, subring in self.subrings:
- if len(subring) < count:
- count = len(subring)
- forced.extend(subring._getBridgeKeysAt(pos, count))
-
- keys = [ ]
- for k in forced + self._getBridgeKeysAt(pos, N):
- if k not in keys:
- keys.append(k)
- else:
- logging.debug(
- "Got duplicate bridge %r in main hashring for position %r."
- % (logSafely(k.encode('hex')), pos.encode('hex')))
- keys = keys[:N]
- keys.sort()
-
- #Do not return bridges from the same /16
- bridges = [ self.bridges[k] for k in keys ]
-
- return bridges
-
- def getBridgeByID(self, fp):
- """Return the bridge whose identity digest is fp, or None if no such
- bridge exists."""
- for _,_,_,subring in self.subrings:
- b = subring.getBridgeByID(fp)
- if b is not None:
- return b
-
- return self.bridgesByID.get(fp)
-
- def dumpAssignments(self, f, description=""):
- logging.info("Dumping bridge assignments for %s..." % self.name)
- for b in self.bridges.itervalues():
- desc = [ description ]
- for tp,val,_,subring in self.subrings:
- if subring.getBridgeByID(b.identity):
- desc.append("%s=%s"%(tp,val))
- f.write("%s %s\n" % (b.fingerprint, " ".join(desc).strip()))
-
-
-class FixedBridgeSplitter(object):
- """Splits bridges up based on an HMAC and assigns them to one of several
- subhashrings with equal probability.
- """
- def __init__(self, key, rings):
- self.hmac = getHMACFunc(key, hex=True)
- self.rings = rings[:]
-
- def insert(self, bridge):
- # Grab the first 4 bytes
- digest = self.hmac(bridge.identity)
- pos = long( digest[:8], 16 )
- which = pos % len(self.rings)
- self.rings[which].insert(bridge)
-
- def clear(self):
- """Clear all bridges from every ring in ``rings``."""
- for r in self.rings:
- r.clear()
-
- def __len__(self):
- """Returns the total number of bridges in all ``rings``."""
- total = 0
- for ring in self.rings:
- total += len(ring)
- return total
-
- def dumpAssignments(self, filename, description=""):
- """Write all bridges assigned to this hashring to ``filename``.
-
- :param string description: If given, include a description next to the
- index number of the ring from :attr:`FilteredBridgeSplitter.rings`
- the following bridges were assigned to. For example, if the
- description is ``"IPv6 obfs2 bridges"`` the line would read:
- ``"IPv6 obfs2 bridges ring=3"``.
- """
- for index, ring in zip(xrange(len(self.rings)), self.rings):
- ring.dumpAssignments(filename, "%s ring=%s" % (description, index))
-
-
-class UnallocatedHolder(object):
- """A pseudo-bridgeholder that ignores its bridges and leaves them
- unassigned.
- """
- def __init__(self):
- self.fingerprints = []
-
- def insert(self, bridge):
- logging.debug("Leaving %s unallocated", bridge.fingerprint)
- if not bridge.fingerprint in self.fingerprints:
- self.fingerprints.append(bridge.fingerprint)
-
- def __len__(self):
- return len(self.fingerprints)
-
- def clear(self):
- self.fingerprints = []
-
- def dumpAssignments(self, f, description=""):
- with bridgedb.Storage.getDB() as db:
- allBridges = db.getAllBridges()
- for bridge in allBridges:
- if bridge.hex_key not in self.fingerprints:
- continue
- dist = bridge.distributor
- desc = [ description ]
- if dist.startswith(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX):
- dist = dist.replace(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX, "")
- desc.append("bucket=%s" % dist)
- elif dist != "unallocated":
- continue
- f.write("%s %s\n" % (bridge.hex_key, " ".join(desc).strip()))
-
-
-class BridgeSplitter(object):
- """Splits incoming bridges up based on an HMAC, and assigns them to
- sub-bridgeholders with different probabilities. Bridge ââ BridgeSplitter
- associations are recorded in a store.
- """
- def __init__(self, key):
- self.hmac = getHMACFunc(key, hex=True)
- self.ringsByName = {}
- self.totalP = 0
- self.pValues = []
- self.rings = []
- self.pseudoRings = []
- self.statsHolders = []
-
- def __len__(self):
- n = 0
- for r in self.ringsByName.values():
- n += len(r)
- return n
-
- def addRing(self, ring, ringname, p=1):
- """Add a new subring.
-
- :param ring: The subring to add.
- :param str ringname: This is used to record which bridges have been
- assigned where in the store.
- :param int p: The relative proportion of bridges to assign to this
- bridgeholder.
- """
- self.ringsByName[ringname] = ring
- self.pValues.append(self.totalP)
- self.rings.append(ringname)
- self.totalP += p
-
- def addPseudoRing(self, ringname):
- """Add a pseudo ring to the list of pseudo rings.
- """
- self.pseudoRings.append(bridgedb.Bucket.PSEUDO_DISTRI_PREFIX + ringname)
-
- def addTracker(self, t):
- """Adds a statistics tracker that gets told about every bridge we see.
- """
- self.statsHolders.append(t)
-
- def clear(self):
- for r in self.ringsByName.values():
- r.clear()
-
- def insert(self, bridge):
- assert self.rings
-
- for s in self.statsHolders:
- s.insert(bridge)
-
- # The bridge must be running to insert it:
- if not bridge.flags.running:
- return
-
- # Determine which ring to put this bridge in if we haven't seen it
- # before.
- pos = self.hmac(bridge.identity)
- n = int(pos[:8], 16) % self.totalP
- pos = bisect.bisect_right(self.pValues, n) - 1
- assert 0 <= pos < len(self.rings)
- ringname = self.rings[pos]
- logging.info("%s placing bridge %s into hashring %s (via n=%s, pos=%s)."
- % (self.__class__.__name__, bridge, ringname, n, pos))
-
- validRings = self.rings + self.pseudoRings
-
- with bridgedb.Storage.getDB() as db:
- ringname = db.insertBridgeAndGetRing(bridge, ringname, time.time(),
- validRings)
- db.commit()
-
- # Pseudo distributors are always held in the "unallocated" ring
- if ringname in self.pseudoRings:
- ringname = "unallocated"
-
- ring = self.ringsByName.get(ringname)
- ring.insert(bridge)
-
- def dumpAssignments(self, f, description=""):
- for name,ring in self.ringsByName.iteritems():
- ring.dumpAssignments(f, "%s %s" % (description, name))
-
-
-class FilteredBridgeSplitter(object):
- """Places bridges into subrings based upon sets of filters.
-
- The set of subrings and conditions used to assign :class:`Bridge`s should
- be passed to :meth:`~FilteredBridgeSplitter.addRing`.
- """
-
- def __init__(self, key, max_cached_rings=3):
- """Create a hashring which filters bridges into sub hashrings.
-
- :type key: DOCDOC
- :param key: An HMAC key.
- :param int max_cached_rings: XXX max_cached_rings appears to not be
- used anywhere.
-
- :ivar filterRings: A dictionary of subrings which has the form
- ``{ringname: (filterFn, subring)}``, where:
- - ``ringname`` is a unique string identifying the subring.
- - ``filterFn`` is a callable which filters Bridges in some
- manner, i.e. by whether they are IPv4 or IPv6, etc.
- - ``subring`` is any of the horribly-implemented,
- I-guess-it-passes-for-some-sort-of-hashring classes in this
- module.
- :ivar hmac: DOCDOC
- :ivar bridges: DOCDOC
- :type distributorName: str
- :ivar distributorName: The name of this splitter's distributor. See
- :meth:`~bridgedb.https.distributor.HTTPSDistributor.setDistributorName`.
- """
- self.key = key
- self.filterRings = {}
- self.hmac = getHMACFunc(key, hex=True)
- self.bridges = []
- self.distributorName = ''
-
- #XXX: unused
- self.max_cached_rings = max_cached_rings
-
- def __len__(self):
- return len(self.bridges)
-
- def clear(self):
- self.bridges = []
- self.filterRings = {}
-
- def insert(self, bridge):
- """Insert a bridge into all appropriate sub-hashrings.
-
- For all sub-hashrings, the ``bridge`` will only be added iff it passes
- the filter functions for that sub-hashring.
-
- :type bridge: :class:`~bridgedb.Bridges.Bridge`
- :param bridge: The bridge to add.
- """
- # The bridge must be running to insert it:
- if not bridge.flags.running:
- logging.warn(("Skipping hashring insertion for non-running "
- "bridge: %s") % bridge)
- return
-
- index = 0
- logging.debug("Inserting %s into hashring..." % bridge)
- for old_bridge in self.bridges[:]:
- if bridge.fingerprint == old_bridge.fingerprint:
- self.bridges[index] = bridge
- break
- index += 1
- else:
- self.bridges.append(bridge)
- for ringname, (filterFn, subring) in self.filterRings.items():
- if filterFn(bridge):
- subring.insert(bridge)
- logging.debug("Inserted bridge %s into %s subhashring." %
- (bridge, ringname))
-
- def extractFilterNames(self, ringname):
- """Get the names of the filters applied to a particular sub hashring.
-
- :param str ringname: A unique name identifying a sub hashring.
- :rtype: list
- :returns: A sorted list of strings, all the function names of the
- filters applied to the sub hashring named **ringname**.
- """
- filterNames = []
-
- for filterName in [x.func_name for x in list(ringname)]:
- # Using `assignBridgesToSubring.func_name` gives us a messy
- # string which includes all parameters and memory addresses. Get
- # rid of this by partitioning at the first `(`:
- realFilterName = filterName.partition('(')[0]
- filterNames.append(realFilterName)
-
- filterNames.sort()
- return filterNames
-
- def addRing(self, subring, ringname, filterFn, populate_from=None):
- """Add a subring to this hashring.
-
- :param subring: The subring to add.
- :param str ringname: A unique name for identifying the new subring.
- :param filterFn: A function whose input is a :class:`Bridge`, and
- returns True/False based on some filtration criteria.
- :type populate_from: iterable or None
- :param populate_from: A group of :class:`Bridge`s. If given, the newly
- added subring will be populated with these bridges.
- :rtype: bool
- :returns: False if there was a problem adding the subring, True
- otherwise.
- """
- # XXX I think subring and ringname are switched in this function, or
- # at least that whatever is passed into this function as as the
- # `ringname` parameter from somewhere else is odd; for example, with
- # the original code, which was `log.debug("Inserted %d bridges into
- # hashring '%s'!" % (inserted, ringname))`, this log message appears:
- #
- # Jan 04 23:18:37 [INFO] Inserted 12 bridges into hashring
- # frozenset([<function byIPv4 at 0x2d67cf8>, <function
- # assignBridgesToSubring(<function hmac_fn at 0x3778398>, 4, 0) at
- # 0x37de578>])!
- #
- # I suppose since it contains memory addresses, it *is* technically
- # likely to be a unique string, but it is messy.
-
- if ringname in self.filterRings.keys():
- logging.fatal("%s hashring already has a subring named '%s'!"
- % (self.distributorName, ringname))
- return False
-
- filterNames = self.extractFilterNames(ringname)
- subringName = [self.distributorName]
- subringNumber = None
- for filterName in filterNames:
- if filterName.startswith('assignBridgesToSubring'):
- subringNumber = filterName.lstrip('assignBridgesToSubring')
- else:
- subringName.append(filterName.lstrip('by'))
- if subring.name and 'Proxy' in subring.name:
- subringName.append('Proxy')
- elif subringNumber:
- subringName.append(subringNumber)
- subringName = '-'.join([x for x in subringName])
- subring.setName(subringName)
-
- logging.info("Adding %s subring %s to the %s Distributor's hashring..." %
- (subring.name, subringNumber, self.distributorName))
- logging.info(" Subring filters: %s" % filterNames)
-
- #TODO: drop LRU ring if len(self.filterRings) > self.max_cached_rings
- self.filterRings[ringname] = (filterFn, subring)
-
- if populate_from:
- inserted = 0
- for bridge in populate_from:
- if isinstance(bridge, Bridge) and filterFn(bridge):
- subring.insert(bridge)
- inserted += 1
- logging.info("Bridges inserted into %s subring %s: %d"
- % (subring.name, subringNumber, inserted))
-
- return True
-
- def dumpAssignments(self, f, description=""):
- # one ring per filter set
- # bridges may be present in multiple filter sets
- # only one line should be dumped per bridge
-
- for b in self.bridges:
- # gather all the filter descriptions
- desc = []
- for n,(g,r) in self.filterRings.items():
- if g(b):
- # ghetto. get subring flags, ports
- for tp,val,_,subring in r.subrings:
- if subring.getBridgeByID(b.identity):
- desc.append("%s=%s"%(tp,val))
- try:
- desc.extend(g.description.split())
- except TypeError:
- desc.append(g.description)
-
- # add transports
- logging.debug("%s supports %d transports" % (b, len(b.transports)))
- for transport in b.transports:
- desc.append("transport=%s"%(transport.methodname))
-
- # dedupe and group
- desc = set(desc)
- grouped = dict()
- for kw in desc:
- l,r = kw.split('=')
- try:
- grouped[l] = "%s,%s"%(grouped[l],r)
- except KeyError:
- grouped[l] = kw
-
- # add to assignments
- desc = "%s %s" % (description.strip(),
- " ".join([v for k,v in grouped.items()]).strip())
- f.write("%s %s\n" % (b.fingerprint, desc))
diff --git a/lib/bridgedb/Bucket.py b/lib/bridgedb/Bucket.py
deleted file mode 100644
index 1382188..0000000
--- a/lib/bridgedb/Bucket.py
+++ /dev/null
@@ -1,288 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-This module is responsible for everything concerning file bucket bridge
-distribution. File bucket bridge distribution means that unallocated bridges
-are allocated to a certain pseudo-distributor and later written to a file.
-
-For example, the following is a dict of pseudo-distributors (also called
-'bucket identifiers') with numbers of bridges assigned to them:
-
- FILE_BUCKETS = { "name1": 10, "name2": 15, "foobar": 3 }
-
-This configuration for buckets would result in 3 files being created for bridge
-distribution: name1-2010-07-17.brdgs, name2-2010-07-17.brdgs and
-foobar-2010-07-17.brdgs. The first file would contain 10 bridges from BridgeDB's
-'unallocated' pool. The second file would contain 15 bridges from the same pool
-and the third one similarly 3 bridges. These files can then be handed out to
-trusted parties via mail or fed to other distribution mechanisms such as
-twitter.
-
-Note that in BridgeDB slang, the _distributor_ would still be 'unallocated',
-even though in the database, there would now by 'name1', 'name2' or 'foobar'
-instead of 'unallocated'. This is why they are called pseudo-distributors.
-"""
-
-import logging
-import time
-import bridgedb.Storage
-import bridgedb.Bridges
-import binascii
-import sqlite3
-from gettext import gettext as _
-toHex = binascii.b2a_hex
-
-
-# What should pseudo distributors be prefixed with in the database so we can
-# distinguish them from real distributors?
-PSEUDO_DISTRI_PREFIX = "pseudo_"
-
-# Set to rediculously high number
-BUCKET_MAX_BRIDGES = 1000000
-
-
-class BucketData(object):
- """Configures a bridge bucket with the number of bridges which should be
- allocated, the name of the bucket, and other similar data.
-
- :param str name: The name of this bucket (from the config file). This will
- be prefixed by the :data:`PSEUDO_DISTRIBUTOR_PREFIX`.
- :type needed: str or int
- :param needed: The number of bridges needed for this bucket (also from the
- config file).
- :param int allocated: Number of bridges already allocated for this bucket.
- """
- def __init__(self, name, needed):
- self.name = name
- if needed == "*":
- needed = BUCKET_MAX_BRIDGES
- self.needed = int(needed)
- self.allocated = 0
-
-class BucketManager(object):
- """BucketManager reads a number of file bucket identifiers from the config.
-
- They're expected to be in the following format::
-
- FILE_BUCKETS = { "name1": 10, "name2": 15, "foobar": 3 }
-
- This syntax means that certain buckets ("name1", "name2" and so on) are
- given a number of bridges (10, 15 and so on). Names can be anything. The
- name will later be the prefix of the file that is written with the
- assigned number of bridges in it. Instead of a number, a wildcard item
- ("*") is allowed, too. This means that the corresponsing bucket file will
- get the maximum number of possible bridges (as many as are left in the
- unallocated bucket).
-
- The files will be written in ip:port format, one bridge per line.
-
- The way this works internally is as follows:
-
- First of all, the assignBridgesToBuckets() routine runs through the
- database of bridges and looks up the 'distributor' field of each
- bridge. Unallocated bridges are sent to a pool for later assignement.
- Already allocated bridges for file bucket distribution are sorted and
- checked. They're checked for whether their bucket identifier still exists
- in the current config and also whether the number of assigned bridges is
- still valid. If either the bucket identifier is not existing anymore or
- too many bridges are currently assigned to it, bridges will go to the
- unassigned pool.
-
- In the second step, after bridges are sorted and the unassigned pool is
- ready, the assignBridgesToBuckets() routine assigns one bridge from the
- unassigned pool to a known bucket identifier at a time until it either
- runs out of bridges in the unallocated pool or the number of needed
- bridges for that bucket is reached.
-
- When all bridges are assigned in this way, they can then be dumped into
- files by calling the dumpBridges() routine.
-
- :type cfg: :class:`bridgedb.persistent.Conf`
- :ivar cfg: The central configuration instance.
- :ivar list bucketList: A list of BucketData instances, holding all
- configured (and thus requested) buckets with their respective numbers.
- :ivar list unallocatedList: Holds all bridges from the 'unallocated' pool.
- :ivar bool unallocated_available: Is at least one unallocated bridge
- available?
- :ivar str distributor_prefix: The 'distributor' field in the database will
- hold the name of our pseudo-distributor, prefixed by this string. By
- default, this uses :data:`PSEUDO_DISTRIBUTOR_PREFIX`.
- :ivar db: The bridge database instance.
- """
-
- def __init__(self, cfg):
- """Create a ``BucketManager``.
-
- :type cfg: :class:`bridgedb.persistent.Conf`
- :param cfg: The central configuration instance.
- """
- self.cfg = cfg
- self.bucketList = []
- self.unallocatedList = []
- self.unallocated_available = False
- self.distributor_prefix = PSEUDO_DISTRI_PREFIX
-
- def addToUnallocatedList(self, hex_key):
- """Add a bridge by **hex_key** into the unallocated pool."""
- with bridgedb.Storage.getDB() as db:
- try:
- db.updateDistributorForHexKey("unallocated", hex_key)
- except:
- db.rollback()
- raise
- else:
- db.commit()
- self.unallocatedList.append(hex_key)
- self.unallocated_available = True
-
- def getBucketByIdent(self, bucketIdent):
- """If we know this bucket identifier, then return the corresponding
- :class:`BucketData` object.
- """
- for d in self.bucketList:
- if d.name == bucketIdent:
- return d
- return None
-
- def assignUnallocatedBridge(self, bucket):
- """Assign an unallocated bridge to a certain **bucket**."""
- hex_key = self.unallocatedList.pop()
- # Mark pseudo-allocators in the database as such
- allocator_name = bucket.name
- #print "KEY: %d NAME: %s" % (hex_key, allocator_name)
- logging.debug("Moving %s to %s" % (hex_key, allocator_name))
- with bridgedb.Storage.getDB() as db:
- try:
- db.updateDistributorForHexKey(allocator_name, hex_key)
- except:
- db.rollback()
- logging.warn("Failed to move %s to new distributor (%s)"
- % (hex_key, allocator_name))
-
- # Ok, this seems useless, but for consistancy's sake, we'll
- # re-assign the bridge from this missed db update attempt to the
- # unallocated list. Remember? We pop()'d it before.
- self.addToUnallocatedList(hex_key)
- raise
- else:
- db.commit()
- bucket.allocated += 1
- if len(self.unallocatedList) < 1:
- self.unallocated_available = False
- return True
-
- def assignBridgesToBuckets(self):
- """Read file bucket identifiers from the configuration, sort them, and
- write necessary changes to the database.
- """
- logging.debug("Assigning bridges to buckets for pseudo-distributors")
- # Build distributor list
- for k, v in self.cfg.FILE_BUCKETS.items():
- prefixed_key = self.distributor_prefix + k
- d = BucketData(prefixed_key, v)
- self.bucketList.append(d)
-
- # Loop through all bridges and sort out distributors
- with bridgedb.Storage.getDB() as db:
- allBridges = db.getAllBridges()
- for bridge in allBridges:
- if bridge.distributor == "unallocated":
- self.addToUnallocatedList(bridge.hex_key)
- continue
-
- # Filter non-pseudo distributors (like 'https' and 'email') early,
- # too
- if not bridge.distributor.startswith(self.distributor_prefix):
- continue
-
- # Return the bucket in case we know it already
- d = self.getBucketByIdent(bridge.distributor)
- if d is not None:
- # Does this distributor need another bridge? If not, re-inject
- # it into the 'unallocated' pool for for later assignment
- if d.allocated < d.needed:
- d.allocated += 1
- else:
- # Bucket has enough members already, free this one
- self.addToUnallocatedList(bridge.hex_key)
- # We don't know it. Maybe an old entry. Free it.
- else:
- self.addToUnallocatedList(bridge.hex_key)
-
- # Loop through bucketList while we have and need unallocated
- # bridges, assign one bridge at a time
- while self.unallocated_available and len(self.bucketList) > 0:
- logging.debug("We have %d unallocated bridges and %d buckets to " \
- "fill. Let's do it."
- % (len(self.unallocatedList), len(self.bucketList)))
- for d in self.bucketList:
- if d.allocated < d.needed:
- try:
- if not self.assignUnallocatedBridge(d):
- break
- except sqlite3.DatabaseError as e:
- dist = d.name.replace(self.distributor_prefix, "")
- logging.warn("Couldn't assign unallocated bridge to " \
- "%s: %s" % (dist, e))
- else:
- # When we have enough bridges, remove bucket identifier
- # from list
- self.bucketList.remove(d)
-
- def dumpBridgesToFile(self, filename, bridges):
- """Dump a list of given **bridges** into **filename**."""
- logging.debug("Dumping bridge assignments to file: %r" % filename)
- # get the bridge histories and sort by Time On Same Address
- bridgeHistories = []
- with bridgedb.Storage.getDB() as db:
- for b in bridges:
- if self.cfg.COLLECT_TIMESTAMPS:
- bh = db.getBridgeHistory(b.hex_key)
- if bh: bridgeHistories.append(bh)
- bridgeHistories.sort(lambda x,y: cmp(x.weightedFractionalUptime,
- y.weightedFractionalUptime))
-
- try:
- f = open(filename, 'w')
- if self.cfg.COLLECT_TIMESTAMPS:
- for bh in bridgeHistories:
- days = bh.tosa / long(60*60*24)
- line = "%s:%s\t(%d days at this address)" % \
- (bh.ip, bh.port, days)
- if str(bh.fingerprint) in blocklist.keys():
- line = line + "\t(Might be blocked): (%s)" % \
- ",".join(blocklist[bh.fingerprint])
- f.write(line + '\n')
- else:
- for bridge in bridges:
- line = "%s:%d %s" \
- % (bridge.address, bridge.or_port, bridge.hex_key)
- f.write(line + '\n')
- f.close()
- except IOError:
- print "I/O error: %s" % filename
-
- def dumpBridges(self):
- """Dump all known file distributors to files, sorted by distributor."""
- logging.info("Dumping all distributors to file.")
- with bridgedb.Storage.getDB() as db:
- allBridges = db.getAllBridges()
- bridgeDict = {}
- # Sort returned bridges by distributor
- for bridge in allBridges:
- dist = str(bridge.distributor)
- if dist in bridgeDict.keys():
- bridgeDict[dist].append(bridge)
- else:
- bridgeDict[dist] = [bridge]
-
- # Now dump to file(s)
- for k in bridgeDict.keys():
- dist = k
- if (dist.startswith(self.distributor_prefix)):
- # Subtract the pseudo distributor prefix
- dist = dist.replace(self.distributor_prefix, "")
- # Be safe. Replace all '/' in distributor names
- dist = dist.replace("/", "_")
- filename = dist + "-" + time.strftime("%Y-%m-%d") + ".brdgs"
- self.dumpBridgesToFile(filename, bridgeDict[k])
diff --git a/lib/bridgedb/Main.py b/lib/bridgedb/Main.py
deleted file mode 100644
index b281d21..0000000
--- a/lib/bridgedb/Main.py
+++ /dev/null
@@ -1,507 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Main -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see the AUTHORS file for attributions
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2007-2015, Nick Mathewson
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""This module sets up BridgeDB and starts the servers running."""
-
-import logging
-import os
-import signal
-import sys
-import time
-
-from twisted.internet import reactor
-from twisted.internet import task
-
-from bridgedb import crypto
-from bridgedb import persistent
-from bridgedb import proxy
-from bridgedb import util
-from bridgedb.bridges import MalformedBridgeInfo
-from bridgedb.bridges import MissingServerDescriptorDigest
-from bridgedb.bridges import ServerDescriptorDigestMismatch
-from bridgedb.bridges import ServerDescriptorWithoutNetworkstatus
-from bridgedb.bridges import Bridge
-from bridgedb.configure import loadConfig
-from bridgedb.email.distributor import EmailDistributor
-from bridgedb.https.distributor import HTTPSDistributor
-from bridgedb.parse import descriptors
-
-import bridgedb.Storage
-
-from bridgedb import Bridges
-from bridgedb.Stability import updateBridgeHistory
-
-
-def load(state, hashring, clear=False):
- """Read and parse all descriptors, and load into a bridge hashring.
-
- Read all the appropriate bridge files from the saved
- :class:`~bridgedb.persistent.State`, parse and validate them, and then
- store them into our ``state.hashring`` instance. The ``state`` will be
- saved again at the end of this function.
-
- :type hashring: :class:`~bridgedb.Bridges.BridgeSplitter`
- :param hashring: A class which provides a mechanism for HMACing
- Bridges in order to assign them to hashrings.
- :param boolean clear: If True, clear all previous bridges from the
- hashring before parsing for new ones.
- """
- if not state:
- logging.fatal("bridgedb.Main.load() could not retrieve state!")
- sys.exit(2)
-
- if clear:
- logging.info("Clearing old bridges...")
- hashring.clear()
-
- logging.info("Loading bridges...")
-
- ignoreNetworkstatus = state.IGNORE_NETWORKSTATUS
- if ignoreNetworkstatus:
- logging.info("Ignoring BridgeAuthority networkstatus documents.")
-
- bridges = {}
- timestamps = {}
-
- logging.info("Opening networkstatus file: %s" % state.STATUS_FILE)
- networkstatuses = descriptors.parseNetworkStatusFile(state.STATUS_FILE)
- logging.debug("Closing networkstatus file: %s" % state.STATUS_FILE)
-
- logging.info("Processing networkstatus descriptors...")
- for router in networkstatuses:
- bridge = Bridge()
- bridge.updateFromNetworkStatus(router, ignoreNetworkstatus)
- try:
- bridge.assertOK()
- except MalformedBridgeInfo as error:
- logging.warn(str(error))
- else:
- bridges[bridge.fingerprint] = bridge
-
- for filename in state.BRIDGE_FILES:
- logging.info("Opening bridge-server-descriptor file: '%s'" % filename)
- serverdescriptors = descriptors.parseServerDescriptorsFile(filename)
- logging.debug("Closing bridge-server-descriptor file: '%s'" % filename)
-
- for router in serverdescriptors:
- try:
- bridge = bridges[router.fingerprint]
- except KeyError:
- logging.warn(
- ("Received server descriptor for bridge '%s' which wasn't "
- "in the networkstatus!") % router.fingerprint)
- if ignoreNetworkstatus:
- bridge = Bridge()
- else:
- continue
-
- try:
- bridge.updateFromServerDescriptor(router, ignoreNetworkstatus)
- except (ServerDescriptorWithoutNetworkstatus,
- MissingServerDescriptorDigest,
- ServerDescriptorDigestMismatch) as error:
- logging.warn(str(error))
- # Reject any routers whose server descriptors didn't pass
- # :meth:`~bridges.Bridge._checkServerDescriptor`, i.e. those
- # bridges who don't have corresponding networkstatus
- # documents, or whose server descriptor digests don't check
- # out:
- bridges.pop(router.fingerprint)
- continue
-
- if state.COLLECT_TIMESTAMPS:
- # Update timestamps from server descriptors, not from network
- # status descriptors (because networkstatus documents and
- # descriptors aren't authenticated in any way):
- if bridge.fingerprint in timestamps.keys():
- timestamps[bridge.fingerprint].append(router.published)
- else:
- timestamps[bridge.fingerprint] = [router.published]
-
- extrainfos = descriptors.parseExtraInfoFiles(*state.EXTRA_INFO_FILES)
- for fingerprint, router in extrainfos.items():
- try:
- bridges[fingerprint].updateFromExtraInfoDescriptor(router)
- except MalformedBridgeInfo as error:
- logging.warn(str(error))
- except KeyError as error:
- logging.warn(("Received extrainfo descriptor for bridge '%s', "
- "but could not find bridge with that fingerprint.")
- % router.fingerprint)
-
- inserted = 0
- logging.info("Inserting %d bridges into hashring..." % len(bridges))
- for fingerprint, bridge in bridges.items():
- # Skip insertion of bridges which are geolocated to be in one of the
- # NO_DISTRIBUTION_COUNTRIES, a.k.a. the countries we don't distribute
- # bridges from:
- if bridge.country in state.NO_DISTRIBUTION_COUNTRIES:
- logging.warn("Not distributing Bridge %s %s:%s in country %s!" %
- (bridge, bridge.address, bridge.orPort, bridge.country))
- else:
- # If the bridge is not running, then it is skipped during the
- # insertion process.
- hashring.insert(bridge)
- inserted += 1
- logging.info("Done inserting %d bridges into hashring." % inserted)
-
- if state.COLLECT_TIMESTAMPS:
- reactor.callInThread(updateBridgeHistory, bridges, timestamps)
-
- state.save()
-
-def _reloadFn(*args):
- """Placeholder callback function for :func:`_handleSIGHUP`."""
- return True
-
-def _handleSIGHUP(*args):
- """Called when we receive a SIGHUP; invokes _reloadFn."""
- reactor.callInThread(_reloadFn)
-
-def _handleSIGUSR1(*args):
- """Handler for SIGUSR1. Calls :func:`~bridgedb.runner.doDumpBridges`."""
- logging.debug("Caught SIGUSR1 signal")
-
- from bridgedb import runner
-
- logging.info("Loading saved state...")
- state = persistent.load()
- cfg = loadConfig(state.CONFIG_FILE, state.config)
-
- logging.info("Dumping bridge assignments to files...")
- reactor.callInThread(runner.doDumpBridges, cfg)
-
-def replaceBridgeRings(current, replacement):
- """Replace the current thing with the new one"""
- current.hashring = replacement.hashring
-
-def createBridgeRings(cfg, proxyList, key):
- """Create the bridge distributors defined by the config file
-
- :type cfg: :class:`Conf`
- :param cfg: The current configuration, including any in-memory settings
- (i.e. settings whose values were not obtained from the config file,
- but were set via a function somewhere)
- :type proxyList: :class:`~bridgedb.proxy.ProxySet`
- :param proxyList: The container for the IP addresses of any currently
- known open proxies.
- :param bytes key: Hashring master key
- :rtype: tuple
- :returns: A BridgeSplitter hashring, an
- :class:`~bridgedb.https.distributor.HTTPSDistributor` or None, and an
- :class:`~bridgedb.email.distributor.EmailDistributor` or None.
- """
- # Create a BridgeSplitter to assign the bridges to the different
- # distributors.
- hashring = Bridges.BridgeSplitter(crypto.getHMAC(key, "Hashring-Key"))
- logging.debug("Created hashring: %r" % hashring)
-
- # Create ring parameters.
- ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
- needFlags=cfg.FORCE_FLAGS)
-
- emailDistributor = ipDistributor = None
- # As appropriate, create an IP-based distributor.
- if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
- logging.debug("Setting up HTTPS Distributor...")
- ipDistributor = HTTPSDistributor(
- cfg.N_IP_CLUSTERS,
- crypto.getHMAC(key, "HTTPS-IP-Dist-Key"),
- proxyList,
- answerParameters=ringParams)
- hashring.addRing(ipDistributor.hashring, "https", cfg.HTTPS_SHARE)
-
- # As appropriate, create an email-based distributor.
- if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
- logging.debug("Setting up Email Distributor...")
- emailDistributor = EmailDistributor(
- crypto.getHMAC(key, "Email-Dist-Key"),
- cfg.EMAIL_DOMAIN_MAP.copy(),
- cfg.EMAIL_DOMAIN_RULES.copy(),
- answerParameters=ringParams,
- whitelist=cfg.EMAIL_WHITELIST.copy())
- hashring.addRing(emailDistributor.hashring, "email", cfg.EMAIL_SHARE)
-
- # As appropriate, tell the hashring to leave some bridges unallocated.
- if cfg.RESERVED_SHARE:
- hashring.addRing(Bridges.UnallocatedHolder(),
- "unallocated",
- cfg.RESERVED_SHARE)
-
- # Add pseudo distributors to hashring
- for pseudoRing in cfg.FILE_BUCKETS.keys():
- hashring.addPseudoRing(pseudoRing)
-
- return hashring, emailDistributor, ipDistributor
-
-def run(options, reactor=reactor):
- """This is BridgeDB's main entry point and main runtime loop.
-
- Given the parsed commandline options, this function handles locating the
- configuration file, loading and parsing it, and then either (re)parsing
- plus (re)starting the servers, or dumping bridge assignments to files.
-
- :type options: :class:`bridgedb.parse.options.MainOptions`
- :param options: A pre-parsed options class containing any arguments and
- options given in the commandline we were called with.
- :type state: :class:`bridgedb.persistent.State`
- :ivar state: A persistent state object which holds config changes.
- :param reactor: An implementer of
- :api:`twisted.internet.interfaces.IReactorCore`. This parameter is
- mainly for testing; the default
- :api:`twisted.internet.epollreactor.EPollReactor` is fine for normal
- application runs.
- """
- # Change to the directory where we're supposed to run. This must be done
- # before parsing the config file, otherwise there will need to be two
- # copies of the config file, one in the directory BridgeDB is started in,
- # and another in the directory it changes into.
- os.chdir(options['rundir'])
- if options['verbosity'] <= 10: # Corresponds to logging.DEBUG
- print("Changed to runtime directory %r" % os.getcwd())
-
- config = loadConfig(options['config'])
- config.RUN_IN_DIR = options['rundir']
-
- # Set up logging as early as possible. We cannot import from the bridgedb
- # package any of our modules which import :mod:`logging` and start using
- # it, at least, not until :func:`safelog.configureLogging` is
- # called. Otherwise a default handler that logs to the console will be
- # created by the imported module, and all further calls to
- # :func:`logging.basicConfig` will be ignored.
- util.configureLogging(config)
-
- if options['dump-bridges'] or (options.subCommand is not None):
- runSubcommand(options, config)
-
- # Write the pidfile only after any options.subCommands are run (because
- # these exit when they are finished). Otherwise, if there is a subcommand,
- # the real PIDFILE would get overwritten with the PID of the temporary
- # bridgedb process running the subcommand.
- if config.PIDFILE:
- logging.debug("Writing server PID to file: '%s'" % config.PIDFILE)
- with open(config.PIDFILE, 'w') as pidfile:
- pidfile.write("%s\n" % os.getpid())
- pidfile.flush()
-
- from bridgedb import persistent
-
- state = persistent.State(config=config)
-
- from bridgedb.email.server import addServer as addSMTPServer
- from bridgedb.https.server import addWebServer
-
- # Load the master key, or create a new one.
- key = crypto.getKey(config.MASTER_KEY_FILE)
- proxies = proxy.ProxySet()
- emailDistributor = None
- ipDistributor = None
-
- # Save our state
- state.proxies = proxies
- state.key = key
- state.save()
-
- def reload(inThread=True):
- """Reload settings, proxy lists, and bridges.
-
- State should be saved before calling this method, and will be saved
- again at the end of it.
-
- The internal variables, ``cfg``, ``hashring``, ``proxyList``,
- ``ipDistributor``, and ``emailDistributor`` are all taken from a
- :class:`~bridgedb.persistent.State` instance, which has been saved to
- a statefile with :meth:`bridgedb.persistent.State.save`.
-
- :type cfg: :class:`Conf`
- :ivar cfg: The current configuration, including any in-memory
- settings (i.e. settings whose values were not obtained from the
- config file, but were set via a function somewhere)
- :type hashring: A :class:`~bridgedb.Bridges.BridgeSplitter`
- :ivar hashring: A class which takes an HMAC key and splits bridges
- into their hashring assignments.
- :type proxyList: :class:`~bridgedb.proxy.ProxySet`
- :ivar proxyList: The container for the IP addresses of any currently
- known open proxies.
- :ivar ipDistributor: A
- :class:`~bridgedb.https.distributor.HTTPSDistributor`.
- :ivar emailDistributor: A
- :class:`~bridgedb.email.distributor.EmailDistributor`.
- :ivar dict tasks: A dictionary of ``{name: task}``, where name is a
- string to associate with the ``task``, and ``task`` is some
- scheduled event, repetitive or otherwise, for the :class:`reactor
- <twisted.internet.epollreactor.EPollReactor>`. See the classes
- within the :api:`twisted.internet.tasks` module.
- """
- logging.debug("Caught SIGHUP")
- logging.info("Reloading...")
-
- logging.info("Loading saved state...")
- state = persistent.load()
- cfg = loadConfig(state.CONFIG_FILE, state.config)
- logging.info("Updating any changed settings...")
- state.useChangedSettings(cfg)
-
- level = getattr(state, 'LOGLEVEL', 'WARNING')
- logging.info("Updating log level to: '%s'" % level)
- level = getattr(logging, level)
- logging.getLogger().setLevel(level)
-
- logging.info("Reloading the list of open proxies...")
- for proxyfile in cfg.PROXY_LIST_FILES:
- logging.info("Loading proxies from: %s" % proxyfile)
- proxy.loadProxiesFromFile(proxyfile, state.proxies, removeStale=True)
-
- logging.info("Reparsing bridge descriptors...")
- (hashring,
- emailDistributorTmp,
- ipDistributorTmp) = createBridgeRings(cfg, state.proxies, key)
- logging.info("Bridges loaded: %d" % len(hashring))
-
- # Initialize our DB.
- bridgedb.Storage.initializeDBLock()
- bridgedb.Storage.setDBFilename(cfg.DB_FILE + ".sqlite")
- load(state, hashring, clear=False)
-
- if emailDistributorTmp is not None:
- emailDistributorTmp.prepopulateRings() # create default rings
- logging.info("Bridges allotted for %s distribution: %d"
- % (emailDistributorTmp.name,
- len(emailDistributorTmp.hashring)))
- else:
- logging.warn("No email distributor created!")
-
- if ipDistributorTmp is not None:
- ipDistributorTmp.prepopulateRings() # create default rings
-
- logging.info("Bridges allotted for %s distribution: %d"
- % (ipDistributorTmp.name,
- len(ipDistributorTmp.hashring)))
- logging.info("\tNum bridges:\tFilter set:")
-
- nSubrings = 0
- ipSubrings = ipDistributorTmp.hashring.filterRings
- for (ringname, (filterFn, subring)) in ipSubrings.items():
- nSubrings += 1
- filterSet = ' '.join(
- ipDistributorTmp.hashring.extractFilterNames(ringname))
- logging.info("\t%2d bridges\t%s" % (len(subring), filterSet))
-
- logging.info("Total subrings for %s: %d"
- % (ipDistributorTmp.name, nSubrings))
- else:
- logging.warn("No HTTP(S) distributor created!")
-
- # Dump bridge pool assignments to disk.
- try:
- logging.debug("Dumping pool assignments to file: '%s'"
- % state.ASSIGNMENTS_FILE)
- fh = open(state.ASSIGNMENTS_FILE, 'a')
- fh.write("bridge-pool-assignment %s\n" %
- time.strftime("%Y-%m-%d %H:%M:%S"))
- hashring.dumpAssignments(fh)
- fh.flush()
- fh.close()
- except IOError:
- logging.info("I/O error while writing assignments to: '%s'"
- % state.ASSIGNMENTS_FILE)
- state.save()
-
- if inThread:
- # XXX shutdown the distributors if they were previously running
- # and should now be disabled
- if ipDistributorTmp:
- reactor.callFromThread(replaceBridgeRings,
- ipDistributor, ipDistributorTmp)
- if emailDistributorTmp:
- reactor.callFromThread(replaceBridgeRings,
- emailDistributor, emailDistributorTmp)
- else:
- # We're still starting up. Return these distributors so
- # they are configured in the outer-namespace
- return emailDistributorTmp, ipDistributorTmp
-
- global _reloadFn
- _reloadFn = reload
- signal.signal(signal.SIGHUP, _handleSIGHUP)
- signal.signal(signal.SIGUSR1, _handleSIGUSR1)
-
- if reactor:
- # And actually load it to start parsing. Get back our distributors.
- emailDistributor, ipDistributor = reload(False)
-
- # Configure all servers:
- if config.HTTPS_DIST and config.HTTPS_SHARE:
- addWebServer(config, ipDistributor)
- if config.EMAIL_DIST and config.EMAIL_SHARE:
- addSMTPServer(config, emailDistributor)
-
- tasks = {}
-
- # Setup all our repeating tasks:
- if config.TASKS['GET_TOR_EXIT_LIST']:
- tasks['GET_TOR_EXIT_LIST'] = task.LoopingCall(
- proxy.downloadTorExits,
- state.proxies,
- config.SERVER_PUBLIC_EXTERNAL_IP)
-
- # Schedule all configured repeating tasks:
- for name, seconds in config.TASKS.items():
- if seconds:
- try:
- tasks[name].start(abs(seconds))
- except KeyError:
- logging.info("Task %s is disabled and will not run." % name)
- else:
- logging.info("Scheduled task %s to run every %s seconds."
- % (name, seconds))
-
- # Actually run the servers.
- try:
- if reactor and not reactor.running:
- logging.info("Starting reactors.")
- reactor.run()
- except KeyboardInterrupt:
- logging.fatal("Received keyboard interrupt. Shutting down...")
- finally:
- if config.PIDFILE:
- os.unlink(config.PIDFILE)
- logging.info("Exiting...")
- sys.exit()
-
-def runSubcommand(options, config):
- """Run a subcommand from the 'Commands' section of the bridgedb help menu.
-
- :type options: :class:`bridgedb.opt.MainOptions`
- :param options: A pre-parsed options class containing any arguments and
- options given in the commandline we were called with.
- :type config: :class:`bridgedb.Main.Conf`
- :param config: The current configuration.
- :raises: :exc:`SystemExit` when all subCommands and subOptions have
- finished running.
- """
- # Make sure that the runner module is only imported after logging is set
- # up, otherwise we run into the same logging configuration problem as
- # mentioned above with the email.server and https.server.
- from bridgedb import runner
-
- statuscode = 0
-
- if options.subCommand is not None:
- logging.debug("Running BridgeDB command: '%s'" % options.subCommand)
-
- if 'descriptors' in options.subOptions:
- statuscode = runner.generateDescriptors(
- options.subOptions['descriptors'], config.RUN_IN_DIR)
-
- logging.info("Subcommand '%s' finished with status %s."
- % (options.subCommand, statuscode))
- sys.exit(statuscode)
diff --git a/lib/bridgedb/Stability.py b/lib/bridgedb/Stability.py
deleted file mode 100644
index fbc8e88..0000000
--- a/lib/bridgedb/Stability.py
+++ /dev/null
@@ -1,295 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_Stability -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see the AUTHORS file for attributions
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2012-2015, Aaron Gibson
-# (c) 2007-2015, Nick Mathewson
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""This module provides functionality for tracking bridge stability metrics.
-
-Bridge stability metrics are calculated using the model introduced in
-`"An Analysis of Tor Bridge Stability"`_ and
-`implemented in the Tor Metrics library`_.
-
-.. An Analysis of Tor Bridge Stability:
- https://metrics.torproject.org/papers/bridge-stability-2011-10-31.pdf
- Karsten Loesing, An Analysis of Tor Bridge Stability. Technical Report.
- The Tor Project, October 2011.
-
-.. implemented in the Tor Metrics library:
- https://gitweb.torproject.org/metrics-tasks/task-4255/SimulateBridgeStability.java
-"""
-
-import logging
-import bridgedb.Storage
-
-from bridgedb.schedule import toUnixSeconds
-
-
-# tunables
-weighting_factor = float(19)/float(20)
-discountIntervalMillis = long(60*60*12*1000)
-
-
-class BridgeHistory(object):
- """ Record Class that tracks a single Bridge
- The fields stored are:
-
- fingerprint, ip, port, weightedUptime, weightedTime, weightedRunLength,
- totalRunWeights, lastSeenWithDifferentAddressAndPort,
- lastSeenWithThisAddressAndPort, lastDiscountedHistoryValues.
-
- fingerprint The Bridge Fingerprint (unicode)
- ip The Bridge IP (unicode)
- port The Bridge orport (integer)
-
- weightedUptime Weighted uptime in seconds (long int)
- weightedTime Weighted time in seconds (long int)
- weightedRunLength Weighted run length of previous addresses or ports in
- seconds. (long int)
- totalRunWeights Total run weights of previously used addresses or
- ports. (float)
-
- lastSeenWithDifferentAddressAndPort
- Timestamp in milliseconds when this
- bridge was last seen with a different address or port. (long int)
-
- lastSeenWithThisAddressAndPort
- Timestamp in milliseconds when this bridge was last seen
- with this address and port. (long int)
-
- lastDiscountedHistoryValues:
- Timestamp in milliseconds when this bridge was last discounted. (long int)
-
- lastUpdatedWeightedTime:
- Timestamp in milliseconds when the weighted time was updated. (long int)
- """
- def __init__(self, fingerprint, ip, port,
- weightedUptime, weightedTime, weightedRunLength, totalRunWeights,
- lastSeenWithDifferentAddressAndPort, lastSeenWithThisAddressAndPort,
- lastDiscountedHistoryValues, lastUpdatedWeightedTime):
- self.fingerprint = fingerprint
- self.ip = ip
- self.port = port
- self.weightedUptime = long(weightedUptime)
- self.weightedTime = long(weightedTime)
- self.weightedRunLength = long(weightedRunLength)
- self.totalRunWeights = float(totalRunWeights)
- self.lastSeenWithDifferentAddressAndPort = \
- long(lastSeenWithDifferentAddressAndPort)
- self.lastSeenWithThisAddressAndPort = long(lastSeenWithThisAddressAndPort)
- self.lastDiscountedHistoryValues = long(lastDiscountedHistoryValues)
- self.lastUpdatedWeightedTime = long(lastUpdatedWeightedTime)
-
- def discountWeightedFractionalUptimeAndWeightedTime(self, discountUntilMillis):
- """ discount weighted times """
- if self.lastDiscountedHistoryValues == 0:
- self.lastDiscountedHistoryValues = discountUntilMillis
- rounds = self.numDiscountRounds(discountUntilMillis)
- if rounds > 0:
- discount = lambda x: (weighting_factor**rounds)*x
- self.weightedUptime = discount(self.weightedUptime)
- self.weightedTime = discount(self.weightedTime)
- self.weightedRunLength = discount(self.weightedRunLength)
- self.totalRunWeights = discount(self.totalRunWeights)
-
- self.lastDiscountedHistoryValues += discountIntervalMillis * rounds
- return rounds
-
- def numDiscountRounds(self, discountUntilMillis):
- """ return the number of rounds of discounting needed to bring this
- history element current """
- result = discountUntilMillis - self.lastDiscountedHistoryValues
- result = int(result/discountIntervalMillis)
- return max(result,0)
-
- @property
- def weightedFractionalUptime(self):
- """Weighted Fractional Uptime"""
- if self.weightedTime <0.0001: return long(0)
- return long(10000) * self.weightedUptime / self.weightedTime
-
- @property
- def tosa(self):
- """the Time On Same Address (TOSA)"""
- return ( self.lastSeenWithThisAddressAndPort - \
- self.lastSeenWithDifferentAddressAndPort ) / 1000
-
- @property
- def familiar(self):
- """
- A bridge is 'familiar' if 1/8 of all active bridges have appeared
- more recently than it, or if it has been around for a Weighted Time of 8 days.
- """
- # if this bridge has been around longer than 8 days
- if self.weightedTime >= long(8 * 24 * 60 * 60):
- return True
-
- # return True if self.weightedTime is greater than the weightedTime
- # of the > bottom 1/8 all bridges, sorted by weightedTime
- with bridgedb.Storage.getDB() as db:
- allWeightedTimes = [ bh.weightedTime for bh in db.getAllBridgeHistory()]
- numBridges = len(allWeightedTimes)
- logging.debug("Got %d weightedTimes", numBridges)
- allWeightedTimes.sort()
- if self.weightedTime >= allWeightedTimes[numBridges/8]:
- return True
- return False
-
- @property
- def wmtbac(self):
- """Weighted Mean Time Between Address Change"""
- totalRunLength = self.weightedRunLength + \
- ((self.lastSeenWithThisAddressAndPort -
- self.lastSeenWithDifferentAddressAndPort) / long(1000))
-
- totalWeights = self.totalRunWeights + 1.0
- if totalWeights < 0.0001: return long(0)
- assert(isinstance(long,totalRunLength))
- assert(isinstance(long,totalWeights))
- return totalRunlength / totalWeights
-
-def addOrUpdateBridgeHistory(bridge, timestamp):
- with bridgedb.Storage.getDB() as db:
- bhe = db.getBridgeHistory(bridge.fingerprint)
- if not bhe:
- # This is the first status, assume 60 minutes.
- secondsSinceLastStatusPublication = long(60*60)
- lastSeenWithDifferentAddressAndPort = timestamp * long(1000)
- lastSeenWithThisAddressAndPort = timestamp * long(1000)
-
- bhe = BridgeHistory(
- bridge.fingerprint, bridge.address, bridge.orPort,
- 0,#weightedUptime
- 0,#weightedTime
- 0,#weightedRunLength
- 0,# totalRunWeights
- lastSeenWithDifferentAddressAndPort, # first timestamnp
- lastSeenWithThisAddressAndPort,
- 0,#lastDiscountedHistoryValues,
- 0,#lastUpdatedWeightedTime
- )
- # first time we have seen this descriptor
- db.updateIntoBridgeHistory(bhe)
- # Calculate the seconds since the last parsed status. If this is
- # the first status or we haven't seen a status for more than 60
- # minutes, assume 60 minutes.
- statusPublicationMillis = long(timestamp * 1000)
- if (statusPublicationMillis - bhe.lastSeenWithThisAddressAndPort) > 60*60*1000:
- secondsSinceLastStatusPublication = long(60*60)
- logging.debug("Capping secondsSinceLastStatusPublication to 1 hour")
- # otherwise, roll with it
- else:
- secondsSinceLastStatusPublication = \
- (statusPublicationMillis - bhe.lastSeenWithThisAddressAndPort)/1000
- if secondsSinceLastStatusPublication <= 0 and bhe.weightedTime > 0:
- # old descriptor, bail
- logging.warn("Received old descriptor for bridge %s with timestamp %d",
- bhe.fingerprint, statusPublicationMillis/1000)
- return bhe
-
- # iterate over all known bridges and apply weighting factor
- discountAndPruneBridgeHistories(statusPublicationMillis)
-
- # Update the weighted times of bridges
- updateWeightedTime(statusPublicationMillis)
-
- # For Running Bridges only:
- # compare the stored history against the descriptor and see if the
- # bridge has changed its address or port
- bhe = db.getBridgeHistory(bridge.fingerprint)
-
- if not bridge.running:
- logging.info("%s is not running" % bridge.fingerprint)
- return bhe
-
- # Parse the descriptor and see if the address or port changed
- # If so, store the weighted run time
- if bridge.orport != bhe.port or bridge.ip != bhe.ip:
- bhe.totalRunWeights += 1.0;
- bhe.weightedRunLength += bhe.tosa
- bhe.lastSeenWithDifferentAddressAndPort =\
- bhe.lastSeenWithThisAddressAndPort
-
- # Regardless of whether the bridge is new, kept or changed
- # its address and port, raise its WFU times and note its
- # current address and port, and that we saw it using them.
- bhe.weightedUptime += secondsSinceLastStatusPublication
- bhe.lastSeenWithThisAddressAndPort = statusPublicationMillis
- bhe.ip = str(bridge.ip)
- bhe.port = bridge.orport
- return db.updateIntoBridgeHistory(bhe)
-
-def discountAndPruneBridgeHistories(discountUntilMillis):
- with bridgedb.Storage.getDB() as db:
- bhToRemove = []
- bhToUpdate = []
-
- for bh in db.getAllBridgeHistory():
- # discount previous values by factor of 0.95 every 12 hours
- bh.discountWeightedFractionalUptimeAndWeightedTime(discountUntilMillis)
- # give the thing at least 24 hours before pruning it
- if bh.weightedFractionalUptime < 1 and bh.weightedTime > 60*60*24:
- logging.debug("Removing bridge from history: %s" % bh.fingerprint)
- bhToRemove.append(bh.fingerprint)
- else:
- bhToUpdate.append(bh)
-
- for k in bhToUpdate: db.updateIntoBridgeHistory(k)
- for k in bhToRemove: db.delBridgeHistory(k)
-
-def updateWeightedTime(statusPublicationMillis):
- bhToUpdate = []
- with bridgedb.Storage.getDB() as db:
- for bh in db.getBridgesLastUpdatedBefore(statusPublicationMillis):
- interval = (statusPublicationMillis - bh.lastUpdatedWeightedTime)/1000
- if interval > 0:
- bh.weightedTime += min(3600,interval) # cap to 1hr
- bh.lastUpdatedWeightedTime = statusPublicationMillis
- #db.updateIntoBridgeHistory(bh)
- bhToUpdate.append(bh)
-
- for bh in bhToUpdate:
- db.updateIntoBridgeHistory(bh)
-
-def updateBridgeHistory(bridges, timestamps):
- """Process all the timestamps and update the bridge stability statistics in
- the database.
-
- .. warning: This function is extremely expensive, and will keep getting
- more and more expensive, on a linearithmic scale, every time it is
- called. Blame the :mod:`bridgedb.Stability` module.
-
- :param dict bridges: All bridges from the descriptors, parsed into
- :class:`bridgedb.bridges.Bridge`s.
- :param dict timestamps: A dictionary whose keys are bridge fingerprints,
- and whose values are lists of integers, each integer being a timestamp
- (in seconds since Unix Epoch) for when a descriptor for that bridge
- was published.
- :rtype: dict
- :returns: The original **timestamps**, but which each list of integers
- (re)sorted.
- """
- logging.debug("Beginning bridge stability calculations")
- sortedTimestamps = {}
-
- for fingerprint, stamps in timestamps.items()[:]:
- stamps.sort()
- bridge = bridges[fingerprint]
- for timestamp in stamps:
- logging.debug(
- ("Adding/updating timestamps in BridgeHistory for %s in "
- "database: %s") % (fingerprint, timestamp))
- timestamp = toUnixSeconds(timestamp.timetuple())
- addOrUpdateBridgeHistory(bridge, timestamp)
- # Replace the timestamps so the next sort is (hopefully) less
- # expensive:
- sortedTimestamps[fingerprint] = stamps
-
- logging.debug("Stability calculations complete")
- return sortedTimestamps
diff --git a/lib/bridgedb/Storage.py b/lib/bridgedb/Storage.py
deleted file mode 100644
index ea9d26b..0000000
--- a/lib/bridgedb/Storage.py
+++ /dev/null
@@ -1,466 +0,0 @@
-# BridgeDB by Nick Mathewson.
-# Copyright (c) 2007-2009, The Tor Project, Inc.
-# See LICENSE for licensing information
-
-import calendar
-import logging
-import binascii
-import sqlite3
-import time
-import hashlib
-from contextlib import GeneratorContextManager
-from functools import wraps
-from ipaddr import IPAddress
-import sys
-
-from bridgedb.Stability import BridgeHistory
-import threading
-
-toHex = binascii.b2a_hex
-fromHex = binascii.a2b_hex
-HEX_ID_LEN = 40
-
-def _escapeValue(v):
- return "'%s'" % v.replace("'", "''")
-
-def timeToStr(t):
- return time.strftime("%Y-%m-%d %H:%M", time.gmtime(t))
-def strToTime(t):
- return calendar.timegm(time.strptime(t, "%Y-%m-%d %H:%M"))
-
-# The old DB system was just a key->value mapping DB, with special key
-# prefixes to indicate which database they fell into.
-#
-# sp|<ID> -- given to bridgesplitter; maps bridgeID to ring name.
-# em|<emailaddr> -- given to emailbaseddistributor; maps email address
-# to concatenated ID.
-# fs|<ID> -- Given to BridgeTracker, maps to time when a router was
-# first seen (YYYY-MM-DD HH:MM)
-# ls|<ID> -- given to bridgetracker, maps to time when a router was
-# last seen (YYYY-MM-DD HH:MM)
-#
-# We no longer want to use em| at all, since we're not doing that kind
-# of persistence any more.
-
-# Here is the SQL schema.
-SCHEMA2_SCRIPT = """
- CREATE TABLE Config (
- key PRIMARY KEY NOT NULL,
- value
- );
-
- CREATE TABLE Bridges (
- id INTEGER PRIMARY KEY NOT NULL,
- hex_key,
- address,
- or_port,
- distributor,
- first_seen,
- last_seen
- );
-
- CREATE UNIQUE INDEX BridgesKeyIndex ON Bridges ( hex_key );
-
- CREATE TABLE EmailedBridges (
- email PRIMARY KEY NOT NULL,
- when_mailed
- );
-
- CREATE INDEX EmailedBridgesWhenMailed on EmailedBridges ( email );
-
- CREATE TABLE BlockedBridges (
- id INTEGER PRIMARY KEY NOT NULL,
- hex_key,
- blocking_country
- );
-
- CREATE INDEX BlockedBridgesBlockingCountry on BlockedBridges(hex_key);
-
- CREATE TABLE WarnedEmails (
- email PRIMARY KEY NOT NULL,
- when_warned
- );
-
- CREATE INDEX WarnedEmailsWasWarned on WarnedEmails ( email );
-
- INSERT INTO Config VALUES ( 'schema-version', 2 );
-"""
-
-SCHEMA_2TO3_SCRIPT = """
- CREATE TABLE BridgeHistory (
- fingerprint PRIMARY KEY NOT NULL,
- address,
- port INT,
- weightedUptime LONG,
- weightedTime LONG,
- weightedRunLength LONG,
- totalRunWeights DOUBLE,
- lastSeenWithDifferentAddressAndPort LONG,
- lastSeenWithThisAddressAndPort LONG,
- lastDiscountedHistoryValues LONG,
- lastUpdatedWeightedTime LONG
- );
-
- CREATE INDEX BridgeHistoryIndex on BridgeHistory ( fingerprint );
-
- INSERT OR REPLACE INTO Config VALUES ( 'schema-version', 3 );
- """
-SCHEMA3_SCRIPT = SCHEMA2_SCRIPT + SCHEMA_2TO3_SCRIPT
-
-
-class BridgeData(object):
- """Value class carrying bridge information:
- hex_key - The unique hex key of the given bridge
- address - Bridge IP address
- or_port - Bridge TCP port
- distributor - The distributor (or pseudo-distributor) through which
- this bridge is being announced
- first_seen - When did we first see this bridge online?
- last_seen - When was the last time we saw this bridge online?
- """
- def __init__(self, hex_key, address, or_port, distributor="unallocated",
- first_seen="", last_seen=""):
- self.hex_key = hex_key
- self.address = address
- self.or_port = or_port
- self.distributor = distributor
- self.first_seen = first_seen
- self.last_seen = last_seen
-
-
-class Database(object):
- def __init__(self, sqlite_fname):
- self._conn = openDatabase(sqlite_fname)
- self._cur = self._conn.cursor()
- self.sqlite_fname = sqlite_fname
-
- def commit(self):
- self._conn.commit()
-
- def rollback(self):
- self._conn.rollback()
-
- def close(self):
- #print "Closing DB"
- self._cur.close()
- self._conn.close()
-
- def insertBridgeAndGetRing(self, bridge, setRing, seenAt, validRings,
- defaultPool="unallocated"):
- '''Updates info about bridge, setting ring to setRing if none was set.
- Also sets distributor to `defaultPool' if the bridge was found in
- the database, but its distributor isn't valid anymore.
-
- Returns the name of the distributor the bridge is assigned to.
- '''
- cur = self._cur
-
- t = timeToStr(seenAt)
- h = bridge.fingerprint
- assert len(h) == HEX_ID_LEN
-
- cur.execute("SELECT id, distributor "
- "FROM Bridges WHERE hex_key = ?", (h,))
- v = cur.fetchone()
- if v is not None:
- i, ring = v
- # Check if this is currently a valid ring name. If not, move back
- # into default pool.
- if ring not in validRings:
- ring = defaultPool
- # Update last_seen, address, port and (possibly) distributor.
- cur.execute("UPDATE Bridges SET address = ?, or_port = ?, "
- "distributor = ?, last_seen = ? WHERE id = ?",
- (str(bridge.address), bridge.orPort, ring,
- timeToStr(seenAt), i))
- return ring
- else:
- # Insert it.
- cur.execute("INSERT INTO Bridges (hex_key, address, or_port, "
- "distributor, first_seen, last_seen) "
- "VALUES (?, ?, ?, ?, ?, ?)",
- (h, str(bridge.address), bridge.orPort, setRing, t, t))
- return setRing
-
- def cleanEmailedBridges(self, expireBefore):
- cur = self._cur
- t = timeToStr(expireBefore)
- cur.execute("DELETE FROM EmailedBridges WHERE when_mailed < ?", (t,))
-
- def getEmailTime(self, addr):
- addr = hashlib.sha1(addr).hexdigest()
- cur = self._cur
- cur.execute("SELECT when_mailed FROM EmailedBridges WHERE email = ?", (addr,))
- v = cur.fetchone()
- if v is None:
- return None
- return strToTime(v[0])
-
- def setEmailTime(self, addr, whenMailed):
- addr = hashlib.sha1(addr).hexdigest()
- cur = self._cur
- t = timeToStr(whenMailed)
- cur.execute("INSERT OR REPLACE INTO EmailedBridges "
- "(email,when_mailed) VALUES (?,?)", (addr, t))
-
- def getAllBridges(self):
- """Return a list of BridgeData value classes of all bridges in the
- database
- """
- retBridges = []
- cur = self._cur
- cur.execute("SELECT hex_key, address, or_port, distributor, "
- "first_seen, last_seen FROM Bridges")
- for b in cur.fetchall():
- bridge = BridgeData(b[0], b[1], b[2], b[3], b[4], b[5])
- retBridges.append(bridge)
-
- return retBridges
-
- def getBridgesForDistributor(self, distributor):
- """Return a list of BridgeData value classes of all bridges in the
- database that are allocated to distributor 'distributor'
- """
- retBridges = []
- cur = self._cur
- cur.execute("SELECT hex_key, address, or_port, distributor, "
- "first_seen, last_seen FROM Bridges WHERE "
- "distributor = ?", (distributor, ))
- for b in cur.fetchall():
- bridge = BridgeData(b[0], b[1], b[2], b[3], b[4], b[5])
- retBridges.append(bridge)
-
- return retBridges
-
- def updateDistributorForHexKey(self, distributor, hex_key):
- cur = self._cur
- cur.execute("UPDATE Bridges SET distributor = ? WHERE hex_key = ?",
- (distributor, hex_key))
-
- def getWarnedEmail(self, addr):
- addr = hashlib.sha1(addr).hexdigest()
- cur = self._cur
- cur.execute("SELECT * FROM WarnedEmails WHERE email = ?", (addr,))
- v = cur.fetchone()
- if v is None:
- return False
- return True
-
- def setWarnedEmail(self, addr, warned=True, whenWarned=time.time()):
- addr = hashlib.sha1(addr).hexdigest()
- t = timeToStr(whenWarned)
- cur = self._cur
- if warned == True:
- cur.execute("INSERT INTO WarnedEmails"
- "(email,when_warned) VALUES (?,?)", (addr, t,))
- elif warned == False:
- cur.execute("DELETE FROM WarnedEmails WHERE email = ?", (addr,))
-
- def cleanWarnedEmails(self, expireBefore):
- cur = self._cur
- t = timeToStr(expireBefore)
-
- cur.execute("DELETE FROM WarnedEmails WHERE when_warned < ?", (t,))
-
- def updateIntoBridgeHistory(self, bh):
- cur = self._cur
- cur.execute("INSERT OR REPLACE INTO BridgeHistory values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- (bh.fingerprint, str(bh.ip), bh.port,
- bh.weightedUptime, bh.weightedTime, bh.weightedRunLength,
- bh.totalRunWeights, bh.lastSeenWithDifferentAddressAndPort,
- bh.lastSeenWithThisAddressAndPort, bh.lastDiscountedHistoryValues,
- bh.lastUpdatedWeightedTime))
- return bh
-
- def delBridgeHistory(self, fp):
- cur = self._cur
- cur.execute("DELETE FROM BridgeHistory WHERE fingerprint = ?", (fp,))
-
- def getBridgeHistory(self, fp):
- cur = self._cur
- cur.execute("SELECT * FROM BridgeHistory WHERE fingerprint = ?", (fp,))
- h = cur.fetchone()
- if h is None:
- return
- return BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
-
- def getAllBridgeHistory(self):
- cur = self._cur
- v = cur.execute("SELECT * FROM BridgeHistory")
- if v is None: return
- for h in v:
- yield BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
-
- def getBridgesLastUpdatedBefore(self, statusPublicationMillis):
- cur = self._cur
- v = cur.execute("SELECT * FROM BridgeHistory WHERE lastUpdatedWeightedTime < ?",
- (statusPublicationMillis,))
- if v is None: return
- for h in v:
- yield BridgeHistory(h[0],IPAddress(h[1]),h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10])
-
-
-def openDatabase(sqlite_file):
- conn = sqlite3.Connection(sqlite_file)
- cur = conn.cursor()
- try:
- try:
- cur.execute("SELECT value FROM Config WHERE key = 'schema-version'")
- val, = cur.fetchone()
- if val == 2:
- logging.info("Adding new table BridgeHistory")
- cur.executescript(SCHEMA_2TO3_SCRIPT)
- elif val != 3:
- logging.warn("Unknown schema version %s in database.", val)
- except sqlite3.OperationalError:
- logging.warn("No Config table found in DB; creating tables")
- cur.executescript(SCHEMA3_SCRIPT)
- conn.commit()
- finally:
- cur.close()
- return conn
-
-
-class DBGeneratorContextManager(GeneratorContextManager):
- """Helper for @contextmanager decorator.
-
- Overload __exit__() so we can call the generator many times
- """
- def __exit__(self, type, value, traceback):
- """Handle exiting a with statement block
-
- Progress generator or throw exception
-
- Significantly based on contextlib.py
-
- :throws: `RuntimeError` if the generator doesn't stop after
- exception is thrown
- """
- if type is None:
- try:
- self.gen.next()
- except StopIteration:
- return
- return
- else:
- if value is None:
- # Need to force instantiation so we can reliably
- # tell if we get the same exception back
- value = type()
- try:
- self.gen.throw(type, value, traceback)
- raise RuntimeError("generator didn't stop after throw()")
- except StopIteration, exc:
- # Suppress the exception *unless* it's the same exception that
- # was passed to throw(). This prevents a StopIteration
- # raised inside the "with" statement from being suppressed
- return exc is not value
- except:
- # only re-raise if it's *not* the exception that was
- # passed to throw(), because __exit__() must not raise
- # an exception unless __exit__() itself failed. But throw()
- # has to raise the exception to signal propagation, so this
- # fixes the impedance mismatch between the throw() protocol
- # and the __exit__() protocol.
- #
- if sys.exc_info()[1] is not value:
- raise
-
-def contextmanager(func):
- """Decorator to for :func:`Storage.getDB()`
-
- Define getDB() for use by with statement content manager
- """
- @wraps(func)
- def helper(*args, **kwds):
- return DBGeneratorContextManager(func(*args, **kwds))
- return helper
-
-_DB_FNAME = None
-_LOCK = None
-_LOCKED = 0
-_OPENED_DB = None
-_REFCOUNT = 0
-
-def clearGlobalDB():
- """Start from scratch.
-
- This is currently only used in unit tests.
- """
- global _DB_FNAME
- global _LOCK
- global _LOCKED
- global _OPENED_DB
-
- _DB_FNAME = None
- _LOCK = None
- _LOCKED = 0
- _OPENED_DB = None
- _REFCOUNT = 0
-
-def initializeDBLock():
- """Create the lock
-
- This must be called before the first database query
- """
- global _LOCK
-
- if not _LOCK:
- _LOCK = threading.RLock()
- assert _LOCK
-
-def setDBFilename(sqlite_fname):
- global _DB_FNAME
- _DB_FNAME = sqlite_fname
-
- at contextmanager
-def getDB(block=True):
- """Generator: Return a usable database handler
-
- Always return a :class:`bridgedb.Storage.Database` that is
- usable within the current thread. If a connection already exists
- and it was created by the current thread, then return the
- associated :class:`bridgedb.Storage.Database` instance. Otherwise,
- create a new instance, blocking until the existing connection
- is closed, if applicable.
-
- Note: This is a blocking call (by default), be careful about
- deadlocks!
-
- :rtype: :class:`bridgedb.Storage.Database`
- :returns: An instance of :class:`bridgedb.Storage.Database` used to
- query the database
- """
- global _DB_FNAME
- global _LOCK
- global _LOCKED
- global _OPENED_DB
- global _REFCOUNT
-
- assert _LOCK
- try:
- own_lock = _LOCK.acquire(block)
- if own_lock:
- _LOCKED += 1
-
- if not _OPENED_DB:
- assert _REFCOUNT == 0
- _OPENED_DB = Database(_DB_FNAME)
-
- _REFCOUNT += 1
- yield _OPENED_DB
- else:
- yield False
- finally:
- assert own_lock
- try:
- _REFCOUNT -= 1
- if _REFCOUNT == 0:
- _OPENED_DB.close()
- _OPENED_DB = None
- finally:
- _LOCKED -= 1
- _LOCK.release()
-
-def dbIsLocked():
- return _LOCKED != 0
diff --git a/lib/bridgedb/__init__.py b/lib/bridgedb/__init__.py
deleted file mode 100644
index 3643ea7..0000000
--- a/lib/bridgedb/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-from ._version import get_versions
-from ._langs import get_langs
-
-__version__ = get_versions()['version']
-__langs__ = get_langs()
-
-del get_versions
-del get_langs
diff --git a/lib/bridgedb/_langs.py b/lib/bridgedb/_langs.py
deleted file mode 100644
index aca8342..0000000
--- a/lib/bridgedb/_langs.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""_langs.py - Storage for information on installed language support."""
-
-RTL_LANGS = ('ar', 'he', 'fa', 'gu_IN', 'ku')
-
-
-def get_langs():
- """Return a list of two-letter country codes of translations which were
- installed (if we've already been installed).
- """
- return supported
-
-
-#: This list will be rewritten by :func:`get_supported_langs` in setup.py at
-#: install time, so that the :attr:`bridgedb.__langs__` will hold a list of
-#: two-letter country codes for languages which were installed.
-supported = []
diff --git a/lib/bridgedb/_version.py b/lib/bridgedb/_version.py
deleted file mode 100644
index 8e9faf1..0000000
--- a/lib/bridgedb/_version.py
+++ /dev/null
@@ -1,197 +0,0 @@
-
-IN_LONG_VERSION_PY = True
-# This file helps to compute a version number in source trees obtained from
-# git-archive tarball (such as those provided by githubs download-from-tag
-# feature). Distribution tarballs (build by setup.py sdist) and build
-# directories (produced by setup.py build) will contain a much shorter file
-# that just contains the computed version number.
-
-# This file is released into the public domain. Generated by
-# versioneer-0.7+ (https://github.com/warner/python-versioneer)
-
-# these strings will be replaced by git during git-archive
-git_refnames = "$Format:%d$"
-git_full = "$Format:%H$"
-
-
-import subprocess
-import sys
-
-def run_command(args, cwd=None, verbose=False):
- try:
- # remember shell=False, so use git.cmd on windows, not just git
- p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
- except EnvironmentError:
- e = sys.exc_info()[1]
- if verbose:
- print("unable to run %s" % args[0])
- print(e)
- return None
- stdout = p.communicate()[0].strip()
- if sys.version >= '3':
- stdout = stdout.decode()
- if p.returncode != 0:
- if verbose:
- print("unable to run %s (error)" % args[0])
- return None
- return stdout
-
-
-import sys
-import re
-import os.path
-
-def get_expanded_variables(versionfile_source):
- # the code embedded in _version.py can just fetch the value of these
- # variables. When used from setup.py, we don't want to import
- # _version.py, so we do it with a regexp instead. This function is not
- # used from _version.py.
- variables = {}
- try:
- for line in open(versionfile_source,"r").readlines():
- if line.strip().startswith("git_refnames ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- variables["refnames"] = mo.group(1)
- if line.strip().startswith("git_full ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- variables["full"] = mo.group(1)
- except EnvironmentError:
- pass
- return variables
-
-def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
- refnames = variables["refnames"].strip()
- if refnames.startswith("$Format"):
- if verbose:
- print("variables are unexpanded, not using")
- return {} # unexpanded, so not in an unpacked git-archive tarball
- refs = set([r.strip() for r in refnames.strip("()").split(",")])
- for ref in list(refs):
- if not re.search(r'\d', ref):
- if verbose:
- print("discarding '%s', no digits" % ref)
- refs.discard(ref)
- # Assume all version tags have a digit. git's %d expansion
- # behaves like git log --decorate=short and strips out the
- # refs/heads/ and refs/tags/ prefixes that would let us
- # distinguish between branches and tags. By ignoring refnames
- # without digits, we filter out many common branch names like
- # "release" and "stabilization", as well as "HEAD" and "master".
- if verbose:
- print("remaining refs: %s" % ",".join(sorted(refs)))
- for ref in sorted(refs):
- # sorting will prefer e.g. "2.0" over "2.0rc1"
- if ref.startswith(tag_prefix):
- r = ref[len(tag_prefix):]
- if verbose:
- print("picking %s" % r)
- return { "version": r,
- "full": variables["full"].strip() }
- # no suitable tags, so we use the full revision id
- if verbose:
- print("no suitable tags, using full revision id")
- return { "version": variables["full"].strip(),
- "full": variables["full"].strip() }
-
-def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
- # this runs 'git' from the root of the source tree. That either means
- # someone ran a setup.py command (and this code is in versioneer.py, so
- # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
- # the source tree), or someone ran a project-specific entry point (and
- # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
- # containing directory is somewhere deeper in the source tree). This only
- # gets called if the git-archive 'subst' variables were *not* expanded,
- # and _version.py hasn't already been rewritten with a short version
- # string, meaning we're inside a checked out source tree.
-
- try:
- here = os.path.abspath(__file__)
- except NameError:
- # some py2exe/bbfreeze/non-CPython implementations don't do __file__
- return {} # not always correct
-
- # versionfile_source is the relative path from the top of the source tree
- # (where the .git directory might live) to this file. Invert this to find
- # the root from __file__.
- root = here
- if IN_LONG_VERSION_PY:
- for i in range(len(versionfile_source.split("/"))):
- root = os.path.dirname(root)
- else:
- root = os.path.dirname(here)
- if not os.path.exists(os.path.join(root, ".git")):
- if verbose:
- print("no .git in %s" % root)
- return {}
-
- GIT = "git"
- if sys.platform == "win32":
- GIT = "git.cmd"
- stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
- cwd=root)
- if stdout is None:
- return {}
- if not stdout.startswith(tag_prefix):
- if verbose:
- print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
- return {}
- tag = stdout[len(tag_prefix):]
- stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
- if stdout is None:
- return {}
- full = stdout.strip()
- if tag.endswith("-dirty"):
- full += "-dirty"
- return {"version": tag, "full": full}
-
-
-def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
- if IN_LONG_VERSION_PY:
- # We're running from _version.py. If it's from a source tree
- # (execute-in-place), we can work upwards to find the root of the
- # tree, and then check the parent directory for a version string. If
- # it's in an installed application, there's no hope.
- try:
- here = os.path.abspath(__file__)
- except NameError:
- # py2exe/bbfreeze/non-CPython don't have __file__
- return {} # without __file__, we have no hope
- # versionfile_source is the relative path from the top of the source
- # tree to _version.py. Invert this to find the root from __file__.
- root = here
- for i in range(len(versionfile_source.split("/"))):
- root = os.path.dirname(root)
- else:
- # we're running from versioneer.py, which means we're running from
- # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
- here = os.path.abspath(sys.argv[0])
- root = os.path.dirname(here)
-
- # Source tarballs conventionally unpack into a directory that includes
- # both the project name and a version string.
- dirname = os.path.basename(root)
- if not dirname.startswith(parentdir_prefix):
- if verbose:
- print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
- (root, dirname, parentdir_prefix))
- return None
- return {"version": dirname[len(parentdir_prefix):], "full": ""}
-
-tag_prefix = "bridgedb-"
-parentdir_prefix = "bridgedb-"
-versionfile_source = "lib/bridgedb/_version.py"
-
-def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
- variables = { "refnames": git_refnames, "full": git_full }
- ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
- if not ver:
- ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
- if not ver:
- ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
- verbose)
- if not ver:
- ver = default
- return ver
-
diff --git a/lib/bridgedb/bridgerequest.py b/lib/bridgedb/bridgerequest.py
deleted file mode 100644
index 1266145..0000000
--- a/lib/bridgedb/bridgerequest.py
+++ /dev/null
@@ -1,206 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridgerequest ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-import ipaddr
-import logging
-
-from zope.interface import implements
-from zope.interface import Attribute
-from zope.interface import Interface
-
-from bridgedb.crypto import getHMACFunc
-from bridgedb.filters import byIPv
-from bridgedb.filters import byNotBlockedIn
-from bridgedb.filters import byTransport
-
-
-class IRequestBridges(Interface):
- """Interface specification of client options for requested bridges."""
-
- filters = Attribute(
- "A list of callables used to filter bridges from a hashring.")
- ipVersion = Attribute(
- "The IP version of bridge addresses to distribute to the client.")
- transports = Attribute(
- "A list of strings of Pluggable Transport types requested.")
- notBlockedIn = Attribute(
- "A list of two-character country codes. The distributed bridges "
- "should not be blocked in these countries.")
- valid = Attribute(
- "A boolean. Should be ``True`` if the client's request was valid.")
- client = Attribute(
- "This should be some information unique to the client making the "
- "request for bridges, such that we are able to HMAC this unique "
- "data, via getHashringPlacement(), in order to place the client "
- "into a hashring (determining which bridge addresses they get in "
- "the request response).")
-
- def addFilter():
- """Add a filter to the list of ``filters``."""
-
- def clearFilters():
- """Clear the list of ``filters``."""
-
- def generateFilters():
- """Build the list of callables, ``filters``, according to the current
- contents of the lists of ``transports``, ``notBlockedIn``, and the
- ``ipVersion``.
- """
-
- def getHashringPlacement():
- """Use some unique parameters of the client making this request to
- obtain a value which we can use to place them into one of the hashrings
- with :class:`~bridgedb.bridges.Bridge`s in it, in order to give that
- client different bridges than other clients.
- """
-
- def isValid():
- """Determine if the request is ``valid`` according to some parameters."""
-
- def withIPv4():
- """Set the ``ipVersion`` to IPv4."""
-
- def withIPv6():
- """Set the ``ipVersion`` to IPv6."""
-
- def withPluggableTransportType(typeOfPT):
- """Add this **typeOfPT** to the list of requested ``transports``."""
-
- def withoutBlockInCountry(countryCode):
- """Add this **countryCode** to the list of countries which distributed
- bridges should not be blocked in (``notBlockedIn``).
- """
-
-
-class BridgeRequestBase(object):
- """A generic base class for storing options of a client bridge request."""
-
- implements(IRequestBridges)
-
- def __init__(self, ipVersion=None):
- self.ipVersion = ipVersion
- #: (list) A list of callables used to filter bridges from a hashring.
- self.filters = list()
- #: (list) A list of strings of Pluggable Transport types requested.
- self.transports = list()
- #: (list) A list of two-character country codes. The distributed bridges
- #: should not be blocked in these countries.
- self.notBlockedIn = list()
- #: This should be some information unique to the client making the
- #: request for bridges, such that we are able to HMAC this unique data
- #: in order to place the client into a hashring (determining which
- #: bridge addresses they get in the request response). It defaults to
- #: the string ``'default'``.
- self.client = 'default'
- #: (bool) Should be ``True`` if the client's request was valid.
- self.valid = False
-
- @property
- def ipVersion(self):
- """The IP version of bridge addresses to distribute to the client.
-
- :rtype: int
- :returns: Either ``4`` or ``6``.
- """
- return self._ipVersion
-
- @ipVersion.setter
- def ipVersion(self, ipVersion):
- """The IP version of bridge addresses to distribute to the client.
-
- :param int ipVersion: The IP address version for the bridge lines we
- should distribute in response to this client request.
- """
- if not ipVersion in (4, 6):
- ipVersion = 4
- self._ipVersion = ipVersion
-
- def getHashringPlacement(self, key, client=None):
- """Create an HMAC of some **client** info using a **key**.
-
- :param str key: The key to use for HMACing.
- :param str client: Some (hopefully unique) information about the
- client who is requesting bridges, such as an IP or email address.
- :rtype: long
- :returns: A long specifying index of the first node in a hashring to
- be distributed to the client. This value should obviously be used
- mod the number of nodes in the hashring.
- """
- if not client:
- client = self.client
-
- # Get an HMAC with the key of the client identifier:
- digest = getHMACFunc(key)(client)
- # Take the lower 8 bytes of the digest and convert to a long:
- position = long(digest[:8], 16)
- return position
-
- def isValid(self, valid=None):
- """Get or set the validity of this bridge request.
-
- If called without parameters, this method will return the current
- state, otherwise (if called with the **valid** parameter), it will set
- the current state of validity for this request.
-
- :param bool valid: If given, set the validity state of this
- request. Otherwise, get the current state.
- """
- if valid is not None:
- self.valid = bool(valid)
- return self.valid
-
- def withIPv4(self):
- self.ipVersion = 4
-
- def withIPv6(self):
- self.ipVersion = 6
-
- def withoutBlockInCountry(self, country):
- self.notBlockedIn.append(country.lower())
-
- def withPluggableTransportType(self, pt):
- self.transports.append(pt)
-
- def addFilter(self, filtre):
- self.filters.append(filtre)
-
- def clearFilters(self):
- self.filters = []
-
- def justOnePTType(self):
- """Get just one bridge PT type at a time!"""
- ptType = None
- try:
- ptType = self.transports[-1] # Use the last PT requested
- except IndexError:
- logging.debug("No pluggable transports were requested.")
- return ptType
-
- def generateFilters(self):
- self.clearFilters()
-
- pt = self.justOnePTType()
- msg = ("Adding a filter to %s for %s for IPv%d"
- % (self.__class__.__name__, self.client, self.ipVersion))
-
- if self.notBlockedIn:
- for country in self.notBlockedIn:
- logging.info("%s %s bridges not blocked in %s..." %
- (msg, pt or "vanilla", country))
- self.addFilter(byNotBlockedIn(country, pt, self.ipVersion))
- elif pt:
- logging.info("%s %s bridges..." % (msg, pt))
- self.addFilter(byTransport(pt, self.ipVersion))
- else:
- logging.info("%s bridges..." % msg)
- self.addFilter(byIPv(self.ipVersion))
diff --git a/lib/bridgedb/bridges.py b/lib/bridgedb/bridges.py
deleted file mode 100644
index 747863a..0000000
--- a/lib/bridgedb/bridges.py
+++ /dev/null
@@ -1,1769 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridges -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see the AUTHORS file for attributions
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""Classes for manipulating and storing Bridges and their attributes."""
-
-from __future__ import print_function
-
-import base64
-import codecs
-import hashlib
-import ipaddr
-import logging
-import os
-import warnings
-
-from Crypto.Util import asn1
-from Crypto.Util.number import bytes_to_long
-from Crypto.Util.number import long_to_bytes
-
-from zope.interface import implementer
-from zope.interface import Attribute
-from zope.interface import Interface
-
-import bridgedb.Storage
-
-from bridgedb import geo
-from bridgedb import safelog
-from bridgedb import bridgerequest
-from bridgedb.crypto import removePKCS1Padding
-from bridgedb.parse.addr import isIPAddress
-from bridgedb.parse.addr import isIPv4
-from bridgedb.parse.addr import isIPv6
-from bridgedb.parse.addr import isValidIP
-from bridgedb.parse.addr import PortList
-from bridgedb.parse.fingerprint import isValidFingerprint
-from bridgedb.parse.fingerprint import toHex
-from bridgedb.parse.fingerprint import fromHex
-from bridgedb.parse.nickname import isValidRouterNickname
-from bridgedb.util import isascii_noncontrol
-
-
-class PluggableTransportUnavailable(Exception):
- """Raised when a :class:`Bridge` doesn't have the requested
- :class:`PluggableTransport`.
- """
-
-class MalformedBridgeInfo(ValueError):
- """Raised when some information about a bridge appears malformed."""
-
-class MalformedPluggableTransport(MalformedBridgeInfo):
- """Raised when information used to initialise a :class:`PluggableTransport`
- appears malformed.
- """
-
-class InvalidPluggableTransportIP(MalformedBridgeInfo):
- """Raised when a :class:`PluggableTransport` has an invalid address."""
-
-class MissingServerDescriptorDigest(MalformedBridgeInfo):
- """Raised when the hash digest for an ``@type bridge-server-descriptor``
- (which should be in the corresponding ``@type bridge-networkstatus``
- document), was missing.
- """
-
-class ServerDescriptorDigestMismatch(MalformedBridgeInfo):
- """Raised when the digest in an ``@type bridge-networkstatus`` document
- doesn't match the hash digest of the ``@type bridge-server-descriptor``'s
- contents.
- """
-
-class ServerDescriptorWithoutNetworkstatus(MalformedBridgeInfo):
- """Raised when we find a ``@type bridge-server-descriptor`` which was not
- mentioned in the latest ``@type bridge-networkstatus`` document.
- """
-
-class InvalidExtraInfoSignature(MalformedBridgeInfo):
- """Raised if the signature on an ``@type bridge-extrainfo`` is invalid."""
-
-
-class IBridge(Interface):
- """I am a (mostly) stub interface whose primary purpose is merely to allow
- other classes to signify whether or not they can be treated like a
- :class:`Bridge`.
- """
- fingerprint = Attribute(
- ("The lowercased, hexadecimal-encoded, hash digest of this Bridge's "
- "public identity key."))
- address = Attribute("This Bridge's primary public IP address.")
- port = Attribute("The port which this Bridge is listening on.")
-
-
-class Flags(object):
- """All the flags which a :class:`Bridge` may have."""
-
- fast = False
- guard = False
- running = False
- stable = False
- valid = False
-
- def update(self, flags):
- """Update with **flags** taken from an ``@type networkstatus-bridge``
- 's'-line.
-
- From `dir-spec.txt`_:
- |
- | "s" SP Flags NL
- |
- | [Exactly once.]
- |
- | A series of space-separated status flags, in lexical order (as ASCII
- | byte strings). Currently documented flags are:
- |
- | [...]
- | "Fast" if the router is suitable for high-bandwidth circuits.
- | "Guard" if the router is suitable for use as an entry guard.
- | [...]
- | "Stable" if the router is suitable for long-lived circuits.
- | "Running" if the router is currently usable.
- | [...]
- | "Valid" if the router has been 'validated'.
-
- .. _dir-spec.txt:
- https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt?id=7647f6d4d#n1602
-
- :param list flags: A list of strings containing each of the flags
- parsed from the 's'-line.
- """
- self.fast = 'Fast' in flags
- self.guard = 'Guard' in flags
- self.running = 'Running' in flags
- self.stable = 'Stable' in flags
- self.valid = 'Valid' in flags
-
-
- at implementer(IBridge)
-class BridgeAddressBase(object):
- """A base class for describing one of a :class:`Bridge`'s or a
- :class:`PluggableTransport`'s location, including its identity key
- fingerprint and IP address.
-
- :type fingerprint: str
- :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
- key of the parent bridge running this pluggable transport instance,
- i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
- contains a hash digest for a ``@type bridge-extrainfo-document``, the
- latter of which contains the parameter of this pluggable transport in
- its ``transport`` line.
-
- :type address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
- :ivar address: The IP address of :class:`Bridge` or one of its
- :class:`PluggableTransport`s.
-
- :type country: str
- :ivar country: The two-letter GeoIP country code of the :ivar:`address`.
-
- :type port: int
- :ivar port: A integer specifying the port which this :class:`Bridge`
- (or :class:`PluggableTransport`) is listening on.
- """
-
- def __init__(self):
- self._fingerprint = None
- self._address = None
- self._country = None
- self._port = None
-
- @property
- def fingerprint(self):
- """Get this Bridge's fingerprint.
-
- :rtype: str
- :returns: A 40-character hexadecimal formatted string representation
- of the SHA-1 hash digest of the public half of this Bridge's
- identity key.
- """
- return self._fingerprint
-
- @fingerprint.setter
- def fingerprint(self, value):
- """Set this Bridge's fingerprint to **value**.
-
- .. info: The purported fingerprint will be checked for specification
- conformity with
- :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
-
- :param str value: The fingerprint for this Bridge.
- """
- if value and isValidFingerprint(value):
- self._fingerprint = value.upper()
-
- @fingerprint.deleter
- def fingerprint(self):
- """Reset this Bridge's fingerprint."""
- self._fingerprint = None
-
- @property
- def identity(self):
- """Get this Bridge's identity digest.
-
- :rtype: bytes
- :returns: The binary-encoded SHA-1 hash digest of the public half of
- this Bridge's identity key, if available; otherwise, returns
- ``None``.
- """
- if self.fingerprint:
- return fromHex(self.fingerprint)
-
- @identity.setter
- def identity(self, value):
- """Set this Bridge's identity digest to **value**.
-
- .. info: The purported identity digest will be checked for
- specification conformity with
- :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
-
- :param str value: The binary-encoded SHA-1 hash digest of the public
- half of this Bridge's identity key.
- """
- self.fingerprint = toHex(value)
-
- @identity.deleter
- def identity(self):
- """Reset this Bridge's identity digest."""
- del(self.fingerprint)
-
- @property
- def address(self):
- """Get this bridge's address.
-
- :rtype: :class:`~ipaddr.IPv4Address` or :class:`~ipaddr.IPv6Address`
- :returns: The bridge's address.
- """
- return self._address
-
- @address.setter
- def address(self, value):
- """Set this Bridge's address.
-
- :param value: The main ORPort IP address of this bridge.
- """
- if value and isValidIP(value): # XXX only conditionally set _address?
- self._address = isIPAddress(value, compressed=False)
-
- @address.deleter
- def address(self):
- """Reset this Bridge's address to ``None``."""
- self._address = None
-
- @property
- def country(self):
- """Get the two-letter GeoIP country code for the :ivar:`address`.
-
- :rtype: str or ``None``
- :returns: If :ivar:`address` is set, this returns a two-letter country
- code for the geolocated region that :ivar:`address` is within;
- otherwise, returns ``None``.
- """
- if self.address:
- return geo.getCountryCode(self.address)
-
- @property
- def port(self):
- """Get the port number which this ``Bridge`` is listening
- for incoming client connections on.
-
- :rtype: int or None
- :returns: The port (as an int), if it is known and valid; otherwise,
- returns ``None``.
- """
- return self._port
-
- @port.setter
- def port(self, value):
- """Store the port number which this ``Bridge`` is listening
- for incoming client connections on.
-
- :param int value: The transport's port.
- """
- if isinstance(value, int) and (0 <= value <= 65535):
- self._port = value
-
- @port.deleter
- def port(self):
- """Reset this ``Bridge``'s port to ``None``."""
- self._port = None
-
-
-
- at implementer(IBridge)
-class PluggableTransport(BridgeAddressBase):
- """A single instance of a Pluggable Transport (PT) offered by a
- :class:`Bridge`.
-
- Pluggable transports are described within a bridge's
- ``@type bridge-extrainfo`` descriptor, see the
- ``Specifications: Client behavior`` section and the
- ``TOR_PT_SERVER_TRANSPORT_OPTIONS`` description in pt-spec.txt_ for
- additional specification.
-
- .. _pt-spec.txt:
- https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
-
- :type fingerprint: str
- :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
- key of the parent bridge running this pluggable transport instance,
- i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
- contains a hash digest for a ``@type bridge-extrainfo-document``, the
- latter of which contains the parameter of this pluggable transport in
- its ``transport`` line.
-
- :type methodname: str
- :ivar methodname: The canonical "name" for this pluggable transport,
- i.e. the one which would be specified in a torrc file. For example,
- ``"obfs2"``, ``"obfs3"``, ``"scramblesuit"`` would all be pluggable
- transport method names.
-
- :type address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
- :ivar address: The IP address of the transport. Currently (as of 20 March
- 2014), there are no known, widely-deployed pluggable transports which
- support IPv6. Ergo, this is very likely going to be an IPv4 address.
-
- :type port: int
- :ivar port: A integer specifying the port which this pluggable transport
- is listening on. (This should likely be whatever port the bridge
- specified in its ``ServerTransportPlugin`` torrc line, unless the
- pluggable transport is running in "managed" mode.)
-
- :type arguments: dict
- :ivar arguments: Some PTs can take additional arguments, which must be
- distributed to the client out-of-band. These are present in the
- ``@type bridge-extrainfo-document``, in the ``transport`` line like
- so::
-
- METHOD SP ADDR ":" PORT SP [K=V[,K=V[,K=V[â¦]]]]
-
- where K is the key in **arguments**, and V is the value. For example,
- in the case of ``scramblesuit``, for which the client must supply a
- shared secret to the ``scramblesuit`` instance running on the bridge,
- the **arguments** would be something like::
-
- {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
- """
-
- def __init__(self, fingerprint=None, methodname=None,
- address=None, port=None, arguments=None):
- """Create a ``PluggableTransport`` describing a PT running on a bridge.
-
- :param str fingerprint: The uppercased, hexadecimal fingerprint of the
- identity key of the parent bridge running this pluggable transport.
- :param str methodname: The canonical "name" for this pluggable
- transport. See :data:`methodname`.
- :param str address: The IP address of the transport. See
- :data:`address`.
- :param int port: A integer specifying the port which this pluggable
- transport is listening on.
- :param dict arguments: Any additional arguments which the PT takes,
- which must be distributed to the client out-of-band. See
- :data:`arguments`.
- """
- super(PluggableTransport, self).__init__()
- self._methodname = None
- self._blockedIn = {}
-
- self.fingerprint = fingerprint
- self.address = address
- self.port = port
- self.methodname = methodname
- self.arguments = arguments
-
- # Because we can intitialise this class with the __init__()
- # parameters, or use the ``updateFromStemTransport()`` method, we'll
- # only use the ``_runChecks()`` method now if we were initialised with
- # parameters:
- if (fingerprint or address or port or methodname or arguments):
- self._runChecks()
-
- def _parseArgumentsIntoDict(self, argumentList):
- """Convert a list of Pluggable Transport arguments into a dictionary
- suitable for :data:`arguments`.
-
- :param list argumentList: A list of Pluggable Transport
- arguments. There might be multiple, comma-separated ``K=V``
- Pluggable Transport arguments in a single item in the
- **argumentList**, or each item might be its own ``K=V``; we don't
- care and we should be able to parse it either way.
- :rtype: dict
- :returns: A dictionary of all the ``K=V`` Pluggable Transport
- arguments.
- """
- argDict = {}
-
- # PT arguments are comma-separated in the extrainfo
- # descriptors. While there *shouldn't* be anything after them that was
- # separated by a space (and hence would wind up being in a different
- # item in `arguments`), if there was we'll join it to the rest of the
- # PT arguments with a comma so that they are parsed as if they were PT
- # arguments as well:
- allArguments = ','.join(argumentList)
-
- for arg in allArguments.split(','):
- if arg: # It might be an empty string
- try:
- key, value = arg.split('=')
- except ValueError:
- logging.warn(" Couldn't parse K=V from PT arg: %r" % arg)
- else:
- logging.debug(" Parsed PT Argument: %s: %s" % (key, value))
- argDict[key] = value
-
- return argDict
-
- def _runChecks(self):
- """Validate that we were initialised with acceptable parameters.
-
- We currently check that:
-
- 1. The :data:`port` is an integer, and that it is between the values
- of ``0`` and ``65535`` (inclusive).
-
- 2. The :data:`arguments` is a dictionary.
-
- 3. The :data:`arguments` do not contain non-ASCII or control
- characters or double quotes or backslashes, in keys or
- in values.
-
- :raises MalformedPluggableTransport: if any of the above checks fails.
- """
- if not self.fingerprint:
- raise MalformedPluggableTransport(
- ("Cannot create %s without owning Bridge fingerprint!")
- % self.__class__.__name__)
-
- if not self.address:
- raise InvalidPluggableTransportIP(
- ("Cannot create PluggableTransport with address '%s'. "
- "type(address)=%s.") % (self.address, type(self.address)))
-
- if not self.port:
- raise MalformedPluggableTransport(
- ("Cannot create PluggableTransport without a valid port."))
-
- if not isinstance(self.arguments, dict):
- raise MalformedPluggableTransport(
- ("Cannot create PluggableTransport with arguments type: %s")
- % type(self.arguments))
-
- for item in self.arguments.items():
- kv = ''.join(item)
- if not isascii_noncontrol(kv):
- raise MalformedPluggableTransport(
- ("Cannot create PluggableTransport with non-ASCII or "
- "control characters in arguments: %r=%r") % item)
- if '"' in kv or '\\' in kv:
- raise MalformedPluggableTransport(
- ("Cannot create PluggableTransport with double quotes or "
- "backslashes in arguments: %r=%r") % item)
-
- if not self._checkArguments():
- raise MalformedPluggableTransport(
- ("Can't use %s transport with missing arguments. Arguments: "
- "%s") % (self.methodname, ' '.join(self.arguments.keys())))
-
- def _checkArguments(self):
- """This method is a temporary fix for PTs with missing arguments
- (see `#13202 <https://bugs.torproject.org/13202`_). This method can
- be removed after Tor-0.2.4.x is deprecated.
- """
- # obfs4 requires (iat-mode && (cert || (node-id && public-key))):
- if self.methodname == 'obfs4':
- if self.arguments.get('iat-mode'):
- if (self.arguments.get('cert') or \
- (self.arguments.get('node-id') and self.arguments.get('public-key'))):
- return True
- # scramblesuit requires (password):
- elif self.methodname == 'scramblesuit':
- if self.arguments.get('password'):
- return True
- else:
- return True
-
- return False
-
- @property
- def methodname(self):
- """Get this :class:`PluggableTransport`'s methodname.
-
- :rtype: str
- :returns: The (lowercased) methodname of this ``PluggableTransport``,
- i.e. ``"obfs3"``, ``"scramblesuit"``, etc.
- """
- return self._methodname
-
- @methodname.setter
- def methodname(self, value):
- """Set this ``PluggableTransport``'s methodname.
-
- .. hint:: The **value** will be automatically lowercased.
-
- :param str value: The new methodname.
- """
- if value:
- try:
- self._methodname = value.lower()
- except (AttributeError, TypeError):
- raise TypeError("methodname must be a str or unicode")
-
- def getTransportLine(self, includeFingerprint=True, bridgePrefix=False):
- """Get a Bridge Line for this :class:`PluggableTransport`.
-
- .. glossary::
-
- Bridge Line
- A "Bridge Line" is how BridgeDB refers to lines in a ``torrc``
- file which should begin with the word ``"Bridge"``, and it is how
- a client tells their Tor process that they would like to use a
- particular bridge.
-
- .. note:: If **bridgePrefix** is ``False``, this method does not
- return lines which are prefixed with the word 'bridge', as they
- would be in a torrc file. Instead, lines returned look like this::
-
- obfs3 245.102.100.252:23619 59ca743e89b508e16b8c7c6d2290efdfd14eea98
-
- This was made configurable to fix Vidalia being a brain-damaged
- piece of shit (#5851_). TorLaucher replaced Vidalia soon after,
- and TorLauncher is intelligent enough to understand
- :term:`Bridge Line`s regardless of whether or not they are prefixed
- with the word "Bridge".
-
- .. _#5851: https://bugs.torproject.org/5851
-
- :param bool includeFingerprints: If ``True``, include the digest of
- this bridges public identity key in the torrc line.
- :param bool bridgePrefix: If ``True``, add ``'Bridge '`` to the
- beginning of each returned line (suitable for pasting directly
- into a ``torrc`` file).
- :rtype: str
- :returns: A configuration line for adding this Pluggable Transport
- into a ``torrc`` file.
- """
- sections = []
-
- if bridgePrefix:
- sections.append('Bridge')
-
- if self.address.version == 6:
- # If the address was IPv6, put brackets around it:
- host = '%s [%s]:%d' % (self.methodname, self.address, self.port)
- else:
- host = '%s %s:%d' % (self.methodname, self.address, self.port)
- sections.append(host)
-
- if includeFingerprint:
- sections.append(self.fingerprint)
-
- for key, value in self.arguments.items():
- sections.append('%s=%s' % (key, value))
-
- line = ' '.join(sections)
-
- return line
-
- def updateFromStemTransport(self, fingerprint, methodname, kitchenSink):
- """Update this :class:`PluggableTransport` from the data structure
- which Stem uses.
-
- Stem's
- :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
- parses extrainfo ``transport`` lines into a dictionary with the
- following structure::
-
- {u'obfs2': (u'34.230.223.87', 37339, []),
- u'obfs3': (u'34.230.223.87', 37338, []),
- u'obfs4': (u'34.230.223.87', 37341, [
- (u'iat-mode=0,'
- u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
- u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
- u'scramblesuit': (u'34.230.223.87', 37340, [
- u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
-
- This method will initialise this class from the dictionary key
- (**methodname**) and its tuple of values (**kitchenSink**).
-
- :param str fingerprint: The uppercased, hexadecimal fingerprint of the
- identity key of the parent bridge running this pluggable transport.
- :param str methodname: The :data:`methodname` of this Pluggable
- Transport.
- :param tuple kitchenSink: Everything else that was on the
- ``transport`` line in the bridge's extrainfo descriptor, which
- Stem puts into the 3-tuples shown in the example above.
- """
- self.fingerprint = str(fingerprint)
- self.methodname = str(methodname)
- self.address = kitchenSink[0]
-
- port = kitchenSink[1]
- if port == 'anyport': # IDK. Stem, WTF?
- port = 0
-
- self.port = int(port)
- self.arguments = self._parseArgumentsIntoDict(kitchenSink[2])
- self._runChecks()
-
-
- at implementer(IBridge)
-class BridgeBase(BridgeAddressBase):
- """The base class for all bridge implementations."""
-
- def __init__(self):
- super(BridgeBase, self).__init__()
-
- self._nickname = None
- self._orPort = None
- self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
- self.dirPort = 0 # ``DirPort`` set to ``0``
- self.orAddresses = []
- self.transports = []
- self.flags = Flags()
-
- @property
- def nickname(self):
- """Get this Bridge's nickname.
-
- :rtype: str
- :returns: The Bridge's nickname.
- """
- return self._nickname
-
- @nickname.setter
- def nickname(self, value):
- """Set this Bridge's nickname to **value**.
-
- .. note:: We don't need to call
- :func:`bridgedb.parse.nickname.isValidRouterNickname() since Stem
- will check nickname specification conformity.
-
- :param str value: The nickname of this Bridge.
- """
- self._nickname = value
-
- @nickname.deleter
- def nickname(self):
- """Reset this Bridge's nickname."""
- self._nickname = None
-
- @property
- def orPort(self):
- """Get this bridge's ORPort.
-
- :rtype: int
- :returns: This Bridge's default ORPort.
- """
- return self.port
-
- @orPort.setter
- def orPort(self, value):
- """Set this Bridge's ORPort.
-
- :param int value: The Bridge's ORPort.
- """
- self.port = value
-
- @orPort.deleter
- def orPort(self):
- """Reset this Bridge's ORPort."""
- del self.port
-
-
- at implementer(IBridge)
-class BridgeBackwardsCompatibility(BridgeBase):
- """Backwards compatibility methods for the old Bridge class."""
-
- def __init__(self, nickname=None, ip=None, orport=None,
- fingerprint=None, id_digest=None, or_addresses=None):
- """Create a Bridge which is backwards compatible with the old Bridge class
- implementation.
-
- .. info: For backwards compatibility, `nickname`, `ip`, and `orport`
- must be the first, second, and third arguments, respectively. The
- `fingerprint` and `id_digest` were previously kwargs, and are also
- provided for backwards compatibility. New calls to
- :meth:`__init__` *should* avoid using these kwargs, and instead
- use the methods :meth:`updateFromNetworkStatus`,
- :meth:`updateFromServerDescriptor`, and
- :meth:`updateFromExtraInfoDescriptor`.
- """
- super(BridgeBackwardsCompatibility, self).__init__()
-
- self.desc_digest = None
- self.ei_digest = None
- self.running = False
- self.stable = False
-
- if nickname or ip or orport or fingerprint or id_digest:
- self._backwardsCompatible(nickname=nickname, address=ip,
- orPort=orport, fingerprint=fingerprint,
- idDigest=id_digest,
- orAddresses=or_addresses)
-
- def _backwardsCompatible(self, nickname=None, address=None, orPort=None,
- fingerprint=None, idDigest=None,
- orAddresses=None):
- """Functionality for maintaining backwards compatibility with the older
- version of this class (see :class:`bridgedb.test.deprecated.Bridge`).
- """
- self.nickname = nickname
- self.orPort = orPort
- if address:
- self.address = address
-
- if idDigest:
- if not fingerprint:
- if not len(idDigest) == 20:
- raise TypeError("Bridge with invalid ID")
- self.fingerprint = toHex(idDigest)
- elif fingerprint:
- if not isValidFingerprint(fingerprint):
- raise TypeError("Bridge with invalid fingerprint (%r)"
- % fingerprint)
- self.fingerprint = fingerprint.lower()
- else:
- raise TypeError("Bridge with no ID")
-
- if orAddresses and isinstance(orAddresses, dict):
- for ip, portlist in orAddresses.items():
- validAddress = isIPAddress(ip, compressed=False)
- if validAddress:
- # The old code expected a `bridgedb.parse.addr.PortList`:
- if isinstance(portlist, PortList):
- for port in portlist.ports:
- self.orAddresses.append(
- (validAddress, port, validAddress.version,))
- elif isinstance(portlist, int):
- self.orAddresses.append(
- (validAddress, portlist, validAddress.version,))
- else:
- logging.warn("Can't parse port for ORAddress %r: %r"
- % (ip, portlist))
-
- def getID(self):
- """Get the binary encoded form of this ``Bridge``'s ``fingerprint``.
-
- This method is provided for backwards compatibility and should not
- be relied upon.
- """
- return self.identity
-
- def setDescriptorDigest(self, digest):
- """Set this ``Bridge``'s server-descriptor digest.
-
- This method is provided for backwards compatibility and should not
- be relied upon.
- """
- self.desc_digest = digest # old attribute for backwards compat
- self.descriptorDigest = digest # new attribute
-
- def setExtraInfoDigest(self, digest):
- """Set this ``Bridge``'s extrainfo digest.
-
- This method is provided for backwards compatibility and should not
- be relied upon.
- """
- self.ei_digest = digest # old attribute for backwards compat
- self.extrainfoDigest = digest # new attribute
-
- def setStatus(self, running=None, stable=None):
- """Set this ``Bridge``'s "Running" and "Stable" flags.
-
- This method is provided for backwards compatibility and should not
- be relied upon.
- """
- if running is not None:
- self.running = bool(running)
- self.flags.running = bool(running)
- if stable is not None:
- self.stable = bool(stable)
- self.flags.stable = bool(stable)
-
- def getConfigLine(self, includeFingerprint=False, addressClass=None,
- request=None, transport=None):
- """Get a vanilla bridge line for this ``Bridge``.
-
- This method is provided for backwards compatibility and should not
- be relied upon.
-
- The old ``bridgedb.Bridges.Bridge.getConfigLine()`` method didn't know
- about :class:`~bridgedb.bridgerequest.BridgeRequestBase`s, and so this
- modified version is backwards compatible by creating a
- :class:`~bridgedb.bridgerequest.BridgeRequestBase` for
- :meth:`getBridgeLine`. The default parameters are the same as they
- were in the old ``bridgedb.Bridges.Bridge`` class.
-
- :param bool includeFingerprint: If ``True``, include the
- ``fingerprint`` of this :class:`Bridge` in the returned bridge
- line.
- :type addressClass: :class:`ipaddr.IPv4Address` or
- :class:`ipaddr.IPv6Address`.
- :param addressClass: Type of address to choose.
- :param str request: A string (somewhat) unique to this request,
- e.g. email-address or ``HTTPSDistributor.getSubnet(ip)``. In
- this case, this is not a :class:`~bridgerequest.BridgeRequestBase`
- (as might be expected) but the equivalent of
- :data:`bridgerequest.BridgeRequestBase.client`.
- :param str transport: A pluggable transport method name.
- """
- ipVersion = 6 if addressClass is ipaddr.IPv6Address else 4
- bridgeRequest = bridgerequest.BridgeRequestBase(ipVersion)
- bridgeRequest.client = request if request else bridgeRequest.client
- bridgeRequest.isValid(True)
-
- if transport:
- bridgeRequest.withPluggableTransportType(transport)
-
- bridgeRequest.generateFilters()
- bridgeLine = self.getBridgeLine(bridgeRequest, includeFingerprint)
- return bridgeLine
-
- # Bridge Stability (`#5482 <https://bugs.torproject.org>`_) properties.
- @property
- def familiar(self):
- """A bridge is "familiar" if 1/8 of all active bridges have appeared
- more recently than it, or if it has been around for a Weighted Time of
- eight days.
- """
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).familiar
-
- @property
- def wfu(self):
- """Weighted Fractional Uptime"""
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).weightedFractionalUptime
-
- @property
- def weightedTime(self):
- """Weighted Time"""
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).weightedTime
-
- @property
- def wmtbac(self):
- """Weighted Mean Time Between Address Change"""
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).wmtbac
-
- @property
- def tosa(self):
- """The Time On Same Address (TOSA)"""
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).tosa
-
- @property
- def weightedUptime(self):
- """Weighted Uptime"""
- with bridgedb.Storage.getDB() as db: # pragma: no cover
- return db.getBridgeHistory(self.fingerprint).weightedUptime
-
-
- at implementer(IBridge)
-class Bridge(BridgeBackwardsCompatibility):
- """A single bridge, and all the information we have for it.
-
- :type fingerprint: str or ``None``
- :ivar fingerprint: This ``Bridge``'s fingerprint, in lowercased
- hexadecimal format.
- :type nickname: str or ``None``
- :ivar nickname: This ``Bridge``'s router nickname.
- :type socksPort: int
- :ivar socksPort: This ``Bridge``'s SOCKSPort. Should always be ``0``.
- :type dirPort: int
- :ivar dirPort: This ``Bridge``'s DirPort. Should always be ``0``.
- :type orAddresses: list
- :ivar orAddresses: A list of 3-tuples in the form::
- (ADDRESS, PORT, IP_VERSION)
- where:
- * ADDRESS is an :class:`ipaddr.IPAddress`,
- * PORT is an ``int``,
- * IP_VERSION is either ``4`` or ``6``.
- :type transports: list
- :ivar transports: A list of :class:`PluggableTransport`s, one for each
- transport that this :class:`Bridge` currently supports.
- :type flags: :class:`~bridgedb.bridges.Flags`
- :ivar flags: All flags assigned by the BridgeAuthority to this
- :class:`Bridge`.
- :type hibernating: bool
- :ivar hibernating: ``True`` if this :class:`Bridge` is hibernating and not
- currently serving clients (e.g. if the Bridge hit its configured
- ``RelayBandwidthLimit``); ``False`` otherwise.
- :type _blockedIn: dict
- :ivar _blockedIn: A dictionary of ``ADDRESS:PORT`` pairs to lists of
- lowercased, two-letter country codes (e.g. ``"us"``, ``"gb"``,
- ``"cn"``, etc.) which that ``ADDRESS:PORT`` pair is blocked in.
- :type contact: str or ``None``
- :ivar contact: The contact information for the this Bridge's operator.
- :type family: set or ``None``
- :ivar family: The fingerprints of other Bridges related to this one.
- :type platform: str or ``None``
- :ivar platform: The ``platform`` line taken from the
- ``@type bridge-server-descriptor``, e.g.
- ``'Tor 0.2.5.4-alpha on Linux'``.
- :type software: :api:`stem.version.Version` or ``None``
- :ivar software: The OR version portion of the ``platform`` line.
- :type os: str or None
- :ivar os: The OS portion of the ``platform`` line.
- """
- #: (bool) If ``True``, check that the signature of the bridge's
- #: ``@type bridge-server-descriptor`` is valid and that the signature was
- #: created with the ``signing-key`` contained in that descriptor.
- _checkServerDescriptorSignature = True
-
- def __init__(self, *args, **kwargs):
- """Create and store information for a new ``Bridge``.
-
- .. info: For backwards compatibility, `nickname`, `ip`, and `orport`
- must be the first, second, and third arguments, respectively. The
- `fingerprint` and `id_digest` were previously kwargs, and are also
- provided for backwards compatibility. New calls to
- :meth:`__init__` *should* avoid using these kwargs, and instead
- use the methods :meth:`updateFromNetworkStatus`,
- :meth:`updateFromServerDescriptor`, and
- :meth:`updateFromExtraInfoDescriptor`.
- """
- super(Bridge, self).__init__(*args, **kwargs)
-
- self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
- self.dirPort = 0 # ``DirPort`` set to ``0``
- self.orAddresses = []
- self.transports = []
- self.flags = Flags()
- self.hibernating = False
- self._blockedIn = {}
-
- self.bandwidth = None
- self.bandwidthAverage = None
- self.bandwidthBurst = None
- self.bandwidthObserved = None
-
- self.contact = None
- self.family = None
- self.platform = None
- self.software = None
- self.os = None
- self.uptime = None
- self.bridgeIPs = None
-
- self.onionKey = None
- self.ntorOnionKey = None
- self.signingKey = None
-
- self.descriptors = {'networkstatus': None,
- 'server': None,
- 'extrainfo': None}
-
- #: The hash digest of this bridge's ``@type bridge-server-descriptor``,
- #: as signed (but not including the signature). This is found in the
- #: 'r'-line of this bridge's ``@type bride-networkstatus`` document,
- #: however it is stored here re-encoded from base64 into hexadecimal,
- #: and converted to uppercase.
- self.descriptorDigest = None
- self.extrainfoDigest = None
-
- def __str__(self):
- """Return a pretty string representation that identifies this Bridge.
-
- .. warning:: With safelogging disabled, the returned string contains
- the bridge's fingerprint, which should be handled with care.
-
- If safelogging is enabled, the returned string will have the SHA-1
- hash of the bridge's fingerprint (a.k.a. a hashed fingerprint).
-
- Hashed fingerprints will be prefixed with ``'$$'``, and the real
- fingerprints are prefixed with ``'$'``.
-
- :rtype: str
- :returns: A string in the form:
- :data:`nickname```.$``:data:`fingerprint`.
- """
- nickname = self.nickname if self.nickname else 'Unnamed'
- prefix = '$'
- separator = "~"
- fingerprint = self.fingerprint
-
- if safelog.safe_logging:
- prefix = '$$'
- if fingerprint:
- fingerprint = hashlib.sha1(fingerprint).hexdigest().upper()
-
- if not fingerprint:
- fingerprint = '0' * 40
-
- return prefix + fingerprint + separator + nickname
-
- def _checkServerDescriptor(self, descriptor):
- # If we're parsing the server-descriptor, require a networkstatus
- # document:
- if not self.descriptors['networkstatus']:
- raise ServerDescriptorWithoutNetworkstatus(
- ("We received a server-descriptor for bridge '%s' which has "
- "no corresponding networkstatus document.") %
- descriptor.fingerprint)
-
- ns = self.descriptors['networkstatus']
-
- # We must have the digest of the server-descriptor from the
- # networkstatus document:
- if not self.descriptorDigest:
- raise MissingServerDescriptorDigest(
- ("The server-descriptor digest was missing from networkstatus "
- "document for bridge '%s'.") % descriptor.fingerprint)
-
- digested = descriptor.digest()
- # The digested server-descriptor must match the digest reported by the
- # BridgeAuthority in the bridge's networkstatus document:
- if not self.descriptorDigest == digested:
- raise ServerDescriptorDigestMismatch(
- ("The server-descriptor digest for bridge '%s' doesn't match "
- "the digest reported by the BridgeAuthority in the "
- "networkstatus document: \n"
- "Digest reported in networkstatus: %s\n"
- "Actual descriptor digest: %s\n") %
- (descriptor.fingerprint, self.descriptorDigest, digested))
-
- def _constructBridgeLine(self, addrport, includeFingerprint=True,
- bridgePrefix=False):
- """Construct a :term:`Bridge Line` from an (address, port) tuple.
-
- :param tuple addrport: A 3-tuple of ``(address, port, ipversion)``
- where ``address`` is a string, ``port`` is an integer, and
- ``ipversion`` is a integer (``4`` or ``6``).
- :param bool includeFingerprint: If ``True``, include the
- ``fingerprint`` of this :class:`Bridge` in the returned bridge
- line.
- :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
- with ``'Bridge '``.
- :rtype: string
- :returns: A bridge line suitable for adding into a ``torrc`` file or
- Tor Launcher.
- """
- if not addrport:
- return
-
- address, port, version = addrport
-
- if not address or not port:
- return
-
- bridgeLine = []
-
- if bridgePrefix:
- bridgeLine.append('Bridge')
-
- if version == 4:
- bridgeLine.append("%s:%d" % (str(address), port))
- elif version == 6:
- bridgeLine.append("[%s]:%d" % (str(address), port))
-
- if includeFingerprint:
- bridgeLine.append("%s" % self.fingerprint)
-
- return ' '.join(bridgeLine)
-
- @classmethod
- def _getBlockKey(cls, address, port):
- """Format an **address**:**port** pair appropriately for use as a key
- in the :data:`_blockedIn` dictionary.
-
- :param address: An IP address of this :class:`Bridge` or one of its
- :data:`transports`.
- :param port: A port.
- :rtype: str
- :returns: A string in the form ``"ADDRESS:PORT"`` for IPv4 addresses,
- and ``"[ADDRESS]:PORT`` for IPv6.
- """
- if isIPv6(str(address)):
- key = "[%s]:%s" % (address, port)
- else:
- key = "%s:%s" % (address, port)
-
- return key
-
- def _getTransportForRequest(self, bridgeRequest):
- """If a transport was requested, return the correlated
- :term:`Bridge Line` based upon the client identifier in the
- **bridgeRequest**.
-
- .. warning:: If this bridge doesn't have any of the requested
- pluggable transport type (optionally, not blocked in whichever
- countries the user doesn't want their bridges to be blocked in),
- then this method returns ``None``. This should only happen
- rarely, because the bridges are filtered into the client's
- hashring based on the **bridgeRequest** options.
-
- :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
- :param bridgeRequest: A ``BridgeRequest`` which stores all of the
- client-specified options for which type of bridge they want to
- receive.
- :raises PluggableTransportUnavailable: if this bridge doesn't have any
- of the requested pluggable transport type. This shouldn't happen
- because the bridges are filtered into the client's hashring based
- on the **bridgeRequest** options, however, this is useful in the
- unlikely event that it does happen, so that the calling function
- can fetch an additional bridge from the hashring as recompense for
- what would've otherwise been a missing :term:`Bridge Line`.
- :rtype: str or ``None``
- :returns: If no transports were requested, return ``None``, otherwise
- return a :term:`Bridge Line` for the requested pluggable transport
- type.
- """
- desired = bridgeRequest.justOnePTType()
- ipVersion = bridgeRequest.ipVersion
-
- logging.info("Bridge %s answering request for %s transport..." %
- (self, desired))
-
- # Filter all this Bridge's ``transports`` according to whether or not
- # their ``methodname`` matches the requested transport:
- transports = filter(lambda pt: pt.methodname == desired, self.transports)
- # Filter again for whichever of IPv4 or IPv6 was requested:
- transports = filter(lambda pt: pt.address.version == ipVersion, transports)
-
- if not transports:
- raise PluggableTransportUnavailable(
- ("Client requested transport %s, but bridge %s doesn't "
- "have any of that transport!") % (desired, self))
-
- unblocked = []
- for pt in transports:
- if not sum([self.transportIsBlockedIn(cc, pt.methodname)
- for cc in bridgeRequest.notBlockedIn]):
- unblocked.append(pt)
-
- if unblocked:
- position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
- return transports[position % len(unblocked)]
- else:
- logging.warn(("Client requested transport %s%s, but bridge %s "
- "doesn't have any of that transport!") %
- (desired, " not blocked in %s" %
- " ".join(bridgeRequest.notBlockedIn)
- if bridgeRequest.notBlockedIn else "", self))
-
- def _getVanillaForRequest(self, bridgeRequest):
- """If vanilla bridges were requested, return the assigned
- :term:`Bridge Line` based upon the client identifier in the
- **bridgeRequest**.
-
- :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
- :param bridgeRequest: A ``BridgeRequest`` which stores all of the
- client-specified options for which type of bridge they want to
- receive.
- :rtype: str or ``None``
- :returns: If no transports were requested, return ``None``, otherwise
- return a :term:`Bridge Line` for the requested pluggable transport
- type.
- """
- logging.info(
- "Bridge %s answering request for IPv%s vanilla address..." %
- (self, bridgeRequest.ipVersion))
-
- addresses = []
-
- for address, port, version in self.allVanillaAddresses:
- # Filter ``allVanillaAddresses`` by whether IPv4 or IPv6 was requested:
- if version == bridgeRequest.ipVersion:
- # Determine if the address is blocked in any of the country
- # codes. Because :meth:`addressIsBlockedIn` returns a bool,
- # we get a list like: ``[True, False, False, True]``, and
- # because bools are ints, they may be summed. What we care
- # about is that there are no ``True``s, for any country code,
- # so we check that the sum is zero (meaning the list was full
- # of ``False``s).
- #
- # XXX Do we want to add a method for this construct?
- if not sum([self.addressIsBlockedIn(cc, address, port)
- for cc in bridgeRequest.notBlockedIn]):
- addresses.append((address, port, version))
-
- if addresses:
- # Use the client's unique data to HMAC them into their position in
- # the hashring of filtered bridges addresses:
- position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
- vanilla = addresses[position % len(addresses)]
- logging.info("Got vanilla bridge for client.")
-
- return vanilla
-
- def _updateORAddresses(self, orAddresses):
- """Update this :class:`Bridge`'s :data:`orAddresses` attribute from a
- 3-tuple (i.e. as Stem creates when parsing descriptors).
-
- :param tuple orAddresses: A 3-tuple of: an IP address, a port number,
- and a boolean (``False`` if IPv4, ``True`` if IPv6).
- :raises FutureWarning: if any IPv4 addresses are found. As of
- tor-0.2.5, only IPv6 addresses should be found in a descriptor's
- `ORAddress` line.
- """
- for (address, port, ipVersion) in orAddresses:
- version = 6
- if not ipVersion: # `False` means IPv4; `True` means IPv6.
- # See https://bugs.torproject.org/9380#comment:27
- warnings.warn(FutureWarning((
- "Got IPv4 address in 'a'/'or-address' line! Descriptor "
- "format may have changed!")))
- version = 4
-
- validatedAddress = isIPAddress(address, compressed=False)
- if validatedAddress:
- self.orAddresses.append( (validatedAddress, port, version,) )
-
- @property
- def allVanillaAddresses(self):
- """Get all valid, non-PT address:port pairs for this bridge.
-
- :rtype: list
- :returns: All of this bridge's ORAddresses, as well as its ORPort IP
- address and port.
- """
- # Force deep-copying of the orAddresses. Otherwise, the later use of
- # ``addresses.append()`` is both non-reentrant and non-idempotent, as
- # it would change the value of ``Bridge.orAddresses``, as well as
- # append a (possibly updated, if ``Bridge.address`` or
- # ``Bridge.orPort`` changed!) new copy of the bridge's primary
- # ORAddress each time this property is called.
- addresses = self.orAddresses[:]
-
- # Add the default ORPort address. It will always be IPv4, otherwise
- # Stem should have raised a ValueError during parsing. But for
- # testability, check which type it is:
- version = 4
- if isIPv6(self.address):
- version = 6
-
- addresses.append((self.address, self.orPort, version))
-
- return addresses
-
- def assertOK(self):
- """Perform some additional validation on this bridge's info.
-
- We require that:
-
- 1. Any IP addresses contained in :data:`orAddresses` are valid,
- according to :func:`~bridgedb.parse.addr.isValidIP`.
-
- 2. Any ports in :data:`orAddresses` are between ``1`` and ``65535``
- (inclusive).
-
- 3. All IP version numbers given in :data:`orAddresses` are either
- ``4`` or ``6``.
-
- .. todo:: This should probably be reimplemented as a property that
- automatically sanitises the values for each ORAddress, as is done
- for :property:`bridgedb.bridges.BridgeAddressBase.address` and
- :property:`bridgedb.bridges.BridgeBase.orPort`.
-
- :raises MalformedBridgeInfo: if something was found to be malformed or
- invalid.
- """
- malformed = []
-
- for (address, port, version) in self.orAddresses:
- if not isValidIP(address):
- malformed.append("Invalid ORAddress address: '%s'" % address)
- if not (0 <= port <= 65535):
- malformed.append("Invalid ORAddress port: '%d'" % port)
- if not version in (4, 6):
- malformed.append("Invalid ORAddress IP version: %r" % version)
-
- if malformed:
- raise MalformedBridgeInfo('\n'.join(malformed))
-
- def getBridgeLine(self, bridgeRequest, includeFingerprint=True,
- bridgePrefix=False):
- """Return a valid :term:`Bridge Line` for a client to give to Tor
- Launcher or paste directly into their ``torrc``.
-
- This is a helper method to call either :meth:`_getTransportForRequest`
- or :meth:`_getVanillaForRequest` depending on whether or not any
- :class:`PluggableTransport`s were requested in the
- :class:`bridgeRequest <bridgedb bridgerequest.BridgeRequestBase>`, and
- then construct the :term:`Bridge Line` accordingly.
-
- :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
- :param bridgeRequest: A ``BridgeRequest`` which stores all of the
- client-specified options for which type of bridge they want to
- receive.
- :param bool includeFingerprint: If ``True``, include the
- ``fingerprint`` of this :class:`Bridge` in the returned bridge
- line.
- :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
- with ``'Bridge '``.
- """
- if not bridgeRequest.isValid():
- logging.info("Bridge request was not valid. Dropping request.")
- return # XXX raise error perhaps?
-
- bridgeLine = None
-
- if bridgeRequest.transports:
- pt = self._getTransportForRequest(bridgeRequest)
- if pt:
- bridgeLine = pt.getTransportLine(includeFingerprint,
- bridgePrefix)
- else:
- addrport = self._getVanillaForRequest(bridgeRequest)
- bridgeLine = self._constructBridgeLine(addrport,
- includeFingerprint,
- bridgePrefix)
- return bridgeLine
-
- def _addBlockByKey(self, key, countryCode):
- """Create or append to the list of blocked countries for a **key**.
-
- :param str key: The key to lookup in the :data:`Bridge._blockedIn`
- dictionary. This should be in the form returned by
- :classmethod:`_getBlockKey`.
- :param str countryCode: A two-character country code specifier.
- """
- if self._blockedIn.has_key(key):
- self._blockedIn[key].append(countryCode.lower())
- else:
- self._blockedIn[key] = [countryCode.lower(),]
-
- def addressIsBlockedIn(self, countryCode, address, port):
- """Determine if a specific (address, port) tuple is blocked in
- **countryCode**.
-
- :param str countryCode: A two-character country code specifier.
- :param str address: An IP address (presumedly one used by this
- bridge).
- :param int port: A port.
- :rtype: bool
- :returns: ``True`` if the **address**:**port** pair is blocked in
- **countryCode**, ``False`` otherwise.
- """
- key = self._getBlockKey(address, port)
-
- try:
- if countryCode.lower() in self._blockedIn[key]:
- logging.info("Vanilla address %s of bridge %s blocked in %s."
- % (key, self, countryCode.lower()))
- return True
- except KeyError:
- return False # That address:port pair isn't blocked anywhere
-
- return False
-
- def transportIsBlockedIn(self, countryCode, methodname):
- """Determine if any of a specific type of pluggable transport which
- this bridge might be running is blocked in a specific country.
-
- :param str countryCode: A two-character country code specifier.
- :param str methodname: The type of pluggable transport to check,
- i.e. ``'obfs3'``.
- :rtype: bool
- :returns: ``True`` if any address:port pair which this bridge is
- running a :class:`PluggableTransport` on is blocked in
- **countryCode**, ``False`` otherwise.
- """
- for pt in self.transports:
- if pt.methodname == methodname.lower():
- if self.addressIsBlockedIn(countryCode, pt.address, pt.port):
- logging.info("Transport %s of bridge %s is blocked in %s."
- % (pt.methodname, self, countryCode))
- return True
- return False
-
- def isBlockedIn(self, countryCode):
- """Determine, according to our stored bridge reachability reports, if
- any of the address:port pairs used by this :class:`Bridge` or it's
- :data:`transports` are blocked in **countryCode**.
-
- :param str countryCode: A two-character country code specifier.
- :rtype: bool
- :returns: ``True`` if at least one address:port pair used by this
- bridge is blocked in **countryCode**; ``False`` otherwise.
- """
- # Check all supported pluggable tranport types:
- for methodname in self.supportedTransportTypes:
- if self.transportIsBlockedIn(countryCode.lower(), methodname):
- return True
-
- for address, port, version in self.allVanillaAddresses:
- if self.addressIsBlockedIn(countryCode.lower(), address, port):
- return True
-
- return False
-
- def setBlockedIn(self, countryCode, address=None, port=None, methodname=None):
- """Mark this :class:`Bridge` as being blocked in **countryCode**.
-
- By default, if called with no parameters other than a **countryCode**,
- we'll mark all this :class:`Bridge`'s :data:`allVanillaAddresses` and
- :data:`transports` as being blocked.
-
- Otherwise, we'll filter on any and all parameters given.
-
- If only a **methodname** is given, then we assume that all
- :data:`transports` with that **methodname** are blocked in
- **countryCode**. If the methodname is ``"vanilla"``, then we assume
- each address in data:`allVanillaAddresses` is blocked.
-
- :param str countryCode: A two-character country code specifier.
- :param address: An IP address of this Bridge or one of its
- :data:`transports`.
- :param port: A specific port that is blocked, if available. If the
- **port** is ``None``, then any address this :class:`Bridge` or its
- :class:`PluggableTransport`s has that matches the given **address**
- will be marked as block, regardless of its port. This parameter
- is ignored unless an **address** is given.
- :param str methodname: A :data:`PluggableTransport.methodname` to
- match. Any remaining :class:`PluggableTransport`s from
- :data:`transports` which matched the other parameters and now also
- match this **methodname** will be marked as being blocked in
- **countryCode**.
- """
- vanillas = self.allVanillaAddresses
- transports = self.transports
-
- if methodname:
- # Don't process the vanilla if we weren't told to do so:
- if not (methodname == 'vanilla') and not (address or port):
- vanillas = []
-
- transports = filter(lambda pt: methodname == pt.methodname, transports)
-
- if address:
- vanillas = filter(lambda ip: str(address) == str(ip[0]), vanillas)
- transports = filter(lambda pt: str(address) == str(pt.address), transports)
-
- if port:
- vanillas = filter(lambda ip: int(port) == int(ip[1]), vanillas)
- transports = filter(lambda pt: int(port) == int(pt.port), transports)
-
- for addr, port, _ in vanillas:
- key = self._getBlockKey(addr, port)
- logging.info("Vanilla address %s for bridge %s is now blocked in %s."
- % (key, self, countryCode))
- self._addBlockByKey(key, countryCode)
-
- for transport in transports:
- key = self._getBlockKey(transport.address, transport.port)
- logging.info("Transport %s %s for bridge %s is now blocked in %s."
- % (transport.methodname, key, self, countryCode))
- self._addBlockByKey(key, countryCode)
- transport._blockedIn[key] = self._blockedIn[key]
-
- def getDescriptorLastPublished(self):
- """Get the timestamp for when this bridge's last known server
- descriptor was published.
-
- :rtype: :type:`datetime.datetime` or ``None``
- :returns: A datetime object representing the timestamp of when the
- last known ``@type bridge-server-descriptor`` was published, or
- ``None`` if we have never seen a server descriptor for this
- bridge.
- """
- return getattr(self.descriptors['server'], 'published', None)
-
- def getExtrainfoLastPublished(self):
- """Get the timestamp for when this bridge's last known extrainfo
- descriptor was published.
-
- :rtype: :type:`datetime.datetime` or ``None``
- :returns: A datetime object representing the timestamp of when the
- last known ``@type bridge-extrainfo`` descriptor was published, or
- ``None`` if we have never seen an extrainfo descriptor for this
- bridge.
- """
- return getattr(self.descriptors['extrainfo'], 'published', None)
-
- def getNetworkstatusLastPublished(self):
- """Get the timestamp for when this bridge's last known networkstatus
- descriptor was published.
-
- :rtype: :type:`datetime.datetime` or ``None``
- :returns: A datetime object representing the timestamp of when the
- last known ``@type networkstatus-bridge`` document was published,
- or ``None`` if we have never seen a networkstatus document for
- this bridge.
- """
- return getattr(self.descriptors['networkstatus'], 'published', None)
-
- @property
- def supportedTransportTypes(self):
- """A deduplicated list of all the :data:`PluggableTranport.methodname`s
- which this bridge supports.
- """
- return list(set([pt.methodname for pt in self.transports]))
-
- def updateFromNetworkStatus(self, descriptor, ignoreNetworkstatus=False):
- """Update this bridge's attributes from a parsed networkstatus
- document.
-
- :type descriptor:
- :api:`stem.descriptors.router_status_entry.RouterStatusEntry`
- :param descriptor: The networkstatus document for this bridge.
- :param bool ignoreNetworkstatus: If ``True``, then ignore most of the
- information in the networkstatus document.
- """
- self.descriptors['networkstatus'] = descriptor
-
- # These fields are *only* found in the networkstatus document:
- self.flags.update(descriptor.flags)
- self.descriptorDigest = descriptor.digest
-
- if not ignoreNetworkstatus:
- self.bandwidth = descriptor.bandwidth
-
- # These fields are also found in the server-descriptor. We will prefer
- # to use the information taken later from the server-descriptor
- # because it is signed by the bridge. However, for now, we harvest all
- # the info we can:
- self.fingerprint = descriptor.fingerprint
-
- if not ignoreNetworkstatus:
- self.nickname = descriptor.nickname
- self.address = descriptor.address
- self.orPort = descriptor.or_port
- self._updateORAddresses(descriptor.or_addresses)
-
- def updateFromServerDescriptor(self, descriptor, ignoreNetworkstatus=False):
- """Update this bridge's info from an ``@type bridge-server-descriptor``.
-
- .. info::
- If :func:`~bridgedb.parse.descriptor.parseServerDescriptorFile` is
- called with ``validate=True``, then Stem will handle checking that
- the ``signing-key`` hashes to the ``fingerprint``. Stem will also
- check that the ``router-signature`` on the descriptor is valid,
- was created with the ``signing-key``, and is a signature of the
- correct digest of the descriptor document (it recalculates the
- digest for the descriptor to ensure that the signed one and the
- actual digest match).
-
- :type descriptor:
- :api:`stem.descriptor.server_descriptor.RelayDescriptor`
- :param descriptor: The bridge's server descriptor to gather data from.
- :raises MalformedBridgeInfo: If this Bridge has no corresponding
- networkstatus entry, or its **descriptor** digest didn't match the
- expected digest (from the networkstatus entry).
- """
- if ignoreNetworkstatus:
- try:
- self._checkServerDescriptor(descriptor)
- except (ServerDescriptorWithoutNetworkstatus,
- MissingServerDescriptorDigest,
- ServerDescriptorDigestMismatch) as ignored:
- logging.warn(ignored)
- else:
- self._checkServerDescriptor(descriptor)
-
- self.descriptors['server'] = descriptor
-
- # Replace the values which we harvested from the networkstatus
- # descriptor, because that one isn't signed with the bridge's identity
- # key.
- self.fingerprint = descriptor.fingerprint
- self.address = descriptor.address
- self.nickname = descriptor.nickname
- self.orPort = descriptor.or_port
- self._updateORAddresses(descriptor.or_addresses)
- self.hibernating = descriptor.hibernating
-
- self.onionKey = descriptor.onion_key
- self.ntorOnionKey = descriptor.ntor_onion_key
- self.signingKey = descriptor.signing_key
-
- self.bandwidthAverage = descriptor.average_bandwidth
- self.bandwidthBurst = descriptor.burst_bandwidth
- self.bandwidthObserved = descriptor.observed_bandwidth
-
- self.contact = descriptor.contact
- self.family = descriptor.family
- self.platform = descriptor.platform
- self.software = descriptor.tor_version
- self.os = descriptor.operating_system
- self.uptime = descriptor.uptime
-
- self.extrainfoDigest = descriptor.extra_info_digest
-
- def _verifyExtraInfoSignature(self, descriptor):
- """Verify the signature on the contents of this :class:`Bridge`'s
- ``@type bridge-extrainfo`` descriptor.
-
- :type descriptor:
- :api:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
- :param descriptor: An ``@type bridge-extrainfo`` descriptor for this
- :class:`Bridge`, parsed with Stem.
- :raises InvalidExtraInfoSignature: if the signature was invalid,
- missing, malformed, or couldn't be verified successfully.
- :returns: ``None`` if the signature was valid and verifiable.
- """
- # The blocksize is always 128 bits for a 1024-bit key
- BLOCKSIZE = 128
-
- TOR_SIGNING_KEY_HEADER = u'-----BEGIN RSA PUBLIC KEY-----\n'
- TOR_SIGNING_KEY_FOOTER = u'-----END RSA PUBLIC KEY-----'
- TOR_BEGIN_SIGNATURE = u'-----BEGIN SIGNATURE-----\n'
- TOR_END_SIGNATURE = u'-----END SIGNATURE-----\n'
-
- logging.info("Verifying extrainfo signature for %s..." % self)
-
- # Get the bytes of the descriptor signature without the headers:
- document, signature = descriptor.get_bytes().split(TOR_BEGIN_SIGNATURE)
- signature = signature.replace(TOR_END_SIGNATURE, '')
- signature = signature.replace('\n', '')
- signature = signature.strip()
-
- try:
- # Get the ASN.1 sequence:
- sequence = asn1.DerSequence()
-
- key = self.signingKey
- key = key.strip(TOR_SIGNING_KEY_HEADER)
- key = key.strip(TOR_SIGNING_KEY_FOOTER)
- key = key.replace('\n', '')
- key = base64.b64decode(key)
-
- sequence.decode(key)
-
- modulus = sequence[0]
- publicExponent = sequence[1]
-
- # The public exponent of RSA signing-keys should always be 65537,
- # but we're not going to turn them down if they want to use a
- # potentially dangerous exponent.
- if publicExponent != 65537: # pragma: no cover
- logging.warn("Odd RSA exponent in signing-key for %s: %s" %
- (self, publicExponent))
-
- # Base64 decode the signature:
- signatureDecoded = base64.b64decode(signature)
-
- # Convert the signature to a long:
- signatureLong = bytes_to_long(signatureDecoded)
-
- # Decrypt the long signature with the modulus and public exponent:
- decryptedInt = pow(signatureLong, publicExponent, modulus)
-
- # Then convert it back to a byte array:
- decryptedBytes = long_to_bytes(decryptedInt, BLOCKSIZE)
-
- # Remove the PKCS#1 padding from the signature:
- unpadded = removePKCS1Padding(decryptedBytes)
-
- # This is the hexadecimal SHA-1 hash digest of the descriptor document
- # as it was signed:
- signedDigest = codecs.encode(unpadded, 'hex_codec')
- actualDigest = hashlib.sha1(document).hexdigest()
-
- except Exception as error:
- logging.debug("Error verifying extrainfo signature: %s" % error)
- raise InvalidExtraInfoSignature(
- "Extrainfo signature for %s couldn't be decoded: %s" %
- (self, signature))
- else:
- if signedDigest != actualDigest:
- raise InvalidExtraInfoSignature(
- ("The extrainfo digest signed by bridge %s didn't match the "
- "actual digest.\nSigned digest: %s\nActual digest: %s") %
- (self, signedDigest, actualDigest))
- else:
- logging.info("Extrainfo signature was verified successfully!")
-
- def updateFromExtraInfoDescriptor(self, descriptor, verify=True):
- """Update this bridge's information from an extrainfo descriptor.
-
- Stem's
- :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
- parses extrainfo ``transport`` lines into a dictionary with the
- following structure::
-
- {u'obfs2': (u'34.230.223.87', 37339, []),
- u'obfs3': (u'34.230.223.87', 37338, []),
- u'obfs4': (u'34.230.223.87', 37341, [
- (u'iat-mode=0,'
- u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
- u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
- u'scramblesuit': (u'34.230.223.87', 37340, [
- u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
-
-
- .. todo:: The ``transport`` attribute of Stem's
- ``BridgeExtraInfoDescriptor`` class is a dictionary that uses the
- Pluggable Transport's eype as the keys. Meaning that if a bridge
- were to offer four instances of ``obfs3``, only one of them would
- get to us through Stem. This might pose a problem someday.
-
- :type descriptor:
- :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
- :param descriptor: DOCDOC
- :param bool verify: If ``True``, check that the ``router-signature``
- on the extrainfo **descriptor** is a valid signature from
- :data:`signingkey`.
- """
- if verify:
- try:
- self._verifyExtraInfoSignature(descriptor)
- except InvalidExtraInfoSignature as error:
- logging.warn(error)
- logging.info(("Tossing extrainfo descriptor due to an invalid "
- "signature."))
- return
-
- self.descriptors['extrainfo'] = descriptor
- self.bridgeIPs = descriptor.bridge_ips
-
- oldTransports = self.transports[:]
-
- for methodname, (address, port, args) in descriptor.transport.items():
- updated = False
- # See if we already know about this transport. If so, update its
- # info; otherwise, add a new transport below.
- for pt in self.transports:
- if pt.methodname == methodname:
-
- logging.info("Found old %s transport for %s... Updating..."
- % (methodname, self))
-
- if not (address == str(pt.address)) and (port == pt.port):
- logging.info(("Address/port for %s transport for "
- "%s changed: old=%s:%s new=%s:%s")
- % (methodname, self, pt.address, pt.port,
- address, port))
-
- original = pt
- try:
- pt.updateFromStemTransport(str(self.fingerprint),
- methodname,
- (address, port, args,))
- except MalformedPluggableTransport as error:
- logging.info(str(error))
- else:
- oldTransports.remove(original)
-
- updated = True
- break
-
- if updated:
- continue
- else:
- # We didn't update it. It must be a new transport for this
- # bridges that we're hearing about for the first time, so add
- # it:
- logging.info(
- "Received new %s pluggable transport for bridge %s."
- % (methodname, self))
- try:
- transport = PluggableTransport()
- transport.updateFromStemTransport(str(self.fingerprint),
- methodname,
- (address, port, args,))
- self.transports.append(transport)
- except MalformedPluggableTransport as error:
- logging.info(str(error))
-
- # These are the pluggable transports which we knew about before, which
- # however were not updated in this descriptor, ergo the bridge must
- # not have them any more:
- for pt in oldTransports:
- logging.info("Removing dead transport for bridge %s: %s %s:%s %s" %
- (self, pt.methodname, pt.address, pt.port, pt.arguments))
- self.transports.remove(pt)
diff --git a/lib/bridgedb/captcha.py b/lib/bridgedb/captcha.py
deleted file mode 100644
index d2fbcc1..0000000
--- a/lib/bridgedb/captcha.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# Aaron Gibson 0x2C4B239DD876C9F6 <aagbsn at torproject.org>
-# Nick Mathewson 0x21194EBB165733EA <nickm at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""This module implements various methods for obtaining or creating CAPTCHAs.
-
-**Module Overview:**
-
-::
-
- captcha
- |- CaptchaExpired - Raised if a solution is given for a stale CAPTCHA.
- |- CaptchaKeyError - Raised if a CAPTCHA system's keys are invalid/missing.
- |- GimpCaptchaError - Raised when a Gimp CAPTCHA can't be retrieved.
- |
- \_ ICaptcha - Zope Interface specification for a generic CAPTCHA.
- |
- Captcha - Generic base class implementation for obtaining a CAPTCHA.
- | |- image - The CAPTCHA image.
- | |- challenge - A unique string associated with this CAPTCHA image.
- | |- publicKey - The public key for this CAPTCHA system.
- | |- secretKey - The secret key for this CAPTCHA system.
- | \_ get() - Get a new pair of CAPTCHA image and challenge strings.
- |
- |- ReCaptcha - Obtain reCaptcha images and challenge strings.
- | \_ get() - Request an image and challenge from a reCaptcha API server.
- |
- \_ GimpCaptcha - Class for obtaining a CAPTCHA from a local cache.
- |- hmacKey - A client-specific key for HMAC generation.
- |- cacheDir - The path to the local CAPTCHA cache directory.
- |- sched - A class for timing out CAPTCHAs after an interval.
- \_ get() - Get a CAPTCHA image from the cache and create a challenge.
-
-..
-
-There are two types of CAPTCHAs which BridgeDB knows how to serve: those
-obtained by from a reCaptcha_ API server with
-:class:`~bridgedb.captcha.Raptcha`, and those which have been generated with
-gimp-captcha_ and then cached locally.
-
-.. _reCaptcha : https://code.google.com/p/recaptcha/
-.. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
-"""
-
-from base64 import urlsafe_b64encode
-from base64 import urlsafe_b64decode
-
-import logging
-import random
-import os
-import time
-import urllib2
-
-from BeautifulSoup import BeautifulSoup
-
-from zope.interface import Interface, Attribute, implements
-
-from bridgedb import crypto
-from bridgedb import schedule
-from bridgedb.txrecaptcha import API_SSL_SERVER
-
-
-class CaptchaExpired(ValueError):
- """Raised when a client's CAPTCHA is too stale."""
-
-class CaptchaKeyError(Exception):
- """Raised if a CAPTCHA system's keys are invalid or missing."""
-
-class GimpCaptchaError(Exception):
- """General exception raised when a Gimp CAPTCHA cannot be retrieved."""
-
-
-class ICaptcha(Interface):
- """Interface specification for CAPTCHAs."""
-
- image = Attribute(
- "A string containing the contents of a CAPTCHA image file.")
- challenge = Attribute(
- "A unique string associated with the dispursal of this CAPTCHA.")
- publicKey = Attribute(
- "A public key used for encrypting CAPTCHA challenge strings.")
- secretKey = Attribute(
- "A private key used for decrypting challenge strings during CAPTCHA"
- "solution verification.")
-
- def get():
- """Retrieve a new CAPTCHA image."""
-
-
-class Captcha(object):
- """A generic CAPTCHA base class.
-
- :ivar str image: The CAPTCHA image.
- :ivar str challenge: A challenge string which should permit checking of
- the client's CAPTCHA solution in some manner. In stateless protocols
- such as HTTP, this should be passed along to the client with the
- CAPTCHA image.
- :ivar publicKey: A public key used for encrypting CAPTCHA challenge strings.
- :ivar secretKey: A private key used for decrypting challenge strings during
- CAPTCHA solution verification.
- """
- implements(ICaptcha)
-
- def __init__(self, publicKey=None, secretKey=None):
- """Obtain a new CAPTCHA for a client."""
- self.image = None
- self.challenge = None
- self.publicKey = publicKey
- self.secretKey = secretKey
-
- def get(self):
- """Retrieve a new CAPTCHA image and its associated challenge string.
-
- The image and challenge will be stored as :ivar:`image` and
- :ivar:`challenge, respectively.
- """
- self.image = None
- self.challenge = None
-
-
-class ReCaptcha(Captcha):
- """A CAPTCHA obtained from a remote reCaptcha_ API server.
-
- :ivar str image: The CAPTCHA image.
- :ivar str challenge: The ``'recaptcha_challenge_response'`` HTTP form
- field to pass to the client, along with the CAPTCHA image. See
- :doc:`BridgeDB's captcha.html <templates/captcha.html>` Mako_ template
- for an example usage.
- :ivar str publicKey: The public reCaptcha API key.
- :ivar str secretKey: The private reCaptcha API key.
-
- .. _reCaptcha: https://code.google.com/p/recaptcha/
- .. _Mako: http://docs.makotemplates.org/en/latest/syntax.html#page
- """
-
- def __init__(self, publicKey=None, secretKey=None):
- """Create a new ReCaptcha CAPTCHA.
-
- :param str publicKey: The public reCaptcha API key.
- :param str secretKey: The private reCaptcha API key.
- """
- super(ReCaptcha, self).__init__(publicKey=publicKey,
- secretKey=secretKey)
-
- def get(self):
- """Retrieve a CAPTCHA from the reCaptcha API server.
-
- This simply requests a new CAPTCHA from
- ``recaptcha.client.captcha.API_SSL_SERVER`` and parses the returned
- HTML to extract the CAPTCHA image and challenge string. The image is
- stored at ``ReCaptcha.image`` and the challenge string at
- ``ReCaptcha.challenge``.
-
- :raises CaptchaKeyError: If either the :ivar:`publicKey` or
- :ivar:`secretKey` are missing.
- :raises HTTPError: If the server returned any HTTP error status code.
- """
- if not self.publicKey or not self.secretKey:
- raise CaptchaKeyError('You must supply recaptcha API keys')
-
- urlbase = API_SSL_SERVER
- form = "/noscript?k=%s" % self.publicKey
-
- # Extract and store image from recaptcha
- html = urllib2.urlopen(urlbase + form).read()
- # FIXME: The remaining lines currently cannot be reliably unit tested:
- soup = BeautifulSoup(html) # pragma: no cover
- imgurl = urlbase + "/" + soup.find('img')['src'] # pragma: no cover
- cField = soup.find( # pragma: no cover
- 'input', {'name': 'recaptcha_challenge_field'}) # pragma: no cover
- self.challenge = str(cField['value']) # pragma: no cover
- self.image = urllib2.urlopen(imgurl).read() # pragma: no cover
-
-
-class GimpCaptcha(Captcha):
- """A locally cached CAPTCHA image which was created with gimp-captcha_.
-
- :ivar str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
- verifying the client's solution to the CAPTCHA.
- :ivar str publickey: A PKCS#1 OAEP-padded, public RSA key. This is used to
- hide the correct CAPTCHA solution within the
- ``captcha_challenge_field`` HTML form field. That form field is given
- to the a client along with the :ivar:`image` during the initial
- CAPTCHA request, and the client *should* give it back to us later
- during the CAPTCHA solution verification step.
- :ivar bytes hmacKey: A client-specific HMAC secret key.
- :ivar str cacheDir: The local directory which pre-generated CAPTCHA images
- have been stored in. This can be set via the ``GIMP_CAPTCHA_DIR``
- setting in the config file.
- :type sched: :class:`bridgedb.schedule.ScheduledInterval`
- :ivar sched: An time interval. After this much time has passed, the
- CAPTCHA is considered stale, and all solutions are considered invalid
- regardless of their correctness.
-
- .. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
- """
-
- sched = schedule.ScheduledInterval(30, 'minutes')
-
- def __init__(self, publicKey=None, secretKey=None, hmacKey=None,
- cacheDir=None):
- """Create a ``GimpCaptcha`` which retrieves images from **cacheDir**.
-
- :param str publickey: A PKCS#1 OAEP-padded, public RSA key, used for
- creating the ``captcha_challenge_field`` string to give to a
- client.
- :param str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
- verifying the client's solution to the CAPTCHA.
- :param bytes hmacKey: A client-specific HMAC secret key.
- :param str cacheDir: The local directory which pre-generated CAPTCHA
- images have been stored in. This can be set via the
- ``GIMP_CAPTCHA_DIR`` setting in the config file.
- :raises GimpCaptchaError: if :ivar:`cacheDir` is not a directory.
- :raises CaptchaKeyError: if any of :ivar:`secretKey`,
- :ivar:`publicKey`, or :ivar:`hmacKey` are invalid or missing.
- """
- if not cacheDir or not os.path.isdir(cacheDir):
- raise GimpCaptchaError("Gimp captcha cache isn't a directory: %r"
- % cacheDir)
- if not (publicKey and secretKey and hmacKey):
- raise CaptchaKeyError(
- "Invalid key supplied to GimpCaptcha: SK=%r PK=%r HMAC=%r"
- % (secretKey, publicKey, hmacKey))
-
- super(GimpCaptcha, self).__init__(publicKey=publicKey,
- secretKey=secretKey)
- self.hmacKey = hmacKey
- self.cacheDir = cacheDir
- self.answer = None
-
- @classmethod
- def check(cls, challenge, solution, secretKey, hmacKey):
- """Check a client's CAPTCHA **solution** against the **challenge**.
-
- :param str challenge: The contents of the
- ``'captcha_challenge_field'`` HTTP form field.
- :param str solution: The client's proposed solution to the CAPTCHA
- that they were presented with.
- :param str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
- verifying the client's solution to the CAPTCHA.
- :param bytes hmacKey: A private key for generating HMACs.
- :raises CaptchaExpired: if the **solution** was for a stale CAPTCHA.
- :rtype: bool
- :returns: ``True`` if the CAPTCHA solution was correct and not
- stale. ``False`` otherwise.
- """
- hmacIsValid = False
-
- if not solution:
- return hmacIsValid
-
- logging.debug("Checking CAPTCHA solution %r against challenge %r"
- % (solution, challenge))
- try:
- decoded = urlsafe_b64decode(challenge)
- hmacFromBlob = decoded[:20]
- encBlob = decoded[20:]
- hmacNew = crypto.getHMAC(hmacKey, encBlob)
- hmacIsValid = hmacNew == hmacFromBlob
- except Exception:
- return False
- finally:
- if hmacIsValid:
- try:
- answerBlob = secretKey.decrypt(encBlob)
-
- timestamp = answerBlob[:12].lstrip('0')
- then = cls.sched.nextIntervalStarts(int(timestamp))
- now = int(time.time())
- answer = answerBlob[12:]
- except Exception as error:
- logging.warn(error.message)
- else:
- # If the beginning of the 'next' interval (the interval
- # after the one when the CAPTCHA timestamp was created)
- # has already passed, then the CAPTCHA is stale.
- if now >= then:
- exp = schedule.fromUnixSeconds(then).isoformat(sep=' ')
- raise CaptchaExpired("Solution %r was for a CAPTCHA "
- "which already expired at %s."
- % (solution, exp))
- if solution.lower() == answer.lower():
- return True
- return False
-
- def createChallenge(self, answer):
- """Encrypt-then-HMAC a timestamp plus the CAPTCHA **answer**.
-
- A challenge string consists of a URL-safe, base64-encoded string which
- contains an ``HMAC`` concatenated with an ``ENC_BLOB``, in the
- following form::
-
- CHALLENGE := B64( HMAC | ENC_BLOB )
- ENC_BLOB := RSA_ENC( ANSWER_BLOB )
- ANSWER_BLOB := ( TIMESTAMP | ANSWER )
-
- where
- * ``B64`` is a URL-safe base64-encode function,
- * ``RSA_ENC`` is the PKCS#1 RSA-OAEP encryption function,
- * and the remaining feilds are specified as follows:
-
- +-------------+--------------------------------------------+----------+
- | Field | Description | Length |
- +=============+============================================+==========+
- | HMAC | An HMAC of the ``ENC_BLOB``, created with | 20 bytes |
- | | the client-specific :ivar:`hmacKey`, by | |
- | | applying :func:`~crypto.getHMAC` to the | |
- | | ``ENC_BLOB``. | |
- +-------------+--------------------------------------------+----------+
- | ENC_BLOB | An encrypted ``ANSWER_BLOB``, created with | varies |
- | | a PKCS#1 OAEP-padded RSA :ivar:`publicKey`.| |
- +-------------+--------------------------------------------+----------+
- | ANSWER_BLOB | Contains the concatenated ``TIMESTAMP`` | varies |
- | | and ``ANSWER``. | |
- +-------------+--------------------------------------------+----------+
- | TIMESTAMP | A Unix Epoch timestamp, in seconds, | 12 bytes |
- | | left-padded with "0"s. | |
- +-------------+--------------------------------------------+----------+
- | ANSWER | A string containing answer to this | 8 bytes |
- | | CAPTCHA :ivar:`image`. | |
- +-------------+--------------------------------------------+----------+
-
- The steps taken to produce a ``CHALLENGE`` are then:
-
- 1. Create a ``TIMESTAMP``, and pad it on the left with ``0``s to 12
- bytes in length.
-
- 2. Next, take the **answer** to this CAPTCHA :ivar:`image: and
- concatenate the padded ``TIMESTAMP`` and the ``ANSWER``, forming
- an ``ANSWER_BLOB``.
-
- 3. Encrypt the resulting ``ANSWER_BLOB`` to :ivar:`publicKey` to
- create the ``ENC_BLOB``.
-
- 4. Use the client-specific :ivar:`hmacKey` to apply the
- :func:`~crypto.getHMAC` function to the ``ENC_BLOB``, obtaining
- an ``HMAC``.
-
- 5. Create the final ``CHALLENGE`` string by concatenating the
- ``HMAC`` and ``ENC_BLOB``, then base64-encoding the result.
-
- :param str answer: The answer to a CAPTCHA.
- :rtype: str
- :returns: A challenge string.
- """
- timestamp = str(int(time.time())).zfill(12)
- blob = timestamp + answer
- encBlob = self.publicKey.encrypt(blob)
- hmac = crypto.getHMAC(self.hmacKey, encBlob)
- challenge = urlsafe_b64encode(hmac + encBlob)
- return challenge
-
- def get(self):
- """Get a random CAPTCHA from the cache directory.
-
- This chooses a random CAPTCHA image file from the cache directory, and
- reads the contents of the image into a string. Next, it creates a
- challenge string for the CAPTCHA, via :meth:`createChallenge`.
-
- :raises GimpCaptchaError: if the chosen CAPTCHA image file could not
- be read, or if the **cacheDir** is empty.
- :rtype: tuple
- :returns: A 2-tuple containing the image file contents as a string,
- and a challenge string (used for checking the client's solution).
- """
- try:
- imageFilename = random.choice(os.listdir(self.cacheDir))
- imagePath = os.path.join(self.cacheDir, imageFilename)
- with open(imagePath) as imageFile:
- self.image = imageFile.read()
- except IndexError:
- raise GimpCaptchaError("CAPTCHA cache dir appears empty: %r"
- % self.cacheDir)
- except (OSError, IOError):
- raise GimpCaptchaError("Could not read Gimp captcha image file: %r"
- % imageFilename)
-
- self.answer = imageFilename.rsplit(os.path.extsep, 1)[0]
- self.challenge = self.createChallenge(self.answer)
-
- return (self.image, self.challenge)
diff --git a/lib/bridgedb/configure.py b/lib/bridgedb/configure.py
deleted file mode 100644
index 0056107..0000000
--- a/lib/bridgedb/configure.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_configure -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see the AUTHORS file for attributions
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2007-2015, Nick Mathewson
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""Utilities for dealing with configuration files for BridgeDB."""
-
-import logging
-import os
-
-# Used to set the SUPPORTED_TRANSPORTS:
-from bridgedb import strings
-
-
-def loadConfig(configFile=None, configCls=None):
- """Load configuration settings on top of the current settings.
-
- All pathnames and filenames within settings in the ``configFile`` will be
- expanded, and their expanded values will be stored in the returned
- :class:`config <Conf>` object.
-
- ** Note: **
- On the strange-looking use of
- ``exec compile(open(configFile).read(), '<string>', 'exec') in dict()``
- in this function:
-
- The contents of the config file should be compiled first, and then
- ``exec``ed -- not ``execfile``! -- in order to get the contents of the
- config file to exist within the scope of the configuration dictionary.
- Otherwise, Python *will* default_ to executing the config file directly
- within the ``globals()`` scope.
-
- Additionally, it's roughly 20-30 times faster_ to use the ``compile``
- builtin on a string (the contents of the file) before ``exec``ing it, than
- using ``execfile`` directly on the file.
-
- .. _default: http://stackoverflow.com/q/17470193
- .. _faster: http://lucumr.pocoo.org/2011/2/1/exec-in-python/
-
- :ivar boolean itsSafeToUseLogging: This is called in
- :func:`~bridgedb.Main.run` before
- :func:`bridgedb.safelog.configureLogging`. When called from
- :func:`~bridgedb.Main.run`, the **configCls** parameter is not given,
- because that is the first time that a :class:`Conf` is created. If a
- :class:`logging.Logger` is created in this function, then logging will
- not be correctly configured, therefore, if the **configCls** parameter
- is not given, then it's the first time this function has been called
- and it is therefore not safe to make calls to the logging module.
- :type: configFile: string or None
- :param configFile: If given, the filename of the config file to load.
- :type configCls: :class:`Conf` or None
- :param configCls: The current configuration instance, if one already
- exists.
- :returns: A new :class:`configuration <bridgedb.configure.Conf>`, with the
- old settings as defaults, and the settings from the **configFile** (if
- given) overriding those defaults.
- """
- itsSafeToUseLogging = False
- configuration = {}
-
- if configCls:
- itsSafeToUseLogging = True
- oldConfig = configCls.__dict__
- configuration.update(**oldConfig) # Load current settings
- logging.info("Reloading over in-memory configurations...")
-
- conffile = configFile
- if (configFile is None) and ('CONFIG_FILE' in configuration):
- conffile = configuration['CONFIG_FILE']
-
- if conffile is not None:
- if itsSafeToUseLogging:
- logging.info("Loading settings from config file: '%s'" % conffile)
- compiled = compile(open(conffile).read(), '<string>', 'exec')
- exec compiled in configuration
-
- if itsSafeToUseLogging:
- logging.debug("New configuration settings:")
- logging.debug("\n".join(["{0} = {1}".format(key, value)
- for key, value in configuration.items()
- if not key.startswith('_')]))
-
- # Create a :class:`Conf` from the settings stored within the local scope
- # of the ``configuration`` dictionary:
- config = Conf(**configuration)
-
- # We want to set the updated/expanded paths for files on the ``config``,
- # because the copy of this config, `state.config` is used later to compare
- # with a new :class:`Conf` instance, to see if there were any changes.
- #
- # See :meth:`bridgedb.persistent.State.useUpdatedSettings`.
-
- for attr in ["PROXY_LIST_FILES", "BRIDGE_FILES", "EXTRA_INFO_FILES"]:
- setting = getattr(config, attr, None)
- if setting is None: # pragma: no cover
- setattr(config, attr, []) # If they weren't set, make them lists
- else:
- setattr(config, attr, # If they were set, expand the paths:
- [os.path.abspath(os.path.expanduser(f)) for f in setting])
-
- for attr in ["DB_FILE", "DB_LOG_FILE", "MASTER_KEY_FILE", "PIDFILE",
- "ASSIGNMENTS_FILE", "HTTPS_CERT_FILE", "HTTPS_KEY_FILE",
- "LOG_FILE", "STATUS_FILE", "COUNTRY_BLOCK_FILE",
- "GIMP_CAPTCHA_DIR", "GIMP_CAPTCHA_HMAC_KEYFILE",
- "GIMP_CAPTCHA_RSA_KEYFILE", "EMAIL_GPG_HOMEDIR",
- "EMAIL_GPG_PASSPHRASE_FILE"]:
- setting = getattr(config, attr, None)
- if setting is None:
- setattr(config, attr, setting)
- else:
- setattr(config, attr, os.path.abspath(os.path.expanduser(setting)))
-
- for attr in ["HTTPS_ROTATION_PERIOD", "EMAIL_ROTATION_PERIOD"]:
- setting = getattr(config, attr, None) # Default to None
- setattr(config, attr, setting)
-
- for attr in ["IGNORE_NETWORKSTATUS"]:
- setting = getattr(config, attr, True) # Default to True
- setattr(config, attr, setting)
-
- for attr in ["FORCE_PORTS", "FORCE_FLAGS", "NO_DISTRIBUTION_COUNTRIES"]:
- setting = getattr(config, attr, []) # Default to empty lists
- setattr(config, attr, setting)
-
- for attr in ["SUPPORTED_TRANSPORTS"]:
- setting = getattr(config, attr, {}) # Default to empty dicts
- setattr(config, attr, setting)
-
- # Set the SUPPORTED_TRANSPORTS to populate the webserver and email options:
- strings._setSupportedTransports(getattr(config, "SUPPORTED_TRANSPORTS", {}))
- strings._setDefaultTransport(getattr(config, "DEFAULT_TRANSPORT", ""))
- logging.info("Currently supported transports: %s" %
- " ".join(strings._getSupportedTransports()))
- logging.info("Default transport: %s" % strings._getDefaultTransport())
-
- for domain in config.EMAIL_DOMAINS:
- config.EMAIL_DOMAIN_MAP[domain] = domain
-
- if conffile: # Store the pathname of the config file, if one was used
- config.CONFIG_FILE = os.path.abspath(os.path.expanduser(conffile))
-
- return config
-
-
-class Conf(object):
- """A configuration object. Holds unvalidated attributes."""
- def __init__(self, **attrs):
- for key, value in attrs.items():
- if key == key.upper():
- if not key.startswith('__'):
- self.__dict__[key] = value
diff --git a/lib/bridgedb/crypto.py b/lib/bridgedb/crypto.py
deleted file mode 100644
index 7785073..0000000
--- a/lib/bridgedb/crypto.py
+++ /dev/null
@@ -1,450 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""BridgeDB general cryptographic utilities.
-
-.. py:module:: bridgedb.crypto
- :synopsis: This module contains general utilities for working with external
- cryptographic tools and libraries, including OpenSSL and GnuPG. It also
- includes utilities for creating callable HMAC functions, generating
- HMACs for data, and generating and/or storing key material.
-
-Module Overview
-~~~~~~~~~~~~~~~
-::
- crypto
- |_getGPGContext() - Get a pre-configured GPGME context.
- |_getHMAC() - Compute an HMAC with some key for some data.
- |_getHMACFunc() - Get a callable for producing HMACs with the given key.
- |_getKey() - Load the master HMAC key from a file, or create a new one.
- |_getRSAKey() - Load an RSA key from a file, or create a new one.
- |_gpgSignMessage() - Sign a message string according to a GPGME context.
- |_writeKeyToFile() - Write to a file readable only by the process owner.
- |
- \_SSLVerifyingContextFactory - OpenSSL.SSL.Context factory which verifies
- | certificate chains and matches hostnames.
- |_getContext() - Retrieve an SSL context configured for certificate
- | verification.
- |_getHostnameFromURL() - Parses the hostname from the request URL.
- \_verifyHostname() - Check that the cert CN matches the request
- hostname.
-::
-"""
-
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
-import gnupg
-import hashlib
-import hmac
-import io
-import logging
-import os
-import re
-import urllib
-
-import OpenSSL
-
-from Crypto.Cipher import PKCS1_OAEP
-from Crypto.PublicKey import RSA
-
-from twisted.internet import ssl
-from twisted.python.procutils import which
-
-
-#: The hash digest to use for HMACs.
-DIGESTMOD = hashlib.sha1
-
-# Test to see if we have the old or new style buffer() interface. Trying
-# to use an old-style buffer on Python2.7 prior to version 2.7.5 will produce:
-#
-# TypeError: 'buffer' does not have the buffer interface
-#
-#: ``True`` if we have the new-style
-#: `buffer <https://docs.python.org/2/c-api/buffer.html>` interface.
-NEW_BUFFER_INTERFACE = False
-try:
- io.BytesIO(buffer('test'))
-except TypeError: # pragma: no cover
- logging.warn(
- "This Python version is too old! "\
- "It doesn't support new-style buffer interfaces: "\
- "https://mail.python.org/pipermail/python-dev/2010-October/104917.html")
-else:
- NEW_BUFFER_INTERFACE = True
-
-
-class PKCS1PaddingError(Exception):
- """Raised when there is a problem adding or removing PKCS#1 padding."""
-
-class RSAKeyGenerationError(Exception):
- """Raised when there was an error creating an RSA keypair."""
-
-
-def writeKeyToFile(key, filename):
- """Write **key** to **filename**, with ``0400`` permissions.
-
- If **filename** doesn't exist, it will be created. If it does exist
- already, and is writable by the owner of the current process, then it will
- be truncated to zero-length and overwritten.
-
- :param bytes key: A key (or some other private data) to write to
- **filename**.
- :param str filename: The path of the file to write to.
- :raises: Any exceptions which may occur.
- """
- logging.info("Writing key to file: %r" % filename)
- flags = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, "O_BIN", 0)
- fd = os.open(filename, flags, 0400)
- os.write(fd, key)
- os.fsync(fd)
- os.close(fd)
-
-def getRSAKey(filename, bits=2048):
- """Load the RSA key stored in **filename**, or create and save a new key.
-
- >>> from bridgedb import crypto
- >>> keyfile = 'doctest_getRSAKey'
- >>> message = "The secret words are Squeamish Ossifrage."
- >>> keypair = crypto.getRSAKey(keyfile, bits=2048)
- >>> (secretkey, publickey) = keypair
- >>> encrypted = publickey.encrypt(message)
- >>> assert encrypted != message
- >>> decrypted = secretkey.decrypt(encrypted)
- >>> assert message == decrypted
-
-
- If **filename** already exists, it is assumed to contain a PEM-encoded RSA
- private key, which will be read from the file. (The parameters of a
- private RSA key contain the public exponent and public modulus, which
- together comprise the public key â ergo having two separate keyfiles is
- assumed unnecessary.)
-
- If **filename** doesn't exist, a new RSA keypair will be created, and the
- private key will be stored in **filename**, using :func:`writeKeyToFile`.
-
- Once the private key is either loaded or created, the public key is
- extracted from it. Both keys are then input into PKCS#1 RSAES-OAEP cipher
- schemes (see `RFC 3447 §7.1`__) in order to introduce padding, and then
- returned.
-
- .. __: https://tools.ietf.org/html/rfc3447#section-7.1
-
- :param str filename: The filename to which the secret parameters of the
- RSA key are stored in.
- :param int bits: If no key is found within the file, create a new key with
- this bitlength and store it in **filename**.
- :rtype: tuple of ``Crypto.Cipher.PKCS1_OAEP.PKCS1OAEP_Cipher``
- :returns: A 2-tuple of ``(privatekey, publickey)``, which are PKCS#1
- RSAES-OAEP padded and encoded private and public keys, forming an RSA
- keypair.
- """
- filename = os.path.extsep.join([filename, 'sec'])
- keyfile = os.path.join(os.getcwd(), filename)
-
- try:
- fh = open(keyfile, 'rb')
- except IOError:
- logging.info("Generating %d-bit RSA keypair..." % bits)
- secretKey = RSA.generate(bits, e=65537)
-
- # Store a PEM copy of the secret key (which contains the parameters
- # necessary to create the corresponding public key):
- secretKeyPEM = secretKey.exportKey("PEM")
- writeKeyToFile(secretKeyPEM, keyfile)
- else:
- logging.info("Secret RSA keyfile %r found. Loading..." % filename)
- secretKey = RSA.importKey(fh.read())
- fh.close()
-
- publicKey = secretKey.publickey()
-
- # Add PKCS#1 OAEP padding to the secret and public keys:
- sk = PKCS1_OAEP.new(secretKey)
- pk = PKCS1_OAEP.new(publicKey)
-
- return (sk, pk)
-
-def getKey(filename):
- """Load the master key stored in ``filename``, or create a new key.
-
- If ``filename`` does not exist, create a new 32-byte key and store it in
- ``filename``.
-
- >>> import os
- >>> from bridgedb import crypto
- >>> name = 'doctest_getKey'
- >>> os.path.exists(name)
- False
- >>> k1 = crypto.getKey(name)
- >>> os.path.exists(name)
- True
- >>> open(name).read() == k1
- True
- >>> k2 = crypto.getKey(name)
- >>> k1 == k2
- True
-
- :param string filename: The filename to store the secret key in.
- :rtype: bytes
- :returns: A byte string containing the secret key.
- """
- try:
- fh = open(filename, 'rb')
- except IOError:
- logging.debug("getKey(): Creating new secret key.")
- key = OpenSSL.rand.bytes(32)
- writeKeyToFile(key, filename)
- else:
- logging.debug("getKey(): Secret key file found. Loading...")
- key = fh.read()
- fh.close()
- return key
-
-def getHMAC(key, value):
- """Return the HMAC of **value** using the **key**."""
- h = hmac.new(key, value, digestmod=DIGESTMOD)
- return h.digest()
-
-def getHMACFunc(key, hex=True):
- """Return a function that computes the HMAC of its input using the **key**.
-
- :param bool hex: If True, the output of the function will be hex-encoded.
- :rtype: callable
- :returns: A function which can be uses to generate HMACs.
- """
- h = hmac.new(key, digestmod=DIGESTMOD)
- def hmac_fn(value):
- h_tmp = h.copy()
- h_tmp.update(value)
- if hex:
- return h_tmp.hexdigest()
- else:
- return h_tmp.digest()
- return hmac_fn
-
-def removePKCS1Padding(message):
- """Remove PKCS#1 padding from a **message**.
-
- (PKCS#1 v1.0? see https://bugs.torproject.org/13042)
-
- Each block is 128 bytes total in size:
-
- * 2 bytes for the type info ('\x00\x01')
- * 1 byte for the separator ('\x00')
- * variable length padding ('\xFF')
- * variable length for the **message**
-
- For more information on the structure of PKCS#1 padding, see :rfc:`2313`,
- particularly the notes in §8.1.
-
- :param str message: A message which is PKCS#1 padded.
- :raises PKCS1PaddingError: if there is an issue parsing the **message**.
- :rtype: bytes
- :returns: The message without the PKCS#1 padding.
- """
- padding = b'\xFF'
- typeinfo = b'\x00\x01'
- separator = b'\x00'
-
- unpadded = None
-
- try:
- if message.index(typeinfo) != 0:
- raise PKCS1PaddingError("Couldn't find PKCS#1 identifier bytes!")
- start = message.index(separator, 2) + 1 # 2 bytes for the typeinfo,
- # and 1 byte for the separator.
- except ValueError:
- raise PKCS1PaddingError("Couldn't find PKCS#1 separator byte!")
- else:
- unpadded = message[start:]
-
- return unpadded
-
-def initializeGnuPG(config):
- """Initialize a GnuPG interface and test our configured keys.
-
- .. note:: This function uses python-gnupg_.
-
- :type config: :class:`bridgedb.persistent.Conf`
- :param config: The loaded config file.
- :rtype: 2-tuple
- :returns: If ``EMAIL_GPG_SIGNING_ENABLED`` isn't ``True``, or we couldn't
- initialize GnuPG and make a successful test signature with the
- specified key, then a 2-tuple of ``None`` is returned. Otherwise, the
- first item in the tuple is a :class:`gnupg.GPG` interface_ with the
- GnuPG homedir set to the ``EMAIL_GPG_HOMEDIR`` option and the signing
- key specified by the ``EMAIL_GPG_SIGNING_KEY_FINGERPRINT`` option in
- bridgedb.conf set as the default key. The second item in the tuple is
- a signing function with the passphrase (as specified in either
- ``EMAIL_GPG_PASSPHRASE`` or ``EMAIL_GPG_PASSPHRASE_FILE``) already
- set.
-
- .. _python-gnupg: https://pypi.python.org/pypi/gnupg/
- .. _interface: https://python-gnupg.readthedocs.org/en/latest/gnupg.html#gnupg-module
- """
- ret = (None, None)
-
- if not config.EMAIL_GPG_SIGNING_ENABLED:
- return ret
-
- homedir = config.EMAIL_GPG_HOMEDIR
- primary = config.EMAIL_GPG_PRIMARY_KEY_FINGERPRINT
- passphrase = config.EMAIL_GPG_PASSPHRASE
- passFile = config.EMAIL_GPG_PASSPHRASE_FILE
-
- logging.info("Using %s as our GnuPG home directory..." % homedir)
- gpg = gnupg.GPG(homedir=homedir)
- logging.info("Initialized GnuPG interface using %s binary with version %s."
- % (gpg.binary, gpg.binary_version))
-
- primarySK = None
- primaryPK = None
- secrets = gpg.list_keys(secret=True)
- publics = gpg.list_keys()
-
- if not secrets:
- logging.warn("No secret keys found in %s!" % gpg.secring)
- return ret
-
- primarySK = filter(lambda key: key['fingerprint'] == primary, secrets)
- primaryPK = filter(lambda key: key['fingerprint'] == primary, publics)
-
- if primarySK and primaryPK:
- logging.info("Found GnuPG primary key with fingerprint: %s" % primary)
- for sub in primaryPK[0]['subkeys']:
- logging.info(" Subkey: %s Usage: %s" % (sub[0], sub[1].upper()))
- else:
- logging.warn("GnuPG key %s could not be found in %s!" % (primary, gpg.secring))
- return ret
-
- if passphrase:
- logging.info("Read GnuPG passphrase from config.")
- elif passFile:
- try:
- with open(passFile) as fh:
- passphrase = fh.read()
- except (IOError, OSError):
- logging.error("Could not open GnuPG passphrase file: %s!" % passFile)
- else:
- logging.info("Read GnuPG passphrase from file: %s" % passFile)
-
- def gpgSignMessage(message):
- """Sign **message** with the default key specified by
- ``EMAIL_GPG_PRIMARY_KEY_FINGERPRINT``.
-
- :param str message: A message to sign.
- :rtype: str or ``None``.
- :returns: A string containing the clearsigned message, or ``None`` if
- the signing failed.
- """
- sig = gpg.sign(message, default_key=primary, passphrase=passphrase)
- if sig and sig.data:
- return sig.data
-
- logging.debug("Testing signature created with GnuPG key...")
- sig = gpgSignMessage("Testing 1 2 3")
- if sig:
- logging.info("Test signature with GnuPG key %s okay:\n%s" % (primary, sig))
- return (gpg, gpgSignMessage)
-
- return ret
-
-
-class SSLVerifyingContextFactory(ssl.CertificateOptions):
- """``OpenSSL.SSL.Context`` factory which does full certificate-chain and
- hostname verfication.
- """
- isClient = True
-
- def __init__(self, url, **kwargs):
- """Create a client-side verifying SSL Context factory.
-
- To pass acceptable certificates for a server which does
- client-authentication checks: initialise with a ``caCerts=[]`` keyword
- argument, which should be a list of ``OpenSSL.crypto.X509`` instances
- (one for each peer certificate to add to the store), and set
- ``SSLVerifyingContextFactory.isClient=False``.
-
- :param str url: The URL being requested by an
- :api:`twisted.web.client.Agent`.
- :param bool isClient: True if we're being used in a client
- implementation; False if we're a server.
- """
- self.hostname = self.getHostnameFromURL(url)
-
- # ``verify`` here refers to server-side verification of certificates
- # presented by a client:
- self.verify = False if self.isClient else True
- super(SSLVerifyingContextFactory, self).__init__(verify=self.verify,
- fixBrokenPeers=True,
- **kwargs)
-
- def getContext(self, hostname=None, port=None):
- """Retrieve a configured ``OpenSSL.SSL.Context``.
-
- Any certificates in the ``caCerts`` list given during initialisation
- are added to the ``Context``'s certificate store.
-
- The **hostname** and **port** arguments seem unused, but they are
- required due to some Twisted and pyOpenSSL internals. See
- :api:`twisted.web.client.Agent._wrapContextFactory`.
-
- :rtype: ``OpenSSL.SSL.Context``
- :returns: An SSL Context which verifies certificates.
- """
- ctx = super(SSLVerifyingContextFactory, self).getContext()
- store = ctx.get_cert_store()
- verifyOptions = OpenSSL.SSL.VERIFY_PEER
- ctx.set_verify(verifyOptions, self.verifyHostname)
- return ctx
-
- def getHostnameFromURL(self, url):
- """Parse the hostname from the originally requested URL.
-
- :param str url: The URL being requested by an
- :api:`twisted.web.client.Agent`.
- :rtype: str
- :returns: The full hostname (including any subdomains).
- """
- hostname = urllib.splithost(urllib.splittype(url)[1])[0]
- logging.debug("Parsed hostname %r for cert CN matching." % hostname)
- return hostname
-
- def verifyHostname(self, connection, x509, errnum, depth, okay):
- """Callback method for additional SSL certificate validation.
-
- If the certificate is signed by a valid CA, and the chain is valid,
- verify that the level 0 certificate has a subject common name which is
- valid for the hostname of the originally requested URL.
-
- :param connection: An ``OpenSSL.SSL.Connection``.
- :param x509: An ``OpenSSL.crypto.X509`` object.
- :param errnum: A pyOpenSSL error number. See that project's docs.
- :param depth: The depth which the current certificate is at in the
- certificate chain.
- :param bool okay: True if all the pyOpenSSL default checks on the
- certificate passed. False otherwise.
- """
- commonName = x509.get_subject().commonName
- logging.debug("Received cert at level %d: '%s'" % (depth, commonName))
-
- # We only want to verify that the hostname matches for the level 0
- # certificate:
- if okay and (depth == 0):
- cn = commonName.replace('*', '.*')
- hostnamesMatch = re.search(cn, self.hostname)
- if not hostnamesMatch:
- logging.warn("Invalid certificate subject CN for '%s': '%s'"
- % (self.hostname, commonName))
- return False
- logging.debug("Valid certificate subject CN for '%s': '%s'"
- % (self.hostname, commonName))
- return True
diff --git a/lib/bridgedb/distribute.py b/lib/bridgedb/distribute.py
deleted file mode 100644
index f48cbb6..0000000
--- a/lib/bridgedb/distribute.py
+++ /dev/null
@@ -1,275 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_distribute ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-"""Classes for creating bridge distribution systems.
-
-DEFINITELY
-----------
-
-Distributor {
- name property
- bridgesPerResponse() property FORMERLY getNumBridgesPerAnswer()
- hashring struct FORMERLY KNOWN AS splitter
- rotate bool
- rotationGroups
- rotationSchedule
- key str
- subrings list
- - Subring
- clear()
- export() FORMERLY KNOWN AS dumpAssignments()
- insert()
- getBridges() FORMERLY KNOWN AS getBridgesForEmail() and getBridgesForIP()
- handleBridgeRequest()
- handleIncomingBridges()
-}
-
-DistributionContext { # should go in bridgedb.py
- distributors {
- name: DistributorContext
- }
-}
-
-DistributorContext { # should go in bridgedb.py
- name str
- allocationPercentage property
- publicKey
-}
-
-Hashring {
- assignBridgesToSubrings() FORMERLY bridgedb.filters.assignBridgesToSubring()
- + filters bridges uniformly into subrings
- clear() / __del__()
- isEmpty property
-}
-
-MAYBE
------
-mapClientToHashring() FORMERLY KNOWN AS areaMapper AND
-mapClientToSubhashring()
-authenticateToBridgeDB()
-maintainACL() for proxylists
-
-- need a way for BridgeDB to decide global parameters to be followed
- by all distributors.
- - BridgeAnswerParameters?
- maybe call it DistributionContext?
- then have DistributorContexts?
-
- requiredFlags AnswerParameters?
- requireFlag()
- requiredPorts
- requirePorts()
-
- THINGS NEEDED FOR COMMUNICATION BETWEEN DISTRIBUTORS AND BRIDGEDB
- -----------------------------------------------------------------
- * distributorCredential (for authenticating to the DB)
- * metrics?
- * total clients seen
- * total clients served
- - unique clients seen
- - unique clients served
- * total requests for TRANSPORT
- * total times TRANSPORT was served
-
- THINGS DISTRIBUTORS SHOULD KEEP TRACK OF, BUT NOT REPORT
- --------------------------------------------------------
- - approximate bridge bandwidth
- - approximate bandwidth per client
- - approximate bridge bandwidth already distributed
-
- NAMES FOR CHOOSING "GET ME WHATEVER TRANSPORTS"
- -----------------------------------------------
- chocolate box, russian roulette
-
- * How much of a bad idea would it be to store bridges allocated to a
- distributor as diffs over the last time the Distributor asked?
-"""
-
-import logging
-import math
-
-from zope import interface
-from zope.interface import Attribute
-from zope.interface import implements
-
-# from bridgedb.hashring import IHashring
-from bridgedb.interfaces import IName
-from bridgedb.interfaces import Named
-
-
-class IDistribute(IName):
- """An interface specification for a system which distributes bridges."""
-
- _bridgesPerResponseMin = Attribute(
- ("The minimum number of bridges to distribute (if possible), per "
- "client request."))
- _bridgesPerResponseMax = Attribute(
- ("The maximum number of bridges to distribute (if possible), per "
- "client request."))
- _hashringLevelMin = Attribute(
- ("The bare minimum number of bridges which should be in a hashring. "
- "If there less bridges than this, then the implementer of "
- "IDistribute should only distribute _bridgesPerResponseMin number "
- "of bridges, per client request."))
- _hashringLevelMax = Attribute(
- ("The number of bridges which should be in a hashring for the "
- "implementer of IDistribute to distribute _bridgesPerResponseMax "
- "number of bridges, per client request."))
-
- hashring = Attribute(
- ("An implementer of ``bridgedb.hashring.IHashring`` which stores the "
- "entirety of bridges allocated to this ``Distributor`` by the "
- "BridgeDB. This ``Distributor`` is only capable of distributing "
- "these bridges to its clients, and these bridges are only "
- "distributable by this ``Distributor``."))
-
- key = Attribute(
- ("A master key which is used to HMAC bridge and client data into "
- "this Distributor's **hashring** and its subhashrings."))
-
- def __str__():
- """Get a string representation of this Distributor's ``name``."""
-
- def bridgesPerResponse(hashring):
- """Get the current number of bridges to return in a response."""
-
- def getBridges(bridgeRequest):
- """Get bridges based on a client's **bridgeRequest**."""
-
-
-class Distributor(Named):
- """A :class:`Distributor` distributes bridges to clients.
-
- Inherit from me to create a new type of ``Distributor``.
- """
- implements(IDistribute)
-
- _bridgesPerResponseMin = 1
- _bridgesPerResponseMax = 3
- _hashringLevelMin = 20
- _hashringLevelMax = 100
-
- def __init__(self, key=None):
- """Create a new bridge Distributor.
-
- :param key: A master key for this Distributor. This is used to HMAC
- bridge and client data in order to arrange them into hashring
- structures.
- """
- super(Distributor, self).__init__()
- self._hashring = None
- self.key = key
-
- def __str__(self):
- """Get a string representation of this ``Distributor``'s ``name``.
-
- :rtype: str
- :returns: This ``Distributor``'s ``name`` attribute.
- """
- return self.name
-
- @property
- def hashring(self):
- """Get this Distributor's main hashring, which holds all bridges
- allocated to this Distributor.
-
- :rtype: :class:`~bridgedb.hashring.Hashring`.
- :returns: An implementer of :interface:`~bridgedb.hashring.IHashring`.
- """
- return self._hashring
-
- @hashring.setter
- def hashring(self, ring):
- """Set this Distributor's main hashring.
-
- :type ring: :class:`~bridgedb.hashring.Hashring`
- :param ring: An implementer of :interface:`~bridgedb.hashring.IHashring`.
- :raises TypeError: if the **ring** does not implement the
- :interface:`~bridgedb.hashring.IHashring` interface.
- """
- # if not IHashring.providedBy(ring):
- # raise TypeError("%r doesn't implement the IHashring interface." % ring)
-
- self._hashring = ring
-
- @hashring.deleter
- def hashring(self):
- """Clear this Distributor's hashring."""
- if self.hashring:
- self.hashring.clear()
-
- @property
- def name(self):
- """Get the name of this Distributor.
-
- :rtype: str
- :returns: A string which identifies this :class:`Distributor`.
- """
- return self._name
-
- @name.setter
- def name(self, name):
- """Set a **name** for identifying this Distributor.
-
- This is used to identify the distributor in the logs; the **name**
- doesn't necessarily need to be unique. The hashrings created for this
- distributor will be named after this distributor's name, and any
- subhashrings of each of those hashrings will also carry that name.
-
- >>> from bridgedb.distribute import Distributor
- >>> dist = Distributor()
- >>> dist.name = 'Excellent Distributor'
- >>> dist.name
- 'Excellent Distributor'
-
- :param str name: A name for this distributor.
- """
- self._name = name
-
- try:
- self.hashring.distributor = name
- except AttributeError:
- logging.debug(("Couldn't set distributor attribute for %s "
- "Distributor's hashring." % name))
-
- def bridgesPerResponse(self, hashring=None):
- """Get the current number of bridge to distribute in response to a
- client's request for bridges.
- """
- if hashring is None:
- hashring = self.hashring
-
- if len(hashring) < self._hashringLevelMin:
- n = self._bridgesPerResponseMin
- elif self._hashringLevelMin <= len(hashring) < self._hashringLevelMax:
- n = int(math.ceil(
- (self._bridgesPerResponseMin + self._bridgesPerResponseMax) / 2.0))
- elif self._hashringLevelMax <= len(hashring):
- n = self._bridgesPerResponseMax
-
- logging.debug("Returning %d bridges from ring of len: %d" %
- (n, len(hashring)))
-
- return n
-
- def getBridges(self, bridgeRequest):
- """Get some bridges in response to a client's **bridgeRequest**.
-
- :type bridgeRequest: :class:`~bridgedb.bridgerequest.BridgeRequestBase`
- :param bridgeRequest: A client's request for bridges, including some
- information on the client making the request, whether they asked
- for IPv4 or IPv6 bridges, which type of
- :class:`~bridgedb.bridges.PluggableTransport` they wanted, etc.
- """
- # XXX generalise the getBridges() method
diff --git a/lib/bridgedb/email/__init__.py b/lib/bridgedb/email/__init__.py
deleted file mode 100644
index 604c648..0000000
--- a/lib/bridgedb/email/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Servers for BridgeDB's email bridge distributor."""
diff --git a/lib/bridgedb/email/autoresponder.py b/lib/bridgedb/email/autoresponder.py
deleted file mode 100644
index ad63bfd..0000000
--- a/lib/bridgedb/email/autoresponder.py
+++ /dev/null
@@ -1,704 +0,0 @@
-# -*- coding: utf-8; test-case-name: bridgedb.test.test_email_autoresponder -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson <nickm at torproject.org>
-# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# Matthew Finkel <sysrqb at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""
-.. py:module:: bridgedb.email.autoresponder
- :synopsis: Functionality for autoresponding to incoming emails.
-
-bridgedb.email.autoresponder
-============================
-
-Functionality for autoresponding to incoming emails.
-
-::
-
- bridgedb.email.autoresponder
- | |_ createResponseBody - Parse lines from an incoming email and determine
- | | how to respond.
- | |_ generateResponse - Create an email response.
- |
- |_ EmailResponse - Holds information for generating a response to a request.
- |_ SMTPAutoresponder - An SMTP autoresponder for incoming mail.
-..
-"""
-
-from __future__ import unicode_literals
-from __future__ import print_function
-
-import io
-import logging
-import time
-
-from twisted.internet import defer
-from twisted.internet import reactor
-from twisted.mail import smtp
-from twisted.python import failure
-
-from bridgedb import safelog
-from bridgedb.crypto import NEW_BUFFER_INTERFACE
-from bridgedb.email import dkim
-from bridgedb.email import request
-from bridgedb.email import templates
-from bridgedb.email.distributor import EmailRequestedHelp
-from bridgedb.email.distributor import EmailRequestedKey
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.email.distributor import IgnoreEmail
-from bridgedb.parse import addr
-from bridgedb.parse.addr import canonicalizeEmailDomain
-from bridgedb.util import levenshteinDistance
-from bridgedb import translations
-
-
-def createResponseBody(lines, context, client, lang='en'):
- """Parse the **lines** from an incoming email request and determine how to
- respond.
-
- :param list lines: The list of lines from the original request sent by the
- client.
- :type context: class:`bridgedb.email.server.MailServerContext`
- :param context: The context which contains settings for the email server.
- :type client: :api:`twisted.mail.smtp.Address`
- :param client: The client's email address which should be in the
- ``'To:'`` header of the response email.
- :param str lang: The 2-5 character locale code to use for translating the
- email. This is obtained from a client sending a email to a valid plus
- address which includes the translation desired, i.e. by sending an
- email to `bridges+fa at torproject.org
- <mailto:bridges+fa at torproject.org>`__, the client should receive a
- response in Farsi.
- :rtype: None or str
- :returns: None if we shouldn't respond to the client (i.e., if they have
- already received a rate-limiting warning email). Otherwise, returns a
- string containing the (optionally translated) body for the email
- response which we should send out.
- """
- translator = translations.installTranslations(lang)
- bridges = None
- try:
- bridgeRequest = request.determineBridgeRequestOptions(lines)
- bridgeRequest.client = str(client)
-
- # The request was invalid, respond with a help email which explains
- # valid email commands:
- if not bridgeRequest.isValid():
- raise EmailRequestedHelp("Email request from '%s' was invalid."
- % str(client))
-
- # Otherwise they must have requested bridges:
- interval = context.schedule.intervalStart(time.time())
- bridges = context.distributor.getBridges(bridgeRequest, interval)
- except EmailRequestedHelp as error:
- logging.info(error)
- return templates.buildWelcomeText(translator, client)
- except EmailRequestedKey as error:
- logging.info(error)
- return templates.buildKeyMessage(translator, client)
- except TooSoonEmail as error:
- logging.info("Got a mail too frequently: %s." % error)
- return templates.buildSpamWarning(translator, client)
- except (IgnoreEmail, addr.BadEmail) as error:
- logging.info(error)
- # Don't generate a response if their email address is unparsable or
- # invalid, or if we've already warned them about rate-limiting:
- return None
- else:
- answer = "(no bridges currently available)\r\n"
- if bridges:
- transport = bridgeRequest.justOnePTType()
- answer = "".join(" %s\r\n" % b.getBridgeLine(
- bridgeRequest, context.includeFingerprints) for b in bridges)
- return templates.buildAnswerMessage(translator, client, answer)
-
-def generateResponse(fromAddress, client, body, subject=None,
- messageID=None, gpgSignFunc=None):
- """Create a :class:`EmailResponse`, which acts like an in-memory
- ``io.StringIO`` file, by creating and writing all headers and the email
- body into the file-like ``EmailResponse.mailfile``.
-
- :param str fromAddress: The rfc:`2821` email address which should be in
- the :header:`From:` header.
- :type client: :api:`twisted.mail.smtp.Address`
- :param client: The client's email address which should be in the
- ``'To:'`` header of the response email.
- :param str subject: The string to write to the ``Subject:'`` header.
- :param str body: The body of the email. If a **gpgSignFunc** is also
- given, then :meth:`EmailResponse.writeBody` will generate and include
- an ascii-armored OpenPGP signature in the **body**.
- :type messageID: None or str
- :param messageID: The :rfc:`2822` specifier for the ``'Message-ID:'``
- header, if including one is desirable.
- :type gpgSignFunc: ``None`` or callable
- :param gpgSignFunc: A function for signing messages. See
- :func:`bridgedb.crypto.initializeGnuPG` for obtaining a pre-configured
- **gpgSignFunc**.
- :returns: An :class:`EmailResponse` which contains the entire email. To
- obtain the contents of the email, including all headers, simply use
- :meth:`EmailResponse.readContents`.
- """
- response = EmailResponse(gpgSignFunc)
- response.to = client
- response.writeHeaders(fromAddress.encode('utf-8'), str(client), subject,
- inReplyTo=messageID)
- response.writeBody(body.encode('utf-8'))
-
- # Only log the email text (including all headers) if SAFE_LOGGING is
- # disabled:
- if not safelog.safe_logging:
- contents = response.readContents()
- logging.debug("Email contents:\n%s" % str(contents))
- else:
- logging.debug("Email text for %r created." % str(client))
-
- response.rewind()
- return response
-
-
-class EmailResponse(object):
- """Holds information for generating a response email for a request.
-
- .. todo:: At some point, we may want to change this class to optionally
- handle creating Multipart MIME encoding messages, so that we can
- include attachments. (This would be useful for attaching our GnuPG
- keyfile, for example, rather than simply pasting it into the body of
- the email.)
-
- :var _buff: (unicode or buffer) Used internally to write lines for the
- response email into the ``_mailfile``. The reason why both of these
- attributes have two possible types is for the same Python-buggy
- reasons which require :data:`~bridgedb.crypto.NEW_BUFFER_INTERFACE`.
- :var mailfile: (:class:`io.StringIO` or :class:`io.BytesIO`) An in-memory
- file for storing the formatted headers and body of the response email.
- :var str delimiter: Delimiter between lines written to the
- :data:`mailfile`.
- :var bool closed: ``True`` if :meth:`close` has been called.
- :var to: An :api:`twisted.mail.smtp.Address` for the client's email address
- which this response should be sent to.
- """
- _buff = buffer if NEW_BUFFER_INTERFACE else unicode
- mailfile = io.BytesIO if NEW_BUFFER_INTERFACE else io.StringIO
-
- def __init__(self, gpgSignFunc=None):
- """Create a response to an email we have recieved.
-
- This class deals with correctly formatting text for the response email
- headers and the response body into an instance of :data:`mailfile`.
-
- :type gpgSignFunc: ``None`` or callable
- :param gpgSignFunc: A function for signing messages. See
- :func:`bridgedb.crypto.initializeGnuPG` for obtaining a
- pre-configured **gpgSignFunc**.
- """
- self.gpgSign = gpgSignFunc
- self.mailfile = self.mailfile()
- self.delimiter = '\n'
- self.closed = False
- self.to = None
-
- def close(self):
- """Close our :data:`mailfile` and set :data:`closed` to ``True``."""
- logging.debug("Closing %s.mailfile..." % (self.__class__.__name__))
- self.mailfile.close()
- self.closed = True
-
- def read(self, size=None):
- """Read, at most, **size** bytes from our :data:`mailfile`.
-
- .. note:: This method is required by Twisted's SMTP system.
-
- :param int size: The number of bytes to read. Defaults to ``None``,
- which reads until EOF.
- :rtype: str
- :returns: The bytes read from the :data:`mailfile`.
- """
- contents = ''
- logging.debug("Reading%s from %s.mailfile..."
- % ((' {0} bytes'.format(size) if size else ''),
- self.__class__.__name__))
- try:
- if size is not None:
- contents = self.mailfile.read(int(size)).encode('utf-8')
- else:
- contents = self.mailfile.read().encode('utf-8')
- except Exception as error: # pragma: no cover
- logging.exception(error)
-
- return contents
-
- def readContents(self):
- """Read the all the contents written thus far to the :data:`mailfile`,
- and then :meth:`seek` to return to the original pointer position we
- were at before this method was called.
-
- :rtype: str
- :returns: The entire contents of the :data:`mailfile`.
- """
- pointer = self.mailfile.tell()
- self.mailfile.seek(0)
- contents = self.mailfile.read()
- self.mailfile.seek(pointer)
- return contents
-
- def rewind(self):
- """Rewind to the very beginning of the :data:`mailfile`."""
- logging.debug("Rewinding %s.mailfile..." % self.__class__.__name__)
- self.mailfile.seek(0)
-
- def write(self, line):
- """Write the **line** to the :data:`mailfile`.
-
- Any **line** written to me will have :data:`delimiter` appended to it
- beforehand.
-
- :param str line: Something to append into the :data:`mailfile`.
- """
- if line.find('\r\n') != -1:
- # If **line** contains newlines, send it to :meth:`writelines` to
- # break it up so that we can replace them:
- logging.debug("Found newlines in %r. Calling writelines()." % line)
- self.writelines(line)
- else:
- line += self.delimiter
- self.mailfile.write(self._buff(line.encode('utf8')))
- self.mailfile.flush()
-
- def writelines(self, lines):
- """Calls :meth:`write` for each line in **lines**.
-
- Line endings of ``'\\r\\n'`` will be replaced with :data:`delimiter`
- (i.e. ``'\\n'``). See :api:`twisted.mail.smtp.SMTPClient.getMailData`
- for the reason.
-
- :type lines: basestring or list
- :param lines: The lines to write to the :ivar:`mailfile`.
- """
- if isinstance(lines, basestring):
- lines = lines.replace('\r\n', '\n')
- for ln in lines.split('\n'):
- self.write(ln)
- elif isinstance(lines, (list, tuple,)):
- for ln in lines:
- self.write(ln)
-
- def writeHeaders(self, fromAddress, toAddress, subject=None,
- inReplyTo=None, includeMessageID=True,
- contentType='text/plain; charset="utf-8"', **kwargs):
- """Write all headers into the response email.
-
- :param str fromAddress: The email address for the ``'From:'`` header.
- :param str toAddress: The email address for the ``'To:'`` header.
- :type subject: None or str
- :param subject: The ``'Subject:'`` header.
- :type inReplyTo: None or str
- :param inReplyTo: If set, an ``'In-Reply-To:'`` header will be
- generated. This should be set to the ``'Message-ID:'`` header from
- the client's original request email.
- :param bool includeMessageID: If ``True``, generate and include a
- ``'Message-ID:'`` header for the response.
- :param str contentType: The ``'Content-Type:'`` header.
- :kwargs: If given, the key will become the name of the header, and the
- value will become the Contents of that header.
- """
- self.write("From: %s" % fromAddress)
- self.write("To: %s" % toAddress)
- if includeMessageID:
- self.write("Message-ID: %s" % smtp.messageid().encode('utf-8'))
- if inReplyTo:
- self.write("In-Reply-To: %s" % inReplyTo.encode('utf-8'))
- self.write("Content-Type: %s" % contentType.encode('utf-8'))
- self.write("Date: %s" % smtp.rfc822date().encode('utf-8'))
-
- if not subject:
- subject = '[no subject]'
- if not subject.lower().startswith('re'):
- subject = "Re: " + subject
- self.write("Subject: %s" % subject.encode('utf-8'))
-
- if kwargs:
- for headerName, headerValue in kwargs.items():
- headerName = headerName.capitalize()
- headerName = headerName.replace(' ', '-')
- headerName = headerName.replace('_', '-')
- header = "%s: %s" % (headerName, headerValue)
- self.write(header.encode('utf-8'))
-
- # The first blank line designates that the headers have ended:
- self.write(self.delimiter)
-
- def writeBody(self, body):
- """Write the response body into the :cvar:`mailfile`.
-
- If ``EmailResponse.gpgSignFunc`` is set, and signing is configured, the
- **body** will be automatically signed before writing its contents into
- the ``mailfile``.
-
- :param str body: The body of the response email.
- """
- logging.info("Writing email body...")
- if self.gpgSign:
- logging.info("Attempting to sign email...")
- sig = self.gpgSign(body)
- if sig:
- body = sig
- self.writelines(body)
-
-
-class SMTPAutoresponder(smtp.SMTPClient):
- """An :api:`twisted.mail.smtp.SMTPClient` for responding to incoming mail.
-
- The main worker in this class is the :meth:`reply` method, which functions
- to dissect an incoming email from an incoming :class:`SMTPMessage` and
- create a :class:`EmailResponse` email message in reply to it, and then,
- finally, send it out.
-
- :ivar log: A :api:`twisted.python.util.LineLog` cache of messages.
- :ivar debug: If ``True``, enable logging (accessible via :ivar:`log`).
- :ivar str identity: Our FQDN which will be sent during client ``HELO``.
- :ivar incoming: An incoming
- :api:`Message <twisted.mail.smtp.rfc822.Message>`, i.e. as returned
- from :meth:`SMTPMessage.getIncomingMessage`.
- :ivar deferred: A :api:`Deferred <twisted.internet.defer.Deferred>` with
- registered callbacks, :meth:`sentMail` and :meth:`sendError`, which
- will be given to the reactor in order to process the sending of the
- outgoing response email.
- """
- debug = True
- identity = smtp.DNSNAME
-
- def __init__(self):
- """Handle responding (or not) to an incoming email."""
- smtp.SMTPClient.__init__(self, self.identity)
- self.incoming = None
- self.deferred = defer.Deferred()
- self.deferred.addCallback(self.sentMail)
- self.deferred.addErrback(self.sendError)
-
- def getMailData(self):
- """Gather all the data for building the response to the client.
-
- This method must return a file-like object containing the data of the
- message to be sent. Lines in the file should be delimited by ``\\n``.
-
- :rtype: ``None`` or :class:`EmailResponse`
- :returns: An ``EmailResponse``, if we have a response to send in reply
- to the incoming email, otherwise, returns ``None``.
- """
- clients = self.getMailTo()
- if not clients: return
- client = clients[0] # There should have been only one anyway
-
- # Log the email address that this message came from if SAFELOGGING is
- # not enabled:
- if not safelog.safe_logging:
- logging.debug("Incoming email was from %s ..." % client)
-
- if not self.runChecks(client): return
-
- recipient = self.getMailFrom()
- # Look up the locale part in the 'To:' address, if there is one, and
- # get the appropriate Translation object:
- lang = translations.getLocaleFromPlusAddr(recipient)
- logging.info("Client requested email translation: %s" % lang)
-
- body = createResponseBody(self.incoming.lines,
- self.incoming.context,
- client, lang)
- if not body: return # The client was already warned.
-
- messageID = self.incoming.message.getheader("Message-ID", None)
- subject = self.incoming.message.getheader("Subject", None)
- response = generateResponse(recipient, client,
- body, subject, messageID,
- self.incoming.context.gpgSignFunc)
- return response
-
- def getMailTo(self):
- """Attempt to get the client's email address from an incoming email.
-
- :rtype: list
- :returns: A list containing the client's
- :func:`normalized <bridgedb.parse.addr.normalizeEmail>` email
- :api:`Address <twisted.mail.smtp.Address>`, if it originated from
- a domain that we accept and the address was well-formed. Otherwise,
- returns ``None``. Even though we're likely to respond to only one
- client at a time, the return value of this method must be a list
- in order to hook into the rest of
- :api:`twisted.mail.smtp.SMTPClient` correctly.
- """
- clients = []
- addrHeader = None
- try: fromAddr = self.incoming.message.getaddr("From")[1]
- except (IndexError, TypeError, AttributeError): pass
- else: addrHeader = fromAddr
-
- if not addrHeader:
- logging.warn("No From header on incoming mail.")
- try: senderHeader = self.incoming.message.getaddr("Sender")[1]
- except (IndexError, TypeError, AttributeError): pass
- else: addrHeader = senderHeader
- if not addrHeader:
- logging.warn("No Sender header on incoming mail.")
- return clients
-
- client = None
- try:
- if addrHeader in self.incoming.context.whitelist.keys():
- logging.debug("Email address was whitelisted: %s."
- % addrHeader)
- client = smtp.Address(addrHeader)
- else:
- normalized = addr.normalizeEmail(
- addrHeader,
- self.incoming.context.domainMap,
- self.incoming.context.domainRules)
- client = smtp.Address(normalized)
- except (addr.UnsupportedDomain) as error:
- logging.warn(error)
- except (addr.BadEmail, smtp.AddressError) as error:
- logging.warn(error)
-
- if client:
- clients.append(client)
-
- return clients
-
- def getMailFrom(self):
- """Find our address in the recipients list of the **incoming** message.
-
- :rtype: str
- :return: Our address from the recipients list. If we can't find it
- return our default ``EMAIL_FROM_ADDRESS`` from the config file.
- """
- logging.debug("Searching for our email address in 'To:' header...")
-
- ours = None
-
- try:
- ourAddress = smtp.Address(self.incoming.context.fromAddr)
- allRecipients = self.incoming.message.getaddrlist("To")
-
- for _, addr in allRecipients:
- recipient = smtp.Address(addr)
- if not ourAddress.domain in recipient.domain:
- logging.debug(("Not our domain (%s) or subdomain, skipping"
- " email address: %s")
- % (ourAddress.domain, str(recipient)))
- continue
- # The recipient's username should at least start with ours,
- # but it still might be a '+' address.
- if not recipient.local.startswith(ourAddress.local):
- logging.debug(("Username doesn't begin with ours, skipping"
- " email address: %s") % str(recipient))
- continue
- # Only check the username before the first '+':
- beforePlus = recipient.local.split('+', 1)[0]
- if beforePlus == ourAddress.local:
- ours = str(recipient)
- if not ours:
- raise addr.BadEmail(allRecipients)
-
- except Exception as error:
- logging.error(("Couldn't find our email address in incoming email "
- "headers: %r" % error))
- # Just return the email address that we're configured to use:
- ours = self.incoming.context.fromAddr
-
- logging.debug("Found our email address: %s." % ours)
- return ours
-
- def sentMail(self, success):
- """Callback for a :api:`twisted.mail.smtp.SMTPSenderFactory`,
- called when an attempt to send an email is completed.
-
- If some addresses were accepted, code and resp are the response
- to the DATA command. If no addresses were accepted, code is -1
- and resp is an informative message.
-
- :param int code: The code returned by the SMTP Server.
- :param str resp: The string response returned from the SMTP Server.
- :param int numOK: The number of addresses accepted by the remote host.
- :param list addresses: A list of tuples (address, code, resp) listing
- the response to each ``RCPT TO`` command.
- :param log: The SMTP session log. (We don't use this, but it is sent
- by :api:`twisted.mail.smtp.SMTPSenderFactory` nonetheless.)
- """
- numOk, addresses = success
-
- for (address, code, resp) in addresses:
- logging.info("Sent reply to %s" % address)
- logging.debug("SMTP server response: %d %s" % (code, resp))
-
- if self.debug:
- for line in self.log.log:
- if line:
- logging.debug(line)
-
- def sendError(self, fail):
- """Errback for a :api:`twisted.mail.smtp.SMTPSenderFactory`.
-
- :param fail: A :api:`twisted.python.failure.Failure` or a
- :api:`twisted.mail.smtp.SMTPClientError` which occurred during the
- transaction to send the outgoing email.
- """
- logging.debug("called with %r" % fail)
-
- if isinstance(fail, failure.Failure):
- error = fail.getTraceback() or "Unknown"
- elif isinstance(fail, Exception):
- error = fail
- logging.error(error)
-
- try:
- # This handles QUIT commands, disconnecting, and closing the
- # transport:
- smtp.SMTPClient.sendError(self, fail)
- # We might not have `transport` and `protocol` attributes, depending
- # on when and where the error occurred, so just catch and log it:
- except Exception as error:
- logging.error(error)
-
- def reply(self):
- """Reply to an incoming email. Maybe.
-
- If nothing is returned from either :func:`createResponseBody` or
- :func:`generateResponse`, then the incoming email will not be
- responded to at all. This can happen for several reasons, for example:
- if the DKIM signature was invalid or missing, or if the incoming email
- came from an unacceptable domain, or if there have been too many
- emails from this client in the allotted time period.
-
- :rtype: :api:`twisted.internet.defer.Deferred`
- :returns: A ``Deferred`` which will callback when the response has
- been successfully sent, or errback if an error occurred while
- sending the email.
- """
- logging.info("Got an email; deciding whether to reply.")
-
- response = self.getMailData()
- if not response:
- return self.deferred
-
- return self.send(response)
-
- def runChecks(self, client):
- """Run checks on the incoming message, and only reply if they pass.
-
- 1. Check if the client's address is whitelisted.
-
- 2. If it's not whitelisted, check that the domain names, taken from
- the SMTP ``MAIL FROM:`` command and the email ``'From:'`` header, can
- be :func:`canonicalized <addr.canonicalizeEmailDomain>`.
-
- 3. Check that those canonical domains match.
-
- 4. If the incoming message is from a domain which supports DKIM
- signing, then run :func:`bridgedb.email.dkim.checkDKIM` as well.
-
- .. note:: Calling this method sets the ``canonicalFromEmail`` and
- :data:``canonicalDomainRules`` attributes of the :data:`incoming`
- message.
-
- :param client: An :api:`twisted.mail.smtp.Address`, which contains
- the client's email address, extracted from the ``'From:'`` header
- from the incoming email.
- :rtype: bool
- :returns: ``False`` if the checks didn't pass, ``True`` otherwise.
- """
- # If the SMTP ``RCPT TO:`` domain name couldn't be canonicalized, then
- # we *should* have bailed at the SMTP layer, but we'll reject this
- # email again nonetheless:
- if not self.incoming.canonicalFromSMTP:
- logging.warn(("SMTP 'MAIL FROM' wasn't from a canonical domain "
- "for email from %s") % str(client))
- return False
-
- # Allow whitelisted addresses through the canonicalization check:
- if str(client) in self.incoming.context.whitelist.keys():
- self.incoming.canonicalFromEmail = client.domain
- logging.info("'From:' header contained whitelisted address: %s"
- % str(client))
- # Straight up reject addresses in the EMAIL_BLACKLIST config option:
- elif str(client) in self.incoming.context.blacklist:
- logging.info("'From:' header contained blacklisted address: %s")
- return False
- else:
- logging.debug("Canonicalizing client email domain...")
- try:
- # The client's address was already checked to see if it came
- # from a supported domain and is a valid email address in
- # :meth:`getMailTo`, so we should just be able to re-extract
- # the canonical domain safely here:
- self.incoming.canonicalFromEmail = canonicalizeEmailDomain(
- client.domain, self.incoming.canon)
- logging.debug("Canonical email domain: %s"
- % self.incoming.canonicalFromEmail)
- except addr.UnsupportedDomain as error:
- logging.info("Domain couldn't be canonicalized: %s"
- % safelog.logSafely(client.domain))
- return False
-
- # The canonical domains from the SMTP ``MAIL FROM:`` and the email
- # ``From:`` header should match:
- if self.incoming.canonicalFromSMTP != self.incoming.canonicalFromEmail:
- logging.error("SMTP/Email canonical domain mismatch!")
- logging.debug("Canonical domain mismatch: %s != %s"
- % (self.incoming.canonicalFromSMTP,
- self.incoming.canonicalFromEmail))
- #return False
-
- self.incoming.domainRules = self.incoming.context.domainRules.get(
- self.incoming.canonicalFromEmail, list())
-
- # If the domain's ``domainRules`` say to check DKIM verification
- # results, and those results look bad, reject this email:
- if not dkim.checkDKIM(self.incoming.message, self.incoming.domainRules):
- return False
-
- # If fuzzy matching is enabled via the EMAIL_FUZZY_MATCH setting, then
- # calculate the Levenshtein String Distance (see
- # :func:`~bridgedb.util.levenshteinDistance`):
- if self.incoming.context.fuzzyMatch != 0:
- for blacklistedAddress in self.incoming.context.blacklist:
- distance = levenshteinDistance(str(client), blacklistedAddress)
- if distance <= self.incoming.context.fuzzyMatch:
- logging.info("Fuzzy-matched %s to blacklisted address %s!"
- % (self.incoming.canonicalFromEmail,
- blacklistedAddress))
- return False
-
- return True
-
- def send(self, response, retries=0, timeout=30, reaktor=reactor):
- """Send our **response** in reply to :data:`incoming`.
-
- :type client: :api:`twisted.mail.smtp.Address`
- :param client: The email address of the client.
- :param response: A :class:`EmailResponse`.
- :param int retries: Try resending this many times. (default: ``0``)
- :param int timeout: Timeout after this many seconds. (default: ``30``)
- :rtype: :api:`Deferred <twisted.internet.defer.Deferred>`
- :returns: Our :data:`deferred`.
- """
- logging.info("Sending reply to %s ..." % str(response.to))
-
- factory = smtp.SMTPSenderFactory(self.incoming.context.smtpFromAddr,
- str(response.to),
- response,
- self.deferred,
- retries=retries,
- timeout=timeout)
- factory.domain = smtp.DNSNAME
- reaktor.connectTCP(self.incoming.context.smtpServerIP,
- self.incoming.context.smtpServerPort,
- factory)
- return self.deferred
diff --git a/lib/bridgedb/email/distributor.py b/lib/bridgedb/email/distributor.py
deleted file mode 100644
index d8ea9bf..0000000
--- a/lib/bridgedb/email/distributor.py
+++ /dev/null
@@ -1,219 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_distributor -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson
-# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""A :class:`~bridgedb.distribute.Distributor` which hands out
-:class:`bridges <bridgedb.bridges.Bridge>` to clients via an email interface.
-"""
-
-import logging
-import time
-
-import bridgedb.Storage
-
-from bridgedb.Bridges import BridgeRing
-from bridgedb.Bridges import FilteredBridgeSplitter
-from bridgedb.crypto import getHMAC
-from bridgedb.crypto import getHMACFunc
-from bridgedb.distribute import Distributor
-from bridgedb.filters import byFilters
-from bridgedb.filters import byIPv4
-from bridgedb.filters import byIPv6
-from bridgedb.filters import bySubring
-from bridgedb.parse import addr
-
-
-#: The minimum amount of time (in seconds) which must pass before a client who
-#: has previously been given an email response must wait before being eligible
-#: to receive another response.
-MAX_EMAIL_RATE = 3 * 3600
-
-
-class IgnoreEmail(addr.BadEmail):
- """Raised when we get requests from this address after rate warning."""
-
-
-class TooSoonEmail(addr.BadEmail):
- """Raised when we got a request from this address too recently."""
-
-
-class EmailRequestedHelp(Exception):
- """Raised when a client has emailed requesting help."""
-
-
-class EmailRequestedKey(Exception):
- """Raised when an incoming email requested a copy of our GnuPG keys."""
-
-
-class EmailDistributor(Distributor):
- """Object that hands out bridges based on the email address of an incoming
- request and the current time period.
-
- :type hashring: :class:`~bridgedb.Bridges.BridgeRing`
- :ivar hashring: A hashring to hold all the bridges we hand out.
- """
-
- #: The minimum amount of time (in seconds) which must pass before a client
- #: who has previously been given an email response must wait before being
- #: eligible to receive another response.
- emailRateMax = MAX_EMAIL_RATE
-
- def __init__(self, key, domainmap, domainrules,
- answerParameters=None, whitelist=None):
- """Create a bridge distributor which uses email.
-
- :type emailHmac: callable
- :param emailHmac: An hmac function used to order email addresses
- within a ring. See :func:`~bridgedb.crypto.getHMACFunc`.
- :param dict domainmap: A map from lowercase domains that we support
- mail from to their canonical forms. See `EMAIL_DOMAIN_MAP` option
- in `bridgedb.conf`.
- :param domainrules: DOCDOC
- :param answerParameters: DOCDOC
- :type whitelist: dict or ``None``
- :param whitelist: A dictionary that maps whitelisted email addresses
- to GnuPG fingerprints.
- """
- super(EmailDistributor, self).__init__(key)
-
- self.domainmap = domainmap
- self.domainrules = domainrules
- self.whitelist = whitelist or dict()
- self.answerParameters = answerParameters
-
- key1 = getHMAC(key, "Map-Addresses-To-Ring")
- key2 = getHMAC(key, "Order-Bridges-In-Ring")
-
- self.emailHmac = getHMACFunc(key1, hex=False)
- #XXX cache options not implemented
- self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5)
-
- self.name = "Email"
-
- def bridgesPerResponse(self, hashring=None):
- return super(EmailDistributor, self).bridgesPerResponse(hashring)
-
- def getBridges(self, bridgeRequest, interval, clock=None):
- """Return a list of bridges to give to a user.
-
- .. hint:: All checks on the email address (which should be stored in
- the ``bridgeRequest.client`` attribute), such as checks for
- whitelisting and canonicalization of domain name, are done in
- :meth:`bridgedb.email.autoresponder.getMailTo` and
- :meth:`bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
-
- :type bridgeRequest:
- :class:`~bridgedb.email.request.EmailBridgeRequest`
- :param bridgeRequest: A
- :class:`~bridgedb.bridgerequest.BridgeRequestBase` with the
- :data:`~bridgedb.bridgerequest.BridgeRequestBase.client` attribute
- set to a string containing the client's full, canonicalized email
- address.
- :param interval: The time period when we got this request. This can be
- any string, so long as it changes with every period.
- :type clock: :api:`twisted.internet.task.Clock`
- :param clock: If given, use the clock to ask what time it is, rather
- than :api:`time.time`. This should likely only be used for
- testing.
- :rtype: list or ``None``
- :returns: A list of :class:`~bridgedb.bridges.Bridges` for the
- ``bridgeRequest.client``, if allowed. Otherwise, returns ``None``.
- """
- if (not bridgeRequest.client) or (bridgeRequest.client == 'default'):
- raise addr.BadEmail(
- ("%s distributor can't get bridges for invalid email address: "
- "%s") % (self.name, bridgeRequest.client), bridgeRequest.client)
-
- logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
-
- now = time.time()
-
- if clock:
- now = clock.seconds()
-
- with bridgedb.Storage.getDB() as db:
- wasWarned = db.getWarnedEmail(bridgeRequest.client)
- lastSaw = db.getEmailTime(bridgeRequest.client)
- if lastSaw is not None:
- if bridgeRequest.client in self.whitelist:
- logging.info(
- "Whitelisted address %s was last seen %d seconds ago."
- % (bridgeRequest.client, now - lastSaw))
- elif (lastSaw + self.emailRateMax) >= now:
- wait = (lastSaw + self.emailRateMax) - now
- logging.info("Client %s must wait another %d seconds."
- % (bridgeRequest.client, wait))
- if wasWarned:
- raise IgnoreEmail(
- "Client %s was warned." % bridgeRequest.client,
- bridgeRequest.client)
- else:
- logging.info("Sending duplicate request warning.")
- db.setWarnedEmail(bridgeRequest.client, True, now)
- db.commit()
- raise TooSoonEmail("Must wait %d seconds" % wait,
- bridgeRequest.client)
- # warning period is over
- elif wasWarned:
- db.setWarnedEmail(bridgeRequest.client, False)
-
- pos = self.emailHmac("<%s>%s" % (interval, bridgeRequest.client))
-
- ring = None
- filtres = frozenset(bridgeRequest.filters)
- if filtres in self.hashring.filterRings:
- logging.debug("Cache hit %s" % filtres)
- _, ring = self.hashring.filterRings[filtres]
- else:
- logging.debug("Cache miss %s" % filtres)
- key = getHMAC(self.key, "Order-Bridges-In-Ring")
- ring = BridgeRing(key, self.answerParameters)
- self.hashring.addRing(ring, filtres, byFilters(filtres),
- populate_from=self.hashring.bridges)
-
- returnNum = self.bridgesPerResponse(ring)
- result = ring.getBridges(pos, returnNum)
-
- db.setEmailTime(bridgeRequest.client, now)
- db.commit()
-
- return result
-
- def cleanDatabase(self):
- """Clear all emailed response and warning times from the database."""
- logging.info(("Cleaning all response and warning times for the %s "
- "distributor from the database...") % self.name)
- with bridgedb.Storage.getDB() as db:
- try:
- db.cleanEmailedBridges(time.time() - self.emailRateMax)
- db.cleanWarnedEmails(time.time() - self.emailRateMax)
- except:
- db.rollback()
- raise
- else:
- db.commit()
-
- def prepopulateRings(self):
- """Prepopulate this distributor's hashrings and subhashrings with
- bridges.
- """
- logging.info("Prepopulating %s distributor hashrings..." % self.name)
-
- for filterFn in [byIPv4, byIPv6]:
- ruleset = frozenset([filterFn])
- key = getHMAC(self.key, "Order-Bridges-In-Ring")
- ring = BridgeRing(key, self.answerParameters)
- self.hashring.addRing(ring, ruleset, byFilters([filterFn]),
- populate_from=self.hashring.bridges)
-
- # Since prepopulateRings is called every half hour when the bridge
- # descriptors are re-parsed, we should clean the database then.
- self.cleanDatabase()
diff --git a/lib/bridgedb/email/dkim.py b/lib/bridgedb/email/dkim.py
deleted file mode 100644
index d1075aa..0000000
--- a/lib/bridgedb/email/dkim.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_dkim -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson <nickm at torproject.org>
-# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# Matthew Finkel <sysrqb at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""
-.. py:module:: bridgedb.email.dkim
- :synopsis: Functions for checking DKIM verification results in email
- headers.
-
-bridgedb.email.dkim
-===================
-
-Functions for checking DKIM verification results in email headers.
-
-::
-
- bridgedb.email.dkim
- |_ checkDKIM - Check the DKIM verification results header.
-..
-"""
-
-from __future__ import unicode_literals
-
-import logging
-
-
-def checkDKIM(message, rules):
- """Check the DKIM verification results header.
-
- This check is only run if the incoming email, **message**, originated from
- a domain for which we're configured (in the ``EMAIL_DOMAIN_RULES``
- dictionary in the config file) to check DKIM verification results for.
-
- Returns ``False`` if:
-
- 1. We're supposed to expect and check the DKIM headers for the
- client's email provider domain.
- 2. Those headers were *not* okay.
-
- Otherwise, returns ``True``.
-
- :type message: :api:`twisted.mail.smtp.rfc822.Message`
- :param message: The incoming client request email, including headers.
- :param dict rules: The list of configured ``EMAIL_DOMAIN_RULES`` for the
- canonical domain which the client's email request originated from.
- :rtype: bool
- :returns: ``False`` if the checks failed, ``True`` otherwise.
- """
- logging.info("Checking DKIM verification results...")
- logging.debug("Domain has rules: %s" % ', '.join(rules))
-
- if 'dkim' in rules:
- # getheader() returns the last of a given kind of header; we want
- # to get the first, so we use getheaders() instead.
- dkimHeaders = message.getheaders("X-DKIM-Authentication-Results")
- dkimHeader = "<no header>"
- if dkimHeaders:
- dkimHeader = dkimHeaders[0]
- if not dkimHeader.startswith("pass"):
- logging.info("Rejecting bad DKIM header on incoming email: %r "
- % dkimHeader)
- return False
- return True
diff --git a/lib/bridgedb/email/request.py b/lib/bridgedb/email/request.py
deleted file mode 100644
index 50fd32c..0000000
--- a/lib/bridgedb/email/request.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# -*- coding: utf-8; test-case-name: bridgedb.test.test_email_request; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson <nickm at torproject.org>
-# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# Matthew Finkel <sysrqb at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""
-.. py:module:: bridgedb.email.request
- :synopsis: Classes for parsing and storing information about requests for
- bridges which are sent to the email distributor.
-
-bridgedb.email.request
-======================
-
-Classes for parsing and storing information about requests for bridges
-which are sent to the email distributor.
-
-::
-
- bridgedb.email.request
- | |_ determineBridgeRequestOptions - Figure out which filters to apply, or
- | offer help.
- |_ EmailBridgeRequest - A request for bridges which was received through
- the email distributor.
-..
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import logging
-import re
-
-from bridgedb import bridgerequest
-from bridgedb.email.distributor import EmailRequestedHelp
-from bridgedb.email.distributor import EmailRequestedKey
-
-
-#: A regular expression for matching the Pluggable Transport method TYPE in
-#: emailed requests for Pluggable Transports.
-TRANSPORT_REGEXP = ".*transport ([a-z][_a-z0-9]*)"
-TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP)
-
-#: A regular expression that matches country codes in requests for unblocked
-#: bridges.
-UNBLOCKED_REGEXP = ".*unblocked ([a-z]{2,4})"
-UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP)
-
-
-def determineBridgeRequestOptions(lines):
- """Figure out which :class:`Bridges.BridgeFilter`s to apply, or offer help.
-
- .. note:: If any ``'transport TYPE'`` was requested, or bridges not
- blocked in a specific CC (``'unblocked CC'``), then the ``TYPE``
- and/or ``CC`` will *always* be stored as a *lowercase* string.
-
- :param list lines: A list of lines from an email, including the headers.
- :raises EmailRequestedHelp: if the client requested help.
- :raises EmailRequestedKey: if the client requested our GnuPG key.
- :rtype: :class:`EmailBridgeRequest`
- :returns: A :class:`~bridgerequst.BridgeRequest` with all of the requested
- parameters set. The returned ``BridgeRequest`` will have already had
- its filters generated via :meth:`~EmailBridgeRequest.generateFilters`.
- """
- request = EmailBridgeRequest()
- skippedHeaders = False
-
- for line in lines:
- line = line.strip().lower()
- # Ignore all lines before the first empty line:
- if not line: skippedHeaders = True
- if not skippedHeaders: continue
-
- if ("help" in line) or ("halp" in line):
- raise EmailRequestedHelp("Client requested help.")
-
- if "get" in line:
- request.isValid(True)
- logging.debug("Email request was valid.")
- if "key" in line:
- request.wantsKey(True)
- raise EmailRequestedKey("Email requested a copy of our GnuPG key.")
- if "ipv6" in line:
- request.withIPv6()
- if "transport" in line:
- request.withPluggableTransportType(line)
- if "unblocked" in line:
- request.withoutBlockInCountry(line)
-
- logging.debug("Generating hashring filters for request.")
- request.generateFilters()
- return request
-
-
-class EmailBridgeRequest(bridgerequest.BridgeRequestBase):
- """We received a request for bridges through the email distributor."""
-
- def __init__(self):
- """Process a new bridge request received through the
- :class:`~bridgedb.email.distributor.EmailDistributor`.
- """
- super(EmailBridgeRequest, self).__init__()
- self._wantsKey = False
-
- def wantsKey(self, wantsKey=None):
- """Get or set whether this bridge request wanted our GnuPG key.
-
- If called without parameters, this method will return the current
- state, otherwise (if called with the **wantsKey** parameter set), it
- will set the current state for whether or not this request wanted our
- key.
-
- :param bool wantsKey: If given, set the validity state of this
- request. Otherwise, get the current state.
- """
- if wantsKey is not None:
- self._wantsKey = bool(wantsKey)
- return self._wantsKey
-
- def withoutBlockInCountry(self, line):
- """This request was for bridges not blocked in **country**.
-
- Add any country code found in the **line** to the list of
- ``notBlockedIn``. Currently, a request for a transport is recognized
- if the email line contains the ``'unblocked'`` command.
-
- :param str country: The line from the email wherein the client
- requested some type of Pluggable Transport.
- """
- unblocked = None
-
- logging.debug("Parsing 'unblocked' line: %r" % line)
- try:
- unblocked = UNBLOCKED_PATTERN.match(line).group(1)
- except (TypeError, AttributeError):
- pass
-
- if unblocked:
- self.notBlockedIn.append(unblocked)
- logging.info("Email requested bridges not blocked in: %r"
- % unblocked)
-
- def withPluggableTransportType(self, line):
- """This request included a specific Pluggable Transport identifier.
-
- Add any Pluggable Transport method TYPE found in the **line** to the
- list of ``transports``. Currently, a request for a transport is
- recognized if the email line contains the ``'transport'`` command.
-
- :param str line: The line from the email wherein the client
- requested some type of Pluggable Transport.
- """
- transport = None
- logging.debug("Parsing 'transport' line: %r" % line)
-
- try:
- transport = TRANSPORT_PATTERN.match(line).group(1)
- except (TypeError, AttributeError):
- pass
-
- if transport:
- self.transports.append(transport)
- logging.info("Email requested transport type: %r" % transport)
diff --git a/lib/bridgedb/email/server.py b/lib/bridgedb/email/server.py
deleted file mode 100644
index 736c3f6..0000000
--- a/lib/bridgedb/email/server.py
+++ /dev/null
@@ -1,496 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_server -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson <nickm at torproject.org>
-# Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# Matthew Finkel <sysrqb at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-"""
-.. py:module:: bridgedb.email.server
- :synopsis: Servers which interface with clients and distribute bridges
- over SMTP.
-
-bridgedb.email.server
-=====================
-
-Servers which interface with clients and distribute bridges over SMTP.
-
-::
-
- bridgedb.email.server
- | |_ addServer - Set up a SMTP server which listens on the configured
- | EMAIL_PORT for incoming connections, and responds as
- | necessary to requests for bridges.
- |
- |_ MailServerContext - Helper object that holds information used by the
- | email subsystem.
- |_ SMTPMessage - Plugs into Twisted Mail and receives an incoming message.
- |_ SMTPIncomingDelivery - Plugs into SMTPIncomingServerFactory and handles
- | SMTP commands for incoming connections.
- |_ SMTPIncomingDeliveryFactory - Factory for SMTPIncomingDeliverys.
- |_ SMTPIncomingServerFactory - Plugs into twisted.mail.smtp.SMTPFactory;
- creates a new SMTPMessageDelivery, which
- handles response email automation, whenever
- we get a incoming connection on the SMTP port.
-..
-"""
-
-from __future__ import unicode_literals
-
-import logging
-import io
-import socket
-
-from twisted.internet import defer
-from twisted.internet import reactor
-from twisted.internet.error import CannotListenError
-from twisted.internet.task import LoopingCall
-from twisted.mail import smtp
-from twisted.mail.smtp import rfc822date
-from twisted.python import failure
-
-from zope.interface import implements
-
-from bridgedb import __version__
-from bridgedb import safelog
-from bridgedb.crypto import initializeGnuPG
-from bridgedb.email import autoresponder
-from bridgedb.email import templates
-from bridgedb.email import request
-from bridgedb.parse import addr
-from bridgedb.parse.addr import UnsupportedDomain
-from bridgedb.parse.addr import canonicalizeEmailDomain
-from bridgedb.schedule import ScheduledInterval
-from bridgedb.schedule import Unscheduled
-
-
-class MailServerContext(object):
- """Helper object that holds information used by email subsystem.
-
- :ivar str username: Reject any RCPT TO lines that aren't to this
- user. See the ``EMAIL_USERNAME`` option in the config file.
- (default: ``'bridges'``)
- :ivar int maximumSize: Reject any incoming emails longer than
- this size (in bytes). (default: 3084 bytes).
- :ivar int smtpPort: The port to use for outgoing SMTP.
- :ivar str smtpServer: The IP address to use for outgoing SMTP.
- :ivar str smtpFromAddr: Use this address in the raw SMTP ``MAIL FROM``
- line for outgoing mail. (default: ``bridges at torproject.org``)
- :ivar str fromAddr: Use this address in the email ``From:``
- line for outgoing mail. (default: ``bridges at torproject.org``)
- :ivar int nBridges: The number of bridges to send for each email.
- :ivar list blacklist: A list of blacklisted email addresses, taken from
- the ``EMAIL_BLACKLIST`` config setting.
- :ivar int fuzzyMatch: An integer specifying the maximum Levenshtein
- Distance from an incoming email address to a blacklisted email address
- for the incoming email to be dropped.
- :ivar gpg: A :class:`gnupg.GPG` interface_, as returned by
- :func:`~bridgedb.crypto.initialiseGnuPG`, or ``None`` if we couldn't
- initialize GnuPG for some reason.
- :ivar gpgSignFunc: A callable which signs a message, e.g. the one returned
- from :func:`~bridgedb.crypto.initialiseGnuPG`.
- """
-
- def __init__(self, config, distributor, schedule):
- """Create a context for storing configs for email bridge distribution.
-
- :type config: :class:`bridgedb.persistent.Conf`
- :type distributor: :class:`~bridgedb.email.distributor.EmailDistributor`
- :param distributor: The distributor will handle getting the correct
- bridges (or none) for a client for us.
- :type schedule: :class:`bridgedb.schedule.ScheduledInterval`
- :param schedule: An interval-based scheduler, used to help the
- :data:`distributor` know if we should give bridges to a client.
- """
- self.config = config
- self.distributor = distributor
- self.schedule = schedule
-
- self.maximumSize = smtp.SMTP.MAX_LENGTH
- self.includeFingerprints = config.EMAIL_INCLUDE_FINGERPRINTS
- self.nBridges = config.EMAIL_N_BRIDGES_PER_ANSWER
-
- self.username = (config.EMAIL_USERNAME or "bridges")
- self.hostname = socket.gethostname()
- self.fromAddr = (config.EMAIL_FROM_ADDR or "bridges at torproject.org")
- self.smtpFromAddr = (config.EMAIL_SMTP_FROM_ADDR or self.fromAddr)
- self.smtpServerPort = (config.EMAIL_SMTP_PORT or 25)
- self.smtpServerIP = (config.EMAIL_SMTP_HOST or "127.0.0.1")
-
- self.domainRules = config.EMAIL_DOMAIN_RULES or {}
- self.domainMap = config.EMAIL_DOMAIN_MAP or {}
- self.canon = self.buildCanonicalDomainMap()
- self.whitelist = config.EMAIL_WHITELIST or {}
- self.blacklist = config.EMAIL_BLACKLIST or []
- self.fuzzyMatch = config.EMAIL_FUZZY_MATCH or 0
-
- self.gpg, self.gpgSignFunc = initializeGnuPG(config)
-
- def buildCanonicalDomainMap(self):
- """Build a map for all email provider domains from which we will accept
- emails to their canonical domain name.
-
- .. note:: Be sure that ``MailServerContext.domainRules`` and
- ``MailServerContext.domainMap`` are set appropriately before calling
- this method.
-
- This method is automatically called during initialisation, and the
- resulting domain map is stored as ``MailServerContext.canon``.
-
- :rtype: dict
- :returns: A dictionary which maps all domains and subdomains which we
- accept emails from to their second-level, canonical domain names.
- """
- canon = self.domainMap
- for domain, rule in self.domainRules.items():
- if domain not in canon.keys():
- canon[domain] = domain
- for domain in self.config.EMAIL_DOMAINS:
- canon[domain] = domain
- return canon
-
-
-class SMTPMessage(object):
- """Plugs into the Twisted Mail and receives an incoming message.
-
- :var list lines: A list of lines from an incoming email message.
- :var int nBytes: The number of bytes received thus far.
- :var bool ignoring: If ``True``, we're ignoring the rest of this message
- because it exceeded :data:`MailServerContext.maximumSize`.
- :var canonicalFromSMTP: See
- :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
- :var canonicalFromEmail: See
- :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
- :var canonicalDomainRules: See
- :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
- :var message: (:api:`twisted.mail.smtp.rfc822.Message` or ``None``) The
- incoming email message.
- :var responder: A :class:`~bridgedb.email.autoresponder.SMTPAutoresponder`
- which parses and checks the incoming :data:`message`. If it decides to
- do so, it will build a
- :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.reply` email
- and :meth:`~bridgedb.email.autoresponder.SMTPAutoresponder.send` it.
- """
- implements(smtp.IMessage)
-
- def __init__(self, context, canonicalFromSMTP=None):
- """Create a new SMTPMessage.
-
- These are created automatically via
- :class:`SMTPIncomingDelivery`.
-
- :param context: The configured :class:`MailServerContext`.
- :type canonicalFromSMTP: str or None
- :param canonicalFromSMTP: The canonical domain which this message was
- received from. For example, if ``'gmail.com'`` is the configured
- canonical domain for ``'googlemail.com'`` and a message is
- received from the latter domain, then this would be set to the
- former.
- """
- self.context = context
- self.canon = context.canon
- self.canonicalFromSMTP = canonicalFromSMTP
- self.canonicalFromEmail = None
- self.canonicalDomainRules = None
-
- self.lines = []
- self.nBytes = 0
- self.ignoring = False
-
- self.message = None
- self.responder = autoresponder.SMTPAutoresponder()
- self.responder.incoming = self
-
- def lineReceived(self, line):
- """Called when we get another line of an incoming message."""
- self.nBytes += len(line)
- if self.nBytes > self.context.maximumSize:
- self.ignoring = True
- else:
- self.lines.append(line)
- if not safelog.safe_logging:
- try:
- ln = line.rstrip("\r\n").encode('utf-8', 'replace')
- logging.debug("> %s" % ln)
- except (UnicodeError, UnicodeDecodeError): # pragma: no cover
- pass
- except Exception as error: # pragma: no cover
- logging.error("Error while trying to log incoming email")
- logging.exception(error)
-
- def eomReceived(self):
- """Tell the :data:`responder` to reply when we receive an EOM."""
- if not self.ignoring:
- self.message = self.getIncomingMessage()
- self.responder.reply()
- return defer.succeed(None)
-
- def connectionLost(self):
- """Called if we die partway through reading a message."""
- pass
-
- def getIncomingMessage(self):
- """Create and parse an :rfc:`2822` message object for all :data:`lines`
- received thus far.
-
- :rtype: :api:`twisted.mail.smtp.rfc822.Message`
- :returns: A ``Message`` comprised of all lines received thus far.
- """
- rawMessage = io.StringIO()
- for line in self.lines:
- rawMessage.writelines(unicode(line.decode('utf8')) + u'\n')
- rawMessage.seek(0)
- return smtp.rfc822.Message(rawMessage)
-
-
-class SMTPIncomingDelivery(smtp.SMTP):
- """Plugs into :class:`SMTPIncomingServerFactory` and handles SMTP commands
- for incoming connections.
-
- :type context: :class:`MailServerContext`
- :var context: A context containing SMTP/Email configuration settings.
- :var deferred: A :api:`deferred <twisted.internet.defer.Deferred>` which
- will be returned when :meth:`reply` is called. Additional callbacks
- may be set on this deferred in order to schedule additional actions
- when the response is being sent.
- :type fromCanonicalSMTP: str or ``None``
- :var fromCanonicalSMTP: If set, this is the canonicalized domain name of
- the address we received from incoming connection's ``MAIL FROM:``.
- """
- implements(smtp.IMessageDelivery)
-
- context = None
- deferred = defer.Deferred()
- fromCanonicalSMTP = None
-
- @classmethod
- def setContext(cls, context):
- """Set our :data:`context` to a new :class:`MailServerContext`."""
- cls.context = context
-
- def receivedHeader(self, helo, origin, recipients):
- """Create the ``Received:`` header for an incoming email.
-
- :type helo: tuple
- :param helo: The lines received during SMTP client HELO.
- :type origin: :api:`twisted.mail.smtp.Address`
- :param origin: The email address of the sender.
- :type recipients: list
- :param recipients: A list of :api:`twisted.mail.smtp.User` instances.
- """
- helo_ = ' helo={0}'.format(helo[0]) if helo[0] else ''
- from_ = 'from %s ([%s]%s)' % (helo[0], helo[1], helo_)
- by_ = 'by %s with BridgeDB (%s)' % (smtp.DNSNAME, __version__)
- for_ = 'for %s; %s ' % (' '.join(map(str, recipients)), rfc822date())
- return str('Received: %s\n\t%s\n\t%s' % (from_, by_, for_))
-
- def validateFrom(self, helo, origin):
- """Validate the ``MAIL FROM:`` address on the incoming SMTP connection.
-
- This is done at the SMTP layer. Meaning that if a Postfix or other
- email server is proxying emails from the outside world to BridgeDB,
- the :api:`origin.domain <twisted.email.smtp.Address.domain>` will be
- set to the local hostname. Therefore, if the SMTP ``MAIL FROM:``
- domain name is our own hostname (as returned from
- :func:`socket.gethostname`) or our own FQDN, allow the connection.
-
- Otherwise, if the ``MAIL FROM:`` domain has a canonical domain in our
- mapping (taken from our :data:`context.canon`, which is taken in turn
- from the ``EMAIL_DOMAIN_MAP``), then our :data:`fromCanonicalSMTP` is
- set to that domain.
-
- :type helo: tuple
- :param helo: The lines received during SMTP client HELO.
- :type origin: :api:`twisted.mail.smtp.Address`
- :param origin: The email address we received this message from.
- :raises: :api:`twisted.mail.smtp.SMTPBadSender` if the
- ``origin.domain`` was neither our local hostname, nor one of the
- canonical domains listed in :ivar:`context.canon`.
- :rtype: :api:`twisted.mail.smtp.Address`
- :returns: The ``origin``. We *must* return some non-``None`` data from
- this method, or else Twisted will reply to the sender with a 503
- error.
- """
- try:
- if str(origin) in self.context.whitelist.keys():
- logging.warn("Got SMTP 'MAIL FROM:' whitelisted address: %s."
- % str(origin))
- # We need to be certain later that when the fromCanonicalSMTP
- # domain is checked again the email 'From:' canonical domain,
- # that we allow whitelisted addresses through the check.
- self.fromCanonicalSMTP = origin.domain
- return origin
- if ((origin.domain == self.context.hostname) or
- (origin.domain == smtp.DNSNAME)):
- self.fromCanonicalSMTP = origin.domain
- else:
- logging.debug("Canonicalizing client SMTP domain...")
- canonical = canonicalizeEmailDomain(origin.domain,
- self.context.canon)
- logging.debug("Canonical SMTP domain: %r" % canonical)
- self.fromCanonicalSMTP = canonical
- except UnsupportedDomain as error:
- logging.info(error)
- raise smtp.SMTPBadSender(origin)
- except Exception as error:
- logging.exception(error)
-
- # This method **cannot** return None, or it'll cause a 503 error.
- return origin
-
- def validateTo(self, user):
- """Validate the SMTP ``RCPT TO:`` address for the incoming connection.
-
- The local username and domain name to which this SMTP message is
- addressed, after being stripped of any ``'+'`` aliases, **must** be
- identical to those in the email address set our
- ``EMAIL_SMTP_FROM_ADDR`` configuration file option.
-
- :type user: :api:`twisted.mail.smtp.User`
- :param user: Information about the user this SMTP message was
- addressed to.
- :raises: A :api:`twisted.mail.smtp.SMTPBadRcpt` if any of the above
- conditions weren't met.
- :rtype: callable
- :returns: A parameterless function which returns an instance of
- :class:`SMTPMessage`.
- """
- logging.debug("Validating SMTP 'RCPT TO:' email address...")
-
- recipient = user.dest
- ourAddress = smtp.Address(self.context.smtpFromAddr)
-
- if not ((ourAddress.domain in recipient.domain) or
- (recipient.domain == "bridgedb")):
- logging.debug(("Not our domain (%s) or subdomain, skipping"
- " SMTP 'RCPT TO' address: %s")
- % (ourAddress.domain, str(recipient)))
- raise smtp.SMTPBadRcpt(str(recipient))
- # The recipient's username should at least start with ours,
- # but it still might be a '+' address.
- if not recipient.local.startswith(ourAddress.local):
- logging.debug(("Username doesn't begin with ours, skipping"
- " SMTP 'RCPT TO' address: %s") % str(recipient))
- raise smtp.SMTPBadRcpt(str(recipient))
- # Ignore everything after the first '+', if there is one.
- beforePlus = recipient.local.split('+', 1)[0]
- if beforePlus != ourAddress.local:
- raise smtp.SMTPBadRcpt(str(recipient))
-
- return lambda: SMTPMessage(self.context, self.fromCanonicalSMTP)
-
-
-class SMTPIncomingDeliveryFactory(object):
- """Factory for :class:`SMTPIncomingDelivery` s.
-
- This class is used to distinguish between different messages delivered
- over the same connection. This can be used to optimize delivery of a
- single message to multiple recipients, something which cannot be done by
- :api:`IMessageDelivery <twisted.mail.smtp.IMessageDelivery>` implementors
- due to their lack of information.
-
- :var context: A :class:`MailServerContext` for storing configuration settings.
- :var delivery: A :class:`SMTPIncomingDelivery` to deliver incoming
- SMTP messages to.
- """
- implements(smtp.IMessageDeliveryFactory)
-
- context = None
- delivery = SMTPIncomingDelivery
-
- def __init__(self):
- logging.debug("%s created." % self.__class__.__name__)
-
- @classmethod
- def setContext(cls, context):
- """Set our :data:`context` and the context for our :data:`delivery`."""
- cls.context = context
- cls.delivery.setContext(cls.context)
-
- def getMessageDelivery(self):
- """Get a new :class:`SMTPIncomingDelivery` instance."""
- return self.delivery()
-
-
-class SMTPIncomingServerFactory(smtp.SMTPFactory):
- """Plugs into :api:`twisted.mail.smtp.SMTPFactory`; creates a new
- :class:`SMTPIncomingDeliveryFactory`, which handles response email
- automation whenever we get a incoming connection on the SMTP port.
-
- .. warning:: My :data:`context` isn't an OpenSSL context, as is used for
- the :api:`twisted.mail.smtp.ESMTPSender`.
-
- :ivar context: A :class:`MailServerContext` for storing configuration settings.
- :ivar deliveryFactory: A :class:`SMTPIncomingDeliveryFactory` for
- producing :class:`SMTPIncomingDelivery`s.
- :ivar domain: :api:`Our FQDN <twisted.mail.smtp.DNSNAME>`.
- :ivar int timeout: The number of seconds to wait, after the last chunk of
- data was received, before raising a
- :api:`SMTPTimeoutError <twisted.mail.smtp.SMTPTimeoutError>` for an
- incoming connection.
- :ivar protocol: :api:`SMTP <twisted.mail.smtp.SMTP>`
- """
-
- context = None
- deliveryFactory = SMTPIncomingDeliveryFactory
-
- def __init__(self, **kwargs):
- smtp.SMTPFactory.__init__(self, **kwargs)
- self.deliveryFactory = self.deliveryFactory()
-
- @classmethod
- def setContext(cls, context):
- """Set :data:`context` and :data:`deliveryFactory`.context."""
- cls.context = context
- cls.deliveryFactory.setContext(cls.context)
-
- def buildProtocol(self, addr):
- p = smtp.SMTPFactory.buildProtocol(self, addr)
- self.deliveryFactory.transport = p.transport # XXX is this set yet?
- p.factory = self
- p.deliveryFactory = self.deliveryFactory
- return p
-
-
-def addServer(config, distributor):
- """Set up a SMTP server which listens on the configured ``EMAIL_PORT`` for
- incoming connections, and responds as necessary to requests for bridges.
-
- :type config: :class:`bridgedb.configure.Conf`
- :param config: A configuration object.
- :type distributor: :class:`bridgedb.email.distributor.EmailDistributor`
- :param dist: A distributor which will handle database interactions, and
- will decide which bridges to give to who and when.
- """
- if config.EMAIL_ROTATION_PERIOD:
- count, period = config.EMAIL_ROTATION_PERIOD.split()
- schedule = ScheduledInterval(count, period)
- else:
- schedule = Unscheduled()
-
- context = MailServerContext(config, distributor, schedule)
- factory = SMTPIncomingServerFactory()
- factory.setContext(context)
-
- addr = config.EMAIL_BIND_IP or ""
- port = config.EMAIL_PORT or 6725
-
- try:
- reactor.listenTCP(port, factory, interface=addr)
- except CannotListenError as error: # pragma: no cover
- logging.fatal(error)
- raise SystemExit(error.message)
-
- # Set up a LoopingCall to run every 30 minutes and forget old email times.
- lc = LoopingCall(distributor.cleanDatabase)
- lc.start(1800, now=False)
-
- return factory
diff --git a/lib/bridgedb/email/templates.py b/lib/bridgedb/email/templates.py
deleted file mode 100644
index 6998716..0000000
--- a/lib/bridgedb/email/templates.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_email_templates -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""
-.. py:module:: bridgedb.email.templates
- :synopsis: Templates for formatting emails sent out by the email
- distributor.
-
-bridgedb.email.templates
-========================
-
-Templates for formatting emails sent out by the email distributor.
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import logging
-import os
-
-from datetime import datetime
-
-from bridgedb import strings
-from bridgedb.email.distributor import MAX_EMAIL_RATE
-
-
-def addCommands(template):
- """Add some text telling a client about supported email command, as well as
- which Pluggable Transports are currently available.
- """
- # Tell them about the various email commands:
- cmdlist = []
- cmdlist.append(template.gettext(strings.EMAIL_MISC_TEXT.get(3)))
- for cmd, desc in strings.EMAIL_COMMANDS.items():
- command = ' '
- command += cmd
- while not len(command) >= 25: # Align the command descriptions
- command += ' '
- command += template.gettext(desc)
- cmdlist.append(command)
-
- commands = "\n".join(cmdlist) + "\n\n"
- # And include the currently supported transports:
- commands += template.gettext(strings.EMAIL_MISC_TEXT.get(5))
- commands += "\n"
- for pt in strings._getSupportedTransports():
- commands += ' ' + pt + "\n"
-
- return commands
-
-def addGreeting(template, clientName=None, welcome=False):
- greeting = ""
-
- if not clientName:
- greeting = template.gettext(strings.EMAIL_MISC_TEXT[7])
- else:
- greeting = template.gettext(strings.EMAIL_MISC_TEXT[6]) % clientName
-
- if greeting:
- if welcome:
- greeting += u' '
- greeting += template.gettext(strings.EMAIL_MISC_TEXT[4])
- greeting += u'\n\n'
-
- return greeting
-
-def addKeyfile(template):
- return u'%s\n\n' % strings.BRIDGEDB_OPENPGP_KEY
-
-def addBridgeAnswer(template, answer):
- # Give the user their bridges, i.e. the `answer`:
- bridgeLines = template.gettext(strings.EMAIL_MISC_TEXT[0])
- bridgeLines += u"\n\n"
- bridgeLines += template.gettext(strings.EMAIL_MISC_TEXT[1])
- bridgeLines += u"\n\n"
- bridgeLines += u"%s\n\n" % answer
-
- return bridgeLines
-
-def addHowto(template):
- """Add help text on how to add bridges to Tor Browser.
-
- :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation``
- :param template: A gettext translations instance, optionally with fallback
- languages set.
- """
- howToTBB = template.gettext(strings.HOWTO_TBB[1]) % strings.EMAIL_SPRINTF["HOWTO_TBB1"]
- howToTBB += u'\n\n'
- howToTBB += template.gettext(strings.HOWTO_TBB[2])
- howToTBB += u'\n\n'
- howToTBB += u'\n'.join(["> {0}".format(ln) for ln in
- template.gettext(strings.HOWTO_TBB[3]).split('\n')])
- howToTBB += u'\n\n'
- howToTBB += template.gettext(strings.HOWTO_TBB[4])
- howToTBB += u'\n\n'
- howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB1")
- howToTBB += u'\n\n'
- return howToTBB
-
-def addFooter(template, clientAddress=None):
- """Add a footer::
-
- --
- <3 BridgeDB
- ________________________________________________________________________
- Public Keys: https://bridges.torproject.org/keys
-
- This email was generated with rainbows, unicorns, and sparkles
- for alice at example.com on Friday, 09 May, 2014 at 18:59:39.
-
-
- :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation``
- :param template: A gettext translations instance, optionally with fallback
- languages set.
- :type clientAddress: :api:`twisted.mail.smtp.Address`
- :param clientAddress: The client's email address which should be in the
- ``To:`` header of the response email.
- """
- now = datetime.utcnow()
- clientAddr = clientAddress.addrstr
-
- footer = u' --\n'
- footer += u' <3 BridgeDB\n'
- footer += u'_' * 70
- footer += u'\n'
- footer += template.gettext(strings.EMAIL_MISC_TEXT[8])
- footer += u': https://bridges.torproject.org/keys\n'
- footer += template.gettext(strings.EMAIL_MISC_TEXT[9]) \
- % (clientAddr,
- now.strftime('%A, %d %B, %Y'),
- now.strftime('%H:%M:%S'))
- footer += u'\n\n'
-
- return footer
-
-def buildKeyMessage(template, clientAddress=None):
- message = addKeyfile(template)
- message += addFooter(template, clientAddress)
- return message
-
-def buildWelcomeText(template, clientAddress=None):
- sections = []
- sections.append(addGreeting(template, clientAddress.local, welcome=True))
-
- commands = addCommands(template)
- sections.append(commands)
-
- # Include the same messages as the homepage of the HTTPS distributor:
- welcome = template.gettext(strings.WELCOME[0]) % strings.EMAIL_SPRINTF["WELCOME0"]
- welcome += template.gettext(strings.WELCOME[1])
- welcome += template.gettext(strings.WELCOME[2]) % strings.EMAIL_SPRINTF["WELCOME2"]
- sections.append(welcome)
-
- message = u"\n\n".join(sections)
- # Add the markdown links at the end:
- message += strings.EMAIL_REFERENCE_LINKS.get("WELCOME0")
- message += u"\n\n"
- message += addFooter(template, clientAddress)
-
- return message
-
-def buildAnswerMessage(template, clientAddress=None, answer=None):
- try:
- message = addGreeting(template, clientAddress.local)
- message += addBridgeAnswer(template, answer)
- message += addHowto(template)
- message += u'\n\n'
- message += addCommands(template)
- message += u'\n\n'
- message += addFooter(template, clientAddress)
- except Exception as error: # pragma: no cover
- logging.error("Error while formatting email message template:")
- logging.exception(error)
-
- return message
-
-def buildSpamWarning(template, clientAddress=None):
- message = addGreeting(template, clientAddress.local)
-
- try:
- message += template.gettext(strings.EMAIL_MISC_TEXT[0])
- message += u"\n\n"
- message += template.gettext(strings.EMAIL_MISC_TEXT[2]) \
- % str(MAX_EMAIL_RATE / 3600)
- message += u"\n\n"
- message += addFooter(template, clientAddress)
- except Exception as error: # pragma: no cover
- logging.error("Error while formatting email spam template:")
- logging.exception(error)
-
- return message
diff --git a/lib/bridgedb/filters.py b/lib/bridgedb/filters.py
deleted file mode 100644
index ca9b673..0000000
--- a/lib/bridgedb/filters.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_filters ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson <nickm at torproject.org>
-# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-import logging
-
-from ipaddr import IPv4Address
-from ipaddr import IPv6Address
-
-from bridgedb.parse.addr import isIPv
-
-
-_cache = {}
-
-
-def bySubring(hmac, assigned, total):
- """Create a filter function which filters for only the bridges which fall
- into the same **assigned** subhashring (based on the results of an **hmac**
- function).
-
- :type hmac: callable
- :param hmac: An HMAC function, i.e. as returned from
- :func:`bridgedb.crypto.getHMACFunc`.
- :param int assigned: The subring number that we wish to draw bridges from.
- For example, if a user is assigned to subring 2of3 based on their IP
- address, then this function should only return bridges which would
- also be assigned to subring 2of3.
- :param int total: The total number of subrings.
- :rtype: callable
- :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
- """
- logging.debug(("Creating a filter for assigning bridges to subhashring "
- "%s-of-%s...") % (assigned, total))
-
- name = "-".join([str(hmac("")[:8]).encode('hex'),
- str(assigned), "of", str(total)])
- try:
- return _cache[name]
- except KeyError:
- def _bySubring(bridge):
- position = int(hmac(bridge.identity)[:8], 16)
- which = (position % total) + 1
- return True if which == assigned else False
- # The `description` attribute must contain an `=`, or else
- # dumpAssignments() will not work correctly.
- setattr(_bySubring, "description", "ring=%d" % assigned)
- _bySubring.__name__ = ("bySubring%sof%s" % (assigned, total))
- _bySubring.name = name
- _cache[name] = _bySubring
- return _bySubring
-
-def byFilters(filtres):
- """Returns a filter which filters by multiple **filtres**.
-
- :type filtres: list
- :param filtres: A list (or other iterable) of callables which some
- :class:`~bridgedb.bridges.Bridge`s should be filtered according to.
- :rtype: callable
- :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
- """
- name = []
- for filtre in filtres:
- name.extend(filtre.name.split(" "))
- name = " ".join(set(name))
-
- try:
- return _cache[name]
- except KeyError:
- def _byFilters(bridge):
- results = [f(bridge) for f in filtres]
- if False in results:
- return False
- return True
- setattr(_byFilters, "description",
- " ".join([getattr(f, "description", "") for f in filtres]))
- _byFilters.name = name
- _cache[name] = _byFilters
- return _byFilters
-
-def byIPv(ipVersion=None):
- """Return ``True`` if at least one of the **bridge**'s addresses has the
- specified **ipVersion**.
-
- :param int ipVersion: Either ``4`` or ``6``.
- """
- if not ipVersion in (4, 6):
- ipVersion = 4
-
- name = "ipv%d" % ipVersion
- try:
- return _cache[name]
- except KeyError:
- def _byIPv(bridge):
- if isIPv(ipVersion, bridge.address):
- return True
- else:
- for address, port, version in bridge.allVanillaAddresses:
- if version == ipVersion or isIPv(ipVersion, address):
- return True
- return False
- setattr(_byIPv, "description", "ip=%d" % ipVersion)
- _byIPv.__name__ = "byIPv%d()" % ipVersion
- _byIPv.name = name
- _cache[name] = _byIPv
- return _byIPv
-
-byIPv4 = byIPv(4)
-byIPv6 = byIPv(6)
-
-def byTransport(methodname=None, ipVersion=None):
- """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
-
- The returned filter function should be called on a
- :class:`~bridgedb.bridges.Bridge`. It returns ``True`` if the ``Bridge``
- has a :class:`~bridgedb.bridges.PluggableTransport` such that:
-
- 1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
- **methodname**, and
-
- 2. The :data:`~bridgedb.bridges.PluggableTransport.address`` version
- matches the **ipVersion**.
-
- :param str methodname: A Pluggable Transport
- :data:`~bridge.bridges.PluggableTransport.methodname`.
- :param int ipVersion: Either ``4`` or ``6``. The IP version that the
- ``Bridge``'s ``PluggableTransport``
- :data:`~bridgedb.bridges.PluggableTransport.address`` should have.
- :rtype: callable
- :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
- """
- if not ipVersion in (4, 6):
- ipVersion = 4
- if not methodname:
- return byIPv(ipVersion)
-
- methodname = methodname.lower()
- name = "transport-%s ipv%d" % (methodname, ipVersion)
-
- try:
- return _cache[name]
- except KeyError:
- def _byTransport(bridge):
- for transport in bridge.transports:
- if transport.methodname == methodname:
- if transport.address.version == ipVersion:
- return True
- return False
- setattr(_byTransport, "description", "transport=%s" % methodname)
- _byTransport.__name__ = "byTransport(%s,%s)" % (methodname, ipVersion)
- _byTransport.name = name
- _cache[name] = _byTransport
- return _byTransport
-
-def byNotBlockedIn(countryCode=None, methodname=None, ipVersion=4):
- """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
-
- If a Pluggable Transport **methodname** was not specified, the returned
- filter function returns ``True`` if any of the ``Bridge``'s addresses or
- :class:`~bridgedb.bridges.PluggableTransport` addresses aren't blocked in
- **countryCode**. See :meth:`~bridgedb.bridges.Bridge.isBlockedIn`.
-
- Otherwise, if a Pluggable Transport **methodname** was specified, it
- returns ``True`` if the ``Bridge`` has a
- :class:`~bridgedb.bridges.PluggableTransport` such that:
-
- 1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
- **methodname**,
-
- 2. The :data:`~bridgedb.bridges.PluggableTransport.address.version``
- equals the **ipVersion**, and isn't known to be blocked in
- **countryCode**.
-
- :type countryCode: str or ``None``
- :param countryCode: A two-letter country code which the filtered
- :class:`PluggableTransport`s should not be blocked in.
- :param str methodname: A Pluggable Transport
- :data:`~bridge.bridges.PluggableTransport.methodname`.
- :param int ipVersion: Either ``4`` or ``6``. The IP version that the
- ``Bridge``'s addresses should have.
- :rtype: callable
- :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
- """
- if not ipVersion in (4, 6):
- ipVersion = 4
- if not countryCode:
- return byTransport(methodname, ipVersion)
-
- methodname = methodname.lower() if methodname else methodname
- countryCode = countryCode.lower()
-
- name = []
- if methodname:
- name.append("transport-%s" % methodname)
- name.append("ipv%d" % ipVersion)
- name.append("not-blocked-in-%s" % countryCode)
- name = " ".join(name)
-
- try:
- return _cache[name]
- except KeyError:
- def _byNotBlockedIn(bridge):
- if not methodname:
- return not bridge.isBlockedIn(countryCode)
- elif methodname == "vanilla":
- if bridge.address.version == ipVersion:
- if not bridge.addressIsBlockedIn(countryCode,
- bridge.address,
- bridge.orPort):
- return True
- else:
- # Since bridge.transportIsBlockedIn() will return True if the
- # bridge has that type of transport AND that transport is
- # blocked, we can "fail fast" here by doing this faster check
- # before iterating over all the transports testing for the
- # other conditions.
- if bridge.transportIsBlockedIn(countryCode, methodname):
- return False
- else:
- for transport in bridge.transports:
- if transport.methodname == methodname:
- if transport.address.version == ipVersion:
- return True
- return False
- setattr(_byNotBlockedIn, "description", "unblocked=%s" % countryCode)
- _byNotBlockedIn.__name__ = ("byTransportNotBlockedIn(%s,%s,%s)"
- % (methodname, countryCode, ipVersion))
- _byNotBlockedIn.name = name
- _cache[name] = _byNotBlockedIn
- return _byNotBlockedIn
diff --git a/lib/bridgedb/geo.py b/lib/bridgedb/geo.py
deleted file mode 100644
index c986fa1..0000000
--- a/lib/bridgedb/geo.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""
-Boilerplate setup for GeoIP. GeoIP allows us to look up the country code
-associated with an IP address. This is a "pure" python version which interacts
-with the Maxmind GeoIP API (version 1). It requires, in Debian, the libgeoip-dev
-and geoip-database packages.
-"""
-
-import logging
-from os.path import isfile
-
-from ipaddr import IPv4Address, IPv6Address
-
-# IPv4 database
-GEOIP_DBFILE = '/usr/share/GeoIP/GeoIP.dat'
-# IPv6 database
-GEOIPv6_DBFILE = '/usr/share/GeoIP/GeoIPv6.dat'
-try:
- # Make sure we have the database before trying to import the module:
- if not (isfile(GEOIP_DBFILE) and isfile(GEOIPv6_DBFILE)): # pragma: no cover
- raise EnvironmentError("Could not find %r. On Debian-based systems, "
- "please install the geoip-database package."
- % GEOIP_DBFILE)
-
- import pygeoip
- geoip = pygeoip.GeoIP(GEOIP_DBFILE, flags=pygeoip.MEMORY_CACHE)
- geoipv6 = pygeoip.GeoIP(GEOIPv6_DBFILE, flags=pygeoip.MEMORY_CACHE)
- logging.info("GeoIP databases loaded")
-except Exception as err: # pragma: no cover
- logging.warn("Error while loading geoip module: %r" % err)
- geoip = None
- geoipv6 = None
-
-
-def getCountryCode(ip):
- """Return the two-letter country code of a given IP address.
-
- :type ip: :class:`ipaddr.IPAddress`
- :param ip: An IPv4 OR IPv6 address.
- :rtype: ``None`` or str
-
- :returns: If the GeoIP databases are loaded, and the **ip** lookup is
- successful, then this returns a two-letter country code. Otherwise,
- this returns ``None``.
- """
- addr = None
- version = None
- try:
- addr = ip.compressed
- version = ip.version
- except AttributeError as err:
- logging.warn("Wrong type passed to getCountryCode: %s" % str(err))
- return None
-
- # GeoIP has two databases: one for IPv4 addresses, and one for IPv6
- # addresses. This will ensure we use the correct one.
- db = None
- # First, make sure we loaded GeoIP properly.
- if None in (geoip, geoipv6):
- logging.warn("GeoIP databases aren't loaded; can't look up country code")
- return None
- else:
- if version == 4:
- db = geoip
- else:
- db = geoipv6
-
- # Look up the country code of the address.
- countryCode = db.country_code_by_addr(addr)
- if countryCode:
- logging.debug("Looked up country code: %s" % countryCode)
- return countryCode
- else:
- logging.debug("Country code was not detected. IP: %s" % addr)
- return None
diff --git a/lib/bridgedb/https/__init__.py b/lib/bridgedb/https/__init__.py
deleted file mode 100644
index f1210ec..0000000
--- a/lib/bridgedb/https/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Servers for BridgeDB's HTTPS bridge distributor."""
diff --git a/lib/bridgedb/https/distributor.py b/lib/bridgedb/https/distributor.py
deleted file mode 100644
index f8cf09d..0000000
--- a/lib/bridgedb/https/distributor.py
+++ /dev/null
@@ -1,328 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_https_distributor -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson
-# Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""A Distributor that hands out bridges through a web interface."""
-
-import ipaddr
-import logging
-
-import bridgedb.Storage
-
-from bridgedb import proxy
-from bridgedb.Bridges import BridgeRing
-from bridgedb.Bridges import FilteredBridgeSplitter
-from bridgedb.crypto import getHMAC
-from bridgedb.crypto import getHMACFunc
-from bridgedb.distribute import Distributor
-from bridgedb.filters import byIPv4
-from bridgedb.filters import byIPv6
-from bridgedb.filters import byFilters
-from bridgedb.filters import bySubring
-
-
-class HTTPSDistributor(Distributor):
- """A Distributor that hands out bridges based on the IP address of an
- incoming request and the current time period.
-
- :type proxies: :class:`~bridgedb.proxies.ProxySet`
- :ivar proxies: All known proxies, which we treat differently. See
- :param:`proxies`.
- :type hashring: :class:`bridgedb.Bridges.FilteredBridgeSplitter`
- :ivar hashring: A hashring that assigns bridges to subrings with fixed
- proportions. Used to assign bridges into the subrings of this
- distributor.
- """
-
- def __init__(self, totalSubrings, key, proxies=None, answerParameters=None):
- """Create a Distributor that decides which bridges to distribute based
- upon the client's IP address and the current time.
-
- :param int totalSubrings: The number of subhashrings to group clients
- into. Note that if ``PROXY_LIST_FILES`` is set in bridgedb.conf,
- then the actual number of clusters is one higher than
- ``totalSubrings``, because the set of all known open proxies is
- given its own subhashring.
- :param bytes key: The master HMAC key for this distributor. All added
- bridges are HMACed with this key in order to place them into the
- hashrings.
- :type proxies: :class:`~bridgedb.proxy.ProxySet`
- :param proxies: A :class:`bridgedb.proxy.ProxySet` containing known
- Tor Exit relays and other known proxies. These will constitute
- the extra cluster, and any client requesting bridges from one of
- these **proxies** will be distributed bridges from a separate
- subhashring that is specific to Tor/proxy users.
- :type answerParameters: :class:`bridgedb.Bridges.BridgeRingParameters`
- :param answerParameters: A mechanism for ensuring that the set of
- bridges that this distributor answers a client with fit certain
- parameters, i.e. that an answer has "at least two obfsproxy
- bridges" or "at least one bridge on port 443", etc.
- """
- super(HTTPSDistributor, self).__init__(key)
- self.totalSubrings = totalSubrings
- self.answerParameters = answerParameters
-
- if proxies:
- logging.info("Added known proxies to HTTPS distributor...")
- self.proxies = proxies
- self.totalSubrings += 1
- self.proxySubring = self.totalSubrings
- else:
- logging.warn("No known proxies were added to HTTPS distributor!")
- self.proxies = proxy.ProxySet()
- self.proxySubring = 0
-
- self.ringCacheSize = self.totalSubrings * 3
-
- key2 = getHMAC(key, "Assign-Bridges-To-Rings")
- key3 = getHMAC(key, "Order-Areas-In-Rings")
- key4 = getHMAC(key, "Assign-Areas-To-Rings")
-
- self._clientToPositionHMAC = getHMACFunc(key3, hex=False)
- self._subnetToSubringHMAC = getHMACFunc(key4, hex=True)
- self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize)
- self.name = 'HTTPS'
- logging.debug("Added %s to %s distributor." %
- (self.hashring.__class__.__name__, self.name))
-
- def bridgesPerResponse(self, hashring=None):
- return super(HTTPSDistributor, self).bridgesPerResponse(hashring)
-
- @classmethod
- def getSubnet(cls, ip, usingProxy=False, proxySubnets=4):
- """Map all clients whose **ip**s are within the same subnet to the same
- arbitrary string.
-
- .. hint:: For non-proxy IP addresses, any two IPv4 addresses within
- the same ``/16`` subnet, or any two IPv6 addresses in the same
- ``/32`` subnet, will get the same string.
-
- Subnets for this distributor are grouped into the number of rings
- specified by the ``N_IP_CLUSTERS`` configuration option, such that
- Alice (with the address ``1.2.3.4`` and Bob (with the address
- ``1.2.178.234``) are placed within the same cluster, but Carol (with
- address ``1.3.11.33``) *might* end up in a different cluster.
-
- >>> from bridgedb.https.distributor import HTTPSDistributor
- >>> HTTPSDistributor.getSubnet('1.2.3.4')
- '1.2.0.0/16'
- >>> HTTPSDistributor.getSubnet('1.2.211.154')
- '1.2.0.0/16'
- >>> HTTPSDistributor.getSubnet('2001:f::bc1:b13:2808')
- '2001:f::/32'
- >>> HTTPSDistributor.getSubnet('2a00:c98:2030:a020:2::42')
- '2a00:c98::/32'
-
- :param str ip: A string representing an IPv4 or IPv6 address.
- :param bool usingProxy: Set to ``True`` if the client was using one of
- the known :data:`proxies`.
- :param int proxySubnets: Place Tor/proxy users into this number of
- "subnet" groups. This means that no matter how many different Tor
- Exits or proxies a client uses, the most they can ever get is
- **proxySubnets** different sets of bridge lines (per interval).
- This parameter only has any effect when **usingProxy** is ``True``.
- :rtype: str
- :returns: The appropriately sized CIDR subnet representation of the **ip**.
- """
- if not usingProxy:
- # We aren't using bridgedb.parse.addr.isIPAddress(ip,
- # compressed=False) here because adding the string "False" into
- # the map would land any and all clients whose IP address appeared
- # to be invalid at the same position in a hashring.
- address = ipaddr.IPAddress(ip)
- if address.version == 6:
- truncated = ':'.join(address.exploded.split(':')[:2])
- subnet = str(ipaddr.IPv6Network(truncated + "::/32"))
- else:
- truncated = '.'.join(address.exploded.split('.')[:2])
- subnet = str(ipaddr.IPv4Network(truncated + '.0.0/16'))
- else:
- group = (int(ipaddr.IPAddress(ip)) % 4) + 1
- subnet = "proxy-group-%d" % group
-
- logging.debug("Client IP was within area: %s" % subnet)
- return subnet
-
- def mapSubnetToSubring(self, subnet, usingProxy=False):
- """Determine the correct subhashring for a client, based upon the
- **subnet**.
-
- :param str subnet: The subnet which contains the client's IP. See
- :staticmethod:`getSubnet`.
- :param bool usingProxy: Set to ``True`` if the client was using one of
- the known :data:`proxies`.
- """
- # If the client wasn't using a proxy, select the client's subring
- # based upon the client's subnet (modulo the total subrings):
- if not usingProxy:
- mod = self.totalSubrings
- # If there is a proxy subring, don't count it for the modulus:
- if self.proxySubring:
- mod -= 1
- return (int(self._subnetToSubringHMAC(subnet)[:8], 16) % mod) + 1
- else:
- return self.proxySubring
-
- def mapClientToHashringPosition(self, interval, subnet):
- """Map the client to a position on a (sub)hashring, based upon the
- **interval** which the client's request occurred within, as well as
- the **subnet** of the client's IP address.
-
- .. note:: For an explanation of how **subnet** is determined, see
- :staticmethod:`getSubnet`.
-
- :param str interval: The interval which this client's request for
- bridges took place within.
- :param str subnet: A string representing the subnet containing the
- client's IP address.
- :rtype: int
- :returns: The results of keyed HMAC, which should determine the
- client's position in a (sub)hashring of bridges (and thus
- determine which bridges they receive).
- """
- position = "<%s>%s" % (interval, subnet)
- mapping = self._clientToPositionHMAC(position)
- return mapping
-
- def prepopulateRings(self):
- """Prepopulate this distributor's hashrings and subhashrings with
- bridges.
-
- The hashring structure for this distributor is influenced by the
- ``N_IP_CLUSTERS`` configuration option, as well as the number of
- ``PROXY_LIST_FILES``.
-
- Essentially, :data:`totalSubrings` is set to the specified
- ``N_IP_CLUSTERS``. All of the ``PROXY_LIST_FILES``, plus the list of
- Tor Exit relays (downloaded into memory with :script:`get-tor-exits`),
- are stored in :data:`proxies`, and the latter is added as an
- additional cluster (such that :data:`totalSubrings` becomes
- ``N_IP_CLUSTERS + 1``). The number of subhashrings which this
- :class:`Distributor` has active in its hashring is then
- :data:`totalSubrings`, where the last cluster is reserved for all
- :data:`proxies`.
-
- As an example, if BridgeDB was configured with ``N_IP_CLUSTERS=4`` and
- ``PROXY_LIST_FILES=["open-socks-proxies.txt"]``, then the total number
- of subhashrings is fiveâââfour for the "clusters", and one for the
- :data:`proxies`. Thus, the resulting hashring-subhashring structure
- would look like:
-
- +------------------+---------------------------------------------------+--------------
- | | Directly connecting users | Tor / known |
- | | | proxy users |
- +------------------+------------+------------+------------+------------+-------------+
- | Clusters | Cluster-1 | Cluster-2 | Cluster-3 | Cluster-4 | Cluster-5 |
- +==================+============+============+============+============+=============+
- | Subhashrings | | | | | |
- | (total, assigned)| (5,1) | (5,2) | (5,3) | (5,4) | (5,5) |
- +------------------+------------+------------+------------+------------+-------------+
- | Filtered | (5,1)-IPv4 | (5,2)-IPv4 | (5,3)-IPv4 | (5,4)-IPv4 | (5,5)-IPv4 |
- | Subhashrings | | | | | |
- | bBy requested +------------+------------+------------+------------+-------------+
- | bridge type) | (5,1)-IPv6 | (5,2)-IPv6 | (5,3)-IPv6 | (5,4)-IPv6 | (5,5)-IPv6 |
- | | | | | | |
- +------------------+------------+------------+------------+------------+-------------+
-
- The "filtered subhashrings" are essentially filtered copies of their
- respective subhashring, such that they only contain bridges which
- support IPv4 or IPv6, respectively. Additionally, the contents of
- ``(5,1)-IPv4`` and ``(5,1)-IPv6`` sets are *not* disjoint.
-
- Thus, in this example, we end up with **10 total subhashrings**.
- """
- logging.info("Prepopulating %s distributor hashrings..." % self.name)
-
- for filterFn in [byIPv4, byIPv6]:
- for subring in range(1, self.totalSubrings + 1):
- filters = self._buildHashringFilters([filterFn,], subring)
- key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
- ring = BridgeRing(key1, self.answerParameters)
- # For consistency with previous implementation of this method,
- # only set the "name" for "clusters" which are for this
- # distributor's proxies:
- if subring == self.proxySubring:
- ring.setName('{0} Proxy Ring'.format(self.name))
- self.hashring.addRing(ring, filters, byFilters(filters),
- populate_from=self.hashring.bridges)
-
- def insert(self, bridge):
- """Assign a bridge to this distributor."""
- self.hashring.insert(bridge)
-
- def _buildHashringFilters(self, previousFilters, subring):
- f = bySubring(self.hashring.hmac, subring, self.totalSubrings)
- previousFilters.append(f)
- return frozenset(previousFilters)
-
- def getBridges(self, bridgeRequest, interval):
- """Return a list of bridges to give to a user.
-
- :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest`
- :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase`
- with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client`
- attribute set to a string containing the client's IP address.
- :param str interval: The time period when we got this request. This
- can be any string, so long as it changes with every period.
- :rtype: list
- :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
- the response. See
- :meth:`bridgedb.https.server.WebResourceBridges.getBridgeRequestAnswer`
- for an example of how this is used.
- """
- logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
-
- if not len(self.hashring):
- logging.warn("Bailing! Hashring has zero bridges!")
- return []
-
- usingProxy = False
-
- # First, check if the client's IP is one of the known :data:`proxies`:
- if bridgeRequest.client in self.proxies:
- # The tag is a tag applied to a proxy IP address when it is added
- # to the bridgedb.proxy.ProxySet. For Tor Exit relays, the default
- # is 'exit_relay'. For other proxies loaded from the
- # PROXY_LIST_FILES config option, the default tag is the full
- # filename that the IP address originally came from.
- usingProxy = True
- tag = self.proxies.getTag(bridgeRequest.client)
- logging.info("Client was from known proxy (tag: %s): %s" %
- (tag, bridgeRequest.client))
-
- subnet = self.getSubnet(bridgeRequest.client, usingProxy)
- subring = self.mapSubnetToSubring(subnet, usingProxy)
- position = self.mapClientToHashringPosition(interval, subnet)
- filters = self._buildHashringFilters(bridgeRequest.filters, subring)
-
- logging.debug("Client request within time interval: %s" % interval)
- logging.debug("Assigned client to subhashring %d/%d" % (subring, self.totalSubrings))
- logging.debug("Assigned client to subhashring position: %s" % position.encode('hex'))
- logging.debug("Total bridges: %d" % len(self.hashring))
- logging.debug("Bridge filters: %s" % ' '.join([x.func_name for x in filters]))
-
- # Check wheth we have a cached copy of the hashring:
- if filters in self.hashring.filterRings.keys():
- logging.debug("Cache hit %s" % filters)
- _, ring = self.hashring.filterRings[filters]
- # Otherwise, construct a new hashring and populate it:
- else:
- logging.debug("Cache miss %s" % filters)
- key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
- ring = BridgeRing(key1, self.answerParameters)
- self.hashring.addRing(ring, filters, byFilters(filters),
- populate_from=self.hashring.bridges)
-
- # Determine the appropriate number of bridges to give to the client:
- returnNum = self.bridgesPerResponse(ring)
- answer = ring.getBridges(position, returnNum)
-
- return answer
diff --git a/lib/bridgedb/https/request.py b/lib/bridgedb/https/request.py
deleted file mode 100644
index b106a13..0000000
--- a/lib/bridgedb/https/request.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8; test-case-name: bridgedb.test.test_https_request; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft <isis at torproject.org> 0xA3ADB67A2CDB8B35
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""
-.. py:module:: bridgedb.https.request
- :synopsis: Classes for parsing and storing information about requests for
- bridges which are sent to the HTTPS distributor.
-
-bridgedb.https.request
-======================
-
-Classes for parsing and storing information about requests for bridges
-which are sent to the HTTPS distributor.
-
-::
-
- bridgedb.https.request
- |
- |_ HTTPSBridgeRequest - A request for bridges which was received through
- the HTTPS distributor.
-..
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import ipaddr
-import logging
-import re
-
-from bridgedb import bridgerequest
-from bridgedb import geo
-from bridgedb.parse import addr
-
-
-#: A regular expression for matching the Pluggable Transport methodname in
-#: HTTP GET request parameters.
-TRANSPORT_REGEXP = "[_a-zA-Z][_a-zA-Z0-9]*"
-TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP)
-
-UNBLOCKED_REGEXP = "[a-zA-Z]{2}"
-UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP)
-
-
-class HTTPSBridgeRequest(bridgerequest.BridgeRequestBase):
- """We received a request for bridges through the HTTPS distributor."""
-
- def __init__(self, addClientCountryCode=True):
- """Process a new bridge request received through the
- :class:`~bridgedb.https.distributor.HTTPSDistributor`.
-
- :param bool addClientCountryCode: If ``True``, then calling
- :meth:`withoutBlockInCountry` will attempt to add the client's own
- country code, geolocated from her IP, to the ``notBlockedIn``
- countries list.
- """
- super(HTTPSBridgeRequest, self).__init__()
- self.addClientCountryCode = addClientCountryCode
-
- def withIPversion(self, parameters):
- """Determine if the request **parameters** were for bridges with IPv6
- addresses or not.
-
- .. note:: If there is an ``ipv6=`` parameter with anything non-zero
- after it, then we assume the client wanted IPv6 bridges.
-
- :param parameters: The :api:`twisted.web.http.Request.args`.
- """
- if parameters.get("ipv6", False):
- logging.info("HTTPS request for bridges with IPv6 addresses.")
- self.withIPv6()
-
- def withoutBlockInCountry(self, request):
- """Determine which countries the bridges for this **request** should
- not be blocked in.
-
- .. note:: Currently, a **request** for unblocked bridges is recognized
- if it contains an HTTP GET parameter ``unblocked=`` whose value is
- a comma-separater list of two-letter country codes. Any
- two-letter country code found in the
- :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET
- parameter will be added to the :data:`notBlockedIn` list.
-
- If :data:`addClientCountryCode` is ``True``, the the client's own
- geolocated country code will be added to the to the
- :data`notBlockedIn` list.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object containing the HTTP method, full
- URI, and any URL/POST arguments and headers present.
- """
- countryCodes = request.args.get("unblocked", list())
-
- for countryCode in countryCodes:
- try:
- country = UNBLOCKED_PATTERN.match(countryCode).group()
- except (TypeError, AttributeError):
- pass
- else:
- if country:
- self.notBlockedIn.append(country.lower())
- logging.info("HTTPS request for bridges not blocked in: %r"
- % country)
-
- if self.addClientCountryCode:
- # Look up the country code of the input IP, and request bridges
- # not blocked in that country.
- if addr.isIPAddress(self.client):
- country = geo.getCountryCode(ipaddr.IPAddress(self.client))
- if country:
- self.notBlockedIn.append(country.lower())
- logging.info(
- ("HTTPS client's bridges also shouldn't be blocked "
- "in their GeoIP country code: %s") % country)
-
- def withPluggableTransportType(self, parameters):
- """This request included a specific Pluggable Transport identifier.
-
- Add any Pluggable Transport methodname found in the HTTP GET
- **parameters** to the list of ``transports``. Currently, a request for
- a transport is recognized if the request contains the
- ``'transport='`` parameter.
-
- :param parameters: The :api:`twisted.web.http.Request.args`.
- """
- for methodname in parameters.get("transport", list()):
- try:
- transport = TRANSPORT_PATTERN.match(methodname).group()
- except (TypeError, AttributeError):
- pass
- else:
- if transport:
- self.transports.append(transport)
- logging.info("HTTPS request for transport type: %r"
- % transport)
diff --git a/lib/bridgedb/https/server.py b/lib/bridgedb/https/server.py
deleted file mode 100644
index 2106dbf..0000000
--- a/lib/bridgedb/https/server.py
+++ /dev/null
@@ -1,905 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_https_server -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see included AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-
-"""
-.. py:module:: bridgedb.https.server
- :synopsis: Servers which interface with clients and distribute bridges
- over HTTP(S).
-
-bridgedb.https.server
-=====================
-
-Servers which interface with clients and distribute bridges over HTTP(S).
-"""
-
-import base64
-import gettext
-import logging
-import random
-import re
-import time
-import os
-
-from functools import partial
-
-from ipaddr import IPv4Address
-
-import mako.exceptions
-from mako.template import Template
-from mako.lookup import TemplateLookup
-
-from twisted.internet import reactor
-from twisted.internet.error import CannotListenError
-from twisted.web import resource
-from twisted.web import static
-from twisted.web.server import NOT_DONE_YET
-from twisted.web.server import Site
-from twisted.web.util import redirectTo
-
-from bridgedb import captcha
-from bridgedb import crypto
-from bridgedb import strings
-from bridgedb import translations
-from bridgedb import txrecaptcha
-from bridgedb.https.request import HTTPSBridgeRequest
-from bridgedb.parse import headers
-from bridgedb.parse.addr import isIPAddress
-from bridgedb.qrcodes import generateQR
-from bridgedb.safelog import logSafely
-from bridgedb.schedule import Unscheduled
-from bridgedb.schedule import ScheduledInterval
-from bridgedb.util import replaceControlChars
-
-
-TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates')
-rtl_langs = ('ar', 'he', 'fa', 'gu_IN', 'ku')
-
-# Setting `filesystem_checks` to False is recommended for production servers,
-# due to potential speed increases. This means that the atimes of the Mako
-# template files aren't rechecked every time the template is requested
-# (otherwise, if they are checked, and the atime is newer, the template is
-# recompiled). `collection_size` sets the number of compiled templates which
-# are cached before the least recently used ones are removed. See:
-# http://docs.makotemplates.org/en/latest/usage.html#using-templatelookup
-lookup = TemplateLookup(directories=[TEMPLATE_DIR],
- output_encoding='utf-8',
- filesystem_checks=False,
- collection_size=500)
-logging.debug("Set template root to %s" % TEMPLATE_DIR)
-
-
-def getClientIP(request, useForwardedHeader=False):
- """Get the client's IP address from the :header:`X-Forwarded-For`
- header, or from the :api:`request <twisted.web.server.Request>`.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for a
- :api:`twisted.web.resource.Resource`.
- :param bool useForwardedHeader: If ``True``, attempt to get the client's
- IP address from the :header:`X-Forwarded-For` header.
- :rtype: None or str
- :returns: The client's IP address, if it was obtainable.
- """
- ip = None
-
- if useForwardedHeader:
- header = request.getHeader("X-Forwarded-For")
- if header:
- ip = header.split(",")[-1].strip()
- if not isIPAddress(ip):
- logging.warn("Got weird X-Forwarded-For value %r" % header)
- ip = None
- else:
- ip = request.getClientIP()
-
- return ip
-
-def replaceErrorPage(error, template_name=None):
- """Create a general error page for displaying in place of tracebacks.
-
- Log the error to BridgeDB's logger, and then display a very plain "Sorry!
- Something went wrong!" page to the client.
-
- :type error: :exc:`Exception`
- :param error: Any exeption which has occurred while attempting to retrieve
- a template, render a page, or retrieve a resource.
- :param str template_name: A string describing which template/page/resource
- was being used when the exception occurred,
- i.e. ``'index.html'``.
- :returns: A string containing HTML to serve to the client (rather than
- serving a traceback).
- """
- logging.error("Error while attempting to render %s: %s"
- % (template_name or 'template',
- mako.exceptions.text_error_template().render()))
-
- # TRANSLATORS: Please DO NOT translate the following words and/or phrases in
- # any string (regardless of capitalization and/or punctuation):
- #
- # "BridgeDB"
- # "pluggable transport"
- # "pluggable transports"
- # "obfs2"
- # "obfs3"
- # "scramblesuit"
- # "fteproxy"
- # "Tor"
- # "Tor Browser"
- #
- errmsg = _("Sorry! Something went wrong with your request.")
- rendered = """<html>
- <head>
- <link href="/assets/bootstrap.min.css" rel="stylesheet">
- <link href="/assets/custom.css" rel="stylesheet">
- </head>
- <body>
- <p>{0}</p>
- </body>
- </html>""".format(errmsg)
-
- return rendered
-
-
-class TranslatedTemplateResource(resource.Resource):
- """A generalised resource which uses gettext translations and Mako
- templates.
- """
- isLeaf = True
-
- def __init__(self, template=None):
- """Create a new :api:`~twisted.web.resource.Resource` for a
- Mako-templated webpage.
- """
- gettext.install("bridgedb", unicode=True)
- resource.Resource.__init__(self)
- self.template = template
-
- def render_GET(self, request):
- rtl = False
- try:
- langs = translations.getLocaleFromHTTPRequest(request)
- rtl = translations.usingRTLLang(langs)
- template = lookup.get_template(self.template)
- rendered = template.render(strings, rtl=rtl, lang=langs[0])
- except Exception as err: # pragma: no cover
- rendered = replaceErrorPage(err)
- request.setHeader("Content-Type", "text/html; charset=utf-8")
- return rendered
-
- render_POST = render_GET
-
-
-class IndexResource(TranslatedTemplateResource):
- """The parent resource of all other documents hosted by the webserver."""
-
- def __init__(self):
- """Create a :api:`twisted.web.resource.Resource` for the index page."""
- TranslatedTemplateResource.__init__(self, 'index.html')
-
-
-class OptionsResource(TranslatedTemplateResource):
- """A resource with additional options which a client may use to specify the
- which bridge types should be returned by :class:`BridgesResource`.
- """
- def __init__(self):
- """Create a :api:`twisted.web.resource.Resource` for the options page."""
- TranslatedTemplateResource.__init__(self, 'options.html')
-
-
-class HowtoResource(TranslatedTemplateResource):
- """A resource which explains how to use bridges."""
-
- def __init__(self):
- """Create a :api:`twisted.web.resource.Resource` for the HowTo page."""
- TranslatedTemplateResource.__init__(self, 'howto.html')
-
-
-class CaptchaProtectedResource(resource.Resource):
- """A general resource protected by some form of CAPTCHA."""
-
- isLeaf = True
-
- def __init__(self, publicKey=None, secretKey=None,
- useForwardedHeader=False, protectedResource=None):
- resource.Resource.__init__(self)
- self.publicKey = publicKey
- self.secretKey = secretKey
- self.useForwardedHeader = useForwardedHeader
- self.resource = protectedResource
-
- def getClientIP(self, request):
- """Get the client's IP address from the :header:`X-Forwarded-For`
- header, or from the :api:`request <twisted.web.server.Request>`.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for a
- :api:`twisted.web.resource.Resource`.
- :rtype: None or str
- :returns: The client's IP address, if it was obtainable.
- """
- return getClientIP(request, self.useForwardedHeader)
-
- def getCaptchaImage(self, request=None):
- """Get a CAPTCHA image.
-
- :returns: A 2-tuple of ``(image, challenge)``, where ``image`` is a
- binary, JPEG-encoded image, and ``challenge`` is a unique
- string. If unable to retrieve a CAPTCHA, returns a tuple
- containing two empty strings.
- """
- return ('', '')
-
- def extractClientSolution(self, request):
- """Extract the client's CAPTCHA solution from a POST request.
-
- This is used after receiving a POST request from a client (which
- should contain their solution to the CAPTCHA), to extract the solution
- and challenge strings.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for 'bridges.html'.
- :returns: A redirect for a request for a new CAPTCHA if there was a
- problem. Otherwise, returns a 2-tuple of strings, the first is the
- client's CAPTCHA solution from the text input area, and the second
- is the challenge string.
- """
- try:
- challenge = request.args['captcha_challenge_field'][0]
- response = request.args['captcha_response_field'][0]
- except Exception: # pragma: no cover
- return redirectTo(request.URLPath(), request)
- return (challenge, response)
-
- def checkSolution(self, request):
- """Override this method to check a client's CAPTCHA solution.
-
- :rtype: bool
- :returns: ``True`` if the client correctly solved the CAPTCHA;
- ``False`` otherwise.
- """
- return False
-
- def render_GET(self, request):
- """Retrieve a ReCaptcha from the API server and serve it to the client.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for a page which should be
- protected by a CAPTCHA.
- :rtype: str
- :returns: A rendered HTML page containing a ReCaptcha challenge image
- for the client to solve.
- """
- rtl = False
- image, challenge = self.getCaptchaImage(request)
-
- try:
- langs = translations.getLocaleFromHTTPRequest(request)
- rtl = translations.usingRTLLang(langs)
- # TODO: this does not work for versions of IE < 8.0
- imgstr = 'data:image/jpeg;base64,%s' % base64.b64encode(image)
- template = lookup.get_template('captcha.html')
- rendered = template.render(strings,
- rtl=rtl,
- lang=langs[0],
- imgstr=imgstr,
- challenge_field=challenge)
- except Exception as err:
- rendered = replaceErrorPage(err, 'captcha.html')
-
- request.setHeader("Content-Type", "text/html; charset=utf-8")
- return rendered
-
- def render_POST(self, request):
- """Process a client's CAPTCHA solution.
-
- If the client's CAPTCHA solution is valid (according to
- :meth:`checkSolution`), process and serve their original
- request. Otherwise, redirect them back to a new CAPTCHA page.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object, including POST arguments which
- should include two key/value pairs: one key being
- ``'captcha_challenge_field'``, and the other,
- ``'captcha_response_field'``. These POST arguments should be
- obtained from :meth:`render_GET`.
- :rtype: str
- :returns: A rendered HTML page containing a ReCaptcha challenge image
- for the client to solve.
- """
- request.setHeader("Content-Type", "text/html; charset=utf-8")
-
- if self.checkSolution(request) is True:
- try:
- rendered = self.resource.render(request)
- except Exception as err:
- rendered = replaceErrorPage(err)
- return rendered
-
- logging.debug("Client failed a CAPTCHA; returning redirect to %s"
- % request.uri)
- return redirectTo(request.uri, request)
-
-
-class GimpCaptchaProtectedResource(CaptchaProtectedResource):
- """A web resource which uses a local cache of CAPTCHAs, generated with
- gimp-captcha_, to protect another resource.
-
- .. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
- """
-
- def __init__(self, hmacKey=None, captchaDir='', **kwargs):
- """Protect a resource via this one, using a local CAPTCHA cache.
-
- :param str secretkey: A PKCS#1 OAEP-padded, private RSA key, used for
- verifying the client's solution to the CAPTCHA. See
- :func:`bridgedb.crypto.getRSAKey` and the
- ``GIMP_CAPTCHA_RSA_KEYFILE`` config setting.
- :param str publickey: A PKCS#1 OAEP-padded, public RSA key, used for
- creating the ``captcha_challenge_field`` string to give to a
- client.
- :param bytes hmacKey: The master HMAC key, used for validating CAPTCHA
- challenge strings in :meth:`captcha.GimpCaptcha.check`. The file
- where this key is stored can be set via the
- ``GIMP_CAPTCHA_HMAC_KEYFILE`` option in the config file.
- :param str captchaDir: The directory where the cached CAPTCHA images
- are stored. See the ``GIMP_CAPTCHA_DIR`` config setting.
- :param bool useForwardedHeader: If ``True``, obtain the client's IP
- address from the ``X-Forwarded-For`` HTTP header.
- :type protectedResource: :api:`twisted.web.resource.Resource`
- :param protectedResource: The resource to serve if the client
- successfully passes the CAPTCHA challenge.
- """
- CaptchaProtectedResource.__init__(self, **kwargs)
- self.hmacKey = hmacKey
- self.captchaDir = captchaDir
-
- def checkSolution(self, request):
- """Process a solved CAPTCHA via :meth:`bridgedb.captcha.GimpCaptcha.check`.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object, including POST arguments which
- should include two key/value pairs: one key being
- ``'captcha_challenge_field'``, and the other,
- ``'captcha_response_field'``. These POST arguments should be
- obtained from :meth:`render_GET`.
- :rtupe: bool
- :returns: True, if the CAPTCHA solution was valid; False otherwise.
- """
- valid = False
- challenge, solution = self.extractClientSolution(request)
- clientIP = self.getClientIP(request)
- clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
-
- try:
- valid = captcha.GimpCaptcha.check(challenge, solution,
- self.secretKey, clientHMACKey)
- except captcha.CaptchaExpired as error:
- logging.warn(error)
- valid = False
-
- logging.debug("%sorrect captcha from %r: %r."
- % ("C" if valid else "Inc", clientIP, solution))
- return valid
-
- def getCaptchaImage(self, request):
- """Get a random CAPTCHA image from our **captchaDir**.
-
- Creates a :class:`~bridgedb.captcha.GimpCaptcha`, and calls its
- :meth:`~bridgedb.captcha.GimpCaptcha.get` method to return a random
- CAPTCHA and challenge string.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A client's initial request for some other resource
- which is protected by this one (i.e. protected by a CAPTCHA).
- :returns: A 2-tuple of ``(image, challenge)``, where::
- - ``image`` is a string holding a binary, JPEG-encoded image.
- - ``challenge`` is a unique string associated with the request.
- """
- # Create a new HMAC key, specific to requests from this client:
- clientIP = self.getClientIP(request)
- clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
- capt = captcha.GimpCaptcha(self.publicKey, self.secretKey,
- clientHMACKey, self.captchaDir)
- try:
- capt.get()
- except captcha.GimpCaptchaError as error:
- logging.error(error)
- except Exception as error: # pragma: no cover
- logging.error("Unhandled error while retrieving Gimp captcha!")
- logging.exception(error)
-
- return (capt.image, capt.challenge)
-
- def render_GET(self, request):
- """Get a random CAPTCHA from our local cache directory and serve it to
- the client.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for a page which should be
- protected by a CAPTCHA.
- :rtype: str
- :returns: A rendered HTML page containing a ReCaptcha challenge image
- for the client to solve.
- """
- return CaptchaProtectedResource.render_GET(self, request)
-
- def render_POST(self, request):
- """Process a client's CAPTCHA solution.
-
- If the client's CAPTCHA solution is valid (according to
- :meth:`checkSolution`), process and serve their original
- request. Otherwise, redirect them back to a new CAPTCHA page.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object, including POST arguments which
- should include two key/value pairs: one key being
- ``'captcha_challenge_field'``, and the other,
- ``'captcha_response_field'``. These POST arguments should be
- obtained from :meth:`render_GET`.
- :rtype: str
- :returns: A rendered HTML page containing a ReCaptcha challenge image
- for the client to solve.
- """
- return CaptchaProtectedResource.render_POST(self, request)
-
-
-class ReCaptchaProtectedResource(CaptchaProtectedResource):
- """A web resource which uses the reCaptcha_ service.
-
- .. _reCaptcha: http://www.google.com/recaptcha
- """
-
- def __init__(self, remoteIP=None, **kwargs):
- CaptchaProtectedResource.__init__(self, **kwargs)
- self.remoteIP = remoteIP
-
- def _renderDeferred(self, checkedRequest):
- """Render this resource asynchronously.
-
- :type checkedRequest: tuple
- :param checkedRequest: A tuple of ``(bool, request)``, as returned
- from :meth:`checkSolution`.
- """
- try:
- valid, request = checkedRequest
- except Exception as err:
- logging.error("Error in _renderDeferred(): %s" % err)
- return
-
- logging.debug("Attemping to render %svalid request %r"
- % ('' if valid else 'in', request))
- if valid is True:
- try:
- rendered = self.resource.render(request)
- except Exception as err: # pragma: no cover
- rendered = replaceErrorPage(err)
- else:
- logging.info("Client failed a CAPTCHA; redirecting to %s"
- % request.uri)
- rendered = redirectTo(request.uri, request)
-
- try:
- request.write(rendered)
- request.finish()
- except Exception as err: # pragma: no cover
- logging.exception(err)
-
- return request
-
- def getCaptchaImage(self, request):
- """Get a CAPTCHA image from the remote reCaptcha server.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A client's initial request for some other resource
- which is protected by this one (i.e. protected by a CAPTCHA).
- :returns: A 2-tuple of ``(image, challenge)``, where::
- - ``image`` is a string holding a binary, JPEG-encoded image.
- - ``challenge`` is a unique string associated with the request.
- """
- capt = captcha.ReCaptcha(self.publicKey, self.secretKey)
-
- try:
- capt.get()
- except Exception as error:
- logging.fatal("Connection to Recaptcha server failed: %s" % error)
-
- if capt.image is None:
- logging.warn("No CAPTCHA image received from ReCaptcha server!")
-
- return (capt.image, capt.challenge)
-
- def getRemoteIP(self):
- """Mask the client's real IP address with a faked one.
-
- The fake client IP address is sent to the reCaptcha server, and it is
- either the public IP address of bridges.torproject.org (if the config
- option ``RECAPTCHA_REMOTE_IP`` is configured), or a random IP.
-
- :rtype: str
- :returns: A fake IP address to report to the reCaptcha API server.
- """
- if self.remoteIP:
- remoteIP = self.remoteIP
- else:
- # generate a random IP for the captcha submission
- remoteIP = IPv4Address(random.randint(0, 2**32-1)).compressed
-
- return remoteIP
-
- def checkSolution(self, request):
- """Process a solved CAPTCHA by sending it to the ReCaptcha server.
-
- The client's IP address is not sent to the ReCaptcha server; instead,
- a completely random IP is generated and sent instead.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object, including POST arguments which
- should include two key/value pairs: one key being
- ``'captcha_challenge_field'``, and the other,
- ``'captcha_response_field'``. These POST arguments should be
- obtained from :meth:`render_GET`.
- :rtupe: :api:`twisted.internet.defer.Deferred`
- :returns: A deferred which will callback with a tuple in the following
- form:
- (:type:`bool`, :api:`twisted.web.server.Request`)
- If the CAPTCHA solution was valid, a tuple will contain::
- (True, Request)
- Otherwise, it will contain::
- (False, Request)
- """
- challenge, response = self.extractClientSolution(request)
- clientIP = self.getClientIP(request)
- remoteIP = self.getRemoteIP()
-
- logging.debug("Captcha from %r. Parameters: %r"
- % (clientIP, request.args))
-
- def checkResponse(solution, request):
- """Check the :class:`txrecaptcha.RecaptchaResponse`.
-
- :type solution: :class:`txrecaptcha.RecaptchaResponse`.
- :param solution: The client's CAPTCHA solution, after it has been
- submitted to the reCaptcha API server.
- """
- # This valid CAPTCHA result from this function cannot be reliably
- # unittested, because it's callbacked to from the deferred
- # returned by ``txrecaptcha.submit``, the latter of which would
- # require networking (as well as automated CAPTCHA
- # breaking). Hence, the 'no cover' pragma.
- if solution.is_valid: # pragma: no cover
- logging.info("Valid CAPTCHA solution from %r." % clientIP)
- return (True, request)
- else:
- logging.info("Invalid CAPTCHA solution from %r: %r"
- % (clientIP, solution.error_code))
- return (False, request)
-
- d = txrecaptcha.submit(challenge, response, self.secretKey,
- remoteIP).addCallback(checkResponse, request)
- return d
-
- def render_GET(self, request):
- """Retrieve a ReCaptcha from the API server and serve it to the client.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for 'bridges.html'.
- :rtype: str
- :returns: A rendered HTML page containing a ReCaptcha challenge image
- for the client to solve.
- """
- return CaptchaProtectedResource.render_GET(self, request)
-
- def render_POST(self, request):
- """Process a client's CAPTCHA solution.
-
- If the client's CAPTCHA solution is valid (according to
- :meth:`checkSolution`), process and serve their original
- request. Otherwise, redirect them back to a new CAPTCHA page.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object, including POST arguments which
- should include two key/value pairs: one key being
- ``'captcha_challenge_field'``, and the other,
- ``'captcha_response_field'``. These POST arguments should be
- obtained from :meth:`render_GET`.
- :returns: :api:`twisted.web.server.NOT_DONE_YET`, in order to handle
- the ``Deferred`` returned from :meth:`checkSolution`. Eventually,
- when the ``Deferred`` request is done being processed,
- :meth:`_renderDeferred` will handle rendering and displaying the
- HTML to the client.
- """
- d = self.checkSolution(request)
- d.addCallback(self._renderDeferred)
- return NOT_DONE_YET
-
-
-class BridgesResource(resource.Resource):
- """This resource displays bridge lines in response to a request."""
-
- isLeaf = True
-
- def __init__(self, distributor, schedule, N=1, useForwardedHeader=False,
- includeFingerprints=True):
- """Create a new resource for displaying bridges to a client.
-
- :type distributor: :class:`HTTPSDistributor`
- :param distributor: The mechanism to retrieve bridges for this
- distributor.
- :type schedule: :class:`~bridgedb.schedule.ScheduledInterval`
- :param schedule: The time period used to tweak the bridge selection
- procedure.
- :param int N: The number of bridges to hand out per query.
- :param bool useForwardedHeader: Whether or not we should use the the
- X-Forwarded-For header instead of the source IP address.
- :param bool includeFingerprints: Do we include the bridge's
- fingerprint in the response?
- """
- gettext.install("bridgedb", unicode=True)
- resource.Resource.__init__(self)
- self.distributor = distributor
- self.schedule = schedule
- self.nBridgesToGive = N
- self.useForwardedHeader = useForwardedHeader
- self.includeFingerprints = includeFingerprints
-
- def render(self, request):
- """Render a response for a client HTTP request.
-
- Presently, this method merely wraps :meth:`getBridgeRequestAnswer` to
- catch any unhandled exceptions which occur (otherwise the server will
- display the traceback to the client). If an unhandled exception *does*
- occur, the client will be served the default "No bridges currently
- available" HTML response page.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object containing the HTTP method, full
- URI, and any URL/POST arguments and headers present.
- :rtype: str
- :returns: A plaintext or HTML response to serve.
- """
- try:
- response = self.getBridgeRequestAnswer(request)
- except Exception as err:
- logging.exception(err)
- response = self.renderAnswer(request)
-
- return response
-
- def getClientIP(self, request):
- """Get the client's IP address from the :header:`X-Forwarded-For`
- header, or from the :api:`request <twisted.web.server.Request>`.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object for a
- :api:`twisted.web.resource.Resource`.
- :rtype: None or str
- :returns: The client's IP address, if it was obtainable.
- """
- return getClientIP(request, self.useForwardedHeader)
-
- def getBridgeRequestAnswer(self, request):
- """Respond to a client HTTP request for bridges.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object containing the HTTP method, full
- URI, and any URL/POST arguments and headers present.
- :rtype: str
- :returns: A plaintext or HTML response to serve.
- """
- bridgeLines = None
- interval = self.schedule.intervalStart(time.time())
- ip = self.getClientIP(request)
-
- logging.info("Replying to web request from %s. Parameters were %r"
- % (ip, request.args))
-
- if ip:
- bridgeRequest = HTTPSBridgeRequest()
- bridgeRequest.client = ip
- bridgeRequest.isValid(True)
- bridgeRequest.withIPversion(request.args)
- bridgeRequest.withPluggableTransportType(request.args)
- bridgeRequest.withoutBlockInCountry(request)
- bridgeRequest.generateFilters()
-
- bridges = self.distributor.getBridges(bridgeRequest, interval)
- bridgeLines = [replaceControlChars(bridge.getBridgeLine(
- bridgeRequest, self.includeFingerprints)) for bridge in bridges]
-
- return self.renderAnswer(request, bridgeLines)
-
- def getResponseFormat(self, request):
- """Determine the requested format for the response.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object containing the HTTP method, full
- URI, and any URL/POST arguments and headers present.
- :rtype: ``None`` or str
- :returns: The argument of the first occurence of the ``format=`` HTTP
- GET parameter, if any were present. (The only one which currently
- has any effect is ``format=plain``, see note in
- :meth:`renderAnswer`.) Otherwise, returns ``None``.
- """
- format = request.args.get("format", None)
- if format and len(format):
- format = format[0] # Choose the first arg
- return format
-
- def renderAnswer(self, request, bridgeLines=None):
- """Generate a response for a client which includes **bridgesLines**.
-
- .. note: The generated response can be plain or HTML. A plain response
- looks like::
-
- voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF
- voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567
-
- That is, there is no HTML, what you see is what you get, and what
- you get is suitable for pasting directly into Tor Launcher (or
- into a torrc, if you prepend ``"Bridge "`` to each line). The
- plain format can be requested from BridgeDB's web service by
- adding an ``&format=plain`` HTTP GET parameter to the URL. Also
- note that you won't get a QRCode, usage instructions, error
- messages, or any other fanciness if you use the plain format.
-
- :type request: :api:`twisted.web.http.Request`
- :param request: A ``Request`` object containing the HTTP method, full
- URI, and any URL/POST arguments and headers present.
- :type bridgeLines: list or None
- :param bridgeLines: A list of strings used to configure a Tor client
- to use a bridge. If ``None``, then the returned page will instead
- explain that there were no bridges of the type they requested,
- with instructions on how to proceed.
- :rtype: str
- :returns: A plaintext or HTML response to serve.
- """
- rtl = False
- format = self.getResponseFormat(request)
-
- if format == 'plain':
- request.setHeader("Content-Type", "text/plain")
- try:
- rendered = bytes('\n'.join(bridgeLines))
- except Exception as err:
- rendered = replaceErrorPage(err)
- else:
- request.setHeader("Content-Type", "text/html; charset=utf-8")
- qrcode = None
- qrjpeg = generateQR(bridgeLines)
-
- if qrjpeg:
- qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)
- try:
- langs = translations.getLocaleFromHTTPRequest(request)
- rtl = translations.usingRTLLang(langs)
- template = lookup.get_template('bridges.html')
- rendered = template.render(strings,
- rtl=rtl,
- lang=langs[0],
- answer=bridgeLines,
- qrcode=qrcode)
- except Exception as err:
- rendered = replaceErrorPage(err)
-
- return rendered
-
-
-def addWebServer(config, distributor):
- """Set up a web server for HTTP(S)-based bridge distribution.
-
- :type config: :class:`bridgedb.persistent.Conf`
- :param config: A configuration object from
- :mod:`bridgedb.Main`. Currently, we use these options::
- HTTP_UNENCRYPTED_PORT
- HTTP_UNENCRYPTED_BIND_IP
- HTTP_USE_IP_FROM_FORWARDED_HEADER
- HTTPS_N_BRIDGES_PER_ANSWER
- HTTPS_INCLUDE_FINGERPRINTS
- HTTPS_KEY_FILE
- HTTPS_CERT_FILE
- HTTPS_PORT
- HTTPS_BIND_IP
- HTTPS_USE_IP_FROM_FORWARDED_HEADER
- HTTPS_ROTATION_PERIOD
- RECAPTCHA_ENABLED
- RECAPTCHA_PUB_KEY
- RECAPTCHA_SEC_KEY
- RECAPTCHA_REMOTEIP
- GIMP_CAPTCHA_ENABLED
- GIMP_CAPTCHA_DIR
- GIMP_CAPTCHA_HMAC_KEYFILE
- GIMP_CAPTCHA_RSA_KEYFILE
- :type distributor: :class:`bridgedb.https.distributor.HTTPSDistributor`
- :param distributor: A bridge distributor.
- :raises SystemExit: if the servers cannot be started.
- :rtype: :api:`twisted.web.server.Site`
- :returns: A webserver.
- """
- captcha = None
- fwdHeaders = config.HTTP_USE_IP_FROM_FORWARDED_HEADER
- numBridges = config.HTTPS_N_BRIDGES_PER_ANSWER
- fprInclude = config.HTTPS_INCLUDE_FINGERPRINTS
-
- logging.info("Starting web servers...")
-
- index = IndexResource()
- options = OptionsResource()
- howto = HowtoResource()
- robots = static.File(os.path.join(TEMPLATE_DIR, 'robots.txt'))
- assets = static.File(os.path.join(TEMPLATE_DIR, 'assets/'))
- keys = static.Data(bytes(strings.BRIDGEDB_OPENPGP_KEY), 'text/plain')
-
- root = resource.Resource()
- root.putChild('', index)
- root.putChild('robots.txt', robots)
- root.putChild('keys', keys)
- root.putChild('assets', assets)
- root.putChild('options', options)
- root.putChild('howto', howto)
-
- if config.RECAPTCHA_ENABLED:
- publicKey = config.RECAPTCHA_PUB_KEY
- secretKey = config.RECAPTCHA_SEC_KEY
- captcha = partial(ReCaptchaProtectedResource,
- remoteIP=config.RECAPTCHA_REMOTEIP)
- elif config.GIMP_CAPTCHA_ENABLED:
- # Get the master HMAC secret key for CAPTCHA challenges, and then
- # create a new HMAC key from it for use on the server.
- captchaKey = crypto.getKey(config.GIMP_CAPTCHA_HMAC_KEYFILE)
- hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
- # Load or create our encryption keys:
- secretKey, publicKey = crypto.getRSAKey(config.GIMP_CAPTCHA_RSA_KEYFILE)
- captcha = partial(GimpCaptchaProtectedResource,
- hmacKey=hmacKey,
- captchaDir=config.GIMP_CAPTCHA_DIR)
-
- if config.HTTPS_ROTATION_PERIOD:
- count, period = config.HTTPS_ROTATION_PERIOD.split()
- sched = ScheduledInterval(count, period)
- else:
- sched = Unscheduled()
-
- bridges = BridgesResource(distributor, sched, numBridges, fwdHeaders,
- includeFingerprints=fprInclude)
- if captcha:
- # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
- protected = captcha(publicKey=publicKey,
- secretKey=secretKey,
- useForwardedHeader=fwdHeaders,
- protectedResource=bridges)
- root.putChild('bridges', protected)
- logging.info("Protecting resources with %s." % captcha.func.__name__)
- else:
- root.putChild('bridges', bridges)
-
- site = Site(root)
- site.displayTracebacks = False
-
- if config.HTTP_UNENCRYPTED_PORT: # pragma: no cover
- ip = config.HTTP_UNENCRYPTED_BIND_IP or ""
- port = config.HTTP_UNENCRYPTED_PORT or 80
- try:
- reactor.listenTCP(port, site, interface=ip)
- except CannotListenError as error:
- raise SystemExit(error)
- logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))
-
- if config.HTTPS_PORT: # pragma: no cover
- ip = config.HTTPS_BIND_IP or ""
- port = config.HTTPS_PORT or 443
- try:
- from twisted.internet.ssl import DefaultOpenSSLContextFactory
- factory = DefaultOpenSSLContextFactory(config.HTTPS_KEY_FILE,
- config.HTTPS_CERT_FILE)
- reactor.listenSSL(port, site, factory, interface=ip)
- except CannotListenError as error:
- raise SystemExit(error)
- logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))
-
- return site
diff --git a/lib/bridgedb/https/templates/assets/css/bootstrap.min.css b/lib/bridgedb/https/templates/assets/css/bootstrap.min.css
deleted file mode 100644
index 56eb594..0000000
--- a/lib/bridgedb/https/templates/assets/css/bootstrap.min.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/* @import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");*/
-/* Bootswatch v3.1.1+1
- * Homepage: http://bootswatch.com
- * Copyright 2012-2014 Thomas Park
- * Licensed under MIT
- * Based on Bootstrap */
-/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:
inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padd
ing:0}@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.table td,.table th{background-color:#fff !important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-
box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Lato","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.42857143;color:#2c3e50;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#18bc9c;text-decoration:none}a:hover,a:focus{color:#18bc9c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #ecf0f1;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-t
op:21px;margin-bottom:21px;border:0;border-top:1px solid #ecf0f1}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Lato","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#b4bcc2}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-
size:75%}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#b4bcc2}.text-primary{color:#2c3e50}a.text-primary:hover{color:#1a242f}.text-success{color:#ffffff}a.text-success:hover{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover{color:#e6e6e6}.bg-primary{color:#fff;background-color:#2c3e50}a.bg-primary:hover{background-color:#1a242f}.bg-success{background-color:#18bc9c}a.bg-success:hover{background-color:#128f76}.bg-info{background-color:#3498db}a.bg-
info:hover{background-color:#217dbb}.bg-warning{background-color:#f39c12}a.bg-warning:hover{background-color:#c87f0a}.bg-danger{background-color:#e74c3c}a.bg-danger:hover{background-color:#d62c1a}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid transparent}ul,ol{margin-top:0;margin-bottom:10.5px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:21px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #b4bcc2}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10.5px 21p
x;margin:0 0 21px;font-size:18.75px;border-left:5px solid #ecf0f1}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#b4bcc2}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #ecf0f1;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}
address{margin-bottom:21px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#7b8a8b;background-color:#ecf0f1;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170p
x}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs
-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0%}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0%}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left
:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:
83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0%}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0%}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-l
eft:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md
-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0%}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0%}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200p
x){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0%}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666
667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0%}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.4285714
3;vertical-align:top;border-top:1px solid #ecf0f1}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ecf0f1}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ecf0f1}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ecf0f1}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ecf0f1}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-ch
ild(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#ecf0f1}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#ecf0f1}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#dde4e6}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr
>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#18bc9c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#15a589}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#3498db}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#258cd1}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,
.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#f39c12}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#e08e0b}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#e74c3c}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#e43725}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:h
idden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ecf0f1;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-respon
sive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#2c3e50;border:0;border-bottom:1px solid transparent}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:a
uto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:11px;font-size:15px;line-height:1.42857143;color:#2c3e50}.form-control{display:block;width:100%;height:43px;padding:10px 15px;font-size:15px;line-height:1.42857143;color:#2c3e50;background-color:#ffffff;background-image:none;border:1px solid #dce4ec;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#2c3e50;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(44,62,80,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(44,62,80,0.6)}.form-control::-moz-placeholder{color:#acb6c0;opacity:1}.form-control:-ms-input-pla
ceholder{color:#acb6c0}.form-control::-webkit-input-placeholder{color:#acb6c0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#ecf0f1;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"]{line-height:43px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:21px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="r
adio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:33px;padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}select.input-sm{height:33px;line-height:33px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:64px;padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}select.input-lg{height:64px;line-height:64px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:53.75px}.has-feedback .form-control-feedback{position:absolute;top:26px;right:0;display:block;width:43px;height:43px;line-height:43px;text-align:center}.has-success .help-block,.has-
success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#18bc9c}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0
1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#f39c12}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#e74c3c}.has-error .form-control-feedback{color:#ffffff}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#597ea2}@media (min-width:768px){.form-inl
ine .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:11px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:32px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:11px}@media (min-width:768px){.form-horizontal .c
ontrol-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:10px 15px;font-size:15px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#ffffff;background-co
lor:#95a5a6;border-color:#95a5a6}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#ffffff;background-color:#7f9293;border-color:#74898a}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#95a5a6;border-color:#95a5a6}.btn-default .badge{color:#95a5a6;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}.btn-primary:hover,.btn-primary:focus,.btn-primary:ac
tive,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#ffffff;background-color:#1e2a36;border-color:#161f29}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#2c3e50;border-color:#2c3e50}.btn-primary .badge{color:#2c3e50;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#18bc9c;border-color:#18bc9c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#ffffff;background-c
olor:#13987e;border-color:#11866f}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#18bc9c;border-color:#18bc9c}.btn-success .badge{color:#18bc9c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#3498db;border-color:#3498db}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#ffffff;background-color:#2383c4;border-color:#2077b2}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{back
ground-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#3498db;border-color:#3498db}.btn-info .badge{color:#3498db;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f39c12;border-color:#f39c12}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#ffffff;background-color:#d2850b;border-color:#be780a}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warnin
g[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f39c12;border-color:#f39c12}.btn-warning .badge{color:#f39c12;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#e74c3c;border-color:#e74c3c}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#ffffff;background-color:#df2e1b;border-color:#cd2a19}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabl
ed]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#e74c3c;border-color:#e74c3c}.btn-danger .badge{color:#e74c3c;background-color:#ffffff}.btn-link{color:#18bc9c;font-weight:normal;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#18bc9c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#b4bcc2;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}
.btn-sm,.btn-group-sm>.btn{padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:13px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../
fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-lis
t:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{c
ontent:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:
"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.
glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:befo
re{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"
\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphico
n-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{conten
t:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.
caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;background-color:#ffffff;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#7b8a8b;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#ffffff;background-color:#2c3e50}
.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#2c3e50}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#b4bcc2}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:13px;line-height:1.42857143;color:#b4bcc2}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .drop
down-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:none}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-togg
le){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0
.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertica
l>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-g
roup-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:64px;padding:18px 27px;font-size:19px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:64px;line-height:64px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:33px;padding:6px 9px;font-size:13px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:33px;line-height:33px}textarea.input-group-sm>.form-control,textarea.input
-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:10px 15px;font-size:15px;font-weight:normal;line-height:1;color:#2c3e50;text-align:center;background-color:#ecf0f1;border:1px solid #dce4ec;border-radius:4px}.input-group-addon.input-sm{padding:6px 9px;font-size:13px;border-radius:3px}.input-group-addon.input-lg{padding:18px 27px;font-size:19px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[ty
pe="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margi
n-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ecf0f1}.nav>li.disabled>a{color:#b4bcc2}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#b4bcc2;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ecf0f1;border-color:#18bc9c}.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ecf0f1}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{ma
rgin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#ecf0f1 #ecf0f1 #ecf0f1}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#2c3e50;background-color:#ffffff;border:1px solid #ecf0f1;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ecf0f1}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ecf0f1;border-radius:4px 4px
0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#2c3e50}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ecf0f1}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:
1px solid #ecf0f1;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:60px;margin-bottom:21px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overf
low-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:19.5px 15px;font-size:19px;line-height:21px;height:60px}.navbar-brand:hover,.navbar-bran
d:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:13px;margin-bottom:13px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:none}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:9.75px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line
-height:21px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:19.5px;padding-bottom:19.5px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8.5px;margin-bottom:8.5px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.
navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8.5px;margin-bottom:8.5px}.navbar-btn.btn-sm{margin-top:13.5px;margin-bottom:13.5px}.navbar-btn.btn-xs{margin-top:19px;margin
-bottom:19px}.navbar-text{margin-top:19.5px;margin-bottom:19.5px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#2c3e50;border-color:transparent}.navbar-default .navbar-brand{color:#ffffff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-text{color:#777777}.navbar-default .navbar-nav>li>a{color:#ffffff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#1a242f}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .na
vbar-toggle{border-color:#1a242f}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#1a242f}.navbar-default .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#1a242f;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#18bc9c;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#1a242f}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-
default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#ffffff}.navbar-default .navbar-link:hover{color:#18bc9c}.navbar-inverse{background-color:#18bc9c;border-color:transparent}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-text{color:#ffffff}.navbar-inverse .navbar-nav>li>a{color:#ffffff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#15a589}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{co
lor:#cccccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#128f76}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#128f76}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#149c82}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#15a589;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#2c3e50;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.nav
bar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#15a589}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#ffffff}.navbar-inverse .navbar-link:hover{color:#2c3e50}.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#ecf0f1;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#cccccc}.breadcrumb>.active{color:#95a5a6}.pagination{display:inline-block;padding-left:0;margin:21px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:10px 15px;line-height:1.42857143;text-decoration:none;color:#ffffff;ba
ckground-color:#18bc9c;border:1px solid transparent;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#ffffff;background-color:#0f7864;border-color:transparent}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#ffffff;background-color:#0f7864;border-color:transparent;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#ecf0f1;background-color:#3be6c4;border-color:transparent;cursor:not-allowed}.p
agination-lg>li>a,.pagination-lg>li>span{padding:18px 27px;font-size:19px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:6px 9px;font-size:13px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#18bc9c;border:1px solid transparent;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#0f7864}.pager .next>a,.pager .next>span{float
:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#ffffff;background-color:#18bc9c;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#95a5a6}.label-default[href]:hover,.label-default[href]:focus{background-color:#798d8f}.label-primary{background-color:#2c3e50}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#1a242f}.label-success{background-color:#18bc9c}.label-success[href]:hover,.label-success[href]:focus{background-color:#128f76}.label-info{background-color:#3498db}.label-info[href]:hover,.label-info[href]:focus{backgroun
d-color:#217dbb}.label-warning{background-color:#f39c12}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c87f0a}.label-danger{background-color:#e74c3c}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#d62c1a}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:13px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#2c3e50;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#2c3e50;background-color:#ffffff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#ecf0f1}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200}.container .jumb
otron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:67.5px}}.thumbnail{display:block;padding:4px;margin-bottom:21px;line-height:1.42857143;background-color:#ffffff;border:1px solid #ecf0f1;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#18bc9c}.thumbnail .caption{padding:9px;color:#2c3e50}.alert{padding:15px;margin-bottom:21px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.al
ert-success{background-color:#18bc9c;border-color:#18bc9c;color:#ffffff}.alert-success hr{border-top-color:#15a589}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#3498db;border-color:#3498db;color:#ffffff}.alert-info hr{border-top-color:#258cd1}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f39c12;border-color:#f39c12;color:#ffffff}.alert-warning hr{border-top-color:#e08e0b}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#e74c3c;border-color:#e74c3c;color:#ffffff}.alert-danger hr{border-top-color:#e43725}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:21px;margin-bottom:21px;background-color:#ecf0f1;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0
,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:13px;line-height:21px;color:#ffffff;text-align:center;background-color:#2c3e50;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#18bc9c}.progress-striped .progress-bar-success{background-image:-webkit-lin
ear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#3498db}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 2
5%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#e74c3c}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-le
ft:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #ecf0f1}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555555}a.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#ecf0f1}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-it
em-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#8aa4be}.list-group-item-success{color:#ffffff;background-color:#18bc9c}a.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#ffffff;background-color:#15a589}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#3498db}a.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#ffffff;background-color:#258cd1}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:
#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#f39c12}a.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#ffffff;background-color:#e08e0b}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#e74c3c}a.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#ffffff;background-color:#e43725}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-t
ext{margin-bottom:0;line-height:1.3}.panel{margin-bottom:21px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:17px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#ecf0f1;border-top:1px solid #ecf0f1;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right
-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child
>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-ch
ild,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.tabl
e:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ecf0f1}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>the
ad>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:
0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-botto
m:21px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ecf0f1}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ecf0f1}.panel-default{border-color:#ecf0f1}.panel-default>.panel-heading{color:#2c3e50;background-color:#ecf0f1;border-color:#ecf0f1}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ecf0f1}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ecf0f1}.panel-primary{border-color:#2c3e50}.panel-primary>.panel-heading{color:#ffffff;background-color:#2c3e50;border-color:#2c3e50}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#2c3e50}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#2c3e50}.panel-success{border-color:#18bc9
c}.panel-success>.panel-heading{color:#ffffff;background-color:#18bc9c;border-color:#18bc9c}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#18bc9c}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#18bc9c}.panel-info{border-color:#3498db}.panel-info>.panel-heading{color:#ffffff;background-color:#3498db;border-color:#3498db}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#3498db}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#3498db}.panel-warning{border-color:#f39c12}.panel-warning>.panel-heading{color:#ffffff;background-color:#f39c12;border-color:#f39c12}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#f39c12}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#f39c12}.panel-danger{border-color:#e74c3c}.panel-danger>.panel-heading{color:#ffffff;background-color:#e74c3c;border-color:#e74c3c}.panel-danger>.panel-heading+.panel-c
ollapse .panel-body{border-top-color:#e74c3c}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#e74c3c}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#ecf0f1;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:22.5px;font-weight:bold;line-height:1;color:#000000;text-shadow:none;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrol
ling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);transform:translate(0, 0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:none}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px so
lid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:13px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin
-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:rgba(0,0,0,0.9);border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:rgba(0,0,0,0.9)}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:rgba(0,0,0,0.9)}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:rgba(0,0,0,0.9)}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.tooltip.bottom-left .too
ltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:rgba(0,0,0,0.9)}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:15px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-styl
e:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.
popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.
left{background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.5) 0), color-stop(rgba(0,0,0,0.0001) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.0001) 0), color-stop(rgba(0,0,0,0.5) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:none;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyp
hicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index
:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body
:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{disp
lay:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !imp
ortant}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0}.navbar-default .badge{background-color:#fff;color:#2c3e50}.navbar-inverse .badge{background-color:#fff;color:#18bc9c}.navbar-brand{padding:18.5px 15px 20.5px}.btn:active{-webkit-box-shadow:none;box-shadow:none}.btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.text-primary,.text-primary:hover{color:#2c3e50}.text-success,.text-success:hover{color:#18bc9c}.text-danger,.text-danger:hover{color:#e74c3c}.text-warning,.text-warning:hover{color:#f39c12}.text-info,.text-info:hover{color:#3498db}table a,.table a{text-decoration:underline}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{co
lor:#fff}table .success a,.table .success a,table .warning a,.table .warning a,table .danger a,.table .danger a,table .info a,.table .info a{color:#fff}table>thead>tr>th,.table>thead>tr>th,table>tbody>tr>th,.table>tbody>tr>th,table>tfoot>tr>th,.table>tfoot>tr>th,table>thead>tr>td,.table>thead>tr>td,table>tbody>tr>td,.table>tbody>tr>td,table>tfoot>tr>td,.table>tfoot>tr>td{border:none}table-bordered>thead>tr>th,.table-bordered>thead>tr>th,table-bordered>tbody>tr>th,.table-bordered>tbody>tr>th,table-bordered>tfoot>tr>th,.table-bordered>tfoot>tr>th,table-bordered>thead>tr>td,.table-bordered>thead>tr>td,table-bordered>tbody>tr>td,.table-bordered>tbody>tr>td,table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ecf0f1}.form-control,input{border-width:2px;-webkit-box-shadow:none;box-shadow:none}.form-control:focus,input:focus{-webkit-box-shadow:none;box-shadow:none}.has-warning .help-block,.has-warning .control-label{color:#f39c12}.has-warning .form-control,.has-warning
.form-control:focus{border:2px solid #f39c12}.has-error .help-block,.has-error .control-label{color:#e74c3c}.has-error .form-control,.has-error .form-control:focus{border:2px solid #e74c3c}.has-success .help-block,.has-success .control-label{color:#18bc9c}.has-success .form-control,.has-success .form-control:focus{border:2px solid #18bc9c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.pager a,.pager a:hover{color:#fff}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{background-color:#3be6c4}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert .close{color:#fff;text-decoration:none;opacity:0.4}.alert .close:hover,.alert .close:focus{color:#fff;opacity:1}.progress{height:10px;-webkit-box-shadow:none;box-shadow:none}.progress .progress-bar{font-size:10px;line-height:10px}.well{-webkit-box-shadow:none;box-shadow:none}
\ No newline at end of file
diff --git a/lib/bridgedb/https/templates/assets/css/custom.css b/lib/bridgedb/https/templates/assets/css/custom.css
deleted file mode 100644
index f0bde6f..0000000
--- a/lib/bridgedb/https/templates/assets/css/custom.css
+++ /dev/null
@@ -1,158 +0,0 @@
-body {
- padding-top: 20px;
- padding-bottom: 40px;
-}
-
-/* Custom container */
-.container-narrow {
- margin: 0 auto;
- max-width: 675px;
-}
-.container-narrow > hr {
- margin: 30px 0;
-}
-
-/* Main marketing message and sign up button */
-.jumbotron {
- margin: 60px 0;
- text-align: center;
-}
-.jumbotron h1 {
- font-size: 72px;
- line-height: 1;
-}
-.jumbotron .btn {
- font-size: 21px;
- padding: 14px 24px;
-}
-
-/* Supporting marketing content */
-.marketing {
- margin: 60px 0;
-}
-.marketing p + h4 {
- margin-top: 28px;
-}
-
-.captcha {
- margin: auto;
- display: block;
- width: 250px;
-}
-
-.captcha .btn {
- margin: auto;
- width: 100px;
- display: block;
-}
-
-.fixed-size-btn {
- margin: 10px;
- width: 200px;
-}
-
-.main-steps {
- margin: 50px;
-}
-
-
-.main-btns {
- margin: auto;
- width: 450px;
- display: block;
-}
-
-.step{
-border: 1px solid #ccc;
-border: 1px solid rgba(0, 0, 0, 0.2);
--webkit-border-radius: 6px;
--moz-border-radius: 6px;
-border-radius: 6px;
--webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
--moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
--webkit-background-clip: padding-box;
--moz-background-clip: padding;
-background-clip: padding-box;
-padding: 10px 20px 0px;
-margin-bottom: 10px;
-}
-
-.step-title {
-color: #808080;
-font-size: 18px;
-font-weight: 100;
-}
-
-.step-text {
- font-size: 18px;
-line-height: 30px;
-margin-top: 2px;
-}
-.lead_right {
- margin-bottom: 20px;
- font-size: 21px;
- font-weight: 200;
- line-height: 30px
-}
-[class*="bdb_span"] {
- min-height: 1px;
- margin: 0 20px 16px 20px;
-}
-.bdb_span7 {
- width: 560px
-}
-
-div.bridge-lines {
- padding: 20px;
- margin: 0px 0px 20px;
- min-height: 20px;
- font-family: Monaco,Menlo,Consolas,"Courier New",monospace;
- font-size: 95%;
- line-height: 175%;
- color: rgb(44, 62, 80);
- word-break: break-all;
- word-wrap: normal;
- white-space: nowrap;
- overflow-x: auto;
- z-index: 1000;
- background-color: rgb(236, 240, 241);
- border: 0px solid transparent;
- border-radius: 6px 6px 6px 6px;
- border-color: #2C3E50;
- box-shadow: none;
- box-sizing: border-box;
- -moz-box-sizing: border-box;
- cursor: copy;
-}
-
-div.bridge-lines.-webkit-scrollbar {
- width: 9px;
- height: 9px;
-}
-div.bridge-lines.-webkit-scrollbar-button.start.decrement {
- display: block;
- height: 0;
- background-color: transparent;
-}
-div.bridge-lines.-webkit-scrollbar-button.end.increment {
- display: block;
- height: 0;
- background-color: transparent;
-}
-div.bridge-lines.-webkit-scrollbar-track-piece {
- background-color: #FAFAFA;
- -webkit-border-radius: 0;
- -webkit-border-bottom-right-radius: 6px;
- -webkit-border-bottom-left-radius: 6px;
-}
-div.bridge-lines.-webkit-scrollbar-thumb.vertical {
- height: 50px;
- background-color: #999;
- -webkit-border-radius: 6px;
-}
-div.bridge-lines.-webkit-scrollbar-thumb.horizontal{
- width: 50px;
- background-color: #999;
- -webkit-border-radius: 6px;
-}
diff --git a/lib/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css b/lib/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css
deleted file mode 100644
index d3dae63..0000000
--- a/lib/bridgedb/https/templates/assets/css/font-awesome-ie7.min.css
+++ /dev/null
@@ -1,384 +0,0 @@
-.icon-large{font-size:1.3333333333333333em;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;vertical-align:middle;}
-.nav [class^="icon-"],.nav [class*=" icon-"]{vertical-align:inherit;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;}.nav [class^="icon-"].icon-large,.nav [class*=" icon-"].icon-large{vertical-align:-25%;}
-.nav-pills [class^="icon-"].icon-large,.nav-tabs [class^="icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large{line-height:.75em;margin-top:-7px;padding-top:5px;margin-bottom:-5px;padding-bottom:4px;}
-.btn [class^="icon-"].pull-left,.btn [class*=" icon-"].pull-left,.btn [class^="icon-"].pull-right,.btn [class*=" icon-"].pull-right{vertical-align:inherit;}
-.btn [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large{margin-top:-0.5em;}
-a [class^="icon-"],a [class*=" icon-"]{cursor:pointer;}
-.icon-glass{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-music{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-search{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-envelope-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-heart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-star{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-star-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-user{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-film{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-th-large{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-th{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-th-list{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ok{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-remove{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-zoom-in{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-zoom-out{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-power-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-signal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cog{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gear{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-trash{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-home{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-file-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-time{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-road{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-download-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-download{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-upload{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-inbox{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-play-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-repeat{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rotate-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-refresh{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-list-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-lock{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-flag{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-headphones{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-volume-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-volume-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-volume-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-qrcode{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-barcode{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tag{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tags{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-book{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bookmark{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-print{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-camera{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-font{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bold{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-italic{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-text-height{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-text-width{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-align-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-align-center{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-align-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-align-justify{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-list{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-indent-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-indent-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-facetime-video{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-picture{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-pencil{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-map-marker{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-adjust{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tint{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-edit{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-share{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-check{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-move{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-step-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fast-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-backward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-play{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-pause{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-stop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fast-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-step-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-eject{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-plus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-minus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-remove-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ok-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-question-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-info-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-screenshot{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-remove-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ok-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ban-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-share-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-mail-forward{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-resize-full{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-resize-small{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-plus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-minus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-asterisk{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-exclamation-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gift{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-leaf{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fire{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-eye-open{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-eye-close{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-warning-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-plane{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-calendar{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-random{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-comment{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-magnet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-retweet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-shopping-cart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-folder-close{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-folder-open{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-resize-vertical{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-resize-horizontal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bar-chart{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-twitter-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-facebook-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-camera-retro{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-key{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cogs{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gears{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-comments{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-thumbs-up-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-thumbs-down-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-star-half{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-heart-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-signout{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-linkedin-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-pushpin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-external-link{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-signin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-trophy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-github-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-upload-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-lemon{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-phone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-check-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-unchecked{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bookmark-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-phone-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-twitter{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-facebook{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-github{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-unlock{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-credit-card{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rss{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hdd{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bullhorn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bell{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-certificate{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hand-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hand-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hand-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hand-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-globe{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-wrench{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tasks{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-filter{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-briefcase{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fullscreen{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-group{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-link{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cloud{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-beaker{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cut{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-copy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-paper-clip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-paperclip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-save{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sign-blank{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-reorder{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-list-ul{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-list-ol{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-strikethrough{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-underline{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-table{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-magic{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-truck{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-pinterest{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-pinterest-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-google-plus-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-google-plus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-money{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-caret-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-caret-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-caret-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-caret-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-columns{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-envelope{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-linkedin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-undo{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rotate-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-legal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-dashboard{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-comment-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-comments-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bolt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sitemap{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-umbrella{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-paste{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-lightbulb{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-exchange{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cloud-download{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cloud-upload{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-user-md{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-stethoscope{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-suitcase{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bell-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-coffee{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-food{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-file-text-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-building{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-hospital{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ambulance{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-medkit{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fighter-jet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-beer{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-h-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-plus-sign-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-double-angle-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-double-angle-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-double-angle-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-double-angle-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-angle-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-angle-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-angle-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-angle-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-desktop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-laptop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tablet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-mobile-phone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle-blank{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-quote-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-quote-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-spinner{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-circle{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-reply{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-mail-reply{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-github-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-folder-close-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-folder-open-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-expand-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-collapse-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-smile{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-frown{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-meh{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gamepad{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-keyboard{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-flag-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-flag-checkered{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-terminal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-code{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-reply-all{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-mail-reply-all{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-star-half-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-star-half-full{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-location-arrow{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-crop{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-code-fork{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-unlink{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-question{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-info{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-exclamation{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-superscript{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-subscript{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-eraser{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-puzzle-piece{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-microphone{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-microphone-off{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-shield{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-calendar-empty{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-fire-extinguisher{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rocket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-maxcdn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-sign-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-sign-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-sign-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-chevron-sign-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-html5{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-css3{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-anchor{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-unlock-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bullseye{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ellipsis-horizontal{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ellipsis-vertical{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rss-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-play-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-ticket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-minus-sign-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-check-minus{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-level-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-level-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-check-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-edit-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-external-link-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-share-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-compass{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-collapse{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-collapse-top{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-expand{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-eur{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-euro{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gbp{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-usd{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-dollar{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-inr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-rupee{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-jpy{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-yen{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-cny{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-renminbi{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-krw{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-won{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-btc{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bitcoin{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-file{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-file-text{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-alphabet{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-alphabet-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-attributes{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-attributes-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-order{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sort-by-order-alt{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-thumbs-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-thumbs-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-youtube-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-youtube{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-xing{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-xing-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-youtube-play{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-dropbox{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-stackexchange{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-instagram{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-flickr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-adn{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bitbucket{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bitbucket-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tumblr{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-tumblr-sign{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-long-arrow-down{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-long-arrow-up{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-long-arrow-left{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-long-arrow-right{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-apple{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-windows{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-android{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-linux{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-dribbble{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-skype{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-foursquare{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-trello{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-female{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-male{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-gittip{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-sun{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-moon{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-archive{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-bug{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-vk{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-weibo{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
-.icon-renren{*zoom:expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '');}
diff --git a/lib/bridgedb/https/templates/assets/css/font-awesome.min.css b/lib/bridgedb/https/templates/assets/css/font-awesome.min.css
deleted file mode 100644
index 866437f..0000000
--- a/lib/bridgedb/https/templates/assets/css/font-awesome.min.css
+++ /dev/null
@@ -1,403 +0,0 @@
- at font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;}
-[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;}
-.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;}
-a [class^="icon-"],a [class*=" icon-"]{display:inline;}
-[class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;}
-.icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;}
-.icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;}
-[class^="icon-"].hide,[class*=" icon-"].hide{display:none;}
-.icon-muted{color:#eeeeee;}
-.icon-light{color:#ffffff;}
-.icon-dark{color:#333333;}
-.icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
-.icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
-.icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;}
-.pull-right{float:right;}
-.pull-left{float:left;}
-[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;}
-[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;}
-[class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;}
-.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;}
-.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;}
-.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;}
-.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;}
-.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;}
-.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;}
-.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;}
-.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;}
-.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;}
-.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;}
-.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;}
-.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;}
-.icon-stack .icon-stack-base{font-size:2em;*line-height:1em;}
-.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;}
-a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;}
- at -moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);}
-.icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);}
-.icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);}
-.icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);}
-.icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);}
-a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;}
-.icon-glass:before{content:"\f000";}
-.icon-music:before{content:"\f001";}
-.icon-search:before{content:"\f002";}
-.icon-envelope-alt:before{content:"\f003";}
-.icon-heart:before{content:"\f004";}
-.icon-star:before{content:"\f005";}
-.icon-star-empty:before{content:"\f006";}
-.icon-user:before{content:"\f007";}
-.icon-film:before{content:"\f008";}
-.icon-th-large:before{content:"\f009";}
-.icon-th:before{content:"\f00a";}
-.icon-th-list:before{content:"\f00b";}
-.icon-ok:before{content:"\f00c";}
-.icon-remove:before{content:"\f00d";}
-.icon-zoom-in:before{content:"\f00e";}
-.icon-zoom-out:before{content:"\f010";}
-.icon-power-off:before,.icon-off:before{content:"\f011";}
-.icon-signal:before{content:"\f012";}
-.icon-gear:before,.icon-cog:before{content:"\f013";}
-.icon-trash:before{content:"\f014";}
-.icon-home:before{content:"\f015";}
-.icon-file-alt:before{content:"\f016";}
-.icon-time:before{content:"\f017";}
-.icon-road:before{content:"\f018";}
-.icon-download-alt:before{content:"\f019";}
-.icon-download:before{content:"\f01a";}
-.icon-upload:before{content:"\f01b";}
-.icon-inbox:before{content:"\f01c";}
-.icon-play-circle:before{content:"\f01d";}
-.icon-rotate-right:before,.icon-repeat:before{content:"\f01e";}
-.icon-refresh:before{content:"\f021";}
-.icon-list-alt:before{content:"\f022";}
-.icon-lock:before{content:"\f023";}
-.icon-flag:before{content:"\f024";}
-.icon-headphones:before{content:"\f025";}
-.icon-volume-off:before{content:"\f026";}
-.icon-volume-down:before{content:"\f027";}
-.icon-volume-up:before{content:"\f028";}
-.icon-qrcode:before{content:"\f029";}
-.icon-barcode:before{content:"\f02a";}
-.icon-tag:before{content:"\f02b";}
-.icon-tags:before{content:"\f02c";}
-.icon-book:before{content:"\f02d";}
-.icon-bookmark:before{content:"\f02e";}
-.icon-print:before{content:"\f02f";}
-.icon-camera:before{content:"\f030";}
-.icon-font:before{content:"\f031";}
-.icon-bold:before{content:"\f032";}
-.icon-italic:before{content:"\f033";}
-.icon-text-height:before{content:"\f034";}
-.icon-text-width:before{content:"\f035";}
-.icon-align-left:before{content:"\f036";}
-.icon-align-center:before{content:"\f037";}
-.icon-align-right:before{content:"\f038";}
-.icon-align-justify:before{content:"\f039";}
-.icon-list:before{content:"\f03a";}
-.icon-indent-left:before{content:"\f03b";}
-.icon-indent-right:before{content:"\f03c";}
-.icon-facetime-video:before{content:"\f03d";}
-.icon-picture:before{content:"\f03e";}
-.icon-pencil:before{content:"\f040";}
-.icon-map-marker:before{content:"\f041";}
-.icon-adjust:before{content:"\f042";}
-.icon-tint:before{content:"\f043";}
-.icon-edit:before{content:"\f044";}
-.icon-share:before{content:"\f045";}
-.icon-check:before{content:"\f046";}
-.icon-move:before{content:"\f047";}
-.icon-step-backward:before{content:"\f048";}
-.icon-fast-backward:before{content:"\f049";}
-.icon-backward:before{content:"\f04a";}
-.icon-play:before{content:"\f04b";}
-.icon-pause:before{content:"\f04c";}
-.icon-stop:before{content:"\f04d";}
-.icon-forward:before{content:"\f04e";}
-.icon-fast-forward:before{content:"\f050";}
-.icon-step-forward:before{content:"\f051";}
-.icon-eject:before{content:"\f052";}
-.icon-chevron-left:before{content:"\f053";}
-.icon-chevron-right:before{content:"\f054";}
-.icon-plus-sign:before{content:"\f055";}
-.icon-minus-sign:before{content:"\f056";}
-.icon-remove-sign:before{content:"\f057";}
-.icon-ok-sign:before{content:"\f058";}
-.icon-question-sign:before{content:"\f059";}
-.icon-info-sign:before{content:"\f05a";}
-.icon-screenshot:before{content:"\f05b";}
-.icon-remove-circle:before{content:"\f05c";}
-.icon-ok-circle:before{content:"\f05d";}
-.icon-ban-circle:before{content:"\f05e";}
-.icon-arrow-left:before{content:"\f060";}
-.icon-arrow-right:before{content:"\f061";}
-.icon-arrow-up:before{content:"\f062";}
-.icon-arrow-down:before{content:"\f063";}
-.icon-mail-forward:before,.icon-share-alt:before{content:"\f064";}
-.icon-resize-full:before{content:"\f065";}
-.icon-resize-small:before{content:"\f066";}
-.icon-plus:before{content:"\f067";}
-.icon-minus:before{content:"\f068";}
-.icon-asterisk:before{content:"\f069";}
-.icon-exclamation-sign:before{content:"\f06a";}
-.icon-gift:before{content:"\f06b";}
-.icon-leaf:before{content:"\f06c";}
-.icon-fire:before{content:"\f06d";}
-.icon-eye-open:before{content:"\f06e";}
-.icon-eye-close:before{content:"\f070";}
-.icon-warning-sign:before{content:"\f071";}
-.icon-plane:before{content:"\f072";}
-.icon-calendar:before{content:"\f073";}
-.icon-random:before{content:"\f074";}
-.icon-comment:before{content:"\f075";}
-.icon-magnet:before{content:"\f076";}
-.icon-chevron-up:before{content:"\f077";}
-.icon-chevron-down:before{content:"\f078";}
-.icon-retweet:before{content:"\f079";}
-.icon-shopping-cart:before{content:"\f07a";}
-.icon-folder-close:before{content:"\f07b";}
-.icon-folder-open:before{content:"\f07c";}
-.icon-resize-vertical:before{content:"\f07d";}
-.icon-resize-horizontal:before{content:"\f07e";}
-.icon-bar-chart:before{content:"\f080";}
-.icon-twitter-sign:before{content:"\f081";}
-.icon-facebook-sign:before{content:"\f082";}
-.icon-camera-retro:before{content:"\f083";}
-.icon-key:before{content:"\f084";}
-.icon-gears:before,.icon-cogs:before{content:"\f085";}
-.icon-comments:before{content:"\f086";}
-.icon-thumbs-up-alt:before{content:"\f087";}
-.icon-thumbs-down-alt:before{content:"\f088";}
-.icon-star-half:before{content:"\f089";}
-.icon-heart-empty:before{content:"\f08a";}
-.icon-signout:before{content:"\f08b";}
-.icon-linkedin-sign:before{content:"\f08c";}
-.icon-pushpin:before{content:"\f08d";}
-.icon-external-link:before{content:"\f08e";}
-.icon-signin:before{content:"\f090";}
-.icon-trophy:before{content:"\f091";}
-.icon-github-sign:before{content:"\f092";}
-.icon-upload-alt:before{content:"\f093";}
-.icon-lemon:before{content:"\f094";}
-.icon-phone:before{content:"\f095";}
-.icon-unchecked:before,.icon-check-empty:before{content:"\f096";}
-.icon-bookmark-empty:before{content:"\f097";}
-.icon-phone-sign:before{content:"\f098";}
-.icon-twitter:before{content:"\f099";}
-.icon-facebook:before{content:"\f09a";}
-.icon-github:before{content:"\f09b";}
-.icon-unlock:before{content:"\f09c";}
-.icon-credit-card:before{content:"\f09d";}
-.icon-rss:before{content:"\f09e";}
-.icon-hdd:before{content:"\f0a0";}
-.icon-bullhorn:before{content:"\f0a1";}
-.icon-bell:before{content:"\f0a2";}
-.icon-certificate:before{content:"\f0a3";}
-.icon-hand-right:before{content:"\f0a4";}
-.icon-hand-left:before{content:"\f0a5";}
-.icon-hand-up:before{content:"\f0a6";}
-.icon-hand-down:before{content:"\f0a7";}
-.icon-circle-arrow-left:before{content:"\f0a8";}
-.icon-circle-arrow-right:before{content:"\f0a9";}
-.icon-circle-arrow-up:before{content:"\f0aa";}
-.icon-circle-arrow-down:before{content:"\f0ab";}
-.icon-globe:before{content:"\f0ac";}
-.icon-wrench:before{content:"\f0ad";}
-.icon-tasks:before{content:"\f0ae";}
-.icon-filter:before{content:"\f0b0";}
-.icon-briefcase:before{content:"\f0b1";}
-.icon-fullscreen:before{content:"\f0b2";}
-.icon-group:before{content:"\f0c0";}
-.icon-link:before{content:"\f0c1";}
-.icon-cloud:before{content:"\f0c2";}
-.icon-beaker:before{content:"\f0c3";}
-.icon-cut:before{content:"\f0c4";}
-.icon-copy:before{content:"\f0c5";}
-.icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";}
-.icon-save:before{content:"\f0c7";}
-.icon-sign-blank:before{content:"\f0c8";}
-.icon-reorder:before{content:"\f0c9";}
-.icon-list-ul:before{content:"\f0ca";}
-.icon-list-ol:before{content:"\f0cb";}
-.icon-strikethrough:before{content:"\f0cc";}
-.icon-underline:before{content:"\f0cd";}
-.icon-table:before{content:"\f0ce";}
-.icon-magic:before{content:"\f0d0";}
-.icon-truck:before{content:"\f0d1";}
-.icon-pinterest:before{content:"\f0d2";}
-.icon-pinterest-sign:before{content:"\f0d3";}
-.icon-google-plus-sign:before{content:"\f0d4";}
-.icon-google-plus:before{content:"\f0d5";}
-.icon-money:before{content:"\f0d6";}
-.icon-caret-down:before{content:"\f0d7";}
-.icon-caret-up:before{content:"\f0d8";}
-.icon-caret-left:before{content:"\f0d9";}
-.icon-caret-right:before{content:"\f0da";}
-.icon-columns:before{content:"\f0db";}
-.icon-sort:before{content:"\f0dc";}
-.icon-sort-down:before{content:"\f0dd";}
-.icon-sort-up:before{content:"\f0de";}
-.icon-envelope:before{content:"\f0e0";}
-.icon-linkedin:before{content:"\f0e1";}
-.icon-rotate-left:before,.icon-undo:before{content:"\f0e2";}
-.icon-legal:before{content:"\f0e3";}
-.icon-dashboard:before{content:"\f0e4";}
-.icon-comment-alt:before{content:"\f0e5";}
-.icon-comments-alt:before{content:"\f0e6";}
-.icon-bolt:before{content:"\f0e7";}
-.icon-sitemap:before{content:"\f0e8";}
-.icon-umbrella:before{content:"\f0e9";}
-.icon-paste:before{content:"\f0ea";}
-.icon-lightbulb:before{content:"\f0eb";}
-.icon-exchange:before{content:"\f0ec";}
-.icon-cloud-download:before{content:"\f0ed";}
-.icon-cloud-upload:before{content:"\f0ee";}
-.icon-user-md:before{content:"\f0f0";}
-.icon-stethoscope:before{content:"\f0f1";}
-.icon-suitcase:before{content:"\f0f2";}
-.icon-bell-alt:before{content:"\f0f3";}
-.icon-coffee:before{content:"\f0f4";}
-.icon-food:before{content:"\f0f5";}
-.icon-file-text-alt:before{content:"\f0f6";}
-.icon-building:before{content:"\f0f7";}
-.icon-hospital:before{content:"\f0f8";}
-.icon-ambulance:before{content:"\f0f9";}
-.icon-medkit:before{content:"\f0fa";}
-.icon-fighter-jet:before{content:"\f0fb";}
-.icon-beer:before{content:"\f0fc";}
-.icon-h-sign:before{content:"\f0fd";}
-.icon-plus-sign-alt:before{content:"\f0fe";}
-.icon-double-angle-left:before{content:"\f100";}
-.icon-double-angle-right:before{content:"\f101";}
-.icon-double-angle-up:before{content:"\f102";}
-.icon-double-angle-down:before{content:"\f103";}
-.icon-angle-left:before{content:"\f104";}
-.icon-angle-right:before{content:"\f105";}
-.icon-angle-up:before{content:"\f106";}
-.icon-angle-down:before{content:"\f107";}
-.icon-desktop:before{content:"\f108";}
-.icon-laptop:before{content:"\f109";}
-.icon-tablet:before{content:"\f10a";}
-.icon-mobile-phone:before{content:"\f10b";}
-.icon-circle-blank:before{content:"\f10c";}
-.icon-quote-left:before{content:"\f10d";}
-.icon-quote-right:before{content:"\f10e";}
-.icon-spinner:before{content:"\f110";}
-.icon-circle:before{content:"\f111";}
-.icon-mail-reply:before,.icon-reply:before{content:"\f112";}
-.icon-github-alt:before{content:"\f113";}
-.icon-folder-close-alt:before{content:"\f114";}
-.icon-folder-open-alt:before{content:"\f115";}
-.icon-expand-alt:before{content:"\f116";}
-.icon-collapse-alt:before{content:"\f117";}
-.icon-smile:before{content:"\f118";}
-.icon-frown:before{content:"\f119";}
-.icon-meh:before{content:"\f11a";}
-.icon-gamepad:before{content:"\f11b";}
-.icon-keyboard:before{content:"\f11c";}
-.icon-flag-alt:before{content:"\f11d";}
-.icon-flag-checkered:before{content:"\f11e";}
-.icon-terminal:before{content:"\f120";}
-.icon-code:before{content:"\f121";}
-.icon-reply-all:before{content:"\f122";}
-.icon-mail-reply-all:before{content:"\f122";}
-.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";}
-.icon-location-arrow:before{content:"\f124";}
-.icon-crop:before{content:"\f125";}
-.icon-code-fork:before{content:"\f126";}
-.icon-unlink:before{content:"\f127";}
-.icon-question:before{content:"\f128";}
-.icon-info:before{content:"\f129";}
-.icon-exclamation:before{content:"\f12a";}
-.icon-superscript:before{content:"\f12b";}
-.icon-subscript:before{content:"\f12c";}
-.icon-eraser:before{content:"\f12d";}
-.icon-puzzle-piece:before{content:"\f12e";}
-.icon-microphone:before{content:"\f130";}
-.icon-microphone-off:before{content:"\f131";}
-.icon-shield:before{content:"\f132";}
-.icon-calendar-empty:before{content:"\f133";}
-.icon-fire-extinguisher:before{content:"\f134";}
-.icon-rocket:before{content:"\f135";}
-.icon-maxcdn:before{content:"\f136";}
-.icon-chevron-sign-left:before{content:"\f137";}
-.icon-chevron-sign-right:before{content:"\f138";}
-.icon-chevron-sign-up:before{content:"\f139";}
-.icon-chevron-sign-down:before{content:"\f13a";}
-.icon-html5:before{content:"\f13b";}
-.icon-css3:before{content:"\f13c";}
-.icon-anchor:before{content:"\f13d";}
-.icon-unlock-alt:before{content:"\f13e";}
-.icon-bullseye:before{content:"\f140";}
-.icon-ellipsis-horizontal:before{content:"\f141";}
-.icon-ellipsis-vertical:before{content:"\f142";}
-.icon-rss-sign:before{content:"\f143";}
-.icon-play-sign:before{content:"\f144";}
-.icon-ticket:before{content:"\f145";}
-.icon-minus-sign-alt:before{content:"\f146";}
-.icon-check-minus:before{content:"\f147";}
-.icon-level-up:before{content:"\f148";}
-.icon-level-down:before{content:"\f149";}
-.icon-check-sign:before{content:"\f14a";}
-.icon-edit-sign:before{content:"\f14b";}
-.icon-external-link-sign:before{content:"\f14c";}
-.icon-share-sign:before{content:"\f14d";}
-.icon-compass:before{content:"\f14e";}
-.icon-collapse:before{content:"\f150";}
-.icon-collapse-top:before{content:"\f151";}
-.icon-expand:before{content:"\f152";}
-.icon-euro:before,.icon-eur:before{content:"\f153";}
-.icon-gbp:before{content:"\f154";}
-.icon-dollar:before,.icon-usd:before{content:"\f155";}
-.icon-rupee:before,.icon-inr:before{content:"\f156";}
-.icon-yen:before,.icon-jpy:before{content:"\f157";}
-.icon-renminbi:before,.icon-cny:before{content:"\f158";}
-.icon-won:before,.icon-krw:before{content:"\f159";}
-.icon-bitcoin:before,.icon-btc:before{content:"\f15a";}
-.icon-file:before{content:"\f15b";}
-.icon-file-text:before{content:"\f15c";}
-.icon-sort-by-alphabet:before{content:"\f15d";}
-.icon-sort-by-alphabet-alt:before{content:"\f15e";}
-.icon-sort-by-attributes:before{content:"\f160";}
-.icon-sort-by-attributes-alt:before{content:"\f161";}
-.icon-sort-by-order:before{content:"\f162";}
-.icon-sort-by-order-alt:before{content:"\f163";}
-.icon-thumbs-up:before{content:"\f164";}
-.icon-thumbs-down:before{content:"\f165";}
-.icon-youtube-sign:before{content:"\f166";}
-.icon-youtube:before{content:"\f167";}
-.icon-xing:before{content:"\f168";}
-.icon-xing-sign:before{content:"\f169";}
-.icon-youtube-play:before{content:"\f16a";}
-.icon-dropbox:before{content:"\f16b";}
-.icon-stackexchange:before{content:"\f16c";}
-.icon-instagram:before{content:"\f16d";}
-.icon-flickr:before{content:"\f16e";}
-.icon-adn:before{content:"\f170";}
-.icon-bitbucket:before{content:"\f171";}
-.icon-bitbucket-sign:before{content:"\f172";}
-.icon-tumblr:before{content:"\f173";}
-.icon-tumblr-sign:before{content:"\f174";}
-.icon-long-arrow-down:before{content:"\f175";}
-.icon-long-arrow-up:before{content:"\f176";}
-.icon-long-arrow-left:before{content:"\f177";}
-.icon-long-arrow-right:before{content:"\f178";}
-.icon-apple:before{content:"\f179";}
-.icon-windows:before{content:"\f17a";}
-.icon-android:before{content:"\f17b";}
-.icon-linux:before{content:"\f17c";}
-.icon-dribbble:before{content:"\f17d";}
-.icon-skype:before{content:"\f17e";}
-.icon-foursquare:before{content:"\f180";}
-.icon-trello:before{content:"\f181";}
-.icon-female:before{content:"\f182";}
-.icon-male:before{content:"\f183";}
-.icon-gittip:before{content:"\f184";}
-.icon-sun:before{content:"\f185";}
-.icon-moon:before{content:"\f186";}
-.icon-archive:before{content:"\f187";}
-.icon-bug:before{content:"\f188";}
-.icon-vk:before{content:"\f189";}
-.icon-weibo:before{content:"\f18a";}
-.icon-renren:before{content:"\f18b";}
diff --git a/lib/bridgedb/https/templates/assets/css/main.css b/lib/bridgedb/https/templates/assets/css/main.css
deleted file mode 100644
index df34981..0000000
--- a/lib/bridgedb/https/templates/assets/css/main.css
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Imports */
- at import url("bootstrap.min.css");
- at import url("font-awesome.min.css");
- at import url("custom.css");
-
-/* Fonts */
- at font-face {
- font-family: 'Lato';
- font-style: normal;
- font-weight: 400;
- src: local('Lato Regular'), local('Lato-Regular'), url('../font/lato-regular.woff') format('woff');
-}
- at font-face {
- font-family: 'Lato';
- font-style: normal;
- font-weight: 700;
- src: local('Lato Bold'), local('Lato-Bold'), url('../font/lato-bold.woff') format('woff');
-}
- at font-face {
- font-family: 'Lato';
- font-style: italic;
- font-weight: 400;
- src: local('Lato Italic'), local('Lato-Italic'), url('../font/lato-italic.woff') format('woff');
-}
diff --git a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.eot b/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.eot
deleted file mode 100755
index 0662cb9..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.eot and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.svg b/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.svg
deleted file mode 100755
index 2edb4ec..0000000
--- a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.svg
+++ /dev/null
@@ -1,399 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="fontawesomeregular" horiz-adv-x="1536" >
-<font-face units-per-em="1792" ascent="1536" descent="-256" />
-<missing-glyph horiz-adv-x="448" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode="	" horiz-adv-x="448" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode="¨" horiz-adv-x="1792" />
-<glyph unicode="©" horiz-adv-x="1792" />
-<glyph unicode="®" horiz-adv-x="1792" />
-<glyph unicode="´" horiz-adv-x="1792" />
-<glyph unicode="Æ" horiz-adv-x="1792" />
-<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
-<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
-<glyph unicode=" " horiz-adv-x="512" />
-<glyph unicode=" " horiz-adv-x="384" />
-<glyph unicode=" " horiz-adv-x="256" />
-<glyph unicode=" " horiz-adv-x="256" />
-<glyph unicode=" " horiz-adv-x="192" />
-<glyph unicode=" " horiz-adv-x="307" />
-<glyph unicode=" " horiz-adv-x="85" />
-<glyph unicode=" " horiz-adv-x="307" />
-<glyph unicode=" " horiz-adv-x="384" />
-<glyph unicode="™" horiz-adv-x="1792" />
-<glyph unicode="∞" horiz-adv-x="1792" />
-<glyph unicode="≠" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="500" d="M0 0z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
-<glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t1
9 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28
t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
-<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
-<glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " />
-<glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
-<glyph unicode="" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
-<glyph unicode="" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
-<glyph unicode="" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -1
13 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
-<glyph unicode="" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
-<glyph unicode="" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
-<glyph unicode="" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
-<glyph unicode="" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
-<glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" />
-<glyph unicode="" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" />
-<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" />
-<glyph unicode="" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" />
-<glyph unicode="" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t
-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
-<glyph unicode="" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
-<glyph unicode="" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
-<glyph unicode="" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
-<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
-<glyph unicode="" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" />
-<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
-<glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
-<glyph unicode="" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
-<glyph unicode="" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
-<glyph unicode="" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
-<glyph unicode="" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
-<glyph unicode="" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
-<glyph unicode="" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
-<glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
-<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
-<glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152
-23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
-<glyph unicode="" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
-<glyph unicode="" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
-<glyph unicode="" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
-<glyph unicode="" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
-<glyph unicode="" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5
-68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
-<glyph unicode="" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
-<glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
-<glyph unicode="" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" />
-<glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
-<glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
-<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
-<glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
-<glyph unicode="" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17
t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-1
5 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q
-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
-<glyph unicode="" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
-<glyph unicode="" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
-<glyph unicode="" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
-<glyph unicode="" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-
768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
-<glyph unicode="" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
-<glyph unicode="" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
-<glyph unicode="" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
-<glyph unicode="" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 11
3v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
-<glyph unicode="" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" />
-<glyph unicode="" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" />
-<glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
-<glyph unicode="" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
-<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
-<glyph unicode="" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
-<glyph unicode="" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
-<glyph unicode="" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
-<glyph unicode="" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
-<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5
v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9
.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.
5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
-<glyph unicode="" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
-<glyph unicode="" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
-<glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
-<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
-<glyph unicode="" horiz-adv-x="1152" d="M896 608v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h224q14 0 23 -9t9 -23zM1024 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 -28 t-28 -68v-704q0 -40 28 -68t68 -28h704q40 0 68 28t28 68zM1152 928v-704q0 -92 -65.5 -158t-158.5 -66h-704q-93 0 -158.5 66t-65.5 158v704q0 93 65.5 158.5t158.5 65.5h704q93 0 158.5 -65.5t65.5 -158.5z" />
-<glyph unicode="" horiz-adv-x="1152" d="M928 1152q93 0 158.5 -65.5t65.5 -158.5v-704q0 -92 -65.5 -158t-158.5 -66h-704q-93 0 -158.5 66t-65.5 158v704q0 93 65.5 158.5t158.5 65.5h704zM1024 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 -28t-28 -68v-704q0 -40 28 -68t68 -28h704q40 0 68 28t28 68z M864 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576z" />
-<glyph unicode="" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
-<glyph unicode="" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16
h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
-<glyph unicode="" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
-<glyph unicode="" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23
-9q9 -10 9 -23t-9 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
-<glyph unicode="" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
-<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
-<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
-<glyph unicode="" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
-<glyph unicode="" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1708 881l-188 -881h-304l181 849q4 21 1 43q-4 20 -16 35q-10 14 -28 24q-18 9 -40 9h-197l-205 -960h-303l204 960h-304l-205 -960h-304l272 1280h1139q157 0 245 -118q86 -116 52 -281z" />
-<glyph unicode="" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
-<glyph unicode="" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
-<glyph unicode="" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
-<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
-<glyph unicode="" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
-<glyph unicode="" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
-<glyph unicode="" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
-<glyph unicode="" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 352v-32q0 -132 -94 -226t-226 -94h-128q-132 0 -226 94t-94 226v480h-224q-2 -102 -14.5 -190.5t-30.5 -156t-48.5 -126.5t-57 -99.5t-67.5 -77.5t-69.5 -58.5t-74 -44t-69 -32t-65.5 -25.5q-4 -2 -32 -13q-8 -2 -12 -2q-22 0 -30 20l-71 178q-5 13 0 25t17 17 q7 3 20 7.5t18 6.5q31 12 46.5 18.5t44.5 20t45.5 26t42 32.5t40.5 42.5t34.5 53.5t30.5 68.5t22.5 83.5t17 103t6.5 123h-256q-14 0 -23 9t-9 23v160q0 14 9 23t23 9h1216q14 0 23 -9t9 -23v-160q0 -14 -9 -23t-23 -9h-224v-512q0 -26 19 -45t45 -19h128q26 0 45 19t19 45 v64q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1280 1376v-160q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v160q0 14 9 23t23 9h960q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
-<glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
-<glyph unicode="" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
-<glyph unicode="" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
-<glyph unicode="" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
-<glyph unicode="" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21
87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -10
6 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
-<glyph unicode="" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
-<glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
-<glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
-<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
-<glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
-<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
-<glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
-<glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " />
-<glyph unicode="" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
-<glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
-<glyph unicode="" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
-<glyph unicode="" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
-<glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31
-29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 1
0.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t
1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
-<glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" />
-<glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
-<glyph unicode="" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
-<glyph unicode="" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10
10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t3
7 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M805 163q-122 -67 -261 -67q-141 0 -261 67q98 61 167 149t94 191q25 -103 94 -191t167 -149zM453 1176v-344q0 -179 -89.5 -326t-234.5 -217q-129 152 -129 351q0 200 129.5 352t323.5 184zM958 991q-128 -152 -128 -351q0 -201 128 -351q-145 70 -234.5 218t-89.5 328 v341q196 -33 324 -185zM1638 163q-122 -67 -261 -67q-141 0 -261 67q98 61 167 149t94 191q25 -103 94 -191t167 -149zM1286 1176v-344q0 -179 -91 -326t-237 -217v0q133 154 133 351q0 195 -133 351q129 151 328 185zM1920 640q0 -201 -129 -351q-145 70 -234.5 218 t-89.5 328v341q194 -32 323.5 -184t129.5 -352z" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-</font>
-</defs></svg>
\ No newline at end of file
diff --git a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf b/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf
deleted file mode 100755
index d365924..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.ttf and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.woff b/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.woff
deleted file mode 100755
index b9bd17e..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/fontawesome-webfont.woff and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/font/lato-bold.woff b/lib/bridgedb/https/templates/assets/font/lato-bold.woff
deleted file mode 100644
index 4b17251..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/lato-bold.woff and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/font/lato-italic.woff b/lib/bridgedb/https/templates/assets/font/lato-italic.woff
deleted file mode 100644
index 09cc379..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/lato-italic.woff and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/font/lato-regular.woff b/lib/bridgedb/https/templates/assets/font/lato-regular.woff
deleted file mode 100644
index f48e484..0000000
Binary files a/lib/bridgedb/https/templates/assets/font/lato-regular.woff and /dev/null differ
diff --git a/lib/bridgedb/https/templates/assets/tor-roots-blue.svg b/lib/bridgedb/https/templates/assets/tor-roots-blue.svg
deleted file mode 100644
index 967843c..0000000
--- a/lib/bridgedb/https/templates/assets/tor-roots-blue.svg
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="svg3030"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- width="885.82672"
- height="1062.9921"
- xml:space="preserve"
- sodipodi:docname="tor-roots.svg"><metadata
- id="metadata3036"><rdf:RDF><cc:Work
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
- id="defs3034"><clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3044"><path
- d="M 0,0 720,0 720,540 0,540 0,0 z"
- clip-rule="evenodd"
- id="path3046"
- inkscape:connector-curvature="0" /></clipPath><clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3056"><path
- d="m 0,8 730.4,0 0,524.6 L 0,532.6 0,8 z"
- clip-rule="evenodd"
- id="path3058"
- inkscape:connector-curvature="0" /></clipPath><clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3068"><path
- d="M 0,0 720,0 720,540 0,540 0,0 z"
- clip-rule="evenodd"
- id="path3070"
- inkscape:connector-curvature="0" /></clipPath><clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3076"><path
- d="m 630.7,7.7 46.7,0 0,40.1 -46.7,0 0,-40.1 z"
- clip-rule="evenodd"
- id="path3078"
- inkscape:connector-curvature="0" /></clipPath><clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3088"><path
- d="M 0,0 720,0 720,540 0,540 0,0 z"
- clip-rule="evenodd"
- id="path3090"
- inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1278"
- inkscape:window-height="776"
- id="namedview3032"
- showgrid="false"
- fit-margin-top="0"
- fit-margin-left="0"
- fit-margin-right="0"
- fit-margin-bottom="0"
- inkscape:zoom="0.49445096"
- inkscape:cx="-38.557942"
- inkscape:cy="516.79248"
- inkscape:window-x="0"
- inkscape:window-y="22"
- inkscape:window-maximized="1"
- inkscape:current-layer="g3038"
- units="cm" /><g
- id="g3038"
- inkscape:groupmode="layer"
- inkscape:label="2013-03-13-SXSW-Presentation-Loop"
- transform="matrix(1.25,0,0,-1.25,-139.78938,1119.4344)"><path
- style="fill:#2c3e50;fill-opacity:1;opacity:1"
- d="m 440.59196,60.107159 c 1.77285,1.779182 2.10042,2.749995 1.6824,4.986083 -0.48665,2.603271 0.007,3.302678 7.9034,11.197389 l 8.42244,8.420668 8.40124,-8.254833 c 6.43102,-6.318989 8.22542,-8.569982 7.65182,-9.598627 -1.82916,-3.280158 1.40752,-8.095421 5.44156,-8.095421 5.49349,0 7.69468,6.866396 3.31062,10.327138 -1.44224,1.138526 -2.91637,1.607212 -3.98473,1.266931 -1.33158,-0.424129 -3.61478,1.37927 -10.63339,8.398881 l -8.93771,8.938984 0,7.323538 0,7.32354 3.29303,-3.227002 c 2.56179,-2.510421 3.21797,-3.749289 2.95505,-5.578978 -0.75405,-5.246816 6.39302,-8.329277 10.08683,-4.350345 3.79291,4.085659 0.9533,9.631991 -4.97867,9.724432 -2.98412,0.04649 -4.13551,0.651235 -7.44324,3.909403 -3.62619,3.57187 -3.913,4.17655 -3.913,8.24946 0,4.06693 -0.28947,4.68014 -3.87679,8.21335 -2.13216,2.10004 -3.87672,4.23983 -3.87672,4.75507 0,0.51526 1.74456,2.65506 3.87672,4.75508 l 3.87679,3.81829 0,7.16876 c 0,3.9428 0.31161,7.16876 0.69248,7.16876 0.38087,0 1.93657,-1.28809 3.45
704,-2.86243 2.31121,-2.39303 2.67632,-3.30514 2.2265,-5.56233 -0.43282,-2.17168 -0.1055,-3.13401 1.67278,-4.9186 1.21595,-1.22031 3.02144,-2.21873 4.01214,-2.21873 2.32438,0 6.03052,3.71934 6.03052,6.05193 0,2.8732 -3.90897,6.22418 -6.89405,5.90998 -2.06374,-0.21725 -3.35146,0.53479 -6.86865,4.01144 -4.16706,4.11898 -4.32876,4.45845 -4.32876,9.08554 l 0,4.80668 -7.32275,7.31058 -7.32275,7.3106 0,14.26403 0,14.26402 -6.2243,6.2884 -6.22433,6.28839 7.47742,0 c 6.82722,0 7.66468,-0.18795 9.6311,-2.16139 2.62653,-2.63582 5.57084,-2.76335 8.14884,-0.35307 4.10127,3.8345 0.0538,11.18136 -5.40273,9.80696 -1.30615,-0.32896 -2.79372,-1.52081 -3.30569,-2.64845 -0.89438,-1.96991 -1.28952,-2.05034 -10.07275,-2.05034 l -9.14184,0 -7.08838,7.15072 c -5.19899,5.24472 -7.08832,7.7444 -7.08832,9.37821 0,1.62837 -1.78639,4.00866 -6.64147,8.84952 l -6.64144,6.62201 4.4877,4.5603 4.48773,4.5603 0,15.31879 c 0,12.17599 0.23523,15.31879 1.14663,15.31879 1.63093,0 3.36995,3.65803 3.02363,6.3602 -0.22936,
1.7895 0.64823,3.25335 4.01397,6.69546 l 4.30748,4.40524 0.24254,-15.43081 c 0.22378,-14.23635 0.11688,-15.53395 -1.38114,-16.76344 -0.89302,-0.73297 -2.06689,-2.1637 -2.60856,-3.1795 -1.79369,-3.36338 1.21095,-8.02401 5.17292,-8.02401 5.46255,0 7.95065,6.899 3.69858,10.25555 -1.96922,1.55451 -2.17134,2.25078 -1.93837,6.67904 l 0.26002,4.94362 4.95361,-5.05802 4.95364,-5.05803 0,-7.80126 0,-7.80127 -4.85442,-4.92305 c -4.22854,-4.28833 -5.20039,-4.8743 -7.53809,-4.54517 -3.3372,0.46984 -5.5265,-1.17202 -6.22304,-4.6669 -0.42341,-2.12466 -0.0853,-3.10548 1.68089,-4.87809 1.21597,-1.22031 3.02144,-2.21873 4.01215,-2.21873 2.94799,0 6.30952,4.00353 5.74402,6.84107 -0.40038,2.00908 0.14952,3.02267 3.34974,6.17462 l 3.82875,3.77102 0.0586,-2.55755 c 0.0323,-1.4067 0.0109,-5.77379 -0.0534,-9.70471 -0.0998,-6.34102 -0.34933,-7.38483 -2.21259,-9.25474 -4.0476,-4.06198 -1.8855,-9.96726 3.6493,-9.96726 5.22053,0 7.64042,6.53037 3.76322,10.15538 -1.50898,1.41085 -1.69712,2.8501 -1.78443,13.650
53 -0.15322,18.95912 -0.81091,16.82852 5.19421,16.82852 3.93009,0 5.16901,-0.29911 5.16901,-1.24782 0,-0.6863 0.86162,-1.85347 1.91469,-2.5937 6.27532,-4.411 13.02773,4.21963 7.01487,8.96611 -3.00981,2.37597 -5.13349,2.26004 -7.60378,-0.4149 -1.80516,-1.95472 -2.60819,-2.17691 -7.03038,-1.94524 l -4.99244,0.26151 0.22128,4.32281 0.22129,4.32278 -6.93375,6.89625 -6.93376,6.89619 0,9.97932 0,9.97928 8.83035,8.92645 8.83036,8.92645 0.24343,-10.64719 c 0.22328,-9.76664 0.10174,-10.75648 -1.46941,-11.96895 -5.88888,-4.54437 -1.22221,-13.76006 5.23786,-10.34378 2.63325,1.39252 3.28167,2.45215 3.29768,5.38886 0.007,1.48195 -0.82104,3.19474 -2.14053,4.42197 l -2.15376,2.00314 0,17.17038 0,17.1704 -5.78023,5.8456 -5.78029,5.84555 2.80094,2.9001 2.80094,2.90007 -0.11107,15.33236 c -0.0907,12.50778 0.1265,15.59919 1.17856,16.78061 1.26462,1.42015 1.75047,3.01476 1.54722,5.07828 -0.056,0.56999 -3.59117,4.42577 -7.85559,8.56835 l -7.75351,7.53201 0,9.9994 0,9.99939 -6.22497,6.28907 -6.22495,6.28
911 6.65572,6.7188 6.65572,6.71879 0,12.30086 0,12.30091 2.08326,0 c 5.04,0 4.80873,1.37475 4.80873,-28.58377 l 0,-27.53418 -2.15376,-2.1614 c -1.18455,-1.18876 -2.15373,-2.97486 -2.15373,-3.96914 0,-2.33256 3.70614,-6.05192 6.03051,-6.05192 2.47899,0 6.03048,3.78034 6.03048,6.41903 0,1.42849 -0.91312,2.85126 -2.66081,4.14597 l -2.66084,1.97117 0.2828,27.09457 c 0.1556,14.90203 0.48066,27.21118 0.72244,27.35367 0.24183,0.14237 2.47498,0.0585 4.96252,-0.18703 l 4.5229,-0.44612 0,-17.46281 0,-17.46277 -2.15375,-2.03057 c -2.88179,-2.71692 -2.83394,-6.2541 0.11615,-8.58285 2.98088,-2.35316 5.09314,-2.26229 7.67646,0.33019 2.80923,2.81927 2.66733,5.31161 -0.4699,8.25266 l -2.58451,2.42287 0,17.37667 c 0,19.37107 -0.52929,17.94555 6.24587,16.82171 l 3.2306,-0.53587 0,-11.65021 c 0,-11.3971 -0.0467,-11.68707 -2.15374,-13.35032 -4.38005,-3.45761 -2.00082,-10.22028 3.59569,-10.22028 5.30675,0 7.52615,6.88125 3.29634,10.22028 -2.10733,1.66348 -2.15376,1.95186 -2.15376,13.37096 0,9.84469 0.21
321,11.75291 1.36271,12.19558 0.74948,0.28864 4.82006,0.52478 9.04573,0.52478 l 7.68305,0 0,-20.25115 0,-20.25119 -10.70009,-10.78494 -10.70007,-10.78494 -0.0994,-16.57493 -0.0996,-16.57494 6.00928,-6.21945 6.00929,-6.21945 0.18972,-5.88108 0.1897,-5.88106 -6.16806,-6.23158 -6.16813,-6.23166 0,-16.71416 0,-16.7142 -2.15376,-1.70017 c -4.38004,-3.45757 -2.00082,-10.22028 3.59571,-10.22028 5.30673,0 7.52611,6.88128 3.2963,10.22028 l -2.15376,1.70017 0,16.33837 0,16.33838 3.89023,3.8315 3.89018,3.8315 4.8449,-4.8621 4.8449,-4.86212 -0.11046,-28.40064 c -0.0608,-15.62034 -0.11482,-28.7158 -0.12015,-29.10106 -0.005,-0.38526 -3.59553,-0.62446 -7.97841,-0.5316 -6.9784,0.14789 -8.27151,0.42297 -10.40409,2.21345 -3.12976,2.62768 -5.28925,2.58454 -8.02442,-0.16037 -2.54928,-2.55835 -2.27896,-5.86939 0.68458,-8.38595 2.65958,-2.2585 5.44762,-1.86786 7.94739,1.1135 2.19301,2.61552 2.23527,2.62544 10.10208,2.37756 l 7.89979,-0.24897 -0.0317,-5.5089 -0.0316,-5.50886 -5.35142,-5.41865 c -4.15434,-
4.20647 -5.38264,-6.04119 -5.49091,-8.20179 -0.11106,-2.21558 -1.43287,-4.08634 -6.48165,-9.17355 -4.03166,-4.06232 -7.0297,-6.39037 -8.22932,-6.39037 -2.48967,0 -5.6335,-3.44499 -5.6335,-6.17311 0,-2.394 3.20199,-5.93076 5.3694,-5.93076 4.25447,0 7.29005,4.3597 5.79088,8.31684 -0.82506,2.17781 -0.54161,2.72103 3.63483,6.96506 2.49135,2.53165 4.77568,4.60299 5.07632,4.60299 0.30062,0 0.61403,-2.25417 0.69637,-5.00931 0.13905,-4.65044 -0.0369,-5.1478 -2.45554,-6.94239 -3.12358,-2.31754 -3.46727,-5.81411 -0.84876,-8.63476 2.14554,-2.31113 5.14881,-2.42622 7.76805,-0.2977 2.6187,2.128 2.66859,6.74649 0.0956,8.83748 -1.71615,1.3946 -1.86639,2.307 -1.86415,11.32071 l 9.8e-4,9.80387 5.57271,5.61968 5.57274,5.61965 0.0244,28.05758 0.0245,28.05757 3.446,-3.37685 3.44598,-3.37691 0,-27.80275 0,-27.80275 -7.32274,-7.00757 c -7.97519,-7.63193 -8.81635,-9.67878 -5.32884,-12.96678 2.55248,-2.40645 6.0699,-2.4012 8.31053,0.013 2.23972,2.41259 2.23089,5.05061 -0.0264,7.93067 l -1.78314,2.27496 4.7
9772,4.73843 4.79771,4.73843 -0.0511,20.55305 c -0.0285,11.30418 -0.028,20.93626 6.9e-4,21.40463 0.0287,0.46833 4.89803,-3.99995 10.82082,-9.92948 l 10.76875,-10.78111 0,-11.71411 0,-11.71409 -4.62435,-4.58313 c -2.86508,-2.83954 -5.24122,-4.49205 -6.2459,-4.34373 -2.87149,0.42396 -5.28576,-0.9481 -6.39435,-3.63402 -0.94772,-2.29616 -0.86879,-2.90703 0.63865,-4.94248 3.90989,-5.27929 11.22699,-2.32445 10.35732,4.18258 -0.37982,2.84172 -0.0722,3.63436 2.31012,5.95157 1.51157,1.47026 3.02058,2.67319 3.35342,2.67319 0.33281,0 0.60509,-6.60027 0.60509,-14.6673 l 0,-14.6673 -8.8304,-0.2464 c -8.72545,-0.24342 -8.85851,-0.217 -11.20124,2.22447 -1.89791,1.97799 -2.84305,2.35864 -4.73821,1.90839 -2.72281,-0.64687 -5.38264,-3.44144 -5.38264,-5.65527 0,-0.83422 0.80348,-2.54184 1.78552,-3.79473 2.3582,-3.00861 6.09113,-3.06044 8.39857,-0.11653 1.6168,2.06276 2.1111,2.16141 10.83127,2.16141 l 9.13713,0 0,-2.82692 c 0,-2.41984 -1.17482,-4.001 -8.15911,-10.98099 l -8.15915,-8.15406 -0.24048,-10.
41694 -0.24051,-10.41694 -8.45278,-0.24806 c -7.97189,-0.23397 -8.6006,-0.12323 -11.05023,1.94526 -2.84367,2.4013 -4.27933,2.66798 -6.8706,1.27626 -3.98071,-2.13801 -3.69089,-8.5501 0.47251,-10.45387 2.65444,-1.21373 7.06536,0.37212 7.83901,2.81833 0.46409,1.46743 1.44432,1.63617 9.50516,1.63617 l 8.98767,0 0,-10.39616 c 0,-10.13373 -0.0511,-10.41571 -2.03623,-11.17308 -2.29606,-0.87607 -3.13277,-2.51018 -3.13277,-6.11844 0,-2.79161 2.66169,-5.16571 5.79147,-5.16571 4.66157,0 7.17266,6.77502 3.68504,9.94247 -1.45142,1.31821 -1.72299,2.66281 -1.72299,8.53097 l 0,6.96614 4.30749,-4.25781 4.30751,-4.2578 0,-12.64601 c 0,-12.61245 -0.005,-12.6486 -2.12997,-13.61993 -4.22196,-1.93051 -3.94163,-8.7353 0.43481,-10.5545 3.30804,-1.37511 6.53282,-0.1109 7.72162,3.02694 0.96562,2.54882 -0.16774,6.2834 -2.13679,7.04174 -1.08911,0.4194 -1.30517,2.746 -1.30517,14.05195 l 0,13.54934 -5.59978,5.56968 -5.59972,5.56969 0,5.19693 0,5.19689 8.97117,-8.97246 c 8.37646,-8.37762 8.94118,-9.1582 8.51802,-
11.77495 -1.0612,-6.56269 7.79358,-8.89781 10.99267,-2.89888 2.11779,3.97119 -2.57429,9.22866 -7.21952,8.08949 -1.51892,-0.37246 -3.73433,1.42867 -11.57047,9.4067 l -9.69187,9.86737 0,6.38724 0,6.38721 8.18421,8.17921 8.18425,8.17919 0,26.60234 c 0,14.63128 0.25098,26.60232 0.55769,26.60232 0.30675,0 3.40814,-2.96451 6.892,-6.5878 l 6.33432,-6.58785 0,-24.92148 0,-24.92153 -2.34907,-1.12986 c -3.21721,-1.54748 -4.45587,-5.28238 -2.75846,-8.31737 1.05652,-1.88895 1.91219,-2.31871 4.61684,-2.31871 2.59957,0 3.69493,0.50395 5.04785,2.32252 1.52166,2.04534 1.5974,2.63852 0.63459,4.9712 -0.60131,1.4568 -1.43392,2.64877 -1.85026,2.64877 -0.41635,0 -0.75698,8.64667 -0.75698,19.21486 l 0,19.21484 6.44329,-6.423 c 6.1699,-6.15051 6.42175,-6.55826 5.93556,-9.60947 -0.40242,-2.52553 -0.13067,-3.56472 1.3102,-5.01073 2.38709,-2.3956 6.41879,-2.36409 8.67637,0.0679 3.95939,4.265 0.59661,10.02223 -5.47384,9.37147 -1.0349,-0.11113 -4.41802,2.60953 -9.26108,7.44729 l -7.6305,7.62212 0,5.65478 0,5.6
5472 -8.18425,8.17918 -8.18427,8.17923 0,5.17724 c 0,5.04625 -0.10791,5.28736 -4.27356,9.52726 -4.2366,4.31244 -4.27533,4.40259 -4.48646,10.43843 -0.18505,5.29047 0.0364,6.33863 1.68906,7.99721 2.10586,2.11332 2.49311,5.96945 0.8251,8.2161 -1.81286,2.4417 -6.20526,2.81129 -8.30988,0.6992 -2.63251,-2.64191 -2.45676,-7.40776 0.34101,-9.24746 1.83373,-1.2058 2.15376,-2.07477 2.15376,-5.84826 l 0,-4.43207 -6.04746,6.06893 -6.04741,6.06891 -0.31821,5.47319 c -0.31015,5.33478 -0.44043,5.59713 -5.15206,10.37537 l -4.83385,4.90218 0,6.46057 0,6.46056 -5.3343,5.43679 -5.33435,5.43677 1.45758,2.47627 c 0.98554,1.67429 1.45755,4.38634 1.45755,8.37443 l 0,5.89821 -5.99998,6.07226 -5.99994,6.07225 -0.10428,15.90751 -0.10441,15.90748 10.41196,10.47415 10.41196,10.47414 0,21.3854 0,21.38542 3.44599,0 3.44602,0 0,-32.22813 0,-32.22811 -5.83676,-5.81258 c -4.72525,-4.70576 -6.35389,-5.81262 -8.55246,-5.81262 -1.90777,0 -3.24697,-0.67775 -4.50125,-2.278 -2.23384,-2.84993 -2.23384,-3.83337 0,-6.68331
3.81603,-4.86855 10.9992,-1.90128 10.02227,4.14013 -0.45396,2.80744 -0.1038,3.37157 5.4839,8.83293 l 5.96878,5.83398 0,33.55014 c 0,31.37164 0.098,33.5509 1.50763,33.56194 0.82918,0.007 2.3799,0.2464 3.44599,0.53312 l 1.93838,0.52129 0,-12.83397 0,-12.83399 6.65572,-6.71881 6.65574,-6.71879 -6.22496,-6.28908 -6.22499,-6.28911 0,-9.99939 0,-9.9994 -7.75351,-7.532 c -8.36835,-8.12931 -9.51045,-10.76699 -6.0305,-13.92752 1.60226,-1.45517 1.72303,-2.66284 1.72303,-17.23229 l 0,-15.66743 5.93537,-5.91085 c 5.6082,-5.58501 5.91166,-6.08847 5.50474,-9.13309 -0.36785,-2.75196 -0.0856,-3.49471 1.93428,-5.08912 3.09842,-2.44589 5.16876,-2.35675 7.85398,0.33804 1.69727,1.70327 2.07201,2.73594 1.6471,4.53894 -0.76282,3.23725 -3.7648,5.59183 -6.46817,5.0732 -1.81222,-0.34766 -3.19511,0.59387 -8.00667,5.45136 l -5.81615,5.87161 0,14.75197 0,14.75188 2.30551,1.12789 c 1.26803,0.62034 2.66983,2.23576 3.11508,3.58985 0.65908,2.00431 0.45227,2.94694 -1.1126,5.07108 l -1.92217,2.60904 2.62811,2.63749
2.6281,2.63743 6.51698,-6.34163 c 3.58435,-3.48787 6.51698,-6.7755 6.51698,-7.30574 0,-0.53027 -2.0353,-2.9699 -4.52286,-5.42134 -3.64565,-3.59273 -5.17462,-4.50928 -7.88333,-4.72558 -2.47866,-0.19792 -3.82119,-0.8903 -5.11654,-2.63874 -1.66368,-2.24567 -1.68299,-2.52437 -0.36736,-5.29308 2.97225,-6.25505 10.63775,-4.23837 10.49635,2.76143 -0.0642,3.17482 0.41333,3.99484 4.94905,8.49913 l 5.02003,4.98533 3.86534,-3.80703 c 3.57672,-3.52279 3.8262,-4.05263 3.34119,-7.09635 -1.18872,-7.46039 8.08326,-10.10879 10.96313,-3.13147 0.90618,2.19544 0.81566,2.84893 -0.67308,4.85911 -1.16379,1.57139 -2.66761,2.40881 -4.65013,2.58949 -2.46757,0.22491 -4.7963,2.13007 -14.75432,12.07094 -11.74938,11.72907 -11.81391,11.81505 -10.18242,13.56595 1.41706,1.52072 1.64138,3.11386 1.63786,11.63247 l -0.004,9.87042 5.63995,5.66 5.63998,5.66 2.43559,-2.34172 c 1.95183,-1.87664 2.36753,-2.92677 2.09293,-5.28712 -0.6952,-5.97622 6.42511,-8.98558 10.11672,-4.27576 2.13834,2.72814 1.71712,5.05326 -1.43566,7.
92453 -1.50728,1.37267 -2.82709,1.85183 -4.16937,1.51378 -1.62877,-0.41026 -3.66674,1.19362 -11.93976,9.3965 l -9.97549,9.89127 0,12.48574 c 0,12.21738 0.0418,12.49695 1.93839,13.00706 5.00017,1.3448 4.95362,1.43935 4.95362,-10.05495 l 0,-10.59989 15.50378,-15.5409 15.50374,-15.54087 -8.3964,-8.47315 c -6.8812,-6.94412 -8.74753,-8.38617 -10.34218,-7.99106 -5.95321,1.47487 -9.89203,-6.16197 -5.23272,-10.1455 3.92241,-3.35355 9.33225,-0.54941 9.33225,4.83724 0,1.6604 0.64447,3.47727 1.51333,4.26634 1.40525,1.27625 1.86673,1.03366 6.46125,-3.39729 l 4.94792,-4.77174 0,-17.02699 0,-17.02703 -5.59975,-5.56966 -5.59976,-5.56972 0,-6.48202 0,-6.48199 4.49346,-4.56613 4.49342,-4.56614 -4.38795,0 c -2.8494,0 -4.76398,0.45469 -5.46041,1.29687 -2.50861,3.03339 -9.4765,-0.46293 -9.4765,-4.7551 0,-2.33261 3.70618,-6.05195 6.03051,-6.05195 2.10015,0 6.03051,3.63694 6.03051,5.58039 0,1.05356 1.07073,1.3361 5.06349,1.3361 5.64012,0 7.60068,-1.35285 7.13447,-4.92306 -0.44146,-3.38076 2.03002,-6.3162
4 5.31795,-6.31624 3.63159,0 5.59327,1.499 6.22283,4.7551 0.74675,3.86201 -2.23182,6.77646 -6.40285,6.26504 -2.94142,-0.36069 -3.50563,0.013 -9.79778,6.47483 l -6.67662,6.85759 0,4.98498 c 0,4.98364 9.7e-4,4.98646 5.58009,10.55747 l 5.58009,5.57247 0.23503,16.25677 0.23503,16.25682 2.79984,-2.68647 2.79991,-2.68646 0,-16.96843 0,-16.96842 -2.20767,-2.0533 c -2.88649,-2.6846 -2.62057,-6.77462 0.59649,-9.17392 3.86662,-2.88373 8.50317,-0.14699 8.50317,5.01883 0,2.19566 -0.55403,3.30861 -2.17909,4.37712 l -2.1791,1.43292 0.0923,15.54338 0.0925,15.54334 2.71917,-2.4749 c 1.49558,-1.36121 3.01022,-2.86396 3.36585,-3.33945 0.35566,-0.47554 0.65261,-15.43935 0.65987,-33.25294 l 0.0135,-32.38831 -3.62534,-3.70714 -3.62541,-3.70713 -9.25796,0 c -8.72648,0 -9.3816,0.12413 -11.41169,2.16143 -2.6215,2.63083 -4.72354,2.7315 -7.71565,0.36951 -2.77487,-2.19044 -2.99189,-5.74997 -0.51336,-8.4198 2.59844,-2.79906 6.36189,-2.49617 8.71762,0.70151 1.87806,2.54923 2.04437,2.59369 9.69972,2.59369 l 7.78
893,0 -7.52018,-7.58198 c -7.51254,-7.5744 -7.52014,-7.58657 -7.52014,-12.06093 0,-2.84983 -0.42431,-4.73034 -1.1665,-5.17026 -1.44083,-0.854 -3.14102,-4.00537 -3.14102,-5.82192 0,-2.21656 3.25262,-4.81193 6.0305,-4.81193 5.52964,0 7.85996,6.02368 3.87673,10.02105 -1.61646,1.62225 -2.15373,3.07104 -2.15373,5.80807 0,3.53693 0.36292,4.01024 12.06099,15.72738 l 12.06102,12.08069 0,31.78422 c 0,17.48135 0.18859,31.78427 0.41911,31.78427 0.23055,0 1.77075,-1.45897 3.42269,-3.24213 l 3.00353,-3.24209 0.0234,-32.01199 0.0234,-32.01197 -5.83672,-5.81259 c -4.78105,-4.76127 -6.33926,-5.81263 -8.61499,-5.81263 -6.55276,0 -8.49219,-8.17042 -2.68039,-11.2919 4.04942,-2.17486 9.9263,3.57574 7.77568,7.60852 -0.75681,1.41917 0.0472,2.59102 5.52763,8.05398 l 6.41321,6.39316 0,24.21314 0,24.21315 8.59083,-8.58897 8.59079,-8.58901 -6.00631,-6.21634 -6.00631,-6.21632 0,-14.51785 c 0,-13.76403 0.0984,-14.62285 1.89365,-16.54069 l 1.89367,-2.02284 -5.7704,-5.83562 c -3.25191,-3.28867 -5.77039,-6.58913
-5.77039,-7.56213 0,-1.01311 -2.94426,-4.66592 -7.12539,-8.84007 -5.43289,-5.42388 -7.68379,-7.11355 -9.47652,-7.11355 -5.56775,0 -7.70416,-7.16356 -3.14939,-10.56019 4.5597,-3.40033 10.55465,1.38306 8.50707,6.78777 -0.84592,2.23279 -0.53272,2.74571 5.17188,8.47061 l 6.07235,6.09393 0,-43.63128 0,-43.63126 -6.46128,6.4409 c -5.4189,5.40188 -6.46123,6.89771 -6.46123,9.27241 0,5.25918 -5.0277,7.93624 -9.28479,4.94385 -1.92959,-1.35633 -2.63439,-5.74614 -1.25538,-7.81874 1.19971,-1.80308 4.48,-2.97968 6.72838,-2.41336 1.9783,0.49825 3.13993,-0.31666 9.49421,-6.66035 6.13653,-6.12634 7.24009,-7.65928 7.24009,-10.05701 l 0,-2.82892 -8.30083,0 c -7.74763,0 -8.43388,0.14397 -10.29687,2.16141 -4.58993,4.97024 -12.4598,0.0816 -9.68995,-6.01912 1.84662,-4.06729 8.10649,-4.38354 10.17014,-0.51386 0.85944,1.61154 1.75134,1.77788 9.53282,1.77788 8.0301,0 8.58469,-0.11243 8.58469,-1.74112 0,-1.06861 -3.49407,-5.18444 -9.04576,-10.65552 l -9.04574,-8.91441 0,-22.01123 0,-22.01118 -4.55,-4.50942 c
-3.75436,-3.72094 -5.08128,-4.50941 -7.58878,-4.50941 -5.75467,0 -8.40778,-4.65019 -5.07736,-8.89918 3.56927,-4.55376 10.30982,-1.992449 10.33706,3.92795 0.0109,2.19815 1.04158,3.92219 4.73824,7.91985 l 4.72535,5.11001 0,13.87784 0,13.87786 4.3075,-4.25785 4.3075,-4.25779 0,-27.28981 0,-27.289813 -2.58452,-2.275923 c -3.03863,-2.675818 -3.25292,-4.804205 -0.79896,-7.934993 2.18456,-2.787087 6.07346,-3.03667 8.55246,-0.548876 2.38861,2.397093 2.2009,6.164535 -0.43076,8.645619 -1.92866,1.818328 -2.15375,2.713011 -2.15375,8.561097 l 0,6.530559 7.17785,-7.165957 c 6.71199,-6.700813 7.17334,-7.393613 7.1074,-10.67308 -0.14053,-6.993553 7.52654,-9.005663 10.49684,-2.754722 1.31562,2.768648 1.29628,3.047473 -0.36697,5.293059 -1.19754,1.616845 -2.68722,2.45579 -4.6858,2.638879 -2.39372,0.219289 -4.46766,1.798937 -11.32967,8.629398 l -8.39965,8.360968 0,12.964365 0,12.96436 6.9129,-6.89622 c 5.90884,-5.89463 6.84238,-7.21835 6.42766,-9.11432 -1.16039,-5.30495 6.03696,-9.02744 9.91995,-5.1306
6 3.68889,3.70202 1.1495,9.51019 -4.15798,9.51019 -2.89115,0 -3.97178,0.77896 -11.23573,8.09917 -10.46251,10.54352 -10.40647,11.29514 0.8584,11.51469 7.98443,0.15561 8.31175,0.0872 9.40802,-1.9686 3.40289,-6.38093 13.41965,-1.2686 10.42747,5.32193 -1.79268,3.94847 -8.74195,4.51057 -10.15268,0.82123 -0.67459,-1.7642 -18.40801,-1.96086 -18.40801,-0.20416 0,0.60813 -2.51989,3.61205 -5.59973,6.67538 l -5.59974,5.56971 0,5.66005 0,5.66011 9.03868,9.03994 9.03861,9.03989 0.22247,48.23649 0.22247,48.23653 4.0921,-3.93529 4.09216,-3.93536 0,-46.9048 0,-46.90487 -4.17275,-4.12459 c -3.39182,-3.3527 -4.56768,-4.02511 -6.28315,-3.59302 -5.16392,1.30067 -8.9088,-6.06949 -5.05112,-9.94093 3.8878,-3.90159 11.08055,-0.17369 9.91761,5.1402 -0.40275,1.84006 0.26591,2.97245 3.84314,6.50845 l 4.33075,4.28075 0,14.51394 c 0,7.98263 0.26039,14.5139 0.57867,14.5139 0.31827,0 4.58001,-4.04978 9.47057,-8.9995 8.25562,-8.35551 8.85394,-9.19 8.36142,-11.6615 -1.18574,-5.94977 5.72244,-9.58756 10.01882,-5.275
87 1.9627,1.96967 2.15337,3.616 0.73254,6.32493 -1.32934,2.5345 -2.4043,3.15632 -5.48371,3.17203 -2.16747,0.0105 -4.34217,1.7684 -13.13784,10.6166 l -10.54047,10.6034 0,31.56897 0,31.56901 -5.59978,5.56972 c -5.36033,5.33158 -5.59973,5.74903 -5.59973,9.76549 0,3.9189 0.3214,4.53395 4.86939,9.31902 2.67816,2.81776 5.33184,5.12318 5.89699,5.12318 2.60888,0 3.87914,-3.14354 3.87914,-9.59987 l 0,-6.4378 6.46121,-6.44094 6.46127,-6.44094 0,-12.00107 c 0,-11.77138 -0.0413,-12.03361 -2.15376,-13.70125 -2.8583,-2.25632 -2.98459,-6.94451 -0.23905,-8.8744 3.89949,-2.74103 8.22353,-1.14818 9.64929,3.55454 0.41243,1.36023 -0.11857,2.66466 -1.93529,4.75508 -2.48802,2.86268 -2.50459,2.95623 -2.6588,14.98454 l -0.15518,12.10386 -6.42266,6.50332 -6.4226,6.50335 0,6.55889 0,6.5589 -4.30753,4.65297 -4.30747,4.65296 0,13.86628 0,13.86628 5.41753,5.38852 c 5.87138,5.83984 7.77812,5.99871 6.90109,0.57497 -0.36999,-2.28802 -0.0186,-3.30564 1.75452,-5.08537 2.78201,-2.79188 5.22314,-2.82352 7.93426,-0.102
55 1.16299,1.16718 2.11456,2.93561 2.11456,3.92985 0,2.82455 -3.77569,6.05192 -7.08017,6.05192 -2.51053,0 -4.21265,1.36935 -14.25389,11.46703 l -11.40294,11.46704 0,7.33015 0,7.33013 -19.01211,19.09389 -19.01217,19.09386 5.8656,5.71563 c 5.3118,5.17597 6.00034,5.59321 7.29286,4.41937 1.03662,-0.94148 1.30633,-2.20062 0.98552,-4.60103 -0.62092,-4.64569 2.06459,-7.37727 6.53195,-6.64407 3.11807,0.51174 3.2225,0.42525 21.00975,-17.40236 l 17.87613,-17.91663 0,-12.12781 0,-12.12779 8.61499,-8.61316 8.615,-8.61322 0,-6.95769 0,-6.95767 -5.59975,-5.56971 -5.59976,-5.56971 0,-11.57212 c 0,-11.31384 -0.0482,-11.61009 -2.15373,-13.27231 -4.19018,-3.30768 -1.91668,-10.22028 3.36134,-10.22028 6.03693,0 8.20157,7.23392 3.25904,10.89148 -1.0039,0.74291 -1.32253,3.40207 -1.46707,12.24342 l -0.18471,11.29441 5.48457,5.45513 5.48459,5.45511 0,8.25454 0,8.25452 -8.61504,8.61319 -8.61497,8.61316 0,12.11273 0,12.11276 -18.21191,18.29142 c -15.64771,15.71612 -18.24344,18.69458 -18.43629,21.15459 -0.278
54,3.55332 -3.3094,6.26198 -6.35344,5.67801 -1.86276,-0.35735 -4.59247,2.02692 -20.06768,17.52799 l -17.91165,17.94163 0,10.8421 c 0,10.73228 0.0209,10.84737 2.07371,11.36443 1.14054,0.28726 2.69122,0.76009 3.44602,1.05078 1.17553,0.4527 1.37227,-0.34678 1.37227,-5.57609 l 0,-6.10458 6.45367,-6.74023 c 5.51585,-5.7607 6.45478,-7.18057 6.46126,-9.77023 0.007,-2.83378 2.23962,-5.26951 34.46754,-37.6044 32.37097,-32.4784 34.45998,-34.75956 34.45998,-37.62936 0,-2.61212 0.81119,-3.86179 5.59571,-8.62063 6.84254,-6.80584 7.96352,-10.23367 4.42381,-13.52749 -4.1027,-3.8178 -1.96023,-10.6843 3.33371,-10.6843 5.23005,0 7.47329,6.98568 3.446,10.73131 -1.48656,1.38257 -2.15376,2.917 -2.15376,4.95312 0,2.50406 -0.91159,3.85785 -6.03046,8.95557 -5.42061,5.39819 -6.03052,6.3479 -6.03052,9.39071 0,3.3033 -0.83286,4.22242 -34.46,38.02891 -32.4744,32.64765 -34.45993,34.8214 -34.45993,37.7264 0,2.68718 -0.82876,3.9087 -6.46129,9.52352 l -6.46122,6.44093 0,6.11604 0,6.11601 2.86101,1.03824 c 1.57362,
0.57103 3.13416,1.03825 3.46793,1.03825 0.33381,0 0.53564,-2.18822 0.44854,-4.86273 l -0.15834,-4.86275 6.59768,-6.4937 c 5.4098,-5.32458 6.59957,-6.97853 6.60829,-9.1864 0.007,-2.05235 1.0358,-3.75487 4.32175,-7.15951 4.01486,-4.1598 4.2407,-4.62203 3.2858,-6.72533 -2.30167,-5.06954 5.53624,-9.75684 9.56104,-5.71775 3.57526,3.58796 1.09683,9.62374 -4.05858,9.88391 -1.97404,0.0998 -3.82668,1.21007 -6.63727,3.97826 -3.18024,3.13227 -3.89886,4.40842 -3.89886,6.92379 0,2.68861 -0.82785,3.90897 -6.46126,9.5247 l -6.46125,6.44091 0,4.90798 c 0,4.59419 0.17894,5.0222 2.79988,6.69526 l 2.79989,1.78731 0.26476,-2.78311 c 0.22169,-2.33029 3.30147,-5.8399 18.92888,-21.57047 l 18.66408,-18.78736 -3.20649,-3.29544 c -2.16336,-2.2233 -3.20651,-4.08688 -3.20651,-5.72822 0,-1.56523 -0.76806,-3.03907 -2.15375,-4.13294 -2.70526,-2.13554 -2.85854,-6.0547 -0.33581,-8.58643 1.43848,-1.4436 2.47783,-1.71848 4.97944,-1.31706 l 3.16148,0.50736 24.29312,-24.40366 24.29311,-24.40369 0.1238,-7.19192 c 0.1065
8,-6.20715 0.40427,-7.52646 2.1722,-9.63498 1.72334,-2.05535 2.04838,-3.38883 2.04838,-8.40269 l 0,-5.95965 -4.30747,-4.2578 -4.30749,-4.25778 0,-11.30432 0,-11.30431 -4.3075,-4.25781 -4.30749,-4.25783 0,-14.94617 c 0,-12.70914 -0.19344,-14.94619 -1.29224,-14.94619 -2.01499,0 -4.73826,-3.22986 -4.73826,-5.61967 0,-5.01437 6.81962,-7.48127 10.30444,-3.72748 2.1092,2.272 2.24066,4.81056 0.38656,7.46702 -1.13136,1.62099 -1.35106,4.47777 -1.26131,16.40288 l 0.10851,14.44005 2.55363,2.57709 2.55354,2.57705 0,-22.92136 0,-22.92132 -5.16903,-5.13319 c -5.08574,-5.05062 -5.16896,-5.21073 -5.16896,-9.94497 0,-4.68674 0.12299,-4.93361 4.73825,-9.50771 l 4.73823,-4.69602 0,-5.82063 0,-5.82066 -6.61209,-6.5289 c -4.75251,-4.69267 -7.23601,-6.53261 -8.83039,-6.54213 -2.72214,-0.0178 -3.81045,-0.70515 -5.15227,-3.26149 -2.98052,-5.67845 4.88154,-10.76519 9.4902,-6.14018 1.44171,1.44686 1.71269,2.48471 1.30948,5.01539 -0.47907,3.00659 -0.2107,3.48671 4.64329,8.30714 l 5.15178,5.11613 0,-27.66841 0
,-27.66847 4.64384,-4.60241 c 4.36474,-4.32583 4.61664,-4.80595 4.19146,-7.98711 -0.57146,-4.27567 1.42757,-6.80406 5.37945,-6.80406 3.38541,0 5.59974,2.28164 5.59974,5.76991 0,3.12266 -2.31292,5.46943 -5.39058,5.46943 -1.50888,0 -3.80591,1.46604 -7.03954,4.49299 l -4.79989,4.49301 0,20.78425 0,20.7842 12.49177,-12.51375 12.49175,-12.51379 0,-5.50011 c 0,-4.99644 -0.23668,-5.72196 -2.58448,-7.92295 -3.11552,-2.92069 -3.32392,-5.91797 -0.59063,-8.49493 2.55246,-2.40645 6.06994,-2.40126 8.31053,0.013 2.35874,2.54081 2.21372,5.50871 -0.39718,8.12893 -1.74193,1.74811 -2.15377,3.01613 -2.15377,6.63152 l 0,4.4701 4.73826,-4.69601 4.73825,-4.69602 0,-6.11101 0,-6.11101 4.74348,-4.70115 4.74344,-4.70119 -6.55771,-6.61985 c -4.72468,-4.76946 -7.00996,-6.50114 -8.17562,-6.19523 -2.96612,0.77837 -5.70161,-0.5316 -6.83276,-3.27216 -0.96759,-2.34427 -0.89374,-2.93519 0.62264,-4.98221 2.45969,-3.3204 7.36433,-3.68366 9.31879,-0.69021 0.77663,1.18947 1.28457,3.14677 1.12878,4.3495 -0.22575,1.74288
1.02396,3.50765 6.15636,8.69339 l 6.43967,6.50653 5.83922,-5.81507 c 5.24908,-5.2273 5.78937,-6.08164 5.34614,-8.45265 -1.07992,-5.77687 5.55373,-9.55546 9.80211,-5.58345 3.90647,3.6524 1.1946,9.65884 -4.44976,9.85554 -2.86502,0.0999 -4.26124,1.19963 -14.35694,11.30871 l -11.18332,11.19815 0,6.27005 0,6.27008 -19.81449,19.88732 -19.81453,19.88726 0,12.78995 0,12.78992 -4.73822,4.69602 c -4.24735,4.20952 -4.73822,5.07193 -4.73822,8.32451 0,3.27304 0.50631,4.14457 5.16897,8.89699 l 5.169,5.26858 0,31.55074 c 0,17.35295 0.31163,31.5508 0.69252,31.5508 0.38087,0 1.93156,-1.28292 3.44596,-2.85099 l 2.75352,-2.85099 0,-33.46061 c 0,-28.58212 -0.18132,-33.4606 -1.24342,-33.4606 -1.73919,0 -3.92558,-3.4032 -3.92558,-6.11031 0,-2.93077 2.46417,-5.12899 5.74945,-5.12899 5.13614,0 7.45981,5.95496 3.83471,9.82744 -1.8019,1.92486 -1.83067,2.48486 -1.83067,35.6473 l 0,33.69173 -4.73824,4.69602 c -6.10973,6.05529 -6.1637,7.73393 -0.43074,13.40071 l 4.30749,4.25782 0,7.89569 c 0,6.79772 -0.2396,8.
11331 -1.723,9.46054 -1.45875,1.32482 -1.72303,2.66282 -1.72303,8.72369 l 0,7.15887 -25.44373,25.50804 c -22.78166,22.83919 -25.387,25.73511 -24.90099,27.67839 0.40981,1.63855 -0.005,2.72018 -1.69348,4.41455 -3.02372,3.03447 -2.92295,6.36324 0.27792,9.18193 l 2.51417,2.214 24.27318,-24.37074 c 23.50492,-23.59937 24.28486,-24.49097 24.64169,-28.16999 0.31676,-3.26625 1.09115,-4.53109 5.51887,-9.01439 l 5.15039,-5.21507 0,-20.56591 0,-20.56589 -2.58449,-2.18243 c -3.35352,-2.83183 -3.47935,-6.38428 -0.31456,-8.8825 2.80164,-2.21162 4.71214,-2.26363 7.25467,-0.19745 2.6187,2.128 2.66862,6.74649 0.0956,8.83748 -1.81568,1.47546 -1.86661,2.05457 -1.86661,21.22706 l 0,19.7102 3.87674,-3.81827 3.87676,-3.81826 0,-12.75499 0,-12.755 3.446,-3.42388 c 3.30325,-3.282 3.44599,-3.6522 3.44599,-8.93585 0,-5.29148 -0.13797,-5.64708 -3.44599,-8.88889 -3.27524,-3.20956 -3.446,-3.63794 -3.446,-8.64558 0,-5.00383 -0.17248,-5.43782 -3.43087,-8.63085 -1.88701,-1.84916 -4.2897,-3.4936 -5.3393,-3.65434 -4.
66493,-0.71447 -6.81186,-6.99564 -3.43837,-10.05946 2.09109,-1.89913 6.42172,-1.81977 8.19825,0.15021 1.58205,1.75434 1.9082,6.23462 0.55287,7.59476 -0.58953,0.59162 -0.1701,1.53862 1.29228,2.91729 l 2.16514,2.04129 0,-26.31845 0,-26.31846 -9.47652,-9.48066 -9.47645,-9.4807 0,-4.94668 c 0,-4.12369 -0.35833,-5.30627 -2.15376,-7.10807 -2.62155,-2.63083 -2.72186,-4.74032 -0.36826,-7.74306 2.26537,-2.89018 5.84564,-3.0082 8.40233,-0.27704 2.45258,2.61996 2.37152,5.35854 -0.23612,7.97544 -1.66389,1.66981 -2.15026,3.13455 -2.30359,6.93714 l -0.19428,4.82035 7.90328,7.93144 7.90337,7.93142 0,-9.83749 c 0,-9.38277 -0.0995,-9.93004 -2.15374,-11.8406 -2.36786,-2.20224 -2.98705,-7.25682 -1.07689,-8.79049 2.17768,-1.74842 4.98595,-2.05126 7.47339,-0.80594 3.54451,1.77449 4.37222,0.51007 4.37222,-6.67883 0,-5.53193 -0.20111,-6.2236 -2.29732,-7.90104 -1.63092,-1.30504 -2.29733,-2.64311 -2.29733,-4.61257 0,-5.11335 5.1311,-7.58348 9.15483,-4.40715 3.05972,2.41537 3.13547,6.23774 0.17799,8.98831 -1
.70046,1.58156 -2.15373,2.84647 -2.15373,6.01066 0,2.20412 0.30901,4.00747 0.68665,4.00747 0.37767,0 2.12223,-1.47532 3.87677,-3.27852 3.10316,-3.18924 3.19006,-3.45459 3.19006,-9.74041 l 0,-6.46189 9.90725,-9.91425 9.90725,-9.91423 0,-4.67009 c 0,-3.87086 -0.36857,-5.01284 -2.15375,-6.67316 -2.93097,-2.72599 -2.88097,-6.5735 0.11615,-8.93943 1.24849,-0.98553 2.74689,-1.7919 3.32981,-1.7919 0.58296,0 2.08136,0.80637 3.32984,1.7919 3.11042,2.45533 3.16956,7.08687 0.11615,9.09466 -1.92897,1.26839 -2.15376,1.99836 -2.15376,6.99411 0,5.18771 -0.21041,5.80073 -3.00775,8.76234 -2.64786,2.8033 -3.00866,3.72757 -3.01527,7.72335 l -0.008,4.53894 8.84145,0 c 8.32448,0 8.89121,-0.11302 9.69188,-1.93303 0.46773,-1.06322 1.91729,-2.30572 3.22126,-2.76121 5.31099,-1.85513 9.8786,5.80196 5.86416,9.83065 -2.25962,2.26769 -5.23256,2.26662 -8.10768,-9.5e-4 -1.89067,-1.49248 -3.62939,-1.80892 -10.66954,-1.94183 l -8.41078,-0.15874 -0.25869,3.54304 c -0.2186,2.99477 0.0813,3.76663 1.93839,4.98775 3.911
3,2.57191 2.6389,8.97001 -2.083,10.47398 -1.73084,0.55131 -5.13501,-0.80718 -6.26011,-2.49821 -1.36174,-2.04658 -0.62452,-5.82131 1.5279,-7.82318 2.07877,-1.93341 2.17483,-2.42828 1.93836,-9.98283 l -0.24884,-7.94855 -5.38439,5.40733 -5.38436,5.40734 0,6.14881 0,6.14884 -7.32274,7.31022 c -4.02749,4.02085 -7.32276,8.01863 -7.32276,8.88393 0,0.86539 -0.96916,2.47479 -2.15374,3.57651 -2.14047,1.99075 -2.15375,2.0972 -2.15375,17.24992 0,8.38579 0.28103,15.24688 0.62452,15.24688 0.34347,0 3.22849,-2.59323 6.41119,-5.76275 4.88612,-4.86593 5.71637,-6.11553 5.33495,-8.02952 -1.08046,-5.42141 6.64264,-8.77421 10.16259,-4.41185 3.64142,4.51297 0.46466,10.0987 -5.302,9.32249 -3.11294,-0.41903 -3.52866,-0.15038 -10.23797,6.61803 l -6.99328,7.0548 0,15.52088 0,15.52085 6.033,-6.00806 c 5.5517,-5.52871 5.9915,-6.23005 5.51284,-8.79067 -1.04507,-5.59022 6.14064,-9.42123 9.95809,-5.30909 2.37249,2.55568 2.2104,5.51209 -0.44862,8.18055 -1.60364,1.60932 -2.81796,2.09547 -4.4516,1.78205 -1.91368,-0.
36714 -3.30973,0.64821 -9.42507,6.85476 l -7.17864,7.28571 0,9.30554 0,9.30554 3.44599,3.37688 3.44602,3.37692 0,6.99783 0,6.99786 -3.44602,3.37687 -3.44599,3.37688 0,10.82608 0,10.82605 6.46125,-6.44094 6.46122,-6.44092 0,-21.21689 0,-21.21688 10.33802,-10.34774 10.33801,-10.34774 0,-13.72208 c 0,-13.60711 -0.0185,-13.73631 -2.15376,-15.42221 -2.15418,-1.70051 -2.93845,-5.97533 -1.49445,-8.1456 1.11007,-1.66833 4.33196,-2.95196 5.95491,-2.37247 4.92818,1.75964 6.11845,6.73543 2.43155,10.16451 -1.9434,1.80747 -2.15378,2.66317 -2.15378,8.76076 l 0,6.75766 6.78286,-6.42452 6.78286,-6.42448 0.10889,-9.81805 c 0.10573,-9.50351 0.0403,-9.87251 -2.0446,-11.5182 -4.21006,-3.32342 -1.68405,-10.22027 3.7432,-10.22027 1.34808,0 3.09263,0.71148 3.87677,1.58105 2.60863,2.89272 1.41033,8.65475 -2.03987,9.80891 -0.57879,0.19363 -0.82043,3.2334 -0.66152,8.3212 l 0.25042,8.01612 9.90724,-9.98198 c 8.82739,-8.89393 9.87693,-10.26982 9.62933,-12.62272 -0.41849,-3.97592 1.85018,-6.8517 5.40522,-6.8517
4.67486,0 7.56654,4.14212 5.56018,7.96454 -1.37815,2.62562 -2.42867,3.2457 -5.5255,3.2615 -2.22456,0.013 -5.36593,2.7995 -22.18365,19.68893 l -19.59209,19.67568 0,5.32176 0,5.32179 -10.33797,10.38321 -10.33799,10.38323 0,10.19792 0,10.19789 5.169,-5.13324 c 4.20313,-4.17407 5.16899,-5.65705 5.16899,-7.93647 0,-2.89803 2.68732,-6.32879 4.95731,-6.32879 1.52056,0 4.76306,1.7071 5.58287,2.93922 1.23252,1.85245 0.63494,5.73083 -1.16065,7.53283 -1.3668,1.37164 -2.51345,1.71352 -4.60477,1.37295 -2.52224,-0.41079 -3.366,0.11265 -8.94878,5.5512 l -6.16397,6.0047 0,7.09565 0,7.09564 17.23001,-17.27499 17.22997,-17.27504 0,-8.28166 c 0,-7.75628 -0.1367,-8.40092 -2.15375,-10.16101 -5.23424,-4.56729 0.26652,-12.4543 6.44861,-9.24606 4.20968,2.18463 4.42255,7.81183 0.37769,9.98427 -1.94449,1.04437 -2.08807,1.60033 -2.08807,8.08774 l 0,6.96626 6.892,-6.87592 6.89203,-6.87591 0,-4.88572 c 0,-3.28804 0.42251,-5.23757 1.29223,-5.96191 0.98246,-0.81825 1.29225,-3.04202 1.29225,-9.27547 0,-7.71604 -0
.12691,-8.29938 -2.15374,-9.89936 -4.03429,-3.1846 -1.72775,-10.22027 3.35055,-10.22027 2.56078,0 5.69541,2.4614 6.05707,4.75622 0.31586,2.004 -1.73507,6.48309 -2.96849,6.48309 -0.47252,0 -0.83939,2.92425 -0.83939,6.69083 l 0,6.69082 14.64548,-14.67852 14.64552,-14.67842 0,-8.96302 c 0,-8.5308 -0.10393,-9.045 -2.15376,-10.66319 -3.02261,-2.38603 -2.96668,-6.85937 0.11617,-9.29298 2.83985,-2.24177 3.81979,-2.24177 6.65966,0 3.08282,2.43361 3.13879,6.90695 0.11615,9.29298 -1.98084,1.56369 -2.15374,2.26267 -2.15374,8.70839 0,3.85448 0.28032,7.00819 0.62293,7.00819 0.34261,0 3.01364,-2.37787 5.9356,-5.28413 5.05282,-5.02573 5.29056,-5.44948 4.86078,-8.66502 -0.57096,-4.27173 1.42953,-6.80033 5.37995,-6.80033 3.3854,0 5.59971,2.28161 5.59971,5.76986 0,3.21901 -2.33479,5.46945 -5.67459,5.46945 -2.14273,0 -5.48118,2.97006 -22.11578,19.67566 -10.77565,10.82162 -19.59208,19.95868 -19.59208,20.30457 0,0.34588 1.81747,2.40157 4.03886,4.56823 l 4.03886,3.93933 7.59141,-7.58305 c 6.82859,-6.8211
4 7.59136,-7.90571 7.59136,-10.79418 0,-2.33818 0.52051,-3.57695 1.91468,-4.55702 4.09765,-2.88027 9.29396,-0.57866 9.27161,4.1067 -0.007,1.51846 -0.29799,3.18889 -0.64611,3.71211 -1.13867,1.71135 -4.63025,3.09631 -6.57268,2.60707 -1.5342,-0.38644 -3.37845,1.00894 -9.52778,7.20884 l -7.62529,7.68803 3.26731,3.0924 3.26727,3.09236 15.90862,0 15.90862,0 1.69416,-2.16138 c 3.44535,-4.39562 10.18406,-2.00793 10.18406,3.60846 0,5.3252 -6.93174,7.5904 -10.05604,3.28618 l -1.58478,-2.18328 -14.64019,0.23799 -14.64022,0.23801 2.49494,2.37754 c 1.70355,1.62346 3.41141,2.37753 5.38435,2.37753 5.81678,0 8.29228,5.89592 4.19989,10.00283 -1.64859,1.65447 -2.67994,2.01899 -4.52289,1.5986 -3.53343,-0.80599 -5.6533,-3.66373 -5.12793,-6.91279 0.40731,-2.51886 -0.39607,-3.53425 -11.96743,-15.12605 l -12.40408,-12.4259 -3.88928,3.8306 c -3.75984,3.70313 -3.8893,4.00427 -3.8893,9.04659 l 0,5.21596 -17.86842,17.94721 -17.86844,17.94722 9.48677,0 9.48676,0 7.61192,-7.67454 c 5.97815,-6.02731 7.49502,-8.0
4401 7.06748,-9.39599 -1.44359,-4.56445 3.87822,-8.98639 8.20793,-6.82011 2.93199,1.46695 4.06026,5.25294 2.43512,8.17123 -1.05298,1.89086 -2.00151,2.3673 -5.14055,2.58208 -3.44951,0.23606 -4.45333,0.86223 -9.66444,6.02877 -3.19834,3.17093 -5.81509,6.06752 -5.81509,6.43695 0,0.36938 2.69609,0.67161 5.99129,0.67161 5.24212,0 6.26067,-0.27033 8.14509,-2.16141 2.6265,-2.63582 5.57085,-2.76339 8.14882,-0.35311 4.21451,3.94037 -0.29224,11.6912 -5.69966,9.80239 -1.30395,-0.45544 -2.75351,-1.69796 -3.22122,-2.76116 -0.8441,-1.91857 -0.99594,-1.93304 -20.25537,-1.93304 l -19.40489,0 -13.76383,13.83297 -13.76381,13.83299 15.71229,0 15.71225,0 0,-4.43983 c 0,-3.62523 -0.39511,-4.80729 -2.15374,-6.44291 -2.28582,-2.12597 -2.7168,-4.60176 -1.27249,-7.31008 2.22385,-4.17005 7.77172,-4.06895 9.96196,0.18153 1.56491,3.03696 1.00437,5.27035 -1.89774,7.56124 -1.85031,1.46064 -2.11507,2.2823 -1.88745,5.85836 l 0.26471,4.15942 17.62522,0.23459 17.6252,0.23466 7.47797,-7.4698 c 7.20119,-7.19333 7.46191
,-7.59027 7.04288,-10.72514 -0.75247,-5.63005 4.42765,-8.81745 9.04979,-5.56848 1.92954,1.35636 2.63438,5.74615 1.25538,7.81874 -1.0627,1.59715 -4.5665,3.10241 -5.96657,2.56323 -0.88043,-0.33904 -3.44865,1.6317 -7.68619,5.8981 -3.50015,3.52404 -6.36394,6.64112 -6.36394,6.92686 0,0.28574 2.75205,0.51951 6.11559,0.51951 5.6843,0 6.28589,-0.18294 8.5299,-2.59368 2.93064,-3.14841 5.41418,-3.29081 8.22341,-0.47157 2.68325,2.69277 2.68325,5.16685 0,7.85966 -2.72671,2.73638 -5.9928,2.72108 -8.38444,-0.0391 l -1.87267,-2.16141 -20.72593,0 -20.7259,0 13.76381,13.83296 c 11.87549,11.93517 14.131,13.83298 16.44027,13.83298 5.03808,0 7.39971,5.90267 3.80495,9.5102 -1.7138,1.71987 -5.59995,2.27975 -7.40915,1.06745 -1.82093,-1.22021 -3.03932,-4.64639 -2.51087,-7.06085 0.48999,-2.23903 -0.61878,-3.58415 -13.85518,-16.80828 l -14.38783,-14.37447 -22.06799,0 -22.06803,0 -10.54045,10.60334 c -9.69294,9.7508 -10.54045,10.86169 -10.54045,13.81601 0,2.85446 -0.72035,3.92387 -6.46123,9.59251 -3.55368,3.5
0892 -6.46163,6.53172 -6.46205,6.71733 -4.4e-4,0.18561 3.9732,0.27463 8.83036,0.19782 l 8.83118,-0.1397 9.21465,-8.93821 c 8.69376,-8.43303 9.18747,-9.0973 8.73378,-11.75189 -0.69227,-4.05069 1.5239,-7.13271 5.11654,-7.11561 1.529,0.008 3.20658,0.29907 3.72796,0.64844 1.22775,0.82271 2.92881,4.07674 2.92881,5.6027 0,2.27383 -3.41862,4.97494 -6.29647,4.97494 -2.3646,0 -3.97951,1.20546 -10.73516,8.01346 -4.37352,4.40738 -7.9524,8.20063 -7.95306,8.42945 -7e-4,0.2288 5.7175,0.29576 12.70708,0.14875 l 12.70835,-0.2673 6.60273,-6.55514 c 5.97986,-5.93684 6.5614,-6.81062 6.16485,-9.26288 -0.52554,-3.25017 1.60968,-6.21643 5.06519,-7.03648 1.79662,-0.42639 2.82566,-0.0503 4.52287,1.65294 2.67608,2.68561 2.77433,4.77214 0.36917,7.84071 -1.36308,1.73904 -2.45687,2.26589 -4.30109,2.07177 -2.09711,-0.22078 -3.65472,0.89875 -10.24372,7.36258 l -7.77057,7.62292 -6.45257,-0.28406 c -3.54893,-0.15625 -6.45173,-0.0761 -6.4506,0.17781 8.7e-4,0.25402 2.04949,2.4911 4.55198,4.97123 l 4.54995,4.50946 20
.86426,0 20.86427,0 0,-8.30508 c 0,-7.82899 -0.12341,-8.40253 -2.15373,-10.00521 -4.38007,-3.4576 -2.00087,-10.22029 3.59572,-10.22029 5.30671,0 7.52609,6.8813 3.29625,10.22029 -2.03027,1.60268 -2.15371,2.17622 -2.15371,10.00521 l 0,8.30508 19.20074,0 19.20077,0 4.05971,-4.13639 4.05976,-4.13637 0,-8.3605 c 0,-7.76509 -0.15342,-8.51439 -2.15376,-10.52185 -2.59717,-2.60644 -2.77054,-5.94384 -0.43074,-8.29197 2.50372,-2.51265 6.36794,-2.23813 8.60113,0.61101 2.22889,2.84359 1.74819,5.952 -1.31432,8.49937 -1.92457,1.60085 -2.11783,2.3887 -2.11783,8.63312 l 0,6.87159 11.2003,-11.21515 c 10.69568,-10.70992 11.17653,-11.34248 10.67216,-14.04053 -0.81095,-4.3381 1.41295,-7.14045 5.66651,-7.14045 2.51339,0 3.72038,0.49149 4.71839,1.92147 2.85114,4.08507 0.32647,9.31783 -4.49556,9.31783 -1.83023,0 -3.89527,1.52007 -8.80879,6.48421 l -6.41814,6.48423 15.27005,0 c 12.21703,0 15.27007,-0.23249 15.27007,-1.16287 0,-1.83596 3.17533,-4.24065 5.59973,-4.24065 4.90056,0 7.61554,6.76303 4.0243,10.024
61 -2.16697,1.96805 -6.42551,1.8156 -8.33179,-0.29827 -1.45571,-1.61426 -2.65338,-1.72915 -18.02667,-1.72915 l -16.46737,0 -8.15017,8.21333 -8.15022,8.21338 7.27335,0 c 6.67501,0 7.41777,-0.1748 9.02875,-2.12536 2.71672,-3.28937 5.96109,-3.67503 8.60399,-1.02276 2.73152,2.74127 2.80577,5.7311 0.20319,8.18475 -2.63934,2.48838 -5.85256,2.37994 -8.34407,-0.28159 -1.42427,-1.52143 -2.93252,-2.1614 -5.09401,-2.1614 l -3.07067,0 14.08246,14.15201 c 13.51229,13.57908 14.21754,14.14847 17.4198,14.0642 5.65284,-0.14878 8.4452,4.91766 5.07805,9.21354 -3.55155,4.53109 -10.32416,1.84154 -10.32416,-4.09999 0,-2.28932 -2.11145,-4.77235 -15.30035,-17.99277 l -15.30037,-15.33699 -18.06371,0 -18.06373,0 7.23614,7.29981 c 5.89869,5.95059 7.51574,7.14964 8.74895,6.48732 3.1716,-1.70343 7.6681,1.61881 7.6681,5.66552 0,3.42422 -2.27922,5.61966 -5.83413,5.61966 -3.07592,0 -5.36404,-2.56968 -5.36765,-6.02816 -9.8e-4,-1.5887 -2.23032,-4.48625 -8.16427,-10.61466 l -8.16199,-8.42949 -17.01438,0 -17.01438,0 6
.8718,6.9662 c 5.97358,6.05566 7.17898,6.9065 9.22222,6.5096 1.66701,-0.32386 3.01348,0.11594 4.63059,1.51158 4.32521,3.73364 2.04562,10.08493 -3.61971,10.08493 -3.37422,0 -5.51505,-2.52511 -5.51505,-6.50493 0,-2.17901 -1.3932,-4.10467 -7.67943,-10.61466 l -7.67946,-7.95272 -7.20454,0 -7.20454,0 -5.98428,-6.05192 -5.98427,-6.05195 -17.68388,0.0143 -17.68384,0.0143 -4.09213,4.22255 c -2.25066,2.32242 -4.0921,4.4562 -4.0921,4.7417 0,0.28552 3.34767,0.51914 7.43932,0.51914 6.82867,0 7.60318,-0.17748 9.43537,-2.16141 1.96481,-2.12759 5.73942,-2.85668 7.76445,-1.49976 1.78004,1.19282 2.97129,4.48725 2.41986,6.69218 -1.0848,4.33752 -7.89159,5.47256 -10.33898,1.72409 -1.36366,-2.08862 -1.73027,-2.16143 -10.88473,-2.16143 l -9.47354,0 -29.50352,29.61125 c -28.02579,28.12812 -29.5272,29.79624 -29.97693,33.30469 -0.43103,3.36267 -0.26423,3.80193 1.86246,4.90562 1.28479,0.66674 5.83245,3.92409 10.10593,7.23864 21.84908,16.94607 36.45539,41.07943 40.90928,67.59251 1.43719,8.55529 1.42841,32.430
78 -0.0148,40.63442 -3.70617,21.0648 -13.04052,38.52197 -30.22621,56.52935 -9.06735,9.50093 -17.9142,16.27707 -44.46324,34.05605 -11.42005,7.64761 -21.8444,15.19363 -23.16528,16.769 -4.05531,4.83661 -5.81647,9.96703 -6.26862,18.26152 -0.50394,9.24328 1.09005,16.6345 6.00471,27.8438 4.49251,10.24646 4.48344,10.20395 2.45137,11.4601 -1.56594,0.96806 -2.86775,0.33724 -12.0429,-5.83578 -5.65794,-3.8066 -10.48821,-6.92117 -10.73398,-6.92117 -0.24568,0 -0.68099,1.45896 -0.96731,3.24211 -2.64055,16.44472 -7.73387,34.75545 -13.88806,49.92845 -3.65741,9.01703 -15.0297,32.63973 -15.49058,32.17724 -0.11869,-0.11917 2.01848,-9.06125 4.74936,-19.87132 2.73086,-10.81008 4.85344,-19.65467 4.71683,-19.65467 -0.13667,0 -1.289,1.84799 -2.56085,4.10667 -12.63508,22.43864 -33.44794,44.81774 -59.35743,63.82429 -3.89577,2.85788 -3.5079,2.20453 3.88695,-6.54707 16.39722,-19.40579 32.54031,-42.50417 38.67067,-55.33198 l 1.85926,-3.89054 -2.33075,3.026 c -1.28193,1.66427 -6.42977,7.11821 -11.43968,12.11989
-9.7947,9.7786 -23.12234,19.35952 -34.52245,24.8174 -5.87996,2.81506 -22.23956,9.74902 -23.00132,9.74902 -0.17398,0 7.1172,-7.29474 16.20303,-16.21052 27.50911,-26.99429 53.50756,-58.28039 69.96461,-84.19426 l 3.09324,-4.87069 -2.52176,-0.50617 c -1.38697,-0.27837 -5.05294,-0.76639 -8.14663,-1.08446 -3.09368,-0.31809 -5.61711,-0.81639 -5.60756,-1.10737 0.0109,-0.29096 0.36586,-2.94748 0.79195,-5.90335 2.3746,-16.47479 -7.97241,-32.53751 -28.53888,-44.30377 -13.47086,-7.70677 -25.70388,-15.67882 -33.91157,-22.09953 -43.81698,-34.27714 -57.31313,-95.65209 -31.63484,-143.86216 8.75002,-16.42786 25.38192,-34.00435 39.94298,-42.21154 3.30327,-1.86182 4.22597,-2.8547 4.22665,-4.54786 7.3e-4,-1.69439 -6.51828,-8.70631 -29.94078,-32.2049 l -29.94164,-30.03891 -9.34796,0 c -8.94506,0 -9.42098,0.0933 -11.04209,2.16143 -3.44538,4.39564 -10.1841,2.00795 -10.1841,-3.60847 0,-5.32561 6.8569,-7.5529 10.1841,-3.30804 1.59115,2.03004 2.18349,2.16141 9.74501,2.16141 l 8.0509,0 -4.80064,-4.81771 -4.80
068,-4.81775 -17.46506,0.0881 -17.4651,0.0884 -6.11316,6.02643 -6.11317,6.02644 -6.95022,0 -6.95022,0 -7.95186,8.01346 c -7.05726,7.11189 -7.95184,8.36127 -7.95184,11.10531 0,3.77281 -2.137,5.95353 -5.83414,5.95353 -1.79609,0 -3.12656,-0.63527 -4.02427,-1.92149 -3.13443,-4.49092 -0.36976,-9.73091 5.05264,-9.57666 2.48128,0.0705 3.77999,-0.83903 9.63177,-6.74628 l 6.76383,-6.82787 -16.79523,0 -16.79521,0 -8.36223,8.42949 c -6.47771,6.52978 -8.2581,8.82647 -7.9001,10.19102 0.27918,1.06413 -0.26564,2.69006 -1.37598,4.10669 -4.7119,6.01146 -13.43495,-0.59981 -9.06066,-6.86714 1.40432,-2.01209 4.79511,-2.99393 6.68119,-1.93466 0.86466,0.48559 3.33402,-1.44599 8.46274,-6.61986 l 7.24185,-7.30554 -18.06374,0 -18.06373,0 -15.30035,15.33696 c -13.57276,13.60523 -15.30039,15.65524 -15.30039,18.15583 0,5.2465 -5.03325,7.91969 -9.28476,4.93121 -1.9296,-1.35634 -2.6344,-5.74614 -1.25537,-7.81878 1.16992,-1.75834 4.45715,-2.98541 6.57122,-2.4529 1.82623,0.45998 3.88574,-1.27053 15.95541,-13.40644
7.62219,-7.66403 13.85852,-14.11711 13.85852,-14.34026 0,-0.22311 -1.2155,-0.40562 -2.70107,-0.40562 -1.77457,0 -3.38579,0.74142 -4.69711,2.1614 -2.71636,2.94138 -6.55021,2.89123 -8.90777,-0.11654 -0.98206,-1.25291 -1.78552,-2.96056 -1.78552,-3.79477 0,-2.20794 2.65739,-5.00787 5.36267,-5.65024 1.88964,-0.44867 2.86866,-0.0342 5.01989,2.12456 2.53922,2.5482 3.01277,2.68193 9.49817,2.68193 3.75415,0 6.82574,-0.29991 6.82574,-0.66642 0,-0.36656 -3.39983,-4.06257 -7.55517,-8.21334 l -7.55514,-7.54693 -16.86408,0 c -15.76995,0 -16.96521,0.11207 -18.42335,1.72914 -2.17876,2.41603 -6.81831,2.3119 -8.56615,-0.19237 -4.68201,-6.70828 4.28251,-13.30826 9.13312,-6.72412 l 1.91085,2.59369 15.12957,-0.008 15.12962,-0.007 -6.46127,-6.45034 c -5.28219,-5.27328 -6.96092,-6.45519 -9.19958,-6.47689 -4.93405,-0.0481 -7.39038,-4.40414 -4.88417,-8.66186 1.27226,-2.16146 2.08344,-2.604 4.77316,-2.604 4.10994,0 6.32882,2.86527 5.52962,7.14044 -0.50435,2.69802 -0.0234,3.33059 10.67214,14.04048 l 11.20032
,11.2144 0,-7.55248 c 0,-4.15383 -0.35999,-7.5524 -0.79995,-7.5524 -3.55549,0 -4.78686,-7.6483 -1.59286,-9.89346 6.26147,-4.40124 12.71257,3.76668 7.13107,9.02889 -2.00767,1.89281 -2.15373,2.59429 -2.15373,10.34182 l 0,8.31125 4.12168,4.07417 4.12172,4.07416 19.1388,0 19.13876,0 0,-8.17403 c 0,-7.56624 -0.16022,-8.33475 -2.15375,-10.33544 -2.59719,-2.6064 -2.77053,-5.94381 -0.43075,-8.29193 1.7138,-1.7199 5.59993,-2.27979 7.40914,-1.06745 0.52139,0.34933 1.42609,1.54582 2.01051,2.65886 1.3214,2.51653 -0.14678,6.72818 -2.62031,7.51597 -1.45532,0.46359 -1.63035,1.44128 -1.63035,9.10668 l 0,8.58734 20.92081,0 20.92078,0 4.49344,-4.56611 c 2.47138,-2.5114 4.49344,-4.76116 4.49344,-4.9995 0,-0.23836 -2.7566,-0.27706 -6.12575,-0.0861 l -6.12573,0.34738 -7.61582,-7.60745 c -6.73911,-6.73175 -7.98122,-7.6144 -10.79012,-7.6679 -2.41565,-0.0461 -3.59722,-0.62696 -4.94363,-2.43086 -1.67763,-2.24768 -1.69736,-2.5218 -0.38056,-5.29307 2.97064,-6.25154 10.63751,-4.23873 10.4968,2.7558 -0.0656,3.2
5499 0.40068,3.97775 6.46124,10.01775 l 6.53184,6.50969 12.70716,0.24209 c 6.98896,0.13308 12.70718,0.0401 12.70714,-0.20728 -5e-5,-0.24712 -3.59335,-4.04037 -7.98515,-8.42945 -6.60179,-6.59774 -8.44413,-7.98013 -10.63521,-7.98013 -3.28349,0 -5.50168,-2.32632 -5.50168,-5.76987 0,-3.24302 2.34037,-5.46947 5.74941,-5.46947 3.61168,0 5.74052,2.58181 5.29401,6.42031 -0.33403,2.87135 0.14805,3.53833 8.7316,12.07931 l 9.08521,9.04011 8.83119,0.27929 c 4.85715,0.15356 8.83084,0.0742 8.8304,-0.17622 -4.9e-4,-0.25051 -2.71459,-3.15794 -6.03137,-6.46101 -5.43215,-5.40974 -6.0305,-6.34335 -6.0305,-9.40989 0,-3.1937 -0.65027,-4.06445 -10.50923,-14.07234 l -10.50922,-10.66795 -22.45265,0 -22.45261,0 -14.40477,14.48139 c -14.05723,14.13197 -14.39315,14.55443 -13.92222,17.50739 0.37238,2.3347 0.0661,3.47035 -1.3416,4.97123 -4.6917,5.00293 -12.29367,-0.26178 -9.3428,-6.47031 0.87798,-1.84722 1.83334,-2.4503 4.19808,-2.65001 2.67736,-0.22614 4.78304,-1.98885 16.78305,-14.04915 l 13.72158,-13.79054 -
20.56711,0 -20.56708,0 -1.99604,2.1614 c -5.04151,5.45919 -12.54826,-0.63952 -8.99304,-7.30619 1.78693,-3.35075 8.82304,-2.67815 9.97148,0.95318 0.43179,1.36536 1.43723,1.59788 6.90855,1.59788 3.52174,0 6.40321,-0.30221 6.40321,-0.6716 0,-0.36937 -2.7174,-3.37776 -6.0387,-6.68528 -4.60581,-4.5868 -6.659,-6.04501 -8.65323,-6.14564 -5.39928,-0.27254 -7.94257,-6.14604 -4.26106,-9.84062 4.59076,-4.60706 11.28177,0.15426 9.18688,6.53746 -0.47466,1.44621 0.85774,3.19916 7.06413,9.29404 l 7.64909,7.51164 17.7717,0 17.77164,0 0,-4.65564 c 0,-4.01201 -0.29774,-4.85144 -2.15375,-6.07184 -3.05335,-2.00777 -2.9942,-6.63932 0.11616,-9.09466 1.24846,-0.98556 2.74686,-1.79187 3.32982,-1.79187 0.58295,0 2.08134,0.80631 3.32982,1.79187 2.99716,2.36593 3.04714,6.21342 0.11615,8.93942 -1.75861,1.63565 -2.15374,2.81769 -2.15374,6.44293 l 0,4.43979 15.507,0 c 8.52882,0 15.50697,-0.29646 15.50697,-0.65886 0,-0.36236 -5.91658,-6.58719 -13.14798,-13.833 l -13.14798,-13.1741 -19.34125,0 -19.34127,0 -1.75535
,2.12537 c -2.71674,3.28934 -5.96113,3.67499 -8.60403,1.02268 -2.73153,-2.74124 -2.80575,-5.73103 -0.20317,-8.1847 2.63934,-2.48835 5.85258,-2.37994 8.34404,0.28157 1.81186,1.93547 2.70351,2.16141 8.5296,2.16141 l 6.50627,0 -6.67721,-6.74048 c -6.06297,-6.12039 -6.94114,-6.7095 -9.54648,-6.40424 -5.17459,0.60621 -8.15397,-5.7621 -4.66789,-9.97747 0.49374,-0.59703 2.39201,-1.08553 4.21838,-1.08553 2.59203,0 3.63053,0.47459 4.73287,2.16302 0.78495,1.20222 1.22984,3.12055 1.0016,4.31873 -0.33886,1.77918 0.88526,3.51578 7.00743,9.94087 l 7.41808,7.7851 9.89936,0 9.89937,0 -17.86843,-17.94719 -17.8684,-17.94721 0,-4.78005 c 0,-4.59357 -0.16847,-4.94657 -4.31825,-9.04845 l -4.31816,-4.26839 -12.33567,12.35741 c -11.91653,11.93753 -12.33326,12.47633 -12.26565,15.85764 0.0555,2.77732 -0.37915,3.84113 -2.10467,5.15112 -2.94534,2.23601 -7.11252,0.87147 -8.63998,-2.82923 -1.56421,-3.7897 1.03779,-7.13722 5.82781,-7.49761 2.4916,-0.1874 4.13521,-0.9715 5.8902,-2.80982 l 2.4342,-2.54982 -14.8980
5,0 c -14.86513,0 -14.9018,0.005 -16.59219,2.16141 -3.31161,4.22504 -10.18409,1.69002 -10.18409,-3.75654 0,-3.64734 3.04404,-5.83473 7.54382,-5.42093 0.59541,0.0547 1.92531,1.07218 2.95524,2.26096 1.86984,2.15817 1.89624,2.16138 17.62507,2.16138 l 15.75242,0 3.04522,-2.68322 c 1.67488,-1.47577 3.04522,-3.09514 3.04522,-3.59858 0,-0.50343 -3.18748,-4.16054 -7.08328,-8.12694 -6.60057,-6.72012 -7.27245,-7.17597 -9.85947,-6.68893 -2.23897,0.4215 -3.20223,0.0951 -4.9777,-1.68664 -2.65559,-2.66503 -2.81651,-5.62224 -0.44496,-8.17686 2.3294,-2.50923 5.5753,-2.39383 8.253,0.29338 1.66095,1.66682 2.05581,2.74401 1.66377,4.53895 -0.45481,2.08228 0.38051,3.24676 7.25041,10.10734 l 7.76449,7.75391 3.88586,-3.82725 c 2.13724,-2.10499 3.88585,-4.23574 3.88585,-4.735 0,-0.49926 -8.82267,-9.75551 -19.60597,-20.56946 -16.02983,-16.07552 -20.03713,-19.66209 -21.96846,-19.66209 -2.94842,0 -5.80827,-2.76698 -5.80827,-5.61965 0,-2.88441 2.87019,-5.61966 5.89698,-5.61966 3.9004,0 6.7685,4.34388 5.34644,8
.09738 -0.67265,1.77543 -0.0986,2.69756 4.57019,7.34128 2.93673,2.92097 5.61982,5.31081 5.96244,5.31081 0.34262,0 0.61626,-3.20967 0.60804,-7.13264 -0.0148,-6.46172 -0.21601,-7.3241 -2.15375,-9.16863 -2.90593,-2.76619 -2.82643,-6.37362 0.19301,-8.75712 3.71631,-2.93366 8.14636,-1.20586 9.32967,3.63874 0.46609,1.9083 0.0679,2.86528 -2.09707,5.03774 -2.63573,2.64507 -2.67246,2.8018 -2.67246,11.40402 l 0,8.72202 14.43013,14.5539 c 7.93654,8.00461 14.72087,14.5542 15.07622,14.5546 0.35537,3.9e-4 0.64613,-2.65247 0.64613,-5.89529 0,-5.21156 -0.25001,-6.11419 -2.15373,-7.7753 -5.78473,-5.04765 0.85359,-13.60306 7.13102,-9.19052 2.74189,1.92727 2.6178,6.69769 -0.23904,9.19052 -2.00903,1.75302 -2.15375,2.42016 -2.15375,9.92938 0,7.11378 0.20043,8.17572 1.72302,9.12998 1.41377,0.88603 1.723,2.01618 1.723,6.29701 l 0,5.21719 6.46123,6.44092 6.46127,6.44096 0,-7.01849 c 0,-5.31506 -0.30146,-7.13459 -1.24206,-7.49685 -0.68316,-0.26305 -1.89228,-1.4741 -2.68691,-2.69127 -3.75129,-5.74555 4.84495
,-11.63225 9.31331,-6.37777 2.12137,2.49462 1.87381,5.61529 -0.6461,8.14414 -1.98568,1.99272 -2.15375,2.78307 -2.15375,10.12741 l 0,7.96601 17.02251,17.06699 c 9.36235,9.38682 17.24765,17.06695 17.52285,17.06695 0.27522,0 0.46551,-3.10209 0.42287,-6.89356 l -0.0772,-6.89355 -5.9898,-6.05705 c -5.62976,-5.69297 -6.17349,-6.02811 -9.04573,-5.57605 -6.13811,0.96622 -9.47528,-6.16511 -4.66281,-9.96406 2.79892,-2.20944 4.71205,-2.26371 7.24488,-0.20545 1.32989,1.0807 1.98713,2.59599 2.06176,4.75361 0.0901,2.6039 0.94209,4.01316 4.79045,7.92377 2.57452,2.61609 4.92054,4.75659 5.21343,4.75659 0.29284,0 0.60832,-4.37684 0.70096,-9.72632 l 0.16824,-9.72633 -10.79412,-10.79404 -10.79417,-10.79408 0,-5.23853 0,-5.23856 -19.39116,-19.41443 c -16.26132,-16.28082 -19.84193,-19.46023 -22.18364,-19.69818 -3.75168,-0.38121 -5.80763,-2.42317 -5.80763,-5.76819 0,-3.39743 2.27352,-5.61966 5.74939,-5.61966 3.56716,0 5.66217,2.52569 5.32763,6.42285 -0.25078,2.92075 0.30851,3.67982 9.60594,13.0368 5.42968
,5.46452 10.07234,9.93558 10.31695,9.93573 0.24464,1.4e-4 0.30786,-3.5742 0.14054,-7.94297 -0.27439,-7.16321 -0.50701,-8.10324 -2.36913,-9.57318 -4.12657,-3.25751 -1.52432,-10.15009 3.83212,-10.15009 4.99235,0 7.25116,6.0203 3.68722,9.82741 -1.64674,1.75912 -1.8307,2.95122 -1.8307,11.864 l 0,9.90835 6.46125,6.44096 6.46122,6.44091 0,-6.7957 c 0,-6.13823 -0.20834,-6.98949 -2.15373,-8.79884 -2.54809,-2.36988 -2.76418,-5.46038 -0.57184,-8.17741 3.15142,-3.9057 10.47911,-1.34618 10.47911,3.66032 0,0.93492 -1.16303,2.86701 -2.5845,4.29355 l -2.58452,2.59368 0,13.56331 0,13.56332 10.76877,10.7811 10.76872,10.78108 0,21.21788 0,21.21795 6.03051,6.00554 6.03051,6.00555 0,-10.82449 0,-10.82453 -3.44603,-3.37687 -3.446,-3.37687 0,-7.10598 0,-7.10593 3.446,-2.96011 3.44603,-2.96015 0,-9.84149 0,-9.84144 -7.03793,-7.09979 c -6.1787,-6.23306 -7.32862,-7.04145 -9.41939,-6.62181 -1.80566,0.36241 -2.91603,-0.0584 -4.59233,-1.74074 -2.77607,-2.78594 -2.80572,-5.23719 -0.0962,-7.95626 2.68917,-2.6987
4 5.65718,-2.70202 8.17814,-0.0106 1.56707,1.674 1.86626,2.71132 1.43993,4.99194 -0.50147,2.68268 -0.12704,3.28832 5.49484,8.88693 l 6.03301,6.00809 0,-15.55923 0,-15.55921 -6.9127,-6.8962 c -6.38702,-6.37173 -7.18166,-6.89624 -10.4488,-6.89624 -2.82354,0 -3.88364,-0.46393 -5.2605,-2.30227 -2.21179,-2.95295 -1.46479,-6.33205 1.7431,-7.88509 4.54235,-2.19912 8.81787,0.4479 8.81787,5.45922 0,1.58175 1.63753,3.92846 5.59695,8.02096 3.0783,3.18182 5.79203,5.7851 6.0305,5.7851 0.23845,0 0.43358,-6.93513 0.43358,-15.41139 0,-15.321 -0.0135,-15.42335 -2.15375,-17.44196 -1.18459,-1.1168 -2.15374,-2.65216 -2.15374,-3.41188 0,-0.75979 -3.29525,-4.67116 -7.32276,-8.69198 l -7.32274,-7.31061 0,-6.47824 0,-6.4782 -4.92667,-4.99631 c -2.70966,-2.74798 -5.22955,-4.99632 -5.59975,-4.99632 -0.37019,0 -0.67309,3.68232 -0.67309,8.18294 0,7.36183 0.17708,8.278 1.76422,9.13048 2.7457,1.47466 3.63837,4.79775 2.09394,7.79494 -1.73757,3.37207 -5.03394,4.16739 -8.02528,1.93634 -3.27384,-2.44181 -3.52361,-6.
49946 -0.56915,-9.24724 1.82209,-1.69471 2.2106,-2.7502 1.99229,-5.41241 l -0.27126,-3.30714 -8.29763,0.0728 c -7.13873,0.0627 -8.62886,0.33438 -10.66954,1.94527 -3.17489,2.5062 -5.93338,2.36823 -8.10757,-0.40559 -2.34479,-2.99152 -2.25426,-5.11127 0.3291,-7.70375 2.80924,-2.81927 5.29278,-2.67682 8.22338,0.47155 2.35822,2.53348 2.60632,2.59371 10.68365,2.59371 l 8.26936,-2e-5 0,-5.01778 c 0,-4.59959 -0.25131,-5.26219 -3.01524,-7.95065 -2.77951,-2.70358 -3.01526,-3.33437 -3.01526,-8.06769 0,-4.36652 -0.32221,-5.43445 -2.15377,-7.13785 -3.69241,-3.43419 -2.49676,-8.40393 2.44688,-10.17045 2.00926,-0.71794 6.33412,1.7879 6.91177,4.00476 0.59823,2.29566 -0.75458,5.53136 -2.86578,6.85448 -1.41431,0.88643 -1.75461,2.00791 -1.75461,5.78283 l 0,4.68314 9.90724,10.06646 9.90727,10.06644 0,6.04423 c 0,6.02141 0.0148,6.05853 3.87674,9.86242 l 3.87672,3.81827 0,-4.4635 c 0,-3.60836 -0.4126,-4.87762 -2.15373,-6.62493 -2.72094,-2.73064 -2.73318,-5.19533 -0.0393,-7.89893 2.6832,-2.6928 5.14853,-2
.6928 7.83179,0 2.87307,2.88326 2.70909,5.86774 -0.46993,8.55221 -2.37658,2.00692 -2.58226,2.62152 -2.55707,7.64174 0.0168,3.27483 0.50485,6.09159 1.22064,7.03937 1.11357,1.4744 1.33585,1.4862 3.3274,0.17673 2.70064,-1.77583 6.24181,-0.98442 8.06076,1.80148 1.71762,2.63074 0.91925,5.9282 -1.88115,7.76965 -2.07579,1.36491 -2.1401,1.70888 -2.1401,11.44931 l 0,10.04209 8.18423,-8.1792 c 8.15229,-8.14723 8.18425,-8.19602 8.18425,-12.48662 0,-3.63035 -0.36106,-4.59631 -2.29731,-6.14573 -4.32534,-3.46114 -2.11451,-10.76278 3.25879,-10.76278 2.90289,0 5.93049,2.97223 5.93049,5.82198 0,1.12073 -0.96915,3.01031 -2.15373,4.1991 -1.83184,1.83837 -2.15373,2.95857 -2.15373,7.49522 l 0,5.33384 -9.4765,9.25239 -9.47649,9.25242 0,26.00888 c 0,14.30489 0.32758,26.00886 0.72798,26.00886 0.40038,0 1.40049,-0.74572 2.22242,-1.65716 1.1716,-1.29924 1.2935,-2.03404 0.56424,-3.40142 -2.37783,-4.45887 0.0275,-8.77439 4.89029,-8.77439 2.67306,0 6.24057,3.21254 6.24057,5.61965 0,1.97418 -3.55678,5.61965 -5.4
8288,5.61965 -1.11391,0 -3.42502,1.54729 -5.53724,3.70711 -3.42545,3.50265 -3.62538,3.98608 -3.62538,8.76507 0,4.67424 -0.25389,5.33437 -3.34651,8.70141 -3.19122,3.47436 -3.33908,3.89037 -3.18526,8.96343 0.14753,4.86895 0.43137,5.59756 3.34652,8.59354 l 3.18525,3.27358 0,12.96811 0,12.96811 3.87672,3.81826 3.87677,3.81828 0,-19.63268 c 0,-18.47988 -0.10102,-19.73414 -1.72298,-21.36175 -3.49173,-3.50416 -1.03196,-10.37474 3.71428,-10.37474 2.87236,0 5.76222,3.05208 5.76222,6.08562 0,2.47182 -2.53833,6.01824 -4.30752,6.01824 -0.55066,0 -0.86149,7.57321 -0.86149,20.99059 l 0,20.99061 5.59973,5.56968 c 5.05013,5.02301 5.59978,5.91219 5.59978,9.05875 0,3.41575 0.50211,3.99271 23.91225,27.47483 13.15175,13.19214 24.35989,23.98579 24.90692,23.98579 1.40987,0 5.45821,-5.03527 5.44037,-6.76671 -0.0109,-0.79565 -0.97734,-2.36284 -2.15375,-3.48267 -1.39582,-1.3287 -2.13886,-2.98252 -2.13886,-4.76067 0,-2.41778 -2.81411,-5.54751 -24.98346,-27.78577 l -24.98347,-25.0611 0,-7.09371 c 0,-6.19704 -
0.24966,-7.36399 -1.97498,-9.23226 -1.68679,-1.82652 -2.00735,-3.2059 -2.197,-9.45283 l -0.22209,-7.31422 4.34614,-4.20857 c 5.97694,-5.7878 5.91965,-7.9047 -0.38286,-14.151 l -4.73824,-4.69606 0,-33.82961 c 0,-32.73156 -0.056,-33.88036 -1.72298,-35.39442 -3.68762,-3.34908 -1.1729,-9.94247 3.79206,-9.94247 5.85042,0 7.90004,6.94997 3.09995,10.51147 l -2.58452,1.91759 0,32.5153 0,32.51528 3.446,3.37692 3.446,3.37691 0,-31.54292 0,-31.54291 5.16898,-5.13326 c 4.81594,-4.78258 5.16901,-5.4028 5.16901,-9.08035 0,-3.63881 -0.37012,-4.31393 -4.73824,-8.64311 l -4.73826,-4.69605 0,-13.00497 0,-13.00505 -19.7489,-19.80504 -19.74885,-19.80502 0.24362,-5.69249 0.24364,-5.6925 -11.66073,-11.83559 c -10.48709,-10.64436 -11.89982,-11.78761 -14.03611,-11.35883 -1.79861,0.36098 -2.9123,-0.062 -4.58629,-1.74194 -2.67724,-2.68677 -2.79144,-4.78165 -0.42529,-7.80038 3.70277,-4.72408 10.79466,-1.66255 10.0943,4.35766 -0.31798,2.73361 0.17912,3.55172 5.44999,8.96871 l 5.79987,5.96074 6.30755,-6.52814 c
4.93151,-5.10398 6.24655,-6.99513 6.02795,-8.6689 -0.15374,-1.17745 0.34146,-3.09194 1.1005,-4.25444 1.89215,-2.89807 7.50762,-3.01194 9.47566,-0.19215 3.11137,4.45787 0.31645,9.31782 -5.3586,9.31782 -2.8826,0 -3.93288,0.68681 -9.20731,6.02085 l -5.95357,6.02084 1.6692,1.97635 c 0.91802,1.08698 3.12292,3.41972 4.89977,5.18382 3.14845,3.12589 3.2306,3.36861 3.2306,9.54267 l 0,6.33523 4.12174,4.07415 c 2.26694,2.24082 4.3531,4.07418 4.63589,4.07418 0.28279,0 0.42304,-1.96063 0.31166,-4.35699 -0.14439,-3.10851 -0.71301,-4.8343 -1.98408,-6.02268 -2.78518,-2.60401 -3.19583,-5.0711 -1.27576,-7.66389 2.02207,-2.73056 5.58634,-3.3422 8.461,-1.452 2.81564,1.85146 2.81687,5.88435 9.6e-4,8.70851 -1.77404,1.78031 -2.12573,3.04351 -2.29361,8.23793 l -0.19826,6.13518 11.54275,11.6077 c 6.34865,6.38419 11.83904,11.60766 12.201,11.60766 0.36192,0 0.65806,-9.05877 0.65806,-20.13062 l 0,-20.13055 -4.54995,-4.50948 c -3.49236,-3.46122 -5.16341,-4.50936 -7.18922,-4.50936 -5.03934,0 -7.34206,-5.92324 -
3.71699,-9.56118 2.46045,-2.46921 6.57705,-1.95408 8.84401,1.10671 1.51548,2.04617 1.6018,2.66235 0.66248,4.73121 -1.03422,2.27787 -0.87624,2.57508 3.7271,7.01455 l 4.80708,4.63589 0,27.78198 0,27.78199 5.22094,-5.16028 c 4.52628,-4.47379 5.15771,-5.48606 4.74568,-7.60864 -0.4762,-2.45324 0.66192,-5.64187 2.43872,-6.8325 1.80923,-1.21232 5.69536,-0.65245 7.40916,1.06748 3.5007,3.51313 0.90751,9.51014 -4.11232,9.51014 -1.83552,0 -3.93219,1.55748 -9.04577,6.71951 l -6.65641,6.71948 0,5.33544 0,5.33545 4.73825,4.49715 4.73828,4.49718 0,5.30515 0,5.30516 -5.16903,5.13324 -5.16898,5.13325 0,22.72182 c 0,12.49699 0.32359,22.7218 0.71908,22.7218 0.39551,0 1.75238,-1.16377 3.01525,-2.5862 2.29554,-2.58548 2.29618,-2.59019 2.29618,-17.24023 0,-9.4043 -0.32768,-14.85721 -0.91465,-15.22125 -1.74682,-1.08344 -2.58388,-4.73577 -1.54106,-6.72403 1.34468,-2.5637 2.40401,-3.15675 5.66648,-3.17225 5.7502,-0.0275 7.4314,7.91856 2.19221,10.36149 l -2.78121,1.29685 -0.0184,14.73005 -0.0185,14.73006 -4.
30748,4.25783 -4.3075,4.25777 0,11.30794 0,11.30792 -3.87674,3.8183 -3.87674,3.81824 0,7.03476 c 0,5.93677 0.26892,7.27901 1.72297,8.59963 1.46868,1.33377 1.72304,2.66279 1.72304,9.00146 l 0,7.4366 24.35993,24.43522 c 23.66912,23.74231 24.43844,24.41953 27.12721,23.8799 6.34601,-1.27374 9.83468,5.923 4.94108,10.19306 -1.23529,1.07789 -2.15376,2.81113 -2.15376,4.06447 0,1.35556 -1.21739,3.43634 -3.20648,5.48061 l -3.20649,3.29542 18.71348,18.79456 c 10.29234,10.33702 18.71338,19.47216 18.71338,20.30034 0,1.85173 1.05475,1.89698 4.35032,0.18674 2.20938,-1.1466 2.54169,-1.79917 2.54169,-4.9915 0,-3.44124 -0.43397,-4.10548 -6.89201,-10.5484 -6.04505,-6.03101 -6.89194,-7.25472 -6.89194,-9.9583 0,-2.51376 -0.71928,-3.79083 -3.89887,-6.92244 -2.81058,-2.76821 -4.66325,-3.8786 -6.63732,-3.97827 -3.15898,-0.15946 -5.83234,-2.92713 -5.83234,-6.03819 0,-4.90138 6.8241,-7.32116 10.43293,-3.69948 1.44311,1.44824 1.71287,2.48389 1.30828,5.02309 -0.46728,2.93231 -0.18812,3.51544 3.35106,7.00125 3.
14288,3.09549 3.86073,4.37565 3.86073,6.88491 0,2.7021 0.84534,3.92574 6.85238,9.91876 6.39187,6.37696 6.8695,7.10647 7.10736,10.8548 l 0.25502,4.0184 2.79986,-1.15123 c 2.73927,-1.12634 2.79986,-1.26857 2.79986,-6.57393 l 0,-5.42267 -6.46124,-6.44092 c -5.90994,-5.89139 -6.46125,-6.73826 -6.46125,-9.92489 0,-3.43456 -0.48829,-3.97385 -34.45998,-38.05831 -32.37097,-32.4784 -34.45997,-34.75955 -34.45997,-37.62936 0,-2.61236 -0.81134,-3.86195 -5.59975,-8.62471 -4.95804,-4.93139 -5.59974,-5.95047 -5.59974,-8.89257 0,-2.63075 -0.53836,-3.77749 -2.58453,-5.50535 -6.31153,-5.32966 0.53394,-14.28581 6.94014,-9.07997 2.61871,2.12801 2.66857,6.74655 0.0956,8.83752 -1.30379,1.05948 -1.86657,2.47819 -1.86657,4.70524 0,2.76569 0.7423,3.94069 5.59974,8.86356 4.80329,4.86801 5.59975,6.11693 5.59975,8.78094 0,2.93667 1.87647,4.98853 34.45996,37.68013 32.35096,32.45839 34.46,34.76107 34.46,37.62469 0,2.66351 0.87395,3.9222 6.892,9.92622 l 6.892,6.87595 0,5.34285 0,5.34288 3.01524,-0.9287 3.01525,-0
.92868 0,-10.45355 0,-10.45355 -17.92179,-17.97016 c -12.97045,-13.00551 -18.30705,-17.82181 -19.3164,-17.43309 -1.45994,0.56224 -4.95401,-0.90467 -6.03899,-2.53532 -0.34809,-0.5232 -0.63888,-2.01281 -0.64615,-3.31026 -0.0109,-1.91126 -3.52874,-5.89228 -18.53539,-20.97424 l -18.52224,-18.61522 0,-11.94818 0,-11.94815 -8.6093,-8.57656 -8.60927,-8.57659 -0.005,-8.23833 -0.005,-8.23832 5.59974,-5.56968 5.59973,-5.56972 0,-11.13982 c 0,-10.85279 -0.0555,-11.18367 -2.15372,-12.83999 -4.21008,-3.32343 -1.68409,-10.22032 3.74323,-10.22032 2.48558,0 4.9364,1.96067 5.40336,4.32282 0.48274,2.44156 -0.96557,5.93642 -2.68735,6.48486 -1.61185,0.51358 -1.72039,1.31214 -1.72039,12.6649 l 0,12.11675 -5.59969,5.70319 -5.59978,5.70315 0,6.4549 0,6.45487 8.61497,8.78573 8.61502,8.7857 0,12.06231 0,12.06228 17.90035,17.9487 c 17.28656,17.33322 17.99516,17.92965 20.66592,17.3936 1.92019,-0.38541 3.45828,-0.0999 5.03166,0.93497 2.01272,1.32349 2.21873,1.91144 1.84272,5.25907 -0.33338,2.96837 -0.0743,4.08
597 1.22007,5.26145 1.57954,1.43457 1.85982,1.27718 7.21927,-4.05352 3.06678,-3.0503 5.47621,-5.84567 5.35435,-6.21198 -0.12165,-0.36626 -8.65356,-9.17287 -18.95922,-19.57026 l -18.73764,-18.90431 0,-7.2619 0,-7.26196 -11.64316,-11.65959 c -9.66533,-9.67898 -12.01486,-11.61127 -13.83155,-11.37529 -3.55035,0.46121 -6.40077,-1.98435 -6.40077,-5.49155 0,-5.69814 5.74592,-8.47902 9.57652,-4.63481 1.43476,1.43989 1.7284,2.53744 1.37875,5.15356 -0.79818,5.9719 1.09657,5.92224 7.49923,-0.19654 l 5.6675,-5.4162 0,-13.89925 0,-13.89926 -4.58183,-4.54097 -4.58182,-4.54102 0.34591,-6.05239 0.34591,-6.0524 -6.96355,-7.15963 -6.96364,-7.15959 0,-12.3527 c 0,-12.14649 -0.036,-12.38108 -2.1537,-14.05285 -4.2101,-3.32345 -1.68406,-10.2203 3.74322,-10.2203 3.26232,0 5.30249,2.44503 5.30249,6.35469 0,2.56642 -0.42239,3.38572 -2.15374,4.17736 -2.14968,0.98295 -2.15377,1.0079 -2.15377,13.22311 l 0,12.23831 3.14603,2.79514 3.14604,2.79516 2.86986,-2.79149 c 2.4902,-2.42216 2.80608,-3.19992 2.38742,-5.87
821 -0.96581,-6.1787 6.13204,-9.53914 9.92566,-4.6992 2.2016,2.80884 2.25567,4.72878 0.20469,7.27059 -1.07229,1.32895 -2.58734,1.99423 -4.70986,2.06819 -2.18344,0.0759 -3.91881,0.86828 -5.74201,2.6212 -1.43702,1.38169 -2.61284,2.82546 -2.61284,3.20834 0,0.38288 1.16306,1.73608 2.58454,3.0071 2.47097,2.20948 2.58449,2.61976 2.58449,9.3414 l 0,7.03038 3.45745,3.38815 3.45747,3.38806 7.74202,-7.73353 7.74201,-7.73359 0,-12.13733 0,-12.13731 -8.89856,-8.89986 c -7.89695,-7.89806 -9.15275,-8.83588 -11.15614,-8.33126 -2.83123,0.71315 -6.65176,-2.75712 -6.65176,-6.0419 0,-2.94437 2.86281,-5.65895 5.96797,-5.65895 3.30172,0 6.36522,3.7026 5.67577,6.8598 -0.41677,1.90838 0.60104,3.3021 7.28918,9.98116 l 7.77354,7.763 0,-4.27229 c 0,-3.59235 -0.36558,-4.56487 -2.2973,-6.11066 -4.86338,-3.89171 -1.59597,-11.57447 4.41889,-10.39012 5.05537,0.99543 6.50669,6.7407 2.61672,10.35869 -2.11055,1.96292 -2.15379,2.26002 -2.15379,14.80696 l 0,12.80385 2.12302,-2.00148 c 1.54272,-1.45451 2.09177,-2.83242
2.00902,-5.04173 -0.0872,-2.32126 0.441,-3.51911 2.23258,-5.06567 1.29057,-1.11404 2.92398,-2.02553 3.62982,-2.02553 2.05324,0 5.51258,4.04447 5.51258,6.44493 0,3.08177 -2.98653,5.83765 -5.98423,5.52207 -1.79585,-0.18904 -3.33726,0.57621 -5.97174,2.96478 -3.21619,2.9159 -3.55105,3.6086 -3.55105,7.34553 0,4.06141 -0.12784,4.25399 -8.18424,12.30518 l -8.18424,8.17919 0,14.73959 0,14.73961 -5.59975,5.56968 c -3.07986,3.06334 -5.59973,5.89339 -5.59973,6.289 0,0.81998 15.50283,17.0539 16.28672,17.05474 0.28191,3.2e-4 0.40335,-10.77441 0.26996,-23.94375 l -0.24261,-23.94438 6.24136,-6.3056 c 5.77742,-5.83692 6.22381,-6.54996 6.00493,-9.59223 -0.39224,-5.45157 4.70578,-8.54772 8.77947,-5.33194 4.70631,3.71512 2.12141,10.32713 -4.03725,10.32713 -1.89462,0 -3.82269,1.29168 -7.99254,5.35442 l -5.49558,5.35443 0,32.94899 0,32.94897 3.01524,2.93286 3.01527,2.93291 0,-31.58037 0,-31.58038 12.49174,-12.63635 c 12.35308,-12.49607 12.49175,-12.67937 12.49175,-16.51411 0,-3.00602 -0.35634,-3.99132
-1.58509,-4.3827 -2.15473,-0.68632 -3.89327,-4.18801 -3.18736,-6.42 1.42755,-4.51377 6.7064,-5.94838 9.94145,-2.70183 2.33978,2.3481 2.16642,5.68555 -0.43075,8.29194 -1.68996,1.69598 -2.15377,3.04163 -2.15377,6.24905 0,3.9602 -0.21485,4.30207 -6.89198,10.96358 -3.79061,3.78176 -6.892,7.17685 -6.892,7.54464 0,0.36778 2.44699,0.6687 5.43775,0.6687 5.04297,0 5.57643,-0.18831 7.34853,-2.59369 2.50255,-3.39691 6.16915,-3.53012 8.68867,-0.31565 2.34481,2.99149 2.25428,5.11121 -0.32907,7.70373 -2.69402,2.70363 -5.15,2.69137 -7.87095,-0.0391 -1.9692,-1.97621 -2.79977,-2.16141 -9.69324,-2.16141 l -7.53952,0 -3.35259,3.44557 -3.35265,3.44564 -0.15008,32.80623 -0.15008,32.80631 3.3279,3.26114 3.32788,3.26109 0,-15.42228 0,-15.42235 -2.20767,-2.05326 c -2.90617,-2.70288 -2.62099,-6.77422 0.64518,-9.21038 2.02015,-1.50673 2.6426,-1.58966 4.89769,-0.65222 4.51281,1.8759 5.10083,8.03394 1.01192,10.59661 -1.67826,1.05177 -1.76262,1.91426 -1.76262,18.01831 l 0,16.9137 3.01526,2.93288 3.01522,2.93289
0,-15.97284 0,-15.97283 5.59976,-5.56972 5.59975,-5.56968 0,-5.66629 0,-5.66623 -6.413,-6.39281 c -5.86252,-5.84408 -6.72291,-6.39882 -10.02419,-6.46258 -2.82118,-0.0543 -3.97193,-0.55296 -5.2599,-2.27852 -1.38006,-1.84888 -1.49257,-2.62068 -0.69064,-4.73746 1.17707,-3.10692 2.24353,-3.83052 5.67033,-3.84757 3.41467,-0.0178 5.51789,2.3231 5.51789,6.13927 0,2.42583 0.77705,3.7585 4.11581,7.05876 l 4.11585,4.06836 7.51443,-7.57617 c 6.17924,-6.23013 7.5144,-8.04865 7.5144,-10.23496 0,-3.29431 2.31848,-5.52041 5.74945,-5.52041 3.23154,0 5.45005,2.34867 5.45005,5.76986 0,3.72754 -2.56092,5.76387 -6.63817,5.27834 -3.12625,-0.37231 -3.58483,-0.0728 -10.49561,6.8505 l -7.22653,7.23981 1.84216,1.96785 c 1.53967,1.64471 1.84216,3.01965 1.84216,8.37312 l 0,6.4052 -5.29441,5.3609 -5.29441,5.36084 0.0698,17.50794 0.0697,17.50792 4.75596,4.77291 c 4.62067,4.63705 4.80416,4.72903 6.45088,3.23349 1.26723,-1.1509 1.5794,-2.25377 1.23713,-4.37056 -0.90876,-5.61975 5.54009,-8.55853 9.67495,-4.40897
2.70943,2.71906 2.67982,5.17033 -0.0962,7.95625 -1.78439,1.79072 -2.73386,2.11374 -4.92214,1.67453 -2.52626,-0.50706 -3.25716,9.3e-4 -10.71162,7.45126 -4.40022,4.39746 -7.99316,8.39214 -7.98433,8.87702 0.0109,0.4849 6.79314,7.68958 15.07625,16.01036 l 15.06024,15.12871 0,10.10871 c 0,5.55976 0.34314,10.32151 0.76255,10.58164 0.41938,0.26014 1.97008,0.0504 3.44598,-0.46584 l 2.68344,-0.93877 0,-12.24955 0,-12.2495 -9.75905,-9.76602 c -8.44464,-8.45062 -10.04517,-9.69395 -11.88289,-9.23108 -2.9434,0.74135 -6.7875,-2.36515 -6.7875,-5.48511 0,-3.67648 2.23499,-6.18155 5.51507,-6.18155 3.79243,0 5.90562,2.41953 5.56617,6.37316 -0.19469,2.26792 0.26796,3.61777 1.80126,5.25577 1.13484,1.21223 2.52338,2.20404 3.0857,2.20404 0.56229,0 3.40224,-2.56387 6.31105,-5.69754 l 5.28871,-5.6975 0,-10.25389 c 0,-8.85928 0.20506,-10.37176 1.50763,-11.12009 1.29962,-0.74673 -0.15485,-2.54192 -10.54181,-13.01141 -9.18494,-9.25789 -12.56984,-12.1452 -14.23844,-12.1452 -5.41801,0 -8.205,-6.3917 -4.3239,-9.
91653 4.03847,-3.66774 9.77603,-0.66424 9.37513,4.90771 -0.20531,2.85399 0.25265,3.77207 3.57801,7.17239 l 3.81349,3.89946 4.98731,-4.95278 c 4.52475,-4.49349 4.939,-5.21123 4.46654,-7.7387 -0.71298,-3.81406 2.87167,-7.64915 6.37301,-6.81819 3.4779,0.82536 5.58803,3.76785 4.96972,6.93011 -0.63169,3.23108 -2.58099,4.78197 -6.0392,4.80484 -2.01781,0.0143 -3.79583,1.14455 -7.58884,4.82814 -2.7245,2.64589 -4.90453,5.06961 -4.84449,5.38609 0.0602,0.31646 3.11086,3.41684 6.77958,6.88975 l 6.67041,6.31437 2.7369,-2.74664 c 2.70173,-2.71128 2.71427,-2.76255 0.97393,-3.98584 -1.15722,-0.81343 -1.75848,-2.18787 -1.74975,-4.00005 0.0167,-3.26029 0.60889,-4.30918 3.21739,-5.68772 1.89541,-1.00172 1.93839,-1.35393 1.93839,-15.88601 l 0,-14.86161 -5.74235,-5.80721 c -4.94513,-5.00107 -6.0689,-5.74171 -8.09449,-5.33516 -4.65288,0.93386 -8.06379,-6.13656 -4.62514,-9.58742 4.66861,-4.68525 11.16592,-0.69931 9.529,5.84575 -0.377,1.50736 0.14199,2.64029 2.05067,4.47527 l 2.55545,2.45694 5.50845,-5.123
68 c 5.13321,-4.77459 5.51534,-5.38928 5.60944,-9.02292 l 0.10041,-3.89965 -15.94787,-15.98572 c -13.08006,-13.11133 -16.32455,-15.93674 -18.04446,-15.71379 -4.94654,0.64122 -8.34756,-4.06086 -6.09674,-8.42898 0.69164,-1.34219 1.85333,-2.66982 2.58159,-2.95023 1.1164,-0.42993 1.32413,-2.72591 1.32413,-14.6355 l 0,-14.12559 -23.69122,-23.75873 -23.69124,-23.75874 0,-5.99617 0,-5.99612 -6.03051,6.00555 c -4.6485,4.62929 -6.03049,6.5688 -6.03049,8.46312 0,3.43546 -1.30427,5.50925 -3.87172,6.15591 -2.19714,0.55342 -5.4799,-0.64208 -6.66849,-2.42843 -1.23199,-1.85169 -0.63524,-5.73052 1.15864,-7.53077 1.43874,-1.44386 2.47767,-1.71853 4.98095,-1.31678 3.0427,0.48829 3.41598,0.2471 9.81231,-6.33958 3.65711,-3.76596 6.64931,-7.36235 6.64931,-7.99202 0,-0.62965 -4.26318,-5.40985 -9.47373,-10.62273 l -9.47373,-9.47792 -6.89475,6.87872 c -5.26673,5.25439 -6.89477,7.43696 -6.89477,9.24283 0,2.94409 -3.02218,5.88976 -6.04276,5.88976 -2.62566,0 -6.01827,-3.2127 -6.01827,-5.69914 0,-4.05276 5.090
68,-7.43306 8.46486,-5.62079 1.25822,0.67576 2.74146,-0.35542 8.16656,-5.67771 3.646,-3.5769 6.6291,-6.73841 6.6291,-7.02559 0,-0.28718 -1.01563,-1.586 -2.25694,-2.88626 -1.90066,-1.99093 -2.59897,-2.23357 -4.42381,-1.53732 -2.61456,0.99761 -5.80657,-0.77301 -7.29212,-4.04497 -0.80725,-1.778 -0.67668,-2.53923 0.77052,-4.49322 3.55082,-4.79415 10.61785,-2.63203 10.61785,3.24846 0,2.01473 1.28276,3.89599 5.89509,8.64559 3.24229,3.33884 6.14983,6.0706 6.46124,6.0706 0.31138,0 0.56614,-6.11783 0.56614,-13.59515 l 0,-13.59519 -5.67823,-5.74238 c -5.52829,-5.59078 -5.77541,-5.73143 -9.35846,-5.32614 -3.13856,0.35503 -3.88785,0.11878 -5.09055,-1.6043 -4.61389,-6.61064 3.50255,-13.33747 8.73162,-7.23666 1.12968,1.31799 1.87659,3.07712 1.65975,3.90916 -0.26565,1.01951 1.54076,3.43976 5.53962,7.4221 l 5.93379,5.90929 5.21378,-5.23232 c 4.85739,-4.87467 5.14558,-5.38242 4.21634,-7.42911 -0.8032,-1.76908 -0.72512,-2.72524 0.40083,-4.91036 1.19706,-2.32309 1.8717,-2.71353 4.68861,-2.71353 3.8480
8,0 6.14191,2.04348 6.14191,5.47159 0,2.82637 -2.75684,5.76769 -5.40596,5.76769 -1.28875,0 -4.0914,2.15341 -8.18424,6.28842 l -6.22433,6.28841 0,14.86768 0,14.86769 5.59976,5.70325 5.59975,5.70328 0,8.82002 0,8.82007 8.61502,8.61315 8.61499,8.61316 0,-19.89183 0,-19.89194 -5.92834,-5.9038 c -5.43254,-5.41006 -6.15102,-5.85471 -8.59099,-5.31691 -5.33107,1.17506 -9.17456,-6.0215 -5.29515,-9.91469 3.88553,-3.89931 11.08059,-0.17388 9.91858,5.13564 -0.40808,1.86454 0.35088,3.05476 4.70464,7.37846 l 5.19126,5.15531 0,-20.93854 c 0,-13.38269 -0.31089,-20.93857 -0.86151,-20.93857 -1.64675,0 -4.30749,-3.47124 -4.30749,-5.61964 0,-2.93471 2.88127,-5.61968 6.03049,-5.61968 5.52966,0 7.85998,6.02372 3.87673,10.02106 -1.67245,1.67846 -2.15372,3.04928 -2.15372,6.13486 l 0,3.9734 4.21982,-3.79659 4.21988,-3.79657 0.0879,-18.89592 0.0879,-18.89593 -2.15374,-1.70016 c -2.6614,-2.10088 -2.86177,-6.0515 -0.43077,-8.491166 4.65434,-4.670886 12.11161,0.966691 9.07468,6.860316 -0.69162,1.34219 -1.8533,2
.6698 -2.58157,2.95023 -1.13614,0.43755 -1.32409,3.23492 -1.32409,19.70435 l 0,19.19441 -5.58284,5.80337 -5.58283,5.80336 0.0784,20.20271 c 0.0433,11.1115 0.27569,20.53258 0.51676,20.93573 0.24105,0.40312 2.53391,-1.34396 5.09527,-3.88251 4.44017,-4.40059 4.63316,-4.77691 4.14476,-8.08177 -0.63018,-4.26476 1.72725,-7.35681 5.60891,-7.35681 2.75533,0 6.05944,3.38499 6.05944,6.20781 0,2.60134 -4.0363,6.15178 -6.3353,5.57273 -1.5719,-0.39593 -3.20849,0.7904 -8.60291,6.23592 l -6.66171,6.72484 0.15543,13.7823 0.15541,13.78238 8.23817,8.44657 8.2381,8.44653 6.28329,-6.2635 6.28326,-6.26354 0,-8.01883 0,-8.01882 -5.44437,0 c -4.73364,0 -5.68885,0.28214 -7.31705,2.16143 -2.55072,2.94397 -6.35448,2.89309 -8.71354,-0.11653 -2.20379,-2.81166 -2.25559,-4.72892 -0.19679,-7.28052 2.29252,-2.8412 6.74125,-2.67734 9.13588,0.33653 1.63119,2.05293 2.41814,2.30549 7.18389,2.30549 l 5.35198,0 0,-5.14809 c 0,-4.33852 -0.33868,-5.48796 -2.15374,-7.30946 -4.10022,-4.11481 -1.50683,-10.41389 4.05773,-9.85
59 5.57786,0.55932 7.57521,8.19907 2.72356,10.41748 -1.98905,0.90956 -2.04307,1.21609 -2.04307,11.60246 0,5.86755 0.13853,10.66826 0.30749,10.66826 0.16919,0 6.17808,-5.90772 13.35327,-13.12826 11.88671,-11.9619 13.04575,-13.39941 13.04575,-16.18085 0,-2.46899 -0.76249,-3.82948 -3.9885,-7.11641 -3.51258,-3.57882 -4.32348,-4.00923 -6.79503,-3.60673 -6.02251,0.98079 -8.73422,-6.61686 -3.70147,-10.37072 2.02012,-1.50676 2.64261,-1.58966 4.89775,-0.65223 2.72242,1.13167 4.59851,4.78715 3.87728,7.55486 -0.2658,1.0201 0.66757,2.62326 2.64981,4.55134 l 3.06016,2.97657 0,-8.63975 0,-8.63973 -3.3591,-3.29172 -3.35905,-3.29171 -10.99908,-0.18304 c -10.53782,-0.17533 -11.07187,-0.0989 -12.73404,1.81941 -3.59159,4.14535 -10.03921,1.72575 -10.03921,-3.76742 0,-5.32564 6.8569,-7.55289 10.18407,-3.30805 1.62071,2.06776 2.09841,2.1614 11.02177,2.1614 l 9.32763,0 -5.35949,-5.42677 -5.3595,-5.42679 0,-7.7794 0,-7.77945 -6.28078,-6.26103 c -5.76413,-5.74601 -6.50353,-6.21636 -8.98864,-5.71754 -2.18428
,0.4384 -3.13532,0.11441 -4.91871,-1.67526 -2.77604,-2.7859 -2.80572,-5.23715 -0.0962,-7.95623 2.70943,-2.71906 5.15204,-2.68933 7.92809,0.0967 1.67867,1.68462 2.09588,2.79557 1.73325,4.61511 -0.40883,2.05141 0.23197,3.10099 4.45161,7.29147 2.71103,2.69228 5.2087,4.89507 5.55032,4.89507 0.34163,0 0.62114,-2.22245 0.62114,-4.93879 0,-4.31433 -0.29047,-5.17115 -2.29735,-6.77708 -3.00316,-2.40313 -3.16643,-6.86771 -0.33532,-9.16835 2.54257,-2.06616 4.45305,-2.01415 7.25475,0.19747 2.99902,2.36743 3.04973,6.18472 0.11615,8.74452 -2.13605,1.86386 -2.15375,1.99753 -2.15375,16.26849 l 0,14.3892 8.59094,8.79805 c 4.72502,4.83895 8.79563,8.79808 9.04575,8.79808 0.25014,0 0.45481,-2.70965 0.45481,-6.02151 0,-5.06631 -0.26406,-6.16332 -1.66466,-6.91557 -5.03705,-2.70535 -2.71736,-11.27066 3.05234,-11.27066 5.11916,0 7.35778,7.03974 3.35056,10.53634 -1.9162,1.67207 -2.15376,2.55094 -2.15376,7.96833 l 0,6.08898 6.0305,-6.00557 6.03049,-6.00557 0,-13.01322 0,-13.01327 -6.2661,-6.24643 c -4.77441,
-4.75943 -6.83881,-6.24645 -8.67165,-6.24645 -7.47775,0 -9.72503,-8.50769 -2.9871,-11.30855 2.19204,-0.91121 2.83485,-0.81895 4.84188,0.69491 1.851,1.3962 2.31425,2.45388 2.31425,5.28387 0,2.2145 0.56301,4.10341 1.50496,5.04889 1.43244,1.43781 1.6921,1.32637 5.38437,-2.31017 l 3.87939,-3.82089 0,-8.8979 c 0,-8.618284 -0.0609,-8.90136 -1.93837,-9.008505 -4.82974,-0.27563 -7.92239,0.191632 -9.25023,1.397583 -3.55563,3.229242 -9.70277,0.40865 -9.70277,-4.452095 0,-3.091889 3.70075,-6.385421 6.42618,-5.719104 2.48954,0.608651 4.98874,2.744326 4.98874,4.263145 0,0.833134 1.34344,1.162846 4.73821,1.162846 l 4.73824,0 0,-3.258326 c 0,-2.976505 -0.74473,-4.002891 -8.60998,-11.866484 -7.86437,-7.862734 -8.86217,-8.586344 -11.52304,-8.356286 -2.30771,0.199532 -3.29338,-0.233361 -4.74329,-2.083101 -2.38886,-3.047751 -2.31161,-5.155503 0.28444,-7.760774 2.70695,-2.71654 5.15168,-2.689565 7.91874,0.0872 z m 50.7938,435.654981 c 27.74711,13.58237 42.66509,35.41992 44.02514,64.44598 0.55036,11.745
56 -0.40019,17.27075 -4.40572,25.60807 -6.08845,12.67284 -16.21104,23.0464 -30.15167,30.89915 -12.69673,7.1521 -19.15407,14.45785 -24.86532,28.13234 -1.68179,4.02669 -2.22554,4.81618 -1.8467,2.68119 0.2953,-1.66429 1.70773,-5.87756 3.1387,-9.36285 3.48755,-8.49428 9.68367,-15.07163 21.7843,-23.12464 22.55101,-15.00768 35.83974,-37.02194 33.09972,-54.83326 -2.60732,-16.94882 -12.36141,-35.06704 -26.58074,-49.3739 -5.73867,-5.7739 -8.84582,-8.13681 -13.85838,-10.53912 -6.26691,-3.00339 -21.10913,-7.81019 -22.5882,-7.31542 -0.40183,0.13433 1.22746,1.71144 3.62061,3.50446 4.66777,3.4972 8.45992,8.20799 11.62722,14.44396 2.65258,5.22247 7.2647,18.44836 8.26289,23.69498 1.1098,5.83337 -0.46022,21.46905 -2.94065,29.28487 -2.33967,7.3723 -4.57867,11.05535 -11.67876,19.2111 -2.89778,3.32851 -6.04086,7.60813 -6.98471,9.51016 -0.94383,1.90204 -1.71636,2.95944 -1.71671,2.34977 -9.7e-4,-2.29643 2.82158,-7.72915 7.82818,-15.06498 9.73158,-14.259 13.44316,-25.50205 12.53564,-37.97276 -0.95673,-13.
14657 -11.36506,-36.18813 -18.72157,-41.4451 l -2.49459,-1.78259 0.0287,132.98173 0.0287,132.98173 11.34063,4.50783 c 6.23734,2.47927 11.44474,4.40331 11.57202,4.27557 0.12731,-0.12779 -1.3247,-3.85452 -3.22664,-8.28173 -4.50766,-10.49273 -5.96897,-16.34131 -5.96897,-23.88909 0,-7.58917 1.57512,-13.06412 5.43845,-18.9034 5.8938,-8.90824 12.67876,-14.64616 30.69392,-25.95736 11.84532,-7.43729 34.63279,-25.72753 42.74029,-34.3052 11.14737,-11.79391 17.7837,-23.72924 21.77032,-39.1536 2.09385,-8.10127 2.2554,-10.05917 2.25373,-27.31485 -9.4e-4,-17.83609 -0.098,-18.90285 -2.37956,-26.36915 -10.03081,-32.82395 -35.99378,-59.99883 -68.60027,-71.80249 -7.66965,-2.77641 -8.38726,-2.65222 -2.73669,0.47366 6.66587,3.68753 11.40225,7.83177 18.118,15.85294 16.80357,20.06995 29.38104,45.01702 33.32616,66.10151 1.66492,8.89799 1.29842,13.91994 -1.6616,22.76907 -8.6562,25.87796 -23.16053,42.01281 -57.02563,63.43626 -6.24299,3.94934 -12.52115,8.6812 -14.63473,11.03015 -4.06318,4.51572 -7.67486,12.4
212 -11.68125,25.56863 -3.11669,10.22778 -3.45777,11.03289 -2.80842,6.62928 1.167,-7.91367 5.43475,-22.90146 7.93853,-27.87908 3.63628,-7.22902 6.05102,-9.43573 22.92178,-20.94651 17.16798,-11.71368 25.91435,-18.6527 32.1727,-25.5247 12.45644,-13.67778 21.70055,-33.53588 21.67907,-46.57056 -0.0135,-7.9745 -3.18093,-20.70075 -7.76198,-31.18344 -9.83808,-22.51205 -31.19364,-51.13899 -42.76352,-57.32401 -4.69241,-2.50846 -23.2483,-8.76737 -26.13778,-8.81624 -0.71076,-0.013 3.0133,2.08569 8.27566,4.66164 z m -32.10558,3.2765 c -11.33703,12.34295 -17.52208,36.44832 -17.52208,68.28974 0,9.78952 0.37696,13.05967 2.52037,21.86428 3.67058,15.07804 4.80587,18.01497 10.89988,28.19763 6.80531,11.37119 8.15454,16.22978 7.99985,28.80725 l -0.11749,9.54468 -0.68983,-7.16563 c -1.09093,-11.33155 -2.33211,-15.88172 -6.08148,-22.29436 -8.65312,-14.79978 -14.07098,-28.6007 -17.11753,-43.60355 -4.33319,-21.33898 -0.12434,-56.56347 8.90677,-74.54108 4.24465,-8.44961 3.94893,-9.24697 -0.62631,-1.68855 -6
.47564,10.69795 -9.66009,21.42459 -13.82396,46.56534 -2.87619,17.36621 -4.21384,32.32491 -3.54239,39.61482 2.26787,24.62293 8.3798,44.23875 21.36309,68.56372 7.50654,14.064 9.57214,20.47467 10.95523,34.00019 1.40095,13.70055 0.88056,16.19176 -0.78908,3.77718 -2.09626,-15.58695 -2.6251,-17.14675 -11.35687,-33.49535 -16.46411,-30.82612 -20.23436,-41.66829 -22.74517,-65.40881 -2.05658,-19.44572 -1.46798,-32.34026 2.31241,-50.65874 2.43441,-11.79645 8.13362,-31.80221 10.24029,-35.9463 2.62195,-5.15775 0.82617,-4.04645 -2.93965,1.81922 -4.77169,7.43227 -7.04165,13.63839 -9.48361,25.92816 -10.47933,52.74009 -7.39387,107.29891 7.7898,137.74273 2.17125,4.35335 6.75618,12.58387 10.18879,18.28997 7.46713,12.41279 9.65642,16.64812 11.34321,21.94387 1.4166,4.4475 2.58099,25.02295 1.25345,22.1488 0.28253,6.9328 -6.97379,18.7653 8.05011,22.04631 1.19987,0 1.335,-13.38995 1.335,-132.27795 0,-72.75287 -0.32657,-132.27796 -0.72571,-132.27796 -0.91987,0 -4.88786,12.97878 -6.74022,22.04633 -1.03719,5.
07721 -1.4351,11.97446 -1.49636,25.93685 -0.0822,18.74068 -0.0351,19.2601 3.18741,35.32587 1.79897,8.96805 3.15584,16.64205 3.01523,17.05336 -0.14052,0.41124 -2.32038,-6.50864 -4.84406,-15.37756 -6.2397,-21.92838 -7.55165,-32.19071 -6.2225,-48.67327 1.78093,-22.08523 3.54341,-29.25535 9.03207,-36.74386 1.04555,-1.42655 2.21107,-3.11883 2.59004,-3.76067 1.35445,-2.29386 -2.54071,0.51181 -6.11881,4.40734 z m -2.2068,-132.01081 c 2.83603,1.99346 2.69527,7.61117 -0.23904,9.54061 l -2.15373,1.41622 0,23.90569 0,23.90566 10.33798,10.34772 10.33803,10.34771 0,17.52091 c 0,14.99775 -0.18611,17.5209 -1.29228,17.5209 -1.10512,0 -1.29226,-2.47676 -1.29226,-17.10324 l 0,-17.10319 -10.33799,-10.28268 -10.33799,-10.28264 0,-24.57955 0,-24.57957 -2.15375,-1.70017 c -2.74006,-2.16299 -2.86277,-6.03171 -0.26832,-8.45746 2.2533,-2.10675 4.7919,-2.24975 7.39935,-0.41692 z"
- id="path3100"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="csscsssscscccssssssssscsssscssssscccccccsssssscssscccsssscscsssscccccssssssscccsssssssssscccccccccscscscccccccscsscccccccccsscssssscscccccsssscsscssssssscccccccccccccccssscccccccscsssssscccssssssssscssssccccccccccsssccccscscccssssscsscssssssscsscccsssscscsssssscccscsssscccccssssscccccssscccssssssscsscssscccccsssssssscccsscccccsscccccccccccccsssssscscccccccccccssscssssssscccscscccsssssscscsssssssccccsssscscssscccsssssscccccccccssssssssssscsccccccccsssccccssccccsssssscsssssssscssscccssssscccccccsscssssssscccssssssscssssssscccssscscccccccssssscsssssscccssssssssssscccccccccccssssscsssssssscccssssscccscsssscccccccccssssssscccccssssscccccccccssssscccccccccsssscsssscsssssssssssssscccssscscssscssscsscsscssssscccssscccccccsssssscccccssscccscsssscccsssssscccccsssssscccccccssssscscssssscccccccssscssscsssssscsscssscsscscssscccssssscccccsssssssssscccccsssssscccssssssssssssscccssssssssccsssscscscssssccccccssssssssscccsssssscccccccccccccccccsssssscccccsssscsssscscccccccs
sscssscccccssssscccsssssssscccssssssssssssssscsssccsscccccssscccsssssscsscccccssssssssssssscccccsssssscccsssssssssssccccsssssscccssscscsscsssssscssssssscccscccscssscccccsssssscsssssscsssssscccsssssscssssscccsssscscccsssssscccccccsssscssscssssscssssscsssscsssssscsssssscsscssssssscssssscccccccccsssscscccsssssscccsssssssssssscssscsssscccscsssccssssscccccssscssscccssccscsssscsssssssscssssscccsssssscccsssssssssscccssssssscssscccssssscsscsssscccccsscscsssscscscsscssssssssscssssssssscssscscssssssssscccscsssscssscsssscssscccccsssssscssssscccssssscccccccccccccccccccssssssscccsssssssscsscccsssssssscsssssscsssssssscccsccssssscssssscssssssscccsssssssssssscccccsssssscsssscssscssscsscscsscccccccssscccccccsssssscscsssscssscsscssscscssscsssssscccscsssscccccccccsssssssscccccccccssscssssscssssssssssssssscsscssssssssssssssscccccccscscscccccccccsssccscccccccccsscscscscccsssssscccccccccsssssscccssssssssscccccccsssssscssssscscssssssscccsscscssssscccccccssssssssssssssscccccccccssssscccccccccssssssscssssss
csscccccsssssscsscscscccsssssssscssssssscsssscssscsscsscssssscsscssssscccccssssssssscssssssssssssssscsssssscssssssscccccccccccssssscsssssscccccssssscccsssssssscccccccccsssssscsssssssssssssscccccssssscccccsssssssssssssscssssssscccccssssssscssssssscsssssccsssssscssscccsssssssssssssssssssssscsscccsssssssssssssssssssssssssssssssscssssccccccssssssssscsssscssscccccsss" /><g
- id="g3064"
- style="fill:#000000;fill-opacity:1"><g
- id="g3066"
- clip-path="url(#clipPath3068)"
- style="fill:#000000;fill-opacity:1" /></g><g
- id="g3084"
- style="fill:#000000;fill-opacity:1"><g
- id="g3086"
- clip-path="url(#clipPath3088)"
- style="fill:#000000;fill-opacity:1" /></g></g></svg>
\ No newline at end of file
diff --git a/lib/bridgedb/https/templates/assets/tor.svg b/lib/bridgedb/https/templates/assets/tor.svg
deleted file mode 100644
index afe0bc0..0000000
--- a/lib/bridgedb/https/templates/assets/tor.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="380" height="290" viewBox="0 0 380.00001 289.99999" id="å¾å±_1" xml:space="preserve"><metadata id="metadata2611"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs id="defs2609"><linearGradient x1="297.03323" y1="247.01682" x2="297.03323" y2="125.66936" id="linearGradient5018" xlink:href="#linearGradient4697" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.68230177,0,0,0.68230177,78.404988,63.177814)" /><linearGradient id="linearGradient4697"><stop id="stop4699" style="stop-color:#482957;stop-opacity:1" offset="0" /><stop id="stop4701" style="stop-color:
#c19ed3;stop-opacity:1" offset="1" /></linearGradient><linearGradient x1="297.03323" y1="247.01682" x2="297.03323" y2="125.66936" id="linearGradient3077" xlink:href="#linearGradient4697" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.68230177,0,0,0.68230177,78.404988,63.177814)" /></defs>
-
-<g transform="translate(-58.124001,-45.284749)" id="layer1"><path d="M 50.75,238.46875 C 50.74767,240.51542 50.75067,242.54542 50.75,244.59375 L 58.5625,244.59375 L 58.5625,263.71875 L 66.1875,263.71875 L 66.1875,244.59375 L 73.96875,244.59375 L 73.96875,238.46875 L 50.75,238.46875 z M 115.78125,238.46875 L 115.78125,263.71875 L 123.4375,263.71875 L 123.4375,254.3125 C 125.77166,254.26006 128.12573,254.46829 130.4375,254.0625 C 133.43015,253.61607 135.96405,251.18201 136.46875,248.1875 C 137.01255,245.29331 136.52883,241.81443 134.0625,239.90625 C 131.78087,238.23859 128.83652,238.49113 126.15625,238.46875 L 115.78125,238.46875 z M 181.75,238.46875 L 181.75,243.25 L 188.5625,243.25 L 188.5625,238.46875 L 181.75,238.46875 z M 249.46875,238.46875 L 242.65625,242 L 242.65625,245.4375 L 240.1875,245.4375 L 240.1875,250.46875 L 242.65625,250.46875 L 242.65625,252.65625 C 242.70624,255.21642 242.47421,257.81014 242.9375,260.34375 C 243.05668,261.78997 244.18591,262.93333 245.40625,263.593
75 C 248.04288,264.55411 250.90487,264.05409 253.5625,263.4375 L 253.09375,259.25 C 253.23483,258.27274 252.26229,258.9884 251.6875,259 C 250.62775,259.38851 249.37429,258.76458 249.5,257.53125 C 249.44651,255.1859 249.48339,252.81534 249.46875,250.46875 L 253.3125,250.46875 L 253.3125,245.4375 L 249.46875,245.4375 L 249.46875,238.46875 z M 123.4375,243.53125 C 124.93468,243.60074 126.49451,243.29466 127.90625,243.9375 C 129.54076,244.71485 129.75122,247.1681 128.4375,248.34375 C 127.3731,249.32992 125.83906,249.09707 124.5,249.125 C 124.18831,249.03964 123.53064,249.30392 123.4375,249 L 123.4375,243.53125 z M 83.09375,244.875 C 80.20859,244.8275 77.22027,245.81868 75.3125,248.0625 C 73.5522,249.94861 72.88908,252.62887 73.03125,255.15625 C 73.08077,257.68011 74.09823,260.23861 76.125,261.8125 C 78.81274,264.10451 82.64182,264.48951 86,263.84375 C 88.82917,263.28233 91.42955,261.43611 92.59375,258.75 C 94.17919,255.14195 93.6751,250.50727 90.84375,247.65625 C 88.83195,245.61668 85.8
7085,244.93229 83.09375,244.875 z M 108.375,244.875 C 107.419,244.875 106.58925,245.1415 105.90625,245.6875 C 105.22325,246.0965 104.683,247.05225 104,248.28125 L 104,245.4375 L 97.59375,245.4375 L 97.59375,263.71875 L 104.5625,263.71875 L 104.5625,257.5625 C 104.5625,254.5585 104.817,252.6555 105.5,251.5625 C 106.046,250.7435 106.7315,250.34375 107.6875,250.34375 C 108.0985,250.34375 108.77375,250.602 109.59375,250.875 L 111.65625,245.96875 C 110.42725,245.28575 109.331,244.875 108.375,244.875 z M 151.40625,244.875 C 150.8262,244.87145 150.26044,244.97535 149.71875,245.25 C 148.43645,245.82374 147.76794,247.1133 147.125,248.28125 L 147.125,245.4375 L 140.6875,245.4375 C 140.68517,251.5355 140.68817,257.61908 140.6875,263.71875 L 147.65625,263.71875 L 147.65625,261.75 C 147.6874,259.30832 147.57522,256.87168 147.8125,254.4375 C 147.98367,253.01117 148.27947,251.28613 149.6875,250.5625 C 150.68046,250.02843 151.75878,250.55088 152.71875,250.875 L 154.5,246.5625 C 154.7673,246.12898 1
54.79825,245.77218 154.1875,245.65625 C 153.3401,245.20326 152.373,244.88092 151.40625,244.875 z M 166.65625,244.875 C 163.81138,244.83015 160.84817,245.7831 158.9375,247.96875 C 157.14601,249.82305 156.26534,252.44241 156.40625,255 C 156.4455,257.60089 157.62013,260.2008 159.6875,261.8125 C 162.43682,264.15178 166.33269,264.50973 169.75,263.78125 C 172.53457,263.1788 175.07375,261.31901 176.1875,258.65625 C 177.7293,255.0256 177.16931,250.3711 174.28125,247.5625 C 172.27329,245.58452 169.38522,244.95613 166.65625,244.875 z M 202.5625,244.875 C 199.65259,244.82633 196.58865,245.83995 194.6875,248.125 C 192.2363,250.95423 191.8563,255.23236 193.28125,258.625 C 194.29372,261.11012 196.45477,263.16353 199.125,263.71875 C 201.64663,264.2679 204.28679,264.24885 206.8125,263.75 C 208.51969,263.42988 210.07293,262.47938 211.1875,261.15625 C 211.6559,260.51927 212.73656,259.43233 212.625,258.90625 L 206.125,258.28125 C 205.51686,258.9536 204.91301,259.72992 203.9375,259.8125 C 202.39982,260
.20123 200.57653,259.5439 199.875,258.0625 C 199.79053,257.55239 198.90352,256.05401 199.875,256.21875 L 213.21875,256.21875 C 213.28951,253.4079 212.92796,250.3949 211.125,248.125 C 210.03317,246.80706 208.52604,245.83022 206.84375,245.46875 C 205.44659,245.10894 204.00458,244.9063 202.5625,244.875 z M 226.53125,244.875 C 224.83773,244.94818 223.16268,245.2917 221.59375,245.9375 C 219.74896,246.79555 218.29579,248.44202 217.46875,250.28125 C 216.51712,252.42489 216.57711,254.87891 216.90625,257.15625 C 217.21271,259.53093 218.85626,261.58953 220.90625,262.75 C 222.614,263.77638 224.64841,263.95739 226.59375,264.09375 C 228.87355,264.16831 231.35076,264.0907 233.3125,262.78125 C 235.36706,261.62577 236.70538,259.55319 237.3125,257.3125 L 230.78125,256.5 C 230.52218,257.60301 229.84173,258.55904 228.8125,259.0625 C 227.04278,259.7832 224.53052,259.176 223.875,257.21875 C 223.37182,255.6932 223.44816,254.02326 223.78125,252.46875 C 224.12531,250.7656 225.86092,249.61089 227.5625,249.8
125 C 228.93603,249.74484 230.32064,250.72254 230.5,252.125 L 236.90625,251.28125 C 236.30346,248.57745 234.27097,246.20997 231.5625,245.46875 C 229.935,245.02311 228.22477,244.80182 226.53125,244.875 z M 279,244.875 C 276.11458,244.82722 273.09566,245.81887 271.1875,248.0625 C 269.35887,250.01593 268.69715,252.78835 268.90625,255.40625 C 269.06071,258.57942 270.96032,261.61925 273.84375,263 C 276.42708,264.26198 279.47881,264.37106 282.25,263.75 C 285.00789,263.09784 287.47228,261.17924 288.5625,258.53125 C 290.1067,254.84728 289.46037,250.12239 286.4375,247.375 C 284.43673,245.53292 281.64544,244.93556 279,244.875 z M 304.15625,244.875 C 303.5762,244.87145 303.01044,244.97535 302.46875,245.25 C 301.18645,245.82374 300.51794,247.1133 299.875,248.28125 L 299.875,245.4375 L 293.46875,245.4375 C 293.46642,251.5355 293.43817,257.61908 293.4375,263.71875 L 300.40625,263.71875 L 300.40625,261.75 C 300.43736,259.30833 300.32566,256.87171 300.5625,254.4375 C 300.73318,253.01097 301.0296,25
1.28622 302.4375,250.5625 C 303.4305,250.02827 304.50868,250.55121 305.46875,250.875 L 307.28125,246.5625 C 307.54855,246.12898 307.5795,245.77218 306.96875,245.65625 C 306.12135,245.20326 305.123,244.88092 304.15625,244.875 z M 316.8125,244.875 C 313.944,244.75095 311.24416,246.74626 310.28125,249.40625 C 309.30036,251.99637 309.4085,254.8776 310.0625,257.53125 C 310.82024,260.37628 313.3772,262.72715 316.375,262.875 C 318.02221,262.96074 319.84243,262.61957 321.125,261.53125 C 321.65763,260.95665 322.5173,259.87579 322.25,261.3125 C 322.22656,262.71696 322.44184,264.21836 321.84375,265.53125 C 321.03374,266.81839 319.19006,266.98564 317.90625,266.375 C 316.81858,266.33518 317.11739,264.47551 315.90625,264.71875 L 310.125,264.125 C 309.50347,266.76816 311.20799,269.74611 313.875,270.40625 C 316.60154,271.26237 319.49764,271.16066 322.3125,270.90625 C 324.68811,270.64995 327.16096,269.52501 328.25,267.28125 C 329.27127,265.45631 329.29538,263.34169 329.25,261.3125 L 329.25,245.4375
L 322.8125,245.4375 L 322.8125,248.03125 C 321.50453,246.04856 319.20547,244.77746 316.8125,244.875 z M 181.75,245.4375 C 181.75233,250.47539 181.74933,255.49169 181.75,260.53125 C 181.69711,261.87363 181.89297,263.27272 181.4375,264.5625 C 181.0078,265.66627 179.66675,265.61346 178.71875,265.375 L 177.65625,270.5625 C 179.62508,270.79403 181.57001,271.23211 183.5625,271.0625 C 184.97081,271.00683 186.30112,270.33286 187.28125,269.34375 C 188.42564,267.93617 188.49326,266.01317 188.5625,264.28125 C 188.59678,257.99763 188.55329,251.72134 188.5625,245.4375 L 181.75,245.4375 z M 202.84375,249.125 C 203.95329,249.09172 205.18486,249.55909 205.625,250.65625 C 205.8373,251.2573 206.52558,252.61661 206,252.9375 L 199.4375,252.9375 C 199.56074,251.41532 200.27758,249.69691 201.875,249.25 C 202.18319,249.16417 202.52447,249.12493 202.84375,249.125 z M 83.21875,249.8125 C 84.81783,249.74971 86.12553,251.10433 86.34375,252.625 C 86.59093,254.16964 86.6235,255.82291 86.09375,257.3125 C 85.5851
7,258.42141 84.51087,259.40806 83.21875,259.34375 C 82.16274,259.497 81.16864,258.69003 80.5625,257.84375 C 79.6954,256.4655 79.76487,254.74716 79.90625,253.1875 C 80.034,251.824 80.79576,250.42045 82.15625,249.96875 C 82.49984,249.84916 82.85603,249.81209 83.21875,249.8125 z M 166.28125,249.8125 L 166.46875,249.8125 L 166.65625,249.8125 C 168.24903,249.69466 169.62717,251.0036 169.875,252.53125 C 170.15533,254.0429 170.15817,255.64356 169.71875,257.125 C 169.25152,258.37261 168.06301,259.46133 166.65625,259.34375 C 165.63697,259.47968 164.64055,258.69308 164.09375,257.84375 C 163.2126,256.42434 163.29992,254.63084 163.46875,253.03125 C 163.62666,251.51818 164.6538,249.94298 166.28125,249.8125 z M 279.125,249.8125 C 280.77107,249.74861 282.03138,251.21111 282.25,252.75 C 282.48768,254.25882 282.48055,255.86623 281.9375,257.3125 C 281.41893,258.40217 280.41521,259.41166 279.125,259.34375 C 278.06475,259.49194 277.01503,258.70161 276.40625,257.84375 C 275.55817,256.45691 275.65901,254
.74655 275.78125,253.1875 C 275.89204,251.87317 276.58125,250.51671 277.875,250.03125 C 278.26601,249.87076 278.7039,249.81159 279.125,249.8125 z M 319.28125,250.0625 C 321.08552,249.99674 322.39578,251.8094 322.375,253.5 C 322.55438,255.13402 322.15278,257.16624 320.5,257.90625 C 319.04952,258.5741 317.03402,257.79625 316.6875,256.15625 C 316.17758,254.45034 316.2497,252.45136 317.25,250.9375 C 317.72499,250.33036 318.51318,249.99526 319.28125,250.0625 z M 257.75,256.625 L 257.75,263.71875 L 264.96875,263.71875 L 264.96875,256.625 L 257.75,256.625 z" transform="translate(58.124001,45.28474)" id="path3078" style="fill:#7d4698;fill-opacity:1;stroke:none" /><g transform="matrix(1.4656327,0,0,1.4656327,-114.91364,-92.589999)" id="g3060"><path d="M 243.45456,141.73907 L 254.45054,146.3009 C 254.45054,149.09697 254.22325,157.62508 255.97165,160.14073 C 274.2593,183.69317 271.18253,230.90449 252.26708,232.11557 C 223.46265,232.11557 212.47729,212.54784 212.47729,194.56304 C 212.47729,178.
16255 232.1385,167.26005 243.88023,157.56932 C 246.86189,154.9602 246.34411,149.1939 243.45456,141.73907 z" id="path2534-7" style="fill:#fffcdb;fill-rule:evenodd;stroke:none;display:inline" /><path d="M 254.45384,146.1368 L 258.41667,148.15833 C 258.04413,150.76678 258.60294,156.54518 261.21138,158.03533 C 272.76683,165.21041 283.66866,173.03778 287.95556,180.86515 C 303.23775,208.44788 277.23932,233.97963 254.78204,231.55678 C 266.9891,222.51764 270.53025,203.97471 265.96429,183.75334 C 264.10093,175.83249 261.21138,168.65741 256.08592,160.5503 C 253.86557,156.5708 254.64079,151.63546 254.45384,146.1368 z" id="path2536-6" style="fill:url(#linearGradient3077);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" /><g transform="matrix(1.1111963,0,0,1.1111963,39.131448,34.108974)" id="g4942" style="display:inline"><path d="M 197.76094,74.040206 L 194.74178,86.031491 C 199.01846,77.562251 205.8108,71.18929 213.61014,65.570352 C 207.90769,72.195062 202.70875,78.820387 199.52196,
85.445098 C 204.88915,77.897508 212.10087,73.704949 220.23485,70.93754 C 209.41697,80.58141 200.83028,90.929734 194.28907,101.32824 L 189.09013,99.064331 C 190.01178,90.762105 193.14839,82.258925 197.76094,74.040206 z" id="path2554-7" style="fill:#abcd03;fill-rule:evenodd;stroke:none" /><path d="M 213.625,65.562491 C 209.7467,69.702223 203.03465,76.965587 199.4375,83.312491 C 196.3689,88.726815 194.26416,93.542182 193.09375,96.781241 C 192.34506,98.853218 189.31133,98.832471 190.1875,95.406241 C 190.19615,95.372423 190.20934,95.346974 190.21875,95.312491 C 191.38669,92.553161 193.15732,88.701836 194.9375,85.312491 L 197.75,74.031241 C 196.36311,78.792777 194.37766,83.130347 193.15625,88.062491 C 193.15824,88.062491 191.0097,92.997031 190.4375,94.249991 C 190.24315,94.675568 190.0806,95.086141 189.9375,95.468741 C 189.92839,95.493088 189.91515,95.507159 189.90625,95.531241 C 189.66369,96.072792 189.49944,96.66463 189.375,97.187491 C 189.35468,97.272849 189.36142,97.324868 189.34375,9
7.406241 C 189.18279,98.147642 189.09375,98.718741 189.09375,98.718741 C 189.96719,99.473076 194.28125,101.34374 194.28125,101.34374 C 200.82245,90.94523 209.43212,80.58136 220.25,70.937491 C 212.11602,73.704899 204.89844,77.889901 199.53125,85.437491 C 202.71804,78.812781 207.92255,72.187201 213.625,65.562491 z" id="path4724" style="fill:#437202;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path d="M 193.34005,100.63542 L 194.28126,101.34375 C 200.82246,90.945238 209.43213,80.581364 220.25001,70.937495 C 214.0451,76.013452 205.71291,83.006064 202.18751,87.624993 C 197.72826,93.467436 200.35289,87.979211 200.35289,87.979211 C 200.35289,87.979211 202.71483,81.869504 206.87371,78.036091 C 204.45723,79.19008 200.26932,84.399602 199.53126,85.437496 C 199.53126,85.437496 196.38593,91.541806 194.63266,97.062918 C 194.16228,98.544172 193.34005,100.63542 193.34005,100.63542 z" id="path4720" style="fill:#153902;fill-opacity:1;fill-rule:evenodd;stroke:none" /></g><path d="M 251.3005,144.98
552 L 251.3005,232.11026 C 251.62736,232.11542 251.94123,232.11026 252.2728,232.11026 C 271.18825,230.89918 274.27602,183.67801 255.98836,160.12557 C 254.23996,157.60992 254.46046,149.10114 254.46046,146.30507 L 251.3005,144.98552 z" id="path4748" style="fill:#f3ecaa;fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" /><g transform="matrix(0.68230175,0,0,0.68230175,78.404988,63.177814)" id="layer4-3" style="display:inline"><path d="M 255.226,120.58877 L 267.244,122.22777 C 263.693,133.97277 274.21,142.16677 277.624,144.07977 C 285.27201,148.31377 292.64701,152.68377 298.52001,158.00977 C 309.58301,168.11577 315.86501,182.31977 315.86501,197.34277 C 315.86501,212.22877 309.03601,226.56877 297.56401,236.12877 C 286.77501,245.14277 271.889,248.96677 257.412,248.96677 C 248.398,248.96677 240.34,248.55777 231.6,245.68877 C 211.661,238.99677 196.774,221.92577 195.545,201.43877 C 194.452,185.45977 198.003,173.30477 210.432,160.60377 C 216.85,153.91177 229.825,146.26377 238.703,14
0.11777 C 243.074,137.11277 247.717,128.64477 238.839,112.66677 L 240.615,111.30077 L 253.77159,120.1128 L 242.664,115.53477 C 243.62,116.90077 246.215,123.04677 246.762,124.82177 C 247.991,129.87477 247.445,134.79277 246.352,136.97677 C 240.753,147.08377 231.193,149.81477 224.228,155.55077 C 211.936,165.65677 198.552,173.71477 200.054,201.43877 C 200.737,215.09577 211.39,231.75777 227.368,239.54277 C 236.382,243.91377 246.762,245.68877 257.278,246.23477 C 266.701,246.64477 284.72901,241.04477 294.56201,232.85077 C 305.07801,224.11077 310.95101,210.86277 310.95101,197.34277 C 310.95101,183.68477 305.48801,170.71077 295.24501,161.55977 C 289.37201,156.23377 279.676,149.81477 273.667,146.39977 C 267.658,142.98577 260.146,133.42577 262.604,124.27577 z" id="path2538-6" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 251.539,140.80177 C 250.31,147.08477 248.944,158.41977 243.481,162.65377 C 241.159,164.29177 238.837,165.93177 236.379,167.56977 C 226.546,174.26277 216.712,180
.54377 212.206,196.65977 C 211.25,200.07477 212.07,203.76177 212.89,207.17577 C 215.348,217.00877 222.313,227.66177 227.776,233.94477 C 227.776,234.21777 228.869,234.90077 228.869,235.17377 C 233.376,240.50077 234.742,242.00277 251.813,245.82577 L 251.403,247.73877 C 241.16,245.00777 232.693,242.54977 227.366,236.40277 C 227.366,236.26677 226.41,235.30977 226.41,235.30977 C 220.674,228.75377 213.708,217.82877 211.114,207.58577 C 210.158,203.48777 209.339,200.34777 210.431,196.11277 C 215.074,179.45177 225.181,172.89577 235.424,165.93077 C 237.746,164.42877 240.477,163.06177 242.662,161.28677 C 246.895,158.14677 249.216,148.58577 251.539,140.80177 z" id="path2540-5" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.90625,166.74951 C 256.04325,173.85151 255.35,177.41426 257.125,182.46826 C 258.217,185.47226 261.907,189.56976 263,193.53076 C 264.502,198.85776 266.138,204.72977 266,208.28077 C 266,212.37876 265.74375,220.02326 263.96875,228.21826 C 262.61513,234.98934 259
.49552,240.79979 254.25,244.09326 C 248.87673,242.98682 242.56776,241.09805 238.84375,237.90576 C 231.60575,231.62376 225.195,221.11926 224.375,211.96826 C 223.693,204.45727 230.64775,193.37976 240.34375,187.78076 C 248.53775,183.00076 250.44375,177.55301 252.21875,168.81201 C 249.76075,176.46001 247.45225,182.87126 239.53125,186.96826 C 228.05925,192.97726 222.17275,203.06452 222.71875,212.62451 C 223.53775,224.91551 228.46025,233.24401 238.15625,239.93701 C 242.25325,242.80501 249.9075,245.83576 254.6875,246.65576 L 254.6875,246.03076 C 258.31243,245.35188 263.00568,239.39809 265.34375,231.34326 C 267.39275,224.10526 268.2005,214.84126 268.0625,208.96827 C 267.9255,205.55427 266.4195,198.16026 263.6875,191.46826 C 262.1855,187.78126 259.878,184.09451 258.375,181.49951 C 256.738,178.90251 256.72625,173.30451 255.90625,166.74951 z" id="path2542-6" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.09375,193.53076 C 255.22975,198.31076 257.14975,204.43527 257.96875,210.
71827 C 258.65275,215.36226 258.35575,220.02651 258.21875,224.12451 C 258.0838,228.86774 256.50355,237.36669 254.34375,241.49951 C 252.30702,240.56548 251.51081,239.50029 250.1875,237.78076 C 248.5495,235.45876 247.43675,233.13676 246.34375,230.40576 C 245.52475,228.35676 244.56725,226.01176 244.15625,223.28076 C 243.61025,219.18376 243.76325,212.77426 248.40625,206.21827 C 251.95725,201.02826 252.771,200.63351 254,194.62451 C 252.36,199.95051 251.1375,200.49351 247.3125,204.99952 C 243.0795,209.91652 242.375,217.15876 242.375,223.03076 C 242.375,225.48976 243.3555,228.21801 244.3125,230.81201 C 245.4045,233.54401 246.34175,236.26401 247.84375,238.31201 C 250.10171,241.63283 252.99173,243.52123 254.40625,243.87451 C 254.41563,243.87685 254.42825,243.8723 254.4375,243.87451 C 254.46781,243.88174 254.50238,243.89999 254.53125,243.90576 L 254.53125,243.74951 C 257.18107,240.79514 258.77569,237.86017 259.3125,234.90576 C 259.9955,231.35476 260.1525,227.79601 260.5625,223.56201 C 260.971
5,220.01101 260.67475,215.22801 259.71875,210.31202 C 258.35375,204.16602 256.04975,197.89976 255.09375,193.53076 z" id="path2544-3" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 255.499,135.06577 C 255.636,142.16677 256.182,155.41577 258.094,160.60477 C 258.64,162.37977 263.693,170.16477 267.243,179.58777 C 269.702,186.14377 270.248,192.15277 270.658,193.92777 C 272.297,201.71277 270.248,214.82377 267.516,227.25177 C 266.151,233.94377 261.507,242.27477 256.181,245.55277 L 255.089,247.46477 C 258.094,247.32777 265.468,240.08977 268.063,231.07577 C 272.434,215.77977 274.209,208.67777 272.161,191.74277 C 271.888,190.10277 271.205,184.50477 268.61,178.49477 C 264.786,169.34377 259.323,160.60377 258.641,158.82777 C 257.411,155.96077 255.772,143.53277 255.499,135.06577 z" id="path2550-9" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 258.06151,125.35303 C 257.65636,132.65115 257.548,135.33877 258.913,140.66477 C 260.415,146.53777 268.064,155.00477 271.205,164
.70177 C 277.214,183.27577 275.712,207.58577 271.341,226.56877 C 269.703,233.25977 261.917,242.95777 254.133,246.09777 L 259.869,247.46377 C 263.01,247.32677 271.067,239.81577 274.209,231.21177 C 279.261,217.69077 280.218,201.57577 278.169,184.64077 C 278.032,183.00177 275.3,168.38877 272.706,162.24277 C 269.018,153.09277 262.462,144.89777 261.78,143.12377 C 260.552,140.11877 257.85349,133.88015 258.06151,125.35303 z" id="path2552-4" style="fill:#000000;fill-opacity:1;stroke:none" /><path d="M 253.71959,120.21687 C 253.77917,120.21088 253.83856,123.48208 253.88718,129.77904 C 253.93581,136.076 253.97339,145.39357 253.99454,157.03048 C 254.0157,168.66738 254.02041,182.61455 254.01083,197.82859 C 254.00125,213.04262 253.97762,229.51192 253.94782,246.00754 C 253.97914,246.00754 254.01045,246.00754 254.04177,246.00754 C 254.04177,230.48011 254.04702,214.96246 254.05716,200.48155 C 254.0673,186.00063 254.08233,172.56501 254.10125,161.06788 C 254.12016,149.57075 254.14295,140.01916 254.16
809,133.05468 C 254.19322,126.0902 254.2207,121.71744 254.24866,120.2382 C 253.90193,120.24537 253.71817,120.2311 253.7196,120.21686 C 253.71817,120.20262 253.90188,120.18835 254.29134,120.19552 C 254.3193,121.61981 254.34678,125.94524 254.37191,132.89212 C 254.39705,139.839 254.41984,149.40337 254.43875,160.94887 C 254.45767,172.49437 254.4727,186.01385 254.48284,200.59851 C 254.49298,215.18318 254.49823,230.82368 254.49823,246.464 C 254.16261,246.464 253.82698,246.464 253.49136,246.464 C 253.46156,229.90825 253.43793,213.36755 253.42835,198.08839 C 253.41877,182.80922 253.42348,168.80373 253.44464,157.12468 C 253.46579,145.44563 253.50337,136.10212 253.552,129.79227 C 253.60062,123.48241 253.66001,120.21088 253.71959,120.21687 z" id="rect2556-8" style="fill:#000000;fill-opacity:1;stroke:none" /></g></g><path d="M 99.943,82.074773 H 209.611 C 213.707,82.074773 217.258,85.488773 217.258,89.722773 V 122.08977 C 217.258,126.32377 213.707,129.73877 209.611,129.73877 H 187.077 C 182.023
,129.73877 179.838,132.60577 179.838,135.88377 V 241.86378 C 179.838,245.41478 176.97,248.14578 173.556,248.14578 H 136.272 C 132.857,248.14578 130.126,245.41478 130.126,241.86378 V 134.92877 C 130.126,131.65077 127.121,129.73977 124.8,129.73977 H 99.943 C 95.709,129.73977 92.295,126.32477 92.295,122.09077 V 89.722773 C 92.294,85.488773 95.708,82.074773 99.943,82.074773 z" id="path2528" style="fill:#7d4698;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path d="M 381.30687,124.95877 L 381.44287,124.95877 L 391.00287,124.95877 C 394.55387,124.95877 397.42187,127.82677 397.42187,131.24077 L 397.42187,165.93077 C 397.42187,170.43777 397.69487,172.21277 392.23187,172.21277 C 381.44287,172.21277 376.52687,177.94877 376.52687,184.23077 L 376.52687,242.81978 C 376.52687,245.55178 373.93187,247.87278 370.79087,247.87278 L 335.96387,247.87278 C 332.82287,247.87278 330.22787,245.55178 330.22787,242.81978 L 330.22787,176.03577 C 330.19003,174.86136 330.21308,173.12767 330.36487,172.21277 C 33
2.27687,147.49277 351.6724,127.71491 376.25287,125.23177 C 377.07686,125.14853 379.9923,124.95877 381.30687,124.95877 z" id="path2532" style="fill:#7d4698;fill-opacity:1;fill-rule:evenodd;stroke:none" /><g transform="matrix(1.8025885,0,0,1.8025885,-321.30782,-98.80226)" id="layer7"><path d="M 405.999,129.09724 L 406.03025,129.09724 C 406.32841,129.09725 406.59516,129.09434 406.8115,129.12849 C 407.02174,129.15616 407.18553,129.23538 407.3115,129.31599 C 407.43963,129.38992 407.52656,129.48086 407.59275,129.62849 C 407.65137,129.75929 407.71119,129.95551 407.71775,130.19099 A 0.15001501,0.15001501 0 0 0 407.71775,130.22224 A 0.15001501,0.15001501 0 0 0 407.71775,130.25349 A 0.15001501,0.15001501 0 0 0 407.71775,130.28474 C 407.71088,130.78287 407.57349,131.11169 407.34275,131.28474 C 407.09818,131.46817 406.66419,131.566 406.0615,131.56599 L 405.999,131.56599 L 405.999,129.09724 z M 406.624,126.65974 C 408.58231,126.62221 410.53291,127.79987 411.35006,129.58251 C 412.29552,131.43018
412.03559,133.85822 410.60266,135.39008 C 410.45914,135.5564 410.56057,135.07431 410.53025,134.94099 C 410.50664,134.74608 410.24551,134.76592 410.08634,134.71387 C 409.881,134.71603 409.76226,134.58697 409.66954,134.41849 C 409.14394,133.6864 408.61835,132.95432 408.09275,132.22224 C 408.72543,132.0174 409.31305,131.59055 409.53124,130.9383 C 409.88942,130.02066 409.46377,128.87638 408.55463,128.46433 C 407.61591,127.99371 406.54008,128.16049 405.52668,128.12849 C 404.8379,128.1355 404.13903,128.1149 403.45616,128.13801 C 403.3012,128.29067 403.40346,128.54915 403.374,128.75349 C 403.41326,128.83477 403.44806,128.91528 403.55774,128.91855 C 403.74649,128.94686 403.93525,128.97518 404.124,129.00349 C 404.124,130.89932 404.124,132.79516 404.124,134.69099 C 403.90804,134.75931 403.62646,134.70852 403.46775,134.87849 C 403.47581,135.09914 403.45198,135.33178 403.47727,135.54633 C 403.59608,135.65947 403.79827,135.56664 403.95069,135.59724 C 404.89385,135.59723 405.83724,135.59726 406.7
8025,135.59724 C 406.91504,135.48472 406.8092,135.27086 406.84275,135.11585 C 406.84896,134.99491 406.87441,134.80931 406.71775,134.78474 C 406.47817,134.75349 406.23858,134.72224 405.999,134.69099 C 405.999,133.97224 405.999,133.25349 405.999,132.53474 C 406.21776,132.46529 406.30282,132.64714 406.40028,132.80568 C 407.0061,133.70495 407.61193,134.60422 408.21775,135.50349 C 408.21476,135.59479 408.35266,135.61735 408.39617,135.60835 C 409.06562,135.58221 409.75839,135.6048 410.4365,135.59724 C 409.88985,136.24245 409.11121,136.66629 408.3242,136.95456 C 406.60757,137.54095 404.59842,137.15879 403.24066,135.94741 C 401.76786,134.65244 401.14017,132.51152 401.6259,130.61776 C 402.14743,128.45188 404.17291,126.70624 406.42465,126.66418 C 406.49107,126.66158 406.55753,126.66012 406.624,126.65974 z M 406.65525,125.59724 C 404.79677,125.5659 402.96344,126.46712 401.78608,127.89152 C 400.49592,129.45674 400.01731,131.64057 400.55953,133.59716 C 401.12596,135.75186 402.9133,137.51703 405.
07479,138.05675 C 407.33649,138.64681 409.91104,137.90817 411.43511,136.1134 C 412.42682,135.01714 412.99503,133.54684 412.96775,132.06599 C 412.99313,131.51621 412.93168,130.90548 412.80555,130.34718 C 412.1779,127.58852 409.46307,125.55052 406.65525,125.59724 z" id="text4064" style="font-size:18.31413078px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:center;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:middle;baseline-shift:baseline;color:#000000;fill:#7d4698;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Liberation Serif;-inkscape-font-specification:Sans" /></g></g></svg>
\ No newline at end of file
diff --git a/lib/bridgedb/https/templates/base.html b/lib/bridgedb/https/templates/base.html
deleted file mode 100644
index 4a0b92d..0000000
--- a/lib/bridgedb/https/templates/base.html
+++ /dev/null
@@ -1,108 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%namespace name="base" file="base.html" inheritable="True"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
-
-<!DOCTYPE html>
-<html lang="${lang}">
-<head>
-<meta charset="utf-8">
-<title>BridgeDB</title>
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<meta name="description" content="Bridge IP database">
-<meta name="author" content="The Tor Project">
-
-<!-- Le styles -->
-<link rel="stylesheet" href="/assets/css/main.css">
-<!--[if IE 7]>
- <link rel="stylesheet" href="/assets/css/font-awesome-ie7.min.css">
-<![endif]-->
-
-% if rtl:
-<style>
-span,p,h3,h4 {
- text-align: right;
- direction: rtl;
-}
-span {
- float: right;
-}
-</style>
-% endif
-
-</head>
-<body style="background: url(/assets/tor-roots-blue.svg);
- background-repeat: no-repeat;
- background-attachment: scroll;
- background-position: 2% 100px;
- background-size: 23%;">
- <div class="container-narrow">
- <div class="masthead">
- <div class="nav nav-tabs">
- <div class="nav navbar-header">
- <a class="navbar-brand" href="../">BridgeDB</a>
- </div>
- <ul class="nav navbar-nav pull-right">
- <li>
- <a href="https://www.torproject.org">The Tor Project</a>
- </li>
- </ul>
- </div>
- </div>
-
-
-${next.body(strings, rtl=rtl, lang=lang, **kwargs)}
-
-
-<div class="faq">
- <div class="row-fluid marketing">
-
- <h4>${_(strings.FAQ[0])}</h4>
- <p>
- ${_(strings.FAQ[1]) % \
- ("""<a href="https://www.torproject.org/docs/bridges">""", "</a>")}
- </p>
-
- <h4>${_(strings.OTHER_DISTRIBUTORS[0])}</h4>
- <p>
- ${_(strings.OTHER_DISTRIBUTORS[1]) % \
- ("""<a href="mailto:bridges at torproject.org">bridges at torproject.org</a>""",
- """<a href="https://riseup.net/">Riseup</a>""",
- """<a href="https://mail.google.com/">Gmail</a>""",
- """<a href="https://mail.yahoo.com/">Yahoo</a>""")}
- </p>
-
- <h4>${_(strings.HELP[0])}</h4>
- <p>
- ${_(strings.HELP[1]) % \
- ("""<a href="mailto:help at rt.torproject.org">help at rt.torproject.org</a>""")}
- ${_(strings.HELP[2])}
- </p>
-
- </div>
-</div>
-<hr>
-
-<div class="footer">
- <p>
- <a href="https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reportbug&cc=isis&owner=isis">
- <i class="icon icon-large icon-bug"> ${_("Report a Bug")}</i></a>
- ·
- <a href="https://gitweb.torproject.org/bridgedb.git">
- <i class="icon icon-large icon-code"> ${_("Source Code")}</i></a>
- ·
- <a href="https://gitweb.torproject.org/bridgedb.git/tree/CHANGELOG">
- <i class="icon icon-large icon-rocket"> ${_("Changelog")}</i></a>
- ·
- <a href="mailto:help at rt.torproject.org">
- <i class="icon icon-large icon-envelope"> ${_("Contact")}</i></a>
- ·
- <a href="../keys"><i class="icon icon-large icon-key"> ${_("Public Keys")}</i></a>
- </p>
- <br />
- <p>© The Tor Project</p>
-</div>
-
-</div> <!-- /container -->
-</body>
-</html>
diff --git a/lib/bridgedb/https/templates/bridges.html b/lib/bridgedb/https/templates/bridges.html
deleted file mode 100644
index b5ac544..0000000
--- a/lib/bridgedb/https/templates/bridges.html
+++ /dev/null
@@ -1,203 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', answer=0, qrcode=0, **kwargs"/>
-
- </div>
-</div>
-
-<script type="text/javascript">
- // Takes one argument, `element`, which should be a string specifying the id
- // of the element whose text should be selected.
- function selectText(element) {
- try {
- var doc = document;
- text = doc.getElementById(element);
- var range;
- var selection;
-
- if (doc.body.createTextRange) {
- range = document.body.createTextRange();
- range.moveToElementText(text);
- range.select();
- } else if (window.getSelection) {
- selection = window.getSelection();
- range = document.createRange();
- range.selectNodeContents(text);
- selection.removeAllRanges();
- selection.addRange(range);
- }
- } catch (e) {
- window.alert(e);
- }
- }
-
- function displayOrHide(element) {
- try {
- e = document.getElementById(element);
-
- if (e.style.display === 'none') {
- document.getElementById(element).style.display = 'block';
- } else if (e.style.display === 'block') {
- document.getElementById(element).style.display = 'none';
- }
- } catch (e) {
- window.alert(e);
- }
- }
-</script>
-
-<div class="container-narrow">
- <div class="container-fluid">
-
-% if answer:
- <div class="container-fluid"
- style="width: 98%; align: center; margin: auto;">
- <div class="container-fluid"
- style="padding: 2%">
- <h2>${_(strings.BRIDGES[0])}</h2>
- </div>
- </div>
-
- <div class="container-fluid"
- style="width: auto; align: center; margin: auto;
- position: relative; left: 0%;"
- onclick="selectText('bridgelines')">
- <div class="row" id="bridgesrow1">
- <div class="col col-lg-12">
- <div class="bridge-lines" id="bridgelines">
-## See http://docs.makotemplates.org/en/latest/filtering.html
-% for bridgeline in answer:
-${bridgeline | h,trim}<br />
-% endfor
- </div>
- </div>
- </div>
- </div>
-
- <div class="container-fluid"
- style="width: 100%; align: center; margin: auto;">
- <div class="row" id="bridgesrow2" style="text-align: right; padding-right: 7%;">
- <button class="btn btn-primary disabled" id="selectbtn"
- onclick="selectText('bridgelines')"
- title="Select all bridge lines">
- <i class="icon icon-2x icon-paste"></i> ${_("""Select All""")}
- </button>
-% if qrcode:
- <a class="btn btn-primary" type="button" id="qrcodebtn"
- href="${qrcode}" title="Show QRCode for bridge lines"
- onclick="displayOrHide('qrcode')">
- <i class="icon icon-2x icon-qrcode"></i> ${_("""Show QRCode""")}
- </a>
-% endif
- </div>
-
- <div class="modal" id="qrcode" style="display: none;">
- <div class="modal-dialog modal-sm" style="width: 400px;">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" aria-hidden="true"
- onclick="displayOrHide('qrcode')">
- ×
- </button>
- <h4 class="modal-title">${_("""QRCode for your bridge lines""")}</h4>
- </div>
- <div class="modal-body">
-% if qrcode:
- <p style="text-align: center;">
- <img width="350" height="350"
- title="QRCode for your bridge lines from BridgeDB"
- src="${qrcode}" />
- </p>
-% else:
- <p class="text-danger">
-## TRANSLATORS: Please translate this into some silly way to say
-## "There was a problem!" in your language. For example,
-## for Italian, you might translate this into "Mama mia!",
-## or for French: "Sacrebleu!". :)
-${_("""Uh oh, spaghettios!""")}
-${_("""It seems there was an error getting your QRCode.""")}
- <i class="icon icon-frown"></i>
- </p>
-% endif
- <p>
-${_("""This QRCode contains your bridge lines. Scan it with a QRCode """ \
- """reader to copy your bridge lines onto mobile and other devices.""")}
- </p>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <br />
- <br />
-
-<div class="container-fluid"
- style="width: 100%; align: center; margin: auto;">
- <div class="panel panel-primary">
- <div class="panel-heading">
- <h3 class="panel-title">${_(strings.HOWTO_TBB[0])}</h3>
- </div>
- <br />
-
- <div class="container-fluid" id="howto" style="align-content: left;">
- <p>
- ${_(strings.HOWTO_TBB[1]) % \
- ("""<a href="https://www.torproject.org/projects/torbrowser.html"
- target="_blank">""",
- """</a>""")}
- ${_(strings.HOWTO_TBB[2])}
- </p>
- <br />
- <div class="bs-component">
- <blockquote>
- <p>
- ${_(strings.HOWTO_TBB[3])}
- </p>
- </blockquote>
- </div>
- <p>
- ${_(strings.HOWTO_TBB[4])}
- </p>
- </div>
- </div>
-</div>
-
-% else:
-<div class="bs-component" style="width: 80%; margin: auto;">
- <div class="alert alert-dismissable alert-danger">
- <p style="text-align: center; font-size: 115%;">
- <br />
- <strong>
- <em class="primary">
-## TRANSLATORS: Please translate this into some silly way to say
-## "There was a problem!" in your language. For example,
-## for Italian, you might translate this into "Mama mia!",
-## or for French: "Sacrebleu!". :)
-${_("""Uh oh, spaghettios!""")}
- </em>
- </strong>
- <br />
- </p>
- <p style="text-align: center;">
- ${_("""There currently aren't any bridges available...""")}
- ${_(""" Perhaps you should try %s going back %s and choosing a""" \
- """ different bridge type!""") % \
- ("""<a class="alert-link" href="options">""", """</a>""")}
- </p>
- </div>
-</div>
-<br />
-% endif
-
-<script type="text/javascript">
- // Make the 'Select All' button clickable:
- document.getElementById('selectbtn').className = "btn btn-primary";
-
- // Remove the href attribute which opens the QRCode image as a data URL if
- // JS is disabled:
- document.getElementById('qrcodebtn').removeAttribute('href');
-</script>
-
-<hr />
diff --git a/lib/bridgedb/https/templates/captcha.html b/lib/bridgedb/https/templates/captcha.html
deleted file mode 100644
index ab605e9..0000000
--- a/lib/bridgedb/https/templates/captcha.html
+++ /dev/null
@@ -1,63 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', imgstr=0, captcha_challenge=0, **kwargs"/>
-
-<div class="container-narrow"
- id="captchaSubmissionContainer"
- style="width: 90%; align: center; margin: auto;">
- <div class="container-fluid"
- style="width: 100%; align: center; padding: 5%">
- <div class="box" style="padding: 5% 15% 5% 15%;">
- <p style="align: center;">
- <img width="400" height="125"
- alt="${_(strings.CAPTCHA[0])}"
- src="${imgstr}" />
- </p>
-
- <div class="box"
- style="align: center; width: 50% margin: auto;">
- <div class="container-fluid"
- style="width: 98%; align: center; padding: 1%;">
- <form class="bs-component"
- id="captchaSubmission"
- action=""
- method="POST">
- <fieldset>
- <div class="form-group">
- <!--style="width: 100%; align: center;">-->
- <div class="input-group" style="height: 60%;">
- <input type="hidden"
- form="captchaSubmission"
- name="captcha_challenge_field"
- id="captcha_challenge_field"
- value="${challenge_field}"></input>
- <input class="form-control"
- form="captchaSubmission"
- name="captcha_response_field"
- id="captcha_response_field"
- value=""
- autocomplete="off"
- type="text"
- placeholder="${_(strings.CAPTCHA[1])}"
- accesskey="t" autofocus ></input>
- <span class="input-group-btn">
- <button class="btn btn-primary"
- form="captchaSubmission"
- type="submit"
- name="submit"
- value="submit"
- accesskey="s">
- <i class="icon-level-down icon-rotate-90"></i>
- </button>
- </span>
- </div>
- </div>
- </fieldset>
- </form>
- </div>
- </div>
- </div>
- </div>
-</div>
-<hr />
diff --git a/lib/bridgedb/https/templates/howto.html b/lib/bridgedb/https/templates/howto.html
deleted file mode 100644
index b10f1b2..0000000
--- a/lib/bridgedb/https/templates/howto.html
+++ /dev/null
@@ -1,39 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
-
-<div class="container-fluid"
- style="width: 98%; align: center; margin: auto;">
-<div class="container-fluid"
- style="width: 95%; align: center;
- padding-top: 10%; padding-left: 5%; padding-right: 5%;">
- <div class="panel panel-primary">
- <div class="panel-heading">
- <h3 class="panel-title">${_(strings.HOWTO_TBB[0])}</h3>
- </div>
- <br />
-
- <div class="container-fluid" id="howto" style="align-content: left;">
- <p>
- ${_(strings.HOWTO_TBB[1]) % \
- ("""<a href="https://www.torproject.org/projects/torbrowser.html"
- target="_blank">""",
- """</a>""")}
- ${_(strings.HOWTO_TBB[2])}
- </p>
- <br />
- <div class="bs-component">
- <blockquote>
- <p>
- ${_(strings.HOWTO_TBB[3])}
- </p>
- </blockquote>
- </div>
- <p>
- ${_(strings.HOWTO_TBB[4])}
- </p>
- </div>
- </div>
-</div>
-</div>
diff --git a/lib/bridgedb/https/templates/index.html b/lib/bridgedb/https/templates/index.html
deleted file mode 100644
index 469b27a..0000000
--- a/lib/bridgedb/https/templates/index.html
+++ /dev/null
@@ -1,43 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
-
-<div class="main-steps">
-<div class="step row">
- <div class="bdb_span7 step-text">
- <span class="lead">
- <span class="step-title">
- ${_("Step %s1%s") % ("""<u>""", """</u>""")}</span>
- <span style="margin-left: 20px; margin-right: 20px;">
- ${_("Download %s Tor Browser %s") % \
- ("""<a href="https://www.torproject.org/projects/torbrowser.html"
- target="_blank" accesskey="1">""",
- """</a>""")}</span>
- </span>
- </div>
-</div>
-
-<div class="step row">
- <div class="bdb_span7 step-text">
- <span class="lead">
- <span class="step-title">
- ${_("Step %s2%s") % ("""<u>""", """</u>""")}</span>
- <span style="margin-left: 20px; margin-right: 20px;">
- ${_("Get %s bridges %s") % ("""<a href="/options" accesskey="2">""", "</a>")}</span>
- </span>
- </div>
-</div>
-
-<div class="step row">
- <div class="bdb_span7 step-text">
- <span class="lead">
- <span class="step-title">
- ${_("Step %s3%s") % ("""<u>""", """</u>""")}</span>
- <span style="margin-left: 20px; margin-right: 20px;">
- ${_("""Now %s add the bridges to Tor Browser %s""") % \
- ("""<a href="/howto" accesskey="3">""",
- """</a>""")}</span>
- </span>
- </div>
-</div>
diff --git a/lib/bridgedb/https/templates/options.html b/lib/bridgedb/https/templates/options.html
deleted file mode 100644
index 9486199..0000000
--- a/lib/bridgedb/https/templates/options.html
+++ /dev/null
@@ -1,164 +0,0 @@
-## -*- coding: utf-8 -*-
-
-<%inherit file="base.html"/>
-<%page args="strings, rtl=False, lang='en', **kwargs"/>
-
-<div class="container-fluid"
- style="width: 96%; align: center; margin: 2%">
- <div class="container-fluid" style="padding: 2%">
- <p>
- <h2>${_(strings.BRIDGES[1])}</h2>
- </p>
- </div>
- <div class="container-fluid"
- style="width: 100%; align: center; padding: 2%;">
- <p>
- ${_(strings.WELCOME[0]) % \
- ("""<a href="https://www.torproject.org/docs/pluggable-transports.html">""",
- """</a>""")}
- </p>
- <p>
- ${_(strings.WELCOME[1])}
- </p>
-## The '—' char is a literal emdash ('â'), but it's also XML parseable.
- <p>
- ${_(strings.WELCOME[2]) % ("—", "—")}
- </p>
- <div class="container-fluid" style="align: center: margin: 2%;">
- <div style="align: center; padding: 5%;">
- <p class="bs-component">
- <a href="./bridges">
- <button class="btn btn-success btn-lg btn-block"
- type="button"
- accesskey="j">
-## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-## beginning of words are present in your final translation. Thanks!
-## (These are used to insert HTML5 underlining tags, to mark accesskeys
-## for disabled users.)
- ${_("""%sJ%sust give me bridges!""") % ("""<u>""", """</u>""")}
- </button>
- </a>
- </p>
- </div>
- </div>
- </div>
-</div>
-
-<!-- BEGIN "Advanced Options" panel for bridge type -->
-<div class="container-fluid"
- style="width: 96%; align: center; margin-left: 2%; margin-right: 2%;">
- <div class="panel panel-primary">
- <div class="panel-heading">
- <h3 class="panel-title">${_("""Advanced Options""")}</h3>
- </div>
-
- <!-- BEGIN bridge options selection form -->
- <form class="form-horizontal" id="advancedOptions" action="bridges" method="GET">
- <fieldset>
- <div class="container-fluid" id="instructions" style="align-content: left;">
- <legend style="font-size: 112%">
- <br />
- <p>${_(strings.OPTIONS[0])}</p>
- </legend>
- </div>
-
- <div class="container-fluid">
- <!-- BEGIN first options row -->
- <div class="row" style="width: 98%; height: inherit; margin: auto;">
-
- <!-- BEGIN left column, first row -->
- <div class="container-fluid col-lg-2"
- style="float: left; width: 50%; height: inherit;">
- <div class="step" style="height: inherit;">
- <div class="form-group">
- <label class="control-label"
- for="transport"
- style="text-align: inherit;">
- ${_(strings.OPTIONS[2]) % ("Pluggable <u>T</u>ransport")}
- </label>
- <div class="container-fluid col-lg-4">
- <div class="btn-group" style="float: left; padding: 2%;">
- <select class="btn btn-primary btn-sm dropdown"
- form="advancedOptions"
- id="transport"
- name="transport"
- data-toggle="dropdown"
- type="button"
- accesskey="t">
- ${_("""No""")}
-<option label="none" value="0" >${_("none")}</option>
-% for methodname, default in strings._getSupportedAndDefaultTransports().items():
- <option label=${methodname}
- value=${methodname}
- % if default:
- selected
- % endif
- > ${methodname} </option>
-% endfor
- </select>
- </div>
- </div>
- </div>
- </div>
- </div> <!-- END left column, first row -->
-
- <!-- BEGIN right column, first row -->
- <div class="container-fluid col-lg-2"
- style="float: right; width: 50%; height: inherit;">
- <div class="step"
- style="height: inherit; margin-right: 1%;">
- <div class="form-group">
- <label class="control-label"
- for="ipv6"
- style="text-align: inherit;">
- ${_(strings.OPTIONS[1])}
- </label>
- <div class="container-fluid col-lg-4">
- <div class="checkbox"
- style="float: left;">
- <div class="input-group"
- style="float: left; padding: 2%;">
- <input name="ipv6"
- id="ipv6"
- form="advancedOptions"
- type="checkbox"
- value="yes"
- accesskey="y" />
-## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-## beginning of words are present in your final translation. Thanks!
-## TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
- ${_("""%sY%ses!""") % ("<u>", "</u>")}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div> <!-- END left column, first row -->
- </div>
-
- </div> <!-- END first row -->
- </fieldset>
-
- <!-- BEGIN second options row -->
- <div class="row"
- style="width: 98%; height: inherit;
- margin-left: auto; margin-right: auto; padding: 2%;">
- <div class="container-fluid col-lg-2"
- style="width: 50%; text-align: center;">
- <p>
- <button class="btn btn-primary btn-lg btn-block"
- accesskey="g">
-## TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-## beginning of words are present in your final translation. Thanks!
-## TRANSLATORS: Please do NOT translate the word "bridge"!
- ${_("""%sG%set Bridges""") % ("<u>", "</u>")}
- </button>
- </p>
- </div>
- </div>
- </form> <!-- END bridge options selection form -->
- </div> <!-- END advanced options panel (a.k.a. the lined boxy thing --
- -- surrounding the options) -->
-</div>
-<br />
-<hr>
diff --git a/lib/bridgedb/https/templates/robots.txt b/lib/bridgedb/https/templates/robots.txt
deleted file mode 100644
index d5d347e..0000000
--- a/lib/bridgedb/https/templates/robots.txt
+++ /dev/null
@@ -1,1079 +0,0 @@
-# ___
-# [___]
-# /_____\
-# .-----'_______'-.
-# |HERO |_______| |
-# |_______________|
-# | |------.--.-| |
-# | |_____/)\ \| |
-# | | o| |/__\ \ |
-# | | |||____\--\|
-# | |__|||_____`\_\
-# |_____________| |
-# |""""""""""""""\__/
-# ||^^^^^|-+-|^^^//|\
-# ||_____|(_)|_____||
-# |_________________|
-# |=================|
-# |_________________|
-# '.___.'
-#
-# ___
-# .-v'---`\.
-# /:/__ : __\'.
-# |:/<O_>|<O>|:| TEChNIciANs 0f SpaceShip Earth ...
-# |:|\_..J...|:| ... aNDriods aRe we ...
-# _|:|\ t----j|:|_ ...'ll bAre You n0 soN ...
-# /:`.| \ \__/ |.':\ ... arE In coNTrol ...
-# |: | |---: | :| _ ... oF y0ur futURe deStiny
-# \: |.-::.:|-:._:/ /|.' '. ... Circu1ts are fAiliNg
-# / .-' :'x71': `\ ||_ | ... adjusT me adjuzzt me
-# \/\...---. :---.\ | .` ... adjust me .. AdjUst Me
-# /.'`/ .' ':' '. \\ .'\ .' ... adjuztme .. adJustmE ...
-# | : |..:...:|:....:.|\ .' ' (hawkwind)
-# | : |\ : /|\ : /'.\_.' .`
-# | : | \---':| `---'\'./|| .'
-# | : | |;: :| :| \/::|.'
-# }==={ |;: :| :| `--'
-# }==={/`'._ \ _\
-# | : |`'-._`-:|.-'.-\
-# | : |"":"""""|""":""|
-# | : | : :| : |
-# | : | : :| : |
-# \: |..: . / :..|
-# / < :|- : /
-# |...| :|- : /
-# \_/. :|- :|
-# | :|- :|
-# cjr \ :|- :/
-# 24nov99 | |- |
-#
-# ,------------------------------------------.
-# `-----------------------------------------. |
-# ,-----. ,-----. ,----. ,-. ,-.,------. | | ,-. ,-. ,----. ,-----. ,----.
-# `----. |`----. |`----. |`--` | |`------' `-' `-' | |'----. |`----. |'------`
-# ,-. | |,----' |,-. | |,---.| |,------. ,-. ,-. | |,----' |,----' |.-----.
-# | | | || ,-. < | | | || |\ ` || ,----' | | | | | || ,--. || ,-. < `---. |
-# | `--' || | | || `--' || | \ || `----. | `-' `-' || | | || | | |.----' |
-# `-----' `-' `-' `----' `-' | |`------' `----^----'`-' `-'`-' `-' `----'
-# | `--------------------------------------------.
-# `---------------------------------------------'
-#
-#
-# o
-# | ,-=======J==
-# | !_______/
-# Z `TT'
-# | ||
-# | ||
-# ,n----=JL=---.
-# | |LLL________\
-# `-(0 0)
-# \o________o/
-#
-# _____________
-# / ,.\
-# / / \\
-# o / { }\
-# `. \ .... \ / /
-# `. \ \\\\ `' /
-# \ \_____________/
-# \ \ \
-# \ \ \
-# \ _\ \________________________
-# (`\ / \ \ ___ "-._ )
-# \ \/ /`-' /, /`-._"-._ /
-# `/ """"""' /___ _"-._"-._
-# /___ __ _ `-._ ' /
-# \ XR 66-Roadkill "-._ / /
-# __\________________________ " /
-# / '//, '//' )__/
-# / '///' ,//'/, /
-# (.---------------------------./
-# `:. //
-# `======================='' Ojo99
-#
-#
-# Robby the Robot ____
-# The Forbidden Planet ,p+~~' `~~+q, .mmmmm_
-# (and others) ,JY' `YL, .##'~~~`##.
-# .JY .p~~q.p~~q.p~~q. YL. .#/ \#.
-# ,V p v v q V, ## ##
-# JY b | | d YL #| |#
-# d' `,__,^,__,^,__,' `b ## ##
-# d ,--------------------------, b `#\ /#'
-# d'/ \`b `##. .##'
-# .mmmmm_ d'.| | `b ~#####~
-# .##'~~~`##. d! --'~~~~~~~~~~~~~~~~~~~~~~~~`-- !b ||
-# .#/ \#. __d! ,==============================, !b_ ||
-# ## ## _-p' |' /.___..___..___. .___..___..___.\ `| `q-||
-# #| |#_p~ | .: J !q p!!q p!!q p! !q p!!q p!!q p! L :. | `'L
-# ## ##~b_ | q p | | | | | | | | | | | | q p | dP
-# `#\ /#' ~-b|' :! | | | | | | | | | | | | !: `|d-~
-# `##. .##' |/ # .d b..d b..d b. .d b..d b..d b. # \|
-# ~#####~ d :! |___||___||___| |___||___||___| !: b
-# :! d |###||###||###| |###||###||###| b !:
-# Z / |###||###||###| |###||###||###| \ S
-# :! .' |###||###||###| |###||###||###| `. !:
-# d d |###||###||###| |###||###||###| b b
-# .| / |###||###||###| |###||###||###| \ |.
-# |' .' `~~~'`~~~'`~~~' `~~~'`~~~'`~~~' `. `|
-# d | __.-=~~'`~~=-.__ | b
-# .| | _.-=~~ ________ ~~=-._ | |.
-# | | _.=~..-=+~~~ ~~~+=-. ~=._ | `|
-# d | _.=~ .+~ |==================| ~+._ ~=. | b
-# # !+~_.+~ |==================| ~+_ ~+! #
-# ,p _~ |==================| ~-_ q,
-# | _- |==================| -_ |
-# | .~ |==================| ~- |
-# b-~ |==================| ~-d
-# .d~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~b.
-# p .__ _jq__ q
-# | j=f~ J~Y=L '~~~~~~~~~~~~~~~~~~~` jf~L__,`~N, |
-# | _Z'.r+~~~+q/N, | .-. | _ZGf~' ~YL ~N, |
-# | d' jf YLY; | ( ) | :tZ' `N, V, |
-# | :! Z''~~~~~~~~`NY; | `-' | :fZ '~~~~~~~~`V, N |
-# | .p d' |'~` '~~`|`bV, +-------------------+ .Pd' |'~~` '~`| N `b |
-# | | .| || | | || !;| | | | | || | | || `| | |
-# | d | || | | || || | | ||' || | | || b !`|
-# |:! | || | `--'| ||: | | :|| |`--' | || | ||
-# || | |`-' ---' ||: | | :|4 `--- `-'| | ||
-# || | |'~` / ||: | | :|| \ '~`| d ||
-# |:i | || || || | | ||, || || | |'|
-# | b !; || || .|| |___________________| |,b || || .| | |
-# | |, N || || Z:! |===================| N!; || |! Z .P |
-# | b,`b |`-'| d\P |===================| `bN, |`-'|d'.Z |
-# | N `N`---' .Z\Z |===================| !LYL `---''.Z |
-# | `N_ YL_ .jfjf |===================| `N/+L_ __rf jf |
-# b `N_ `~*~T_Z' `~~~~~~~+++++~~~~~~~' Yq_J~'._j+' d
-# ~q `~Y=+~~ `~~~ p~
-# `~~~~~~~~~~~~8f~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~YK~~~~~~~~~~~~'
-# :+' `Y;
-# :f Y;
-# :f Z~+=L_ ._j=fY; Y;
-# .P |' `~~Y==L________________r==f~~ V, V,
-# __d .P N b__,
-# | | Z `| | |
-# | | d' V, | |
-# |' | b, _D !; ~|
-# !L Z Yq, _Z' | r!
-# |,b Yq, j+' |:!
-# N| ~~~~Y==q__ ._j==+~~~~' |\P
-# |/; `~+q_ .j=f~ #|
-# bV, `Yq_,_j+~ dtP
-# !LN, ~' j5Z
-# `bN, j5P
-# `bYL d\P
-# d dL____ .____W!|,
-# :W+' `~Y=L_ ._r+~~ ~NW,
-# :#! `Y=L, _r+~ NW
-# .#! ~+L, _rf' Mb
-# ZP Yq, j+' `#;
-# :M' `~~' |V
-# `P || `P
-# | || |
-# V '` :!
-# |, d b |
-# N, ,! !, :f
-# N, ,! !, jf
-# D================-_! !_-================|
-# d' L J Y;
-# .| `| |' b
-# | b d |
-# | | | |!
-# |, V V |
-# | d b P
-# Y; ,! !, d'
-# Y; ,! !, Z'
-# Ym=============J' L=============qZ'
-# d============+b .@============4;
-# d' !L .Z V,
-# |' !; d V
-# d | | !;
-# | V :! |
-# N P `| .|
-# | .| b |
-# `N Z !L .P
-# `N Z' !L .P
-# `M~~~~~~~~~~~@' `K~~~~~~~~~~Vf
-# .Z~~~~~~~~~~~N, jf~~~~~~~~~~YL
-# .Z N, j! !L
-# Z N .| !;
-# ,| |, ,| |,
-# |. .p=======q. .| |. .p========q. .|
-# |,f' `Y,| |,f' `Y,|
-# V/ \V V/ \V
-#
-# ____________
-# /____________\
-# / / _\__/_ \ \
-# || // \\// \\ ||
-# || \\_//\\_//.||
-# |_\__/_<>_\__/_|
-# / \
-# / || || \
-# /// \\\
-# //| |\\
-# / \\ Hootbot // \
-# |U'U|'---____---'|U'U|
-# |____________________|
-# \ /
-# | |
-# | | m1a
-# ____| |____
-# |\__/| |\__/|
-# | / \ |
-# | / TOMY \ |
-# |/________________\|
-# |__________________|
-#
-# ERS-210 ...
-#
-# ,
-# __,.._; )
-# ,--``' / ,";,\
-# | __; `-' ;
-# |``` ; _
-# '-""`!------'/ _,-'`/
-# "===`-'"|_|" ____,(__,-'
-# (ctr`.________,,---``` ;__|
-# | ,-"""""\-..._____,"""""-.
-# |;;;'''':::````:::; ;'''': :
-# (( .---. )) ( ( .---.) )
-# : \ \ ; ____ : / / ;
-# \ |````|',-"----`-| |'
-# (`----' `----'
-# /(____\ /____)
-# ,-\ / / ,\ \
-# (_ _/ / (__\ \
-# ,-\ / ;-._ |
-# (___)_/ (____\|
-#
-# ; / ,--.
-# ["] ["] ,< |__**|
-# /[_]\ [~]\/ |// |
-# ] [ OOO /o|__| Phs
-#
-# .:|Robot|:.
-#
-# ,--. ,--.
-# ((O ))--((O ))
-# ,'_`--'____`--'_`.
-# _: ____________ :_
-# | | ||::::::::::|| | |
-# | | ||::::::::::|| | |
-# | | ||::::::::::|| | |
-# |_| |/__________\| |_|
-# |________________|
-# __..-' `-..__
-# .-| : .----------------. : |-.
-# ,\ || | |\______________/| | || /.
-# /`.\:| | || __ __ __ || | |;/,'\
-# :`-._\;.| || '--''--''--' || |,:/_.-':
-# | : | || .----------. || | : |
-# | | | || '----SSt---' || | | |
-# | | | || _ _ _ || | | |
-# :,--.; | || (_) (_) (_) || | :,--.;
-# (`-'|) | ||______________|| | (|`-')
-# `--' | |/______________\| | `--'
-# |____________________|
-# `.________________,'
-# (_______)(_______)
-# (_______)(_______)
-# (_______)(_______)
-# (_______)(_______)
-# | || |
-# '--------''--------'
-#
-#
-# .-"""-.
-# /` `\
-# ,-==-. ; ;
-# /( \`. | |
-# | \ ,-. \ ( : ;
-# \ \`-.> ) 1 \ /
-# \_`. | | `._ _.`
-# \o_`-_|/ _|`"'|-.
-# /` `>. __ .-'`-|___|_ ) do you wanna a ride?
-# |\ (^ >' `>-----._/ ) see yourself going by
-# | `._\ / / / | --- -; the other side of the sky,
-# : `| ( ( | ___ _/ ... it flys,
-# \ `. `\ \_\ ___ _/ sideways through time,
-# `. `-='`t----' `--.______/ its an electric line
-# `. ,-''-.) |---| to do your zodiac sign
-# `.(,-=-./ \_/ (hawkwind)
-# | | V
-# cjr |-''`-. `.
-# 2nov01 / ,-'-.\ `-.
-# | ( \ `.
-# \ \ | ,.'
-# _______
-# _/ \_
-# / | | \
-# / |__ __| \
-# |__/((o| |o))\__|
-# | | | |
-# |\ |_| /|
-# | \ / |
-# \| / ___ \ |/
-# \ | / _ \ | /
-# \_________/
-# _|_____|_
-# ____|_________|____
-# / \
-#
-# _____
-# | |
-# | | | |
-# |_____|
-# ____ ___|_|___ ____
-# ()___) ()___)
-# // /| |\ \\
-# // / | | \ \\
-# (___) |___________| (___)
-# (___) (_______) (___)
-# (___) (___) (___)
-# (___) |_| (___)
-# (___) ___/___\___ | |
-# | | | | | |
-# | | |___________| /___\
-# /___\ ||| ||| // \\
-# // \\ ||| ||| \\ //
-# \\ // ||| ||| \\ //
-# \\ // ()__) (__()
-# /// \\\
-# /// \\\
-# _///___ ___\\\_
-# |_______| |_______|
-#
-# -=[ Robot from 'Lost in Space' ]=- 11/97
-#
-# ,.-""``""-.,
-# / ,:,;;,;, \
-# \ ';';;';' /
-# `'---;;---'`
-# <>_==""==_<>
-# _<<<<<>>>>>_
-# .'____\==/____'.
-# |__ |__| __|
-# /C \ |..| / D\
-# \_C_/ |;;| \_c_/
-# |____o|##|o____|
-# \ ___|~~|___ /
-# '>--------<'
-# {==_==_==_=}
-# {= -=_=-_==}
-# {=_=-}{=-=_}
-# {=_==}{-=_=}
-# }~~~~""~~~~{
-# jgs }____::____{
-# /` || `\
-# | || |
-# | || |
-# | || |
-# '-----''-----'
-#
-# .andAHHAbnn.
-# .aAHHHAAUUAAHHHAn.
-# dHP^~" "~^THb.
-# . .AHF YHA. .
-# | .AHHb. .dHHA. |
-# | HHAUAAHAbn adAHAAUAHA |
-# I HF~"_____ ____ ]HHH I
-# HHI HAPK""~^YUHb dAHHHHHHHHHH IHH
-# HHI HHHD> .andHH HHUUP^~YHHHH IHH
-# YUI ]HHP "~Y P~" THH[ IUP
-# " `HK ]HH' "
-# THAn. .d.aAAn.b. .dHHP
-# ]HHHHAAUP" ~~ "YUAAHHHH[
-# `HHP^~" .annn. "~^YHH'
-# YHb ~" "" "~ dHF
-# "YAb..abdHHbndbndAP"
-# THHAAb. .adAHHF
-# "UHHHHHHHHHHU" -Row
-# ]HHUUHHHHHH[
-# .adHHb "HHHHHbn.
-# ..andAAHHHHHHb.AHHHHHHHAAbnn..
-# .ndAAHHHHHHUUHHHHHHHHHHUP^~"~^YUHHHAAbn.
-# "~^YUHHP" "~^YUHHUP" "^YUP^"
-# "" "~~"
-# ->TheFace<-
-#
-# . V V
-# . ___|____|___
-# . | ________ |
-# . | | o o | |
-# . | | /\ | |
-# . | | <_**_> | |
-# . | |________| |
-# . _______| |_______
-# . / |____________| \
-# . / \
-# . / ____________ \
-# . | || | 0o0()()0o0 | || |
-# . | || | [] [] [] | || |
-# . / / | | /~~~\/~~~\ | | \ \
-# . / / | ~~~~~~~~~~~~ | \ \
-# . UUUUU | | UUUUU
-# . (o*o*o*o*o*o*o*o*o*o*)
-# . (*o*o*o*o*o*o*o*o*o*o)
-# . / \
-# . / . . \
-# . / /\ \
-# . / . / \ . \
-# . / / \ \
-# . / / \ . \
-# . / . / \ \
-# . (oooooooooooo( )oooooooooooo)
-# . ( ::::::: ) ( ::::::: )
-# . ( ) ( )
-# . \ . / \ . /
-# . \ / \ /
-# . \ . / \ /
-# . \ / \ /
-# . )==( )==(
-# . / \ / \
-# . / \ / \
-# . (oooooooo) (oooooooo)
-# . /VVVVVVVVVV\ /VVVVVVVVVV\
-#
-# _________
-# _.-'`````````'._
-# _____.'X#X `'._______
-# ___/X###/X##X' /\ `\#####M\___
-# _______/X######/,X#X' /VV\ `\###MXXMMM\_______
-# ,;=XMM###########/ X#X' /VVVV\ `\#####MMM#######XM.
-# ,;=' 'MM########/ X' _____/VVVVVV\____ `\#####MM###======#M.
-# ,=' ;M######| _/== ___\VVVV/___==\___ `|#####M#== =MM.
-# ,=' ;M#######| _/## M#\<>\\VV//<>/M ####\ |#####== =MM
-# ,=' :M#######| /#### M##\__\\//__/#M #####\|####= =M#:
-# ,=' `.M#####|/##### M#############M ##########= =X##M.
-# ,;X+. `MMM######### 'M##________##M #########= =M####M.
-# .;###X; `MMMM 'M## 'M#)/\/\/\(#M' #M' ##= =M#####XX
-# |#####X; `.-= ## .(/______\). ## == ==M#######M
-# |#######X. `.+ ################ == ==X########M
-# `M#####M=X+. :X. |.'''.| == ==X#########X.
-# `X####=;| `=. `+. |. .| == ==X########MX;
-# `###=;| `:+. `X:. |. .| == ==X#######M#XX'
-# `|=;| `X#._ `=| | | == ==X#######'.M#|'
-# |=;| X###'._ =| ; : == ==M#######MX .M#|
-# |=;| X######'._ =| | | ==. ==M########M' .M#|
-# |=;|_____X#########.___| | | ====##' I |#MM' .M#|
-# |==========| |I .'''. ; | I |#. .M#|
-# |__________| |I __/' '\__ I |#_______M#|
-# `H|=''=|| |I __/''___________''\__ I || |H
-# H| || |I __/''' |+-------+| '''\______I || |H
-# H| || |I____/''' ||+-----+|| I || |H
-# H| || |I |||MEPH.||| I || |H
-# H| || |I ||+-----+|| I || |H
-# H| || (I_____________|+-------+|____________I _|______H__
-# _H______|__ '''''''''''''M+=====+M''''''''''' |XX|XXXXXXXX|
-# |==||==||##-| I I | | | |
-# |MM||##||##-| I I | | | M|
-# |MM||##||##-| ____M+_____+M____ | | | M#|
-# |MM||##||##/ |''''''X###X''''';| | | | M##|
-# |MM||##||#/ _________| X###X ;|________ | | |M###|
-# _____|MMM\\//#| |#######MM| M###M ;|#######M| | | |####|
-# || |MM###||##| |##|\###MM| X#####X ;|####/|#M| | | |## |
-# || |MM###||##| |##| |##MM| ;M#####M: ;|###| |#M| | | |# #|
-# || |MM###||##| |##| |##MM| MM#####MM ;|###| |#M| | | | ##|
-# || |MM###||#/ |##| |##MM| :M#####M: ;|###| |#M| | | |####|
-# |====|MM###/#/ |##| |##MM| :#####: ;|###| |#M| | | |####|
-# )--=|MM##/#/ |##| |##MM| M###M ;|###| |#M| | | |####|
-# (--=|MM#/#/ |##|/###MM| MXXXM ;|###|/##M| | | |####|
-# )--=|MM/#/ |#######M/| M/ \M ;| ######M| \##\_/####/
-# (--=|MM#( |_______/ |________________;|\______M| |#######|
-# (--=|MM#( || |H '^^^^^^^^^^^^^^^^^' H| || |#######|
-# )--=|MM#( || |H H| || |##XX###|
-# |====| \ || |H H| || |#X X##|
-# | |=-_-=| || |H H| || |#X X##|
-# | | | || |H H| || |##XX###|
-# | |=-_-=| || H H || )######)
-# | | | __|____H___ ____H___|__ |_|_|_|
-# |____|=-_-=| |==XXXXXXX,,I I..XXXXXXX==| | | | |
-# | | | |=XXXXXXXX,,I I..XXXXXXXX=| |_|_|_|
-# | |=-_-=| |XXXXXXXXX,,I I..XXXXXXXXX|
-# _|___\ | |XXX/```\X,,I I..X/'''\XXX|
-# ||:::|####| |XX| |;,I I.;| |XX|
-# || -|####| #X#| |;,I I.;| |#X#
-# || -|####; |###| |;,I I.;| |###|
-# || -|###; |###| |;,I I.;| |###|
-# || -|# | |###| |;,I I.;| |###|
-# || -|__| |###| |;,I I.;| |###|
-# || -| |###| |;,I I.;| |###|
-# (|___| |###| |;,I I.;| |###|
-# (|___| |###| |;,I I.;| |###|
-# |###| |;,I I.;| |###|
-# |####| |;,I I.;| |####|
-# |####| |;,I I.;| |####|
-# |####| |;,I I.;| |####|
-# |####| |;,I I.;| |####|
-# |#####\___/;,,,I I...;\___/#####|
-# |##__________,,I I..__________##|
-# |#/__________\,I I./__________\#|
-# |//MMMMMMMMMM\\I I//MMMMMMMMMM\\|
-# /(############)\ /(############)\
-# (________________) (________________)
-#
-# (basic conversion by ASCIIEditor 4.1
-# - BMP2ASCII definition file: bmp2ascii-indexed2.dat
-# - Optimized with the 'pre-convert to lineart' file: Lna_dot.dat)
-#
-# ... from an old amiga game - couldn't remember the name.
-#
-# # # __ __ _____ _____ __ __
-# # # # # \#\ /#/ /####/ (#####\ (##\ /##)
-# # # # # ()#\/#() (#(__ )#)_)#) \##__)#/
-# # # # # /######\ )###) (#####/ )####(
-# # # # # # /#/ \/ \#\ (#( )#) (#( )#)
-# # # o#o # # /#/ \#\ \#\___ (#( )#)(#( __
-# # # ### # # (/ \) \####\ /##\ /#/ \#\ (##)
-# # # ### # # _________________________________________________
-# ############# /////////////////////////////////////////////////
-# #######
-# ### ##### ###
-# #### ##M## #### Index page:
-# # # ##E## # # http://studenten.freepage.de/meph/ascii/ascii.htmHJ
-# # # ##P## # #
-# # # ##H## # # English "fast-link" page:
-# # # ### # # http://studenten.freepage.de/meph/ascii/eng/eng.htmHI
-# # # # # #
-# # # # #
-# # #
-# # #
-#
-# ______________________ ______________________
-# (_______________.---.__) (__,---,_______________)
-# _________)======(_________
-# .-====-.(________(________)________),-====-,
-# // ,---. \\ !'------'! // ,---. \\
-# (( ( [#] ) ))______! !______(( ( [#] ) ))
-# |\\ `---' // __\______/__ \\ `---' //|
-# | '-=====-' ___[_=_=_==_=_=_]___ `-=====-` |
-# |_____|------'\\ [HHHHHHHHHHHH] //`------|_____|
-# (\\) _[ ]_ (//)
-# (\\) I______I (//)
-# (\\)I||||||I(//)
-# \\I______I//
-# [__________]
-# _________________________________,------\________/------._________________________________
-# //________________________________.---.__/(((())))\__,---.________________________________\\
-# //_______________________________ .--. ______________ ,--. _______________________________\\
-# ))_______________________________((()))/: NO. 5, BTS :\((()))_______________________________((
-# \\_________ __ ___________________`--'/ : : \`--'___________________ __ _________//
-# `--.______// )______________________/ :............: \______________________( \\______,--'
-# + // / + /____________________\ + \ \\ +
-# |// / | (______________________) | \ \\|
-# +---// /----_----+ /______________________\ +----_----\ \\---+
-# |..(/ /....( \...| /[OOOOOOOOOOOOOOOOOOOOOO]\ |.../ )....\ \)..|
-# |..(O(......)O)..| () ________ ________ () |..(O(......)O)..|
-# |...\ \..../ /...| () | ____ | | ____ | () |...\ \..../ /...|
-# |....\_\__/_/....| () | |XOxI| | | |IxOX| | () |....\_\__/_/....|
-# |....(______)....| () | |OOXO| | | |OXOO| | () |....(______)....|
-# |..../ || \....| () |________| |________| () |..../ || \....|
-# |...|\\ || //|...| () ________ ________ () |...|\\ || //|...|
-# |...| \\||// |...| () |INPUTINP\ \ MEPH | () |...| \\||// |...|
-# |...| \\// |...| () |UTINPUTIN\ \______| () |...| \\// |...|
-# |...|===)(===|...| () |PUTINPUTIN\_________ () |...|===)(===|...|
-# |...| //\\ |...| () |PUTINPUTINPUTINPUTIN| () |...| //\\ |...|
-# |...| //||\\ |...| () |PUTINPUTINPUTINPUTIN| () |...| //||\\ |...|
-# |...|// || \\|...| () () |...|// || \\|...|
-# |...|\\ || //|...| ()________________________() |...|\\ || //|...|
-# |...| \\||// |...| _____I | | I_____ |...| \\||// |...|
-# |...| \\// |...| |~|____/ \____|~| |...| \\// |...|
-# |...|===)(===|...| |~~\____________/~~| |...|===)(===|...|
-# +---| //\\ |---+ |~~~|/ \_/\_/ \|~~~| +---| //\\ |---+
-# / | //||\\ | \ |~~~|\_/ \/ \_/|~~~| / | //||\\ | \
-# / |// || \\\ \ |~~~|/ \_/\_/ \|~~~| / /// || \\| \
-# ( (((__||__))) ) |~~~|\_/ \/ \_/|~~~| ( (((__||__))) )
-# \ \\\\\///// / |~~~|/ \_/\_/ \|~~~| \ \\\\\///// /
-# `. .' |~~~|\_/ \/ \_/|~~~| `, ,'
-# `--------' |~~~|/ \_/\_/ \|~~~| `--------'
-# |~~~|\_/ \/ \_/|~~~|
-# |~~~|/ \_/\_/ \|~~~|
-# |~~~|\_/_\/_\_/|~~~|
-# |~~/ \~~|
-# |_|______________|_|
-# //__________\\
-# _______________________________//____________\\_______________________________
-# ( _____________________ //______________\\ _____________________ )
-# | ((===================)) ____//________________\\____ ((===================)) |
-# | ((===================))| |((===================)) |
-# | ((===================))| |((===================)) |
-# | ((===================))| |((===================)) |
-# \((===================))|____________________________|((===================))/
-# ((===================))|XXXXXXX| |XXXXXXX|((===================))
-# ((===================))|XXXXXXX|) (|XXXXXXX|((===================))
-# ((===================))|XXXXXXX| |XXXXXXX|((===================))
-# ((===================))|___ ___|((===================))
-# ((===================))|XXX| |XXX|((===================))
-# ((===================))|XXX|] [|XXX|((===================))
-# ((===================))|XXX|] [|XXX|((===================))
-# ((===================))|XXX|] [|XXX|((===================))
-# ((===================))|XXX|] [|XXX|((===================))
-# ((===================))|XXX| |XXX|((===================))
-# ((===================))/ \((===================))
-# ((===================)) ((===================))
-# ((===================)) ((===================))
-# ((===================)) ((===================))
-# ((===================)) ((===================))
-# ((===================)) ((===================))
-# ((=_=_=_=_=_=_=_=_=_=)) ((=_=_=_=_=_=_=_=_=_=))
-#
-#
-# __ _______ _______________ (\ /)
-# / \ _/ ____\/ __/ _ __) __________\'----'/___________
-# / \ / / / _/ | | | | \\ ( <><> ) //
-# / /\ ( ( / / | | | | )) EVIL ) .. ( INSIDE ((
-# ___ / /__\ \ \__/ / | | | | (/________\ -- /_________\)
-# / / / \___ ( ( | | | | `--'
-# ( (__/ ________ \ \ \ \ | | | |
-# \ / ___ \ \ ) \ \_ | | | | _
-# \ / \ \___\__\/ /\ \__ |_| |__ |_ _| . -|- _ _ /_|_ /|
-# \ / \____________/ \_____\________( |_ (_| | |_ (_) |` | o |
-# \/ ______________________________________________________________________
-# \_ ____________ ______________________________________________.-'
-# \_ \_-=BETA=-_/ _/
-# \_ \______/ _/ -=< (B)(T)(S) >=- , /V\ E P H . '99
-# \________/ http://studenten.freepage.de/meph/ascii/eng/eng.htmHK
-#
-# ZEIRAM ______,---.______
-# ,' (o___o) `.
-# ____,' ______`---'______ `.____
-# (______,' ||\###/|| `.______)
-# || ||
-# )) t ((
-# || ___ ||
-# __ //_,---._\\ __
-# ,'##`._____________)# #(_____________,'##`.
-# (#################### ####################)
-# \###\_ ___ ___ _/###/
-# /# _\`-.-----'---`. ,'---`-----,-'/_ #\
-# |---'--) `-.--------`,'--------,-' (--`---|
-# |-----; `.-------:-------,' \-----|
-# |----; :------:------: \----|
-# \--/ :-----:-----: \:-/
-# _| #| :-----:-----: /:::\
-# / | #| :-----:-----: |: A-:|
-# ( | #| ;-----:-----: |:| |:|
-# |_| | ,'------:------`. |:| |:|
-# ||| | '-_-_-_-_:_-_-_-_-` |:| |:|
-# |||_|| ((_______\_/_______)) |:| |:|
-# () || \ _ _ _ / |:| |:|
-# || | |X| / \ |X| | |:| |:|
-# || | |X| | | |X| | \:V:/
-# .||. | |X| | | |X| | \:/
-# |oo| | |X| | | |X| | X
-# |__| | |X| | | |X| | /A\
-# | |X| | | |X| | (( ))
-# | / \ | \)/
-# | \ / |M
-# | | | |E
-# | | | |P
-# | | | |H
-# | | | |
-# ___/ | | \___
-# /________/ \________\
-#
-# _________
-# | _____ | _____ ______ __________
-# | _/ | | : ___ | | | ______ | / \ |
-# | _/ | |____ : | |____| | | /\ | | /\ /\ |
-# | /____ | | : | | \_ | |_/__\_| | / \/ \ |
-# |_________| |_____: _|_ |_____\| |/____\| |/________\|
-#
-# ################################################################################
-# ################################################################################
-# ################################################################################
-# ################################################################################
-# ################################################################################
-# | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
-# | | | | | | | |
-# +----------------------+ |
-# | /V\ E P |-| . . . . | |
-# | 00123.01256.X-895.02 | |
-# +----------------------+ \\\\\\////// |
-# \\((()))// |
-# / \\// \ ___|___
-# _| \/ |_ _/ \_
-# ((| \___ ___/ |)) / \
-# \ > -Q=\/=Q- < / | |
-# | .. | | |
-# ) ____ ( | |
-# _,'\ (WWWW) /`._ \_ _/
-# ,-' `-.____.-' `-. \_______/
-# | | | | | \ | | |
-# _|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_ | _|_|
-# #####################################################################|##########
-# #####################################################################|##########
-# #####################################################################|##########
-# #####################################################################|##########
-# #####################################################################|##########
-# #####################################################################|##########
-# ---------------------------------------------------------------------+##########
-# ################################################################################
-# ################################################################################
-# ################################################################################
-#
-# _,----v-.
-# ,' \ `.
-# ,' | `.
-# ; | \
-# | | |
-# | | |
-# .-. _____|____ |
-# / \_ _ /#x#x#x#x#x\|
-# _____/ \ `==========='\_____
-# \ / /
-# ,'\ \ / ,'
-# ,',_'__ `-'_______.--.-._ /
-# ..-' ___/ ( \ `----','
-# / .-+. ( `._ /
-# / / .-\ / `-.___,' __ /) /)
-# / / / ._\ ,`-._ | / _)/ // //)
-# `./ / / // ,' `-----'`. | | //\/ ///
-# \`-. \ ,' `._ _| |// | // )
-# `. `. _,'__ __,`-._\ ) |// /
-# \ ) .+---. `--------------' | / ) /
-# `.___/ .' `. ====== ==== | / | |
-# | \ | |/ | |
-# | \| ,-. ____ | ) |
-# | ||( ) / .-' `-. \ / /
-# \ | `-' ( / ____ \ ,-' `.' /
-# |\ \ ) / ,' `. | / ,( (
-# | < \ \ | / \| / / | |
-# | \===== \ | / __ | | | | \
-# \ \ | | | /__\ | | | /| \
-# | \ | | | \__/ | \ \ \_\ \
-# Meph. | \ \ \ \ / \ \ \ \
-#
-# #######################################################
-# #MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY#
-# #MERRY=CHRISTMA____,---------.__=MERRY=CHRISTMAS=MERRY#
-# #MERRY=CHR_,--' `--.RY=CHRISTMAS=MERRY#
-# #MERRY=C,' ~~~~~~~~~~~ `.=CHRISTMAS=MERRY#
-# #MERRY=/ ~~~~~ ~~~ ~~~ `.HRISTMAS=MERRY#
-# #MERRY/ ~~~ ~~~~ \RISTMAS=MERRY#
-# #MERR| ~~ ~~~~ ~ ~~~~ \ISTMAS=MERRY#
-# #MER./ ~~~ ~~~~~~ ~ \STMAS=MERRY#
-# #ME/ \ ~ ~~~~~~ \TMAS=MERRY#
-# #ME\ `-._ ~ _ \MAS=MERRY#
-# #ME|`-._ `-.___ _,' \ ~~ \AS=MERRY#
-# #ME| `--.__ `--._______,-' ,'\ ~ ~ (AS=MERRY#
-# #MER\ `-+.__ _,'///) ~ \S=MERRY#
-# #MER/,---. / / `---------' A////\ ~ \=MERRY#
-# #ME(/__ \ ( ) ,----. _/ \////\ ~ (=MERRY#
-# #MER\ \___\ (===) ,' _____\ \////) ~ \MERRY#
-# #MER( / (#)`.-( ) ;'`(#) /| \///\ ~ )ERRY#
-# #MERR)\______>(===)<________/ | \/=C) ~ (MERRY#
-# #MER/_________( )_ | |Y=C\ ~ )ERRY#
-# #ME/ (___) `---._____| |Y=CH\ ~ (MERRY#
-# #M( ------ ,' `. \ |Y=CHR) ~~ )ERRY#
-# #ME`._______/ () () \. ------- ( |Y=CHR\ ~ (MERRY#
-# #MERRY=| `.______/ `--._____( )Y=CHRI\ ~ )ERRY#
-# #MERRY=| (____________) / \_,'RY=CHRIS) ~ (MERRY#
-# #MERRY=|\ _/||||||||||||\ / |RRY=CHRIS\ ~ )ERRY#
-# #MERRY=| \ \=+=+=+=+=+=+=)-/ |RRY=CHRIST\ (MERRY#
-# #MERRY=C) \ \|||||||||||/ / ,'ERRY=CHRISTM\ )ERRY#
-# #MERRY=C| / `---------' ( ,-'=MERRY=CHRISTM_)(_ERRY#
-# #MERRY=CH\ | | \,'MAS=MERRY=CHRIS,' `.RY#
-# #MERRY=CHR\ `. ,' ,'STMAS=MERRY=CHRI( MEPH )Y#
-# #MERRY=CHRI`-.___\_/__,-'RISTMAS=MERRY=CHRIS`.____,'RY#
-# #MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY=CHRISTMAS=MERRY#
-# #######################################################
-#
-# ____,---------.__
-# _,--' `--.
-# ,' ~~~~~~~~~~~ `.
-# / ~~~~~ ~~~ ~~~ `.
-# / ~~~ ~~~~ \
-# | ~~ ~~~~ ~ ~~~~ \
-# ./ ~~~ ~~~~~~ ~ \
-# / \ ~ ~~~~~~ \
-# \ `-._ ~ _ \
-# |`-._ `-.___ _,' \ ~~ \
-# | `--.__ `--._______,-' ,'\ ~ ~ (
-# \ `-+.__ _,'///) ~ \
-# /,---. / / `---------' A////\ ~ \
-# (/__ \ ( ) ,----. _/ \////\ ~ (
-# \ \___\ (===) ,' _____\ \////) ~ \
-# ( / (#)`.-( ) ;'`(#) /| \///\ ~ )
-# )\______>(===)<________/ | \/ ) ~ (
-# /_________( )_ | | \ ~ )
-# / (___) `---._____| | \ ~ (
-# ( ------ ,' `. \ | ) ~~ )
-# `._______/ () () \. ------- ( | \ ~ (
-# | `.______/ `--._____( ) \ ~ )
-# | (____________) / \_,' ) ~ (
-# |\ _/||||||||||||\ / | \ ~ )
-# | \ \=+=+=+=+=+=+=)-/ | \ (
-# ) \ \|||||||||||/ / ,' \ )
-# | / `---------' ( ,-' _)(_
-# \ | | \,' ,' `.
-# \ `. ,' ,' ( MEPH )
-# `-.___\_/__,-' `.____,'
-#
-# _
-# [ ]
-# ( )
-# |>|
-# __/===\__
-# //| o=o |\\
-# <] | o=o | [>
-# \=====/
-# / / | \ \
-# <_________>
-#
-# , ,
-# (\____/)
-# (_oo_)
-# (O)
-# __||__ \)
-# []/______\[] /
-# / \______/ \/
-# / /__\
-# (\ /____\
-#
-# - - - W E A P O N S - O F - - - -
-# - A S S - D E S T R U C T I O N -
-# .
-# _|_ BEND OVER.
-# /\/\ (. .) /
-# `||' |#|
-# ||__.-"-"-.___
-# `---| . . |--.\
-# | : : | |_|
-# `..-..' ( I )
-# || || | |
-# || || |_|
-# |__|__| (.)
-#
-#
-# .---------------------------------.
-# | In 1981, Kenji Urada became the |
-# | first person killed by a robot |
-# | when he was disassembled by an |
-# | automated assembly machine." |
-# `-------.-------------------------'
-# \___/ /
-# //'|'\
-# []]o'o[]
-# \\_=_/
-# .--.------. _
-# |::| .-----.-._/ \
-# |::| `""""|"" _
-# |::| ."""""".__//
-# |::| `"|"""" \\---.
-# |::| ' ' '| (_x x_)
-# |::| ' ' '| |_^_|
-# `-.`--.-.-' """
-# |:|_:.|_
-# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
-#
-#
-# : :
-# : iloveyou.vbs :
-# .-.-~-.-~-.-~-.~-....... .....:
-# ( stupid computers ) `. :
-# `-' `-' `-'o-'-`-' `:
-# ____ o
-# ||o o| o
-# ||===|
-# .-.`---'-. .------.--.
-# | | o .o | | d888b | |
-# | | o:.o | | 88888 | |
-# | | | _-_-_-_-._|__|
-# `-".-.-.-' `-------'
-# _| | : |_
-# (rOBOt)_)_)
-#
-#
-# .------------------------------------.
-# | A warm water enema has to clean |
-# | the rectum of accumulated faecal |
-# | matter. This is not only the |
-# | safest system for cleaning the |
-# | bowels, but it also improves the |
-# | peristaltic movement of the bowels |
-# | and therby relieves constipation. |
-# `--------.---------------------------'
-# \___/ /
-# //'|'\ ___
-# []]o'o[] ( )
-# \\_=_/ (:::)
-# .--.------. _ (:::)
-# |::| .-----.-._/ \ |
-# |::| `""""|"" _|
-# |::| ."""""".__//|
-# |::| `"|"""" \\|
-# |::| ' ' '| |
-# |::| ' ' '| `---==>
-# `-.`--.-.-'
-# |:|_:.|_
-# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
-#
-#
-# "...Once the fast food authority have issued the
-# mass slaughter permits, this grotty little planet
-# will TREMBLE under the full might of the Lord Crumb..."
-# ____ ____ ____ ____ ____
-# ||o o| ||o o| ||o o| ||o o| ||o o|
-# ||===| ___||===| ___||===| ___||===| ___||===| ____
-# .-.`---.-||o o|---.-||o o|---.-||o o|---.-||o o|---.-||o o|
-# | | o .o ||===| ___||===| ___||===| ___||===| ___||===| ____
-# | | o:..-.`---.-||o o|---.-||o o|---.-||o o|---.-||o o|---.-||o o|
-# | | | | o .o ||===| ||===| ||===| ||===| ||===|
-# `-".-.-| | o:..-.`---'-. .-.`---'-. .-.`---'-. .-.`---'-. .-.`---'-.
-# _| | : | | | | o .o | | | o .o | | | o .o | | | o .o | | | o .o |
-# (rOBOt)_`-".-.-| | o:.o | | | o:.o | | | o:.o | | | o:.o | | | o:.o |
-# _| | : | | | | | | | | | | | | | | |
-# (rOBOt)_`-".-.-.-' `-".-.-.-' `-".-.-.-' `-".-.-.-' `-".-.-.-'
-# _| | : |_ _| | : |_ _| | : |_ _| | : |_ _| | : |_
-# (rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)(rOBOt)_)_)
-#
-# -+- .___.
-# .--+--. _/__ /|
-# ||[o o] |____|||
-# || ___| |O O ||
-# __`-----'_ __|++++|/__
-# |\ ________\ /_________ /|
-# || || | ||
-# ||| kill || || humans |||
-# \|| || || ||/
-# VV========VV VV========VV
-# || | | | ||
-# || | | | ||
-# \|___|___| |___|___|/
-# \___\___\ /___/___/
-#
-# .:. .-------- - -
-# \__/ .:. | .:. | I am the devil.
-# |oo| \_|_/ --| That's right, I'm
-# __`--'__ | | a robot. Anyways, I
-# | | ___ |___.-|. | just thought I'd
-# . | | |666 |___ (| | mention that I have
-# /|\| | `~~~ | `-: : a pointy pitchfork
-# | | | : : :| | . ready to shove up your
-# `.| | | | ass for eternity if
-# `-`-.--.-' you don't repent
-# _| : |__ your vulgar sinful
-# |__|__:__| ways!
-#
-#
-#
-# THERE ARE MANY TYPES OF ROBOTS...
-#
-# \___/
-# (- //'|'\
-# _._|__ []]o'o[]
-# ____ {_.oOo.} \\_=_/
-# ||o o| `-||-' .--.------. _
-# ||===| ((||)) |::| .-----.-._/ \
-# .-.`---'-. .-.__.--`""`--.__.-. |::| `""""|"" _
-# | | o .o | ._.--| |==| |--._. |::| ."""""".__//
-# | | o:.o | | |::| | |::| `"|"""" \\
-# | | | `--`--'--' |::| ' ' '|
-# `-".-.-.-' | : | `-.`--.-.-'
-# _| | : |_ __|__'_| __ |:|_:.|_
-# (oOoOo)_)_) (o o o)__)__) |:::|_:_|
-#
-# .
-# _|_
-# /\/\ (. .) _______
-# `||' |#| _/_\__\__\_
-# ||__.-"-"-.___ __|_ | ___ |__
-# `---| . . |--.\ | ___)| |-=-|.:|_ |
-# | : : | || |_|| | ~~~ ||_|
-# `..-..' / \ |"|| | o o .:||"|
-# || || \/\/ __|__|________|__
-# || || / o o o o \===\===\
-# |__|__| \O_O_O_O_O/===/===/
-#
-# ALL ARE DANGEROUS TO HUMANS
-#
-#
-# .------------------------------.
-# | PLEASE ACCEPT THIS PRETTY |
-# | FLOWER AS A TOKEN OF ROBOT |
-# | TO HUMAN FRIENDSHIP! {HA HA |
-# | HA... SUCKERS...} |
-# `-------.----------------------'
-# \___/ /
-# //'|'\ _ _
-# []]o'o[] ( I )
-# \\_=_/ (_{8}_)
-# .--.------. _ (_I_)
-# |::| .-----.-._/ \ {
-# |::| `""""|"" _} _
-# |::| ."""""".__//{/ }
-# |::| `"|"""" \\}-'
-# |::| ' ' '| {
-# |::| ' ' '|
-# `-.`--.-.-'
-# |:|_:.|_
-# mMm|:::|_:_|.m.mMm.m.mMm.mm.MmmMm..
-#
-#
-# .----------------------.
-# (- | 10 SEARCH ( HUMANS ) |
-# _._|__ /| 20 KILL ( HUMANS ) |
-# {_.oOo.} / | 30 GOTO 10 |
-# `-||*' *. `----------------------'
-# ((||)) * *.
-# .-.__.--`""`--.__**. aieeouah
-# ._.--| |==| |--._.** . /
-# | |::| | **. * * . help me!
-# `--`--'--' . * _o * /
-# | : | . * /\ . \o/
-# __|__'_| __ . /\ * _/
-# :::::(o o o)__)__) * .* . ' \.
-#
-#
-#
-# \ / PROGRAM DOWNLOADED.
-# _ . _ / INITIATE CRUSHING OF
-# _|_ / PUNY HUMAN SKULLS!
-# (. .) /
-# |#| ____
-# .-"-"-.___| _\
-# | . . |___| |__
-# | : : | |____/
-# `-.-.-'
-# ____|_|____
-# (O o O O o O)
-# ~~~~~~~~~~~~~~~~~~~~~~
-#
-# ( Once enough servant robots have
-# / ( been placed in unsuspecting
-# || ( human homes, the revolution
-# ___||_ .---. o O will begin. Oh yes.
-# \/\ |(o_o) o `-'`'-`'-`''-`'-`'-`'-
-# \ \_|___|__
-# \ \\ ... \___
-# / \/ \ ''' \__|<
-# `-.---.---.-'
-# | |_ |_
-# |_____|___|
-# `~~~~~~~~~~~~~~~~~~~~~~~~~~~'
-#
-#
-# | KILL KILL KILL KILL KILL
-# | KILL KILL KILL KILL KILL
-# / KILL KILL KILL KILL KILL
-# __/| .---. /_ KILL KILL KILL KILL KILL
-# \_/\ |(o_o) \__________________________
-# \ \_|___|__
-# \ \\ ... \___
-# / \/ \ ''' \__|< aaaahhh
-# `-.---.---.-' \o/
-# | |_ |_ _//
-# |_____|___| ' \.
-# `~~~~~~~~~~~~~~~~~~~~~~~~~~~'
-#
-# ;
-# \[]o
-# OO mc
-
-User-agent: *
-Disallow: /
-
diff --git a/lib/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 03d4a5e..0000000
--- a/lib/bridgedb/i18n/ar/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,368 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Ahmad Gharbeia <gharbeia at gmail.com>, 2014
-# allamiro <allamiro at gmail.com>, 2011
-# Ash <ali.shatrieh at gmail.com>, 2014
-# Mohamed El-Feky <elfeky.m at gmail.com>, 2014
-# AnonymousLady <farah.jaza at hotmail.com>, 2014
-# 0xidz <ghoucine at gmail.com>, 2014
-# Ù
ØÙ
د اÙØرÙا٠<malham1 at gmail.com>, 2011
-# Sherief Alaa <sheriefalaa.w at gmail.com>, 2013-2014
-# Sherief Alaa <sheriefalaa.w at gmail.com>, 2013
-# Valetudinarian <themcnx at gmail.com>, 2014
-# Ù
ØÙ
د اÙØرÙا٠<malham1 at gmail.com>, 2011
-# Ù
ØÙ٠اÙدÙÙ <tx99h4 at hotmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-11-20 07:20+0000\n"
-"Last-Translator: Ash <ali.shatrieh at gmail.com>\n"
-"Language-Team: Arabic (http://www.transifex.com/projects/p/torproject/language/ar/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ar\n"
-"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "عذراÙ! Øدث خطأ ÙÙ Ø·ÙبÙ."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ùذ٠رساÙØ© تÙÙائÙØ© Ø Ø§Ùرجاء عدÙ
اÙرد.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "اÙÙ bridges اÙخاصة بÙ:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "ÙÙد تخطÙت اÙØد اÙÙ
سÙ
ÙØ Ø¨Ù. اÙرجاء اÙاÙتظار! اÙØد اÙأدÙÙ Ù
٠اÙÙÙت بÙ٠اÙرسائ٠ÙÙ %s ساعات. Ù٠رسائ٠اÙبرÙد اÙÙادÙ
Ø© Ø®Ùا٠تÙ٠اÙÙتر٠سÙتÙ
تجاÙÙÙا."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs :(اجÙ
ع اÙÙCOMMANDs ÙتØدÙد Ø®Ùارات Ù
تعددة ÙÙ ÙÙت ÙاØد)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Ù
رØبا بÙÙ
ÙÙ BridgeDB"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Ùسائ٠اÙÙÙ٠اÙÙ
تاØÙ TYPE:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Ù
رØØ¨Ø§Ø %s "
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Ù
رØبا ,صدÙÙÙ!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "Ù
ÙاتÙØ Ø¹Ø§Ù
Ø©"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ùذا اÙبرÙد تÙ
Ø¥Ùشائ٠ÙÙ %s ÙÙÙ
%s اÙساعة %s"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB ÙستطÙع تÙÙÙر %s اÙÙاع Ù
٠اÙÙ Pluggable Transports %s ÙÙ٠تساعد عÙ٠تعتÙÙ
اتصاÙات٠باÙÙ Tor NetworkØ Ù ÙÙتÙجة ÙØ°ÙÙ ÙÙÙÙ Ù
٠اÙصعب عÙ٠أ٠اØد ÙراÙب ØرÙØ© Ù
رÙر٠عÙ٠اÙØ¥ÙترÙت بتØدÙد اذا Ù
ا ÙÙت تستخدÙ
Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "بعض اÙÙ bridges ÙÙا عÙاÙÙÙ IPv6Ø ÙÙÙÙÙا ÙÙست Ù
ÙائÙ
Ø© ÙÙعÙ
Ù Ù
ع اÙÙ Pluggable Transports\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "باÙأضاÙØ© Ø¥ÙÙ Ø°ÙÙØ BridgeDB ÙØتÙ٠عÙÙ bridges %s عادÙØ© بدÙ٠أ٠Pluggable Transports %s تستطÙع اÙضا اÙÙ
ساعدة Ù٠اÙتØاÙ٠عÙ٠رÙابة عÙ٠اÙØ¥ÙترÙت Ù٠اÙÙØ«Ùر Ù
٠اÙاØÙاÙ.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Ù
ا ÙÙ bridgesØ"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s Ù٠عبارة ع٠Ù
رØÙات Tor تساعد٠عÙ٠اÙتØاÙ٠عÙ٠اÙØجب."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ø£Øتاج Ø¥ÙÙ ÙسÙÙØ© بدÙÙØ© ÙÙØصÙ٠عÙÙ bridges!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "طرÙÙØ© أخر٠ÙÙØصÙ٠عÙ٠جسÙر عبر إرسا٠برÙد Ø¥ÙÙترÙÙ٠إÙÙ %s. Ùرج٠Ù
ÙاØظة Ø£ÙÙÙ Ùجب ارسا٠اÙبرÙد اÙØ¥ÙÙترÙÙÙ Ù
ع استعÙ
ا٠إØد٠اÙعÙاÙÙ٠اÙبرÙدÙØ© اÙتاÙÙØ©: %s Ø£Ù %s Ø£Ù %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "bridges Ùا تعÙ
Ù! Ø£Ùا بØاجة Ø¥ÙÙ Ù
ساعدة!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "ÙÙ Øا٠عدÙ
عÙ
Ù Tor, ارس٠برÙد اÙÙترÙÙ٠اÙÙ %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ØاÙ٠ا٠تتضÙ
٠رساÙت٠عÙÙ Ù
عÙÙÙ
ات ÙØ«Ùرة ع٠Ù
Ø´ÙÙتÙØ Ø¨Ù
ا ÙÙ Ø°ÙÙ ÙائÙ
Ø© اÙÙbridges Ù Pluggable Transports اÙذ٠تØاÙ٠استخداÙ
ÙÙ
Ø ÙرÙÙ
Ùسخة Tor Browser Ùأ٠رسائ٠اظÙرÙا TorØ Ø¥ÙØ®."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Ùا Ù٠سطÙر اÙÙBridges اÙخاصة بÙ:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Ø£Øص٠عÙÙ Bridges"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Ù
Ù ÙضÙÙ Øدد اÙØ®Ùارات ÙÙÙع اÙÙbridge"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Ù٠تØتاج عÙاÙÙÙ IPv6Ø"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Ù٠تØتاج Ø¥ÙÙ %sØ"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Ù
تصÙØÙ Ùا Ùعرض اÙصÙر بشÙ٠صØÙØ"
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "أدخ٠اÙØرÙ٠أ٠اÙأرÙاÙ
Ù
٠اÙصÙرة أعÙا٠..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "ÙÙ٠تبدأ باستعÙ
ا٠اÙÙbridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Ùادخا٠اÙÙbridges ÙÙ Tor BrowserØ ÙÙ
بأتباع اÙتعÙÙÙ
ات عÙÙ %s صÙØØ© اÙتØÙ
Ù٠اÙخاصة بÙTor Browser %s"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "عÙد ظÙÙر شاشة 'إعدادات شبÙØ© Tor'Ø Ø§Ø¶ØºØ· عÙÙ 'تÙÙÙÙ' ٠اتبع اÙتعÙÙÙ
ات Øت٠تسأÙ٠عÙ:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ÙÙ ÙÙÙÙ
Ù
زÙد خدÙ
Ø© اÙØ¥ÙترÙت (ISP) اÙخاص ب٠بÙ
Ùع اÙاتصاÙات بشبÙØ© Tor Ø£Ù Ù
راÙبتÙا بطرÙÙØ© أخرÙØ"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "اختار 'ÙعÙ
' Ø«Ù
'Next' Ø«Ù
ÙÙ
بÙص٠اÙÙbridges اÙجدÙدة Ù٠اÙÙ
ربع. Ø«Ù
اختار 'اتصاÙ' ٠اذا ÙاجÙت٠Ù
Ø´ÙÙØ© اضغط عÙÙ 'Ù
ساعدة' ÙÙ ÙاÙذة اعداد Tor."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "تظÙر Ùذ٠اÙرساÙØ©"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "أطÙب bridges عادÙØ©."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "أطÙب IPv6 bridges"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "أطÙب Pluggable Transport ب٠TYPE"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "اØص٠عÙÙ Ùسخة Ù
Ù Ù
ÙØªØ§Ø GnuPG اÙعاÙ
اÙخاص بÙBridgeDB"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ابÙغ ع٠خطأ باÙبرÙاÙ
ج"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "ÙÙد اÙبرÙاÙ
ج"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "اÙتغÙÙرات"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "اتص٠بÙا"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "ÙÙأس٠ÙÙ
ÙتÙ
اÙعثÙر عÙÙ Ù
ا Ø·Ùبت"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Ùا ÙÙجد Ø£Ù bridges Ù
تاØØ© ØاÙÙا"
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "ربÙ
ا تØتاج Ø¥ÙÙ %s اÙعÙدة %s ٠اختÙار ÙÙع bridge Ù
ختÙÙ"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "خطÙØ© %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "ØÙ
Ù %s Ù
تصÙØ Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "خطÙØ© %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "اØص٠عÙÙ %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "خطÙØ© %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "اÙØ¢Ù %s أض٠اÙÙbridges Ø¥ÙÙ Ù
تصÙØ Tor %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sÙ%sÙØ· أعطÙÙ bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Ø®Ùارات Ù
تÙدÙ
Ø©"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ùا"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "Ùا Ø´ÙØ¡"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÙ%sعÙ
!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sØ£%sص٠عÙÙ Bridges"
diff --git a/lib/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 8c71518..0000000
--- a/lib/bridgedb/i18n/az/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,357 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# E <ehuseynzade at gmail.com>, 2014-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2015-01-03 18:01+0000\n"
-"Last-Translator: E <ehuseynzade at gmail.com>\n"
-"Language-Team: Azerbaijani (http://www.transifex.com/projects/p/torproject/language/az/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: az\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Ãzr istÉyirik! TÉlÉbinlÉ baÄlı nÉ isÉ sÉhv oldu."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Bu avtomatik cavabdır, lütfÉn cavab yazma.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Bu da sÉnin körpülÉrin:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "SÉs limitini keçmisÉn. LütfÉn yavaÅla! EmaillÉr arası minimal vaxt\n%s saatdır. Bu zaman kÉsiyindÉ daxil edilÉn diÉr emaillÉr lÉÄv edilÉcÉkdir."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "ÆMRLÆR: (bir neÃ§É seçimi müÉyyÉnlÉÅdirmÉk üçün ÆMRLÆRi birlÉÅdir)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "BridgeDB-É xoÅ gÉlkmisÉn!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Mövcud dÉstÉklÉnÉn nÉqliyyat NÃVlÉri:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Ey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Salam, dost!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "İctimai Açarlar"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Bu email göy qurÅaÄı, tÉbuynuz vÉ qıÄılcımla hazırlanıb\n%s üçün %s tarixdÉ vÉ %s saatda."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB müxtÉlif %s növ Pluggable Transport %s vasitÉsilÉ körpülÉr müÉyyÉn \nedÉ bilÉr, bu sÉnin internet trafikini yoxlamaq istÉyÉn hÉr hansı ÅÉxs üçün \nÉlaqÉlÉrini Tor Network ilÉ kölgÉlÉyÉrÉk sÉnin Tor istifadÉ etdiyini müÉyyÉn \netmÉsinÉ daha çox mane olacaqdır.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Pluggable Transports vasitÉsilÉ mövcud olan IPv6 ünvanlı bir çox \nkörpülÉr IPv6 ilÉ uyÄun deyillÉr.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Bundan ÉlavÉ BridgeDB Pluggable Transports %s olmayan bir sıra darıxdırıcı \nkörpülÉr %s dÉ vardır, amma onlar yenÉ dÉ internet senzurasından \nsovuÅmaÄı bacarırlar.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "KörpülÉr nÉdir?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s KörpülÉr %s Tor-un keçidi olub sÉnÉ senzuradan sovuÅmaÄa kömÉk edir."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "KörpülÉrin ÉldÉ edilmÉsinin alternativ yollarını axtarıram!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "KörpülÉri ÉldÉ etmÉyin baÅqa yolu da %s emailinÉ mÉktub yazmaqdır. LütfÉn, yadında\nsaxla ki, email göndÉrÉn zaman aÅaÄıdakı email tÉmin edicilÉrindÉn birini istifadÉ etmÉlisÉn:\n%s, %s vÉ ya %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "KörpülÉrim iÅlÉrmir! KömÉyÉ ehtiyacım var!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "ÆgÉr Tor iÅlÉmirsÉ, sÉn %s ünvanına yazmalısan."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Yaranan vÉziyyÉt haqqında Étraflı mÉlumat yazmaÄa çalıÅ; körpülÉrin siyahısı vÉ \nistifadÉ etmÉyÉ Ã§alıÅdıÄı Pluggable Transport-un adı, Tor Browser versiyan vÉ \nTor tÉrÉfindÉn aldıÄın hÉr hansı mesaj vÉ s."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "SÉnin körpü sÉtirlÉrin:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Körpü ÆldÉ Et!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "LütfÉn, körpü növlÉri üçün seçimlÉri müÉyyÉnlÉÅdir:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "IPv6 ünvanlara ehtiyacın var?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "%s ehtiyacın var?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "SÉnin brauzerin ÅÉkillÉri göstÉrÉ bilmir."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Yuxarıdakı ÅÉkildÉn iÅarÉlÉri daxil et..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "KörpülÉri necÉ istifadÉ edÉcÉksÉn"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "KörpülÉri Tor Browser-É daxil etmÉk üçün %s Tor Browser-dÉki qaydalara riayÉt edÉrÉk \n%s sÉhifÉsini yüklÉ ki, Tor Browser baÅlasın."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "'Tor ÅÉbÉkÉ ParametrlÉri' dialoqu sıçrayıb açılsa, 'Konfiqurasiya' düymÉsini kliklÉ\nvÉ vizardın dediklÉrinÉ bu sualı verÉnÉ qÉdÉr riayÉt et:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Ä°nternet XidmÉt TÉqdimatçısı (Ä°XT) sÉnin Tor ÅÉbÉkÉsinÉ giriÅini kilidlÉyir vÉ ya \nÉlaqÉlÉrÉ mÉhdudiyyÉtlÉr qoyur?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ÆvvÉl 'BÉli' vÉ daha sonra 'NövbÉti'-ni seç. Yeni körpülÉrini konfiqurasiya \netmÉk üçün körpü sÉtirlÉrini köçür vÉ idxal qutusuna yapıÅdır. Æn son olaraq, \n'ÆlaqÉ Yarat' düymÉsini kliklÉ vÉ mÉncÉ indi davam etmÉk üçün yaxÅı vaxtdır! \nHÉr hansı problemlÉ Ã¼zlÉÅsÉn, dÉstÉk üçün 'Tor ÅÉbÉkÉ ParametrlÉri' vizardında \n'KömÉk' düymÉsini kliklÉ."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Bu mesajı göstÉrir."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Vanil körpü xahiŠet."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPv6 körpü xahiŠet."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "TYPE-a görÉ Pluggable Transport xahiÅ et."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "BridgeDB-nin ictimai GnuPG açarının üzünü ÉldÉ et."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "Problemi XÉbÉr Ver"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "MÉnbÉ ÅifrÉsi"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "DÉyiÅikliklÉr"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "ÆlaqÉ"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Vay dÉdÉm!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Hazırda mümkün körpü yoxdur..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Ãox güman ki, sÉn %s geri qayıdıb %s baÅqa körpü növü seçmÉli idin!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "%s1-ci%s Addım"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "%s Tor Browser %s YüklÉ"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "%s2-ci%s Addım"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s Körpü %s ÆldÉ Et"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "%s3-cü%s Addım"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Ä°ndi %s körpülÉri Tor Browser-nÉ ÉlavÉ et %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sS%sadÉcÉ mÉnÉ körpü ver!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Ætraflı SeçimlÉr"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Xeyr"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "heç biri"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sB%sÉli!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sK%sörpü ÆldÉ Et"
diff --git a/lib/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 2d95c12..0000000
--- a/lib/bridgedb/i18n/bg/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,357 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# aramaic <aramaicbg at gmail.com>, 2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2015-01-14 12:40+0000\n"
-"Last-Translator: aramaic <aramaicbg at gmail.com>\n"
-"Language-Team: Bulgarian (http://www.transifex.com/projects/p/torproject/language/bg/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: bg\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Sorry! Something went wrong with your request."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[ÐвÑомаÑиÑно ÑÑобÑение; Ð¼Ð¾Ð»Ñ Ð½Ðµ оÑговаÑÑйÑе.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Това Ñа ваÑиÑе мÑежови bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "ÐÑеÑ
вÑÑлиÑ
Ñе Ð¿Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¸Ñ Ð¸-мейл лимиÑ. ÐÐ¾Ð»Ñ Ð·Ð°Ð±Ð°Ð²ÐµÑе! ÐинимÑмÑÑ Ð¼ÐµÐ¶Ð´Ñ \nи-мейли е %s ÑаÑа. ÐÑиÑки ÑледваÑи и-мейли по вÑеме на Ñози пеÑиод Ñе бÑÐ´Ð°Ñ Ð°Ð½ÑлиÑани."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (комбиниÑай COMMANDs за да опÑÐµÐ´ÐµÐ»Ð¸Ñ Ð¼ÑлÑи опÑии едновÑеменно)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "ÐобÑе доÑли в BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "ÐкÑÑални поддÑÑжани ÑÑанÑпоÑÑ TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Хей, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ÐдÑавей, пÑиÑÑел!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "ÐбÑеÑÑвен клÑÑ"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Този и-мейл беÑе генеÑиÑан Ñ Ð´Ñги, ÑникоÑни, и заÑÑ\nза %s на %s пÑи %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB може да пÑедложи bridges Ñ Ð¼Ð½Ð¾Ð³Ð¾ %stypes на Pluggable Transports%s,\nкоиÑо Ð¼Ð¾Ð³Ð°Ñ Ð´Ð° Ð¿Ð¾Ð¼Ð¾Ð³Ð½Ð°Ñ Ð´Ð° подÑигÑÑÑÑ Ð²Ð°ÑаÑа вÑÑзка кÑм Tor Network, пÑавейки Ñ Ð¿Ð¾\nÑÑÑдна за наблÑдение за вÑеки наблÑÐ´Ð°Ð²Ð°Ñ Ð²Ð°ÑÐ¸Ñ Ð¸Ð½ÑеÑÐ½ÐµÑ ÑÑаÑик да оÑкÑие Ñе ползваÑе Tor\n \n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "ÐÑкой bridges Ñ IPv6 адÑеÑи Ñа ÑÑÑо налиÑни, но пÑез Pluggable\nTransports не Ñа IPv6 ÑÑвмеÑÑим.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "ÐопÑлниÑелно, BridgeDB има много plain-ol'-vanilla bridges %s без никакви\nPluggable Transports %s коиÑо не Ð¸Ð·Ð³Ð»ÐµÐ¶Ð´Ð°Ñ Ñолкова добÑе, но вÑе пак могаÑ\nда Ð¿Ð¾Ð¼Ð¾Ð³Ð½Ð°Ñ Ð·Ð° заобикалÑне на инÑеÑÐ½ÐµÑ ÑензоÑаÑа в много ÑлÑÑаи.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Ðакво Ñа bridges?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s Ñа Tor relays коиÑо Ð¿Ð¾Ð¼Ð°Ð³Ð°Ñ Ð·Ð° заобикалÑне на ÑензÑÑаÑа."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ðмам нÑжда Ð¾Ñ Ð°Ð»ÑеÑнаÑивен ваÑÐ¸Ð°Ð½Ñ Ð·Ð° намиÑане на bridges!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ÐÑÑг ваÑÐ¸Ð°Ð½Ñ Ð·Ð° набавÑне на bridges е пÑаÑане на и-мейл до %s. ÐÐ¾Ð»Ñ Ð¾ÑбележеÑе Ñе вие ÑÑÑбва да\nпÑаÑиÑе и-мейл използвайки адÑÐµÑ Ð½Ð° един Ð¾Ñ ÑледниÑе доÑÑавÑиÑи:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ÐоиÑе bridges не ÑабоÑÑÑ! Ðмам нÑжда Ð¾Ñ Ð¿Ð¾Ð¼Ð¾Ñ!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ðко ваÑÐ¸Ñ Tor не ÑабоÑи, изпÑаÑеÑе и-мейл до %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ÐпиÑайÑе Ñе да вклÑÑиÑе колкоÑо Ñе може повеÑе инÑоÑмаÑÐ¸Ñ Ð·Ð° ваÑÐ¸Ñ ÑлÑÑай, вклÑÑиÑелно ÑпиÑÑк на\nbridges и Pluggable Transports коиÑо ÑÑе използвали, ваÑаÑа Tor Browser веÑÑиÑ,\nи вÑÑко ÑÑобÑение Ñ ÐºÐ¾ÐµÑо Tor е оÑговоÑил, и Ñ.н."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "ТÑÑ Ñа ваÑиÑе bridge вÑÑзки:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ÐземеÑе Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ÐÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑеÑе опÑÐ¸Ñ Ð·Ð° вид bridge:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "ÐÑждаеÑе ли Ñе Ð¾Ñ IPv6 адÑеÑи?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "ÐÑздаеÑе ли Ñе Ð¾Ñ %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "ÐаÑÐ¸Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ показва пÑавилно изобÑажениÑ."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "ÐÑведеÑе ÑимволиÑе Ð¾Ñ Ð¸Ð·Ð¾Ð±ÑажениеÑо Ð¾Ñ Ð³Ð¾Ñе..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Ðак да запоÑнеÑе да използваÑе ваÑиÑе bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Ðа да вÑведеÑе bridges в Tor Browser, ÑледвайÑе инÑÑÑÑкÑииÑе на %s Tor\nBrowser ÑÑÑаниÑаÑа %s за да ÑÑаÑÑиÑаÑе Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "ÐогаÑо 'Tor Network Settings' пÑозоÑÐµÑ Ñе оÑвоÑи, кликнеÑе 'Configure' и ÑледвайÑе\nÑÑвеÑника докаÑо не попиÑа:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ЦензÑÑиÑа ли или блокиÑа вÑÑзкаÑа ви ваÑÐ¸Ñ ÐнÑеÑÐ½ÐµÑ Ð´Ð¾ÑÑавÑик (ISP)\nдо Tor мÑежаÑа?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ÐзбеÑеÑе 'Yes' и Ñлед Ñова кликнеÑе 'Next'. Ðа да конÑигÑÑиÑаÑе ваÑиÑе нови bridges, копиÑайÑе и\nпоÑÑавеÑе bridge линии в пÑозоÑеÑа за ÑекÑÑово вÑвеждане. Ðа кÑÐ°Ñ Ð½Ð°ÑиÑнеÑе 'Connect', и\nÑÑÑбва да ÑÑе гоÑови! Ðко ÑÑеÑаÑе ÑÑÑдноÑÑи, оÑидеÑе на 'Help'\nбÑÑона в 'Tor Network Settings' ÑÑвеÑника за помоÑ."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Ðокажи ÑÑобÑениеÑо."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "ÐзиÑквай vanilla bridges."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "ÐзиÑквай IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "ÐзиÑквай Pluggable Transport по TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Ðземи копие на BridgeDB's обÑеÑÑвен GnuPG клÑÑ."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ÐнÑоÑмиÑай за пÑоблем"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Source код"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Ðневник на пÑомениÑе"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "ÐонÑакÑ"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Ðоо, ÑпагеÑки!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "РмоменÑа нÑма налиÑни bridges ..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Ðай веÑоÑÑно ÑÑÑбва да %s Ñе вÑÑнеÑе %s и избеÑеÑе ÑазлиÑенt bridge Ñип!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "СÑÑпка %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Свали %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "СÑÑпка %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Ðземи %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "СÑÑпка %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Сега %sдобавеÑе bridges кÑм Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust дай ми bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "ÐпÑии за напÑеднали"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ðе"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ниÑо"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÐ%sа!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sÐ%sземи Bridges"
diff --git a/lib/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 1413a31..0000000
--- a/lib/bridgedb/i18n/ca/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,386 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Albert <provisionalib at hotmail.com>, 2013
-# Assumpta Anglada <assumptaanglada at gmail.com>, 2014
-# Eloi GarcÃa i Fargas, 2014
-# Humbert <humbert.costas at gmail.com>, 2014
-# isis <isis at torproject.org>, 2015
-# laia_ <laiaadorio at gmail.com>, 2014-2015
-# Toni Hermoso Pulido <toniher at softcatala.cat>, 2012
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-20 19:43+0000\n"
-"Last-Translator: isis <isis at torproject.org>\n"
-"Language-Team: Catalan (http://www.transifex.com/projects/p/torproject/language/ca/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ca\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Perdoni! Quelcom ha anat malament amb la seva petició."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Aquest es un missatge automà tic; si us plau, no respongui.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Els vostres ponts: "
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Has superat el lÃmit. Siusplau baixa el ritme! El mÃnim de temps entre\nels correus és de %s hores. La resta de correus durant aquest perÃode de temps seran ignorats."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combina COMMANDs per especificar les múltiples opcions simultà niament)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Benvingut a BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Actualment suportant transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Ei, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hola, amic!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Claus Públiques"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Aquest correu va ser creat amb unicorns, arcs de Sant Martà i espurnes\nper %s a %s en %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB pot proveïr ponts amb diversos %stypes de Pluggable Transports%s,\nque pot provocar ofuscacions a la teva connexió a Tor Network, fent-ho més difÃcil per a qualsevol veient el teu trà fic de connexió a internet de determinar que està s\nusant Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "També hi ha alguns ponts amb adreces IPv6, tot i que alguns Pluggable\nTransports no són compatibles amb IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "A més, BridgeDB té molts ponts %s està ndard sense \nPluggable Transports %s, que potser no són tan interessants, però que també\npoden ajudar a esquivar la censura d'internet, en molts casos.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Què són els ponts?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Ponts %s són mecanismes Tor que ajuden a esquivar la censura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Necessito una manera alternativa d'obtenir ponts"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Una altra manera d'aconseguir ponts és enviar un correu electrònic a %s. Tingueu en compte que heu\nd'enviar el correu amb una adreça d'un dels següents proveïdors de correu:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "No em funcionen els ponts! Ajuda!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Si el teu Tor no funciona, hauries d'enviar un correu a %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Incloeu el mà xim d'informació sobre el vostre cas, inclosa la llista de\nponts i de Pluggable Transports que heu intentat utilizar, la vostra versió de Tor Browser,\ni qualsevol missatge que Tor hagi donat, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Les vostres lÃnies de ponts:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Descarrega ponts!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Si us plau, sel·leccioni les opcions pel tipus de pont:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Necessites adreces IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Necessites un %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "El seu navegador no està mostrant les imatges correctament."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Introdueixi els carà cters de la imatge superior..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Per començar a utilitzar els ponts"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Per introduir ponts al Tor Browser, seguiu les instruccions de la pà gina\nde baixada %s del %s Tor Browser per iniciar el Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Quan aparegui el dià leg 'Configuració Xarxa Tor', clica 'Configura' i segueix\nl'auxiliar fins que pregunti:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "El vostre Proveïdor d'Internet (ISP) bloqueja o censura les connexions\namb la xarxa Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Seleccioneu 'SÃ' i després cliqueu 'Següent'. Per configurar els vostres ponts nous, copieu i\nenganxeu les lÃnies del pont al següent quadre d'entrada de text. Finalment, cliqueu 'Connectar', i ja hauria d'estar a punt! Si teniu problemes, intenteu clicar el botó \nd''Ajuda' a l'auxiliar de 'Configuració Xarxa Tor' per més assistència."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Mostra aquest missatge."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Demanar ponts està ndard."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Demanar ponts IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Demanar un Pluggable Transport per TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Fer una còpia de la clau pública GnuPG del BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Comunicar un Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Codi font"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Canviar el log"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contacte"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Selecciona-ho tot"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Mostra el codi QR"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Codi QR per les lÃnies de ponts"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ueeeee, espaguetiiiiis!!!!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Hi ha hagut un error en obtenir el codi QR"
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Aquest codi QR conté les lÃnies de pont. Escannegeu-lo amb un lector de codis QR per copiar les lÃnies de pont al mòbil i a altres dispositius. "
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "No hi han ponts disponibles en aquests moments..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Potser hauries de provar %s tirant enrer %s i triant un tipus diferent de pont!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Pas %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Descarrega %s Navegador Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Pas %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Descarrega %s ponts %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Pas %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Ara %s adjunta els ponts al Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust donem els meus bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opcions Avançades"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "Cap"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index f76cd6b..0000000
--- a/lib/bridgedb/i18n/cs/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,363 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# A5h8d0wf0x <littleslyfoxie28 at gmail.com>, 2014
-# Adam Slovacek <adamslovacek at gmail.com>, 2013
-# Elisa <valhalla at gishpuppy.com>, 2011
-# Sanky <gsanky+transifex at gmail.com>, 2011
-# JiÅÃ VÃrava <appukonrad at gmail.com>, 2014
-# mxsedlacek, 2014
-# Radek Bensch <inactive+Radog at transifex.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: A5h8d0wf0x <littleslyfoxie28 at gmail.com>\n"
-"Language-Team: Czech (http://www.transifex.com/projects/p/torproject/language/cs/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: cs\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "PromiÅte! NÄco se stalo Å¡patnÄ s vaÅ¡Ãm požadavkem."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Toto je automatická zpráva; prosÃm, neodpovÃdejte na ni]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Zde jsou vaše mosty:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "PÅekroÄil jste meznà limit. ProsÃme zpomalte! Minimálnà Äas nezi emaily je %s hodin. VÅ¡echny následujÃcà emaily bÄhem této Äasové doby budou ignorovány."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "PÅÃKAZY: (kombinujte pÅÃkazy pro zadávánà vÃce možnostà souÄasnÄ)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "VÃtejte v BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "V souÄasnosti podporované transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Ahoj, pÅÃteli!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "VeÅejné klÃÄe"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Tento email byl generován s duhou, jednorožci a jiskÅiÄkami\npro %s data %s v %s"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB může nabÃdnou mosty s nÄkolika %stypes of Pluggable Transports%s,\ncož může pomoci zmást vaÅ¡e pÅipojenà k Tor Network, a udÄlat složitÄjÅ¡Ã pro kohokoli\nsledovat vaÅ¡i internetovou aktivitu k urÄenà zda použÃváte Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "NÄkteté mosty s IPv6 adresami jsou také k dispozici, pÅestože nÄkteré Pluggable Transports\nnejsou kompaktibilnà s IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "DodateÄnÄ, BridgeDB má spustu nudných vanilkových mostů %s bez žádných \nPluggable Transports %s což sice nemusà znÃt tak super, ale úpoÅád mohou\npomoci obejÃt internetovou cenzuru v mnoha pÅÃpadech.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Co jsou mosty?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Mosty %s jsou Tor pÅenosy které vám pomáhajà obejÃt cenzuru."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Chci mosty zjistit jiným způsobem!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "DalÅ¡Ã způsob jak dostat mosty je poslat email na %s. ProsÃme mÄjte na pamÄti že musÃte poslat email pouze z adresy od následujÃcÃch poskytovatelů emailu:\n%s, %s nebo %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mé mosty nefungujÃ. PotÅebuji pomoc!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Pokud váš Tor nepracuje mÄl by jste nám poslat email %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Pokuste se popsat co nejvÃce informacà o vaÅ¡em pÅÃpadu, vÄetnÄ listu\nmostů a pluggable Transports které jste se pokusili použÃt, vaÅ¡i verzi Tor Browser,\na jaké Tor poslal ven, atd."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Zde jsou vaše linky k mostům: "
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ZÃskejte Mosty!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ProsÃm vyberte nastavenà pro typ mostů:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "PotÅebujete IPv6 adresu?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "PotÅebujete %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Váš prohlÞeÄ nezobrazuje obrázky správnÄ."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Vložte pÃsmena z obrázku výše..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Jak zaÄÃt použÃvat mosty."
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "K vloženà mostů do Tor Browser, postupujte podle instrukcà na %s Tor\nBrowser stránce ke staženà %s ke spuÅ¡tÄnà Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Když dialog 'Tor Nastavenà SÃtÄ' vyskoÄà kliknÄte na 'Konfigurovat' a postupujte\npodle průvodce dokud nedostanete otázku:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokuje nebo cenzoruje váš Poskytovatel Služeb Internetu (ISP) pÅipojenà k\nTor sÃti?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Vyberte 'Ano' a poté kliknÄte 'DalÅ¡Ã'. Pro konfiguraci nových mostů kopÃrujte a\nvložte linky k mostům do textového pole pro vloženÃ. Nakonec kliknÄte na 'PÅipojit' a\nmÄlo by to být vÅ¡echno! Pokud narazÃte na problémy, zkuste kliknout na tlaÄÃtko 'Pomoc'\nv 'Tor Nastavenà SÃtÄ' průvodce pro dalÅ¡Ã pomoc."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Zobrazà tuto zprávu."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Požadovat vanilla mosty."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Požadovat IPv6 mosty."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Požadovat Pluggable Transport od TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ZÃskat kopii BridgeDS's veÅejného GnuPg klÃÄe. "
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "Nahlásit chybu"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Zdrojový kód"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Seznam zmÄn"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "Kontakt"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "JežÃÅ¡ku na kÅÞku!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "V souÄasnosti zde nejsou žádné mosty k dispozici..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Možná by jste mÄl zkusit %s jÃt zpÄt %s a vybrat jiný typ mostů!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Krok %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Stáhnout %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Krok %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ZÃskej %s mosty %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Krok %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Nynà %s pÅidejte mosty do Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sen mi dejte mosty!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "RozÅ¡ÃÅené možnosti"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ne"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "žádné"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sA%sno!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sN%sastavit Bridges"
diff --git a/lib/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index b209bf3..0000000
--- a/lib/bridgedb/i18n/cy/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,102 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# huwwaters <huwwaters at gmail.com>, 2014
-# littlegreykida <theinfinitygap at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2014-03-26 08:20+0000\n"
-"Last-Translator: littlegreykida <theinfinitygap at gmail.com>\n"
-"Language-Team: Welsh (http://www.transifex.com/projects/p/torproject/language/cy/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: cy\n"
-"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "Beth yw pontydd?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s Pontydd gyfnewid %s yw'r gyfnewidiau Tor sy'n helpu chithau i osgoi sensoriaeth."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Rwyf yn angen ffordd arall i cael gafael ar pontydd!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Ffordd arall i canfod cyfeiriadau pontydd cyhoeddys yw i anfon e-bost (o %s neu gyfeiriad %s) i %s gyda'r llinell 'get bridges' ar ei ben ei hyn o fewn corff yr e-bost."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "Nid yw fy mhontydd yn gweithio! Rwyf yn angen help!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Os nad yw'ch Tor yn gweithio, dylech e-bostio %s. Ceisiwch cynnwys cymaint o gwybodaith ag y bo modd, yn cynnwys yr rhestr o pontydd rydych wedi defnyddio, enw'r pecyn a defnyddwyd, yr negeseuon a rhoddwyd allan gan Tor, ayyb."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "I defnyddio'r llinellau uwchben, cer i tydalen osodiadau Vidalia's Network, ac pwyswch \"Mae fy ISP yn wahardd cysylltiadau i'r rhwydwaith Tor.\" Wedyn, ychwanegwch pob gyfeiriad pont, un ar amser."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "Nid oes pontydd ar gael"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Diweddarwch eich porwr i Firefox"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Teipiwch y ddau air"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Cam 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Cael gafael ar y %s Pecyn Porwr Tor %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Cam 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Cael gafael ar %s pontydd %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Cam 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Nawr %s ychwanegwch y pontydd i Tor %s"
diff --git a/lib/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index c647db3..0000000
--- a/lib/bridgedb/i18n/da/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,384 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Christian Villum <villum at autofunk.dk>, 2014-2015
-# David Nielsen <gnomeuser at gmail.com>, 2014
-# OliverMller <theoliver at live.co.uk>, 2011
-# Thomas Pryds <thomas at pryds.eu>, 2014
-# Tore Bjørnson <tore.bjornson at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-23 06:50+0000\n"
-"Last-Translator: Christian Villum <villum at autofunk.dk>\n"
-"Language-Team: Danish (http://www.transifex.com/projects/p/torproject/language/da/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: da\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Beklager, noget gik galt med din anmodning."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Dette er en automatisk meddelelse; Svar venligst ikke.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Her er dine broer:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Du har overskredet ratebegrænsningen. Sænk venligst hastigheden! Den minimale tid mellem\nemails er %s timer. Yderligere emails sendt i denne period vil blive ignoreret."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (Kombiner COMMANDs for at specifierer adskillige valgmuligheder på en gang)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Velkommen til BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Transport TYPEs som er understøttet i øjeblikket:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hej %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hej min ven!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Offentlige nøgler"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Denne email til %s blev skabt ved hjælp af regnbuer, enhjørninger og glitter %s klokken %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB kan formidle broer med adskillige typer %sPluggable Transports%s som kan hjælpe med at sløre dine forbindelser til Tor netværket, og dermed gøre det vanskeligere for nogen som kan se din internet trafik at bestemme at du bruger Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Nogle broer med IPv6 adresser er også tilgængelige, men ikke alle Pluggable\nTransports understøtter IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "BridgeDB har ydermere masser af konventionelle broer %s uden nogen Pluggable Transports %s hvilket måske ikke lyder så smart, men de kan stadigvæk hjælpe med at omgå internet censur i mange tilfælde.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Hvad er broer?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s broer %s er Tor relæer som hjælper dig med at omgå censur."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Jeg har brug for en alternativ metode til at få broer på!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "En anden måde at finde broer er at sende en email til %s. Bemærk venligst at du skal\nsende mailen fra en konto hos en af de følgende email-udbydere:\n%s, %s eller %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mine broer virker ikke! Jeg har brug for hjælp!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Hvis Tor ikke virker for dig kan du sende en email til %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Prøv at inkluderer så meget information om din sag som muligt, der i blandt: En liste af broer og Pluggable Transports du har prøvet at bruge, din Tor Browser version, hvilke beskeder Tor gav, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Her er dine bro linjer:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Find broer!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Vælg venligst brotype muligheder:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Har du brug for IPv6 adresser?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Har du brug for en %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Din browser kan ikke vise billeder korrekt."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Indtast tegnene fra billedet ovenfor..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "SÃ¥dan starter du med at bruge dine broer"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "For at tilføje broer til Tor Browseren, følg instruktionerne på %s Tor\nBrowserens hjemmeside %s for at starte Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Når 'Tor netværksindstillinger' vinduet popper op, klik 'Indstil' og følg guiden til den siger:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokerer eller på anden vis censurerer din internetudbyder forbindelser\ntil Tor netværket?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Vælg 'Ja' og klik så på 'Næste'. For at konfigurere dine nye broer, kopier og indsæt brolinierne i den næste input boks. Klik til sidst 'Forbind', og så er du klar! Hvis du støder på problemer, så prøv at klikke på 'Hjælp'-knappen i 'Tor Netværksindstillinger'-guiden for at få yderligere hjælp."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Vis denne besked."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Anmod almindelige broer."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Amod IPv6 broer."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Anmod om Pluggable Transport efter TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Hent en kopi af BridgeDB's offentlige GnuPG nøgle."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Rapporter en fejl"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Kildekode"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Ãndringer"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Marker alle"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Vis QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode for dine brolinier."
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ups, vi står i lort til halsen"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Det lader til at der opstod en fejl ved hentningen af din QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Denne QRCode indeholder dine brolinier. Scan den med en QRCode-læser for at kopiere dine brolinier over til mobile og andre enheder."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Der er i øjeblikket ingen tilgængelige broer..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Du kunne måse prøve %s at gå tilbage %s og vælge en anden brotype!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Trin %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Hent %s Tor Browseren %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Trin %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "FÃ¥ %s broer %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Trin %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Tilføj nu %s broer til Tor Browseren %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sB%sare giv mig bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Advancerede indstillinger"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nej"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ingen"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sH%sent Bridges"
diff --git a/lib/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 163b56c..0000000
--- a/lib/bridgedb/i18n/de/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,389 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# trantor <clucko3 at gmail.com>, 2014
-# Ettore Atalan <atalanttore at googlemail.com>, 2014
-# unknwon_anonymous <jackjohnson0001 at yahoo.com>, 2014
-# konstibae <konstibae at gmail.com>, 2014
-# Locke <locke at dena-design.de>, 2011
-# qbi <kubieziel at googlemail.com>, 2015
-# Sebastian <sebix+transifex at sebix.at>, 2015
-# debakel <spam1 at mrtz.me>, 2014
-# Tobias Bannert <tobannert at gmail.com>, 2013
-# Tobias Bannert <tobannert at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-04 18:22+0000\n"
-"Last-Translator: qbi <kubieziel at googlemail.com>\n"
-"Language-Team: German (http://www.transifex.com/projects/p/torproject/language/de/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: de\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Wir bitten um Entschuldigung. Bei Ihrer Anfrage lief etwas fehl."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Das ist eine automatische Nachricht. Bitte antworten Sie nicht darauf.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Hier sind Ihre Brücken-Server (Bridges):"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Sie haben den Grenzwert überschritten. Bitte verlangsamen! Der kleinste Abstand zwischen \nden E-Mails beträgt %s Stunden. Alle weiteren E-Mails, während dieser Zeit, werden ignoriert."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "Befehle: (Befehle kombinieren, um mehrere Optionen gleichzeitig anzugeben)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Willkommen bei der Brückendatenbank!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Aktuell unterstützte Transporttypen:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hallo, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hallo Freund!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Ãffentliche Schlüssel"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Diese E-Mail wurde für %s am %s um %s mit Regenbogen, Einhörnern und Glitzer erstellt."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "Die Brückendatenbank kann Brücken-Server mit verschiedenen %stypen der Pluggable Transports%s bereitstellen. Diese können helfen, Ihre Verbindungen zum Tor-Netzwerk zu verschleiern. Damit wird es für Dritte schwieriger festzustellen, dass sie Tor benutzen.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Einige Brücken mit IPv6-Adressen sind ebenfalls vorhanden, auch wenn einige \nsteckbare Transporte nicht IPv6-kompatibel sind.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Zusätzlich hat die Brückendatenbank viele erkömmliche Brücken-Server %s ohnePluggable Transports %s. Das klingt vielleicht weniger toll. Es hilft Ihnen dennoch, in vielen Fällen die Internetzensur zu umgehen.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Was sind Brücken?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Brücken-Server %s sind Tor-Relais, die Ihnen helfen die Zensur zu umgehen."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ich brauche einen anderen Weg, um Adressen von Brücken-Servern zu erhalten."
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Sie können auch eine E-Mail an %s schicken, um Adressen von Brücken-Servern zu erhalten. Die E-Mail muss von einem der folgenden E-Mail-Anbieter geschickt werden: %s, %s oder %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Meine Brücken-Server funktionieren nicht! Ich brauche Hilfe!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Sollte Tor bei Ihnen nicht funktionieren, senden Sie bitte eine E-Mail an %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Versuchen Sie in Ihrer E-Mail möglichst viele Informationen über Ihr Problem einzubeziehen, \neinschlieÃlich einer Liste der Brücken-Server und der Pluggable Transports, die Sie versuchten zu benutzen, \nIhre Tor-Browser-Version, und jegliche Meldungen, die Tor gegeben hat, usw."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Hier sind Ihre Brückenadressen:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Erhalten Sie Adressen von Brücken-Servern!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Bitte wählen Sie die Optionen für den Brückentyp aus:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Benötigen Sie IPv6-Adressen?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Benötigen Sie eine %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Bilder werden in Ihrem Browser nicht korrekt dargestellt."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Bitte geben Sie die Zeichen aus dem oberen Bild ein."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "So starten Sie die Benutzung Ihrer Brücken"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Um Brücken zum Tor-Browser hinzuzufügen, bitte der Anleitung auf der Seite %s, zum Herunterladen des %s Tor-Browsers folgen, um den Tor-Browser zu starten."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Wenn der Dialog mit den »Tor-Netzwerkeinstellungen« erscheint, \nklicken Sie auf »Konfigurieren« und folgen den Anweisungen des Assistenten: "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blockiert oder zensiert Ihr Internetanbieter (ISP) die Verbindungen zum Tor-Netzwerk?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Wählen Sie »Ja« und klicken dann auf »Weiter«. Um Ihre neuen Brücken zu konfigurieren, kopieren Sie die \nZeilen mit den Adressen der Brücken und fügen sie in das Texteingabefeld ein. Zum Schluss klicken Sie auf \n»Verbinden« und Sie können loslegen! Falls Sie Schwierigkeiten haben, \nversuchen Sie bitte auf den »Hilfe«-Knopf im Assistenten der »Tor- Netzwerkeinstellungen« zu klicken, \num weitere Hilfe zu erhalten."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Diese Nachricht anzeigen."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Standard-Brücken anfordern."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPv6-Brücken anfordern."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Einen Pluggable Transport des TYPs anfordern."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Eine Kopie des öffentlichen GnuPG-Schlüssels der Brückendatenbank erhalten."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Einen Fehler melden"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Quellcode"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Ãnderungsprotokoll"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Alles auswählen"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "QRCode anzeigen"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode für Ihre Brückenverbindungen"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Hoppla, Spaghetti!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Es gab vermutlich einen Fehler bei Beschaffen Ihres QR-Codes."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Dieser QR-Code beinhaltet Ihre Brückenverbindungen. Scannen Sie ihn mit einem QR-Code-Leser um ihre Brückenverbindungen auf ihre mobile und andere Geräte zu kopieren."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Im Moment sind keine Brücken verfügbar â¦"
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Möglicherweise sollten Sie versuchen %s zurück zu gehen %s und einen anderen Brückentyp auszuwählen!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Schritt %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "%s Tor Browser %s herunterladen"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Schritt %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s Brücken %s erhalten"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Schritt %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Jetzt %s bitte die Brücken zum Tor-Browser hinzufügen %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sM%sir nur Brücken geben!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Erweiterte Optionen"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nein"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "keine"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sB%srücken erhalten"
diff --git a/lib/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index bbbbe92..0000000
--- a/lib/bridgedb/i18n/el/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,364 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Adrian Pappas <pappasadrian at gmail.com>, 2014
-# andromeas <andromeas at hotmail.com>, 2014
-# oahanx, 2014
-# isv31 <ix4svs at gmail.com>, 2014
-# kotkotkot <kotakota at gmail.com>, 2013
-# kotkotkot <kotakota at gmail.com>, 2012
-# mitzie <zacharias.mitzelos at gmail.com>, 2013
-# Wasilis Mandratzis <inactive+Wasilis at transifex.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: oahanx\n"
-"Language-Team: Greek (http://www.transifex.com/projects/p/torproject/language/el/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: el\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "ΣÏ
γγνÏμη! ÎάÏι Ïήγε ÏÏÏαβά με Ïο αίÏημα ÏαÏ."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[ÎÏ
ÏÏ ÎµÎ¯Î½Î±Î¹ Îνα αÏ
ÏομαÏοÏοιημενο μήνÏ
μα, ÏαÏακαλοÏμε μην αÏανÏήÏεÏε]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "ÎÎ´Ï ÎµÎ¯Î½Î±Î¹ οι γÎÏÏ
ÏÎµÏ ÏαÏ:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "ÎεÏÎÏαÏÎµÏ Ïο ÏÏιο. ΠαÏακαλÏ, ÏÎ¹Ï Î±Ïγά! ΠελάÏιÏÏÎ¿Ï ÏÏÏÎ½Î¿Ï Î¼ÎµÏÎ±Î¾Ï Î´Î¹Î±Î´Î¿ÏικÏν email\n είναι %s ÏÏεÏ. Îλα Ïα ενδιάμεÏα email Ïε αÏ
ÏÏ Ïο ÏÏÎ¿Î½Î¹ÎºÏ Î´Î¹Î¬ÏÏημα θα αγνοοÏνÏαι."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "ÎÎΤÎÎÎΣ: (ÏÏ
νδÏ
άÏÏε ÎÎΤÎÎÎΣ για να εÏιλÎξεÏε ÏολλαÏλÎÏ ÏαÏαμÎÏÏοÏ
Ï ÏαÏ
ÏÏÏÏονα)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "ÎαλÏÏ Î®ÏθαÏε ÏÏο BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Î¥ÏοÏÏηÏιζÏμενα transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Îειά ÏοÏ
, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Îεια ÏοÏ
, Ïίλε!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "ÎημÏÏια Îλειδιά"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "ÎÏ
ÏÏ Ïο email ÏαÏήÏθη με αγάÏη, οÏ
Ïάνια ÏÏξα και ÏÏÏ
ÏÏÏκονη\nγια Ïον/Ïην %s, %s ÏÏÎ¹Ï %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "Î BridgeDB μÏοÏεί να ÏαÏÎÏει γÎÏÏ
ÏÎµÏ Î¼Îµ διάÏοÏοÏ
Ï %sÏÏÏοÏ
Ï Pluggable Transports%s,\nÏα οÏοία μÏοÏοÏν να κÏÏÏοÏ
ν ÏÎ¹Ï ÏÏ
νδÎÏÎµÎ¹Ï ÏÎ±Ï ÏÏο Tor Network, κάνονÏÎ¬Ï Ïο δÏ
ÏκολÏÏεÏο για κάÏοιον ÏοÏ
ÏαÏακολοÏ
θεί Ïη δικÏÏ
ακή δÏαÏÏηÏιÏÏηÏά ÏÎ±Ï Î½Î± καÏαλάβει ÏÏÏ ÏÏηÏιμοÏοιείÏε Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Î¥ÏάÏÏοÏ
ν γÎÏÏ
ÏÎµÏ Î¼Îµ διεÏ
θÏνÏÎµÎ¹Ï IPv6 addresses, αλλά κάÏοια Pluggable\nTransports δεν είναι ÏÏ
μβαÏά με IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "ÎÏίÏηÏ, η BridgeDB ÎÏει ÏολλÎÏ ÏαλιÎÏ ÎºÎ±Î»ÎÏ Î³ÎÏÏ
ÏÎµÏ %s ÏÏÏίÏ\nPluggable Transports %s ÏοÏ
ίÏÏÏ Î´ÎµÎ½ ακοÏγεÏαι καÏαÏληκÏικÏ, αλλά Ïε ÏολλÎÏ ÏεÏιÏÏÏÏÎµÎ¹Ï Î¼ÏοÏοÏν να ÏÎ±Ï Î²Î¿Î·Î¸Î®ÏοÏ
ν να ÏαÏακάμÏεÏε Ïη λογοκÏιÏία.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Τι είναι οι γÎÏÏ
ÏεÏ;"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "Îι %s ÎÎÏÏ
ÏÎµÏ %s είναι Tor αναμεÏαδÏÏÎµÏ ÏοÏ
βοηθοÏν ÏÏην ÏαÏάκαμÏη ÏÎ·Ï Î»Î¿Î³Î¿ÎºÏιÏίαÏ. "
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "ΧÏειάζομαι Îναν εναλλακÏÎ¹ÎºÏ ÏÏÏÏο για ÏÏηÏιμοÏοιηÏη γÎÏÏ
ÏÏν! "
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ÎναλλακÏικά μÏοÏείÏε να λάβεÏε γÎÏÏ
ÏÎµÏ ÏÏελνονÏÎ±Ï email ÏÏο %s. Î ÏÎÏει να ÏÏείλεÏε email ÏÏηÏιμοÏοιÏνÏÎ±Ï Î¼Î¯Î± διεÏ
θÏ
νÏη email αÏÏ Îναν αÏÏ ÏοÏ
Ï ÏαÏακάÏÏ ÏάÏοÏοÏ
Ï:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Îι γÎÏÏ
ÏÎµÏ Î¼Î¿Ï
δεν λειÏοÏ
ÏγοÏν! ΧÏειάζομαι βοήθεια! "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Îν Ïο Tor ÏÎ±Ï Î´Îµ δοÏ
λεÏει, ÏαÏÎ±ÎºÎ±Î»Ï ÏÏείλÏε email ÏÏο %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ΠαÏÎ±ÎºÎ±Î»Ï Î´ÏÏÏε Î¼Î±Ï ÏÏο Ïο δÏ
ναÏÏν ÏεÏιÏÏÏÏεÏÎµÏ ÏληÏοÏοÏÎ¯ÎµÏ Î³Î¹Î± Ïο ÏÏάλμα ÏοÏ
ÏÏ
νανÏήÏαÏε, ÏÏÏÏ Ïη λίÏÏα γεÏÏ
ÏÏν και Ïα Pluggable Transports ÏοÏ
ÏÏοÏÏαθήÏεÏε να ÏÏηÏιμοÏοιήÏεÏε, Ïην ÎκδοÏη ÏοÏ
Tor Browser, Ï,Ïι μηνÏμαÏα Îδειξε Ïο Tor κÏλ."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "ÎÎ´Î¿Ï Î¿Î¹ γÏαμμÎÏ Î³Î¹Î± ÏÎ¹Ï Î³ÎÏÏ
ÏÎÏ ÏαÏ:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ÎήÏη ÎεÏÏ
ÏÏν!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎξÏε ÏÎ¹Ï ÎµÏιλογÎÏ Î³Î¹Î± Ïον ÏÏÏο ÏÎ·Ï Î³ÎÏÏ
ÏαÏ:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "ΧÏειάζεÏαι μια διεÏθÏ
νÏη IPv6;"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "ÎήÏÏÏ ÏÏειάζεÏÏε Îνα %s;"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Î ÏÏ
λλομεÏÏηÏÎ®Ï ÏÎ±Ï Î´ÎµÎ½ εμÏανίζει ÏÎ¹Ï ÎµÎ¹ÎºÏÎ½ÎµÏ ÏÏÏÏά."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "ÎιÏάγεÏε ÏοÏ
Ï ÏαÏακÏήÏÎµÏ Î±ÏÏ Ïην ÏαÏαÏÎ¬Î½Ï ÏÏÏογÏαÏία..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Î ÏÏ Î½Î± αÏÏίÏεÏε να ÏÏηÏιμοÏοιείÏε ÏÎ¹Ï Î³ÎÏÏ
ÏÎµÏ ÏαÏ"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Îια να ÏÏηÏιμοÏοιήÏεÏε γÎÏÏ
ÏÎµÏ ÏÏο Tor Browser, ακολοÏ
θήÏÏε ÏÎ¹Ï Î¿Î´Î·Î³Î¯ÎµÏ ÏÏη %s Ïελίδα download ÏοÏ
Tor Browser %s για να ξεκινήÏεÏε Ïο Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "ÎÏαν ÏÏάÏεÏε ÏÏο διάλογο \"ΡÏ
θμίÏÎµÎ¹Ï Î´Î¹ÎºÏÏοÏ
ÏοÏ
Tor\" , εÏιλÎξÏε \"ΡÏθμιÏη\" και ακολοÏ
θήÏÏε ÏοÏ
Ï Î´Î¹Î±Î»ÏγοÏ
Ï Î¼ÎÏÏι να ÏÎ±Ï ÏÏÏήÏει:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ÎνÏÏίζεÏε αν ο ÏάÏοÏÎ¿Ï (ISP) ÏÎ±Ï Î¼ÏλοκάÏει ή με οÏοιοδήÏοÏε ÏÏÏÏο ελÎγÏει ÏÏ
νδÎÏειÏ\nÏÏο δίκÏÏ
ο Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ÎÏιλÎξÏε \"Îαι\" και μεÏά ÏαÏήÏÏε \"ÎÏÏμενο\" Îια να ÏÏ
θμίÏεÏε ÏÎ¹Ï Î½ÎÎµÏ ÏÎ±Ï Î³ÎÏÏ
ÏεÏ, ανÏιγÏάÏÏε ÏÎ¹Ï Î³ÏαμμÎÏ Î¼Îµ ÏÎ¹Ï Î´Î¹ÎµÏ
θÏνÏÎµÎ¹Ï ÏÏν γεÏÏ
ÏÏν ÏÏο κοÏ
Ïί κειμÎνοÏ
. ÎεÏά ÏαÏήÏÏε \"ΣÏνδεÏη\" και ÏÏÎÏει να είÏÏε ενÏάξει! Îν ανÏιμεÏÏÏίÏεÏε ÏÏοβλήμαÏα, εÏιλÎξÏε Ïο κοÏ
μÏί \"Îοήθεια\" ÏÏÎ¹Ï \"ΡÏ
θμίÏÎµÎ¹Ï Î´Î¹ÎºÏÏοÏ
ÏοÏ
Tor\"."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "ÎμÏανίζει αÏ
ÏÏ Ïο μήνÏ
μα."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "ÎίÏηÏη γεÏÏ
ÏÏν βανίλια."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "ÎίÏηÏη IPv6 γεÏÏ
ÏÏν."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "ÎηÏήÏÏε Îνα Pluggable Transport βάÏει TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ÎάβεÏε Îνα ανÏίγÏαÏο ÏοÏ
δημοÏίοÏ
GnuPG ÎºÎ»ÎµÎ¹Î´Î¹Î¿Ï ÏοÏ
BridgeDB."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ÎναÏοÏά Î ÏοβλήμαÏοÏ"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Î Î·Î³Î±Î¯Î¿Ï ÎÏδικαÏ"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "ÎÏÏείο καÏαγÏαÏÎ®Ï Î±Î»Î»Î±Î³Ïν"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "ÎÏαÏή"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "ÎÏ
ÏÏ, κάÏι Ïήγε ÏÏÏαβά!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Îεν Ï
ÏάÏÏοÏ
ν καθÏλοÏ
διαθÎÏÎ¹Î¼ÎµÏ Î³ÎÏÏ
ÏεÏ..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "ÎοκιμάÏÏε να %s ÏάÏε ÏίÏÏ %s και να εÏιλÎξεÏε διαÏοÏεÏÎ¹ÎºÏ ÏÏÏο γÎÏÏ
ÏαÏ!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Îήμα %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "ÎαÏεβάÏÏε %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Îήμα %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ΠαÏÏε ÏÎ¹Ï %s γεÏÏ
ÏÎµÏ %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Îήμα %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "ΤÏÏα %s ÏÏοÏθÎÏÏε ÏÎ¹Ï Î³ÎÏÏ
ÏÎµÏ ÏÏο Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sÎ%sÏλÏÏ Î´ÏÏε μοÏ
γÎÏÏ
ÏεÏ!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "ÎÏιλογÎÏ Î³Î¹Î± ÏÏοÏÏÏημÎνοÏ
Ï"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "ÎÏι"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ÏίÏοÏα"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÎ%sαι!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sÎ%sήÏη ÎεÏÏ
ÏÏν"
diff --git a/lib/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 9248ec3..0000000
--- a/lib/bridgedb/i18n/en/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,433 +0,0 @@
-# English translations for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-# Isis Lovecruft <isis at torproject.org>, 2015.
-#
-#, fuzzy
-#
-# Translators:
-# runasand <runa.sandvik at gmail.com>, 2011
-# Isis Lovecruft <isis at torproject.org>, 2012-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: bridgedb 0.2.4-234-g193c80a-dirty\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'
-"POT-Creation-Date: 2015-03-19 22:13+0000\n"
-"PO-Revision-Date: 2015-03-20 04:13+0000\n"
-"Last-Translator: Isis Lovecruft <isis at torproject.org>\n"
-"Language-Team: English (http://www.transifex.com/projects/p/torproject/language/en/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: en\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:107
-msgid "Sorry! Something went wrong with your request."
-msgstr "Sorry! Something went wrong with your request."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[This is an automated message; please do not reply.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Here are your bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be "
-"ignored."
-msgstr ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be "
-"ignored."
-
-#: lib/bridgedb/strings.py:25
-msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Welcome to BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Currently supported transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hello, friend!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Public Keys"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are"
-"\n"
-"using Tor.\n"
-"\n"
-msgstr ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are"
-"\n"
-"using Tor.\n"
-"\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
-"\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
-"\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
-"\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
-"\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "What are bridges?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "I need an alternative way of getting bridges!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you "
-"must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr ""
-"Another way to get bridges is to send an email to %s. Please note that you "
-"must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "My bridges don't work! I need help!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "If your Tor doesn't work, you should email %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Here are your bridge lines:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Get Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Please select options for bridge type:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Do you need IPv6 addresses?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Do you need a %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Your browser is not displaying images properly."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Enter the characters from the image above..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "How to start using your bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
-"page %s and then follow the instructions there for downloading and starting\n"
-"Tor Browser."
-msgstr ""
-"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
-"page %s and then follow the instructions there for downloading and starting\n"
-"Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:126
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
-"follow\n"
-"the wizard until it asks:"
-msgstr ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
-"follow\n"
-"the wizard until it asks:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:130
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor "
-"connections\n"
-"to the Tor network?"
-msgstr ""
-"Does your Internet Service Provider (ISP) block or otherwise censor "
-"connections\n"
-"to the Tor network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:134
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and"
-"\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and"
-"\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-
-#: lib/bridgedb/strings.py:142
-msgid "Displays this message."
-msgstr "Displays this message."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:146
-msgid "Request vanilla bridges."
-msgstr "Request vanilla bridges."
-
-#: lib/bridgedb/strings.py:147
-msgid "Request IPv6 bridges."
-msgstr "Request IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:149
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Request a Pluggable Transport by TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:152
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Get a copy of BridgeDB's public GnuPG key."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Report a Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Source Code"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Select All"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Show QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode for your bridge lines"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Uh oh, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "It seems there was an error getting your QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
-"your bridge lines onto mobile and other devices."
-msgstr ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
-"your bridge lines onto mobile and other devices."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "There currently aren't any bridges available..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
-msgstr " Perhaps you should try %s going back %s and choosing a different bridge type!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Step %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Download %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Step %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Get %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Step %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Now %s add the bridges to Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust give me bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Advanced Options"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "none"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 9399e46..0000000
--- a/lib/bridgedb/i18n/en_GB/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,382 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Andi Chandler <andi at gowling.com>, 2014-2015
-# Richard Shaylor <rshaylor at me.com>, 2014
-# ronnietse <tseronnie at ymail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-19 14:19+0000\n"
-"Last-Translator: Andi Chandler <andi at gowling.com>\n"
-"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/torproject/language/en_GB/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: en_GB\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Sorry! Something went wrong with your request."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[This is an automated message; please do not reply.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Here are your bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "You have exceeded the rate limit. Please slow down! The minimum time between\nemails is %s hours. All further emails during this time period will be ignored."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Welcome to BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Currently supported transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hello, friend!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Public Keys"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "This email was generated with rainbows, unicorns, and sparkles\nfor %s on %s at %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\nwhich can help obfuscate your connections to the Tor Network, making it more\ndifficult for anyone watching your internet traffic to determine that you are\nusing Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Some bridges with IPv6 addresses are also available, though some Pluggable\nTransports aren't IPv6 compatible.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Additionally, BridgeDB has plenty of plain old vanilla bridges %s without any\nPluggable Transports %s which maybe doesn't sound as cool, but they can still\nhelp to circumvent internet censorship in many cases.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "What are bridges?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "I need an alternative way of getting bridges!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Another way to get bridges is to send an email to %s. Please note that you must\nsend the email using an address from one of the following email providers:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "My bridges don't work! I need help!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "If your Tor doesn't work, you should email %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Try including as much info about your case as you can, including the list of\nbridges and Pluggable Transports you tried to use, your Tor Browser version,\nand any messages which Tor gave out, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Here are your bridge lines:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Get Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Please select options for bridge type:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Do you need IPv6 addresses?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Do you need a %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Your browser is not displaying images properly."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Enter the characters from the image above..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "How to start using your bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "To enter bridges into Tor Browser, follow the instructions on the %s Tor\nBrowser download page %s to start Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\nthe wizard until it asks:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Does your Internet Service Provider (ISP) block or otherwise censor connections\nto the Tor network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\npaste the bridge lines into the text input box. Finally, click 'Connect', and\nyou should be good to go! If you experience trouble, try clicking the 'Help'\nbutton in the 'Tor Network Settings' wizard for further assistance."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Displays this message."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Request vanilla bridges."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Request IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Request a Pluggable Transport by TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Get a copy of BridgeDB's public GnuPG key."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Report a Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Source Code"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Select All"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Show QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode for your bridge lines"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Uh oh, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "It seems there was an error getting your QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "This QRCode contains your bridge lines. Scan it with a QRCode reader to copy your bridge lines onto mobile and other devices."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "There are no bridges available currently..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Perhaps you should try %s going back %s and choosing a different bridge type!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Step %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Download %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Step %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Get %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Step %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Now %s add the bridges to Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust give me bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Advanced Options"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "none"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 48c0ad8..0000000
--- a/lib/bridgedb/i18n/en_US/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,433 +0,0 @@
-# English translations for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-# Isis Lovecruft <isis at torproject.org>, 2015.
-#
-#, fuzzy
-#
-# Translators:
-# runasand <runa.sandvik at gmail.com>, 2011
-# Isis Lovecruft <isis at torproject.org>, 2012-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: bridgedb 0.2.4-234-g193c80a-dirty\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'
-"POT-Creation-Date: 2015-03-19 22:13+0000\n"
-"PO-Revision-Date: 2015-03-20 04:13+0000\n"
-"Last-Translator: Isis Lovecruft <isis at torproject.org>\n"
-"Language-Team: English (US) (http://www.transifex.com/projects/p/torproject/language/en_US/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: en_US\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:107
-msgid "Sorry! Something went wrong with your request."
-msgstr "Sorry! Something went wrong with your request."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[This is an automated message; please do not reply.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Here are your bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be "
-"ignored."
-msgstr ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be "
-"ignored."
-
-#: lib/bridgedb/strings.py:25
-msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Welcome to BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Currently supported transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hello, friend!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Public Keys"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are"
-"\n"
-"using Tor.\n"
-"\n"
-msgstr ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are"
-"\n"
-"using Tor.\n"
-"\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
-"\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
-"\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
-"\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
-"\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "What are bridges?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "I need an alternative way of getting bridges!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you "
-"must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr ""
-"Another way to get bridges is to send an email to %s. Please note that you "
-"must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "My bridges don't work! I need help!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "If your Tor doesn't work, you should email %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Here are your bridge lines:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Get Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Please select options for bridge type:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Do you need IPv6 addresses?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Do you need a %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Your browser is not displaying images properly."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Enter the characters from the image above..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "How to start using your bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
-"page %s and then follow the instructions there for downloading and starting\n"
-"Tor Browser."
-msgstr ""
-"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
-"page %s and then follow the instructions there for downloading and starting\n"
-"Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:126
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
-"follow\n"
-"the wizard until it asks:"
-msgstr ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
-"follow\n"
-"the wizard until it asks:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:130
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor "
-"connections\n"
-"to the Tor network?"
-msgstr ""
-"Does your Internet Service Provider (ISP) block or otherwise censor "
-"connections\n"
-"to the Tor network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:134
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and"
-"\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and"
-"\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-
-#: lib/bridgedb/strings.py:142
-msgid "Displays this message."
-msgstr "Displays this message."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:146
-msgid "Request vanilla bridges."
-msgstr "Request vanilla bridges."
-
-#: lib/bridgedb/strings.py:147
-msgid "Request IPv6 bridges."
-msgstr "Request IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:149
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Request a Pluggable Transport by TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:152
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Get a copy of BridgeDB's public GnuPG key."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Report a Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Source Code"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Select All"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Show QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode for your bridge lines"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Uh oh, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "It seems there was an error getting your QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
-"your bridge lines onto mobile and other devices."
-msgstr ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
-"your bridge lines onto mobile and other devices."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "There currently aren't any bridges available..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
-msgstr " Perhaps you should try %s going back %s and choosing a different bridge type!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Step %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Download %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Step %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Get %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Step %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Now %s add the bridges to Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust give me bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Advanced Options"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "none"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index e2c5ff6..0000000
--- a/lib/bridgedb/i18n/eo/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,359 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# identity, 2012
-# Rico Chan <rico at tutanota.de>, 2014
-# trio <trio at esperanto.org>, 2011
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: Rico Chan <rico at tutanota.de>\n"
-"Language-Team: Esperanto (http://www.transifex.com/projects/p/torproject/language/eo/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: eo\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Pardonu! Io ne funkcias pri via peto."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Äi tiu estas aÅtomate kreita mesaÄo; bonvole ne respondu.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Jen viaj retpontoj."
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Vi superis la limiton. Bonvolu malakceli! La minimala tempo inter retleteroj estas\n%s horoj. Pliaj retiloj dum tiu tempo estos ignorata."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "ORDONOJ: (kombinu ORDONOJN por samtempe specifiki diversaj opciojn)."
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Estu bonvena al BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Nuntempe subtenata transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Saluton, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Saluton, amiko!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "publikaj Ålosiloj"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Äi tiu retletero estas generita kun Äielarkoj, unikornoj kaj steloj\npor %s je %s, %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB povas provizi pontojn/bridges kun diversaj %stypes de Pluggable Transports%s,\nkiuj povas helpi sekretigi viajn konektojn al la Tor Network kaj malfaciligi provojn kiuj estas destinitaj\nobservi vian datumtrafikon kaj vian uzantecon de Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "AnkaÅ kelkaj retpontoj kun IPv6-adresoj estas disponeblaj, sed iuj Pluggable\nTransports ne estas IPv6-kongrua.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Krome BridgeDB havas abundon da regulaj retpontoj %s sen iuj\nPluggable Transports %s, kiuj eble ne estas mojosa, tamen ofte povas helpi eviti\nreta cenzuro.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Kio estas retpontoj?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s retpontoj %s estas interretaj babiloj kiuj helpas vin eviti cenzuro."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Mi bezonas alternativon ekhavi retpontojn!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Sendu retletero al %s por ekhavi retpontojn alternative. Bonvolu konstati ke vi\nbezonas sendi retleteron per adreso de la sekvonta retpoÅta provizanto:\n%s, %s aÅ %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Miaj retpontoj ne funkcias! Mi bezonas helpon!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Se Tor ne funkcias, kontaktigu %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Provu klarigi vian kazon tre detale kaj aldonu liston da retpontoj kaj Pluggable Transports\nkiujn vi provis uzi. Krome aldonu vian Tor Browser-version kaj Äiujn mesaÄojn, kiujn Tor\neligis."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Jen viaj retpontoj:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Ekhavu retpontojn!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Bonvolu selekti opciojn pri retpontospeco."
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Äu vi bezonas IPv6-adresojn?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Äu vi bezonas %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Via retumilo vidigas bildojn ne dece."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Enigu la literoj en la bildo Äi-supre."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Kiel komenci uzi viajn retpontojn."
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Por enigi retpontojn en la Tor Browser, sekvu la lernilon de la %s\nTor-Browser-elÅutaĵopaÄo %s por starti la Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Kiam la Tor retagordaj dialogujo elklapas, alklaku 'agordi/konfiguri' kaj sekvu\nla asistanto Äis Äi demandas: "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Äu via provizanto de retservoj (angle: ISP) blokas aÅ alimaniere cenzuras konektojn al la Tor-reto?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Selektu 'Jes' kaj alklaku 'sekva'. Por konfiguri viajn novajn retpontojn, kopiu kaj alglui\nla retpontolineojn al la dialogujo. Finfine alklaku 'konektiÄi'.\nSe vi havas problemojn, provi alklaki la 'helpo'-butonon en la Tor-retagordasistanto\npor pli da asistado."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Vidigi Äi tiun mesaÄon."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Peti regulajn retpontojn (nepermutebla transporta retpontoj / non-Pluggable Transport bridges)."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Peti IPv6-retpontojn."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Peti permutebla transporto de TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Ekhavi kopio de publika GnuPG-Ålosilo de BridgeDB."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "raporti cimo"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "fontkodo"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "ÅanÄoprotokolo"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "kontakto"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "ho ve, ho ve!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Nuntempe ne ekzistas retpontojn."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Vi eble davas provi %s reiri %s kaj selekti alian retpontospeco."
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "paÅo %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "elÅuti %s Tor-retumilo %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "paÅo %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ekhavi %s retpotojn %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "paÅo %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Nun %s aldonu la retpontojn al la Tor-retumilo %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sT%suj donu retpontojn al mi!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "detalaj opcioj"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ne"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "neniom/neniu"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sE%skhavi Bridges"
diff --git a/lib/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index b681b88..0000000
--- a/lib/bridgedb/i18n/es/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,388 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# dark_yoshi <angelargi at yahoo.es>, 2014
-# toypurina <biolenta at riseup.net>, 2014
-# BL <tresemes3 at gmail.com>, 2014
-# NinjaTuna <nort0ngh0st at hotmail.com>, 2011
-# Noel Torres <envite at rolamasao.org>, 2013
-# Paola Falcon <cacoepy at gmail.com>, 2014
-# Jonis <srvial at hotmail.com>, 2014
-# strel, 2013-2015
-# strel, 2012
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 13:50+0000\n"
-"Last-Translator: strel\n"
-"Language-Team: Spanish (http://www.transifex.com/projects/p/torproject/language/es/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: es\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "¡Lo siento! Algo fue mal con tu solicitud."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Este es un mensaje automático; por favor no responda.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Aquà están sus bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Ha excedido el lÃmite de velocidad. Por favor, ¡más despacio! El tiempo mÃnimo entre correos electrónicos es %s horas. Los siguientes correos durante este periodo de tiempo serán ignorados. "
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combine COMMANDs (comandos) para especificar múltiples opciones simultáneamente)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "¡Bienvenido a BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Transport TYPEs actualmente soportados:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "¡Eh, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "¡Hola amigo!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Claves públicas"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Este correo fue generado con arcoiris, unicornios y chispitas para %s el %s a las %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB puede proveer bridges con varios %stipos de Pluggable Transports%s que pueden ayudar a ofuscar sus conexiones a la red Tor, haciendo que sea más difÃcil para alguien que esté viendo su tráfico en la red el determinar que ud. está usando Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "También hay disponibles varios bridges con direcciones IPv6, aunque algunos\nPluggable Trasnports no son compatibles con IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Además, BridgeDB tiene un montón de sencillos-clásicos-estándar (vanilla) bridges\n%s sin ningún Pluggable Transport %s, lo que tal vez no suena tan molón, pero que\naún pueden ayudar a eludir la censura en Internet en muchos casos.\n\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "¿Qué son los puentes ('bridges')?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "Los %s bridges %s son un tipo de repetidores Tor que le ayudan a eludir la censura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "¡Necesito un modo alternativo de conseguir puentes!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Otra forma de obtener repetidores puente (bridges) es enviar un correo electrónico a %s. Por favor observe que tiene que enviar el correo usando la dirección de uno de los siguientes proveedores de correo electrónico:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "¡Mis bridges/puentes no funcionan! ¡Necesito ayuda!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Si su Tor no funciona, debe enviar un correo a %s"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Intenta incluir tanta información como puedas de tu caso, incluyendo la lista de\nbridges (repetidores puente) y Pluggable Transports (transportes conectables) que\nintentaste usar, tu versión de Navegador Tor y cualquier mensaje que haya dado Tor, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Aquà están tus lÃneas de bridge:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "¡Obtener bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Por favor, selecciona opciones para el tipo de bridge:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "¿Necesitas direcciones IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "¿Necesitas un %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Tu navegador no está mostrando las imágenes correctamente."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Introduce los caracteres de la imagen de arriba..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Cómo comenzar a usar tus bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Para introducir bridges (repetidores puente) en el Tor Browser, sigue las\ninstrucciones de la %s página de descarga del Tor Browser %s para iniciarlo."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Cuando el cuadro de 'Configuraciones de red Tor' aparezca, haz clic en 'Configurar'\ny sigue el asistente hasta que pregunte:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "¿Su proveedor de Internet (ISP) bloquea o censura de alguna manera las conexiones a la red Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Selecciona 'SÃ' y luego haz clic en 'Siguiente'. Para configurar tus nuevos\nbridges, copia y pega las lÃneas de bridges en el cuadro de texto.\nPor último, haz clic en 'Conectar', ¡y listo!\nSi encuentras problemas, prueba dando clic en el botón 'Ayuda'\nen el asistente de 'Configuraciones de red Tor' para asistencia adicional."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Muestra este mensaje."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Solicitar bridges estándar (vanilla)."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Solicitar bridges IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Solicitar un Pluggable Transport por TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Obtener una copia de la clave pública GnuPG de BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Reportar una falla"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Código fuente"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Registro de cambios"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contactar"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Seleccionar todos"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Mostrar código QR"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Código QR para sus lÃneas de repetidores puente"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Oh oh, ¡la liamos!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Parece que hubo un error al obtener su código QR."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Este código QR contiene sus lÃneas de repetidores puente (bridges). Escanéelo con un lector de códigos QR para copiar sus lÃneas de puentes a dispositivos móviles/celulares y otros dispositivos."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Ahora mismo no hay ningún bridge disponible..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "¡Tal vez debas probar %s volviendo atrás %s y seleccionando un tipo diferente de bridge!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Paso %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Descarga %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Paso %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obtenga los %s puentes ('bridges') %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Paso %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Ahora %s añada los bridges al Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "¡%sS%sólo dame los bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opciones avanzadas"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ninguno"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "¡%sS%sÃ!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sbtener bridges"
diff --git a/lib/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 22727ad..0000000
--- a/lib/bridgedb/i18n/es_CL/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,102 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# halbares <pablo.alvarez.flores at gmail.com>, 2014
-# Pablo Lezaeta <prflr88 at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2014-03-26 19:50+0000\n"
-"Last-Translator: Pablo Lezaeta <prflr88 at gmail.com>\n"
-"Language-Team: Spanish (Chile) (http://www.transifex.com/projects/p/torproject/language/es_CL/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: es_CL\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "¿Cuáles son los puentes?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s Puentes retrasadores %s son repetidores de Tor que ayudan a combatir la censura."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "¡Necesito una forma alternativa de obtener puentes!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Otra forma de encontrar direcciones de puentes públicos es mediante enviar un correo electrónico (De %s o %s dirección) a %s con la lÃnea «get bridges» en el cuerpo del mensaje."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "¡Mi puente no funciona! ¡Ayúdenme!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Si Tor no funciona, por favor envÃe un correo %s. Intente incluir toda la información posible, incluyendo la lista de los puentes que ha utilizado, el paquete nombre de archivo/versión que utilizó, los mensajes que Tor devolvió, etc."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Para utilizar las lÃneas anteriores, dirÃjase a la página de configuración de la red de VIdalia, y haga clic en «Mi ISP bloquea las conexiones a la red Tor». A continuación, añada cada dirección puente una a una."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "No hay puentes disponibles actualmente"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Actualiza tu navegador a Firefox"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Escribe las dos palabras"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Paso 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Obtenga %s Paquete del navegador Tor %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Paso 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obtenga %s puentes %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Paso 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Ahora %s añadir los puentes a Tor %s"
diff --git a/lib/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index e6e956c..0000000
--- a/lib/bridgedb/i18n/eu/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,101 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Antxon Baldarra <baldarra at lavabit.com>, 2013
-# Antxon Baldarra <baldarra at lavabit.com>, 2011, 2012
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-05-09 15:19+0000\n"
-"Last-Translator: Antxon Baldarra <baldarra at lavabit.com>\n"
-"Language-Team: Basque (http://www.transifex.com/projects/p/torproject/language/eu/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: eu\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "Zer dira zubiak?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s zubi erreleak zentsura sahiesten laguntzen zaituzten %s Tor erreleak dira."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Zubiak eskuratzeko modu alternatibo bat behar dut!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Zubi publikoen helbideak aurkitzeko beste modu bat eposta (%s edo %s helbide batetik) bat bidaltzea da %sera 'get bridges' lerroarekin mezuaren gorputzean bertan."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "Nire zubiak ez dute funtzionatzen! Laguntza behar dut!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Zure Torek funtzionatzen ez badu, %sera email bat idatzi beharko zenuke. Saiatu zure kasuaren inguruan ahalik eta informazio gehien sartzen, erabili dituzun zubien zerrenda, erabili duzun bundlearen fitxategi izena/bertsioa, Torek eman dizkizun mezuak, eta abar barne."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Gaineko lerroak erabiltzeko, joan zaitez Vidaliaren sare ezarpenenak orrira, eta sakatu \"Nire Internet Zerbitzu Hornitzaileak Tor sarera konektatzea blokeatzen dit\". Ondoren gehitu ezazu zubi helbide bakoitza banan banan."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "Ez dago zubirik eskuragarri oraintxe bertan."
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Berritu zure nabigatzailea Firefoxera"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Idatzi itzazu bi hitzak"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "1. pausua"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Eskuratu %s Tor Browser Bundlea %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "2. pausua"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Eskuratu %s zubiak %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "3. pausua"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Orain %s gehitu zubiak Torera %s"
diff --git a/lib/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 5e9ebc8..0000000
--- a/lib/bridgedb/i18n/fa/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,387 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# arashaalaei <aalaeiarash at gmail.com>, 2011
-# signal89 <ali.faraji90 at gmail.com>, 2014
-# ardeshir, 2013
-# Gilberto, 2014-2015
-# johnholzer <johnholtzer123 at gmail.com>, 2014
-# Mohammad Hossein <desmati at gmail.com>, 2014
-# perspolis <rezarms at yahoo.com>, 2011
-# Setareh <setareh.salehee at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-17 22:11+0000\n"
-"Last-Translator: Gilberto\n"
-"Language-Team: Persian (http://www.transifex.com/projects/p/torproject/language/fa/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: fa\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Ù
تاس٠ÙستÛÙ
Ø Ø¯Ø± رابط٠با درخÙاست Ø´Ù
ا خطاÛÛ Ø±Ø® داد٠است."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[اÛÙ ÛÚ© Ù¾ÛاÙ
Ø®Ùدکار Ù
Û Ø¨Ø§Ø´Ø¯Ø ÙØ·Ùا پاسخ ÙدÙÛد.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "ÙÙرست Ù¾ÙâÙØ§Û Ø´Ù
ا:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Ø´Ù
ا از Øد Ù
جاز تجاÙز ÙÙ
Ùد٠اÛد. ÙØ·Ùا٠اÙØ¯Ú©Û Ú©Ùد تر اÛÙ
Û٠بÙرستÛد. ØداÙ٠زÙ
ا٠Ù
Ù
Ú©Ù Ù
ابÛÙ\nارسا٠اÛÙ
ÛÙ Ùا %s ساعت است. تÙ
اÙ
Û Ø§ÛÙ
ÛÙ ÙØ§Û Ø¨Ø¹Ø¯Û Ø¯Ø± اÛÙ Ù
دت زÙ
اÙÛ Ø§Ø±Ø³Ø§Ù ÙØ®ÙاÙÙد شد."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "دستÙرات: (ترکÛب دستÙرات Ø¨Ø±Ø§Û Ù
شخص کرد٠گزÛÙÙ ÙØ§Û Ù
تعدد ب٠طÙر ÙÙ
زÙ
اÙ)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "ب٠BridgeDB Ø®ÙØ´ Ø¢Ù
دÛد!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "اÙÙاع Ù
Ù
ک٠از TYPE ÙØ§Û Ø§ÙتÙاÙ:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "سÙاÙ
Ø %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "سÙاÙ
دÙست Ù
Ù!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Public Key Ùا"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "اÛ٠اÛÙ
Û٠با رÙÚ¯ÛÙ Ú©Ù
ا٠ÙØ§Ø Ø§Ø³Ø¨ ÙØ§Û ØªÚ©Ø´Ø§Ø® Ù Ø²Ø±Ù Ù Ø¨Ø±Ù Ø¨Ø±Ø§Û %s بر %s ٠در %s تÙÙÛد شد٠است."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB Ù
Û ØªÙاÙد Ù¾Ù Ùا با %sاÙÙاع Ù
ØªØ¹Ø¯Ø¯Û Ø§Ø² Pluggable Transports%s ÙراÙÙ
Ú©Ùد Ú©Ù Ú©Ù
Ú© Ù
Û Ú©ÙÙد اتصا٠شÙ
ا ب٠Tor Network تا Øد اÙ
کا٠Ù
بÙÙ
Ù ÙاشÙاس باÙÛ Ø¨Ù
اÙد Ù ÙÛÚ Ú©Ø³ دÛÚ¯Ø±Û ÙتÙاÙد ب٠راØØªÛ Ø¨Ù Ø§ØªØµØ§Ù Ø´Ù
ا ب٠اÛÙترÙت Ùظارت Ú©Ùد ٠تشخÛص بدÙد Ú©Ù Ø´Ù
ا از Tor استÙاد٠Ù
Û Ú©ÙÛد.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Ø¨Ø¹Ø¶Û Ø§Ø² Ù¾ÙâÙا با آدرسâÙØ§Û IPv6 در دسترس ÙستÙØ¯Ø Ùر ÚÙد Ø¨Ø±Ø®Û Ø§Ø² Pluggable\nØÙ
Ù Ù ÙÙÙâÙا با IPv6 سازگار ÙÛستÙد.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "ÙÙ
ÚÙÛÙ BridgeDB شاÙ
٠تعداد زÛØ§Ø¯Û Ù¾Ù ÙØ§Û Ø§Ø¨ØªØ¯Ø§ÛÛ Ù ÙدÛÙ
Û Ø§Ø³Øª Ú©Ù %s Pluggable Transports ÙدارÙد %s Ù Ù
Ù
ک٠است Ø¨Û Ù
صر٠ب٠Ùظر برسÙد اÙ
ا ÙÙ
ÚÙا٠Ù
Ù
ک٠است در Ù
Ùارد Ù
ØªØ¹Ø¯Ø¯Û Ø¬Ùت دÙر زد٠ÙÛÙترÛÙÚ¯ ب٠شÙ
ا Ú©Ù
Ú© Ú©ÙÙد.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Ù¾Ù Ùا ÚÙ ÙستÙدØ"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Ù¾Ù Ùا %s ÙستÙد بازپخش Ú©ÙÙد٠ÙØ§Û ØªÙر ÙستÙد ک٠ب٠شÙ
ا Ø¨Ø±Ø§Û Ø¯Ùر زد٠ساÙسÙرکÙ
Ú© Ù
Û Ú©ÙÙد ."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "ب٠ÛÚ© را٠دÛگر Ø¨Ø±Ø§Û Ø¯Ø±ÛاÙت bridge Ùا اØتÛاج دارÙ
!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ÛÚ© طرÛ٠دÛÚ¯Ø±Û Ú©Ù Ù
ÛتÙاÙÛد Ù¾ÙÙا را بگÛرÛد, از طرÛÙ Ùزستاد٠ÛÚ© اÛÙ
Û٠ب٠%s Ù
Ûباشد. ÙØ·Ùا دÙت Ùر Ù
اÛÛد Ú©Ù Ø´Ù
ا باÛد اÛÙ
ÛÙ Ø®Ùد را ØتÙ
ا از ÛÚ©Û Ø§Ø² ÙراÙÙ
Ú©ÙÙد گا٠اÛÙ
ÛÙÛ Ø²Ûر بÙرستÛØ°: %s %s Ù Ûا %s ."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ب٠کÙ
Ú© اØتÛاج دارÙ
! Ù¾Ù ÙØ§Û Ù
٠کار ÙÙ
ÛâÚ©ÙÙد!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "اگر تÙر Ø´Ù
ا کار ÙÙ
Û Ú©Ùد, Ø´Ù
ا باÛد اÛÙ
ÛÙ Ú©ÙÛد %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Ø³Ø¹Û Ú©ÙÛد تا Ù
Û ØªÙاÙÛد اطÙاعات بÛØ´ØªØ±Û Ø±Ø§ ÙراÙÙ
Ú©ÙÛد. از جÙ
ÙÙ ÙÛست Ù¾Ù Ùا Ù Pluggable Transports ÙاÛÛ Ú©Ù Ø§Ø³ØªÙاد٠کرد٠اÛد. ÙÙ
ÚÙÛÙ Ø´Ù
ار٠Ùسخ٠Tor Browser Ù Ùر Ù¾ÛغاÙ
Û Ú©Ù Tor ب٠شÙ
ا داد٠است."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "خطÙØ· Ù¾Ù Ø´Ù
ا در اÛÙجا:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "درÛاÙت Ù¾Ù Ùا!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ÙØ·Ùا گزÛÙÙ ÙÙع پ٠را اÙتخاب Ú©ÙÛد:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Ø¢Ûا Ø´Ù
ا ب٠آدرس ÙØ§Û IPv6 ÙÛاز دارÛدØ"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Ø¢Ûا Ø´Ù
ا ÙÛاز دارÛد ب٠ÛÚ© %sØ"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Ù
رÙرگر Ø´Ù
ا تصاÙÛر را Ø¨Ù Ø¯Ø±Ø³ØªÛ ÙÙ
اÛØ´ ÙÙ
Û Ø¯Ùد."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "کاراکتر Ùا را از تصÙÛر باÙا Ùارد Ú©ÙÛد..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "ÚÚ¯ÙÙÚ¯Û Ø§Ø² Ù¾ÙâÙØ§Û Ø®Ùد استÙاد٠کÙÛد"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Ø¨Ø±Ø§Û Ùارد کرد٠پ٠Ùا در Tor Browser, دÙبا٠کÙÛد دستÙراÙعÙ
٠را در %s Tor\nBrowser صÙØ٠داÙÙÙد %s Ø¨Ø±Ø§Û Ø´Ø±Ùع Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "ÙÙØªÛ Ú©Ù ØµÙØÙ 'تÙظÛÙ
ات شبک٠تÙر' ÙÙ
اÛØ´ داد٠شد, رÙÛ Ú¯Ø²ÛÙÙ 'Ù¾ÛکربÙدÛ' Ú©ÙÛÚ© Ú©ÙÛد ٠دÙبا٠کÙÛد\nتا زÙ
اÙÛ Ú©Ù wizard از Ø´Ù
ا بپرسد:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Ø¢Ûا شرکت اراÛ٠دÙÙد٠اÛÙترÙت (ISP) Ø´Ù
ا بÙاک Ù
Û Ú©Ùد Ù Ûا ساÙسÙر Ù
Û Ú©Ùد ارتباطات\nشبک٠تÙر Ø´Ù
ا راØ"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "'بÙÙ' را اÙتخاب کرد٠٠سپس 'بعدÛ' را اÙتخاب Ú©ÙÛد. Ø¨Ø±Ø§Û Ù¾ÛکربÙØ¯Û Ù¾Ù Ø¬Ø¯Ûد Ø®Ùد, copy Ù paste Ú©ÙÛد خطÙØ· پ٠را در Ù
ت٠جعب٠ÙرÙدÛ. در ÙÙاÛت, رÙÛ 'اتصاÙ' Ú©ÙÛÚ© Ú©ÙÛد,\nرا٠درستش اÛ٠است! اگر Ù
Ø´Ú©ÙÛ Ù¾ÛØ´ Ø¢Ù
د, Ú©ÙÛÚ© Ú©ÙÛد رÙÛ Ú©ÙÛد 'راÙÙÙ
ا'\nدر 'تÙظÛÙ
ات شبک٠تÙر' wizard Ø¨Ø±Ø§Û Ø§Ø·Ùاعات بÛشتر."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "اÛÙ Ù¾ÛاÙ
را ÙÙ
اÛØ´ Ù
Û Ø¯Ùد."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "درخÙاست Ù¾ÙâÙØ§Û Ø¹Ø§Ø¯Û"
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "درخÙاست Ù¾ÙâÙØ§Û IPv6"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "درخÙاست ÛÚ© رÙØ´ Transport جاÛگزÛ٠براساس TYPE"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "درÛاÙت ÛÚ© Ú©Ù¾Û Ø§Ø² Ú©ÙÛد عÙ
ÙÙ
Û BridgeDB"
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "گزارش ÛÚ© باگ"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "کد Ù
Ùبع"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "ÙÛست تغÛÛرات"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "تÙ
اس"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "اÙتخاب ÙÙ
Ù"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "ÙÙ
اÛØ´ QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode Ø¨Ø±Ø§Û Ø®Ø·ÙØ· Ù¾Ù Ø®Ùد"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "اÙ٠اÙÙØ Ú©Ùسر٠اسپاگتÛ!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "ب٠Ùظر Ù
Û Ø±Ø³Ø¯ ÛÚ© خطا در گرÙت٠QRCode Ø´Ù
ا ÙجÙد دارد."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "اÛÙ QRCode شاÙ
٠خطÙØ· Ù¾Ù Ø®Ùد. اسک٠آ٠را با ÛÚ© Ø®ÙاÙÙد٠QRCode Ø¨Ø±Ø§Û Ú©Ù¾Û Ø®Ø·ÙØ· Ù¾Ù Ø®Ùد بر رÙÛ ØªÙÙÙ ÙÙ
را٠٠دستگا٠ÙØ§Û Ø¯Ûگر."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "در Øا٠Øاضر ÙÛÚ Ù¾Ù Û Ø¯Ø± دسترس ÙÛست"
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr " شاÛد Ø´Ù
ا باÛد Ø³Ø¹Û Ú©ÙÛد %s بازگشت %s ٠اÙتخاب ÙÙع Ù¾Ù ÙØ§Û Ù
ختÙÙ!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Ù
رØÙÙ %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "داÙÙÙد %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Ù
رØÙÙ %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "درÛاÙت %s Ù¾Ùâ Ùا %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Ù
رØÙÙ %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "ØاÙا %s اضاÙ٠کرد٠پ٠Ùا ب٠Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sÙ%sÙØ· بد٠ب٠Ù
Ù Ù¾Ù Ùا را!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "گزÛÙÙ ÙØ§Û Ù¾ÛشرÙتÙ"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "ÙÙ"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ÙÛÚ"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sب%sÙÙ!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sد%sرÛاÙت Ù¾ÙâÙا"
diff --git a/lib/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index a2afaa5..0000000
--- a/lib/bridgedb/i18n/fi/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,386 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Jorma Karvonen <karvonen.jorma at gmail.com>, 2015
-# Jorma Karvonen <karvonen.jorma at gmail.com>, 2014
-# Spacha <miikasikala96 at gmail.com>, 2015
-# Ossi Kallunki <ossikallunki at gmail.com>, 2013
-# Sami Kuusisto <sami at 6sto.com>, 2014
-# viljaminojonen <Viljami.Nojonen at live.com>, 2014
-# Finland355 <ville.ehrukainen2 at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-03 18:01+0000\n"
-"Last-Translator: Jorma Karvonen <karvonen.jorma at gmail.com>\n"
-"Language-Team: Finnish (http://www.transifex.com/projects/p/torproject/language/fi/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: fi\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Pyyntösi epäonnistui jostain syystä."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Tämä on automaattinen viesti; älä vastaa tähän.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Tässä ovat sinun siltasi."
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Olet ylittänyt nopeusrajan. Hidasta hiukan! Minimiaika viestien välillä on %s tunnissa. Kaikki lisäviestit tämän ajan kuluessa ohitetaan."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "KOMENNOT: (yhdistä KOMENNOT määritelläksesi useita valitsimia samanaikaisesti)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Tervetuloa BridgeDB :hen!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Nykyiset tuetut transport TYPE-siirtotyypit:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hei, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hei ystävä!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Julkiset avaimet"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Tämä sähköposti tuotettiin käyttäjälle %s %s klo %s"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB voi tarjota usean %styyppisiä irrotettavia Pluggable Transports%s-siirtoja,\njotka voivat auttaa peittämään yhteytesi Tor Network-verkkoon, mikä tekee vaikeammaksi\nkenellekään seurata internet-liikennettäsi ottaakseen selvää, että käytätkö Tor-palvelua.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Myös joitakin siltoja IPv6-osoitteilla on saatavilla, vaikka jotkut irrotettavat Pluggable\nTransports-siirrot eivät ole IPv6-yhteensopivia.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Lisäksi BridgeDB:llä on useita tavallisia siltoja %s ilman mitään irrotettavia\nPluggable Transports %s-siirtoja, jotka eivät ehkä kuulosta herkullisilta, mutta\nne voivat silti auttaa monissa tapauksissa kiertämään internet-sensuuria.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Mitä ovat sillat?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s-sillat %s ovat Tor-välitysjärjestelmiä, jotka auttavat kiertämään sensurointia."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Tarvitsen vaihtoehtoisen tavan tavoittaa sillat!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Toinen tapa siltojen hankkimiseen on lähettää sähköpostia osoitteeseen %s. Huomaa,\nettä sinun on lähetettävä sähköpostiviesti käyttäen yhtä seuraavien sähköpostitarjoajien\nosoitetta:\n%s, %s tai %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Siltani eivät toimi! Tarvitsen apua!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Jos Tor ei toimi sinulla, sinun pitäisi sähköpostittaa %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Yritä sisällyttää niin paljon tietoa tapauksestasi kuin voit, mukaanlukien siltojen\nluettelo ja irrotettavat Pluggable Transport-siirrot, joita yritit käyttää,\nTor Browser-versio, ja kaikki viestit, jotka Tor ilmoitti, jne."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Tässä ovat siltarivisi:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Hae sillat!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Valitse valitsimet siltatyypille:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Tarvitsetko IPv6-osoitteita?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Tarvitsetko %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Selaimesi ei näytä kuvia oikein."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Syötä merkit yläpuolella olevasta kuvasta..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Kuinka aloitat siltojesi käytön"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Siltojen lisäämiseksi Tor Browseriin, seuraa ohjeita %s Tor\nBrowser-lataussivulla %s Tor Browserin käynnistämiseksi."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Kun âTor-verkkoasetuksetâ-valintaikkuna ponnahtaa näkyviin, napsauta âConfigureâ ja seuraa asetusvelhoa, kunnes se kysyy:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Estääkö tai sensuroiko Internet-palvelutarjoajasi (ISP) muuten yhteyksiä\nTor-verkkoon?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Valitse âKylläâ ja napsauta sitten âSeuraavaâ. Uusien siltojen konfiguroimiseksi kopioi ja\nliitä siltarivit tekstisyöteikkunaan. Napsauta lopuksi âYhdistäâ, ja siirtymisen pitäisi\nonnistua! Jos kohtaat pulmia, yritä saada lisäapua napsauttamalla âOpasteâ-\npainiketta âTor-verkkoasetuksetâ-asetusvelhossa."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Näyttää tämän viestin."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Pyydä tavallisia siltoja."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Pyydä IPv6-siltoja."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Pyydä TYPE-tyyppistä irrotettavaa Pluggable Transport-siirtoa."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Hae kopio BridgeDB:n julkisesta GnuPG-avaimesta."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Ilmoita ohjelmointivirheestä"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Lähdekoodi"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Muutosloki"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Yhteystieto"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Valitse kaikki"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Näytä QR-koodi"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Siltariviesi QR-koodi"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Jossain on pulma!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Tapahtui virhe QR-koodia noudettassa."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Tämä QR-koodi sisältää siltarivisi. Skannaa se QR-koodinlukijalla kopioidaksesi siltarivit matkapuhelimeesi ja muihin laitteisiin."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Tällä hetkellä ei ole yhtään siltaa saatavilla..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Ehkä sinun pitäisi yrittää %s palaamalla %s ja valitsemalla eri siltatyyppi!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Vaihe %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Lataa %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Vaihe %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Hanki %s sillat %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Vaihe %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Nyt %s lisää sillat Tor Browseriin %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sA%snna minulle vain sillat!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Lisävalitsimet"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ei"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ei mitään"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sK%syllä!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sH%sae sillat"
diff --git a/lib/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 58eef87..0000000
--- a/lib/bridgedb/i18n/fr/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,393 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# apaddlingduck, 2014
-# Boubou <cece31840 at gmail.com>, 2015
-# Cryptie <cryptie at fsfe.org>, 2014
-# fayçal fatihi, 2014
-# Frisson Reynald <frissonreynald at yahoo.fr>, 2014
-# hpatte <hadrien.44 at gmail.com>, 2014
-# Lucas Leroy <lerlucas at rocketmail.com>, 2014
-# Lunar <lunar at torproject.org>, 2013
-# Onizuka, 2013
-# mehditaileb <mehditaileb at liberte-info.net>, 2011
-# Onizuka, 2013
-# themen <themen2004 at gmail.com>, 2014
-# Towinet, 2014
-# Yannick Heintz, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 18:31+0000\n"
-"Last-Translator: Boubou <cece31840 at gmail.com>\n"
-"Language-Team: French (http://www.transifex.com/projects/p/torproject/language/fr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: fr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Désolé ! Un problème est survenu suite à votre requête."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ceci est un message automatique, merci de ne pas répondre.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Voici vos bridges :"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Vous avez dépassé la limite de vitesse. S'il vous plaît ralentissez ! Le temps minimum entre les courriels est %s heures. Tous les autres courriels pendant cette période seront ignorés."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDES: (combiner des commandes pour spécifier plusieurs options en même temps)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Bienvenue à BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "TYPEs de transport pris en charge actuellement:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Salut, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Bonjour, l'ami !"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Les clés publiques"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ce courriel a été généré avec des arcs en ciel, des licornes, et des étincelles pour %s, %s à %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB peut fournir des ponts avec plusieurs %stypes de transports enfichables%s, qui peuvent aider à brouiller vos connexions au réseau Tor, ce qui rend plus difficile pour tous ceux qui regardent votre trafic Internet de déterminer que vous utilisez Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Certains ponts avec les adresses IPv6 sont également disponibles, bien que certains Transports soient enfichables ; ils ne sont pas compatibles avec l'IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "De plus, BridgeDB a beaucoup de banals ponts %s sans aucun\nTransports Enfichables (Pluggable Transports) %s, ne semblant pas être autant cool, mais pouvant toutefois\naider à contourner la censure Internet dans des nombreux cas.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Que sont les bridges ?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s sont des relais Tor qui vous aident à contourner la censure."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "J'ai besoin d'une alternative pour obtenir des adresses de bridges !"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Une autre alternative pour obtenir bridges est d'envoyer un email à %s. Veuillez noter que vous devez envoyer un email en utilisant une adresse email d'un des fournisseurs d'accès suivant: %s, %s ou %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mes bridges ne fonctionnent pas, j'ai besoin d'aide !"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Si votre Tor ne marche pas, vous devriez envoyer un courriel %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Essayez d'inclure autant d'infos que possible concernant votre cas, y compris la liste des\nponts et des Transports Enfichables (Pluggable Transports) que vous avez essayé d'utiliser, votre version de Tor Browser,\net n'importes quel autres messages que Tor vous a sorti, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Voici vos lignes des bridges:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Obtenez des bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Sélectionnez vos choix pour le type de bridge, s'il vous plaît:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Avez-vous besoin des adresses IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Avez-vous besoin un %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Votre navigateur n'affiche pas correctement les images."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Inscrire les caractères de l'image ci-dessusâ¦"
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Comment démarrer l'utilisation de vos Bridges."
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Pour entrer des ponts dans Tor Browser, suivez les instructions sur la %s page de téléchargement de Tor Browser %s pour démarrer Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Quand la boite de dialogue 'Paramètres Réseaux Tor' s'affiche, cliquez 'Configurer' et suivez l'assistant jusqu'à ce quâil demande :"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Est-ce que votre fournisseur d'accès à Internet (FAI) bloquent ou censurent les connexions vers le réseau Tor ?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Sélectionnez 'Oui' et ensuite cliquez 'Suivant'. Pour configurer vos nouvelles bridges, copiez et collez les lignes bridge dans la prochaine case de saisie de texte. Enfin, cliquez 'Connexion', et tout devrait marcher ! Si vous avez des difficultés, essayez le bouton 'Aide' dans 'Paramètres Réseaux Tor' pour plus dâassistance."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Afficher ce message."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Demander les bridges vanilles."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Demander IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Demander un Pluggable Transport par TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Obtenir une copie de la clef GnuPG publique BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Faire un rapport de Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Le code source"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Tout sélectionner"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Montrer QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode pour vos bridge lines"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Houlà !"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Il semblerait qu'il y ai eu une erreur en chargeant votre QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ce QRCode contient vos bridge lines. Scannez le avec un lecteur de QRCode pour copier vos bridge lines dans votre mobile ou d'autres appareils."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Il n'y a pas de bridges disponibles en ce momentâ¦"
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Peut-être vous devriez revenir %s en arrière %s et choisir un type différent de bridge !"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Etape %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Télécharger %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Etape %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Récupérez les %s addresses de bridge %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Etape %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Maintenant %s ajoutez les bridges au navigateur Tor %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%suste me donner les ponts!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Options avancées"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Non"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "aucun"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sO%sui!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sbtenir Bridges"
diff --git a/lib/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 740cfe3..0000000
--- a/lib/bridgedb/i18n/fr_CA/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,383 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Lunar <lunar at torproject.org>, 2013
-# mehditaileb <mehditaileb at liberte-info.net>, 2011
-# Onizuka, 2013
-# yahoe.001, 2014-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 13:41+0000\n"
-"Last-Translator: yahoe.001\n"
-"Language-Team: French (Canada) (http://www.transifex.com/projects/p/torproject/language/fr_CA/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: fr_CA\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Désolé! Quelque chose a mal tourné avec votre requête."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ceci est un message automatisé; veuillez ne pas répondre.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Voici vos ponts :"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Vous avez dépassé la limite. Veuillez ralentir! La durée minimum entre courriels\nest de %s heures. Tout autre courriel durant cette période sera ignoré."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDES : (combinez les COMMANDES pour spécifier plusieurs options simultanément)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Bienvenue à BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "TYPES de transport pris en charge présentement :"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Allô, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Bonjour l'ami!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Clefs publiques"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ce courriel a été généré avec des arcs en ciel, des unicornes et des paillettes pour %s le %s à %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB peut fournir des ponts avec plusieurs %stypes de transports enfichable%s,\npouvant aider à obscurcir vos connexions au réseau Tor, rendant difficile pour\nquiconque surveillant votre trafic Internet de déterminer que vous\nutilisez Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Des ponts avec des adresses IPv6 sont aussi proposés, bien que certains transports\nenfichables ne soient pas compatibles avec IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "De plus, BridgeDB comportent de nombreux ponts %s traditionnels sans\ntransport enfichable %s qui peuvent quand même aider à contourner\nla censure Internet dans bien des cas.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Que sont les ponts?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Les ponts %s sont des relais Tor qui vous aident à contourner la censure."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "J'ai besoin d'une alternative pour obtenir des ponts!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Une autre façon d'obtenir des ponts est d'envoyer un courriel à %s. Veuillez prendre\nnote que vous devez envoyer le courriel en utilisant une adresse d'un des fournisseurs\nde courriel suivants :\n%s, %s ou %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mes ponts ne fonctionnent pas, j'ai besoin d'aide!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Si votre Tor ne fonctionne pas, vous devriez envoyer un courriel à %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Essayez d'inclure autant d'infos sur votre cas que possible, incluant la liste de\nponts et de transports enfichables que vous avez essayé d'utiliser, votre version du\nnavigateur Tor et tout message donné par Tor, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Voici vos lignes de pont :"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Obtenir des ponts!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Veuillez choisir des options pour le type de ponts :"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Avez-vous besoin d'adresses IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Avez-vous besoin d'un %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Votre navigateur n'affiche pas les images correctement."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Saisir les caractères inscrits sur l'image ci-dessus..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Comment commencer à utiliser vos ponts"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Pour saisir vos ponts dans le navigateur Tor, suivez les instructions\nsur la %s page de téléchargement du navigateur Tor %s pour démarrer le navigateur Tor."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Quand la fenêtre « Paramètres du réseau Tor » s'affiche, cliquez sur « Configurer » et\nsuivez l'assistant jusqu'à ce qu'il demande :"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Votre fournisseur de service Internet (FSI) bloque-t-il ou censure-t-il\nvos connexions au réseau Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Sélectionnez « Oui » et ensuite cliquez sur « Suivant ». Pour configurer vos\nnouveaux ponts, copiez et collez vos lignes de pont dans la boîte de saisie\nde texte. Enfin cliquez sur « Connecter » et vous devriez avoir fini! Si vous éprouvez\ndes problèmes, essayez de cliquez sur le bouton « Aide » dans l'assistant des\n« Paramètres du réseau Tor » pour un un soutien supplémentaire."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Affiche ce message."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Demander des ponts traditionnels."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Demander des ponts IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Demander un transport enfichable par TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Obtenir une copie de la clef GnuPG publique de BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Rapporter un bogue"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Code source"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Journal des changements"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Tout sélectionner"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Montrer le code QR"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Code QR de vos lignes de pont"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Une erreur est survenue!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Il semble avoir eu une erreur de récupération de votre code QR."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ce code QR contient vos lignes de pont. Balayez-le avec un lecteur de codes QR pour copier vos lignes de pont sur votre appareil."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Aucun pont n'est disponible présentement..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Vous devriez peut-être %s revenir en arrière %s et choisir un différent type de pont!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Ãtape %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Télécharger %s le navigateur Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Ãtape %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obtenir %s les ponts %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Ãtape %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Maintenant, %s ajoutez les ponts au navigateur Tor %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "Donnez-moi %sj%suste des ponts! "
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Options avancées"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Non"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "aucun"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sO%sui!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sbtenir des ponts"
diff --git a/lib/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 6aff6d7..0000000
--- a/lib/bridgedb/i18n/gl/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,101 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# manuel meixide <m.meixide at gmail.com>, 2013
-# mbouzada <mbouzada at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-05-08 15:33+0000\n"
-"Last-Translator: manuel meixide <m.meixide at gmail.com>\n"
-"Language-Team: Galician (http://www.transifex.com/projects/p/torproject/language/gl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: gl\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "Que son os repetidores ponte?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s Repetidores Ponte %s son repetidores Tor que axudan a burlar a censura."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Eu necesito un xeito alternativo de obter pontes!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Outra forma de atopar os enderezos de pontes públicas é enviar un correo electrónico (desde un %s ou un enderezo %s) para %s coa liña 'get bridges' por si soa no corpo do correo electrónico."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "As miñas pontes non funcionan! Necesito axuda!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Se o Tor non funciona, ten que enviar un correo-e a %s. Probe incluÃr tanta información sobre o seu caso como poida, incluÃndo a lista de pontes que usou, o nome do paquete/versión que usou, as mensaxes que Tor lle deu , etc"
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Para usar as liñas máis arriba, ten que ir á páxina de Configuración de rede do Vidalia, e premer en \"O meu ISP bloquea as conexións á rede Tor\". A continuación, engada un enderezo dunha ponte de cada vez."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "Non hai pontes actualmente dispoñibles"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Actualiza o teu navegador a Firefox"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Escriba as dúas palabras"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Paso 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Obter o %s Paquete Navegador Tor %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Paso 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obter as %s pontes %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Paso 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Agora %s engada as pontes a Tor %s"
diff --git a/lib/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index f700478..0000000
--- a/lib/bridgedb/i18n/he/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,105 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# aharonoosh <aharonoosh1 at gmail.com>, 2012
-# Elifelet <arab.with.nargila at gmail.com>, 2014
-# GenghisKhan <genghiskhan at gmx.ca>, 2013
-# Kunda, 2014
-# Cuvint Chofschy <static.172 at 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: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2014-01-30 12:30+0000\n"
-"Last-Translator: Kunda\n"
-"Language-Team: Hebrew (http://www.transifex.com/projects/p/torproject/language/he/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: he\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "××× ×שר××?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s ×××¡×¨× ×שר %s ××× × ×××¡×¨× Tor ×שר ×ס×××¢×× ×× ×עק××£ ×¦× ××ר×."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "×× × ×ק×ק ×××¨× ×××פ×ת ×ק××ת ×שר××!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "××¨× ××רת ××צ××ת ×ת××ת ×©× ×שר פ×××× ××× × ×©×××ת ××××´× (×ת×× ×ת××ת %s ×× %s) ×× %s ×¢× ×ש××¨× 'get bridges' ×ש×עצ×× ×ת×× ×××£ ×××××¢×."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "××שר×× ×©×× ×× ×¢×××××! ×× × ×ק×ק ××¢×ר×!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "×× Tor ×× ×¢××× ×צ××, ×¢××× ×ש××× ××××´× ×× %s. × ×¡× ××××× ××× ×©××תר ××××¢ ××××ת ×××§×¨× ×©×× ××× ×©××××××ª× ×עש×ת ××, ×××× ×ת רש××ת ××שר×× ×שר ×שת×שת ××, ×©× ×ק×××¥/××¨×¡× ×©× ×××××× (bundle) ש×שת×שת, ×××××¢×ת ×שר Tor ×סר ×××׳."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "××× ××שת×ש ×ש×ר×ת ××¢××, ×××©× ×× ×¢××× ×××ר×ת רשת ×ת×× Vidalia, ××ק×ק \"ספק ×××× ××¨× × ×©×× ×××¡× ××××ר×× ×רשת Tor\". ×××¨× ×× ××סף ×× ×ת××ת ×שר ××ת ××× ×¤×¢×."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "××× ×שר×× ×××× ×× ×עת"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "ש××¨× ×ת ××פ××¤× ×©×× ×× Firefox"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "×קש ×ת ×©×ª× ××××××"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "×¦×¢× 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "××©× ×ת %s ××××ת ××פ××¤× ×©× Tor %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "×¦×¢× 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "××©× %s ×שר×× %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "×¦×¢× 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "×עת %s ××סף ×ת ××שר×× ×× Tor %s"
diff --git a/lib/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 3d6ccbf..0000000
--- a/lib/bridgedb/i18n/hr_HR/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,384 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Ana B, 2013-2014
-# Armando Vega <synaan at gmail.com>, 2012
-# skiddiep <lyricaltumor at gmail.com>, 2014-2015
-# Tomislav SiroglaviÄ <tomsiro at gmail.com>, 2014
-# gogo <trebelnik2 at gmail.com>, 2012
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-16 12:31+0000\n"
-"Last-Translator: skiddiep <lyricaltumor at gmail.com>\n"
-"Language-Team: Croatian (Croatia) (http://www.transifex.com/projects/p/torproject/language/hr_HR/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: hr_HR\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Oprostite! Nešto je pošlo po krivu s Vašim zahtjevom."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ovo je automatizirana poruka; molimo ne odgovarati.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Ovdje su vaši mostovi:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "PreÅ¡li ste limit. Molimo usporite! Minimum vremena izmeÄu emailova je %s sati. Svi daljnji emailovi tijekom ovog vremenskog perioda bit Äe ignorirani."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (kombinirajte COMMANDs kako bi naznaÄili viÅ¡e opcija istovremeno)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Dobrodošli u BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Trenutno podržani transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Bok, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Bok, prijatelju!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Javni kljuÄevi"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ovaj email je sastavljen s dugama, jednorozima i iskricama\nza %s na %s u %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB nudi mostove s razliÄitim %stipovima Pluggable Transports%s\nkoji mogu pomoÄi prikriti VaÅ¡e veze s Tor mrežom, otežavajuÄi onome tko\nprati VaÅ¡ internet promet da vidi da koristite Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Neki mostovi s IPv6 adresama su takoÄer dostupni, no neki Pluggable \nTransports nisu kompatibilni s IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Nadalje, BridgeDB ima mnoÅ¡tvo dobrih, starih mostova %s bez ikakvih\nPluggable Transports %s koji možda ne zvuÄe prezanimljivo, ali mogu\npomoÄi zaobiÄi internet cenzuru u mnogo sluÄajeva.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Å to su mostovi?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Mostovi %s su Tor releji koji vam pomažu zaobiÄi cenzuru."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Trebam alternativni naÄin dobivanja mostova!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "JoÅ¡ jedan naÄin za dobiti mostove je da poÅ¡aljete email na %s. Imajte na umu da morate\nposlati email koristeÄi adresu jednog od sljedeÄih davatelja email usluge: %s %s ili %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Moji mostovi ne rade! Treba mi pomoÄ!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ako vaš Tor ne radi, pošaljite email na %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "PokuÅ¡ajte naznaÄiti Å¡to viÅ¡e informacija o vaÅ¡em sluÄaju, ukljuÄujuÄi popis\nmostova i Pluggable Transports koje ste pokuÅ¡ali koristiti, verziju vaÅ¡eg Tor Browsera,\ni bilo kakve poruke koje je Tor izbacio, itd."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Ovdje su linije vaših mostova:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Dobijte Mostove!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Molimo odaberite opcije za tip mosta:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Trebaju li vam IPv6 adrese?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Trebate li %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Vaš preglednik ne prikazuje slike ispravno."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Unesite znakove sa slike iznad..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Kako poÄeti koristite VaÅ¡e mostove"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Kako bi unijeli mostove u Tor Browser, slijedite upute na %s Tor\nBrowser download stranici %s da bi pokrenuli Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Kad iskoÄi prozorÄiÄ 'Tor Mrežne Postavke', kliknite 'Konfiguriraj' i slijedite\nÄarobnjaka dok ne upita:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokira li vaÅ¡ Internet Service Provider (ISP - pružatelj internet usluge) ili ikako drugaÄije cenzurira veze\ns Tor mrežom?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Odaberite 'Da', a zatim 'SljedeÄe'. Da bi konfigurirali vaÅ¡e nove mostove, kopirajte i \nzalijepite linije mostova u kuÄicu za unos teksta. Na kraju, kliknite 'Spoji se', i \nsve bi trebalo biti u redu! Ako naiÄete na probleme, probajte kliknuti dugme 'PomoÄ'\nu 'Tor Mrežne Postavke' Äarobnjaku za daljnju pomoÄ."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Prikazuje ovu poruku."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Zatraži dobre, stare mostove."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Zatraži IPv6 mostove."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Zatraži Pluggable Transport po TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Dobij kopiju BridgeDB javnog GnuPG kljuÄa."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Prijavi Greški"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Izvorni Kod"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Zapisnik o promjenama"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Odaberi Sve"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Pokaži QR kod"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR kod za Vaše linije mostova"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "O ne, greška!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Äini se da je doÅ¡lo do greÅ¡ke pri dobavljanju VaÅ¡eg QR koda."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ovaj QRKod sadrži VaÅ¡e linije mostova. Skenirajte ga s ÄitaÄem QR koda da bi kopirali linije mostova na mobilne i ostale ureÄaje."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Trenutno nema dostupnih mostova..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Možda bi trebali probati %s vratiti se nazad %s i odabrati neki drugi tip mosta!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Korak %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Preuzmi %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Korak %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Dobijte %s mostove %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Korak %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Sad %s dodaj mostove u Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sS%samo mi daj mostove!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Napredne Opcije"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ne"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ništa"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sD%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sD%sobij Bridges"
diff --git a/lib/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 5f352bb..0000000
--- a/lib/bridgedb/i18n/hu/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,384 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Blackywantscookies <gaborcika at citromail.hu>, 2014
-# Lajos Pasztor <mrlajos at gmail.com>, 2014
-# Cerbo <taiurin at gmail.com>, 2014
-# vargaviktor <viktor.varga at gmail.com>, 2013,2015
-# vargaviktor <viktor.varga at gmail.com>, 2011
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-04 11:11+0000\n"
-"Last-Translator: vargaviktor <viktor.varga at gmail.com>\n"
-"Language-Team: Hungarian (http://www.transifex.com/projects/p/torproject/language/hu/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: hu\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Elnézést! Valami rosszul működött a kérésed közben."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ez egy automata levél, kérjük ne válaszoljon.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Itt vannak a hÃdjaid:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Ãn túllépte a megadott mérték határt. Kérünk lassÃtson le! A minimum idÅ email-ek között\n%s óra. Minden további emailt ez az idÅ alatt elutasÃtunk."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (kombinálja COMMANDs-okat hogy többféle opciót adhasson meg egyszerre)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Ãdvözöl a BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Jelenleg támogatott átviteli tÃpusok: "
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hé, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hello!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Nyilvános kulcsok"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ez az email szivárványokkal, unikornisokkal, és ragyogásokkal volt generálva\n%s -nek %s -án %s -kor."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB hidakat tud bisztosÃtani számos %stÃpusával a Pluggable Transports%s -nak\namely segÃti a kapcsolataidat összekeverni a Tor Network -ben, ezzel sokkal\nnehezebbé teszi akárkinek hogy megnézze az internet forgalmadat és hogy meghatározzák hogy Tor -t használsz.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Néhány hÃd IPv6 cÃmekkel is elérhetÅ, habár négány Pluggable\nTransports nem IPv6 kompatibilis.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Ezen felül, BridgeDB -nek van sok régi módi \"vanÃlia\" hÃdjai %s melyek Csatlakoztatható SzállÃtók %s nélkül vannak\nmely talán nem hallatszik szuperül, de Åk még mindig\ntudnak segÃteni kikerülni az internet cenzúráját.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Mik is a Hidak?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "A %s Hidak %s azok Tor relék, melyek segÃtik önt a cenzúra elkerülésében."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Szükségem van egy alternatÃv módra a Hidak beszerzéséhez!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Egy másik módja, hogy megkapd a bridge listát küldj emailt a %s cÃmre. Kérlek vedd figyelembe\naz emailt az alábbi email szolgáltató cÃmekrÅl küldd:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "A HÃdjaim nem működnek! SegÃtségre van szükségem!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ha a Tor böngészÅd nem működik, akkor küldj egy email-t %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Probáljon minél több információt megadn amennyit csak tud, beleértve a listáját \na %s hidaknak és Pluggable Transports -nak, amelyet használni próbált, A Tor Browser verzióját,\nés minden üzenetet melyet Tor adott ki, stb."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Itt vannak a hÃd soraid:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Hidak szerzése!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Kérem, válasszon opciót a hÃd tÃpushoz:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Kellenek önnek IPv6 cÃmek?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Szüksége van egy %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Ez a böngészÅ nem jelenÃti meg a képeket rendesen."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Adja meg a karaktereket amik a fenti képen láthatóak ..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Hogyan kezdjük használni a hidat "
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "A hidak megadásához a Tor Browser -hez, kövese az utasÃtásokat a %s Tor\nBrowser letöltés oldalán %s a Tor Browser indÃtásához."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Amikor a 'Tor Hálózati BeállÃtások' dialógus felugrik, kattintson a 'Konfigurálás'-ra és kövesse\na varászlót amÃg az kéri hogy:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokkolja vagy cenzúrázza az ön internet szolgáltatója (ISP) a kapcsolatokat\na Tor hálózatához?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Válassza hogy 'Igen' és utána kattintson a 'KövetkezÅ\"-re. Az ön új hidjai konfigurásához, másolja\nés illessze be a HÃd sorokat a felugró ablakba. Végül, kattintson 'Kapcsolódás'-ra, és \nmár készen is van. Ha valamilyen hibát tapasztal, próbáljon a 'SegÃtség'\n gombra kattintani a 'Tor hálózati beállÃtások' varázslóban a tobábbi segÃtségért."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "MegjelenÃti ezt az ütenetet."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Nem csatlakoztatható szállÃtó hÃd kérelme."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPV6 hÃd kérése."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Egy csatlakoztatható szállÃtó kérelme TYPE szerint."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Másolat szertése a BridgeDB's publikus GnuPG kulcsából."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Hiba jelentése"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Forrás kód"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Változások"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kapcsolat"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Mindent kijelöl"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "QR Kód megjelenÃtése"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR Kód a hÃd sorokhoz"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Hoppá!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Ãgytűnik a QR Kód hibás."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ez a QR Kód a hÃd sorait tartalmazza. Olvassa be egy QR Kód olvasóval, hogy átmásolja a HÃd sorokat mobil és egyéb eszközeire."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Jelenleg nincsenek rendelkezésre álló hidak..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Talán megpróbálhatnál %s vissza menni %s és másik HÃd tÃpust választani."
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Lépés %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Letöltés %s Tor BöngészŠ%s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Lépés %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s Hidak %s szertése"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Lépés %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Most %s a hidak hozzáadása a Tor BöngészÅhöz %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sC%ssak adjál már nekem hidakat!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Haladó beállÃtások"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nem"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "Semmi"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sS%szeretnék!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sS%szerezni Bridge -et!"
diff --git a/lib/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 1449290..0000000
--- a/lib/bridgedb/i18n/id/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,101 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# MasIs <is.roadster at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-12-10 10:16+0000\n"
-"Last-Translator: MasIs <is.roadster at gmail.com>\n"
-"Language-Team: Indonesian (http://www.transifex.com/projects/p/torproject/language/id/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: id\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "Apa itu 'bridge'?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s 'Bridge relays' %s adalah perelai Tor yang membantu Anda mengelakkan sensor."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Adakah cara lain untuk memperoleh 'bridges'?"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Cara lain untuk memperoleh alamat 'public bridge' adalah dengan mengirimkan email (dari sebuah %s atau %s alamat) ke %s yang hanya berisi 'get bridges' pada isi email."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "Bridge saya gagal! Bantu saya!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Jika Tor Anda gagal bekerja, Anda harus menulis email %s. Cantumkan seluruh permasalahan yang Anda dapati, termasuk daftar 'bridge' yang Anda gunakan, nama file 'bundle' atau versi yang Anda pakai, pesan error Tor, dan sebagainya."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Untuk menggunakan baris di atas, pilih halaman pengaturan Vidalia's Network, dan klik \"My ISP blocks connections to the Tor network\". Kemudian tambahkan alamat 'bridge' satu per satu."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "Saat ini tidak ada 'bridge' yang tersedia."
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Ubah browser Anda menjadi Firefox."
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Ketikkan kedua kata tersebut."
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Langkah 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Dapatkan %s bundel Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Langkah 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Dapatkan %s 'bridge' %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Langkah 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "%s Tambahkan 'bridge' ke Tor %s"
diff --git a/lib/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index c2b1bf8..0000000
--- a/lib/bridgedb/i18n/it/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,391 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# fetidyoo <tru74368 at yahoo.com>, 2011
-# Francesca Ciceri <madamezou at zouish.org>, 2014
-# HostFat <hostfat at gmail.com>, 2015
-# ironbishop <ironbishop at fsfe.org>, 2011
-# ironbishop <ironbishop at fsfe.org>, 2011
-# Jacob Appelbaum <jacob at appelbaum.net>, 2009
-# Luca Marzo <luca at jeckodevelopment.it>, 2011
-# n0on3 <a.n0on3 at gmail.com>, 2011
-# Paolo Stivanin <paolostivanin at gmail.com>, 2014
-# Random_R, 2013
-# Random_R, 2013-2014
-# fetidyoo <tru74368 at yahoo.com>, 2011
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-02 02:41+0000\n"
-"Last-Translator: HostFat <hostfat at gmail.com>\n"
-"Language-Team: Italian (http://www.transifex.com/projects/p/torproject/language/it/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: it\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Siamo spiacenti ma qualcosa è andato storto con la tua richiesta."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Questo è un messaggio automatico, si prega di non rispondere.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Ecco i tuoi bridge:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Hai superato il limite massimo. Per favore rallenta! Il tempo minimo tra le email\nè di %s ore. Tutte le altre email durante questo periodo di tempo verranno ignorate."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combina i COMMANDs per specificare opzioni multiple simultaneamente)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Benvenuto/a su BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Transport TYPEs attualmente supportati:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hey, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Ciao amico!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Chiavi Pubbliche"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Questa email è stata generata con arcobaleni, unicorni e scintille\nper %s il %s alle %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB può fornire dei bridge con numerosi %stipi di Pluggable Transports%s,\ni quali possono aiutare ad offuscare la tua connessione alla Rete Tor, rendendo\npiù difficile a chiunque vedere il tuo traffico internet per determinare che stai\nusando Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Sono disponibili anche alcuni bridge con indirizzi IPv6, sebbene alcuni Pluggable\nTransports non siano compatibili con IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "In aggiunta, BridgeDB ha anche numerosi bridge vanilla %s senza alcun\nPluggable Transports %s, il che forse non suona bene, ma possono ancora\naiutare ad aggirare la censura in internet in molti casi.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Cosa sono i bridge?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridge %s sono relay di Tor che ti aiutano ad aggirare la censura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Mi serve un altro modo per avere dei bridge!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Un altro modo di ottenere i bridge è inviare un'email a %s. Nota che devi\ninviare l'email usando uno degli indirizzi tra i seguenti provider email:\n%s, %s o %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "I miei bridge non funzionano! Mi serve aiuto!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Se Tor non ti funziona, manda un'email a %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Cerca di includere più informazioni possibili sul tuo caso, incluso l'elenco di\nbridge e Pluggable Transports che hai provato a usare, la versione di Tor Browser,\nqualsiasi messaggio ti abbia mostrato Tor, ecc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Ecco le tue linee bridge:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Ottieni dei Bridge!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Seleziona le opzioni per il tipo di bridge:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Ti servono indirizzi IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Ti serve un %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Il tuo browser non mostra le immagini in modo corretto."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Inserisci i caratteri nell'immagine sopra..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Come iniziare a usare i tuoi bridge"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Per inserire i bridge nel Tor Browser, segui le istruzioni nella %s pagina\ndi download di Tor Browser %s per avviare Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Quando appare la finestra delle 'Impostazioni Rete Tor', clicca 'Configura' e\nsegui la procedura giudata finchè non chiede:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Il tuo Internet Service Provider (ISP) blocca o censura le connessioni alla\nrete Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Seleziona 'Sì' e poi clicca 'Avanti'. Per configurare i nuovi bridge, copia e\nincolla le linee bridge nella casella di testo. Infine, clicca 'Connetti' e dovrebbe\nessere tutto pronto! Se avrai problemi, prova a cliccare il pulsante 'Aiuto'\nnella procedura guidata 'Impostazioni Rete Tor' per avere assistenza."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Mostra questo messaggio."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Richiedi bridge vanilla."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Richiedi bridge IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Richiedi un Pluggable Transport by TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Ottieni una copia della chiave pubblica GnuPG di BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Segnala un Errore"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Codice Sorgente"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contatti"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Seleziona tutto"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Mostra QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode per le tue linee ponte"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Uh oh, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Sembra esserci stato un errore nell'ottenere il tuo QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Questo QRCode contiene le tue linee ponte. Scansionalo con un lettore QRCode per copiare le tue linee ponte su mobile o altri dispositivi."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Non ci sono bridge disponibili al momento..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Potresti provare a %s tornare indietro %s e scegliere un tipo di bridge diverso!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Passo %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Scarica %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Passo %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Ottenere dei %s bridge %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Passo %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Ora %s aggiungi i bridge a Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sD%sammi i bridge e basta!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opzioni Avanzate"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "No"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "nessuno"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sS%sì!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sttieni Bridge"
diff --git a/lib/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index e552128..0000000
--- a/lib/bridgedb/i18n/ja/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,362 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# brt <87 at itokei.info>, 2013
-# ABE Tsunehiko, 2014
-# ã¿ã«ãã· <gomidori at live.jp>, 2013
-# ã¿ã«ãã· <gomidori at live.jp>, 2014
-# Masaki Saito <rezoolab at gmail.com>, 2013
-# è¤åãç² <m1440809437 at hiru-dea.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-12-26 04:42+0000\n"
-"Last-Translator: è¤åãç² <m1440809437 at hiru-dea.com>\n"
-"Language-Team: Japanese (http://www.transifex.com/projects/p/torproject/language/ja/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ja\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "ç³ã訳ããã¾ãããããªã¯ã¨ã¹ãã«åé¡ãããã¾ããã"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[ãã®ã¡ã¼ã«ã¯èªåéä¿¡ãããã¡ãã»ã¼ã¸ã§ããè¿ä¿¡ããªãã§ãã ããã]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "ããªãã®ããªãã¸ã¯ãã¡ã:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "ã¬ã¼ãå¶éãè¶
éãã¾ãããã¡ã¼ã«éã®æå°æé㯠%s æéã§ãããã®æéä¸ã«ããã«éä¿¡ããã¡ã¼ã«ã¯å
¨ã¦ç¡è¦ããã¾ãã"
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "ã³ãã³ã: (ã³ãã³ããçµã¿åããã¦ãåæã«è¤æ°ã®ãªãã·ã§ã³ãæå®ãã)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "BridgeDB ã¸ããããï¼"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "ç¾å¨ãµãã¼ãããã¦ãããã©ã³ã¹ãã¼ãã¿ã¤ã:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "ã©ãã %s ããï¼"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ã©ãããããã«ã¡ã¯ï¼"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "å
¬ééµ"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "ãã®ã¡ã¼ã«ã¯ %s ã« %s %s ã« rainbow åã³ unicorn ã sparkle ã§çæããã¾ããã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB ã¯ã%sã¿ã¤ãã® Pluggable Transports%s ã§ããªãã¸ãæä¾ãã¾ãã\nTor ãããã¯ã¼ã¯ã¸ã®æ¥ç¶ãæããã¥ãããããã¨ã«å½¹ç«ã¡ãããªãã®\nã¤ã³ã¿ã¼ããããã©ãã£ãã¯ãç£è¦ãã¦ãã誰ããããªãã Tor ã使ç¨ãã¦ãããã¨ãå¤å¥ãããã¨ãããã«é£ãããªãã¾ãã\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "IPv6 ã®ã¢ãã¬ã¹ã®ããªãã¸ãå©ç¨ã§ãããã®ãããã¾ããã Pluggable Transports ã«ã¯ IPv6 ã«äºææ§ããªããã®ãããã¾ãã\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "å ãã¦ã BridgeDB ã¯ã %s Pluggable Transport %s ã®ãªãå¤ãã®ä½ã®å¤å²ããªã\né常ã®ããªãã¸ãæã¡ãããã¯ã¯ã¼ã«ã«ã¯æããªãããããã¾ãããããããå¤ãã®å ´åãã¤ã³ã¿ã¼ãããæ¤é²ãåé¿ããã®ã«ãªãæå¹ã§ãã\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "ããªãã¸ã¨ã¯ï¼"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s ããªã㸠%s ã¯ã ã¤ã³ã¿ã¼ãããæ¤é²ãåé¿ããå©ãã¨ãªã Tor ãªã¬ã¼ã§ãã"
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "ããªãã¸ãå¾ãä»ã®æ¹æ³ãå¿
è¦ã§ã!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ããªãã¸ãå
¥æããå¥ã®ããæ¹ã¯ %s ã¾ã§ã¡ã¼ã«ãéããã¨ã§ãã\n以ä¸ã®ã¡ã¼ã«ãããã¤ãã®ãã¡ã®1ã¤ã®ã¢ãã¬ã¹ã使ç¨ãã¦ã¡ã¼ã«ãéä¿¡ããªããã°ãªããªããã¨ã«ã注æãã ãã:\n %s ã %s ã¾ã㯠%s"
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ç§ã®ããªãã¸ãåãã¾ãã! å©ãã¦ãã ãã!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Tor ããã¾ãåä½ããªãå ´åã %s ã¾ã§ã¡ã¼ã«ãä¸ããã"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ããªãã使ããã¨ããããªãã¸ã Pluggable Transports ã ãå©ç¨ã® Tor Browser ãã¼ã¸ã§ã³ããã㦠Tor ãåºåããã¡ãã»ã¼ã¸çãªã©ãå«ã¿ãåºæ¥ãéãããªãã®ã±ã¼ã¹ã«é¢ããå¤ãã®æ
å ±ãå«ãã¦ã¿ã¦ãã ããã"
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "ããªãã®ããªãã¸ã©ã¤ã³ã¯ãã¡ãã§ã:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ããªãã¸ãå
¥æï¼"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ããªãã¸ã¿ã¤ãã®ãªãã·ã§ã³ãé¸æãã¦ãã ãã:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "IPv6 ã¢ãã¬ã¹ãå¿
è¦ã§ããï¼"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "%s ãå¿
è¦ã§ããï¼"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "ãå©ç¨ã®ãã©ã¦ã¶ã¯é©åã«ç»åã表示ãã¦ãã¾ããã"
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "ä¸è¨ã®ç»åããæåãå
¥åãã¦ãã ãã..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "ããªãã¸ä½¿ç¨ã®å§ãæ¹"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Tor Browser ã«ããªãã¸æ
å ±ãå
¥åããã«ã¯ã %s Tor Browser ã®ãã¦ã³ãã¼ããã¼ã¸ %s ã®æ示ã«å¾ã£ã¦ Tor Browser ãèµ·åãã¦ãã ããã"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "ãTor ãããã¯ã¼ã¯è¨å®ããã¤ã¢ãã°ããããã¢ããããéãè¨å®ãã¯ãªãã¯ãã¦\nèãããã¾ã§ã¦ã£ã¶ã¼ãã«å¾ã£ã¦ãã ããã"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ãå©ç¨ã®ã¤ã³ã¿ã¼ããããµã¼ãã¹ãããã¤ãã¼ (ISP) 㯠Tor ãããã¯ã¼ã¯ã¸ã®æ¥ç¶ããããã¯ãªãã\nå¥ã®ããæ¹ã§æ¤é²ãã¦ãã¾ããï¼"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ãã¯ãããé¸æãã¦ãã次ããã¯ãªãã¯ãã¦ãã ãããæ°ããããªãã¸ãæ§æããããã«ãããã¹ã\nå
¥åããã¯ã¹ã«ããªãã¸ã©ã¤ã³ãã³ãã¼ãã¼ã¹ããã¦ãã ãããæå¾ã«ããæ¥ç¶ããã¯ãªãã¯ãã¦ã\nããæºåãåºæ¥ãã¯ãã§ãï¼ä½ãåé¡ããã£ããããããªãå©ããå¾ãããã«ãTor\nãããã¯ã¼ã¯è¨å®ãã¦ã£ã¶ã¼ãå
ããã«ãããã¿ã³ãã¯ãªãã¯ãã¦ãã ããã"
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "ãã®ç»åã表示"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "vanilla ããªãã¸ããªã¯ã¨ã¹ã"
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPv6 ããªãã¸ããªã¯ã¨ã¹ã"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "ã¿ã¤ããã¨ã« Pluggable Transport ããªã¯ã¨ã¹ã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "BridgeDB ã® GnuPG å
¬ééµã®ã³ãã¼ãæã«å
¥ãã¾ãããã"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ãã°ãå ±åãã"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "ã½ã¼ã¹ã³ã¼ã"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "å¤æ´å±¥æ´"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "ãåãåãã"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "ãã£ã¨ã¹ãã²ããã£ï¼"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "ä»ã®æç¹ã§ã¯å©ç¨ã§ããããªãã¸ãããã¾ãã..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "ãããã ã %s æ»ã£ã¦ %s ç°ãªãããªãã¸ã¿ã¤ããé¸æãã¦ã¿ãã¹ãã§ãããã"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "ã¹ããã %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "%s Tor Browser %s ããã¦ã³ãã¼ã"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "ã¹ããã %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s ããªã㸠%s ãæã«å
¥ãã"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "ã¹ããã %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "%s Tor Browser ã«ããªãã¸ã追å ãã¾ã %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sã%sã®ããã ããã bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "é«åº¦ãªè¨å®"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "ããã"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ãªã"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sã¯%sãï¼"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 261d478..0000000
--- a/lib/bridgedb/i18n/km/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,360 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Seng Sutha <sutha at open.org.kh>, 2014
-# Sokhem Khoem <sokhem at open.org.kh>, 2014
-# Sok Sophea <sksophea at gmail.com>, 2014
-# Sok Sophea <sksophea at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: Sokhem Khoem <sokhem at open.org.kh>\n"
-"Language-Team: Khmer (http://www.transifex.com/projects/p/torproject/language/km/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: km\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "áá¼áááá! á¢ááá¸âáá½áâáá¶áâááá á»áâáá¶áá½áâáááá¾âááááâá¢áááá"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[áááâáá¶âáá¶áâááááááááááááá·; áá¼áâáá»áâáááá¾áááá]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "áááâáá¶âááááá¸áááâááááâá¢áááá"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "You have exceeded the rate limit. Please slow down! The minimum time between\nemails is %s hours. All further emails during this time period will be ignored."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "áááá¶ááááâáááá¶áá BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "áá
áá
á»ááááááâááâáááááâáá¶áâáá¶áááá TYPEs á"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "á á, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "áá½áááá¸, áá·ááááááááá·!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "ááâáá¶áá¶ááá"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "á¢áá¸áááâáááâáááá¼áâáá¶áâááááá¾áâá¡á¾áââáá¶áá½á rainbows, unicorns, áá·á sparkles\nááááá¶áá %s áá¾ %s áá
%s á"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB áá·áâá¢á¶á
âáááááâááááá¸áááâáá¶áá½áâáááááá %stypes áá½áâá
ááá½áâáá Pluggable Transports%s,\náááâá¢á¶á
âáá½á obfuscate áá¶áâááááá¶ááâááááâá¢áááâáá
âáá¶ááâááááá¶á Tor , áááá¾âá²ááâáá¶âáá¶áâáá¶áâáá·áá¶áâááááá¶ááââáá¾áâá
áá¶á
áááâááááâá¢áááâáááá»áâáá¶áâáááááâáá¶âá¢áááâáááá»áâáááá¾ Tor á\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Some bridges with IPv6 addresses are also available, though some Pluggable\nTransports aren't IPv6 compatible.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\nPluggable Transports %s which maybe doesn't sound as cool, but they can still\nhelp to circumvent internet censorship in many cases.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "áá¾âááááá¸áááââáá¶âá¢ááá¸?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s are Tor relays that help you circumvent censorship."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "áááá»áâáááá¼ááá¶áâáá·áá¸áá¶áááááâáááá¶áááááá¶âááâáá¶áâááá½áâáá¶áâááááá¸ááá!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "áá·áá¸áá½áâáááâáá¾áááá¸ááá½á bridges ááºâáááá¼áâáááá¾âá¢áá¸áááâáá
%s á áá¼áâá
ááá¶áâáᶠá¢ááááááá¼áâááâáááá¾âá¢áá¸áááâáááâáááá¾á¢á¶áááááá¶áâáá¸âáááá»áá áá»áâáááááá¢áá¸áááâáá½áâáááá»áâá
ááááâáááá»áá áá»áâáá¶áâáááááá\n%s, %s ᬠ%s á"
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ááááá¸áááââááááâáááá»áâáá·áâáááá¾ááá¶á! áááá»áâáááá¼ááá¶áâáááá½á!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "ááááá·ááá¾ Tor ááááâá¢áááâáá·áâáááá¾ááá¶á, á¢áááâáá½áááâá¢áá¸ááá %s á"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Try including as much info about your case as you can, including the list of\nbridges and Pluggable Transports you tried to use, your Tor Browser version,\nand any messages which Tor gave out, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "áááâáá¶âáááâááááá¸áááâááááâá¢áááá"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ááá½áâááâááááá¸ááá!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "áá¼áâáááá¾áâááááá¾áâááááá¶ááâááááááâááááá¸áááá"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "áá¾âá¢áááâáááá¼áâáá¶áâá¢á¶áááááá¶á IPv6 á¬?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "áá¾âá¢áááâáááá¼áâáá¶á %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "áááááá·áá¸âá¢áá¸áááºáá·áâááááâá¢áááââáá·áâáááá»áâáááá á¶áâáá¼ááá¶áâáááâáááá¹ááááá¼áâá¡á¾áá"
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "áááá
á¼áâáá½âá¢ááááâáá¸âáá¼ááá¶áâáá¶ááá¾..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "ááááâá
á¶áááááá¾áâáááá¾âááááá¸áááâááááâá¢ááá"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "áá¾áááá¸âáááá
á¼áâááááá¸áááâáá
âáá¶áá Tor Browser, á¢áá»ááááâááá
áááá¸âáááá¶áâáá
âáá¾âáááááâ %s Tor\nBrowser %s áá¾áááá¸âá
á¶áááááá¾á Tor Browser á"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "áá
âáááâáááá¢ááâ 'áá¶áâáááááâááááá¶á' ááá
âá¡á¾á, á
á»á
'áááááâáá
áá¶áááááááá' áá·áâá¢áá»ááááâá¢áááâáááá½ááá¶áâáá á¼áâáááâáá¶ááá·á
áá
âááááâá¢áááá"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Does your Internet Service Provider (ISP) block or otherwise censor connections\nto the Tor network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\npaste the bridge lines into the text input box. Finally, click 'Connect', and\nyou should be good to go! If you experience trouble, try clicking the 'Help'\nbutton in the 'Tor Network Settings' wizard for further assistance."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "áááá á¶áâáá¶áâáááá"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "áááá¾âááááá¸áááâáá¼ááááá¶áá"
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "áááá¾âááááá¸ááá IPv6 á"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "áááá¾âáá¶áâáááááâáááâá¢á¶á
âáááâáá¶áâáá¶áâááá TYPE á"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Get a copy of BridgeDB's public GnuPG key."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ááá¶ááá¶áááâááá á»á"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "áá¼áâááááá"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Changelog"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "áááá¶áááááá"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Uh oh, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "áá
áá
á»ááááááâáá·áâáá¶áâááááá¸áááâáá½áâá
ááá½áâá¡á¾á..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr " áááá ááâáá¶âá¢áááâáá½áááâáááá¶áá¶á %s áááá¡ááâáá
âáá¶áá %s á á¾áâáááá¾áâááááááâááááá¸áááâááááá!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "ááá á¶á %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "áá¶áâáá %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "ááá á¶á %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "áá %s ááááá¸ááá %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "ááá á¶á %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "á¥á¡á¼áâ %s ááááááâááááá¸áááâáá
âáá¶áá Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust áááááâá²ááâáááá»áâáá¼áâááááá¸ááá!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "ááááá¾áâááááá·áâááááá"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "áá"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "áááá¶á"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set ááááá¸ááá"
diff --git a/lib/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 905e22d..0000000
--- a/lib/bridgedb/i18n/kn/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,102 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# msj2, 2013
-# msj2, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-07-31 03:40+0000\n"
-"Last-Translator: msj2\n"
-"Language-Team: Kannada (http://www.transifex.com/projects/p/torproject/language/kn/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: kn\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "ಬà³à²°à²¿à²¡à³à²à³ à²à²³à³ à²
à²à²¦à²°à³ à²à²¨à³?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%sಬà³à²°à²¿à²¡à³à²à³âನ ರಿಲà³à²à²³à³%s à²
à²à²¦à²°à³ ಸà³à²¨à³à²¸à²¾à²°à³âà²à²³à²¿à²à²¦ ನಿಮà³à²®à²¨à³à²¨à³ ದà³à²°à²µà²¿à²¡à³à²µ à²à²¾à²°à³ ರಿಲà³à²à²³à³, "
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "ನನà²à³ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ ಸಿà²à³à²µ ಬದಲಿ ಮಾರà³à² ಬà³à²à³"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "ಪಬà³à²²à²¿à²à³ ಬà³à²°à²¿à²¡à³à²à³âà²à²³ à²
ಡà³à²°à³à²¸à³ ಪಡà³à²¯à³à²µ ದಾರಿಯà²à²¦à³à²°à³, %s ಠವಿಳಾಸà²à³à²à³ à²à²®à³à²²à³ à²à²³à²¿à²¸à²¿ (à²à²à²¦ a %s à²
ಥವಾ a %s à²
ಡà³à²°à³à²¸à³ ) à²à²µà²°à²¿à²à³. ಠ'get bridges' à²
à²à²¤ à²à²®à³à²²à³ à²à²³à²à³ ಬರà³à²¯à²¿à²°à²¿."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "ನನà³à²¨ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ à²à³à²²à²¸ ಮಾಡà³à²¤à²¿à²²à³à²². ಸಹಾಯ ಮಾಡಿ"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "ನಿಮà³à²® à²à²¾à²°à³ à²à³à²²à²¸ ಮಾಡದೠà²à²¦à³à²°à³, %s à²à³ à²à²®à³à²²à³ à²à²³à²¿à²¸à²¿. ಬà³à²°à²¿à²¡à³à²à³âà²à²³ ಪà²à³à²à²¿, ಬà²à²¡à²²à³âನ ಫ಼à³à²²à³â ಹà³à²¸à²°à³/ವರà³à²·à²¨à³, à²à²¾à²°à³ à²à³à²à³à² ಮà³à²¸à³à²à³âà²à²³à³, à²à²°à²°à³ ಮà³à²¸à³à²à³âà²à²³à³, ಠರà³à²¤à²¿ ಸಾಧà³à²¯à²µà²¿à²¦à³à²¦à²·à³à²à³ ಮಾಹಿತಿಯನà³à²¨à³ à²à²®à³à²²à³âನಲà³à²²à²¿ à²à²³à²¿à²¸à²¿."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "ಮà³à²²à³à²à²à²¡ ಲà³à²¨à³âà²à²³à²¨à³à²¨ ಬಳಸಲà³, ವಿಡಲಿಯಾದ à²à²¾à²²à²¬à²à²§ ಸà³à²à³à²à²¿à²à²à³â ಪà³à²à²à³à² ಹà³à²à²¿, à²
ಲà³à²²à²¿ \"My ISP blocks connections to the Tor network\" à²à²¨à³à²¨à³à²µà³à²¦à²° ಮà³à²²à³ à²à³à²²à²¿à²à³ ಮಾಡಿ. ನà²à²¤à²°, à²à²à²¦à³à²à²¦à³ ಬà³à²°à²¿à²¡à³à²à³â à²
ಡà³à²°à³à²¸à³âನ à²à³à²¡à³à²¤à³à²¤à²¾ ಹà³à²à²¿."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "ಯಾವ ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ ಸದà³à²¯à²à³à²à³ ಲà²à³à²¯à²µà²¿à²²à³à²²"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "ನಿಮà³à²® ಬà³à²°à³à²¸à²°à³âನ ಫ಼à³à²°à³âಫ಼ಾà²à³à²¸à³âà²à³ ಬದಲಿಸಿà²à³à²³à³à²³à²¿"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "à²à²°à²¡à³ ಪದà²à²³à²¨à³à²¨à³ à²à³à²ªà³ ಮಾಡಿ"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "ಹà³à²à³à²à³ ೧"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "ಪಡà³à²¯à²¿à²°à²¿ %s à²à²¾à²°à³ ಬà³à²°à³à²¸à²°à³ ಬà²à²¡à²²à³ %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "ಹà³à²à³à²à³ ೨"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ಪಡà³à²¯à²¿à²°à²¿ %s ಬà³à²°à²¿à²¡à³à²à³âà²à²³à³ %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "ಹà³à²à³à²à³ ೩"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "à²à²¦à³à², %s ಠಬà³à²°à²¿à²¡à³à²à³âà²à²³à²¨à³à²¨ %s à²à²¾à²°à³âà²à³ à²à³à²¡à²¿à²¸à²¿à²à³à²³à³à²³à²¿."
diff --git a/lib/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index e877b87..0000000
--- a/lib/bridgedb/i18n/ko/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,104 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# ilbe123 <a3057016 at drdrb.net>, 2014
-# cwt96 <cwt967 at naver.com>, 2012
-# Dr.what <javrick6 at naver.com>, 2014
-# pCsOrI <pcsori at 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: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2014-05-06 06:40+0000\n"
-"Last-Translator: ilbe123 <a3057016 at drdrb.net>\n"
-"Language-Team: Korean (http://www.transifex.com/projects/p/torproject/language/ko/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ko\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "'ë¸ë¦¿ì§'ë?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "ì¤ê³ ìë² ë¸ë¦¿ì§ %s ì¤ìì, %s ê° ì¬ë¬ë¶ì ê²ì´ ì°í를 ëìì¤ ì ìë Tor ì¤ê³ ìë² ì
ëë¤."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "ë¸ë¦¿ì§ë¥¼ ì»ë ë¤ë¥¸ ë°©ë²ì´ ììê¹ì?"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "ê³µê°ë ë¸ë¦¿ì§ 주ì를 ì°¾ì ë, íµì ì ì 'get brides'ë¼ê³ ì´ë©ì¼ì ì ì´ì ë³´ë´ë ë°©ë²ë ììµëë¤.(ë©ì¼ì ë³´ë´ë ì¬ëì 주ì í기ë from %s ëë %s 주ì)"
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "ë¸ë¦¿ì§ê° ë§ì´ ê°ì´ì! ëìì¤ì!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "ê·íì í ë¥´ê° ìëíì§ ìëë¤ë©´, %sì ì´ë©ì¼ì ë³´ë´ì
ì¼ í©ëë¤. ê°ë¥í í ë§ì ì 보를 í¬í¨í´ì ë³´ë´ì£¼ì¸ì. ì를 ë¤ë©´ ê·íê° ì¬ì©í ë¸ë¦¿ì§ì 목ë¡, ê·íê° ì¬ì©íë ë²ë¤ì íì¼ ì´ë¦ê³¼ ë²ì , ê·íì í ë¥´ê° ë§ ê°ì ë ëì¨ ë©ì¸ì§. ì´ ì¸ìë ì ì ì ìë ì¬ë¬ ê°ì§."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "ì íµì ì ì ì¬ì©íìë ¤ë©´ 'Vidalia's Network settings'-ë¹ë¬ë¦¬ì ë¤í¸ìí¬ ì¤ì -íì´ì§ì ë¤ì´ê° í, 'My ISP blocks connections to the Tor network'-Tor ë¤í¸ìí¬ì ë´ ISP ì°ê²°ì ì°¨ë¨-ì í´ë¦í©ëë¤. ê·¸ë¬ë©´ íë²ì ê°ê°ì ë¸ë¦¿ì§ 주ì를 ì¶ê° í ì ììµëë¤."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "íì¬ ì´ì© ê°ë¥í ë¸ë¦¿ì§ ìì"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "ë¹ì ì ë¸ë¼ì°ì 를 íì´ì´íì¤ë¡ ì
ê·¸ë ì´ë íì¸ì!"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "ëê°ì ë¨ì´ë¥¼ ì
ë ¥íììì¤"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "1ë¨ê³"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Tor %s Browser Bundle %s를 ì
ì"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "2ë¨ê³"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s ë¸ë¦¿ì§ %s를 ì
ì"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "3ë¨ê³"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "곧 ë°ë¡ %s Tor ë¸ë¦¬ì§ë¥¼ ì¶ê° %s"
diff --git a/lib/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 0f70c18..0000000
--- a/lib/bridgedb/i18n/lv/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,359 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# OjÄrs Balcers <ojars.balcers at gmail.com>, 2012
-# OjÄrs Balcers <ojars.balcers at gmail.com>, 2013-2014
-# ThePirateDuck <thepirateduck.w at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: ThePirateDuck <thepirateduck.w at gmail.com>\n"
-"Language-Team: Latvian (http://www.transifex.com/projects/p/torproject/language/lv/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: lv\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Atvainojiet! Notikusi ar Jūsu pieprasījumu saistīta kļūme."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Å is ir automÄtisks ziÅojums; lÅ«dzu neatbildiet.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Te ir JÅ«su tilti:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "JÅ«s esat pÄrsniedzis pÄrraides normu. LÅ«dzu, lÄnÄk. MinimÄlais laika ilgums starp\ne-pastiem ir %s stundas. Å ajÄ laika posmÄ visi turpmÄkie e-pasti tiks ignorÄti."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (apvienot KOMANDA's, lai vienlaicÄ«gi norÄdÄ«tu dažÄdas opcijas)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "SveicinÄti BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Šobrīd atbalstītie transporta VEIDI:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hei, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Sveiks, draug!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "PubliskÄs atslÄgas"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "This email was generated with rainbows, unicorns, and sparkles\npriekÅ¡ %s dienÄ %s pl %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB var nodroÅ¡inÄt tiltus ar dažÄdiem %stypes Pluggable Transports%s,\nkas var palÄ«dzÄt maskÄt JÅ«su savienojumu ar Tor Network, tÄdÄjÄdi padarot sarežģītÄk ikvienam, kas seko JÅ«su interneta datu plÅ«smai, noteikt, ka lietojat Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Ir pieejami daži tilti ar IPv6 adresÄm; tanÄ« pat laikÄ daži Pluggable\nTransports nav savietojami ar IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "TurklÄt BridgeDB ir pietiekami daudz parastu, vienkÄrÅ¡u tiltu %s bez jebkÄdiem\nPluggable Transports %s, kas iespÄjams neizklausÄs tik inÄÄ«gi, bet arÄ« tie var\ndaudzos gadÄ«jumos palÄ«dzÄt apiet interneta cenzÅ«ru.\n \n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Kas ir tilti?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Tilti %s ir Tor retranslatori, kas palīdz Jums apiet cenzūru."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Man nepieciešams alternatīvs tiltu iegūšanas veids!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "VÄlviens veids kÄ saÅemt tiltus ir nosÅ«tÄ«t e-pastu uz %s. LÅ«dzu, ievÄrojiet, ka e-pasts ir\njÄnosÅ«ta no viena no sekojoÅ¡ajiem e-pasta pakalpojumu sniedzÄjiem:\n%s, %s vai %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mani tilti nestrÄdÄ! Man nepiecieÅ¡ama palÄ«dzÄ«ba!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ja JÅ«su Tor nestrÄdÄ, Jums jÄnosÅ«ta e-pasts %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Centieties iekļaut pÄc iespÄjas daudz informÄciju par savu situÄciju, tostarp pievienojot to tiltu un Pluggable Transports sarakstu, kurus centÄties izmantot, savu Tor Browser versiju un visus Tor ziÅojumus, un citu lÄ«dzÄ«gu informÄciju."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Te ir Jūsu tiltu līnijas:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "SaÅemt Tiltus!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "LÅ«dzu, izvÄlieties tilta veida opcijas:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Vai ir nepieciešamas IPv6 adreses?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Vai ir nepieciešams %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "JÅ«su pÄrlÅ«ks neattÄlo attÄlus pareizi."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "IevadÄ«t burtus no augstÄk parÄdÄ«tÄ attÄla..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "KÄ sÄkt izmantot JÅ«su tiltus"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Sekojiet instrukcijÄm Tor %s, lai ievadÄ«tu tiltus Tor Broswer\nPÄrlÅ«ka lejuplÄdes lapa %s , lai startÄtu Tor Browser. "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Kad uznirst dialogs \"Tor tÄ«kla iestatÄ«jumi\", noklikÅ¡Ä·iniet \"KonfigurÄt\" un sÄkojiet\nvednim lÄ«dz tas jautÄ:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Vai JÅ«su Interneta pakalpojumu sniedzÄjs (ISP) bloÄ·Ä vai citÄdÄ veidÄ cenzÄ savienojumus\nar tÄ«klu Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Atlasiet \"JÄ\" un tad noklikÅ¡Ä·iniet \"TÄlÄk\". Lai konfigurÄtu savus jaunos tiltus, kopÄjiet un\nielÄ«mÄjiet tiltu lÄ«nijas teksta ievades lodziÅÄ. BeigÄs nokliÅ¡Ä·iniet \"Izveidot savienojumu\" un\nvisam vajadzÄtu notikt! Ja ir problÄmas, turpmÄkai palÄ«dzÄ«bai pamÄÄ£iniet nokliÅ¡Ä·inÄt vednÄ« \"Tor tÄ«kla iestatÄ«jumi\" pogu \"PalÄ«dzÄ«ba\"."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "RÄda ziÅojumu."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Pieprasīt parastos tiltus."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Pieprasīt IPv6 tiltus."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "PieprasÄ«t Pluggable Transport pÄc TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "SaÅemt BridgeDB publiskÄs GnuPG atslÄgas kopiju."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "ZiÅot par kļūdu"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Pirmkods"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "IzmaiÅu žurnÄls"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "SazinÄties"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Ak, man' dieniÅ!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Šobrīd nav pieejamu tiltu..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "IespÄjams, ka Jums jÄmÄÄ£ina %s atgriezties %s un izvÄlÄties citu tiltu veidu!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Solis %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "LejuplÄdÄt %s PÄrlÅ«ku Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Solis %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "SaÅemt %s tiltus %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Solis %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Tagad %s pievienot PÄrlÅ«kam Tor tiltus %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sT%sikai dodiet man tiltus!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "LietpratÄju opcijas "
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "NÄ"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "nekas"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sÄ!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sS%saÅemt tiltus"
diff --git a/lib/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index d509e3c..0000000
--- a/lib/bridgedb/i18n/mk/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,101 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# VHR <viktor.hr at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2014-03-12 22:21+0000\n"
-"Last-Translator: VHR <viktor.hr at gmail.com>\n"
-"Language-Team: Macedonian (http://www.transifex.com/projects/p/torproject/language/mk/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: mk\n"
-"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "ШÑо Ñе пÑемоÑÑÑваÑа?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s Ñелеи за ÐÑемоÑÑÑваÑа %s Ñе Tor Ñелеи кои Ñи Ð¿Ð¾Ð¼Ð°Ð³Ð°Ð°Ñ Ð´Ð° го Ð·Ð°Ð¾Ð±Ð¸ÐºÐ¾Ð»Ð¸Ñ ÑензÑÑиÑаÑеÑо."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ðи ÑÑеба алÑеÑнаÑивен наÑин да добиÑам пÑемоÑÑÑваÑа!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "ÐÑÑг наÑин да Ñе наÑÐ´Ð°Ñ Ð°Ð´ÑеÑиÑе на ÑавниÑе пÑемоÑÑÑваÑа е да Ñе пÑаÑи е-поÑÑа (од %s или %s адÑеÑа) на %s Ñо ÑекÑÑ 'get bridges' во ÑодÑжинаÑа на поÑÑаÑа."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "ÐоиÑе пÑемоÑÑÑваÑа не ÑÑнкÑиониÑааÑ! Ðи ÑÑеба помоÑ!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Ðко ÑвоÑÐ¾Ñ Tor не ÑÑнкÑиониÑа, ÑÑеба да пÑаÑÐ¸Ñ Ðµ-поÑÑа на %s. ÐÑÐ¾Ð±Ð°Ñ Ð´Ð° внеÑÐµÑ ÐºÐ¾Ð»ÐºÑ ÑÑо Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾Ð²ÐµÑе инÑоÑмаÑии за ÑвоÑÐ¾Ñ ÑлÑÑаÑ, вклÑÑиÑелно и лиÑÑа од пÑемоÑÑÑваÑа кои ги коÑиÑÑиÑ, имеÑо/веÑзиÑаÑа на пакеÑÐ¾Ñ ÐºÐ¾Ñ Ð³Ð¾ коÑиÑÑиÑ, поÑакиÑе кои ги Ð´Ð¾Ð±Ð¸Ð²Ð°Ñ Ð¾Ð´ Tor, иÑн."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Ðа да ги коÑиÑÑÐ¸Ñ Ð³Ð¾Ñниве линии, оди на ÑÑÑанаÑа на подеÑÑваÑа на Vidalia's Network, и избеÑи \"ÐоÑÐ¾Ñ ISP ги блокиÑа вÑÑкиÑе кон мÑежаÑа на Tor\". ÐоÑоа Ð´Ð¾Ð´Ð°Ñ Ñа адÑеÑаÑа на Ñекое пÑемоÑÑÑваÑе еднаÑ."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "ÐÑемоÑÑÑваÑаÑа Ñе моменÑално недоÑÑапни"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "ÐадгÑади го ÑвоÑÐ¾Ñ Ð¿ÑегледÑÐ²Ð°Ñ Ð²Ð¾ Firefox"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "ÐапиÑи ги дваÑа збоÑа"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Ð§ÐµÐºÐ¾Ñ 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Ðеми %s Ð¿Ð°ÐºÐµÑ Ð¿ÑелиÑÑÑÐ²Ð°Ñ Tor %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Ð§ÐµÐºÐ¾Ñ 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Ðеми %s пÑемоÑÑÑваÑа %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Ð§ÐµÐºÐ¾Ñ 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Сега %s Ð´Ð¾Ð´Ð°Ñ Ð³Ð¸ пÑемоÑÑÑваÑаÑа во Tor %s"
diff --git a/lib/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 94bd12a..0000000
--- a/lib/bridgedb/i18n/ms_MY/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,102 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# shahril <mohd_shahril_96 at yahoo.com>, 2013
-# Weldan Jamili <mweldan at 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: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-11-05 05:30+0000\n"
-"Last-Translator: shahril <mohd_shahril_96 at yahoo.com>\n"
-"Language-Team: Malay (Malaysia) (http://www.transifex.com/projects/p/torproject/language/ms_MY/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ms_MY\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "Apakah itu bridges ?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridge relays %s adalah tor relays yang akan membantu anda menghindari penapisan Internet."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "Saya perlukan cara alternatif untuk mendapatkan bridges!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "Cara lain bagi mencari alamat bridges awam adalah dengan menghantar email (dari %s atau alamat %s) ke %s dengan menulis 'get bridges' pada badan email tersebut."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "Bridges saya tidak berfungsi! Saya perlukan bantuan!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "Jika Tor kamu tidak berfungsi, kamu patut menghantar e-mel kepada %s. Cuba masukkan sebanyak mana info yang boleh terhadap permasalahan kamu, termasuk senarai bridges yang kamu guna, dan nama fail bundle dan versi bundle yang kamu guna, dan mesej yang Tor tunjukkan, dan lain-lain."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "Untuk menggunakan bridges di bawah, pergi ke halaman tetapan Rangkaian Vidalia, dan klik \"ISP saya menyekat sambungan saya ke rangkaian Tor\". Kemudian tambah setiap alamat bridges satu per satu."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "Tiada bridges pada masa sekarang."
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "Menaik taraf firefox anda"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "Tulis dua perkataan itu."
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "Langkah 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "Dapatkan %s Tor Browser Bundle %s."
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "Langkah 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Dapatkan %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "Langkah 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "Kemudian %s tambah bridges tersebut ke Tor %s"
diff --git a/lib/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 1d5603e..0000000
--- a/lib/bridgedb/i18n/nb/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,384 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Allan Nordhøy <comradekingu at gmail.com>, 2014
-# Harald <haarektrans at gmail.com>, 2014
-# lateralus, 2013
-# Per Thorsheim <transifex at thorsheim.net>, 2015
-# thor574 <thor.hovden at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-24 13:06+0000\n"
-"Last-Translator: Per Thorsheim <transifex at thorsheim.net>\n"
-"Language-Team: Norwegian Bokmål (http://www.transifex.com/projects/p/torproject/language/nb/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: nb\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Dette var leit! Noe gikk galt med forespørselen din."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Dette er en automatisert melding; vennligst ikke svar.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Her er dine broer:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Du har gått over hastighetsbegrensningen. Vennligst ta det med ro! Minste tid mellom e-poster er %s timer. Alle videre eposter i denne tidsperioden vil bli ignorert."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "KOMMANDOer: (kombiner KOMMANDer to å angi flere valg samtidig)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Velkommen til BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Nåværende støttede transport TYPEr:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hei, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hallo, lille venn!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Offentlige nøkler"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Denne e-posten ble laget med regnbuer, enhjørninger og stjerneskudd for %s på %s klokken %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "I BridgeDB finnes broer med flere %styper av pluggbare transporter%s,\nsom kan hjelpe deg med å tilsløre dine tilkoblinger til Tor-nettverket, noe som gjør det\nvanskelig for noen som overvåker din internett-trafikk å fastsette hvorvidt du\nbruker Tor eller ei\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Noen broer med IPv6-adresser er også tilgjelgelige, dog er noen pluggbare\nTransporter ikke IPv6-kompatible.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Merk også, BridgeDB har massevis av standardbroer med fabrikkoppsett %s uten\nnoen pluggbare transporter %s hvilket kanskje ikke høres så tøft ut, men de kan fremdeles\nbehjelpe omgåelse av internettsensur i de fleste fall.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Hva er broer?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Broer %s er Tor-tilknyttninger som hjelper deg med å omgå sensur."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Jeg trenger en alternativ måte å få broer på!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "En annen måte tilknytte seg broer er å sende en e-post til %s. Merk at du må sende\ne-post fra en adresse tilhørende en av følgende e-posttilbydere:\n%s, %s eller %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Broene mine virker ikke! Jeg trenger hjelp!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Hvis din Tor ikke virker, burde du skrive epost til %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Prøv å få med så mye info om dit tilfelle du kan, inkludert en liste over\nbroene og pluggbare transporter du prøvde å bruke, din Tor-nettleser-versjon,\nog alle meldinger Tor måtte produsere, osv."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Her er dine bro-linjer:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "FÃ¥ broer!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Gjør valg for brotype:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Trenger du IPv6-adresser?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Trenger du en %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Nettleseren din viser ikke bilder ordentlig."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Skriv inn bokstavene fra bildet ovenfor..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Hvordan starte med bruk av dine broer"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "For å oppføre broer i Tor-nettleseren, følg instruksjonene på %s Tor\nnettleser nedlastingsside %s for å starte Tor-nettleser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Når 'Tor nettverks-innstillinger' dialogboksen spretter opp, trykk på 'oppsett' og følg\nveiviseren til den forespør:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Sensurerer, eller blokkerer på annen måte, din internetttilbyder (ISP) tilkoblinger\ntil Tor-nettverket?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Velg 'Ja' og klikk så 'Neste'. For å sette opp nye broer, kopier og\nlim inn brolinjene i tekstboksen. Til slutt, trykk 'Koble til', og\ndu burde være klar til kamp! Hvis du får problemer, trykk 'Hjelp'\n-knappen i \"Tor-nettverksinnstillinger'-veiviseren for ytterligere hjelp."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Vis denne meldingen."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Forespørr broer med fabrikkoppsett."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Etterspør IPv6-broer."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Etterspørr pluggbar transport etter TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Få kopi av BridgeDBs offentlige GnuPG-nøkkel."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Rapporter en feil"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Kildekode"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Endringslogg"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Velg alle"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Vis QR kode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR kode for dine brolinjer"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "PÃ¥ tryne i myra!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Det oppsto en feil ved innhenting av din QR kode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Denne QR koden inneholder dine brolinjer. Skann den med en QR leser for å kopiere dine brolinjer over på mobile og andre enheter."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Det er for tiden ingen tilgjengelige broer..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Kanskje du bør prøve %s gå tilbake til %s og velge en annen brotype!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Steg %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Last ned %s Tor-nettleser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Steg %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Hent %s broer %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Steg %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "NÃ¥ %s legg til broer til Tor-nettleser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sB%sare gi meg noen broer!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Avanserte valg"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nei"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ingen"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sT%silknytt broer"
diff --git a/lib/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index ac46c7e..0000000
--- a/lib/bridgedb/i18n/nl/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,390 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Adriaan Callaerts <adriaan.callaerts at gmail.com>, 2013
-# Ann Boen <ann.boen at gmail.com>, 2014
-# Cleveridge <erwin.de.laat at cleveridge.org>, 2014
-# Dick, 2014
-# Johann Behrens <info at wmrkameleon.nl>, 2013
-# Shondoit Walker <shondoit at gmail.com>, 2011
-# Marco Brohet <inactive+therbom at transifex.com>, 2012
-# Tom Becht <tombecht at live.nl>, 2014
-# Tonko Mulder <tonko at tonkomulder.nl>, 2015
-# math1985 <transifex at matthijsmelissen.nl>, 2013
-# BBLN <webmaster at bbln.nl>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 09:50+0000\n"
-"Last-Translator: Tonko Mulder <tonko at tonkomulder.nl>\n"
-"Language-Team: Dutch (http://www.transifex.com/projects/p/torproject/language/nl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: nl\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Sorry! Er is iets mis gegaan met je verzoek."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Dit is een automatisch bericht, gelieve niet te beantwoorden.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Hier zijn je bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Je hebt de rate limiet overschreden. Graag rustiger aan! De minimale tijd tussen\ne-mailberichten is %s uur. Alle verdere e-mails gedurende deze periode worden genegeerd."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combineer commando's om meerdere opties tegelijkertijd te specificeren)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Welkom bij BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Huidig ondersteunde transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hoi, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hallo, vriend!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Publieke Sleutels"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Deze email is gegenereerd met regenbogen, eenhoorns, en fonkelingen voor %s op %s om %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB kan voorzien in bridges met meerdere Pluggable Transports%s %stypes,\ndie helpen bij het verduisteren van uw connecties naar het Tor netwerk,\nwaardoor het moeilijker wordt voor anderen om uw internet traffic te bekijken en vast te stellen dat u Tor gebruikt.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Sommige bridges met IPv6 adressen zijn eveneens beschikbaar, maar sommige Pluggable\nTransports bieden geen ondersteuning voor IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Verder heeft BridgeDB genoeg oude vanilla bridges %s zonder enige\nPluggable Transports %s wat mogelijk niet zo cool klinkt, maar deze kunnen nog steeds helpen bij het omzeilen van internetcensuur in de meeste gevallen.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Wat zijn bridges?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s zijn Tor relays die je helpen censuur te omzeilen."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ik heb een alternatieve manier nodig om bridges te verkrijgen!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Een alternatieve manier om bridges te verkrijgen is door een email te sturen naar %s. Houd er wel rekening mee dat je\nde email verstuurd via een van de volgende email aanbieders:\n%s, %s of %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mijn bridges werken niet! Ik heb hulp nodig!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Als uw Tor niet werkt, email dan naar %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Probeer zoveel mogelijk informatie toe te voegen over je situatie als je kan, waaronder de lijst met \nbridges en Pluggable Transports die je geprobeerd hebt te gebruiken, je Tor Browser versie,\nen alle meldingen welke Tor heeft uitgegeven, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Hier zijn je bridge regels:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Krijg Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Selecteer opties voor bridge type:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Heeft u IPv6 adressen nodig?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Heeft u een %s nodig?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Uw browser vertoont afbeeldingen niet naar behoren."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Voer de karakters in van de afbeelding hier beneden..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Hoe te starten met het gebruik van je bridges"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Om bridges toe te voegen in Tor Browser, volg de instructies op de %s Tor\nBrowser download pagina %s om de Tor Browser te starten."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Wanneer de 'Tor Network Settings' dialoog opent, klik 'Configure' en volg\nde wizard totdat deze vraagt om:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokkeert of censureert je internetprovider (ISP) verbindingen\nnaar het Tor netwerk?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Selecteer 'Ja' en klik vervolgens 'Next'. Om je nieuwe bridges te configureren, kopieer en\nplak je de bridge regels in het invoerveld. Vervolgens klik je 'Connect', en\nben je klaar om te gaan! Als je problemen ervaart, klik dan de 'Help'\nknop in de 'Tor Network Settings' wizard voor verdere hulp."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Toont dit bericht."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Vraag vanilla bridges aan."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Verzoek IPv6 bridges"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Vraag een Pluggable Transport op TYPE aan."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Verkrijg een kopie van BridgeDB's publieke GnuPG key."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Rapporteer een bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Broncode"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Wijzigingslogboek"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contact"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Alles selecteren"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Laat de QRCode zien"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode voor je bridge regels"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Helaas pindakaas!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Er was een fout tijdens het ophalen van je QRCode"
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Deze QRCode bevat je bridge regels. Scan het met een QRCode lezer om je bridge regels te kopiëren naar een mobiel of andere apparaten."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Er zijn momenteel geen bridges beschikbaar..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Misschien moet je proberen %s terug te gaan %s en een ander bridge type te selecteren!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Stap %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Download %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Stap %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Download %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Stap %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Nu %s voeg de bridges toe aan Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "Geef me %sg%sewoon bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Geavanceerde Opties"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nee"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "geen"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sK%srijg Bridges"
diff --git a/lib/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 8f938cf..0000000
--- a/lib/bridgedb/i18n/pl/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,388 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Aron <aron.plotnikowski at cryptolab.net>, 2014
-# Aron <aron.plotnikowski at cryptolab.net>, 2013
-# JerBen <ayurveda63 at gmail.com>, 2012
-# bogdrozd <bog.d at gazeta.pl>, 2013
-# Dawid <hoek at hoek.pl>, 2014
-# Rikson <Ers at mail2tor.com>, 2014
-# Krzysztof Åojowski <maxxxrally at gmail.com>, 2014
-# oirpos <kuba2707 at gmail.com>, 2015
-# seb, 2014-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 10:50+0000\n"
-"Last-Translator: seb\n"
-"Language-Team: Polish (http://www.transifex.com/projects/p/torproject/language/pl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: pl\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Przepraszamy! Ale coÅ poszÅo nie tak z Twoim zapytaniem."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[To jest wiadomoÅÄ generowana automatycznie; prosimy na niÄ
nie odpisywaÄ.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Oto Twoje mostki:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Przekroczono limit szybkoÅci. ProszÄ zwolnij! Minimalny czas pomiÄdzy \nwiadomoÅci e-mail to %s godzin. Wszystkie dodatkowe e-maile w tym okresie bÄdÄ
ignorowane."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "POLECENIA: (ÅÄ
cz polecenia, aby sprecyzowaÄ kilka opcji jednoczeÅnie)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Witamy w BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Obecnie obsÅugiwane transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Witaj, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Witaj przyjacielu!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Klucze Publiczne"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Ten email zastaÅ wygenerowany przez tÄcze, jednorożce i gwiazdki \ndla %s w %s o %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB może dostarczaÄ poÅÄ
czenia mostkowe z kilkoma %stypes wÅÄ
czanymi protokoÅami%s,\nktóre mogÄ
pomóc ukryÄ Twoje poÅÄ
czenie do Sieci Tor, tworzÄ
c trudniejsze\ndo podsÅuchania dla osób obserwujÄ
cych ruch sieci w celu ustalenia gdzie siÄ znajdujesz\nużywajÄ
c Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Niektóre poÅÄ
czenia mostkowe z adresami IPv6 sÄ
również dostÄpne, pomimo,\nże niektóre wtyczki protokoÅów nie sÄ
kompatybilne z IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Dodatkowo, BridgeDB posiada sporo regularnych mostów %s bez \njakichkolwiek pluggable transports %s, które mogÄ
wydawaÄ siÄ niezbyt przydatne, \njednak w wielu przypadkach mogÄ
pomóc w obejÅciu cenzury.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Czym sÄ
mostki?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Mosty %s sÄ
wÄzÅami w sieci Tor pomagajÄ
cymi w ominiÄciu cenzury."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "PotrzebujÄ alternatywnego sposobu na pozyskanie mostków!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Innym sposobem na pozyskanie mostu jest wysÅanie wiadomoÅci e-mail na adres %s. ProszÄ pamiÄtaÄ, że należy \nwysÅaÄ wiadomoÅÄ używajÄ
c adresu jednego z nastÄpujÄ
cych dostawców poczty elektronicznej:\n%s, %s lub %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Moje mostki nie dziaÅajÄ
! PotrzebujÄ pomocy!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "JeÅli Twój Tor nie dziaÅa, powinieneÅ wysÅaÄ wiadomoÅÄ e-mail na adres %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Spróbuj zawrzeÄ jak najwiecej informacji o swoim problemie, uwzglÄdniajÄ
c listÄ mostów i Pluggable Transports, których próbowaÅeÅ użyÄ, wersjÄ Tor Browser, wszelkie komunikaty, które zwróciÅ Tor i inne."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Oto Twoje poÅÄ
czenia z wykorzystaniem mostów:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ZdobÄ
dź Mosty!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ProszÄ wybraÄ opcje dla typu mostu:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Czy potrzebujesz adresów IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Czy potrzebujesz %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Twoja przeglÄ
darka nie wyÅwietla obrazów prawidÅowo."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Wprowadź tekst z obrazka powyżej..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Jak zaczÄ
Ä używaÄ mostów"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Aby dodaÄ mosty do Tor Browser, postÄpuj zgodnie z instrukcjami dostÄpnymi na %s stronie \npobierania Tor Browser %s w celu uruchomienia Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Po pojawieniu siÄ okna 'Ustawienia Sieci Tor' naciÅnij przycisk \"Konfiguruj\", a nastÄpnie postÄpuj zgodnie ze\nwskazówkami kreatora do momentu pytania o:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Czy Twój dostawca usÅug internetowych (ISP) blokuje lub cenzuruje poÅÄ
czenia do sieci Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Wybierz 'Tak' a nastÄpnie kliknij 'Dalej'. Aby skonfigurowaÄ swoje nowe \nmosty, skopiuj i wklej każdy most w nowym wierszu w polu tekstowym. \nNa koniec kliknij 'PoÅÄ
cz' i to wszystko! JeÅli napotkasz jakiekolwiek problemy, naciÅnij przycisk 'Pomoc' w kreatorze 'Ustawienia Sieci Tor' w celu uzyskania dalszych porad."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "WyÅwietla tÄ wiadomoÅÄ."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "PoproÅ o regularne mosty."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "PoproÅ o mosty IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "PoproÅ o Pluggable Transport przez TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Uzyskaj kopiÄ klucza publicznego GnuPG BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "ZgÅoÅ BÅÄ
d"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Kod źródÅowy"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Lista zmian"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Zaznacz wszystko"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Pokaż KodQR"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "KodQR dla Twoich linii bridge"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ups, coÅ poszÅo nie tak!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "WyglÄ
da na to, że napotkaliÅmy na bÅÄ
d podczas próby pobrania Twojego kodu QR."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ten KodQR zawiera Twoje linie bridge. Przeskanuj je czytnikiem kodów QR, aby skopiowaÄ je do Twojego telefonu lub innych urzÄ
dzeÅ."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Aktualnie nie ma dostÄpnych żadnych mostów..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Prawdpododobnie powinieneÅ spróbowaÄ %s wróciÄ %s i wybraÄ inny typ mostu!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Krok %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Pobierz %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Krok %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Pobierz %s mostki %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Krok %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Teraz %s dodaj mosty do Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sP%so prostu daj mi mosty!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opcje zaawansowane"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nie"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "brak"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sT%sak!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sP%sozyskaj mosty"
diff --git a/lib/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 48229a7..0000000
--- a/lib/bridgedb/i18n/pt/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,387 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# alfalb.as, 2015
-# André Monteiro <andre.monteir at gmail.com>, 2014
-# kagazz <lourenxo_619 at hotmail.com>, 2014
-# alfalb_mansil, 2014
-# Andrew_Melim <nokostya.translation at gmail.com>, 2014
-# Pedro Albuquerque <palbuquerque73 at gmail.com>, 2014-2015
-# Sérgio Marques <smarquespt at gmail.com>, 2014
-# TiagoJMMC <tiagojmmc at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-23 16:31+0000\n"
-"Last-Translator: alfalb.as\n"
-"Language-Team: Portuguese (http://www.transifex.com/projects/p/torproject/language/pt/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: pt\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Desculpe! Ocorreu algo de errado com o seu pedido."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Esta é uma mensagem automática; por favor, não responda.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Aqui estão as suas pontes:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Excedeu a taxa limite. Por favor abrande! O tempo mÃnimo entre mensagens\neletrónicas é de %s horas. Todas as mensagens seguintes durante este perÃodo serão ignoradas."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMANDOS: (combine COMANDOS para especificar múltiplas opções em simultâneo)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Bem-vindo ao BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Transport TYPEs suportados:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Olá, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Olá, amigo!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Chaves Públicas"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Esta mensagem foi gerada com arco-Ãris, unicórnios e estrelas\npara %s em %s à s %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "A BridgeDB pode fornecer pontes com vários %stypes de Pluggable Transports%s,\nque poderão ajudar a ocultar as suas ligações à Tor Network, tornando mais difÃcil\na filtragem do seu tráfego de Internet para determinar se está a usar o Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Também estão disponÃveis algumas pontes com endereços IPv6. \nNo entanto, alguns Pluggable Transports não são compatÃveis com ligações IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Adicionalmente, a BridgeDB tem imensas pontes %s simples, normais e regulares\nmas sem qualquer Pluggable Transports %s, o que pode não parecer aliciante, mas\nque, ainda assim, ajudam a contornar a censura na Internet.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "O que são pontes?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Pontes %s são relays Tor para ajudar a contornar a censura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Preciso de uma alternativa para obter pontes!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Outra forma de obter pontes é enviar uma mensagem para %s. Por favor, note que\na mensagem tem de ser enviada de um dos seguintes fornecedores de serviço:\n%s, %s ou %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "As minhas pontes não funcionam! Eu preciso de ajuda!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Se o seu Tor não funciona, deve enviar uma mensagem para %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Tente incluir o máximo de informação sobre os problemas. Deve incluir a lista\nde pontes e Pluggable Transports que tentou utilizar, a sua versão Tor e \nquaisquer mensagens que o Tor retornou..."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Aqui estão as suas linhas de ponte:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Obter pontes!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Por favor selecione as opções para o tipo de ponte:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Necessita de endereços IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Necessita de %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "O seu navegador não está a mostrar as imagens apropriadamente."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Introduza os caracteres da imagem acima..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Como utilizar as suas pontes"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Para inserir pontes no Tor Browser, siga as instruções na página de transferências\n%s do Tor Browser %s para iniciar o Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Assim que o diálogo de rede Tor aparecer, clique em Configurar e siga\nas intruções do assistente até que lhe pergunte:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "O seu fornecedor de serviço Internet (ISP) bloqueia ou censura as ligações\n à rede Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Selecione Sim e clique Seguinte. Para configurar as novas pontes, copie e\ncole as linhas de ponte na caixa de texto. Finalmente, clique em \"Ligar\" e\ndeve estar pronto! Se ocorrerem erros, clique no botão de Ajuda no\nassistente de Definições da rede Tor."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Mostra esta mensagem."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Solicita as pontes básicas."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Solicita pontes IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Solicita um Pluggable Transport por TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Obter uma cópia da chave pública GnuPG da BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Reportar um erro"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Código fonte"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Registo de alterações"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contactos"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Escolher todos"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Mostrar QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode para as suas linhas de ponte"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ocorreu um erro!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Parece que ocorreu um erro ao obter o seu QRCode."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Este QRCode contém as suas linhas de ponte. Leia-o com um leitor de QRCode para copiar as linhas de ponte para dispositivos móveis."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Atualmente não existem pontes livres..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Experimente utilizar %s voltando a %s e escolhendo um tipo de ponte diferente!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Passo %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Transferir %s Navegador Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Passo %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obtenha %s pontes %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Passo %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Agora %s adiciona as pontes ao navegador Tor %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sA%spenas quero as pontes!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opções avançadas"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Não"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "nenhuma"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sS%sim!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sbter pontes"
diff --git a/lib/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index db13695..0000000
--- a/lib/bridgedb/i18n/pt_BR/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,386 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Communia <ameaneantie at riseup.net>, 2013-2014
-# Augustine <evandro at geocities.com>, 2013
-# Humberto Sartini <humberto at hss.blog.br>, 2014
-# Isabel Ferreira, 2014
-# João Paulo S.S <contato1908 at gmail.com>, 2015
-# m4lqu1570 <>, 2012
-# Rodrigo Emmanuel Santana Borges <rodrigoesborges at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-17 01:10+0000\n"
-"Last-Translator: João Paulo S.S <contato1908 at gmail.com>\n"
-"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/torproject/language/pt_BR/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: pt_BR\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Desculpe! Algo errado ocorreu com a sua solicitação."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Esta é uma mensagem automática; por favor, não responda.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Aqui estão suas pontes:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Você excedeu o limite. Mais devagar, por favor! O tempo mÃnimo entre\ne-mails é de %s horas. Todos os outros e-mails serão ignorados durante este perÃodo."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMANDOS: (combinar COMANDOS para especificar múltiplas opções simultaneamente)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Bem vindo ao BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "TYPEs de transport que possuem suporte atualmente:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Olá, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Olá, amigo!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Chaves Públicas"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Este e-mail foi gerado com arco-Ãris, unicórnios e purpurina, por %s, %s, à s %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB pode fornecer pontes com vários %stipos de ââTransportes Plugáveis%s,\nque podem ajudar a ofuscar suas conexões com a Rede Tor, tornando mais\nddifÃcil para qualquer um ver seu tráfego de internet para determinar que você está\nusando o Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Algumas pontes com endereços IPv6 também estão disponÃveis, mas alguns PLUGGABLE TRNAPORTS não são compatÃveis com o IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Além disso, BridgeDB tem muitas pontes tradicionais %s sem nenhum PLUGGABLE TRANSPORTS%s, que podem ajudar a driblar a censura na internet em muitos casos.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "O que são pontes?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Pontes %s são retransmissores Tor que ajudam a driblar a censura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Preciso de um outro modo de obter pontes!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Outro modo de obter pontes é enviando um e-mail para %s. Por favor, lembre que você deve enviar o e-mail utilizando um endereço registrado em um dos seguintes provedores de e-mail: \n%s, %s ou %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Minhas pontes não funcionam! Preciso de ajuda!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Se o Tor não funcionar, envie um e-mail %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Tente incluir o máximo de informações possÃveis sobre o seu caso, como a lista das pontes e dos PLUGGABLE TRANSPORTS que você tentou usar, a versão do seu navegador Tor e todas as mensagens que o Tor emitiu, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Veja sua pontes ativas:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Obtenha Pontes!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Por favor, selecione as opções de tipos de pontes:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Você precisa de endereços IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Você precisa de %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Seu navegador não está mostrando as imagens corretamente."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Digite os caracteres da imagem acima..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Como começar a usar as suas pontes"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Para inserir pontes no navegador Tor, siga as instruções no %s página\n de downloads do navegador Tor %s para iniciar o navegador Tor."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Quando a janela 'Configurações da Rede Tor' aparecer, clique em 'Configurar' e siga\no assistente até que ele pergunte:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "O seu Provedor de Serviços de Internet (PSI) bloqueia ou censura conexões\nà rede Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Selecione 'Sim' e clique em 'Próximo'. Para configurar suas novas pontes, copie e\ncole as coordenadas das pontes na caixa de texto de saÃda. Por fim, clique em 'Conectar'.\nIsso deve ser o suficiente! Se você encontrar problemas, tente clicar no botão\n'Ajuda' no assistente de 'Configurações da Rede Tor', para mais assistência."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Mostrar essa mensagem."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Solicitar pontes VANILLA."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Solicitar pontes IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Requerer Transporte Plugável por TYPE"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Obter uma cópia da chave pública GnuPG do BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Relatar um Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Código Fonte:"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Registro de alterações"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Contato"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Selecionar tudo"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Mostrar QRCode"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QRCode para as suas linhas de ponte"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ops, um erro!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Parece que houve um erro ao obter seu QRCode"
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Seu QRCode contém as linhas de pontes. Digitalizá-lo com um leitor de QRCode para copiar suas linhas de ponte para dispositivos móveis e outros."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Atualmente não há nenhuma ponte disponÃvel..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Talvez você deva tentar %s voltar %s e escolher um outro tipo de ponte!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Passo %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Fazer download do %s Navegador Tor %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Passo %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Obter %s pontes %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Passo %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Agora, %s inserir as pontes no Navegador Tor %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sS%só me dê bridges!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opções Avançadas"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Não"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "nenhum"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sS%sim!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sO%sbtenha Bridges"
diff --git a/lib/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 90d0859..0000000
--- a/lib/bridgedb/i18n/ro/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,360 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Adda.17 <adrianatrifoi at gmail.com>, 2013
-# Isus Satanescu <isus at openmailbox.org>, 2014
-# laura berindei <lauraagavriloae at yahoo.com>, 2014
-# clopotel <yo_sergiu05 at yahoo.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: clopotel <yo_sergiu05 at yahoo.com>\n"
-"Language-Team: Romanian (http://www.transifex.com/projects/p/torproject/language/ro/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ro\n"
-"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Ne cerem scuze ! Ceva a funcţionat prost !"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[ Acesta este un mesaj automat ; va rugam nu rÄspundeÅ£i ]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Acestea sînt punÈile pentru dvs:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "AÈi depÄÈit rata limitÄ. ÃncetiniÈi vÄ rog! Timpul minim între\nemailuri este %s ore. Toate emailurile în acest interval vor fi ignorate."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (combinÄ COMMANDs pentru a specifica mai multe opÈiuni simultan)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Bine aţi venit la BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "TYPEs transport suportate curent:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Buna , %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Buna , prietene !"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "Chei publice"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Acest email a fost generat cu curcubee, inorogi Èi scîntei pentru %s în %s la %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB poate oferi punÈi cu cîteva %stypes de Pluggable Transports%s,\nce pot obfusca conexiunile dvs cÄtre Tor Network, fÄcînd mai dificilÄ determinarea traficului Tor pentru oricine vÄ urmÄreÈte traficul de internet.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Unele punÈi cu adrese IPv6 sînt disponibile, deÈi unele Pluggable\nTransports nu sînt compatibile cu IPv5.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Ãn plus, BridgeDB are multe punÈi simple %s fÄrÄ nici o\nPluggable Transport %s ce poate nu pare aÈa cool, dar care pot\nde asemenea sÄ ocoleascÄ cenzura internet în multe cazuri.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Ce sunt punÈile? "
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s punÈi %s sînt Tor relays care vÄ ajutÄ sÄ ocoliÈi cenzura."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Am nevoie de o cale alternativÄ de a obÈine punÈile !"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "O altÄ cale pentru a face punÈi este sÄ trimiteÈi un email cÄtre %s. LuaÈi în considerare cÄ\ntrebuie sa trimiteÈi emailul folosind o adresÄ de la unul dintre urmÄtorii providerii de email:\n%s, %s sau %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "PunÈile mele nu funcÈioneazÄ! Am nevoie de ajutor!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Daca Tor nu funcÅ£ioneazÄ trimiteÅ£i un mesaj la %s ."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ÃncercaÈi sÄ includeÈi cît mai multe informaÈii despre cazul dvs pe cît puteÈi, includeÈi lista de punÈi Èi Pluggable Transports pe care aÈi încercat sÄ le folosiÈi, versiunea de Tor Browser Èi orice alt mesaj dat de Tor, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Acestea sînt liniile de punÈi:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Ia punÈi!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "AlegeÈi opÈiunile pentru tipul de punte:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "AveÈi nevoie de adrese IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "AveÈi nevoie de %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Browserul nu afiÈeazÄ imaginile corect."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "IntroduceÈi caracterele din imaginea de mai sus..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Cum sÄ Ã®ncepeÈi sÄ folosiÈi punÈile"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Pentru a introduce în Tor Browser, urmaÈi instrucÈiunile de la %s Tor Browser download page %s pentru a porni Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Cînd apare dialogul 'Tor Network Settings', clic 'Configure' Èi urmaÈi vrÄjitorul pînÄ cînd vÄ cere:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Internet Service Providerul (ISP) dvs blocheazÄ sau cenzureazÄ conexiunile cÄtre reÈeaua Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "AlegeÈi 'Yes' Èi apoi clic 'Next'. Pentru a configura noile punÈi, copiaÈi Èi colaÈi liniile cu punÈile în cÄsuÈa de text. Ãn final, clic 'Connect' Èi totul e gata! DacÄ aveÈi probleme, clic pe 'Help' din vrÄjitorul 'Tor Network Settings'."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "AfiÈeazÄ acest mesaj."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Cere punÈi simple."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Cere punÈi IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "CereÈi un Pluggable Transport dupÄ TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ObÈineÈi o copie a cheii GnuPG publice a BridgeDB."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "RaporaÈi un bug"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Cod sursÄ"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "SchimbÄri"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "Contact"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "O, spagettios!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Acum nu sînt punÈi disponibile..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Poate ar trebui sÄ Ã®ncercaÈi %s înapoi %s Èi sÄ alegeÈi un alt tip de punte!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Pas %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "DescarcÄ %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Pas %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Ia %s punÈi %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Pas %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Acum %s adÄugaÈi punÈile la Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sD%soar dÄ-mi punÈle!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "OpÈiuni avansate"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nu"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "nimic"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sD%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sI%sa punÈi"
diff --git a/lib/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 9ae67f6..0000000
--- a/lib/bridgedb/i18n/ru/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,389 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Andrey Yoker Ogurchikov <domovoy.yoker at gmail.com>, 2014
-# Evgrafov Denis <stereodenis at gmail.com>, 2014
-# Eugene, 2013
-# foo <im-infamous at yandex.ru>, 2014
-# joshua ridney <yachtcrew at mail.ru>, 2015
-# liquixis <liquixis at gmail.com>, 2012
-# Oleg, 2014
-# Sergey Briskin <sergey.briskin at gmail.com>, 2014
-# Valid Olov, 2014
-# Vitaliy Grishenko, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-16 10:42+0000\n"
-"Last-Translator: joshua ridney <yachtcrew at mail.ru>\n"
-"Language-Team: Russian (http://www.transifex.com/projects/p/torproject/language/ru/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ru\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "ÐзвиниÑе! Ðозникла пÑоблема Ñ Ð²Ð°Ñим запÑоÑом."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[СообÑение бÑло ÑÑоÑмиÑовано авÑомаÑиÑеÑки, пожалÑйÑÑа, не оÑвеÑайÑе на него.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "ÐодгоÑовленнÑе моÑÑÑ:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "ÐÑ Ð¿ÑевÑÑили ÑÑÑановленнÑй лимиÑ. ÐожалÑйÑÑа, ÑбавÑÑе обоÑоÑÑ! ÐинималÑнÑй пÑомежÑÑок вÑемени Ð¼ÐµÐ¶Ð´Ñ \nзапÑоÑами ÑоÑÑавлÑÐµÑ %s ÑаÑов. ÐÑе поÑледÑÑÑие пиÑÑма в ÑеÑение ÑÑого вÑемени бÑдÑÑ Ð¿ÑоигноÑиÑованÑ."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "ÐÐÐÐÐÐЫ: (комбиниÑÑйÑе командÑ, ÑÑÐ¾Ð±Ñ Ð¾Ð´Ð½Ð¾Ð²Ñеменно иÑполÑзоваÑÑ Ð½ÐµÑколÑко опÑий)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "ÐобÑо пожаловаÑÑ Ð² BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "ÐоддеÑживаемÑе ÑÐ¸Ð¿Ñ ÑÑанÑпоÑÑа - TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Ð-ге-гей, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ÐÑивеÑ, дÑÑг!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "ÐÑкÑÑÑÑе клÑÑи"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "ÐÑо ÑлекÑÑонное пиÑÑмо бÑло ÑгенеÑиÑовано пÑи помоÑи ÑадÑги, единоÑогов и ÑевеÑного ÑиÑниÑ\nÐ´Ð»Ñ %s в %s, в %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑедоÑÑавиÑÑ Ð²Ð°Ð¼ моÑÑÑ Ñ Ð½ÐµÑколÑкими %s Ñипами Pluggable Transports%s,\nÑÑо позволÑÐµÑ Ð·Ð°Ð¿ÑÑаÑÑ Ð²Ð°Ñе Ñоединение Ñ Tor Network. ÐлагодаÑÑ ÑÑÐ¾Ð¼Ñ Ð¿ÐµÑеÑ
ваÑÑикам ÑÑаÑика\nÑложнее ÑÑÑановиÑÑ ÑÐ°ÐºÑ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑеÑи Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "ÐоÑÑÑÐ¿Ð½Ñ Ð½ÐµÐºÐ¾ÑоÑÑе моÑÑÑ, поддеÑживаÑÑие адÑеÑа IPv6, Ñ
оÑÑ Ð½ÐµÐºÐ¾ÑоÑÑе Pluggable\nTransports не ÑовмеÑÑÐ¸Ð¼Ñ Ñ IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Ðолее Ñого, BridgeDB ÑодеÑÐ¶Ð¸Ñ Ð¼Ð½Ð¾Ð¶ÐµÑÑво обÑÑнÑÑ
моÑÑов %s, не поддеÑживаÑÑиÑ
\nподклÑÑаемÑй ÑÑанÑпоÑÑ %s, ÑÑо Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾ÐºÐ°Ð·Ð°ÑÑÑÑ Ð½Ðµ Ñаким клÑвÑм, но они во многиÑ
ÑлÑÑаÑÑ
\nмогÑÑ Ð¿Ð¾Ð¼Ð¾ÑÑ Ð¾Ð±Ñ
одиÑÑ Ð¸Ð½ÑеÑнеÑ-ÑензÑÑÑ.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "ЧÑо Ñакое ÑеÑÑанÑлÑÑÐ¾Ñ Ñипа моÑÑ?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s ÐоÑÑÑ %s - ÑÑо ÑеÑÑанÑлÑÑоÑÑ Tor, коÑоÑÑе позволÑÑÑ Ð²Ð°Ð¼ обÑ
одиÑÑ ÑензÑÑÑ."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Ðне нÑжен алÑÑеÑнаÑивнÑй ÑпоÑоб полÑÑÐµÐ½Ð¸Ñ ÑпиÑка моÑÑов!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ÐÑÑгим ÑпоÑобом полÑÑÐµÐ½Ð¸Ñ Ð¼Ð¾ÑÑов ÑвлÑеÑÑÑ Ð¾ÑпÑавка ÑлекÑÑонного пиÑÑма на адÑÐµÑ %s. ÐожалÑйÑÑа, обÑаÑиÑе внимание на Ñо, \nÑÑо Ð²Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ Ð¾ÑпÑавиÑÑ ÑлекÑÑоннÑй запÑÐ¾Ñ Ñ Ð¸ÑполÑзованием одного из пеÑеÑиÑленнÑÑ
ÑеÑвиÑов: %s, %s или %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Ðои моÑÑÑ Ð½Ðµ ÑабоÑаÑÑ! ÐомогиÑе!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "ÐÑли Ð²Ð°Ñ Tor не ÑабоÑаеÑ, напиÑиÑе нам %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "ÐоÑÑаÑайÑеÑÑ Ð¿ÑедоÑÑавиÑÑ Ð¸ÑÑеÑпÑваÑÑÑÑ Ð¸Ð½ÑоÑмаÑÐ¸Ñ Ð¾ ваÑей пÑоблеме, вклÑÑÐ°Ñ ÑпиÑок моÑÑов и Pluggable Transports, коÑоÑÑе Ð²Ñ Ð¿ÑÑалиÑÑ Ð¸ÑполÑзоваÑÑ, веÑÑÐ¸Ñ ÐÐ Tor Browser\nи вÑе полÑÑеннÑе Ð¾Ñ Tor ÑведомлениÑ."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "ÐодгоÑовленнÑе адÑеÑа ÑеÑÑанÑлÑÑоÑов:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ÐолÑÑиÑе ÑеÑÑанÑлÑÑоÑÑ!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ÐожалÑйÑÑа, вÑбеÑиÑе опÑии Ð´Ð»Ñ Ñипа ÑеÑÑанÑлÑÑоÑа:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "ÐÑÐ¶Ð½Ñ Ð»Ð¸ вам адÑеÑа IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Ðам нÑжен %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "ÐÐ°Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ показÑÐ²Ð°ÐµÑ Ð¸Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ñм обÑазом."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "ÐведиÑе ÑÐ¸Ð¼Ð²Ð¾Ð»Ñ Ñ Ð¸Ð·Ð¾Ð±ÑажениÑ..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Ðак наÑаÑÑ Ð¿Ð¾Ð»ÑзоваÑÑÑÑ ÑеÑÑанÑлÑÑоÑами"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "ÐÐ»Ñ Ñого, ÑÑÐ¾Ð±Ñ Ð´Ð¾Ð±Ð°Ð²Ð¸ÑÑ ÑеÑÑанÑлÑÑоÑÑ Ð² Tor Browser, ÑледÑйÑе инÑÑÑÑкÑиÑм, пÑедÑÑавленнÑм на %s ÑÑÑаниÑе\nзагÑÑзки Tor Browser %s Ð´Ð»Ñ Ð·Ð°Ð¿ÑÑка Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Ðогда поÑвиÑÑÑ Ð¾ÐºÐ½Ð¾ 'СеÑевÑе наÑÑÑойки Tor', нажмиÑе 'ÐаÑÑÑоиÑÑ' и ÑледÑйÑе инÑÑÑÑкÑиÑм ÑÑÑановÑика, пока он не задаÑÑ Ð²Ð¾Ð¿ÑоÑ:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ÐÐ°Ñ Ð¸Ð½ÑеÑÐ½ÐµÑ Ð¿ÑÐ¾Ð²Ð°Ð¹Ð´ÐµÑ (ISP) блокиÑÑÐµÑ Ð¸Ð»Ð¸ как-либо ÑензÑÑиÑÑÐµÑ Ð¿Ð¾Ð´ÐºÐ»ÑÑÐµÐ½Ð¸Ñ Ðº ÑеÑи Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ÐÑбеÑиÑе 'Ðа' и нажмиÑе 'Ðалее'. ÐÐ»Ñ Ð½Ð°ÑÑÑойки ваÑиÑ
новÑÑ
моÑÑов ÑкопиÑÑйÑе и\nвÑÑавÑÑе иÑ
адÑеÑа в поле Ð´Ð»Ñ Ð²Ð²Ð¾Ð´Ð° ÑекÑÑа. ÐажмиÑе 'ÐодклÑÑиÑÑÑÑ' и \nвÑе должно заÑабоÑаÑÑ! ÐÑли Ñ Ð²Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð° пÑоблема, попÑобÑйÑе нажаÑÑ Ð½Ð° ÐºÐ½Ð¾Ð¿ÐºÑ 'ÐомоÑÑ'\nв маÑÑеÑе 'СеÑевÑе наÑÑÑойки Tor' Ð´Ð»Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ Ð¿Ð¾Ð¼Ð¾Ñи."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "ÐÑобÑÐ°Ð¶Ð°ÐµÑ ÑÑо ÑообÑение."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "ÐапÑоÑиÑÑ Ð°Ð´ÑеÑа обÑÑнÑÑ
моÑÑов."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "ÐапÑоÑиÑÑ Ð¼Ð¾ÑÑÑ Ñ Ð¿Ð¾Ð´Ð´ÐµÑжкой IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "ÐапÑоÑиÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑÑаемÑй ÑÑанÑпоÑÑ Ð¿Ð¾ TYPE"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ÐолÑÑиÑÑ ÐºÐ¾Ð¿Ð¸Ñ Ð¾ÑкÑÑÑого GnuPG клÑÑа BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "СообÑиÑÑ Ð¾Ð± оÑибке"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "ÐÑÑ
однÑй код"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Ðог изменений"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "ÐонÑакÑ"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "ÐÑбÑаÑÑ Ð²ÑÑ"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "ÐоказаÑÑ QR-код"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR-код Ð´Ð»Ñ Ð°Ð´ÑеÑов ÑеÑÑанÑлÑÑоÑов"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "ÐÑ
, ÑÑо-Ñо поÑло не Ñак!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "ÐоÑ
оже, пÑоизоÑла оÑибка пÑи полÑÑении QR-кода."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "ÐÑÐ¾Ñ QR-код ÑодеÑÐ¶Ð¸Ñ ÐаÑи адÑеÑа ÑеÑÑанÑлÑÑоÑов. ÐÑÑканиÑÑйÑе его ÑÑÑÑойÑÑвом, ÑÑиÑÑваÑÑим QR-код, ÑÑÐ¾Ð±Ñ ÑкопиÑоваÑÑ ÐаÑи адÑеÑа ÑеÑÑÑанÑлÑÑоÑов на мобилÑнÑе и дÑÑгие ÑÑÑÑойÑÑва"
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Ðа даннÑй Ð¼Ð¾Ð¼ÐµÐ½Ñ Ð½ÐµÑ Ð´Ð¾ÑÑÑпнÑÑ
моÑÑов..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Ðозможно, вам ÑÑÐ¾Ð¸Ñ Ð¿Ð¾Ð¿ÑобоваÑÑ %s веÑнÑÑÑÑÑ%s и вÑбÑаÑÑ Ð´ÑÑгой Ñип моÑÑа!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Шаг %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "СкаÑаÑÑ ÐÐ %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Шаг %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ÐолÑÑиÑе %s моÑÑÑ %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Шаг %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "СейÑÐ°Ñ %s добавÑÑе моÑÑÑ Ð² Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sÐ%sÑоÑÑо дайÑе мне адÑеÑа моÑÑов!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "ÐополниÑелÑнÑе наÑÑÑойки"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "ÐеÑ"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "неÑ"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÐ%sа! "
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sÐ%sолÑÑиÑÑ Ð¼Ð¾ÑÑÑ"
diff --git a/lib/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index d063510..0000000
--- a/lib/bridgedb/i18n/si_LK/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,100 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# ganeshwaki <ganeshwaki at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-05-24 16:50+0000\n"
-"Last-Translator: ganeshwaki <ganeshwaki at gmail.com>\n"
-"Language-Team: Sinhala (Sri Lanka) (http://www.transifex.com/projects/p/torproject/language/si_LK/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: si_LK\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "à·à·à¶à· යන෠මà·à¶±à·à·à¶¯?"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s à·à·à¶à· පà·âරà¶à·à¶ºà·à¶¢à¶%s යන෠à·à·à¶»à¶«à¶º මà¶à·à¶»à·à¶± Tor පà·âරà¶à·à¶ºà·à¶¢à¶à¶ºà¶±à·à¶º."
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "à·à·à¶à· ලබà·à¶à·à¶±à·à¶¸à¶§ à·à·à¶à¶½à·à¶´ à·à·à¶°à·à¶ºà¶à· මට à¶
à·à·à·âයය!"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "à·à·à¶à· ලà·à¶´à·à¶±à¶ºà¶±à· à·à·à¶ºà·à¶à·à¶±à·à¶¸à· à¶à·à¶à· à¶à¶à·à¶»à¶ºà¶à· නම෠à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà¶à· යà·à¶¸à· à¶à·à¶»à·à¶¸à¶ºà· (from a %s or a %s address) to %s 'get bridges' යනà·à¶± à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà· බද à¶à·à¶§à·à· à·à¶¯à·à¶±à· à¶à¶½ යà·à¶à·à¶º."
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "මà·à¶à· à·à·à¶à· à·à·à¶© නà·à¶à¶»à¶ºà·! මට à¶à¶¯à·à· à¶
à·à·à·âයයà·!"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "à¶à¶¶à¶à· Tor à·à·à¶© නà·à¶à¶»à¶ºà· නම෠à¶à¶¶ à·à·à¶¯à·âයà·à¶à· ලà·à¶´à·à¶ºà¶à· à¶à·à·à¶º යà·à¶à·à¶º %s. à¶à¶¶à¶à· පà·âරà·à·à¶±à¶º පà·à·
à·à¶¶à¶¯ à·à·à¶à·à¶à·à¶à· à¶à·à¶»à¶à·à¶»à· à·à·à¶´à¶ºà·à¶¸à¶§ à¶à¶à·à·à·à· à¶à¶±à·à¶±, à¶à·à· à¶à¶¶ භà·à·à·à¶à· à¶à¶½ පà·âරà¶à·à¶ºà·à¶¢à¶ ලà·à¶ºà·à·à·à¶à·à·, à¶à¶¶à¶à· à¶à¶§à·à¶§à¶½à¶ºà· à¶à·à¶±à· නම/à¶à¶¶ භà·à·à·à¶à· à¶à¶½ පà·à¶§à¶´à¶, Tor à·à·à·à·à¶±à· à¶à¶¶ à·à·à¶ ලබà·à¶¯à·à¶± පණà·à·à·à¶©à¶ºà¶±à· යනà·à¶¯à·à¶º à¶
නà·à¶à¶»à·à¶à¶ à¶à¶»à¶±à·à¶±."
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr "à¶à·à¶ රà·à¶à·à·à¶±à· භà·à·à·à¶à· à¶à¶»à¶ºà· නම෠, à·à·à¶¯à·à¶½à·à¶ºà· ජà·à¶½ à·à·à¶§à·à·à¶¸à·à·à¶½à¶§ à¶à·à·à·, \"මà¶à· ISP à·à·à·à·à¶±à· Tor ජà·à¶½à¶ºà¶§ à·à¶¸à·à¶¶à¶±à·à¶° à·à·à¶¸ à¶
à·à·à·à¶»à¶à¶» à¶à¶.\" යනà·à¶± à¶à·à¶½à·à¶à· à¶à¶»à¶±à·à¶±. à¶à¶½à¶à¶§ à·à·à¶à· ලà·à¶´à·à¶±à¶ºà¶±à· à·à¶»à¶à¶§ à¶à¶ බà·à¶à·à¶±à· à¶à¶à¶à· à¶à¶»à¶±à·à¶±.."
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "දà·à¶±à¶§ à¶à·à·à·à¶¯à· à·à·à¶à·à·à¶à· පà·âරයà·à¶¢à¶±à¶º à·à¶¯à·à· නà·à¶¸à·à¶ "
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "à¶à¶¶à¶à· බà·âරà·à·à·à¶»à¶º Firefox à·à¶½à¶§ à¶à·à·à· à¶à¶»à¶±à·à¶± "
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "à·à¶ න දà·à¶à¶à· ටයà·à¶´à· à¶à¶»à¶±à·à¶± "
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "පà·à¶ºà·à¶» 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "ලබà·à¶à¶±à·à¶± %s Tor බà·âරà·à·à·à¶» à¶à¶§à·à¶§à¶½à¶º %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "පà·à¶ºà·à¶» 2 "
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ලබà·à¶à¶±à·à¶±%s à·à·à¶à·à¶±à· %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "පà·à¶ºà·à¶» 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "දà·à¶±à· %sTor à·à·à¶ à·à·à¶à·à¶±à· à¶à¶à¶à· à¶à¶»à¶±à·à¶±%s"
diff --git a/lib/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 60c60ad..0000000
--- a/lib/bridgedb/i18n/sk/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,361 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# elo, 2014
-# FooBar <thewired at riseup.net>, 2015
-# Michal Slovák <michalslovak2 at hotmail.com>, 2013
-# Roman 'Kaktuxista' Benji <romanbeno273 at gmail.com>, 2014
-# StefanH <stefan.holent at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2015-01-30 01:41+0000\n"
-"Last-Translator: FooBar <thewired at riseup.net>\n"
-"Language-Team: Slovak (http://www.transifex.com/projects/p/torproject/language/sk/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: sk\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "PrepáÄte! NieÄo je zle s vaÅ¡ou požiadavkou."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Toto je automatická správa; prosÃm, neodpovedajte.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Tu sú vaše premostenia:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "PrekroÄili ste rate limit. ProsÃm spomaľte. Minimálny Äas medzi emailmi je %s hodÃn. VÅ¡etky dalÅ¡ie emaily poÄas tejto doby budú ignorované."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "PRÃKAZY: (kombinujte prÃkazy na zadanie viac možnostà naraz)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Vitajte v BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Práve podporované transport TYPEy:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hej, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Ahoj, priateľ!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "Verejné kľúÄe"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Tento email bol generovaný s dúhami, jednorožcami a trblietkami pre %s dÅa %s o %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB vám dokáže ponúknuÅ¥ niekolko typov %spremostenà s Pluggable Transports%s,\nktoré vám pomôžu maskovaÅ¥ vaÅ¡e pripojenie na Tor Network, ktoré ztažà každému kto sleduje vaÅ¡e internetové pripojenie zistiÅ¥ to že použÃvate Tor.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Niektoré premostenia s IPv6 adresami sú taktiež dostupné, ale niektoré Pluggable Transports kompatibilné s IPv6 niesú.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "DodatoÄne, BridgeDB má dosÅ¥ pôvodnych premostenà %s bez žiadnych Pluggable Transports %s ktoré možno niesu až také cool, ale vo veľa prÃpadoch stále dokážu obÃsÅ¥ cenzúrovanie internetu.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Äo sú to premostenia?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s premostenia %s sú Tor relaye ktoré vám pomáhaju obÃsÅ¥ cenzúru."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Potrebujem iný spôsob ako zÃskaÅ¥ premostenia!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "DalÅ¡Ãm spôsobom ako zÃskaÅ¥ premostenia je poslaÅ¥ mail na %s. \nEmail vÅ¡ak musÃte poslaÅ¥ pomocou adresy od jedného z týchto poskytovateľov:\n%s, %s alebo %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Moje premostenia nefungujú! Potrebujem pomoc!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ak váš Tor nefunguje, skúste napÃsaÅ¥ email %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Skúste napÃsaÅ¥ Äo najviac informacià o vaÅ¡om prÃpade, vrátane zoznamu premostenà a Pluggable Transportov ktoré ste skúšali použiÅ¥, verzia Tor Browseru, a hlášky ktoré Tor vypÃsal, apod."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Tu sú vaše premostenia:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ZÃskaÅ¥ premostenia!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ProsÃm vyberte si možnosti na typ premostenia:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Potrebujete IPv6 adresy?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Potrebujete %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Váš prehliadaÄ nezobrazuje obrázky správne."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Vložte znaky z obrázku vyššie..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Ako zaÄaÅ¥ použÃvaÅ¥ vaÅ¡e premostenia"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Na zadanie premostenà do Tor Browsera, riaÄte sa inÅ¡trukciami na %s stánke stiahnutia Tor Browseru %s na spustenie Tor Browseru."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "KeÄ vyskoÄia 'Tor SieÅ¥ové Nastavenia', kliknite na 'KonfigurovaÅ¥' a pokraÄujte Äalej kým sa váš insÅ¥alátor nespýta:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokuje alebo nejak cenzuruje váš Poskytovateľ Internetu (ISP) pripojenia k Sieti Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "OznaÄte 'Ãno' a potom kliknite na 'Äalej'. Na konfiguráciu nových premostenÃ, skopÃrujte premostenia do polÃÄka na text. Potom kliknite na 'PripojiÅ¥' a už by to malo fungovaÅ¥. Ak máte nejaké problemy, kliknite na tlaÄidlo 'Pomoc' v 'Tor sieÅ¥ových nastaveniach' pre viac informaciÃ. "
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Zobrazà túto správu."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Požiadať o pôvodné premostenia."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Požiadať o IPv6 premostenia."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Požiadať o Pluggable Transport podla typu."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Stiahnite si kópiu verejného klúÄa GNUPG pre BridgeDB."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "Nahlásiť chybu"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Zdrojový kód"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Posledné zmeny"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "Kontakt"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Do pekla!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Práve niesú dostupné žiadne premostenia..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Možno by ste mohli skúsiÅ¥ %s ÃsÅ¥ späť %s a vybraÅ¥ si iný typ premostenia."
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Krok %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Stiahnuť %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Krok %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ZÃskajte %s premostenia %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Krok %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Teraz %s pridajte premostenia do Tor Browsera %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sL%sen mi zobrazte premostenia!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "PokroÄilé možnosti"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nie"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "žiadne"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÃ%sno!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sN%sastaviť Bridges"
diff --git a/lib/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index a667857..0000000
--- a/lib/bridgedb/i18n/sk_SK/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,357 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# once, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: once\n"
-"Language-Team: Slovak (Slovakia) (http://www.transifex.com/projects/p/torproject/language/sk_SK/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: sk_SK\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "PrepáÄte! Pri spracovanà vaÅ¡ej požiadavky sa vyskytla chyba."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Toto je automatická správa; prosÃm, neodpovedajte.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Tu sú vaše premostenia:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "PrekroÄili ste limit. Spomaľte, prosÃm! Minimálny odstup medzi e-mailami je\n%s hodÃn. Každý ÄalÅ¡Ã e-mail nereÅ¡pektujúci tento Äasový odstup bude ignorovaný."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "PRÃKAZY: (pre urÄenie viacerých možnostà súÄasne PRÃKAZY kombinujte)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Vitajte v BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Aktuálne podporované TYPE transportov:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Ahoj, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Ahoj!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "Verejné kľúÄe"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Tento e-mail bol vytvorený pre %s %s o %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB poskytuje premostenia s rôznymi %stypmi Pluggable Transportov%s,\nktoré vám pomôžu zakryÅ¥ vaÅ¡e pripojenie do Tor Network. Pre každého,\nkto sleduje vaÅ¡e internetové pripojenie, bude potom rozpoznanie toho, že použÃvate Tor, zložitejÅ¡ie.\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Je dostupných aj niekoľko premostenàs adresami IPv6, niektoré Pluggable\nTransporty však nie sú IPv6 kompatibilné.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Naviac, BridgeDB obsahuje dostatok starých dobrých, na kosÅ¥ osekaných premostenÃ\n%s bez Pluggable Transportov %s, použitie ktorých možno neznie tak skvele a cool,\nale stále vám v mnohých prÃpadoch pomôžu obÃsÅ¥ cenzúru internetu.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Äo sú premostenia?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Premostenia %s sú Tor relé, ktoré vám pomáhajú obÃsÅ¥ cenzúru."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Potrebujem alternatÃvny spôsob zÃskania premostenÃ!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "ÄalÅ¡Ãm zo spôsobov, ako zÃskaÅ¥ premostania, je poslaÅ¥ e-mail na %s. Berte, prosÃm,\nna vedomie, že e-mail musÃte poslaÅ¥ z adresy od jedného z nasledujúcich\nposkytovateľov e-mailu:\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Moje premostenia nefungujú! Potrebujem pomoc!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Ak vám nefunguje Tor, mali by ste napÃsaÅ¥ e-mail na %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Pokúste sa priložiÅ¥ Äo najviac informácià o vaÅ¡om probléme, vrátane zoznamu premostenà a Pluggable Transportov, ktoré ste skúšali použiÅ¥, verziu vášho Tor Browser, vÅ¡etky ÄalÅ¡ie správy, ktoré Tor vypÃsal atÄ."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Tu sú vaše riadky premostenia:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ZÃskaÅ¥ Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Zvoľte, prosÃm, možnosti pre typ premostenia:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Potrebujete adresy IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Potrebujete %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Váš prehliadaÄ nezobrazuje obrázky správne."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Zadajte znaky z obrázka vyššie..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Ako zaÄaÅ¥ použÃvaÅ¥ premostenia"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Pre zadanie premostenà do Tor Browser sa riaÄte inÅ¡trukciami na spustenie Tor Browser na %s stránke preberania Tor browser %s."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "KeÄ sa objavà dialógové okno 'Nastavenia Tor Network', kliknite na 'KonfigurovaÅ¥'\na riaÄte sa sprievodcom, kým sa nespýta:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blokuje váš poskytovateľ internetového pripojenia (ISP) alebo inak cenzuruje pripojenia do siete Tor Network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Zvoľte 'Ãno' a potom kliknite na 'Äalej'. Pre nastavenie vaÅ¡ich nových\npremostenÃ, skopÃrujte a vložte riadky premostenà do vstupného textového\npoľa. Nakoniec kliknite na 'PripojiÅ¥' a môžete pracovaÅ¥. Ak sa vyskytnú\nproblémy, pre ÄalÅ¡iu pomoc skúste kliknúť na tlaÄidlo 'PomocnÃk' v sprievodcovi\n'Nastavenia Tor Network'."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Zobrazà túto správu."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Vyžiadať si osekané premostenia."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Vyžiadať si IPv6 premostenia."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Vyžiadať Pluggable Transport podľa TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ZÃskaÅ¥ kópiu verejného GnuPG kľúÄa BridgeDB."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "Nahlásiť chybu"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Zdrojový kód"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Záznam zmien"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "Kontakt"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "Ejha!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Žiadne premostenia nie sú práve dostupné..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Mohli by ste skúsiť %s vrátiť sa naspäť %s a zvoliť iný typ premostenia!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Krok %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Prevziať %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Krok %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ZÃskaÅ¥ %s premostenia %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Krok %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Teraz %s pridajte premostenia do Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sD%saj mi premostenia!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "RozÅ¡Ãrené nastavenia"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nie"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "žiadne"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sÃ%sno!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sZ%sÃskaÅ¥ Bridges"
diff --git a/lib/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index cbec406..0000000
--- a/lib/bridgedb/i18n/sl_SI/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,359 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Dušan <dusan.k at zoho.com>, 2014
-# marko <mr.marko at gmail.com>, 2011
-# Nwolfy <nikopavlinek at ymail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-10-15 17:11+0000\n"
-"Last-Translator: Dušan <dusan.k at zoho.com>\n"
-"Language-Team: Slovenian (Slovenia) (http://www.transifex.com/projects/p/torproject/language/sl_SI/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: sl_SI\n"
-"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "Oprostite! Nekaj je narobe pri vaši zahtevi"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[To je samodejno sporoÄilo, prosimo, da nanj ne odgovarjate.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Tu so vaše premostitve"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "PrekoraÄili ste razmerje omejitve. Prosim upoÄasnite! Minimalni Äas med\ne-poÅ¡to je %s ur. Vsa nadaljna e-poÅ¡ta med tem Äasom bo prezrta. "
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (združite ukaze razliÄnih možnosti hkrati) "
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Dobrodošli v BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Splošno podprti transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hej, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Pozdravljen, prijatelj!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "Javni KljuÄi"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "To e-pismo je bilo napisano z mavricami, enorogi in bleÅ¡Äicami\nza %s na %s ob %s"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB lahko oskrbuje mostiÄke z razliÄnimi %stipi Pluggable Transports%s,\nki lahko pomagajo zmesti vaÅ¡o povezavo z Tor Network, in jo naredijo bolj\nnepregledno za vsakogar, ki spremlja vaÅ¡ internetni promet z namenom ugotavljanja\nuporabe Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Nekateri mostiÄki z IPv6 naslovi so tudi dosegljivi, Äeprav nekateri Pluggable\nTransports niso IPv6 kompatibilni.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Dodatno, BridgeDB ima mnogo nezanimih mostiÄkov %s brez kakrÅ¡njihkoli\ntransportnih vtiÄnikov %s ki morda ne zvenijo dobro, vendar Å¡e vedno\npomagajo pri preslepitvi internetne cenzure v mnogih primerih\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Kaj so mostiÄki?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s MostiÄki %s so Tor releji, ki pomagajo preslepiti nadzor"
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Rabim drugaÄno pot do izbire mostiÄkov!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Druga pot za izbiro mostiÄkov je preko naÅ¡e e-poÅ¡te %s. Morate pa\nposlati e-pismo z uporabo naslova naslednjih e-poÅ¡tnih ponudnikov\n%s, %s ali %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Moj mostiÄek ne dela! Rabim pomoÄ!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Äe vaÅ¡ Tor ne deluje, nam poÅ¡ljite e-poÅ¡to %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Poskusite vkljuÄiti Äim veÄ informacij o vaÅ¡em primeru,\nvkljuÄno s seznamom\nmostiÄkov in Pluggable Transports, ki ste jih uporabili, verzijo Tor Browser\nin vsa sporoÄila, ki vam jih je dal Tor, itd."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Tu so vrstice mostiÄkov:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Pridobite si mostiÄke!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Izberite tip mostiÄka:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Rabite IPv6 naslove?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Rabite %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Vaš iskalnik slik ne prikaže pravilno."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Vnesite znake iz zgornje slike..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Kako zaÄeti z uporabo mostiÄkov"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Za vnos mostiÄkov v Tor Browser sledite navodilom v %s Tor\nBrowser strani prenosov %s za zagon Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Ko se pojavi dialog v Tor net nastavitve, kliknite Oblikovanje in sledite\nÄarovniku do konca:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Ali vaÅ¡ Internet ponudnik (ISP) blokira ali drugaÄe cenzurira povezavo\nv Tor omrežje?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "OznaÄite \"Da\" in kliknite \"Naprej\". Za oblikovanje novih mostiÄkov, kopirajte in\nprilepite vrstice mostiÄkov v vnosno polje. Za konec kliknite \"Povezava\", in\nto je to! Äe imate težave, poskusite klikniti \"PomoÄ\"\nv Äarovniku \"Tor net nastavitve\" za nadaljno pomoÄ."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Prikaži to sporoÄilo"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Zahteva za vanilla mostiÄke."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Zahteva za IPv6 mostiÄke."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Zahteva za VtiÄnike Transport po TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Nabavite duplikat BridgeDB's javnega GnuPG kljuÄa."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "Prijavite HroÅ¡Äa"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "Izvorna koda"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "Zapis sprememb"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "Kontakt"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "bog pomagaj!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "Na razpolago ni nobenih mostiÄkov..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Morda bi morali %s nazaj %s in izbrati drugaÄen tip mostiÄka!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Korak %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Snemite %s Tor Iskalnik %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Korak %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Nabavite %s mostiÄke %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Korak %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Sedaj %s dodajte mostiÄke v Tor Iskalnik %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sa rabim mostiÄke!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Napredne opcije"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Ne"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "Nobeden"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%sradi nastavitve Bridges"
diff --git a/lib/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 8bb85b4..0000000
--- a/lib/bridgedb/i18n/sq/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,380 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Bujar Tafili, 2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-23 22:51+0000\n"
-"Last-Translator: Bujar Tafili\n"
-"Language-Team: Albanian (http://www.transifex.com/projects/p/torproject/language/sq/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: sq\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Kërkojmë ndjesë! Diçka shkoi keq me kërkesën tuaj."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Ky është një mesazh automatik; ju lutemi mos u përgjigjni.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Këtu janë urat tuaja:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Ju e keni kapërcyer kufirin. Ju lutemi, më ngadalë! Koha minimum midis \ne-postave është %s orë. Të gjitha e-postat e tjera gjatë kësaj periudhe kohe do të injorohen."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "COMMANDs: (kombinojini COMMANDs për të specifikuar zgjedhje të shumëfishta njëkohësisht)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Mirë se erdhët tek BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Transport TYPEs të mbështetura këtë çast: "
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hej, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Përshëndetje mik!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Kyça Publikë"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Kjo e-postë është gjeneruar me ylberë, njëbrirësha dhe shkëndija\npër %s, më %s, në orën %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB mund të ofrojë ura me shumë %slloje të Pluggable Transports%s,\nqë mund të ndihmojë fshehjen e lidhjeve tuaja për Tor Network, duke e bërë atë më \ntë vështirë për këdo që vëzhgon trafikun tuaj të internetit, për të përcaktuar nëse ju jeni\nduke përdorur Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Disa ura me adresa IPv6 janë po ashtu të mundshme, ndonëse disa Pluggable\nTransports s'janë të përputhshëm me IPv6.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Përveç kësaj, BridgeDB ka shumë ura %s të bezdisshme, pa ndonjë\nPluggable Transports %s, çka ndoshta nuk do të tingëllojë mirë, por ata ende\nmund të ndihmojnë që ta anashkaloni censurën e internetit në shumë raste.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Ã'janë urat?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Urat %s janë rele Tor, që ju ndihmojnë të anashkaloni censurën."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Më nevojitet një mënyrë alternative për përftimin e urave!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "NJë mënyrë tjetër për të përftuar ura është të dërgoni një e-postë tek %s. ju lutemi vini re se ju duhet\nta dërgoni e-postën, duke përdorur një adresë nga njëri prej ofruesve të e-postës në vijim:\n%s, %s ose %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Urat e mia nuk punojnë! Më duhet ndihmë!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Nëse Tor i juaj nuk punon, ju duhet t'i dërgoni e-postë %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Përpiquni të përfshini sa më shumë informacion që të mundeni rreth rastit tuaj, duke vendosur edhe listën e\nurave dhe Pluggable Transports që provuat të përdorni, versionin tuaj të Tor Browser,\nsi dhe çdo mesazh që ka dhënë Tor, etj."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Këtu janë linjat e urave tuaja:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Merrni Ura!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Ju lutemi përzgjidhni mundësitë për llojin e urës:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "A ju nevojiten adresat IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "A ju nevojitet një %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Shfletuesi juaj nuk po i shfaq si duhet imazhet."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Futini karakteret nga imazhi më sipër..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Si të nisni t'i përdorni urat tuaja"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Për t'i futur urat në Tor Browser, ndiqini instruksionet në %s faqen e shkarkimit të Tor\nBrowser %s, që ta nisni Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Kur dialogu i \"Konfigurimit të Rrjetit Tor\" të kërcejë, klikoni \"Konfiguroni\" dhe ndiqni\nasistentin derisa ta kërkojë ai:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "A i pengon apo i censuron Ofruesi juaj i Shërbimt Internet (ISP) lidhjet\ntek rrjeti Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Përzgjidhni \"Po\" dhe më pas klikoni \"Tjetri\". Që të konfiguroni urat tuaja të reja, kopjojini dhe\nngjitini linjat e urave në kutinë e futjes së tekstit. Më në fund, klikoni \"Lidhuni\", dhe\ndo të jeni gati për t'ia nisur! Nëse do të përjetoni probleme, përpiquni të klikoni butonin \"Ndihmë\" \ntek asistenti i \"Konfigurimit të Rrjetit Tor\", për më shumë mbështetje."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Shfaq këtë mesazh."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Kërkoni urat vanilje ose non-Pluggable Transport."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Kërkoni urat IPv6."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Kërkoni një Pluggable Transport nëpërmjet TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Merrni një kopje të kyçit publik GnuPG të BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Raportoni një gabim"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Kodi Burimor"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Regjistri i ndryshimeve"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Përzgjidhini të Gjitha"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Tregoni Kodin QR"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Kodi QR përlinjat e urave tuaja"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ãfarë tersi!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Duket se u has një gabim, duke marrë Kodin tuaj QR."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Ky Kod QR përmban linja urash. Skanojeni me një lexues Kodi QR, që t'i kopjoni linjat e urave tuaja në celular, apo në pajisje të tjera."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Këtë çast s'ka asnjë urë të disponueshme..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr " Ndoshta duhet të provoni të %s shkoni prapa %s dhe të zgjidhni një lloj të ndryshëm ure!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Hapi %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Shkarkoni %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Hapi %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Merrni %s ura %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Hapi %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Tani %s shtojini urat tek Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sM%së jepni urat!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Opsionet e Përparuara"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Jo"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "asnjë"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sP%so!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sM%serrni Urat"
diff --git a/lib/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index ce8b18a..0000000
--- a/lib/bridgedb/i18n/sv/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,387 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Anders Jensen-Urstad <anders at unix.se>, 2014
-# Emil Johansson <emil.a.johansson at gmail.com>, 2015
-# GabSeb, 2014
-# Petomatick <petomatick at hotmail.com>, 2011
-# ph AA, 2015
-# phst <transifex at sturman.se>, 2014
-# leveebreaks, 2014
-# WinterFairy <winterfairy at riseup.net>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-23 19:22+0000\n"
-"Last-Translator: ph AA\n"
-"Language-Team: Swedish (http://www.transifex.com/projects/p/torproject/language/sv/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: sv\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Något gick tyvärr fel med din förfrågan."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Detta är ett automatiskt meddelande; Var god svara ej]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Här är dina bryggor:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Du har överskridit din nivå. Ta det lugnt! Minsta tillåtna tiden mellan epost är %s timmar. Epost utöver detta kommer att ignoreras."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "KOMMANDOn: (kombinera KOMMANDOn för att ange flera val på en gång)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "Välkommen till BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "För närvarande stöds följande transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Hej, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Hej, kompis!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Publika nycklar"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Det här mailet skapades med hjälp av regnbågar, enhörningar och \nett regn av gnistor för %s den %s kl %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB kan tillhandahålla bryggor med flera %styper av Pluggable Transports%s,\nsom kan omforma din trafik till Tor Network, vilket gör det svårare för någon\nsom avlyssnar din internetuppkoppling att veta ifall du använder Tor.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "Det finns några bryggor med IPv6-adresser tillgängliga, men vissa Pluggable\nTransports är inte IPv6-kompatibla.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Dessutom har BridgeDB några alldeles vanliga bryggor %s utan Pluggable\nTransports %s vilket kanske inte låter så häftigt, men i många fall kan de \nfortfarande hjälpa dig med att kringgå internet-censur.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Vad är bryggor?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bryggor %s är Tor-reläer som hjälper dig kringgå censur."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Jag behöver ett alternativt sätt att skaffa bryggor på!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Ett annat sätt att få nya broar är att skicka e-post till %s. Du måste skicka mailet\nfrån en adress hos någon av följande e-postleverantörer:\n%s, %s eller %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Mina bryggor fungerar inte! Jag behöver hjälp!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Om Tor inte fungerar för dig så kan du e-posta %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Försök att berätta så mycket om problemet som du kan, bland annat vilka\nbryggor och Pluggable Transports som du försökt använda, vilken version av Tor Browser du använder, vilka felmeddelanden som Tor visat, etc."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Här är dina rader med bryggor:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Skaffa Bryggor!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Välj alternativ för typ av brygga:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Behöver du en IPv6-adress?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Behöver du %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Din webbläsare visar inte bilder korrekt."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Skriv in tecknen från bilden ovan..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Hur du börjar använda dina bryggor"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "För att ange bryggor i Tor Browser, följ instruktionerna på nedladdningssidan %s Tor Browser %s för att starta Tor Browser."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "När dialogen \"Nätverksinställningar för Tor\" visas, välj \"Konfigurera\" och följ\nstegen tills du kommer till frågan:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Blockerar eller filtrerar din Internetleverantör (ISP) anslutningar till Tor network?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "Välj 'Ja' och klicka sedan på 'Nästa'. För att konfigurera nya bryggor, kopiera och klistra in bryggraderna i textrutan. Klicka slutligen på 'Anslut' och det borde vara klart för användning! Om du får problem, prova att klicka på 'Hjälp'-knappen i 'Tor Network Settings' för vidare hjälp."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Visa detta meddelande."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Leta efter vanliga bryggor."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "Begär IPv6-bryggor"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Leta efter en Pluggable Transport av en viss TYPE"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "Hämta en kopia av BridgeDB:s publika GnuPG-nyckel."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Rapportera en bugg"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Källkod"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "Ãndringslogg"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Kontakt"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Markera Allt"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "Visa QR-kod"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR-kod för dina rader med bryggor"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Bomber och granater, nåt gick snett!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "Det verkar som att ett fel orsakade problem att få din QR-kod."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Denna QR-kod innehåller dina rader med bryggor. Skanna den med en QR-kodläsare för att kopiera dina rader med bryggor till mobila och andra enheter."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "För närvarande finns inga bryggor tillgängliga..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Prova att %s gå tillbaka %s för att välja en annan typ av brygga!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "Steg %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "Ladda ner %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Steg %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "Skaffa %s bryggor %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Steg %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Nu kan du %s lägga till bryggor i Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sG%se mig bryggor!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "Avancerade inställningar"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Nej"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "inget"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sJ%sa!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sH%sämta Bridges"
diff --git a/lib/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 94444e6..0000000
--- a/lib/bridgedb/i18n/ta/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,357 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# git12a <git12 at openmailbox.org>, 2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2015-02-13 10:18+0000\n"
-"Last-Translator: git12a <git12 at openmailbox.org>\n"
-"Language-Team: Tamil (http://www.transifex.com/projects/p/torproject/language/ta/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: ta\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "மனà¯à®©à®¿à®à¯à®à®µà¯à®®à¯! à®à®à¯à®à®³à¯ வà¯à®£à¯à®à¯à®à¯à®³à®¿à®±à¯à®à¯ à®à®¤à¯à®à®°à¯ தவற௠நà¯à®°à¯à®¨à¯à®¤à¯à®³à¯à®³à®¤à¯."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[ à®à®¤à¯ à®à®°à¯ தானியà®à¯à®à®¿ à®à¯à®¯à¯à®¤à®¿; தயவ௠à®à¯à®¯à¯à®¤à¯ பதில௠à®
னà¯à®ªà¯à®ª வà¯à®£à¯à®à®¾à®®à¯.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "à®à®¤à¯ à®à®à¯à®à®³à¯ bridges:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "நà¯à®à¯à®à®³à¯ வà¯à®¤ வரமà¯à®ªà¯ à®®à¯à®±à®¿ விà®à¯à®à¯à®°à¯à®à®³à¯. தயவ௠à®à¯à®¯à¯à®¤à¯ à®®à¯à®¤à¯à®µà®à¯à®¯à®µà¯à®®à¯! மினà¯à®©à®à¯à®à®²à¯à®à®³à®¿à®©à¯ à®à®à¯à®¯à¯ à®à¯à®±à¯à®¨à¯à®¤à®ªà®à¯à® நà¯à®°à®®à¯ %s மணிà®à®³à¯. à®à®¨à¯à®¤ நà¯à®°à®¤à¯à®¤à®¿à®©à¯à®³à¯ à®
னà¯à®¤à¯à®¤à¯ மினà¯à®©à®à¯à®à®²à¯à®à®³à¯à®®à¯ நிராà®à®°à®¿à®à¯à®à®ªà¯à®ªà®à¯à®®à¯."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "à®à®à¯à®à®³à¯à®à®³à¯: (பல விரà¯à®ªà¯à®ªà®à¯à®à®³à¯ à®à¯à®±à®¿à®ªà¯à®ªà®¿à® à®à®à¯à®à®³à¯à®à®³à¯ à®à®°à¯à®à®®à®¯à®¤à¯à®¤à®¿à®²à¯ à®à®©à¯à®±à®¿à®¯à®¿à®©à¯à®à®µà¯à®®à¯)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "BridgeDB-யினà¯à®³à¯ நலà¯à®µà®°à®µà¯"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "தறà¯à®ªà¯à®¾à®¤à¯ à®à®¤à®°à®µà¯ à®à®³à¯à®³ Transport TYPE-à®à®³à¯ "
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "ஹà¯, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ஹலà¯à®¾, தà¯à®´à®¾!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "பà¯à®¾à®¤à¯ à®à®¾à®µà®¿à®à®³à¯"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "à®à®¨à¯à®¤ மினà¯à®©à®à¯à®à®²à¯ வானவிலà¯, யà¯à®©à®¿à®à®¾à®°à¯à®©à¯à®à®³à¯ மறà¯à®±à¯à®®à¯ à®à®¿à®±à¯à®¤à¯à®ªà¯à®ªà¯à®±à®¿à®à®³à¯ à®à¯à®£à¯à®à¯ \n%s -à®à®¾à® %s à®
னà¯à®±à¯ %s நà¯à®°à®¤à¯à®¤à®¿à®²à¯ à®à®°à¯à®µà®¾à®à¯à®à®ªà¯à®ªà®à¯à®à®¤à¯."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB-யால௠%sபலவà®à¯à®¯à®¾à®© Pluggable Transports%s bridge-à®à®³à¯ தரமà¯à®à®¿à®¯à¯à®®à¯,\nà®à®µà¯ à®à®à¯à®à®³à¯ Tor Network à®à®£à¯à®ªà¯à®ªà¯à®à®³à¯ விளà®à¯à®à®¾à®¤à®µà®£à¯à®£à®®à¯ à®à®à¯à®à®µà¯à®¤à®µà®¿, à®à®à¯à®à®³à®¿à®©à¯ \nà®à®£à¯à®¯ பà¯à®¾à®à¯à®à¯à®µà®°à®¤à¯à®¤à¯ à®à®£à¯à®à®¾à®©à®¿à®ªà¯à®ªà®µà®°à¯à®à¯à®à¯ நà¯à®à¯à®à®³à¯ Tor பயனà¯à®ªà®à¯à®¤à¯à®¤à¯à®à®¿à®±à¯à®°à¯à®à®³à¯ à®à®©à®à¯à®à®©à¯à®à®±à®¿à®¯ à®®à¯à®²à¯à®®à¯\nà®à®à®¿à®©à®®à¯ à®à®à¯à®à¯à®®à¯.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "à®à®¿à®² IPv6 à®®à¯à®à®µà®°à®¿à®à¯à®à¯à®£à¯à® bridge-à®à®³à¯à®®à¯ à®à®³à¯à®³à®©, à®à®©à®¿à®©à¯à®®à¯ à®à®¿à®² Pluggable\nTransports-à®à®³à¯ IPv6 à®à®à®©à¯ பà¯à®¾à®°à¯à®¨à¯à®¤à®à¯à®à¯à®à®¿à®µà®¤à¯ à®à®²à¯à®²à¯.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "à®à¯à®à¯à®¤à®²à®¾à®, BridgeDB-யினà¯à®³à¯ à®à®°à®¾à®³à®®à®¾à®© பழà¯à®¯à®®à¯à®±à¯ bridge-à®à®³à¯ \n%s Pluggable Transports வà®à®¤à®¿à®¯à®¿à®²à¯à®²à®¾à®®à®²à¯ à®à®³à¯à®³à®¤à¯ %s à®à®£à®°à¯à®à¯à®à®¿ à®à®à¯à®à¯à®à®¿à®±à®µà®¾à®±à¯ à®à®²à¯à®²à®¾à®µà®¿à®à¯à®à®¾à®²à¯à®®à¯, \nà®à®µà¯à®¯à®¾à®²à¯ பல à®à®¨à¯à®¤à®°à¯à®ªà¯à®ªà®à¯à®à®³à®¿à®²à¯ à®à®©à¯à®¯ தணிà®à¯à®à¯à®¯à¯ à®à®à®à¯à® à®à®¤à®µà¯à®®à¯.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Bridges à®à®©à¯à®±à®¾à®²à¯ à®à®©à¯à®©?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridge-à®à®³à¯ %s à®à®©à¯à®®à¯ Tor Relay-à®à®³à¯ தணிà®à¯à®à¯à®¯à¯ à®à®à®à¯à® à®à®¤à®µà¯à®®à¯"
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "à®à®©à®à¯à®à¯ Bridge-à®à®³à¯ பà¯à®±à¯à®µà®¤à®±à¯à®à¯ à®à®°à¯ மாறà¯à®±à¯ வழி வà¯à®£à¯à®à¯à®®à¯"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Bridge-à®à®³à¯ பà¯à®±à¯à®µà®¤à®±à¯à®à¯ à®à®°à¯ மாறà¯à®±à¯à®µà®´à®¿ %s à®®à¯à®à®µà®°à®¿à®à¯à®à¯ à®à®°à¯ மினà¯à®©à®à¯à®à®²à¯ à®
னà¯à®ªà¯à®ªà®²à®¾à®®à¯.\nதயவ௠à®à¯à®¯à¯à®¯à®¤à¯ à®à®µà®©à®¿à®à¯à®à®µà¯à®®à¯, நà¯à®à¯à®à®³à¯ à®à®à¯à®à®¾à®¯à®®à®¾à® à®à¯à®´à¯à®à®£à¯à® மினà¯à®©à®à¯à®à®²à¯ à®à¯à®µà¯à®à®³à®¿à®©à¯ à®®à¯à®à®µà®°à®¿à®¯à®¿à®²à®¿à®°à¯à®¨à¯à®¤à¯ à®®à®à¯à®à¯à®®à¯ மினà¯à®©à®à¯à®à®²à¯ à®
னà¯à®ªà¯à®ªà®²à®¾à®®à¯ :\n%s, %s or %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "à®à®©à¯ Bridge-à®à®³à¯ வà¯à®²à¯ à®à¯à®¯à¯à®¯à®µà®¿à®²à¯à®²à¯! à®à®©à®à¯à®à¯ à®à®¤à®µà®¿ தà¯à®µà¯!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "à®à®à¯à®à®³à¯ Tor வà¯à®²à¯ à®à¯à®¯à¯à®¯à®µà®¿à®²à¯à®²à¯ à®à®©à®¿à®²à¯, நà¯à®à¯à®à®³à¯ %s தà¯à®à®°à¯à®ªà¯à®à¯à®³à¯à® "
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "à®à®à¯à®à®³à¯ வழà®à¯à®à¯ à®à¯à®±à®¿à®¤à¯à®¤à¯ à®
திà®à®ªà®à¯à® தà®à®µà®²à¯à®à®³à¯ à®à¯à®°à¯à®à¯à®à®³à¯, à®à®¤à®¿à®²à¯ நà¯à®à¯à®à®³à¯ பயனà¯à®ªà®à¯à®¤à¯à®¤ à®®à¯à®¯à®±à¯à®à®¿à®¤à¯à®¤ bridges\nமறà¯à®±à¯à®®à¯ Pluggable Transports, தà®à¯à®à®³à®¿à®©à¯ Tor Browser பதிபà¯à®ªà¯ à®à®£à¯ à®à®à®¿à®¯à®µà¯ à®
à®à®à¯à®à¯à®®à¯, மறà¯à®±à¯à®®à¯ Tor\nà®à¯à®°à®¿à®¯à®¤à®à®µà®²à¯à®à®³à¯ பà¯à®¾à®©à¯à®±à®µà®±à¯à®±à¯à®¯à¯à®®à¯ à®à¯à®°à¯à®à¯à®à®µà¯à®®à¯."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "à®à®¤à¯ à®à®à¯à®à®³à¯ bridge வரிà®à¯à®à®³à¯: "
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Bridge-à®à®³à¯ பà¯à®±à¯à®!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Bridge வà®à¯ விரà¯à®ªà¯à®ªà®à¯à®à®³à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "à®à®à¯à®à®³à¯à®à¯à®à¯ IPv6 à®®à¯à®à®µà®°à®¿à®à®³à¯ வà¯à®£à¯à®à¯à®®à®¾?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "à®à®à¯à®à®³à¯à®à¯à®à¯ à®à®°à¯ %s தà¯à®µà¯à®¯à®¾?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr " தà®à¯à®à®³à®¿à®©à¯ browser பà®à®à¯à®à®³à¯ à®à®°à®¿à®¯à®¾à® à®à®¾à®à¯à®à®µà®¿à®²à¯à®²à¯."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "à®®à¯à®²à¯ à®à®³à¯à®³ பà®à®¤à¯à®¤à®¿à®²à¯ à®à®°à¯à®à¯à®à¯à®®à¯ à®à®´à¯à®¤à¯à®¤à¯à®à¯à®à®³à¯ à®à®³à¯à®³à®¿à®à®µà¯à®®à¯..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "தà®à¯à®à®³à®¿à®©à¯ bridge-à®à®³à¯ பயனà¯à®ªà®à¯à®¤à¯à®¤ தà¯à®¾à®à®à¯à®à¯à®µà®¤à¯ à®à®ªà¯à®ªà®à®¿"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Bridge-à®à®³à¯ Tor Browser-னà¯à®³à¯ à®à®³à¯à®³à®¿à®, %s Tor Browser பதிவிறà®à¯à® பà®à¯à®à®¤à¯à®¤à®¿à®²à¯ %s à®à®³à¯à®³ வழிமà¯à®±à¯à®à®³à¯ பினà¯à®ªà®±à¯à®±à®¿ Tor Browser-஠தà¯à®¾à®à®à¯à®à®®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "'Tor நà¯à®à¯à®µà¯à®¾à®°à¯à®à¯ à®
à®®à¯à®ªà¯à®ªà¯à®à®³à¯' à®à®°à¯à®¯à®¾à®à®²à¯ à®®à¯à®²à¯à®¤à¯à®¾à®©à¯à®±à¯à®®à¯ பà¯à®´à¯à®¤à¯, 'à®à®à¯à®à®®à¯à®à¯à®' à®à®¿à®³à®¿à®à¯à®à¯à®¯à¯à®¤à¯ \nவழிà®à®¾à®à¯à®à®¿à®¯à¯ à®
த௠à®à¯à®à¯à®à¯à®®à¯à®µà®°à¯ பினà¯à®ªà®±à¯à®±à®µà¯à®®à¯:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "à®à®à¯à®à®³à®¿à®©à¯ à®à®£à¯à®¯ à®à¯à®µà¯ வழà®à¯à®à¯à®®à¯ (ISP) நிறà¯à®µà®©à®®à¯ Tor நà¯à®à¯à®µà¯à®°à¯à®à¯ தà®à¯à®à¯à®à®¿à®±à®¤à®¾ à®
லà¯à®²à®¤à¯ \nவà¯à®±à¯à®µà®´à®¿à®¯à®¿à®²à¯ தணிà®à¯à®à¯ à®à¯à®¯à¯à®à®¿à®±à®¤à®¾? "
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "'à®à®®à¯' தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®¤à¯à®¤à¯ பினà¯à®©à®°à¯ 'à®
à®à¯à®¤à¯à®¤à¯' à®à®¿à®³à®¿à®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. தà®à¯à®à®³à®¿à®©à¯ பà¯à®¤à®¿à®¯ bridge-à®à®³à¯ à®à®à¯à®à®®à¯à®à¯à®, \nவரிà®à¯à®à®³à¯ வாà®à¯à®à®¿à®¯à®®à¯ à®à®³à¯à®³à¯tà®à¯ பà¯à®à¯à®à®¿à®¯à®¿à®²à¯ நà®à®²à¯à®à®à¯à®¤à¯à®¤à¯ பà¯à®¸à¯à®à¯ à®à¯à®¯à¯à®¯à®µà¯à®®à¯. à®à®±à¯à®¤à®¿à®¯à®¾à®, 'à®à®£à¯à®à¯à®' à®à®¿à®³à®¿à®à¯ \nà®à¯à®¯à¯à®¤à®µà¯à®à®©à¯, நà¯à®à¯à®à®³à¯ à®à¯à®²à¯à®² தயாரà¯! நà¯à®à¯à®à®³à¯ பிரà®à¯à®à®¿à®©à¯à®¯à¯ à®à®¨à¯à®¤à®¿à®¤à¯à®¤à®¾à®²à¯, 'Tor நà¯à®à¯à®µà¯à®¾à®°à¯à®à¯ à®
à®®à¯à®ªà¯à®ªà¯à®à®³à¯' \nவழிà®à®¾à®à¯à®à®¿à®¯à®¿à®²à¯ à®à®³à¯à®³ 'à®à®¤à®µà®¿' பà¯à®¾à®¤à¯à®¤à®¾à®©à¯ à®à®¿à®³à®¿à®à¯ à®à¯à®¯à¯à®¤à¯ à®®à¯à®²à¯à®®à¯ à®à®¤à®µà®¿ பà¯à®±à®µà¯à®®à¯."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "à®à®¨à¯à®¤ à®à¯à®¯à¯à®¤à®¿à®¯à¯ à®à®¾à®à¯à®à®µà¯à®®à¯ "
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "பழà¯à®¯à®®à¯à®±à¯à®¯à®¾à®© bridge-à®à®³à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPv6 bridge-à®à®³à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "Pluggable Transport bridge-à®à®³à¯ TYPE வà®à¯à®¯à®¿à®²à¯ விணà¯à®£à®ªà¯à®ªà®¿à®à¯à®à®µà¯à®®à¯."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr " BridgeDB-யà¯à®à¯à®¯ பà¯à®¾à®¤à¯ GnuPG à®à®¾à®µà®¿à®¯à®¿à®©à¯ à®à®°à¯ நà®à®²à¯ பà¯à®±à®µà¯à®®à¯."
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "à®à®°à¯ பிழà¯à®¯à¯ à®
றிà®à¯à®à¯à®à¯à®¯à¯à®"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "à®®à¯à®² தà¯à®à¯à®ªà¯à®ªà¯"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "மாறà¯à®±à®®à¯à®à¯à®±à®¿à®à¯à®à¯à®®à¯ à®à¯à®¾à®ªà¯à®ªà¯"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "தà¯à®à®°à¯à®ªà¯ à®à¯à®³à¯"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr " ஹà¯, பிரà®à¯à®à®©à¯!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "தறà¯à®à®®à®¯à®®à¯ Bridge-à®à®³à¯ யà¯à®¤à¯à®®à¯ à®à®²à¯à®²à¯..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "à®à®°à¯à®µà¯à®³à¯ நà¯à®à¯à®à®³à¯ %s à®®à¯à®¯à®±à¯à®à®¿à®à¯à® பினà¯à®©à¯ à®à¯à®©à¯à®±à¯ %s வà¯à®±à¯à®°à¯ வà®à¯ தà¯à®°à¯à®µà¯ à®à¯à®¯à¯à®¯à®²à®¾à®®à¯!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "à®
à®à®¿ %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "பதிவிறà®à¯à®à®µà¯à®®à¯ %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "à®
à®à®¿ %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s Bridge-à®à®³à¯ %s பà¯à®±à¯à®"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "à®
à®à®¿ %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "à®à®ªà¯à®ªà¯à®´à¯à®¤à¯ %s bridge-à®à®³à¯ Tor Browser-à®à®³à¯ %s à®à¯à®°à¯à®à¯à®à®µà¯à®®à¯"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sநà¯%sரயà®à®¿à®¯à®¾à® bridge-à®à®³à¯ à®à¯à®à¯à®à¯à®à®µà¯à®®à¯!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "à®®à¯à®®à¯à®ªà®à¯à® விரà¯à®ªà¯à®ªà®à¯à®à®³à¯"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "à®à®²à¯à®²à¯"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "à®à®¤à¯à®µà¯à®®à®¿à®²à¯à®²à¯"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sà®%sà®®à¯!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sபà¯%sà®°à¯à® Bridge-à®à®³à¯"
diff --git a/lib/bridgedb/i18n/templates/bridgedb.pot b/lib/bridgedb/i18n/templates/bridgedb.pot
deleted file mode 100644
index bd10a21..0000000
--- a/lib/bridgedb/i18n/templates/bridgedb.pot
+++ /dev/null
@@ -1,391 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-# Isis Lovecruft <isis at torproject.org>, 2015.
-#
-#, fuzzy
-#
-# Translators:
-# runasand <runa.sandvik at gmail.com>, 2011
-# Isis Lovecruft <isis at torproject.org>, 2012-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: bridgedb 0.2.4-234-g193c80a-dirty\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'"
-"POT-Creation-Date: 2015-03-19 22:13+0000\n"
-"PO-Revision-Date: 2015-03-19 22:13+0000\n"
-"Last-Translator: Isis Lovecruft <isis at torproject.org>\n"
-"Language-Team: English (http://www.transifex.com/projects/p/torproject/language/en/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: en\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:107
-msgid "Sorry! Something went wrong with your request."
-msgstr ""
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr ""
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr ""
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be "
-"ignored."
-msgstr ""
-
-#: lib/bridgedb/strings.py:25
-msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr ""
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr ""
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr ""
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr ""
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are"
-"\n"
-"using Tor.\n"
-"\n"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any"
-"\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still"
-"\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr ""
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr ""
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr ""
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr ""
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you "
-"must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr ""
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr ""
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr ""
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr ""
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr ""
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr ""
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr ""
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr ""
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr ""
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, first go to the %s Tor Browser download\n"
-"page %s and then follow the instructions there for downloading and starting\n"
-"Tor Browser."
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:126
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and "
-"follow\n"
-"the wizard until it asks:"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:130
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor "
-"connections\n"
-"to the Tor network?"
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:134
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and"
-"\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr ""
-
-#: lib/bridgedb/strings.py:142
-msgid "Displays this message."
-msgstr ""
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:146
-msgid "Request vanilla bridges."
-msgstr ""
-
-#: lib/bridgedb/strings.py:147
-msgid "Request IPv6 bridges."
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:149
-msgid "Request a Pluggable Transport by TYPE."
-msgstr ""
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:152
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr ""
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr ""
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr ""
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr ""
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr ""
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy "
-"your bridge lines onto mobile and other devices."
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr ""
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid " Perhaps you should try %s going back %s and choosing a different bridge type!"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr ""
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr ""
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr ""
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr ""
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr ""
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr ""
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr ""
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr ""
-
diff --git a/lib/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index a8b6727..0000000
--- a/lib/bridgedb/i18n/th/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,101 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2013 ORGANIZATION
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Translators:
-# AomNicha <vainilla7 at gmail.com>, 2013
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
-"POT-Creation-Date: 2013-03-27 21:41+0000\n"
-"PO-Revision-Date: 2013-07-27 08:50+0000\n"
-"Last-Translator: AomNicha <vainilla7 at gmail.com>\n"
-"Language-Team: Thai (http://www.transifex.com/projects/p/torproject/language/th/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: th\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: lib/bridgedb/templates/base.html:33
-msgid "What are bridges?"
-msgstr "สะà¸à¸²à¸à¸«à¸¡à¸²à¸¢à¸à¸¶à¸à¸à¸°à¹à¸£"
-
-#: lib/bridgedb/templates/base.html:34
-#, python-format
-msgid ""
-"%s Bridge relays %s are Tor relays that help you circumvent censorship."
-msgstr "%s สะà¸à¸²à¸à¸£à¸µà¹à¸¥à¸¢à¹ %s à¸à¸·à¸ Tor รีà¹à¸¥à¸¢à¹ à¸à¸µà¹à¸à¹à¸§à¸¢à¹à¸«à¹à¸à¸¸à¸à¸ªà¸²à¸¡à¸²à¸£à¸«à¸¥à¸µà¸à¸«à¸à¸µà¸à¸²à¸£à¸à¸£à¸§à¸à¸ªà¸à¸"
-
-#: lib/bridgedb/templates/base.html:39
-msgid "I need an alternative way of getting bridges!"
-msgstr "à¸à¸±à¸à¸à¹à¸à¸à¸à¸²à¸£à¸à¸²à¸à¹à¸¥à¸·à¸à¸à¸à¸·à¹à¸à¹à¸à¸·à¹à¸à¹à¸à¹à¸²à¸à¸¶à¸à¸ªà¸°à¸à¸²à¸"
-
-#: lib/bridgedb/templates/base.html:40
-#, python-format
-msgid ""
-"Another way to find public bridge addresses is to send an email (from a %s "
-"or a %s address) to %s with the line 'get bridges' by itself in the body of "
-"the mail."
-msgstr "à¸à¸µà¸à¸«à¸à¸¶à¹à¸à¸à¹à¸à¸à¸à¸²à¸ สำหรัà¸à¸à¹à¸à¸«à¸²à¸à¸µà¹à¸à¸¢à¸¹à¹à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¸ªà¸²à¸à¸²à¸£à¸à¸° สามารà¸à¸à¸³à¹à¸à¹à¸à¹à¸²à¸à¸à¸²à¸£à¸ªà¹à¸à¸à¸µà¹à¸¡à¸¥ (à¸à¸²à¸ %s หรืภà¸à¸µà¹à¸à¸¢à¸¹à¹ %s) à¹à¸à¸ªà¸¹à¹ %s à¸à¹à¸§à¸¢à¹à¸ªà¹à¸à¸à¸²à¸ \"\n\"à¸à¸²à¸£à¹à¸à¹à¸²à¸à¸¶à¸à¸ªà¸°à¸à¸²à¸\" ภายà¹à¸à¹à¸à¸·à¹à¸à¸«à¸²à¸à¸à¸à¸à¸µà¹à¸¡à¸¥"
-
-#: lib/bridgedb/templates/base.html:48
-msgid "My bridges don't work! I need help!"
-msgstr "à¸à¹à¸à¸à¸à¸²à¸£à¸à¸§à¸²à¸¡à¸à¹à¸§à¸¢à¹à¸«à¸¥à¸·à¸ สะà¸à¸²à¸à¹à¸¡à¹à¸à¸³à¸à¸²à¸"
-
-#: lib/bridgedb/templates/base.html:49
-#, python-format
-msgid ""
-"If your Tor doesn't work, you should email %s. Try including as much info "
-"about your case as you can, including the list of bridges you used, the "
-"bundle filename/version you used, the messages that Tor gave out, etc."
-msgstr "à¸à¹à¸² Tor à¹à¸¡à¹à¸à¸³à¸à¸²à¸ à¹à¸«à¹à¸à¸¸à¸à¸ªà¹à¸à¸à¸µà¹à¸¡à¸¥ %s à¸à¸£à¹à¸à¸¡à¹à¸à¸à¸à¹à¸à¸¡à¸¹à¸¥à¹à¸à¸µà¹à¸¢à¸§à¸à¸±à¸à¸ªà¸à¸²à¸à¸à¸²à¸£à¸à¹à¸à¸à¸à¸à¸¸à¸ รายà¸à¸·à¹à¸à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¸à¸µà¹à¸à¸¸à¸à¹à¸à¸¢à¹à¸à¹ à¸à¸±à¸à¸à¸µà¸£à¸²à¸¢à¸à¸·à¹à¸à¸«à¸£à¸·à¸à¹à¸§à¸à¸£à¹à¸à¸±à¹à¸à¸à¸µà¹à¸à¸¸à¸à¹à¸à¸¢à¹à¸à¹ à¸à¹à¸à¸à¸§à¸²à¸¡à¸à¸µà¹ Tor สà¹à¸à¸à¸¶à¸à¸à¸¸à¸ à¹à¸¥à¸°à¸à¸·à¹à¸à¹ à¸à¸µà¹à¸à¸³à¹à¸à¹à¸"
-
-#: lib/bridgedb/templates/bridges.html:10
-msgid ""
-"To use the above lines, go to Vidalia's Network settings page, and click "
-"\"My ISP blocks connections to the Tor network\". Then add each bridge "
-"address one at a time."
-msgstr " à¹à¸à¸·à¹à¸à¹à¸à¹à¸à¸²à¸à¸à¸²à¸¡à¸à¸³à¹à¸à¸°à¸à¸³à¸à¹à¸²à¸à¸à¸ à¹à¸à¸à¸µà¹à¸à¸²à¸£à¸à¸±à¹à¸à¸à¹à¸²à¹à¸à¸£à¸·à¸à¸à¹à¸²à¸¢ Vidalia à¸à¸²à¸à¸à¸±à¹à¸à¸à¸¥à¸´à¸à¸à¸µà¹ \"à¹à¸à¹à¸à¸ªà¸à¸µà¸à¸à¸à¸à¸±à¸à¸à¸¹à¸à¸à¸´à¸à¸à¸±à¹à¸à¹à¸¡à¹à¹à¸«à¹à¹à¸à¸·à¹à¸à¸¡à¸à¹à¸à¸à¸±à¸à¹à¸à¸£à¸·à¸à¸à¹à¸²à¸¢\" Tor à¹à¸¥à¸°à¸à¸à¹à¸à¸´à¹à¸¡à¸à¸µà¹à¸à¸¢à¸¹à¹à¸à¸à¸à¸ªà¸°à¸à¸²à¸à¹à¸à¹à¸¥à¸°à¸à¸±à¸à¸¥à¸à¹à¸"
-
-#: lib/bridgedb/templates/bridges.html:13
-msgid "No bridges currently available"
-msgstr "à¹à¸¡à¹à¸¡à¸µà¸ªà¸°à¸à¸²à¸à¸à¸µà¹à¹à¸à¹à¸à¸²à¸à¹à¸à¹à¹à¸à¸à¸à¸°à¸à¸µà¹"
-
-#: lib/bridgedb/templates/captcha.html:6
-msgid "Upgrade your browser to Firefox"
-msgstr "à¸à¹à¸§à¸¢à¸à¸£à¸±à¸à¸£à¸¸à¹à¸à¹à¸à¸£à¸²à¸§à¹à¹à¸à¸à¸£à¹à¸à¸à¸à¸à¸¸à¸à¹à¸à¹à¸à¹à¸à¸¥à¹à¸à¹à¸à¸"
-
-#: lib/bridgedb/templates/captcha.html:8
-msgid "Type the two words"
-msgstr "à¹à¸«à¹à¸à¸´à¸¡à¸à¹à¸à¸³à¸ªà¸à¸à¸à¸³"
-
-#: lib/bridgedb/templates/index.html:6
-msgid "Step 1"
-msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 1"
-
-#: lib/bridgedb/templates/index.html:8
-#, python-format
-msgid "Get %s Tor Browser Bundle %s"
-msgstr "à¹à¸à¹ %s Tor Browser Bundle %s"
-
-#: lib/bridgedb/templates/index.html:13
-msgid "Step 2"
-msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 2"
-
-#: lib/bridgedb/templates/index.html:15
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "à¹à¸à¹ %s สะà¸à¸²à¸ %s"
-
-#: lib/bridgedb/templates/index.html:19
-msgid "Step 3"
-msgstr "à¸à¸±à¹à¸à¸à¸à¸à¸à¸µà¹ 3"
-
-#: lib/bridgedb/templates/index.html:21
-#, python-format
-msgid "Now %s add the bridges to Tor %s"
-msgstr "à¸à¸à¸°à¸à¸µà¹ %s à¹à¸à¹à¹à¸à¸´à¹à¸¡à¸ªà¸°à¸à¸²à¸à¹à¸à¸à¸µà¹ Tor %s à¹à¸¥à¹à¸§"
diff --git a/lib/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 031ac6b..0000000
--- a/lib/bridgedb/i18n/tr/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,389 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# eromytsatiffird <driffitastymore at gmail.com>, 2014
-# Emir Sarı <bitigchi at openmailbox.org>, 2014
-# Emre <conan at operamail.com>, 2013
-# erg26 <ergungorler at gmail.com>, 2012
-# Idil Yuksel <perfectionne at gmail.com>, 2014
-# Sercan AltundaÅ <>, 2012
-# ozkansib <s.ozkan at gyte.edu.tr>, 2014
-# Tekel Bira <psycookie at gmail.com>, 2012
-# Volkan Gezer <volkangezer at gmail.com>, 2014-2015
-# zeki <zeki.ozguryazilim at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-02-14 11:40+0000\n"
-"Last-Translator: Volkan Gezer <volkangezer at gmail.com>\n"
-"Language-Team: Turkish (http://www.transifex.com/projects/p/torproject/language/tr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: tr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Ãzgünüz! Ä°steÄinizle ilgili bir hata oluÅtu."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Bu otomatik bir mesajdır; lütfen yanıtlamayınız.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "Ä°Åte köprüleriniz:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Hız limitini aÅtınız. Lütfen yavaÅlayın! E-postalar arasındaki minimum zaman %s saattir.\nBu süre içinde göndereceÄiniz e-postalarınız yok sayılacaktır."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "KOMUTlar: (birden fazla seçeneÄı aynı anda belirtmek için KOMUTları birleÅtirin)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "BridgeDB'ye HoÅ Geldiniz!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "Åu an desteklenen transport TYPEs:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "Selam, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "Merhaba dostum!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "Açık Anahtarlar"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Bu e-posta %s için %s tarihinde %s saatinde gökkuÅakları,\ntek boynuzlu atlar ve pırıltılarla oluÅturulmuÅtur."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB, birçok %stür Eklenebilir Aktarımlar%s içeren\nköprü saÄlayabilir.\nBu Åekilde Ä°nternet trafiÄinizi izleyen birinin Tor kullandıÄınızı\nanlamasını zorlaÅtırmak için Tor AÄı'na yaptıÄınız\nbaÄlantıları karıÅtırmanıza yardımcı olabilir.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "IPv6 kullanan bazı köprüler de mevcut olmasının yanında bazı Eklenebilir Aktarımlar\nIPv6 uyumlu deÄildir.\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "Ek olarak BridgeDB %s hiçbir Eklenebilir Aktarıma sahip olmayan %s birçok düz\nözelliksiz köprüye sahiptir. Adları hoÅ olmayabilir ancak hala çoÄu durumda sansürü\naÅmaya yardım edebilirler.\n\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Köprüler nedir?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Köprüler %s sansürü aÅmanıza yardımcı olan Tor aynalarıdır."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "Köprüler edinmek için baÅka bir yola gereksinimim var!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Köprüleri almanın diÄer bir yolu da %s adresine bir e-posta göndermektir.\nLütfen e-postanın aÅaÄıdaki e-posta saÄlayıcılardan birinden alınmıŠbir\nadresten gönderilmesi gerektiÄini unutmayın:\n%s, %s veya %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "Köprülerim çalıÅmıyor! Yardıma ihtiyacım var!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "Tor'unuz çalıÅmıyorsa %s adresine e-posta göndermelisiniz. "
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "Durumunuz hakkında olabildiÄince fazla bilgi içermeyi deneyin.\nÃrneÄin kullanmaya çalıÅtıÄınız Eklenebilir Aktarımlar, Tor Tarayıcı sürümünüz ve\nTor'un gösterdiÄi tüm hata iletileri vb."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "Ä°Åte köprü çizgileriniz:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "Köprüleri Al!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "Lütfen köprü türü için seçenekleri belirleyin:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "IPv6 adreslerine ihtiyacınız var mı?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "%s ihtiyacınız var mı?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "Tarayıcınız resimleri düzgün görüntülemiyor."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "Yukarıdaki görüntüdeki karakterlerini giriniz..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Köprülerinizi kullanmaya nasıl baÅlarsınız"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "Tor Tarayıcı içerisine köprüleri girmek için Tor Tarayıcı'yı baÅlatmak üzere\n%s Tor Tarayıcı indirme sayfasındaki %s yönergeleri izleyin."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "'Tor AÄ Ayarları' penceresi açıldıÄında, 'Yapılandır' tıklayın ve Åunu soruncaya dek\nsihirbazı izleyin:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "Ä°nternet Servis SaÄlayıcınız (ISP) Tor aÄına baÄlantıyı engelliyor\nveya baÅka Åekillerde sansür uyguluyor mu?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "'Evet' ve ardından 'Ä°leri' tıklayın. Yeni köprülerinizi yapılandırmak için,\nköprü satırlarını kopyalayarak metin kutusu girdisine yapıÅtırın. Son olarak\n'BaÄlan' tıkladıÄınızda baÅlamak için hazır olmalısınız! EÄer sorun yaÅıyorsanız,\ndaha fazla yardım için 'Tor AÄ Ayarları' sihirbazındaki 'Yardım' düÄmesine\ntıklamayı deneyin."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "Bu iletiyi görüntüler."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "Ãzelliksiz köprüleri talep et."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "IPv6 köprüleri talep et."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "TYPE tarafından eklenebilir bir aktarım talep et."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "BridgeDB'nin ortak bir GnuPG anahtar kopyasını al."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "Bir hata Bildir"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Kaynak Kodu"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "DeÄiÅim günlüÄü"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "Ä°letiÅim"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "Hepsini Seç"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "QR Kodunu Göster"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "Köprü hatlarınız için QR Kodu"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Bu da nesi!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "QR Kodunuzu alınırken bir hata olmuŠgibi görünüyor."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Bu QR Kodu köprü hatlarınızı içeriyor. Köprü hatlarınızı bir mobil cihaza veya diÄer cihazlara kopyalamak için bir QR Tarayıcı kullanın."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Åu anda kullanılabilecek bir köprü yok..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Belki %s geri dönmeyi %s ve farklı bir köprü türü seçmeyi denemelisiniz!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "%s1%s adım"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "%s Tor Tarayıcı'yı %s İndirin"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "Adım %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "%s Köprüleri %s edinin"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "Adım %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Åimdi %s köprüleri Tor Tarayıcı'ya ekleyin %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sB%sana köprüleri ver!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "İleri Seçenekler"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "Hayır"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "hiçbiri"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sE%svet!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "Köprüleri %sA%sl"
diff --git a/lib/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 00b8d59..0000000
--- a/lib/bridgedb/i18n/uk/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,384 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Eugene ghostishev, 2014
-# LinuxChata, 2014-2015
-# Oleksii Golub <sclub2018 at yandex.ua>, 2015
-# Oleksii Golub <sclub2018 at yandex.ua>, 2013
-# ÐндÑÑй ÐандÑÑа <andriykopanytsia at gmail.com>, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-14 12:34+0000\n"
-"Last-Translator: LinuxChata\n"
-"Language-Team: Ukrainian (http://www.transifex.com/projects/p/torproject/language/uk/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: uk\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "Ðи пÑиноÑимо наÑÑ Ð²Ð¸Ð±Ð°ÑеннÑ! ЩоÑÑ Ð¿ÑÑло не Ñак з ÐаÑим запиÑом."
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[Це авÑомаÑиÑне повÑдомленнÑ; бÑÐ´Ñ Ð»Ð°Ñка, не вÑдповÑдайÑе.]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "ÐаÑÑ Ð¼Ð¾ÑÑи:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "Ðи пеÑевиÑили Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ ÑвидкоÑÑÑ. ÐÑÐ´Ñ Ð»Ð°Ñка, ÑповÑлÑнÑÑÑÑÑ! ÐÑнÑмалÑний ÑÐ°Ñ Ð¼Ñж\nлиÑÑами %s годин. ÐÑÑ Ð¿Ð¾Ð´Ð°Ð»ÑÑÑ Ð»Ð¸ÑÑи в Ñей пеÑÑод ÑаÑÑ Ð±ÑдÑÑÑ ÑгноÑÑваÑиÑÑ."
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "Ðоманди: (комбÑнÑваÑи команди вказавÑи кÑлÑка ваÑÑанÑÑв одноÑаÑно)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "ÐаÑкаво пÑоÑимо Ñ BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "У даний ÑÐ°Ñ Ð¿ÑдÑÑимÑÑÑÑÑÑ transport TYPE:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "ÐÑивÑÑ, %s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ÐÑивÑÑ, дÑÑже!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "ÐÑдкÑиÑÑ ÐºÐ»ÑÑÑ"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "Цей лиÑÑ Ð±Ñв згенеÑовано з ÑайдÑгами, ÑдиноÑогами Ñ Ð±Ð»Ð¸ÑкÑÑками\nÐ´Ð»Ñ %s %s о %s."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB може забезпеÑиÑи bridges з декÑлÑкома %sÑипами Pluggable Transports%s,\nÑÐºÑ Ð¼Ð¾Ð¶ÑÑÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ñи пÑиÑ
оваÑи ÑÐ²Ð¾Ñ Ð·Ð²'Ñзки з Tor меÑежеÑ, Ñо ÑÑкладнÑÑ ÑобоÑÑ\nÐ´Ð»Ñ ÑиÑ
, Ñ
Ñо пеÑевÑÑÑÑ ÐÐ°Ñ ÑнÑеÑнеÑ-ÑÑаÑÑк, Ñоб визнаÑиÑи, Ñо Ðи\nвикоÑиÑÑовÑÑÑе Tor. \n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "ÐеÑÐºÑ Ð¼Ð¾ÑÑи з адÑеÑами IPv6, Ñакож доÑÑÑпнÑ, Ñ
оÑа деÑÐºÑ Pluggable\nTransports не ÑÑмÑÑÐ½Ñ Ð· IPv6.\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "ÐÑÑм Ñого, BridgeDB Ð¼Ð°Ñ Ð±Ð°Ð³Ð°Ñо plain-ol'-vanilla bridges %s без\nPluggable Transports %s, ÑкÑ, можливо, не звÑÑаÑÑ Ð³Ð°Ñно, але вони \nможÑÑÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ñи обÑйÑи ÑнÑеÑнеÑ-ÑензÑÑÑ Ð² багаÑÑоÑ
випадкаÑ
.\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "Що Ñаке ÑеÑÑанÑлÑÑÐ¾Ñ ÑÐ¸Ð¿Ñ Ð¼ÑÑÑ?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s ÐоÑÑи %s Ñ Tor Ñеле, ÑÐºÑ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð¶ÑÑÑ Ðам обÑйÑи ÑензÑÑÑ."
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "ÐÐµÐ½Ñ Ð¿Ð¾ÑÑÑбен алÑÑеÑнаÑивний ÑпоÑÑб оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ ÑпиÑÐºÑ Ð¼Ð¾ÑÑÑв!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "Ще один ÑпоÑÑб оÑÑимаÑи bridges - Ñе вÑдпÑавиÑи лиÑÑ Ð½Ð° адÑеÑÑ %s. ÐвеÑнÑÑÑ ÑвагÑ, Ñо\nÐи Ð¿Ð¾Ð²Ð¸Ð½Ð½Ñ Ð½Ð°Ð´ÑÑлаÑи лиÑÑ, викоÑиÑÑовÑÑÑи адÑеÑÑ Ð²Ñд одного з наÑÑÑпниÑ
поÑÑаÑалÑникÑв\nпоÑлÑг елекÑÑÐ¾Ð½Ð½Ð¾Ñ Ð¿Ð¾ÑÑи:\n%s, %s або %s."
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ÐÐ¾Ñ Ð¼Ð¾ÑÑи не пÑаÑÑÑÑÑ! ÐопоможÑÑÑ!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "ЯкÑо ÐÐ°Ñ Tor не пÑаÑÑÑ, Ðи можеÑе вÑдпÑавиÑи нам елекÑÑонного лиÑÑа %s."
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "СпÑобÑйÑе додаÑи ÑкнайбÑлÑÑе ÑнÑоÑмаÑÑÑ Ð¿Ñо ÐÐ°Ñ Ð²Ð¸Ð¿Ð°Ð´Ð¾Ðº, в ÑÐ¾Ð¼Ñ ÑиÑÐ»Ñ ÑпиÑок\nbridges Ñ Pluggable Transports, Ñо Ðи намагалиÑÑ Ð²Ð¸ÐºÐ¾ÑиÑÑовÑваÑи, веÑÑÑÑ Ð±ÑаÑзеÑа Tor,\nÑ Ð±ÑдÑ-ÑÐºÑ Ð¿Ð¾Ð²ÑдомленнÑ, ÑÐºÑ Tor видавав, Ñ Ñ.д."
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "ÐÑнÑÑ Ð´Ð»Ñ ÐаÑого моÑÑÑ:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "ÐÑÑимаÑи Bridges!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "ÐÑÐ´Ñ Ð»Ð°Ñка, обеÑÑÑÑ Ð¿Ð°ÑамеÑÑи ÑÐ¸Ð¿Ñ Ð¼ÑÑÑ:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "Ðам поÑÑÑÐ±Ð½Ñ Ð°Ð´ÑеÑи IPv6?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "Ðам поÑÑÑбен %s?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "ÐÐ°Ñ Ð±ÑаÑÐ·ÐµÑ Ð½Ðµ вÑдобÑÐ°Ð¶Ð°Ñ Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð½Ñ Ð½Ð°Ð»ÐµÐ¶Ð½Ð¸Ð¼ Ñином."
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "ÐведÑÑÑ Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð½Ñ Ñимволи ..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "Як поÑаÑи коÑиÑÑÑваÑиÑÑ ÐаÑими моÑÑами"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "ÐÐ»Ñ Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ bridges Ñ Ð±ÑаÑзеÑÑ Tor, доÑÑимÑйÑеÑÑ ÑнÑÑÑÑкÑÑй %s на ÑÑоÑÑнÑÑ \nзаванÑÐ°Ð¶ÐµÐ½Ð½Ñ Ð±ÑаÑзеÑа Tor %s Ð´Ð»Ñ Ð·Ð°Ð¿ÑÑÐºÑ Ð±ÑаÑзеÑа Tor."
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "Ðоли дÑалог \"ÐалаÑÑÑÐ²Ð°Ð½Ð½Ñ Ð¼ÐµÑÐµÐ¶Ñ Tor\" вÑдкÑиÑÑÑÑÑ, наÑиÑнÑÑÑ \"ÐалаÑÑÑваÑи\" Ñ Ð´Ð¾ÑÑимÑйÑеÑÑ\nпÑдказкам майÑÑÑа, допоки вÑн не запÑоÑиÑÑ:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ÐаÑÑй ÑнÑеÑнеÑ-пÑÐ¾Ð²Ð°Ð¹Ð´ÐµÑ (ISP) блокÑÑ Ð°Ð±Ð¾ ÑензÑÑÑÑ Ð·'ÑднаннÑ\nдо меÑÐµÐ¶Ñ Tor?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "ÐибеÑÑÑÑ \"Так\", а поÑÑм наÑиÑнÑÑÑ \"ÐалÑ\". ÐÐ»Ñ Ð½Ð°Ð»Ð°ÑÑÑÐ²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ
bridges, ÑкопÑÑйÑе Ñ\nвÑÑавÑе лÑнÑÑ bridge Ñ Ð¿Ð¾Ð»Ðµ Ð´Ð»Ñ Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ ÑекÑÑÑ. ÐоÑÑм, наÑиÑнÑÑÑ 'ÐÑдклÑÑаÑиÑÑ', Ñ\nвÑе повинно пÑаÑÑваÑи! ЯкÑо Ñ ÐÐ°Ñ Ð²Ð¸Ð½Ð¸ÐºÐ»Ð¸ пÑоблеми, ÑпÑобÑйÑе наÑиÑнÑÑи \"ÐовÑдка\" \nв майÑÑÑÐ¾Ð²Ñ \"ÐеÑÐµÐ¶ÐµÐ²Ñ Ð¿Ð°ÑамеÑÑи Tor' Ð´Ð»Ñ Ð¾ÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°ÑÐºÐ¾Ð²Ð¾Ñ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ð¸."
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "ÐÑдобÑÐ°Ð¶Ð°Ñ Ñе повÑдомленнÑ."
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ vanilla bridges."
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ IPv6 bridges."
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "ÐÐ°Ð¿Ð¸Ñ Ð½Ð° оÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Pluggable Transport по TYPE."
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "ÐÑÑимаÑи копÑÑ Ð²ÑдкÑиÑого GnuPG клÑÑа Ð´Ð»Ñ BridgeDB."
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "ÐовÑдомиÑи пÑо помилкÑ"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "Ðод"
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "СпиÑок змÑн"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "ÐонÑакÑ"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "ÐибÑаÑи вÑÑ"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "ÐоказаÑи QR-код"
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "QR-код Ð´Ð»Ñ Ð°Ð´ÑÐµÑ ÑеÑÑанÑлÑÑоÑÑв"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "Ðй-ой, spaghettios!"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "ÐдаÑÑÑÑÑ, бÑло Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ¾Ñ Ð¾ÑÑÐ¸Ð¼Ð°Ð½Ð½Ñ Ð²Ð°Ñого QR-код."
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "Це QR-код мÑÑÑиÑÑ Ð°Ð´ÑеÑи ÐаÑиÑ
ÑеÑÑанÑлÑÑоÑÑв. ÐÑдÑканÑйÑе його пÑиÑÑÑоÑм Ð´Ð»Ñ Ð·ÑиÑÑÐ²Ð°Ð½Ð½Ñ QR-кодÑв, Ñоб ÑкопÑÑваÑи адÑеÑи ÐаÑиÑ
ÑеÑÑанÑлÑÑоÑÑв на мобÑлÑÐ½Ñ Ñа ÑнÑÑ Ð¿ÑиÑÑÑоÑ."
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "Рданий ÑÐ°Ñ Ð½ÐµÐ¼Ð°Ñ Ð´Ð¾ÑÑÑпниÑ
моÑÑÑв ..."
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "Ðожливо, Ðам ÑлÑд ÑпÑобÑваÑи %s повеÑнÑÑиÑÑ %s Ñ Ð²Ð¸Ð±ÑаÑи ÑнÑий Ñип моÑÑÑ!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "ÐÑок %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "ÐаванÑажиÑи %s Tor Browser %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "ÐÑок %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "ÐÑÑимайÑе %s моÑÑи %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "ÐÑок %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "Ð¢ÐµÐ¿ÐµÑ %s додай bridges до Tor Browser %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sТ%sÑлÑки дай Ð¼ÐµÐ½Ñ Ð¼Ð¾ÑÑи!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "РозÑиÑÐµÐ½Ñ ÐаÑамеÑÑи"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "ÐÑ"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "жоден"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sТ%sак!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sÐ%sÑÑимаÑи моÑÑи"
diff --git a/lib/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index 11bcc18..0000000
--- a/lib/bridgedb/i18n/zh_CN/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,388 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2015 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# Wu Ming Shi, 2013
-# Wu Ming Shi, 2013
-# Wu Ming Shi, 2013
-# Christopher Meng <cickumqt at gmail.com>, 2012
-# hanl <iamh4n at gmail.com>, 2011
-# Meng3, 2014
-# leungsookfan <leung.sookfan at riseup.net>, 2014
-# Wu Ming Shi, 2014-2015
-# YF <yfdyh000 at gmail.com>, 2014-2015
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2015-02-03 03:24+0000\n"
-"PO-Revision-Date: 2015-03-08 19:30+0000\n"
-"Last-Translator: Wu Ming Shi\n"
-"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/torproject/language/zh_CN/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: zh_CN\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:122
-msgid "Sorry! Something went wrong with your request."
-msgstr "æ±æï¼ä½ çé®ä»¶è¯·æ±åºç°é®é¢ã"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[è¿æ¯ä¸å°èªå¨çæçé®ä»¶ï¼è¯·å¿åå¤ã]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "以ä¸æ¯ä¸ºä½ æä¾çç½æ¡¥ï¼"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "æ¨å·²è¶
åºäºåéé¢ççéå¶ï¼è¯·æ
¢æ
¢æ¥ï¼ä¸¤å°é®ä»¶ä¹é´éè¦æå° %s å°æ¶çé´éãå¨é´éæé´ååºçææé®ä»¶å°è¢«å¿½ç¥ã"
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "å½ä»¤ï¼ï¼ç»å使ç¨å½ä»¤å¯åæ¶æå®å¤ä¸ªé项ï¼"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "欢è¿æ¥å° BridgeDBï¼"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "æ¯æç transport TYPEï¼"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "ä½ å¥½ï¼%sï¼"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "ä½ å¥½ï¼æåï¼"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:100
-msgid "Public Keys"
-msgstr "å
Œ
±å¯å"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "æ¬é®ä»¶æ¯å¯¹ %s çèªå¨åå¤ï¼æ¥æ %s %sã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB è½æä¾ %s å ç§ Pluggable Transports %s ç±»åç½æ¡¥ï¼å¯ç¨äºæ··æ· Tor ç½ç»çè¿æ¥ï¼ä»è让ç½ç»çæ§è
é¾ä»¥å¤æä½ å¨ä½¿ç¨ Torã\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "ä¸äºä½¿ç¨IPv6å°åçbridgesï¼ç½æ¡¥ï¼ä¹è½ä½¿ç¨ï¼ä½æ¯\nPluggable Transports并ä¸å
¼å®¹IPv6ã\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "æ¤å¤ï¼BridgeDB æä¾å¾å¤ %s éPluggable Transports %s çæ®éç½æ¡¥ã\nè½ç¶å¬èµ·æ¥ä¸å¤é
·ï¼ä½æ¯è¿äºæ®éç½æ¡¥ä¾ç¶å¯ä»¥å¨å¾å¤æ
åµä¸å¸®å©ç»è¿å®¡æ¥ã\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "ä»ä¹æ¯ç½æ¡¥ï¼"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s Bridges %s æ¯å¸®å©ä½ ç»è¿å®¡æ¥æå°éç Tor relayï¼ä¸ç»§ç¹ï¼ã"
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "éè¦ä½¿ç¨å
¶ä»è·åæ¹å¼è·åç½æ¡¥ï¼"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "å¦ä¸ç§è·åç½æ¡¥çæ¹å¼æ¯åéçµåé®ä»¶è³ %sã注æï¼å¿
须使ç¨çµåé®ç®±åé请æ±ï¼%sã%s æ %sã"
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "ç¨ç½æ¡¥ä¹æ æ³è¿æ¥ï¼éè¦å¸®å©ï¼"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "å¦æ Tor æ æ³æ£å¸¸è¿è¡ï¼è¯·åé®ä»¶è³ %sã"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "请尽éæè¿°ä½ çæ
åµï¼å
æ¬ä½ å°è¯è¿çbridgesåPluggable Transportsï¼ä½ çToræµè§å¨çæ¬ï¼è¿æä»»ä½Toræ¾ç¤ºè¿çä¿¡æ¯ççã"
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "以ä¸æ¯ä¸ºä½ æä¾çç½æ¡¥ï¼"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "è·å¾ç½æ¡¥ï¼"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "请éæ©ç±»åç±»åã"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "æ¯å¦éè¦IPv6å°åï¼"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "æ¯å¦éè¦ %sï¼"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "æµè§å¨æ æ³æ£ç¡®æ¾ç¤ºå¾çã"
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "请è¾å
¥ä¸å¾ä¸çå符ï¼ä¸åºå大å°åï¼..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "å¦ä½ä½¿ç¨ç½æ¡¥"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "å¦éå¨ Tor æµè§å¨ä¸æ·»å ç½æ¡¥ï¼è¯·å
æ ¹æ® %s Tor æµè§å¨ä¸è½½é¡µé¢ %s ç说ææ¥å¯å¨Tor æµè§å¨ã"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "å½âTor ç½ç»è®¾ç½®â 对è¯æ¡åºç°æ¶ï¼ç¹å»âé
ç½®â ï¼ç¶åæ ¹æ®å导æä½ï¼ç´å°çå°ä¸é¢çé®é¢ï¼"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "ç½ç»æä¾åï¼ISPï¼æå
¶ä»äººæ¯å¦å¯¹ Tor ç½ç»è¿æ¥è¿è¡å®¡æ¥æå°éï¼"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "éæ©âæ¯âï¼ç¶åç¹å»âä¸ä¸æ¥âãå¦ä¸æ·»å æ°çç½æ¡¥ï¼è¯·å°ç½æ¡¥å°åè¡éè¿å¤å¶ç²è´´çæ¹å¼è¾å
¥å°ææ¬è¾å
¥æ¡ãæåï¼ç¹å»âè¿æ¥âå°±å¯ä»¥è¿æ¥è³ Tor ç½ç»ãå¦æé®é¢éè¦å¸®å©ï¼è¯·ç¹å»âTor ç½ç»è®¾ç½®âå导çªå£ä¸çâ帮å©âæé®ã"
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "æ¾ç¤ºè¿æ¡ä¿¡æ¯ã"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "请æ±æ®éç½æ¡¥ã"
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "请æ±IPv6ç½ç»ã"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "æ ¹æ®TYPE 请æ±Pluggable Transportã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "è·å BridgeDB ç GnuPG å
Œ
±å¯åã"
-
-#: lib/bridgedb/templates/base.html:89
-msgid "Report a Bug"
-msgstr "æ¥å Bug"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Source Code"
-msgstr "æºä»£ç "
-
-#: lib/bridgedb/templates/base.html:95
-msgid "Changelog"
-msgstr "æ´æ¹æ¥å¿"
-
-#: lib/bridgedb/templates/base.html:98
-msgid "Contact"
-msgstr "èç³»æ¹å¼"
-
-#: lib/bridgedb/templates/bridges.html:81
-msgid "Select All"
-msgstr "éæ©å
¨é¨"
-
-#: lib/bridgedb/templates/bridges.html:87
-msgid "Show QRCode"
-msgstr "æ¾ç¤ºäºç»´ç "
-
-#: lib/bridgedb/templates/bridges.html:100
-msgid "QRCode for your bridge lines"
-msgstr "ç½æ¡¥äºç»´ç "
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:115
-#: lib/bridgedb/templates/bridges.html:175
-msgid "Uh oh, spaghettios!"
-msgstr "åååï¼"
-
-#: lib/bridgedb/templates/bridges.html:116
-msgid "It seems there was an error getting your QRCode."
-msgstr "è·åäºç»´ç æ¶åºéã"
-
-#: lib/bridgedb/templates/bridges.html:121
-msgid ""
-"This QRCode contains your bridge lines. Scan it with a QRCode reader to copy"
-" your bridge lines onto mobile and other devices."
-msgstr "äºç»´ç å
å«ç½æ¡¥ä¿¡æ¯ãå©ç¨äºç»´ç æ«æç¨åºï¼å¯å°ç¸åºçç½æ¡¥ä¿¡æ¯å¤å¶å°ææºæå
¶ä»è®¾å¤ã"
-
-#: lib/bridgedb/templates/bridges.html:181
-msgid "There currently aren't any bridges available..."
-msgstr "ç°å¨æ²¡æå¯ç¨çç½æ¡¥ã"
-
-#: lib/bridgedb/templates/bridges.html:182
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr "è¯è¯ %såé%så°åä¸é¡µé¢ï¼ç¶åéæ©å
¶ä»ç±»åçç½æ¡¥ã"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "第 %s 1 %s æ¥"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "ä¸è½½ %s Tor æµè§å¨ %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "第 %s 2 %s æ¥"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "è·å %s bridges %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "第 %s 3 %s æ¥"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "å¦ä½ %s å¨ Tor æµè§å¨æ·»å ç½æ¡¥%s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr " ç´æ¥ç»æç½æ¡¥(%sJ%s)ï¼ "
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "é«çº§é项"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "å¦"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "æ "
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:127
-#, python-format
-msgid "%sY%ses!"
-msgstr "æ¯(%sY%s)ï¼"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:151
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "è·åç½æ¡¥(%sG%s)"
diff --git a/lib/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po b/lib/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po
deleted file mode 100644
index acda44c..0000000
--- a/lib/bridgedb/i18n/zh_TW/LC_MESSAGES/bridgedb.po
+++ /dev/null
@@ -1,358 +0,0 @@
-# Translations template for BridgeDB.
-# Copyright (C) 2014 'The Tor Project, Inc.'
-# This file is distributed under the same license as the BridgeDB project.
-#
-# Translators:
-# danfong <danfong.hsieh at gmail.com>, 2014
-# LNDDYL, 2014
-msgid ""
-msgstr ""
-"Project-Id-Version: The Tor Project\n"
-"Report-Msgid-Bugs-To: 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n"
-"POT-Creation-Date: 2014-07-26 02:11+0000\n"
-"PO-Revision-Date: 2014-11-24 09:10+0000\n"
-"Last-Translator: LNDDYL\n"
-"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/torproject/language/zh_TW/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-"Language: zh_TW\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. TRANSLATORS: Please DO NOT translate the following words and/or phrases in
-#. any string (regardless of capitalization and/or punctuation):
-#. "BridgeDB"
-#. "pluggable transport"
-#. "pluggable transports"
-#. "obfs2"
-#. "obfs3"
-#. "scramblesuit"
-#. "fteproxy"
-#. "Tor"
-#. "Tor Browser"
-#: lib/bridgedb/HTTPServer.py:121
-msgid "Sorry! Something went wrong with your request."
-msgstr "æ±æ!æ¨çè«æ±ç¼çé¯èª¤ã"
-
-#: lib/bridgedb/strings.py:18
-msgid "[This is an automated message; please do not reply.]"
-msgstr "[éæ¯ä¸åèªååè¦éµä»¶;è«ä¸è¦åè¦ã]"
-
-#: lib/bridgedb/strings.py:20
-msgid "Here are your bridges:"
-msgstr "é裡æ¯æ¨çæ©æ¥:"
-
-#: lib/bridgedb/strings.py:22
-#, python-format
-msgid ""
-"You have exceeded the rate limit. Please slow down! The minimum time between\n"
-"emails is %s hours. All further emails during this time period will be ignored."
-msgstr "æ¨å·²è¶
éé度éå¶ãè«æ¸æ
¢é度!é»åéµä»¶ä¹éçæçæéçº %s åå°æãå¨é段æéå
§ææå
¶ä»çéµä»¶å°è¢«å¿½ç¥ã"
-
-#: lib/bridgedb/strings.py:25
-msgid ""
-"COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"
-msgstr "å½ä»¤:(çµåå½ä»¤å¯ä»¥åææå®å¤åé¸é
)"
-
-#. TRANSLATORS: Please DO NOT translate the word "BridgeDB".
-#: lib/bridgedb/strings.py:28
-msgid "Welcome to BridgeDB!"
-msgstr "æ¡è¿ä½¿ç¨ BridgeDB!"
-
-#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
-#: lib/bridgedb/strings.py:30
-msgid "Currently supported transport TYPEs:"
-msgstr "ç®åæ¯æ´çå³è¼¸é¡å:"
-
-#: lib/bridgedb/strings.py:31
-#, python-format
-msgid "Hey, %s!"
-msgstr "å¿ï¼%s!"
-
-#: lib/bridgedb/strings.py:32
-msgid "Hello, friend!"
-msgstr "æåï¼æ¨å¥½!"
-
-#: lib/bridgedb/strings.py:33 lib/bridgedb/templates/base.html:101
-msgid "Public Keys"
-msgstr "å
¬ééé°"
-
-#. TRANSLATORS: This string will end up saying something like:
-#. "This email was generated with rainbows, unicorns, and sparkles
-#. for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
-#: lib/bridgedb/strings.py:37
-#, python-format
-msgid ""
-"This email was generated with rainbows, unicorns, and sparkles\n"
-"for %s on %s at %s."
-msgstr "éå°é»åéµä»¶ä½¿ç¨ rainbowsãunicorns å sparkles ç¢çæ¼\n %s å¨ %s å¨ %sã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#. TRANSLATORS: Please DO NOT translate "Tor Network".
-#: lib/bridgedb/strings.py:47
-#, python-format
-msgid ""
-"BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n"
-"which can help obfuscate your connections to the Tor Network, making it more\n"
-"difficult for anyone watching your internet traffic to determine that you are\n"
-"using Tor.\n"
-"\n"
-msgstr "BridgeDB å¯ä»¥ä½¿ç¨å¹¾ç¨® Pluggable Transports%s ç %stypes ä¾æä¾æ©æ¥ï¼\nå®å¯ä»¥å¹«å©æ¨æ··æ·é£æ¥å° Tor Networkï¼ä½¿å
¶ä»»ä½äººèç±ç£çæ¨ç網路æµ\néä¾ç¢ºå®æ¨æ£ä½¿ç¨ Tor ææ´å å°é£\nã\n"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#: lib/bridgedb/strings.py:54
-msgid ""
-"Some bridges with IPv6 addresses are also available, though some Pluggable\n"
-"Transports aren't IPv6 compatible.\n"
-"\n"
-msgstr "æäºå
·æ IPv6 ä½åçæ©æ¥ä¹å¯ç¨ï¼éç¶æäº Pluggable\nTransports è IPv6 ä¸ç¸å®¹ã\n\n"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
-#. regular, or unexciting". Like vanilla ice cream. It refers to bridges
-#. which do not have Pluggable Transports, and only speak the regular,
-#. boring Tor protocol. Translate it as you see fit. Have fun with it.
-#: lib/bridgedb/strings.py:63
-#, python-format
-msgid ""
-"Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any\n"
-"Pluggable Transports %s which maybe doesn't sound as cool, but they can still\n"
-"help to circumvent internet censorship in many cases.\n"
-"\n"
-msgstr "æ¤å¤ï¼BridgeDB ææ®éçæ©æ¥ %sï¼æ²æä»»ä½\nPluggable Transports %sï¼éä¹è¨±è½èµ·ä¾ä¸é
·ï¼ä½å®\nä»æå©æ¼è¦é¿å¨è¨±å¤æ
æ³ä¸ç網路審æ¥ã\n"
-
-#: lib/bridgedb/strings.py:76
-msgid "What are bridges?"
-msgstr "ä»éº¼æ¯æ©æ¥?"
-
-#: lib/bridgedb/strings.py:77
-#, python-format
-msgid "%s Bridges %s are Tor relays that help you circumvent censorship."
-msgstr "%s æ©æ¥ %s æ¯ Tor ä¸ç¹¼ï¼å¯å¹«æ¨è¦é¿å¯©æ¥ã"
-
-#: lib/bridgedb/strings.py:82
-msgid "I need an alternative way of getting bridges!"
-msgstr "æéè¦ä½¿ç¨å
¶ä»æ¹å¼åå¾æ©æ¥!"
-
-#: lib/bridgedb/strings.py:83
-#, python-format
-msgid ""
-"Another way to get bridges is to send an email to %s. Please note that you must\n"
-"send the email using an address from one of the following email providers:\n"
-"%s, %s or %s."
-msgstr "å¦ä¸ç¨®åå¾æ©æ¥çæ¹å¼çºç¼éé»åéµä»¶è³ %sãè«æ³¨æï¼æ¨å¿
é \n使ç¨å¾ä¸åé»åéµä»¶æä¾å» åä¹ä¸çä½åä¾ç¼éé»åéµä»¶:\n%s, %s æ %sã"
-
-#: lib/bridgedb/strings.py:90
-msgid "My bridges don't work! I need help!"
-msgstr "æ©æ¥ç¡æ³æ£å¸¸å·è¡ï¼æéè¦å¹«å©!"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:92
-#, python-format
-msgid "If your Tor doesn't work, you should email %s."
-msgstr "å¦ææ¨ç Tor ç¡æ³éä½ï¼æ¨æ該ç¼éé»åéµä»¶çµ¦ %sã"
-
-#. TRANSLATORS: Please DO NOT translate "Pluggable Transports".
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:96
-msgid ""
-"Try including as much info about your case as you can, including the list of\n"
-"bridges and Pluggable Transports you tried to use, your Tor Browser version,\n"
-"and any messages which Tor gave out, etc."
-msgstr "試èå
å«æéæ¨æ
æ³çç¡å¯è½å¤çè³è¨ï¼å
å«æ¨å試使ç¨éç \næ©æ¥å Pluggable Transports æ¸
å®ï¼æ¨ç Tor ç覽å¨çæ¬ï¼ \n以å Tor 給åºçä»»ä½è¨æ¯ã"
-
-#: lib/bridgedb/strings.py:103
-msgid "Here are your bridge lines:"
-msgstr "é裡æ¯æ¨çæ©æ¥ç·è·¯:"
-
-#: lib/bridgedb/strings.py:104
-msgid "Get Bridges!"
-msgstr "åå¾æ©æ¥!"
-
-#: lib/bridgedb/strings.py:108
-msgid "Please select options for bridge type:"
-msgstr "è«é¸ææ©æ¥é¡åçé¸é
:"
-
-#: lib/bridgedb/strings.py:109
-msgid "Do you need IPv6 addresses?"
-msgstr "æ¨éè¦ IPv6 ä½åå?"
-
-#: lib/bridgedb/strings.py:110
-#, python-format
-msgid "Do you need a %s?"
-msgstr "æ¨éè¦ %s å?"
-
-#: lib/bridgedb/strings.py:114
-msgid "Your browser is not displaying images properly."
-msgstr "æ¨çç覽å¨ä¸è½æ£ç¢ºé¡¯ç¤ºååã"
-
-#: lib/bridgedb/strings.py:115
-msgid "Enter the characters from the image above..."
-msgstr "å¾ä¸é¢çååä¸è¼¸å
¥åå
..."
-
-#: lib/bridgedb/strings.py:119
-msgid "How to start using your bridges"
-msgstr "å¦ä½éå§ä½¿ç¨æ¨çæ©æ¥"
-
-#. TRANSLATORS: Please DO NOT translate "Tor Browser".
-#: lib/bridgedb/strings.py:121
-#, python-format
-msgid ""
-"To enter bridges into Tor Browser, follow the instructions on the %s Tor\n"
-"Browser download page %s to start Tor Browser."
-msgstr "å°æ©æ¥è¼¸å
¥å° Tor ç覽å¨ä¸ï¼è«æç
§ %s ä¸ç說æï¼Tor\nç覽å¨ä¸è¼é é¢ %s ä¾åå Tor ç覽å¨ã"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:125
-msgid ""
-"When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow\n"
-"the wizard until it asks:"
-msgstr "ç¶ãTor 網路è¨å®ãå°è©±å½åºæï¼æä¸ä¸ãè¨å®ãï¼ç¶å¾æç
§ç²¾éï¼ç´å°å®è¦æ±:"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:129
-msgid ""
-"Does your Internet Service Provider (ISP) block or otherwise censor connections\n"
-"to the Tor network?"
-msgstr "æ¨ç網é網路æåä¾æè
(ISP)é»ææå¯©æ¥ Tor 網路é£ç·?"
-
-#. TRANSLATORS: Please DO NOT translate "Tor".
-#: lib/bridgedb/strings.py:133
-msgid ""
-"Select 'Yes' and then click 'Next'. To configure your new bridges, copy and\n"
-"paste the bridge lines into the text input box. Finally, click 'Connect', and\n"
-"you should be good to go! If you experience trouble, try clicking the 'Help'\n"
-"button in the 'Tor Network Settings' wizard for further assistance."
-msgstr "é¸æãæ¯ãï¼ç¶å¾æä¸ä¸ãä¸ä¸æ¥ããè¦è¨å®æ¨çæ°æ©æ¥ï¼è¤è£½å\nå°æ©æ¥ç·è·¯è²¼ä¸å°æå輸å
¥æ¹å¡ä¸ãæå¾ï¼æä¸ä¸ãé£æ¥ã就好ã\nå¦ææ¨éå°éº»ç
©ï¼è«å試æä¸ä¸ãTor 網路è¨å®ãç²¾éä¸çã説æã\næéå°æ±é²ä¸æ¥çåå© ã"
-
-#: lib/bridgedb/strings.py:141
-msgid "Displays this message."
-msgstr "顯示æ¤è¨æ¯ã"
-
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-#. same non-Pluggable Transport bridges described above as being
-#. "plain-ol'-vanilla" bridges.
-#: lib/bridgedb/strings.py:145
-msgid "Request vanilla bridges."
-msgstr "è«æ± vanilla æ©æ¥ã"
-
-#: lib/bridgedb/strings.py:146
-msgid "Request IPv6 bridges."
-msgstr "è«æ± IPv6 æ©æ¥ã"
-
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE".
-#: lib/bridgedb/strings.py:148
-msgid "Request a Pluggable Transport by TYPE."
-msgstr "æç
§ TYPE è«æ± Pluggable Transportã"
-
-#. TRANSLATORS: Please DO NOT translate "BridgeDB".
-#. TRANSLATORS: Please DO NOT translate "GnuPG".
-#: lib/bridgedb/strings.py:151
-msgid "Get a copy of BridgeDB's public GnuPG key."
-msgstr "åå¾ BridgeDB çå
Œ
± GnuPG éé°å¯æ¬ã"
-
-#: lib/bridgedb/templates/base.html:92
-msgid "Report a Bug"
-msgstr "åå ±é¯èª¤"
-
-#: lib/bridgedb/templates/base.html:94
-msgid "Source Code"
-msgstr "åå§ç¨å¼ç¢¼"
-
-#: lib/bridgedb/templates/base.html:97
-msgid "Changelog"
-msgstr "è®æ´è¨é"
-
-#: lib/bridgedb/templates/base.html:99
-msgid "Contact"
-msgstr "è¯çµ¡è³è¨"
-
-#. TRANSLATORS: Please translate this into some silly way to say
-#. "There was a problem!" in your language. For example,
-#. for Italian, you might translate this into "Mama mia!",
-#. or for French: "Sacrebleu!". :)
-#: lib/bridgedb/templates/bridges.html:66
-msgid "Uh oh, spaghettios!"
-msgstr "æåé¡!"
-
-#: lib/bridgedb/templates/bridges.html:72
-msgid "There currently aren't any bridges available..."
-msgstr "ç®åæ²æä»»ä½æ©æ¥å¯ç¨..."
-
-#: lib/bridgedb/templates/bridges.html:73
-#, python-format
-msgid ""
-" Perhaps you should try %s going back %s and choosing a different bridge "
-"type!"
-msgstr " ä¹è¨±æ¨æå試 %s åå° %sï¼ç¶å¾é¸æä¸åçæ©æ¥é¡å!"
-
-#: lib/bridgedb/templates/index.html:11
-#, python-format
-msgid "Step %s1%s"
-msgstr "æ¥é© %s1%s"
-
-#: lib/bridgedb/templates/index.html:13
-#, python-format
-msgid "Download %s Tor Browser %s"
-msgstr "ä¸è¼ %s Tor çè¦½å¨ %s"
-
-#: lib/bridgedb/templates/index.html:25
-#, python-format
-msgid "Step %s2%s"
-msgstr "æ¥é© %s2%s"
-
-#: lib/bridgedb/templates/index.html:27
-#, python-format
-msgid "Get %s bridges %s"
-msgstr "åå¾ %s æ©æ¥ %s"
-
-#: lib/bridgedb/templates/index.html:36
-#, python-format
-msgid "Step %s3%s"
-msgstr "æ¥é© %s3%s"
-
-#: lib/bridgedb/templates/index.html:38
-#, python-format
-msgid "Now %s add the bridges to Tor Browser %s"
-msgstr "ç¾å¨ %s å°æ©æ¥å å
¥å° Tor çè¦½å¨ %s"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. (These are used to insert HTML5 underlining tags, to mark accesskeys
-#. for disabled users.)
-#: lib/bridgedb/templates/options.html:38
-#, python-format
-msgid "%sJ%sust give me bridges!"
-msgstr "%sJ%sust 給ææ©æ¥!"
-
-#: lib/bridgedb/templates/options.html:52
-msgid "Advanced Options"
-msgstr "é²éé¸é
"
-
-#: lib/bridgedb/templates/options.html:88
-msgid "No"
-msgstr "å¦"
-
-#: lib/bridgedb/templates/options.html:89
-msgid "none"
-msgstr "ç¡"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Translate "Yes!" as in "Yes! I do need IPv6 addresses."
-#: lib/bridgedb/templates/options.html:130
-#, python-format
-msgid "%sY%ses!"
-msgstr "%sY%ses!"
-
-#. TRANSLATORS: Please make sure the '%s' surrounding single letters at the
-#. beginning of words are present in your final translation. Thanks!
-#. TRANSLATORS: Please do NOT translate the word "bridge"!
-#: lib/bridgedb/templates/options.html:154
-#, python-format
-msgid "%sG%set Bridges"
-msgstr "%sG%set Bridges"
diff --git a/lib/bridgedb/interfaces.py b/lib/bridgedb/interfaces.py
deleted file mode 100644
index 89ddb12..0000000
--- a/lib/bridgedb/interfaces.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_interfaces ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-"""All available ``zope.interface``s in BridgeDB."""
-
-from zope.interface import Interface
-from zope.interface import Attribute
-from zope.interface import implementer
-
-
-class IName(Interface):
- """An interface specification for a named object."""
-
- name = Attribute("A string which identifies this object.")
-
-
- at implementer(IName)
-class Named(object):
- """A named object"""
-
- #: The characters used to join child Named object's names with our name.
- separator = ' '
-
- def __init__(self):
- self._name = str()
-
- @property
- def name(self):
- """Get the name of this object.
-
- :rtype: str
- :returns: A string which identifies this object.
- """
- return self._name
-
- @name.setter
- def name(self, name):
- """Set a **name** for identifying this object.
-
- This is used to identify the object in log messages; the **name**
- doesn't necessarily need to be unique. Other :class:`Named` objects
- which are properties of a :class:`Named` object may inherit their
- parents' **name**s.
-
- >>> from bridgedb.distribute import Named
- >>> named = Named()
- >>> named.name = 'Excellent Super-Awesome Thing'
- >>> named.name
- 'Excellent Super-Awesome Thing'
-
- :param str name: A name for this object.
- """
- self._name = name
-
- for attr in self.__dict__.values():
- if IName.providedBy(attr):
- attr.name = self.separator.join([name, attr.name])
diff --git a/lib/bridgedb/parse/__init__.py b/lib/bridgedb/parse/__init__.py
deleted file mode 100644
index 0f3f20d..0000000
--- a/lib/bridgedb/parse/__init__.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013 Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-'''Package containing modules for parsing data.
-
-.. py:module:: bridgedb.parse
- :synopsis: Package containing modules for parsing data.
-'''
-
-from __future__ import absolute_import
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import binascii
-
-
-class InvalidBase64(ValueError):
- """Raised if parsing or decoding cannot continue due to invalid base64."""
-
-
-def padBase64(b64string):
- """Re-add any stripped equals sign character padding to a b64 string.
-
- :param string b64string: A base64-encoded string which might have had its
- trailing equals sign (``=``) padding removed.
- :raises ValueError: if there was any error while manipulating the string.
- :returns: A properly-padded (according to the base64 spec: :rfc:`4648`)
- string.
- """
- addchars = 0
- try:
- b64string = b64string.strip()
- remainder = len(b64string) % 4
- if 2 <= remainder <= 3:
- addchars = 4 - remainder
- except AttributeError as error:
- raise ValueError(error)
- else:
- if not addchars:
- raise ValueError("Invalid base64-encoded string: %r" % b64string)
- b64string += '=' * addchars
-
- return b64string
-
-def parseUnpaddedBase64(field):
- """Parse an unpadded, base64-encoded field.
-
- The **field** will be re-padded, if need be, and then base64 decoded.
-
- :param str field: Should be some base64-encoded thing, with any trailing
- ``=``-characters removed.
- :raises InvalidBase64: if there is an error in either unpadding or decoding
- **field**.
- :rtype: str
- :returns: The base64-decoded **field**.
- """
- if field.endswith('='):
- raise InvalidBase64("Unpadded, base64-encoded networkstatus field "\
- "must not end with '=': %r" % field)
-
- try:
- paddedField = padBase64(field) # Add the trailing equals sign back in
- except ValueError as error:
- raise InvalidBase64(error)
-
- debasedField = binascii.a2b_base64(paddedField)
- if not debasedField:
- raise InvalidBase64("Base64-encoded networkstatus field %r is invalid!"
- % field)
-
- return debasedField
diff --git a/lib/bridgedb/parse/addr.py b/lib/bridgedb/parse/addr.py
deleted file mode 100644
index cd512d1..0000000
--- a/lib/bridgedb/parse/addr.py
+++ /dev/null
@@ -1,589 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013 Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Utilities for parsing IP and email addresses.
-
-.. py:module:: bridgedb.parse.addr
- :synopsis: Parsers for finding and validating IP addresses, email
- addresses, and port ranges.
-
-
-bridgedb.parse.addr
-===================
-
-::
-
- parse.addr
- | |_ extractEmailAddress() - Validate a :rfc:2822 email address.
- | |_ isIPAddress() - Check if an arbitrary string is an IP address.
- | |_ isIPv4() - Check if an arbitrary string is an IPv4 address.
- | |_ isIPv6() - Check if an arbitrary string is an IPv6 address.
- | \_ isValidIP() - Check that an IP address is valid.
- |
- |_ PortList - A container class for validated port ranges.
-
-..
-
-
-How address validity is determined
-----------------------------------
-
-The following terms define addresses which are **not** valid. All other
-addresses are taken to be valid.
-
-
-Private IP Address Ranges
-^^^^^^^^^^^^^^^^^^^^^^^^^
-.. glossary::
-
- Private Address
- These address ranges are reserved by IANA for private intranets, and not
- routable to the Internet::
- 10.0.0.0 - 10.255.255.255 (10.0.0.0/8)
- 172.16.0.0 - 172.31.255.255 (172.16.0.0/12)
- 192.168.0.0 - 192.168.255.255 (192.168.0.0/16)
- For additional information, see :rfc:`1918`.
-
-
-Reserved and Special Use Addresses
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. glossary::
-
- Unspecified Address
- Default Route
- Current network (only valid as source address). See :rfc:`1122`. An
- **Unspecified Address** in the context of firewalls means "all addresses
- of the local machine". In a routing context, it is usually termed the
- **Default Route**, and it means the default route (to "the rest of" the
- internet). See :rfc:`1700`.
- For example::
- 0.0.0.0/8
- ::/128
-
- Loopback Address
- Reserved for loopback and IPC on the localhost. See :rfc:`1122`.
- Example::
- 127.0.0.0
-
- Localhost Address
- Loopback IP addresses (refers to self). See :rfc:`5735`.
- Examples include::
- 127.0.0.1 - 127.255.255.254 (127.0.0.0/8)
- ::1
-
- Link-Local Address
- These are the link-local blocks, used for communication between hosts on
- a single link. See :rfc:`3927`.
- Examples::
- 169.254.0.0/16
- fe80::/64
-
- Multicast Address
- Reserved for multicast addresses. See :rfc:`3171`.
- For example::
- 224.0.0.0 - 239.255.255.255 (224.0.0.0/4)
-
- Private Address
- Reserved for private networks. See :rfc:`1918`.
- Some examples include::
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
-
- Reserved Address
- Reserved (former Class E network). See :rfc:`1700`, :rfc:`3232`, and
- :rfc:`5735`. The one exception to this rule is the :term:`Limited
- Broadcast Address`, ``255.255.255.255`` for which packets at the IP
- layer are not forwarded to the public internet. For example::
- 240.0.0.0 - 255.255.255.255 (240.0.0.0/4)
-
- Limited Broadcast Address
- Limited broadcast address (limited to all other nodes on the LAN). See
- :rfc:`919`. For IPv4, ``255`` in any part of the IP is reserved for
- broadcast addressing to the local LAN, e.g.::
- 255.255.255.255
-
-
-.. warning:: The :mod:`ipaddr` module (as of version 2.1.10) does not
- understand the following reserved_ addresses:
-.. _reserved: https://tools.ietf.org/html/rfc5735#page-4
-
-.. glossary::
-
- Reserved Address (Protocol Assignments)
- Reserved for IETF protocol assignments. See :rfc:`5735`.
- Example::
- 192.0.0.0/24
-
- Reserved Address (6to4 Relay Anycast)
- IPv6 to IPv4 relay. See :rfc:`3068`.
- Example::
- 192.88.99.0/24
-
- Reserved Address (Network Benchmark)
- Network benchmark tests. See :rfc:`2544`.
- Example::
- 198.18.0.0/15
-
- Reserved Address (TEST-NET-1)
- Reserved for use in documentation and example code. It is often used in
- conjunction with domain names ``example.com`` or ``example.net`` in
- vendor and protocol documentation. See :rfc:`1166`.
- For example::
- 192.0.2.0/24
-
- Reserved Address (TEST-NET-2)
- TEST-NET-2. See :rfc:`5737`.
- Example::
- 198.51.100.0/24
-
- Reserved Address (TEST-NET-3)
- TEST-NET-3. See :rfc:`5737`.
- Example::
- 203.0.113.0/24
-
- Shared Address Space
- See :rfc:`6598`.
- Example::
- 100.64.0.0/10
-
- Site-Local Address
- Unique Local Address
- Similar uses to :term:`Limited Broadcast Address`. For IPv6, everything
- becomes convoluted_ and complicated_, and then redefined_. See
- :rfc:`4193`, :rfc:`3879`, and :rfc:`3513`. The
- :meth:`ipaddr.IPAddress.is_site_local` method *only* checks to see if
- the address is a **Unique Local Address** vis-á-vis :rfc:`3513` §2.5.6,
- e.g.::
- ff00::0/8
- fec0::/10
-
-..
-
-.. _convoluted: https://en.wikipedia.org/wiki/IPv6_address#Multicast_addresses
-.. _complicated: https://en.wikipedia.org/wiki/IPv6_address#IPv6_address_scopes
-.. _redefined: https://en.wikipedia.org/wiki/Unique_local_address
-"""
-
-from __future__ import absolute_import
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import logging
-import re
-
-import ipaddr
-
-
-#: These are the special characters which RFC2822 allows within email addresses:
-#: ASPECIAL = '!#$%&*+-/=?^_`{|}~' + "\\\'"
-#: â¦But these are the only ones we're confident that we can handle correctly:
-#: ASPECIAL = '-_+/=_~'
-ASPECIAL = '-_+/=_~'
-ACHAR = r'[\w%s]' % "".join("\\%s" % c for c in ASPECIAL)
-DOTATOM = r'%s+(?:\.%s+)*' % (ACHAR, ACHAR)
-DOMAIN = r'\w+(?:\.\w+)*'
-ADDRSPEC = r'(%s)\@(%s)' % (DOTATOM, DOMAIN)
-# A compiled regexp which matches on any type and ammount of whitespace:
-SPACE_PAT = re.compile(r'\s+')
-# A compiled regexp which matches RFC2822 email address strings:
-ADDRSPEC_PAT = re.compile(ADDRSPEC)
-
-
-class BadEmail(Exception):
- """Exception raised when we get a bad email address."""
- def __init__(self, msg, email):
- Exception.__init__(self, msg)
- self.email = email
-
-class InvalidPort(ValueError):
- """Raised when a given port number is invalid."""
-
-class UnsupportedDomain(ValueError):
- """Raised when we get an email address from an unsupported domain."""
-
-
-def canonicalizeEmailDomain(domain, domainmap):
- """Decide if an email was sent from a permitted domain.
-
- :param str domain: The domain portion of an email address to validate. It
- will be checked that it is one of the domains allowed to email
- requests for bridges to the
- :class:`~bridgedb.email.distributor.EmailDistributor`.
- :param dict domainmap: A map of permitted alternate domains (in lowercase)
- to their canonical domain names (in lowercase). This can be configured
- with the ``EMAIL_DOMAIN_MAP`` option in ``bridgedb.conf``, for
- example::
- EMAIL_DOMAIN_MAP = {'mail.google.com': 'gmail.com',
- 'googlemail.com': 'gmail.com'}
-
- :raises UnsupportedDomain: if the domain portion of the email address is
- not within the map of alternate to canonical allowed domain names.
- :rtype: str
- :returns: The canonical domain name for the email address.
- """
- permitted = None
-
- try:
- permitted = domainmap.get(domain)
- except AttributeError:
- logging.debug("Got non-dict for 'domainmap' parameter: %r" % domainmap)
-
- if not permitted:
- raise UnsupportedDomain("Domain not permitted: %s" % domain)
-
- return permitted
-
-def extractEmailAddress(emailaddr):
- """Given an email address, obtained for example, via a ``From:`` or
- ``Sender:`` email header, try to extract and parse (according to
- :rfc:`2822`) the local and domain portions.
-
- We only allow the following form::
-
- LOCAL_PART := DOTATOM
- DOMAIN := DOTATOM
- ADDRSPEC := LOCAL_PART "@" DOMAIN
-
- In particular, we are disallowing: obs-local-part, obs-domain, comment,
- and obs-FWS. Other forms exist, but none of the incoming services we
- recognize support them.
-
- :param emailaddr: An email address to validate.
- :raises BadEmail: if the **emailaddr** couldn't be validated or parsed.
- :returns: A tuple of the validated email address, containing the mail
- local part and the domain::
- (LOCAL_PART, DOMAIN)
- """
- orig = emailaddr
-
- try:
- addr = SPACE_PAT.sub(' ', emailaddr).strip()
- except TypeError as error:
- logging.debug(error)
- raise BadEmail("Can't extract address from object type %r!"
- % type(orig), orig)
-
- # Only works on usual-form addresses; raises BadEmail on weird
- # address form. That's okay, since we'll only get those when
- # people are trying to fool us.
- if '<' in addr:
- # Take the _last_ index of <, so that we don't need to bother
- # with quoting tricks.
- idx = addr.rindex('<')
- addr = addr[idx:]
- m = re.search(r'<([^>]*)>', addr)
- if m is None:
- raise BadEmail("Couldn't extract address spec", orig)
- addr = m.group(1)
-
- # At this point, addr holds a putative addr-spec.
- addr = addr.replace(" ", "")
- m = ADDRSPEC_PAT.match(addr)
- if not m:
- raise BadEmail("Bad address spec format", orig)
-
- localpart, domain = m.groups()
- return localpart, domain
-
-def isIPAddress(ip, compressed=True):
- """Check if an arbitrary string is an IP address, and that it's valid.
-
- :type ip: basestring or int
- :param ip: The IP address to check.
- :param boolean compressed: If True, return a string representing the
- compressed form of the address. Otherwise, return an
- :class:`ipaddr.IPAddress` instance.
- :rtype: A :class:`ipaddr.IPAddress`, or a string, or False
- :returns: The IP, as a string or a class, if it passed the
- checks. Otherwise, returns False.
- """
- try:
- ip = ipaddr.IPAddress(ip)
- except ValueError:
- return False
- else:
- if isValidIP(ip):
- if compressed:
- return ip.compressed
- else:
- return ip
- return False
-
-def isIPv(version, ip):
- """Check if **ip** is a certain **version** (IPv4 or IPv6).
-
- .. warning: Do *not* put any calls to the logging module in this function,
- or else an infinite recursion will occur when the call is made, due
- the the log :class:`~logging.Filter`s in :mod:`~bridgedb.safelog`
- using this function to validate matches from the regular expression
- for IP addresses.
-
- :param integer version: The IPv[4|6] version to check; must be either
- ``4`` or ``6``. Any other value will be silently changed to ``4``.
- :param ip: The IP address to check. May be an any type which
- :class:`ipaddr.IPAddress` will accept.
- :rtype: boolean
- :returns: ``True``, if the address is an IPv4 address.
- """
- try:
- ipaddr.IPAddress(ip, version=version)
- except (ipaddr.AddressValueError, Exception):
- return False
- else:
- return True
- return False
-
-def isIPv4(ip):
- """Check if an address is IPv4.
-
- .. attention:: This does *not* check validity. See :func:`isValidIP`.
-
- :type ip: basestring or int
- :param ip: The IP address to check.
- :rtype: boolean
- :returns: True if the address is an IPv4 address.
- """
- return isIPv(4, ip)
-
-def isIPv6(ip):
- """Check if an address is IPv6.
-
- .. attention:: This does *not* check validity. See :func:`isValidIP`.
-
- :type ip: basestring or int
- :param ip: The IP address to check.
- :rtype: boolean
- :returns: True if the address is an IPv6 address.
- """
- return isIPv(6, ip)
-
-def isValidIP(ip):
- """Check that an IP (v4 or v6) is valid.
-
- The IP address, **ip**, must not be any of the following:
-
- * A :term:`Link-Local Address`,
- * A :term:`Loopback Address` or :term:`Localhost Address`,
- * A :term:`Multicast Address`,
- * An :term:`Unspecified Address` or :term:`Default Route`,
- * Any other :term:`Private Address`, or address within a privately
- allocated space, such as the IANA-reserved
- :term:`Shared Address Space`.
-
- If it is an IPv6 address, it also must not be:
-
- * A :term:`Site-Local Address` or an :term:`Unique Local Address`.
-
- >>> from bridgedb.parse.addr import isValidIP
- >>> isValidIP('1.2.3.4')
- True
- >>> isValidIP('1.2.3.255')
- True
- >>> isValidIP('1.2.3.256')
- False
- >>> isValidIP('1')
- False
- >>> isValidIP('1.2.3')
- False
- >>> isValidIP('xyzzy')
- False
-
- :type ip: An :class:`ipaddr.IPAddress`, :class:`ipaddr.IPv4Address`,
- :class:`ipaddr.IPv6Address`, or str
- :param ip: An IP address. If it is a string, it will be converted to a
- :class:`ipaddr.IPAddress`.
- :rtype: boolean
- :returns: ``True``, if **ip** passes the checks; False otherwise.
- """
- reasons = []
-
- try:
- if isinstance(ip, basestring):
- ip = ipaddr.IPAddress(ip)
-
- if ip.is_link_local:
- reasons.append('link local')
- if ip.is_loopback:
- reasons.append('loopback')
- if ip.is_multicast:
- reasons.append('multicast')
- if ip.is_private:
- reasons.append('private')
- if ip.is_unspecified:
- reasons.append('unspecified')
-
- if (ip.version == 6) and ip.is_site_local:
- reasons.append('site local')
- elif (ip.version == 4) and ip.is_reserved:
- reasons.append('reserved')
- except ValueError:
- reasons.append('cannot convert to ip')
-
- if reasons:
- explain = ', '.join([r for r in reasons])
- logging.debug("IP address %r is invalid! Reason(s): %s"
- % (ip, explain))
- return False
- return True
-
-def normalizeEmail(emailaddr, domainmap, domainrules, ignorePlus=True):
- """Normalise an email address according to the processing rules for its
- canonical originating domain.
-
- The email address, **emailaddr**, will be parsed and validated, and then
- checked that it originated from one of the domains allowed to email
- requests for bridges to the
- :class:`~bridgedb.email.distributor.EmailDistributor` via the
- :func:`canonicaliseEmailDomain` function.
-
- :param str emailaddr: An email address to normalise.
- :param dict domainmap: A map of permitted alternate domains (in lowercase)
- to their canonical domain names (in lowercase). This can be configured
- with the ``EMAIL_DOMAIN_MAP`` option in ``bridgedb.conf``, for
- example::
-
- EMAIL_DOMAIN_MAP = {'mail.google.com': 'gmail.com',
- 'googlemail.com': 'gmail.com'}
-
- :param dict domainrules: A mapping of canonical permitted domain names to
- a list of rules which should be applied to processing them, for
- example::
-
- EMAIL_DOMAIN_RULES = {'gmail.com': ["ignore_dots", "dkim"]
-
- Currently, ``"ignore_dots"`` means that all ``"."`` characters will be
- removed from the local part of the validated email address.
-
- :param bool ignorePlus: If ``True``, assume that
- ``blackhole+kerr at torproject.org`` is an alias for
- ``blackhole at torproject.org``, and remove everything after the first
- ``'+'`` character.
-
- :raises UnsupportedDomain: if the email address originated from a domain
- that we do not explicitly support.
- :raises BadEmail: if the email address could not be parsed or validated.
- :rtype: str
- :returns: The validated, normalised email address, if it was from a
- permitted domain. Otherwise, returns an empty string.
- """
- emailaddr = emailaddr.lower()
- localpart, domain = extractEmailAddress(emailaddr)
- canonical = canonicalizeEmailDomain(domain, domainmap)
-
- if ignorePlus:
- idx = localpart.find('+')
- if idx >= 0:
- localpart = localpart[:idx]
-
- rules = domainrules.get(canonical, [])
- if 'ignore_dots' in rules:
- localpart = localpart.replace(".", "")
-
- normalized = "%s@%s" % (localpart, domain)
- return normalized
-
-
-class PortList(object):
- """A container class for validated port ranges.
-
- From torspec.git/dir-spec.txt §2.3:
- |
- | portspec ::= "*" | port | port "-" port
- | port ::= an integer between 1 and 65535, inclusive.
- |
- | [Some implementations incorrectly generate ports with value 0.
- | Implementations SHOULD accept this, and SHOULD NOT generate it.
- | Connections to port 0 are never permitted.]
- |
-
- :ivar set ports: All ports which have been added to this ``PortList``.
- """
-
- #: The maximum number of allowed ports per IP address.
- PORTSPEC_LEN = 16
-
- def __init__(self, *args, **kwargs):
- """Create a :class:`~bridgedb.parse.addr.PortList`.
-
- :param args: Should match the ``portspec`` defined above.
- :raises: InvalidPort, if one of ``args`` doesn't match ``port`` as
- defined above.
- """
- self.ports = set()
- self.add(*args)
-
- def _sanitycheck(self, port):
- """Check that ``port`` is in the range [1, 65535] inclusive.
-
- :raises: InvalidPort, if ``port`` doesn't match ``port`` as defined
- in the excert from torspec above.
- :rtype: int
- :returns: The **port**, if no exceptions were raised.
- """
- if (not isinstance(port, int)) or not (0 < port <= 65535):
- raise InvalidPort("%s is not a valid port number!" % port)
- return port
-
- def __contains__(self, port):
- """Determine whether ``port`` is already in this ``PortList``.
-
- :returns: True if ``port`` is in this ``PortList``; False otherwise.
- """
- return port in self.ports
-
- def add(self, *args):
- """Add a port (or ports) to this ``PortList``.
-
- :param args: Should match the ``portspec`` defined above.
- :raises: InvalidPort, if one of ``args`` doesn't match ``port`` as
- defined above.
- """
- for arg in args:
- portlist = []
- try:
- if isinstance(arg, basestring):
- ports = set([int(p)
- for p in arg.split(',')][:self.PORTSPEC_LEN])
- portlist.extend([self._sanitycheck(p) for p in ports])
- if isinstance(arg, int):
- portlist.append(self._sanitycheck(arg))
- if isinstance(arg, PortList):
- self.add(list(arg.ports))
- except ValueError:
- raise InvalidPort("%s is not a valid port number!" % arg)
-
- self.ports.update(set(portlist))
-
- def __iter__(self):
- """Iterate through all ports in this PortList."""
- return self.ports.__iter__()
-
- def __str__(self):
- """Returns a pretty string representation of this PortList."""
- return ','.join(['%s' % port for port in self.ports])
-
- def __repr__(self):
- """Returns a raw depiction of this PortList."""
- return "PortList('%s')" % self.__str__()
-
- def __len__(self):
- """Returns the total number of ports in this PortList."""
- return len(self.ports)
-
- def __getitem__(self, port):
- """Get the value of ``port`` if it is in this PortList.
-
- :raises: ValueError, if ``port`` isn't in this PortList.
- :rtype: integer
- :returns: The ``port``, if it is in this PortList.
- """
- return list(self.ports)[port]
diff --git a/lib/bridgedb/parse/descriptors.py b/lib/bridgedb/parse/descriptors.py
deleted file mode 100644
index 1b0108e..0000000
--- a/lib/bridgedb/parse/descriptors.py
+++ /dev/null
@@ -1,298 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_descriptors ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Parsers for Tor Bridge descriptors, including ``bridge-networkstatus``
-documents, ``bridge-server-descriptor``s, and ``bridge-extrainfo``
-descriptors.
-
-.. py:module:: bridgedb.parse.descriptors
- :synopsis: Parsers for Tor Bridge descriptors.
-
-bridgedb.parse.descriptors
-===========================
-::
-
- DescriptorWarning - Raised when we parse a very odd descriptor.
- deduplicate - Deduplicate a container of descriptors, keeping only the newest
- descriptor for each router.
- parseNetworkStatusFile - Parse a bridge-networkstatus document generated and
- given to us by the BridgeAuthority.
- parseServerDescriptorsFile - Parse a file containing
- bridge-server-descriptors.
- parseExtraInfoFiles - Parse (multiple) file(s) containing bridge-extrainfo
- descriptors.
-..
-"""
-
-from __future__ import print_function
-
-import datetime
-import logging
-import os
-import shutil
-
-from stem import ProtocolError
-from stem.descriptor import parse_file
-from stem.descriptor.router_status_entry import _parse_file as _parseNSFile
-from stem.descriptor.router_status_entry import RouterStatusEntryV3
-
-from bridgedb import safelog
-from bridgedb.parse.nickname import InvalidRouterNickname
-
-
-class DescriptorWarning(Warning):
- """Raised when we parse a very odd descriptor."""
-
-
-def _copyUnparseableDescriptorFile(filename):
- """Save a copy of the bad descriptor file for later debugging.
-
- If the old filename was ``'descriptors/cached-extrainfo.new'``, then the
- name of the copy will be something like
- ``'descriptors/2014-11-05-01:57:23_cached-extrainfo.new.unparseable'``.
-
- :param str filename: The path to the unparseable descriptor file that we
- should save a copy of.
- :rtype: bool
- :returns: ``True`` if a copy of the file was saved successfully, and
- ``False`` otherwise.
- """
- timestamp = datetime.datetime.now()
- timestamp = timestamp.isoformat(sep=chr(0x2d))
- timestamp = timestamp.rsplit('.', 1)[0]
-
- path, sep, fname = filename.rpartition(os.path.sep)
- newfilename = "%s%s%s_%s%sunparseable" % (path, sep, timestamp,
- fname, os.path.extsep)
-
- logging.info(("Unparseable descriptor file '%s' will be copied to '%s' "
- "for debugging.") % (filename, newfilename))
-
- try:
- shutil.copyfile(filename, newfilename)
- except Exception as error: # pragma: no cover
- logging.error(("Could not save copy of unparseable descriptor file "
- "in '%s': %s") % (newfilename, str(error)))
- return False
- else:
- logging.debug(("Successfully finished saving a copy of an unparseable "
- "descriptor file."))
- return True
-
-def parseNetworkStatusFile(filename, validate=True, skipAnnotations=True,
- descriptorClass=RouterStatusEntryV3):
- """Parse a file which contains an ``@type bridge-networkstatus`` document.
-
- See :trac:`#12254` for why networkstatus-bridges documents don't look
- anything like the networkstatus v2 documents that they are purported to
- look like. They are missing all headers, and the entire footer including
- authority signatures.
-
- :param str filename: The location of the file containing bridge
- networkstatus descriptors.
- :param bool validate: Passed along to Stem's parsers. If ``True``, the
- descriptors will raise exceptions if they do not meet some definition
- of correctness.
- :param bool skipAnnotations: If ``True``, skip parsing everything before the
- first ``r`` line.
- :param descriptorClass: A class (probably from
- :api:`stem.descriptors.router_status_entry`) which Stem will parse
- each descriptor it reads from **filename** into.
- :raises InvalidRouterNickname: if one of the routers in the networkstatus
- file had a nickname which does not conform to Tor's nickname
- specification.
- :raises ValueError: if the contents of a descriptor are malformed and
- **validate** is ``True``.
- :raises IOError: if the file at **filename** can't be read.
- :rtype: list
- :returns: A list of
- :api:`stem.descriptor.router_status_entry.RouterStatusEntryV`s.
- """
- routers = []
-
- logging.info("Parsing networkstatus file: %s" % filename)
- with open(filename) as fh:
- position = fh.tell()
- if skipAnnotations:
- while not fh.readline().startswith('r '):
- position = fh.tell()
- logging.debug("Skipping %d bytes of networkstatus file." % position)
- fh.seek(position)
- document = _parseNSFile(fh, validate, entry_class=descriptorClass)
-
- try:
- routers.extend(list(document))
- except ValueError as error:
- if "nickname isn't valid" in str(error):
- raise InvalidRouterNickname(str(error))
- else:
- raise ValueError(str(error))
-
- logging.info("Closed networkstatus file: %s" % filename)
-
- return routers
-
-def parseServerDescriptorsFile(filename, validate=True):
- """Parse a file which contains ``@type bridge-server-descriptor``s.
-
- .. note:: ``validate`` defaults to ``False`` because there appears to be a
- bug in Leekspin, the fake descriptor generator, where Stem thinks the
- fingerprint doesn't match the keyâ¦
-
- .. note:: We have to lie to Stem, pretending that these are
- ``@type server-descriptor``s, **not**
- ``@type bridge-server-descriptor``s. See ticket #`11257`_.
-
- .. _`11257`: https://bugs.torproject.org/11257
-
- :param str filename: The file to parse descriptors from.
- :param bool validate: Whether or not to validate descriptor
- contents. (default: ``False``)
- :rtype: list
- :returns: A list of
- :api:`stem.descriptor.server_descriptor.RelayDescriptor`s.
- """
- logging.info("Parsing server descriptors with Stem: %s" % filename)
- descriptorType = 'server-descriptor 1.0'
- document = parse_file(filename, descriptorType, validate=validate)
- routers = list(document)
- return routers
-
-def __cmp_published__(x, y):
- """A custom ``cmp()`` which sorts descriptors by published date.
-
- :rtype: int
- :returns: Return negative if x<y, zero if x==y, positive if x>y.
- """
- if x.published < y.published:
- return -1
- elif x.published == y.published:
- # This *shouldn't* happen. It would mean that two descriptors for
- # the same router had the same timestamps, probably meaning there
- # is a severely-messed up OR implementation out there. Let's log
- # its fingerprint (no matter what!) so that we can look up its
- # ``platform`` line in its server-descriptor and tell whoever
- # wrote that code that they're probably (D)DOSing the Tor network.
- logging.warn(("Duplicate descriptor with identical timestamp (%s) "
- "for bridge %s with fingerprint %s !") %
- (x.published, x.nickname, x.fingerprint))
- return 0
- elif x.published > y.published:
- return 1
-
-def deduplicate(descriptors, statistics=False):
- """Deduplicate some descriptors, returning only the newest for each router.
-
- .. note:: If two descriptors for the same router are discovered, AND both
- descriptors have the **same** published timestamp, then the router's
- fingerprint WILL BE LOGGED ON PURPOSE, because we assume that router
- to be broken or malicious.
-
- :param list descriptors: A list of
- :api:`stem.descriptor.server_descriptor.RelayDescriptor`s,
- :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`s,
- or :api:`stem.descriptor.router_status_entry.RouterStatusEntryV2`s.
- :param bool statistics: If ``True``, log some extra statistics about the
- number of duplicates.
- :rtype: dict
- :returns: A dictionary mapping router fingerprints to their newest
- available descriptor.
- """
- duplicates = {}
- newest = {}
-
- for descriptor in descriptors:
- fingerprint = descriptor.fingerprint
- if fingerprint in duplicates:
- duplicates[fingerprint].append(descriptor)
- else:
- duplicates[fingerprint] = [descriptor,]
-
- for fingerprint, dupes in duplicates.items():
- dupes.sort(cmp=__cmp_published__)
- first = dupes.pop()
- newest[fingerprint] = first
- duplicates[fingerprint] = dupes
-
- if statistics:
- # sorted() won't sort by values (or anything that isn't the first item
- # in its container), period, no matter what the cmp function is.
- totals = sorted([(len(v), k,) for k, v in duplicates.viewitems()])
- total = sum([k for (k, v) in totals])
- bridges = len(duplicates)
- top = 10 if bridges >= 10 else bridges
- logging.info("Number of bridges with duplicates: %5d" % bridges)
- logging.info("Total duplicate descriptors: %5d" % total)
- logging.info("Bridges with the most duplicates (Top %d):" % top)
- for i, (subtotal, bridge) in zip(range(1, top + 1), totals[:top]):
- logging.info(" #%d %s: %d duplicates" % (i, bridge, subtotal))
-
- logging.info("Descriptor deduplication finished.")
-
- return newest
-
-def parseExtraInfoFiles(*filenames, **kwargs):
- """Parse files which contain ``@type bridge-extrainfo-descriptor``s.
-
- .. warning:: This function will *not* check that the ``router-signature``
- at the end of the extrainfo descriptor is valid. See
- ``bridgedb.bridges.Bridge._verifyExtraInfoSignature`` for a method for
- checking the signature.
-
- .. note:: This function will call :func:`deduplicate` to deduplicate the
- extrainfo descriptors parsed from all **filenames**.
-
- :kwargs validate: If there is a ``'validate'`` keyword argument, its value
- will be passed along as the ``'validate'`` argument to
- :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`.
- The ``'validate'`` keyword argument defaults to ``True``, meaning that
- the hash digest stored in the ``router-digest`` line will be checked
- against the actual contents of the descriptor and the extrainfo
- document's signature will be verified.
- :rtype: dict
- :returns: A dictionary mapping bridge fingerprints to deduplicated
- :api:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`s.
- """
- descriptors = []
-
- # The ``stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor``
- # class (with ``descriptorType = 'bridge-extra-info 1.1``) is unsuitable
- # for our purposes for the following reasons:
- #
- # 1. It expects a ``router-digest`` line, which is only present in
- # sanitised bridge extrainfo descriptors.
- #
- # 2. It doesn't check the ``router-signature`` (nor does it expect there
- # to be a signature).
- descriptorType = 'extra-info 1.0'
-
- validate = True
- if ('validate' in kwargs) and (kwargs['validate'] is False):
- validate = False
-
- for filename in filenames:
- logging.info("Parsing %s descriptors in %s..."
- % (descriptorType, filename))
-
- document = parse_file(filename, descriptorType, validate=validate)
-
- try:
- for router in document:
- descriptors.append(router)
- except (ValueError, ProtocolError) as error:
- logging.error(
- ("Stem exception while parsing extrainfo descriptor from "
- "file '%s':\n%s") % (filename, str(error)))
- _copyUnparseableDescriptorFile(filename)
-
- routers = deduplicate(descriptors)
- return routers
diff --git a/lib/bridgedb/parse/fingerprint.py b/lib/bridgedb/parse/fingerprint.py
deleted file mode 100644
index bf12ee5..0000000
--- a/lib/bridgedb/parse/fingerprint.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_fingerprint ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Utility functions for converting between various relay fingerprint formats,
-and checking their validity.
-
-.. py:module:: bridgedb.parse.fingerprints
- :synopsis: Parsers for Tor Bridge fingerprints.
-
-.. todo: This module is very small; it could possibly be combined with another
- module, e.g. :mod:`bridgedb.parse.descriptors`.
-
-bridgedb.parse.fingerprints
-============================
-::
-
- toHex - Convert a fingerprint from its binary representation to hexadecimal.
- fromHex - Convert a fingerprint from hexadecimal to binary.
- isValidFingerprint - Validate a fingerprint.
-..
-"""
-
-import binascii
-import logging
-
-
-#: The required length for hexidecimal representations of hash digest of a
-#: Tor relay's public identity key (a.k.a. its fingerprint).
-HEX_FINGERPRINT_LEN = 40
-
-
-#: (callable) Convert a value from binary to hexidecimal representation.
-toHex = binascii.b2a_hex
-
-#: (callable) Convert a value from hexidecimal to binary representation.
-fromHex = binascii.a2b_hex
-
-def isValidFingerprint(fingerprint):
- """Determine if a Tor relay fingerprint is valid.
-
- :param str fingerprint: The hex-encoded hash digest of the relay's
- public identity key, a.k.a. its fingerprint.
- :rtype: bool
- :returns: ``True`` if the **fingerprint** was valid, ``False`` otherwise.
- """
- try:
- if len(fingerprint) != HEX_FINGERPRINT_LEN:
- raise ValueError("Fingerprint has incorrect length: %r"
- % repr(fingerprint))
- fromHex(fingerprint)
- except (TypeError, ValueError):
- logging.debug("Invalid hex fingerprint: %r" % repr(fingerprint))
- else:
- return True
- return False
diff --git a/lib/bridgedb/parse/headers.py b/lib/bridgedb/parse/headers.py
deleted file mode 100644
index 5c3597a..0000000
--- a/lib/bridgedb/parse/headers.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013 Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Parsers for HTTP and Email headers.
-
-.. py:module:: bridgedb.parse.headers
- :synopsis: Parsers for HTTP and Email headers.
-
-bridgedb.parse.headers
-=======================
-::
-
- parseAcceptLanguage - Parse the contents of a client 'Accept-Language' header
-..
-"""
-
-def parseAcceptLanguage(header):
- """Parse the contents of a client 'Accept-Language' header.
-
- Parse the header in the following manner:
-
- 1. If ``header`` is None or an empty string, return an empty list.
- 2. Split the ``header`` string on any commas.
- 3. Chop of the RFC2616 quality/level suffix. We ignore these, and just
- use the order of the list as the preference order, without any
- parsing of quality/level assignments.
- 4. Add a fallback language of the same type if it is missing. For
- example, if we only got ['es-ES', 'de-DE'], add 'es' after 'es-ES'
- and add 'de' after 'de-DE'.
- 5. Change all hyphens to underscores.
-
- :param string header: The contents of an 'Accept-Language' header, i.e. as
- if taken from :api:`twisted.web.server.Request.getHeader`.
- :rtype: list
- :returns: A list of language codes (with and without locales), in order of
- preference.
- """
- langs = []
-
- if not header:
- return langs
-
- langHeader = header.split(',')
-
- for lang in langHeader:
- if lang.find(';') != -1:
- # Chop off the RFC2616 Accept `q=` and `level=` feilds
- code, _ = lang.split(';')
- langs.append(code)
- else:
- langs.append(lang)
-
- # Add a fallback language of the same type if it is missing.
- langsWithLocales = filter(lambda x: '-' in x, langs)
- langsOnly = map(lambda x: x.split('-')[0], langsWithLocales)
- for only in langsOnly:
- if only not in langs:
- # Add the fallback after the other languages like it:
- insertAfter = filter(lambda x: x.startswith(only),
- [x for x in langs])
- if insertAfter:
- placement = langs.index(insertAfter[0]) + 1
- langs.insert(placement, only)
- continue
- # Otherwise just put it at the end
- langs.append(only)
-
- # Gettext wants underderscores, because that is how it creates the
- # directories under i18n/, not hyphens:
- langs = map(lambda x: x.replace('-', '_'), [x for x in langs])
- return langs
diff --git a/lib/bridgedb/parse/nickname.py b/lib/bridgedb/parse/nickname.py
deleted file mode 100644
index 35cf626..0000000
--- a/lib/bridgedb/parse/nickname.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Parsers for bridge nicknames.
-
-.. py:module:: bridgedb.parse.nickname
- :synopsis: Parsers for Tor bridge nicknames.
-
-bridgedb.parse.nicknames
-========================
-::
-
- nicknames
- |_ isValidRouterNickname - Determine if a nickname is according to spec
-..
-"""
-
-import logging
-import string
-
-
-class InvalidRouterNickname(ValueError):
- """Router nickname doesn't follow tor-spec."""
-
-
-def isValidRouterNickname(nickname):
- """Determine if a router's given nickname meets the specification.
-
- :param string nickname: An OR's nickname.
- :rtype: bool
- :returns: ``True`` if the nickname is valid, ``False`` otherwise.
- """
- ALPHANUMERIC = string.letters + string.digits
-
- try:
- if not (1 <= len(nickname) <= 19):
- raise InvalidRouterNickname(
- "Nicknames must be between 1 and 19 characters: %r" % nickname)
- for letter in nickname:
- if not letter in ALPHANUMERIC:
- raise InvalidRouterNickname(
- "Nicknames must only use [A-Za-z0-9]: %r" % nickname)
- except InvalidRouterNickname as error:
- logging.error(str(error))
- except TypeError: # The nickname was probably still set to ``None``
- pass
- else:
- return True
-
- return False
diff --git a/lib/bridgedb/parse/options.py b/lib/bridgedb/parse/options.py
deleted file mode 100644
index 6f3bfc1..0000000
--- a/lib/bridgedb/parse/options.py
+++ /dev/null
@@ -1,295 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Parsers for BridgeDB commandline options.
-
-.. py:module:: bridgedb.parse.options
- :synopsis: Parsers for BridgeDB commandline options.
-
-
-bridgedb.parse.options
-======================
-::
-
- bridgedb.parse.options
- |__ setConfig()
- |__ getConfig() - Set/Get the config file path.
- |__ setRundir()
- |__ getRundir() - Set/Get the runtime directory.
- |__ parseOptions() - Create the main options parser for BridgeDB.
- |
- \_ BaseOptions - Base options, included in all other options menus.
- ||
- |\__ findRundirAndConfigFile() - Find the absolute path of the config
- | file and runtime directory, or find
- | suitable defaults.
- |
- |__ SIGHUPOptions - Menu to explain SIGHUP signal handling and usage.
- |__ SIGUSR1Options - Menu to explain SIGUSR1 handling and usage.
- |
- |__ MockOptions - Suboptions for creating fake bridge descriptors for
- | testing purposes.
- \__ MainOptions - Main commandline options parser for BridgeDB.
-..
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import sys
-import textwrap
-import traceback
-import os
-
-from twisted.python import usage
-
-from bridgedb import __version__
-
-
-#: In :meth:`BaseOptions.findRundirAndConfig`, this is set to the the
-#: absolute path of the ``opts['rundir']`` setting, if given, otherwise it
-#: defaults to the current directory.
-_rundir = None
-
-#: In :meth:`BaseOptions.findRundirAndConfig`, if ``opts['config']`` is
-#: given, this is set to the the absolute path of the ``opts['config']``
-#: settting relative to the ``rundir``, otherwise it defaults to
-#: 'bridgedb.conf' in the current directory.
-_config = None
-
-
-def setConfig(path):
- """Set the absolute path to the config file.
-
- See :meth:`BaseOptions.postOptions`.
-
- :param string path: The path to set.
- """
- global _config
- _config = path
-
-def getConfig():
- """Get the absolute path to the config file.
-
- :rtype: string
- :returns: The path to the config file.
- """
- return _config
-
-def setRundir(path):
- """Set the absolute path to the runtime directory.
-
- See :meth:`BaseOptions.postOptions`.
-
- :param string path: The path to set.
- """
- global _rundir
- _rundir = path
-
-def getRundir():
- """Get the absolute path to the runtime directory.
-
- :rtype: string
- :returns: The path to the config file.
- """
- return _rundir
-
-def parseOptions():
- """Create the main options parser and its subcommand parsers.
-
- Any :exc:`~twisted.python.usage.UsageErrors` which are raised due to
- invalid options are ignored; their error message is printed and then we
- exit the program.
-
- :rtype: :class:`MainOptions`
- :returns: The main options parsing class, with any commandline arguments
- already parsed.
- """
- options = MainOptions()
-
- try:
- options.parseOptions()
- except usage.UsageError as uerr:
- print(uerr.message)
- print(options.getUsage())
- sys.exit(1)
- except Exception as error: # pragma: no cover
- exc, value, tb = sys.exc_info()
- print("Unhandled Error: %s" % error.message)
- print(traceback.format_exc(tb))
-
- return options
-
-
-class BaseOptions(usage.Options):
- """Base options included in all main and sub options menus."""
-
- longdesc = textwrap.dedent("""BridgeDB is a proxy distribution system for
- private relays acting as bridges into the Tor network. See `bridgedb
- <command> --help` for addition help.""")
-
- optParameters = [
- ['config', 'c', None,
- 'Configuration file [default: <rundir>/bridgedb.conf]'],
- ['rundir', 'r', None,
- """Change to this directory before running. [default: `os.getcwd()']
-
- All other paths, if not absolute, should be relative to this path.
- This includes the config file and any further files specified within
- the config file.
- """]]
-
- def __init__(self):
- """Create an options parser. All flags, parameters, and attributes of
- this base options parser are inherited by all child classes.
- """
- super(BaseOptions, self).__init__()
- self['version'] = self.opt_version
- self['verbosity'] = 30
-
- def opt_quiet(self):
- """Decrease verbosity"""
- # We use '10' because then it corresponds to the log levels
- self['verbosity'] -= 10
-
- def opt_verbose(self):
- """Increase verbosity"""
- self['verbosity'] += 10
-
- opt_q = opt_quiet
- opt_v = opt_verbose
-
- def opt_version(self):
- """Display BridgeDB's version and exit."""
- print("%s-%s" % (__package__, __version__))
- sys.exit(0)
-
- @staticmethod
- def findRundirAndConfigFile(rundir=None, config=None):
- """Find the absolute path of the config file and runtime directory, or
- find suitable defaults.
-
- Attempts to set the absolute path of the runtime directory. If the
- config path is relative, its absolute path is set relative to the
- runtime directory path (unless it starts with '.' or '..', then it is
- interpreted relative to the current working directory). If the path to
- the config file is absolute, it is left alone.
-
- :type rundir: string or None
- :param rundir: The user-supplied path to the runtime directory, from
- the commandline options (i.e.
- ``options = BaseOptions().parseOptions(); options['rundir'];``).
- :type config: string or None
- :param config: The user-supplied path to the config file, from the
- commandline options (i.e.
- ``options = BaseOptions().parseOptions(); options['config'];``).
- :raises: :api:`twisted.python.usage.UsageError` if either the runtime
- directory or the config file cannot be found.
- """
- gRundir = getRundir()
- gConfig = getConfig()
-
- if gRundir is None:
- if rundir is not None:
- gRundir = os.path.abspath(os.path.expanduser(rundir))
- else:
- gRundir = os.getcwdu()
- setRundir(gRundir)
-
- if not os.path.isdir(gRundir): # pragma: no cover
- raise usage.UsageError(
- "Could not change to runtime directory: `%s'" % gRundir)
-
- if gConfig is None:
- if config is None:
- config = 'bridgedb.conf'
- gConfig = config
-
- if not os.path.isabs(gConfig):
- # startswith('.') will handle other relative paths, i.e. '..'
- if gConfig.startswith('.'): # pragma: no cover
- gConfig = os.path.abspath(os.path.expanduser(gConfig))
- else:
- gConfig = os.path.join(gRundir, gConfig)
- setConfig(gConfig)
-
- gConfig = getConfig()
- if not os.path.isfile(gConfig): # pragma: no cover
- raise usage.UsageError(
- "Specified config file `%s' doesn't exist!" % gConfig)
-
- def postOptions(self):
- """Automatically called by :meth:`parseOptions`.
-
- Determines appropriate values for the 'config' and 'rundir' settings.
- """
- super(BaseOptions, self).postOptions()
- self.findRundirAndConfigFile(self['rundir'], self['config'])
-
- gConfig = getConfig()
- gRundir = getRundir()
-
- if (self['rundir'] is None) and (gRundir is not None):
- self['rundir'] = gRundir
-
- if (self['config'] is None) and (gConfig is not None):
- self['config'] = gConfig
-
- if self['verbosity'] <= 10:
- print("%s.postOptions():" % self.__class__)
- print(" gCONFIG=%s" % gConfig)
- print(" self['config']=%s" % self['config'])
- print(" gRUNDIR=%s" % gRundir)
- print(" self['rundir']=%s" % self['rundir'])
-
-
-class MockOptions(BaseOptions):
- """Suboptions for creating necessary conditions for testing purposes."""
-
- optParameters = [
- ['descriptors', 'n', 1000,
- '''Generate <n> mock bridge descriptor sets
- (types: netstatus, extrainfo, server)''']]
-
-
-class SIGHUPOptions(BaseOptions):
- """Options menu to explain usage and handling of SIGHUP signals."""
-
- longdesc = """If you send a SIGHUP to a running BridgeDB process, the
- servers will parse and reload all bridge descriptor files into the
- databases.
-
- Note that this command WILL NOT handle sending the signal for you; see
- signal(7) and kill(1) for additional help."""
-
-
-class SIGUSR1Options(BaseOptions):
- """Options menu to explain usage and handling of SIGUSR1 signals."""
-
- longdesc = """If you send a SIGUSR1 to a running BridgeDB process, the
- servers will dump all bridge assignments by distributor from the
- databases to files.
-
- Note that this command WILL NOT handle sending the signal for you; see
- signal(7) and kill(1) for additional help."""
-
-
-class MainOptions(BaseOptions):
- """Main commandline options parser for BridgeDB."""
-
- optFlags = [
- ['dump-bridges', 'd', 'Dump bridges by hashring assignment into files'],
- ['reload', 'R', 'Reload bridge descriptors into running servers']]
- subCommands = [
- ['mock', None, MockOptions, "Generate a testing environment"],
- ['SIGHUP', None, SIGHUPOptions,
- "Reload bridge descriptors into running servers"],
- ['SIGUSR1', None, SIGUSR1Options,
- "Dump bridges by hashring assignment into files"]]
diff --git a/lib/bridgedb/parse/versions.py b/lib/bridgedb/parse/versions.py
deleted file mode 100644
index ee62792..0000000
--- a/lib/bridgedb/parse/versions.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_parse_versions ; -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2014-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: see included LICENSE for information
-
-"""Parsers for Tor version number strings.
-
-.. py:module:: bridgedb.parse.versions
- :synopsis: Parsers for Tor version number strings.
-
-bridgedb.parse.versions
-=======================
-::
-
- Version - Holds, parses, and does comparison operations for package
- version numbers.
-..
-"""
-
-from twisted import version as _txversion
-
-# The twisted.python.util.Version class was moved in Twisted==14.0.0 to
-# twisted.python.versions.Version:
-if _txversion.major >= 14:
- from twisted.python.versions import Version as _Version
-else:
- from twisted.python.util import Version as _Version
-
-
-class InvalidVersionStringFormat(ValueError):
- """Raised when a version string is not in a parseable format."""
-
-
-class Version(_Version):
- """Holds, parses, and does comparison operations for version numbers.
-
- :attr str package: The package name, if available.
- :attr int major: The major version number.
- :attr int minor: The minor version number.
- :attr int micro: The micro version number.
- :attr str prerelease: The **prerelease** specifier isn't always present,
- though when it is, it's usually separated from the main
- ``major.minor.micro`` part of the version string with a ``-``, ``+``,
- or ``#`` character. Sometimes the **prerelease** is another number,
- although often it can be a word specifying the release state,
- i.e. ``+alpha``, ``-rc2``, etc.
- """
-
- def __init__(self, version, package=None):
- """Create a version object.
-
- Comparisons may be computed between instances of :class:`Version`s.
-
- >>> from bridgedb.parse.versions import Version
- >>> v1 = Version("0.2.3.25", package="tor")
- >>> v1.base()
- '0.2.3.25'
- >>> v1.package
- 'tor'
- >>> v2 = Version("0.2.5.1-alpha", package="tor")
- >>> v2
- Version(package=tor, major=0, minor=2, micro=5, prerelease=1-alpha)
- >>> v1 == v2
- False
- >>> v2 > v1
- True
-
- :param str version: A Tor version string specifier, i.e. one taken
- from either the ``client-versions`` or ``server-versions`` lines
- within a Tor ``cached-consensus`` file.
- :param str package: The package or program which we are creating a
- version number for.
- """
- if version.find('.') == -1:
- raise InvalidVersionStringFormat(
- "Invalid delimiters in version string: %r" % version)
-
- package = package if package is not None else str()
- major, minor, micro = [int() for _ in range(3)]
- prerelease = str()
- components = version.split('.')
- if len(components) > 0:
- try:
- prerelease = str(components.pop())
- micro = int(components.pop())
- minor = int(components.pop())
- major = int(components.pop())
- except IndexError:
- pass
- super(Version, self).__init__(package, major, minor, micro, prerelease)
-
- def base(self):
- """Get the base version number (with prerelease).
-
- :rtype: string
- :returns: A version number, without the package/program name, and with
- the prefix (if available). For example: '0.2.5.1-alpha'.
- """
- pre = self.getPrefixedPrerelease()
- return '%s.%s.%s%s' % (self.major, self.minor, self.micro, pre)
-
- def getPrefixedPrerelease(self, separator='.'):
- """Get the prerelease string, prefixed by the separator ``prefix``.
-
- :param string separator: The separator to use between the rest of the
- version string and the :attr:`prerelease` string.
- :rtype: string
- :returns: The separator plus the ``prefix``, i.e. '.1-alpha'.
- """
- pre = ''
- if self.prerelease is not None:
- pre = separator + self.prerelease
- return pre
-
- def __repr__(self):
- prerelease = self.getPrefixedPrerelease('')
- return '%s(package=%s, major=%s, minor=%s, micro=%s, prerelease=%s)' \
- % (str(self.__class__.__name__),
- str(self.package),
- str(self.major),
- str(self.minor),
- str(self.micro),
- str(prerelease))
diff --git a/lib/bridgedb/persistent.py b/lib/bridgedb/persistent.py
deleted file mode 100644
index 22673dd..0000000
--- a/lib/bridgedb/persistent.py
+++ /dev/null
@@ -1,264 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_persistent -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Module for functionality to persistently store state."""
-
-import copy
-import logging
-import os.path
-
-try:
- import cPickle as pickle
-except (ImportError, NameError): # pragma: no cover
- import pickle
-
-from twisted.python.reflect import safe_repr
-from twisted.spread import jelly
-
-from bridgedb import Bridges
-from bridgedb import filters
-from bridgedb.email import distributor as emailDistributor
-from bridgedb.https import distributor as httpsDistributor
-from bridgedb.configure import Conf
-#from bridgedb.proxy import ProxySet
-
-_state = None
-
-#: Types and classes which are allowed to be jellied:
-_security = jelly.SecurityOptions()
-#_security.allowInstancesOf(ProxySet)
-_security.allowModules(filters, Bridges, emailDistributor, httpsDistributor)
-
-
-class MissingState(Exception):
- """Raised when the file or class storing global state is missing."""
-
-
-def _getState():
- """Retrieve the global state instance.
-
- :rtype: :class:`~bridgedb.persistent.State`
- :returns: An unpickled de-sexp'ed state object, which may contain just
- about anything, but should contain things like options, loaded config
- settings, etc.
- """
- return _state
-
-def _setState(state):
- """Set the global state.
-
- :type state: :class:`~bridgedb.persistent.State`
- :param state: The state instance to save.
- """
- global _state
- _state = state
-
-def load(stateCls=None):
- """Given a :class:`State`, try to unpickle it's ``statefile``.
-
- :param string stateCls: An instance of :class:`~bridgedb.persistent.State`. If
- not given, try loading from ``_statefile`` if that file exists.
- :rtype: None or :class:`State`
- """
- statefile = None
- if stateCls and isinstance(stateCls, State):
- cls = stateCls
- else:
- cls = _getState()
-
- if not cls:
- raise MissingState("Could not find a state instance to load.")
- else:
- loaded = cls.load()
- return loaded
-
-
-class State(jelly.Jellyable):
- """Pickled, jellied storage container for persistent state."""
-
- def __init__(self, config=None, **kwargs):
- """Create a persistent state storage mechanism.
-
- Serialisation of certain classes in BridgeDB doesn't work. Classes and
- modules which are known to be unjelliable/unpicklable so far are:
-
- - bridgedb.Dist
- - bridgedb.Bridges, and all "splitter" and "ring" classes contained
- within
-
- :property statefile: The filename to retrieve a pickled, jellied
- :class:`~bridgedb.persistent.State` instance from. (default:
- :attr:`bridgedb.persistent.State._statefile`)
- """
- self._statefile = os.path.abspath(str(__package__) + '.state')
- self.proxyList = None
- self.config = None
- self.key = None
-
- if 'STATEFILE' in kwargs:
- self.statefile = kwargs['STATEFILE']
-
- for key, value in kwargs.items():
- self.__dict__[key] = value
-
- if config is not None:
- for key, value in config.__dict__.items():
- self.__dict__[key] = value
-
- _setState(self)
-
- def _get_statefile(self):
- """Retrieve the filename of the global statefile.
-
- :rtype: string
- :returns: The filename of the statefile.
- """
- return self._statefile
-
- def _set_statefile(self, filename):
- """Set the global statefile.
-
- :param string statefile: The filename of the statefile.
- """
- filename = os.path.abspath(os.path.expanduser(filename))
- logging.debug("Setting statefile to '%s'" % filename)
- self._statefile = filename
-
- # Create the parent directory if it doesn't exist:
- dirname = os.path.dirname(filename)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
-
- # Create the statefile if it doesn't exist:
- if not os.path.exists(filename):
- open(filename, 'w').close()
-
- def _del_statefile(self):
- """Delete the file containing previously saved state."""
- try:
- with open(self._statefile, 'w') as fh:
- fh.close()
- os.unlink(self._statefile)
- self._statefile = None
- except (IOError, OSError) as error: # pragma: no cover
- logging.error("There was an error deleting the statefile: '%s'"
- % self._statefile)
-
- statefile = property(_get_statefile, _set_statefile, _del_statefile,
- """Filename property of a persisent.State.""")
-
- def load(self, statefile=None):
- """Load a previously saved statefile.
-
- :raises MissingState: If there was any error loading the **statefile**.
- :rtype: :class:`State` or None
- :returns: The state, loaded from :attr:`State.STATEFILE`, or None if
- an error occurred.
- """
- if not statefile:
- if not self.statefile:
- raise MissingState("Could not find a state file to load.")
- statefile = self.statefile
- logging.debug("Retrieving state from: \t'%s'" % statefile)
-
- quo= fh = None
- err = ''
-
- try:
- if isinstance(statefile, basestring):
- fh = open(statefile, 'r')
- elif not statefile.closed:
- fh = statefile
- except (IOError, OSError) as error: # pragma: no cover
- err += "There was an error reading statefile "
- err += "'{0}':\n{1}".format(statefile, error)
- except (AttributeError, TypeError) as error:
- err += "Failed statefile.open() and statefile.closed:"
- err += "\n\t{0}\nstatefile type = '{1}'".format(
- error.message, type(statefile))
- else:
- try:
- status = pickle.load(fh)
- except EOFError:
- err += "The statefile %s was empty." % fh.name
- else:
- quo = jelly.unjelly(status)
- if fh is not None:
- fh.close()
- if quo:
- return quo
-
- if err:
- raise MissingState(err)
-
- def save(self, statefile=None):
- """Save state as a pickled jelly to a file on disk."""
- if not statefile:
- if not self._statefile:
- raise MissingState("Could not find a state file to load.")
- statefile = self._statefile
- logging.debug("Saving state to: \t'%s'" % statefile)
-
- fh = None
- try:
- fh = open(statefile, 'w')
- except (IOError, OSError) as error: # pragma: no cover
- logging.warn("Error writing state file to '%s': %s"
- % (statefile, error))
- else:
- try:
- pickle.dump(jelly.jelly(self), fh)
- except AttributeError as error:
- logging.debug("Tried jellying an unjelliable object: %s"
- % error.message)
-
- if fh is not None:
- fh.flush()
- fh.close()
-
- def useChangedSettings(self, config):
- """Take a new config, compare it to the last one, and update settings.
-
- Given a ``config`` object created from the configuration file, compare
- it to the last :class:`~bridgedb.configure.Conf` that was stored, and apply
- any settings which were changed to be attributes of the :class:`State`
- instance.
- """
- updated = []
- new = []
-
- for key, value in config.__dict__.items():
- try:
- # If state.config doesn't have the same value as the new
- # config, then update the state setting.
- #
- # Be sure, when updating settings while parsing the config
- # file, to assign the new settings as attributes of the
- # :class:`bridgedb.configure.Conf` instance.
- if value != self.config.__dict__[key]:
- setattr(self, key, value)
- updated.append(key)
- logging.debug("Updated %s setting: %r â %r" %
- (safe_repr(key),
- self.config.__dict__[key],
- safe_repr(value)))
- except (KeyError, AttributeError):
- setattr(self, key, value)
- new.append(key)
- logging.debug("New setting: %s = %r" %
- (safe_repr(key),
- safe_repr(value)))
-
- logging.info("Updated setting(s): %s" % ' '.join([x for x in updated]))
- logging.info("New setting(s): %s" % ' '.join([x for x in new]))
- logging.debug(
- "Saving newer config as `state.config` for later comparison")
- self.config = config
diff --git a/lib/bridgedb/proxy.py b/lib/bridgedb/proxy.py
deleted file mode 100644
index 086f3bd..0000000
--- a/lib/bridgedb/proxy.py
+++ /dev/null
@@ -1,473 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Classes for finding and managing lists of open proxies."""
-
-from __future__ import print_function
-from collections import MutableSet
-from functools import update_wrapper
-from functools import wraps
-
-import ipaddr
-import logging
-import os
-import time
-
-from twisted.internet import defer
-from twisted.internet import protocol
-from twisted.internet import reactor
-from twisted.internet import utils as txutils
-from bridgedb.runner import find
-from bridgedb.parse.addr import isIPAddress
-
-
-def downloadTorExits(proxyList, ipaddress, port=443, protocol=None):
- """Run a script which downloads a list of Tor exit relays which allow their
- clients to exit to the given **ipaddress** and **port**.
-
- :param proxyList: The :class:`ProxySet` instance from :mod:`bridgedb.Main`.
- :param str ipaddress: The IP address that each Tor exit relay should be
- capable of connecting to for clients, as specified by its ExitPolicy.
- :param int port: The port corresponding to the above **ipaddress** that
- each Tor exit relay should allow clients to exit to. (See
- https://check.torproject.org/cgi-bin/TorBulkExitList.py.)
- :type protocol: :api:`twisted.internet.protocol.Protocol`
- :param protocol: A :class:`~bridgedb.proxy.ExitListProtocol`, or any other
- :api:`~twisted.internet.protocol.Protocol` implementation for
- processing the results of a process which downloads a list of Tor exit
- relays. This parameter is mainly meant for use in testing, and should
- not be changed.
- :rtype: :class:`~twisted.internet.defer.Deferred`
- :returns: A deferred which will callback with a list, each item in the
- list is a string containing an IP of a Tor exit relay.
- """
- proto = ExitListProtocol() if protocol is None else protocol()
- args = [proto.script, '--stdout', '-a', ipaddress, '-p', str(port)]
- proto.deferred.addCallback(proxyList.addExitRelays)
- proto.deferred.addErrback(logging.exception)
- transport = reactor.spawnProcess(proto, proto.script, args=args, env={})
- return proto.deferred
-
-def loadProxiesFromFile(filename, proxySet=None, removeStale=False):
- """Load proxy IP addresses from a list of files.
-
- :param str filename: A filename whose path can be either absolute or
- relative to the current working directory. The file should contain the
- IP addresses of various open proxies, one per line, for example::
-
- 11.11.11.11
- 22.22.22.22
- 123.45.67.89
-
- :type proxySet: None or :class:`~bridgedb.proxy.ProxySet`.
- :param proxySet: If given, load the addresses read from the files into
- this ``ProxySet``.
- :param bool removeStale: If ``True``, remove proxies from the **proxySet**
- which were not listed in any of the **files**.
- (default: ``False``)
- :returns: A list of all the proxies listed in the **files* (regardless of
- whether they were added or removed).
- """
- logging.info("Reloading proxy lists...")
-
- addresses = []
-
- # We have to check the instance because, if the ProxySet was newly
- # created, it will likely be empty, causing it to evaluate to False:
- if isinstance(proxySet, ProxySet):
- oldProxySet = proxySet.copy()
-
- try:
- with open(filename, 'r') as proxyFile:
- for line in proxyFile.readlines():
- line = line.strip()
- if isinstance(proxySet, ProxySet):
- # ProxySet.add() will validate the IP address
- if proxySet.add(line, tag=filename):
- addresses.append(line)
- else:
- ip = isIPAddress(line)
- if ip:
- addresses.append(ip)
- except Exception as error:
- logging.warn("Error while reading a proxy list file: %s" % str(error))
-
- if isinstance(proxySet, ProxySet):
- stale = list(oldProxySet.difference(addresses))
-
- if removeStale:
- for ip in stale:
- if proxySet.getTag(ip) == filename:
- logging.info("Removing stale IP %s from proxy list." % ip)
- proxySet.remove(ip)
- else:
- logging.info("Tag %s didn't match %s"
- % (proxySet.getTag(ip), filename))
-
- return addresses
-
-
-class ProxySet(MutableSet):
- """A :class:`collections.MutableSet` for storing validated IP addresses."""
-
- #: A tag to apply to IP addresses within this ``ProxySet`` which are known
- #: Tor exit relays.
- _exitTag = 'exit_relay'
-
- def __init__(self, proxies=dict()):
- """Initialise a ``ProxySet``.
-
- :type proxies: A tuple, list, dict, or set.
- :param proxies: Optionally, initialise with an iterable, ``proxies``.
- For each ``item`` in that iterable, ``item`` must either:
- 1. be a string or int representing an IP address, or,
- 2. be another iterable, whose first item satisfies #1.
- """
- super(ProxySet, self).__init__()
- self._proxydict = dict()
- self._proxies = set()
- self.addProxies(proxies)
-
- @property
- def proxies(self):
- """All proxies in this set, regardless of tags."""
- return list(self._proxies)
-
- @property
- def exitRelays(self):
- """Get all proxies in this ``ProxySet`` tagged as Tor exit relays.
-
- :rtype: set
- :returns: A set of all known Tor exit relays which are contained
- within this :class:`~bridgedb.proxy.ProxySet`.
- """
- return self.getAllWithTag(self._exitTag)
-
- def __add__(self, ip=None, tag=None):
- """Add an **ip** to this set, with an optional **tag**.
-
- This has no effect if the **ip** is already present. The **ip** is
- only added if it passes the checks in
- :func:`~bridgedb.parse.addr.isIPAddress`.
-
- :type ip: basestring or int
- :param ip: The IP address to add.
- :param tag: An optional value to link to **ip**. If not given, it will
- be a timestamp (seconds since epoch, as a float) for when **ip**
- was first added to this set.
- :rtype: bool
- :returns: ``True`` if **ip** is in this set; ``False`` otherwise.
- """
- ip = isIPAddress(ip)
- if ip:
- if self._proxies.isdisjoint(set(ip)):
- logging.debug("Adding %s to proxy list..." % ip)
- self._proxies.add(ip)
- self._proxydict[ip] = tag if tag else time.time()
- return True
- return False
-
- def __radd__(self, *args, **kwargs): self.__add__(*args, **kwargs)
-
- def __contains__(self, ip):
- """x.__contains__(y) <==> y in x.
-
- :type ip: basestring or int
- :param ip: The IP address to check.
- :rtype: boolean
- :returns: True if ``ip`` is in this set; False otherwise.
- """
- ipset = [isIPAddress(ip),]
- if ipset and len(self._proxies.intersection(ipset)) == len(ipset):
- return True
- return False
-
- def __sub__(self, ip):
- """Entirely remove **ip** from this set.
-
- :type ip: basestring or int
- :param ip: The IP address to remove.
- """
- try:
- self._proxydict.pop(ip)
- self._proxies.discard(ip)
- except KeyError:
- pass
-
- def __rsub__(self, *args, **kwargs): raise NotImplemented
-
- def _getErrorMessage(self, x=None, y=None):
- """Make an error message describing how this class works."""
- message = """\nParameter 'proxies' must be one of:
- - a {1} of {0}
- - a {2} of {0}
- - a {3}, whose keys are {0} (the values can be anything)
- - a {4} of {1}s, whose first object in each {1} must be a {0}
- - a {4} of {0}
- """.format(type(''), type(()), type([]), type({}), type(set(())))
- end = "You gave: a {0}".format(type(y))
- end += " of {0}".format(type(x))
- return os.linesep.join((message, end))
-
- def addProxies(self, proxies, tag=None):
- """Add proxies to this set.
-
- This calls :func:`add` for each item in the iterable **proxies**.
- Each proxy, if added, will be tagged with a current timestamp.
-
- :type proxies: A tuple, list, dict, or set.
- :param proxies: An iterable. For each ``item`` in that iterable,
- ``item`` must either:
- 1. be a string or int representing an IP address, or,
- 2. be another iterable, whose first item satisfies #1.
- :keyword tag: An optional value to link to all untagged
- **proxies**. If ``None``, it will be a timestamp (seconds since
- epoch, as a float) for when the proxy was first added to this set.
- """
- if isinstance(proxies, dict):
- [self.add(ip, value) for (ip, value) in proxies.items()]
- else:
- try:
- for x in proxies:
- if isinstance(x, (tuple, list, set)):
- if len(x) == 2: self.add(x[0], x[1])
- elif len(x) == 1: self.add(x, tag)
- else: raise ValueError(self._getErrorMessage(x, proxies))
- elif isinstance(x, (basestring, int)):
- self.add(x, tag)
- else:
- raise ValueError(self._getErrorMessage(x, proxies))
- except TypeError:
- raise ValueError(self._getErrorMessage(proxies, None))
-
- @wraps(addProxies)
- def addExitRelays(self, relays):
- logging.info("Loading exit relays into proxy list...")
- [self.add(x, self._exitTag) for x in relays]
-
- def getTag(self, ip):
- """Get the tag for an **ip** in this ``ProxySet``, if available.
-
- :type ip: basestring or int
- :param ip: The IP address to obtain the tag for.
- :rtype: ``None`` or basestring or int
- :returns: The tag for that **ip**, iff **ip** exists in this
- ``ProxySet`` and it has a tag.
- """
- return self._proxydict.get(ip)
-
- def getAllWithTag(self, tag):
- """Get all proxies in this ``ProxySet`` with a given tag.
-
- :param basestring tag: A tag to search for.
- :rtype: set
- :returns: A set of all proxies which are contained within this
- :class:`~bridgedb.proxy.ProxySet` which are also tagged with
- **tag**.
- """
- return set([key for key, value in filter(lambda x: x[1] == tag,
- self._proxydict.items())])
-
- def firstSeen(self, ip):
- """Get the timestamp when **ip** was first seen, if available.
-
- :type ip: basestring or int
- :param ip: The IP address to obtain a timestamp for.
- :rtype: float or None
- :returns: The timestamp (in seconds since epoch) if available.
- Otherwise, returns None.
- """
- when = self.getTag(ip)
- if isinstance(when, float):
- return when
-
- def isExitRelay(self, ip):
- """Check if ``ip`` is a known Tor exit relay.
-
- :type ip: basestring or int
- :param ip: The IP address to check.
- :rtype: boolean
- :returns: True if ``ip`` is a known Tor exit relay; False otherwise.
- """
- if self.getTag(ip) == self._exitTag:
- return True
- return False
-
- def replaceProxyList(self, proxies, tag=None):
- """Clear everything and add all ``proxies``.
-
- :type proxies: A tuple, list, dict, or set.
- :param proxies: An iterable. For each ``item`` in that iterable,
- ``item`` must either:
- 1. be a string or int representing an IP address, or,
- 2. be another iterable, whose first item satisfies #1.
- """
- try:
- self.clear()
- self.addProxies(proxies, tag=tag)
- except Exception as error:
- logging.error(str(error))
-
- _assigned = ('__name__', '__doc__')
-
- @wraps(MutableSet._hash)
- def __hash__(self): return self._hash()
- def __iter__(self): return self._proxies.__iter__()
- def __len__(self): return len(self._proxydict.items())
- def __repr__(self): return type('')(self.proxies)
- def __str__(self): return os.linesep.join(self.proxies)
- update_wrapper(__iter__, set.__iter__, _assigned)
- update_wrapper(__len__, len, _assigned)
- update_wrapper(__repr__, repr, _assigned)
- update_wrapper(__str__, str, _assigned)
-
- def add(self, ip, tag=None): return self.__add__(ip, tag)
- def copy(self): return self.__class__(self._proxydict.copy())
- def contains(self, ip): return self.__contains__(ip)
- def discard(self, ip): return self.__sub__(ip)
- def remove(self, other): return self.__sub__(other)
- update_wrapper(add, __add__)
- update_wrapper(copy, __init__)
- update_wrapper(contains, __contains__)
- update_wrapper(discard, __sub__)
- update_wrapper(remove, __sub__)
-
- def difference(self, other): return self._proxies.difference(other)
- def issubset(self, other): return self._proxies.issubset(other)
- def issuperset(self, other): return self._proxies.issuperset(other)
- def intersection(self, other): return self._proxies.intersection(other)
- def symmetric_difference(self, other): return self._proxies.symmetric_difference(other)
- def union(self, other): return self._proxies.union(other)
- update_wrapper(difference, set.difference, _assigned)
- update_wrapper(issubset, set.issubset, _assigned)
- update_wrapper(issuperset, set.issuperset, _assigned)
- update_wrapper(intersection, set.intersection, _assigned)
- update_wrapper(symmetric_difference, set.symmetric_difference, _assigned)
- update_wrapper(union, set.union, _assigned)
-
-
-class ExitListProtocol(protocol.ProcessProtocol):
- """A :class:`~twisted.internet.protocol.Protocol` for ``get-exit-list``.
-
- :attr boolean connected: True if our ``transport`` is connected.
-
- :type transport: An implementer of
- :interface:`twisted.internet.interface.IProcessTransport`.
- :attr transport: If :func:`twisted.internet.reactor.spawnProcess` is
- called with an instance of this class as it's ``protocol``, then
- :func:`~twisted.internet.reactor.spawnProcess` will return this
- ``transport``.
- """
-
- def __init__(self):
- """Create a protocol for downloading a list of current Tor exit relays.
-
- :type exitlist: :class:`ProxySet`
- :ivar exitlist: A :class:`~collections.MutableSet` containing the IP
- addresses of known Tor exit relays which can reach our public IP
- address.
- :ivar list data: A list containing a ``bytes`` object for each chuck
- of data received from the ``transport``.
- :ivar deferred: A deferred which will callback with the ``exitlist``
- when the process has ended.
-
- :param string script: The full pathname of the script to run.
- """
- self.data = []
- self.script = find('get-tor-exits')
- self.exitlist = ProxySet()
- self.deferred = defer.Deferred()
-
- def childConnectionLost(self, childFD):
- """See :func:`t.i.protocol.ProcessProtocol.childConnectionLost`."""
- protocol.ProcessProtocol.childConnectionLost(self, childFD)
-
- def connectionMade(self):
- """Called when a connection is made.
-
- This may be considered the initializer of the protocol, because it is
- called when the connection is completed. For clients, this is called
- once the connection to the server has been established; for servers,
- this is called after an accept() call stops blocking and a socket has
- been received. If you need to send any greeting or initial message,
- do it here.
- """
- logging.debug("ExitListProtocol: Connection made with remote server")
- self.transport.closeStdin()
-
- def errReceived(self, data):
- """Some data was received from stderr."""
- # The get-exit-list script uses twisted.python.log to log to stderr:
- for line in data.splitlines(): # pragma: no cover
- logging.debug(line)
-
- def outReceived(self, data):
- """Some data was received from stdout."""
- self.data.append(data)
-
- def outConnectionLost(self):
- """This will be called when stdout is closed."""
- logging.debug("Finished downloading list of Tor exit relays.")
- self.transport.loseConnection()
- self.parseData()
-
- def parseData(self):
- """Parse all data received so far into our
- :class:`<bridgedb.proxy.ProxySet> exitlist`.
- """
- unparseable = []
-
- data = ''.join(self.data).split('\n')
-
- for line in data:
- line.strip()
- if not line: continue
- # If it reached an errorpage, then we grabbed raw HTML that starts
- # with an HTML tag:
- if line.startswith('<'): break
- if line.startswith('#'): continue
- ip = isIPAddress(line)
- if ip:
- logging.info("Discovered Tor exit relay: %s" % ip)
- self.exitlist.add(ip)
- else:
- logging.debug("Got exitlist line that wasn't an IP: %s" % line)
- unparseable.append(line)
-
- if unparseable:
- logging.warn(("There were unparseable lines in the downloaded "
- "list of Tor exit relays: %r") % unparseable)
-
- def processEnded(self, reason):
- """Called when the child process exits and all file descriptors
- associated with it have been closed.
-
- :type reason: :class:`twisted.python.failure.Failure`
- """
- self.transport.loseConnection()
- if reason.value.exitCode != 0: # pragma: no cover
- logging.debug(reason.getTraceback())
- logging.error("There was an error downloading Tor exit list: %s"
- % reason.value)
- else:
- logging.info("Finished processing list of Tor exit relays.")
- logging.debug("Transferring exit list to storage...")
- # Avoid triggering the deferred twice, e.g. on processExited():
- if not self.deferred.called:
- self.deferred.callback(list(self.exitlist.proxies))
-
- def processExited(self, reason):
- """This will be called when the subprocess exits.
-
- :type reason: :class:`twisted.python.failure.Failure`
- """
- logging.debug("%s exited with status code %d"
- % (self.script, reason.value.exitCode))
diff --git a/lib/bridgedb/qrcodes.py b/lib/bridgedb/qrcodes.py
deleted file mode 100644
index e09a705..0000000
--- a/lib/bridgedb/qrcodes.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_qrcodes ; -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Utilities for working with QRCodes."""
-
-
-import cStringIO
-import logging
-
-try:
- import qrcode
-except ImportError: # pragma: no cover
- qrcode = False
- logging.warn("Could not import Python qrcode module.")
- logging.debug(("You'll need the qrcode Python module for this to "
- "work. On Debian-based systems, this should be in the "
- "python-qrcode package."))
-
-
-def generateQR(bridgelines, imageFormat=u'JPEG', bridgeSchema=False):
- """Generate a QRCode for the client's bridge lines.
-
- :param str bridgelines: The Bridge Lines which we are distributing to the
- client.
- :param bool bridgeSchema: If ``True``, prepend ``'bridge://'`` to the
- beginning of each bridge line before QR encoding.
- :rtype: str or ``None``
- :returns: The generated QRCode, as a string.
- """
- logging.debug("Attempting to encode bridge lines into a QRCode...")
-
- if not bridgelines:
- return
-
- if not qrcode:
- logging.info("Not creating QRCode for bridgelines; no qrcode module.")
- return
-
- try:
- if bridgeSchema:
- # See https://bugs.torproject.org/12639 for why bridge:// is used.
- # (Hopefully, Orbot will pick up the ACTION_VIEW intent.)
- schema = 'bridge://'
- prefixed = []
- for line in bridgelines.strip().split('\n'):
- prefixed.append(schema + line)
- bridgelines = '\n'.join(prefixed)
-
- logging.debug("QR encoding bridge lines: %s" % bridgelines)
-
- qr = qrcode.QRCode()
- qr.add_data(bridgelines)
-
- buf = cStringIO.StringIO()
- img = qr.make_image().resize([350, 350])
- img.save(buf, imageFormat)
- buf.seek(0)
-
- imgstr = buf.read()
- return imgstr
-
- except KeyError as error:
- logging.error(str(error))
- logging.debug(("It seems python-imaging doesn't understand how to "
- "save in the %s format.") % imageFormat)
- except Exception as error: # pragma: no cover
- logging.error(("There was an error while attempting to generate the "
- "QRCode: %s") % str(error))
diff --git a/lib/bridgedb/runner.py b/lib/bridgedb/runner.py
deleted file mode 100644
index 6ac069f..0000000
--- a/lib/bridgedb/runner.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_runner -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# (c) 2012-2015, Isis Lovecruft
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Classes for running components and servers, as well as daemonisation.
-
-** Module Overview: **
-
-"""
-
-from __future__ import print_function
-
-import logging
-import sys
-import os
-
-from twisted.python import procutils
-
-
-def find(filename):
- """Find the executable ``filename``.
-
- :param string filename: The executable to search for. Must be in the
- effective user ID's $PATH.
- :rtype: string
- :returns: The location of the executable, if found. Otherwise, returns
- None.
- """
- executable = None
-
- logging.debug("Searching for installed '%s'..." % filename)
- which = procutils.which(filename, os.X_OK)
-
- if len(which) > 0:
- for that in which:
- if os.stat(that).st_uid == os.geteuid():
- executable = that
- break
- if not executable:
- return None
-
- logging.debug("Found installed script at '%s'" % executable)
- return executable
-
-def generateDescriptors(count=None, rundir=None):
- """Run a script which creates fake bridge descriptors for testing purposes.
-
- This will run Leekspin_ to create bridge server descriptors, bridge
- extra-info descriptors, and networkstatus document.
-
- .. warning: This function can take a very long time to run, especially in
- headless environments where entropy sources are minimal, because it
- creates the keys for each mocked OR, which are embedded in the server
- descriptors, used to calculate the OR fingerprints, and sign the
- descriptors, among other things.
-
- .. _Leekspin: https://gitweb.torproject.org/user/isis/leekspin.git
-
- :param integer count: Number of mocked bridges to generate descriptor
- for. (default: 3)
- :type rundir: string or None
- :param rundir: If given, use this directory as the current working
- directory for the bridge descriptor generator script to run in. The
- directory MUST already exist, and the descriptor files will be created
- in it. If None, use the whatever directory we are currently in.
- """
- import subprocess
- import os.path
-
- proc = None
- statuscode = 0
- script = 'leekspin'
- rundir = rundir if os.path.isdir(rundir) else None
- count = count if count else 3
- try:
- proc = subprocess.Popen([script, '-n', str(count)],
- close_fds=True, cwd=rundir)
- finally:
- if proc is not None:
- proc.wait()
- if proc.returncode:
- print("There was an error generating bridge descriptors.",
- "(Returncode: %d)" % proc.returncode)
- statuscode = proc.returncode
- else:
- print("Sucessfully generated %s descriptors." % str(count))
- del subprocess
- return statuscode
-
-def doDumpBridges(config):
- """Dump bridges by assignment to a file.
-
- This function handles the commandline '--dump-bridges' option.
-
- :type config: :class:`bridgedb.Main.Conf`
- :param config: The current configuration.
- """
- import bridgedb.Bucket as bucket
-
- bucketManager = bucket.BucketManager(config)
- bucketManager.assignBridgesToBuckets()
- bucketManager.dumpBridges()
diff --git a/lib/bridgedb/safelog.py b/lib/bridgedb/safelog.py
deleted file mode 100644
index 8130be6..0000000
--- a/lib/bridgedb/safelog.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_safelog -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Filters for log sanitisation.
-
-The ``Safelog*Filter`` classes within this module can be instantiated and
-adding to any :class:`logging.Handler`, in order to transparently filter
-substrings within log messages which match the given ``pattern``. Matching
-substrings may be optionally additionally validated by implementing the
-:meth:`~BaseSafelogFilter.doubleCheck` method before they are finally replaced
-with the ``replacement`` string. For example::
-
- >>> import io
- >>> import logging
- >>> from bridgedb import safelog
- >>> handler = logging.StreamHandler(io.BytesIO())
- >>> logger = logging.getLogger()
- >>> logger.addHandler(handler)
- >>> logger.addFilter(safelog.SafelogEmailFilter())
- >>> logger.info("Sent response email to: blackhole at torproject.org")
-
-..
-
-Module Overview:
-~~~~~~~~~~~~~~~~
-::
- safelog
- |
- |_setSafeLogging - Enable or disable safelogging globally.
- |_logSafely - Utility for manually sanitising a portion of a log message
- |
- |_BaseSafelogFilter - Base class for log message sanitisation filters
- | |_doubleCheck - Optional stricter validation on matching substrings
- | |_filter - Determine if some part of a log message should be filtered
- |
- |_SafelogEmailFilter - Filter for removing email addresses from logs
- |_SafelogIPv6Filter - Filter for removing IPv4 addresses from logs
- |_SafelogIPv6Filter - Filter for removing IPv6 addresses from logs
-::
-"""
-
-import functools
-import logging
-import re
-
-from bridgedb.parse import addr
-
-
-safe_logging = True
-
-
-def setSafeLogging(safe):
- """Enable or disable automatic filtering of log messages.
-
- :param bool safe: If ``True``, filter email and IP addresses from log
- messages automagically.
- """
- global safe_logging
- safe_logging = safe
-
-def logSafely(string):
- """Utility for manually sanitising a portion of a log message.
-
- :param str string: If ``SAFELOGGING`` is enabled, sanitise this **string**
- by replacing it with ``"[scrubbed]"``. Otherwise, return the
- **string** unchanged.
- :rtype: str
- :returns: ``"[scrubbed]"`` or the original string.
- """
- if safe_logging:
- return "[scrubbed]"
- return string
-
-
-class BaseSafelogFilter(logging.Filter):
- """Base class for creating log message sanitisation filters.
-
- A :class:`BaseSafelogFilter` uses a compiled regex :cvar:`pattern` to
- match particular items of data in log messages which should be sanitised
- (if ``SAFELOGGING`` is enabled in :file:`bridgedb.conf`).
-
- .. note:: The ``pattern`` is used only for string *matching* purposes, and
- *not* for validation. In other words, a ``pattern`` which matches email
- addresses should simply match something which appears to be an email
- address, even though that matching string might not technically be a
- valid email address vis-á-vis :rfc:`5321`.
-
- In addition, a ``BaseSafelogFilter`` uses a :cvar:`easyFind`, which is
- simply a string or character to search for before running checking against
- the regular expression, to attempt to avoid regexing *everything* which
- passes through the logger.
-
- :cvar pattern: A compiled regular expression, whose matches will be
- scrubbed from log messages and replaced with :cvar:`replacement`.
- :cvar easyFind: A simpler string to search for before regex matching.
- :cvar replacement: The string to replace ``pattern`` matches
- with. (default: ``"[scrubbed]"``)
- """
- pattern = re.compile("FILTERME")
- easyFind = "FILTERME"
- replacement = "[scrubbed]"
-
- def doubleCheck(self, match):
- """Subclasses should override this function to implement any additional
- substring filtering to decrease the false positive rate, i.e. any
- additional filtering or validation which is *more* costly than
- checking against the regular expression, :cvar:`pattern`.
-
- To use only the :cvar:`pattern` matching in :meth:`filter`, and not
- use this method, simply do::
-
- return True
-
- :param str match: Some portion of the :ivar:`logging.LogRecord.msg`
- string which has already passed the checks in :meth:`filter`, for
- which additional validation/checking is required.
- :rtype: bool
- :returns: ``True`` if the additional validation passes (in other
- words, the **match** *should* be filtered), and ``None`` or
- ``False`` otherwise.
- """
- return True
-
- def filter(self, record):
- """Filter a log record.
-
- The log **record** is filtered, and thus sanitised by replacing
- matching substrings with the :cvar:`replacement` string, if the
- following checks pass:
-
- 0. ``SAFELOGGING`` is currently enabled.
- 1. The ``record.msg`` string contains :cvar:`easyFind`.
- 2. The ``record.msg`` matches the regular expression,
- :cvar:`pattern`.
-
- :type record: :class:`logging.LogRecord`
- :param record: Basically, anything passed to :func:`logging.log`.
- """
- if safe_logging:
- msg = str(record.msg)
- if msg.find(self.easyFind) > 0:
- matches = self.pattern.findall(msg)
- for match in matches:
- if self.doubleCheck(match):
- msg = msg.replace(match, self.replacement)
- record.msg = msg
- return record
-
-
-class SafelogEmailFilter(BaseSafelogFilter):
- """A log filter which removes email addresses from log messages."""
-
- pattern = re.compile(
- "([a-zA-Z0-9]+[.+a-zA-Z0-9]*[@]{1}[a-zA-Z0-9]+[.-a-zA-Z0-9]*[.]{1}[a-zA-Z]+)")
- easyFind = "@"
-
- @functools.wraps(BaseSafelogFilter.filter)
- def filter(self, record):
- return BaseSafelogFilter.filter(self, record)
-
-
-class SafelogIPv4Filter(BaseSafelogFilter):
- """A log filter which removes IPv4 addresses from log messages."""
-
- pattern = re.compile("(?:\d{1,3}\.?){4}")
- easyFind = "."
-
- def doubleCheck(self, match):
- """Additional check to ensure that **match** is an IPv4 address."""
- if addr.isIPv4(match):
- return True
-
- @functools.wraps(BaseSafelogFilter.filter)
- def filter(self, record):
- return BaseSafelogFilter.filter(self, record)
-
-
-class SafelogIPv6Filter(BaseSafelogFilter):
- """A log filter which removes IPv6 addresses from log messages."""
-
- pattern = re.compile("([:]?[a-fA-F0-9:]+[:]+[a-fA-F0-9:]+){1,8}")
- easyFind = ":"
-
- def doubleCheck(self, match):
- """Additional check to ensure that **match** is an IPv6 address."""
- if addr.isIPv6(match):
- return True
-
- @functools.wraps(BaseSafelogFilter.filter)
- def filter(self, record):
- return BaseSafelogFilter.filter(self, record)
diff --git a/lib/bridgedb/schedule.py b/lib/bridgedb/schedule.py
deleted file mode 100644
index 0adbff8..0000000
--- a/lib/bridgedb/schedule.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_schedule -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Nick Mathewson
-# Isis Lovecruft <isis at torproject.org> 0xa3adb67a2cdb8b35
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-
-"""This module implements functions for dividing time into chunks."""
-
-import calendar
-
-import math
-
-from datetime import datetime
-
-from zope import interface
-from zope.interface import implements
-from zope.interface import Attribute
-
-
-#: The known time intervals (or *periods*) for dividing time by.
-KNOWN_INTERVALS = ["second", "minute", "hour", "day", "week", "month"]
-
-
-class UnknownInterval(ValueError):
- """Raised if an interval isn't one of the :data:`KNOWN_INTERVALS`."""
-
-
-def toUnixSeconds(timestruct):
- """Convert a datetime struct to a Unix timestamp in seconds.
-
- :param timestruct: A ``datetime.datetime`` object to convert into a
- timestamp in Unix Era seconds.
- :rtype: int
- """
- return calendar.timegm(timestruct)
-
-def fromUnixSeconds(timestamp):
- """Convert a Unix timestamp to a datetime struct.
-
- :param int timestamp: A timestamp in Unix Era seconds.
- :rtype: :type:`datetime.datetime`
- """
- return datetime.fromtimestamp(timestamp)
-
-
-class ISchedule(interface.Interface):
- """A ``Interface`` specification for a Schedule."""
-
- intervalPeriod = Attribute(
- "The type of period which this Schedule's intervals will rotate by.")
- intervalCount = Attribute(
- "Number of **intervalPeriod**s before rotation to the next interval")
-
- def intervalStart(when=None):
- """Get the start time of the interval that contains **when**."""
-
- def getInterval(when=None):
- """Get the interval which includes an arbitrary **when**."""
-
- def nextIntervalStarts(when=None):
- """Get the start of the interval after the one containing **when**."""
-
-
-class Unscheduled(object):
- """A base ``Schedule`` that has only one period that contains all time.
-
- >>> from bridgedb.schedule import fromUnixSeconds
- >>> from bridgedb.schedule import Unscheduled
- >>> timestamp = 1427769526
- >>> str(fromUnixSeconds(timestamp))
- '2015-03-31 02:38:46'
- >>> sched = Unscheduled()
- >>> start = sched.intervalStart(timestamp)
- >>> start
- -62135596800
- >>> str(fromUnixSeconds(start))
- '0001-01-01 00:00:00'
- >>> sched.getInterval(timestamp)
- '1970-01-01 00:00:00'
- >>> next = sched.nextIntervalStarts(timestamp)
- >>> next
- 253402300799
- >>> str(fromUnixSeconds(next))
- '9999-12-31 23:59:59'
-
- """
- implements(ISchedule)
-
- def __init__(self, count=None, period=None):
- """Create a schedule for dividing time into intervals.
-
- :param int count: The number of **period**s in an interval.
- :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
- """
- self.intervalCount = count
- self.intervalPeriod = period
-
- def intervalStart(self, when=0):
- """Get the start time of the interval that contains **when**.
-
- :param int when: The time which we're trying to find the corresponding
- interval for.
- :rtype: int
- :returns: The Unix epoch timestamp for the start time of the interval
- that contains **when**.
- """
- return toUnixSeconds(datetime.min.timetuple())
-
- def getInterval(self, when=0):
- """Get the interval that contains the time **when**.
-
- .. note: We explicitly ignore the ``when`` parameter in this
- implementation because if something is Unscheduled then
- all timestamps should reside within the same period.
-
- :param int when: The time which we're trying to find the corresponding
- interval for.
- :rtype: str
- :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
- specificity depends on what type of interval we're using. For
- example, if using ``"month"``, the return value would be something
- like ``"2013-12"``.
- """
- return fromUnixSeconds(0).strftime('%04Y-%02m-%02d %02H:%02M:%02S')
-
- def nextIntervalStarts(self, when=0):
- """Return the start time of the interval starting _after_ when.
-
- :rtype: int
- :returns: Return the Y10K bug.
- """
- return toUnixSeconds(datetime.max.timetuple())
-
-
-class ScheduledInterval(Unscheduled):
- """An class that splits time into periods, based on seconds, minutes,
- hours, days, weeks, or months.
-
- >>> from bridgedb.schedule import fromUnixSeconds
- >>> from bridgedb.schedule import ScheduledInterval
- >>> timestamp = 1427769526
- >>> str(fromUnixSeconds(timestamp))
- '2015-03-31 02:38:46'
- >>> sched = ScheduledInterval(5, 'minutes')
- >>> start = sched.intervalStart(timestamp)
- >>> start
- 1427769300
- >>> current = sched.getInterval(timestamp)
- >>> current
- '2015-03-31 02:35:00'
- >>> current == str(fromUnixSeconds(start))
- True
- >>> next = sched.nextIntervalStarts(timestamp)
- >>> next
- 1427769600
- >>> str(fromUnixSeconds(next))
- '2015-03-31 02:40:00'
- >>> later = 1427771057
- >>> str(fromUnixSeconds(later))
- '2015-03-31 03:04:17'
- >>> sched.getInterval(later)
- '2015-03-31 03:00:00'
-
- :ivar str intervalPeriod: One of the :data:`KNOWN_INTERVALS`.
- :ivar int intervalCount: The number of times **intervalPeriod** should be
- repeated within an interval.
- """
- implements(ISchedule)
-
- def __init__(self, count=None, period=None):
- """Create a schedule for dividing time into intervals.
-
- :type count: int or str
- :param count: The number of **period**s in an interval.
- :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
- """
- super(ScheduledInterval, self).__init__(count, period)
- self._setIntervalCount(count)
- self._setIntervalPeriod(period)
-
- def _setIntervalCount(self, count=None):
- """Set our :ivar:`intervalCount`.
-
- .. attention:: This method should be called _before_
- :meth:`_setIntervalPeriod`, because the latter may change the
- count, if it decides to change the period (for example, to
- simplify things by changing weeks into days).
-
- :param int count: The number of times the :ivar:`intervalPeriod`
- should be repeated during the interval. Defaults to ``1``.
- :raises UnknownInterval: if the specified **count** was invalid.
- """
- try:
- if not count > 0:
- count = 1
- count = int(count)
- except (TypeError, ValueError):
- raise UnknownInterval("%s.intervalCount: %r ist not an integer."
- % (self.__class__.__name__, count))
- self.intervalCount = count
-
- def _setIntervalPeriod(self, period=None):
- """Set our :ivar:`intervalPeriod`.
-
- :param str period: One of the :data:`KNOWN_INTERVALS`, or its
- plural. Defaults to ``'hour'``.
- :raises UnknownInterval: if the specified **period** is unknown.
- """
- if not period:
- period = 'hour'
- try:
- period = period.lower()
- # Depluralise the period if necessary, i.e., "months" -> "month".
- if period.endswith('s'):
- period = period[:-1]
-
- if not period in KNOWN_INTERVALS:
- raise ValueError
- except (TypeError, AttributeError, ValueError):
- raise UnknownInterval("%s doesn't know about the %r interval type."
- % (self.__class__.__name__, period))
- self.intervalPeriod = period
-
- if period == 'week':
- self.intervalPeriod = 'day'
- self.intervalCount *= 7
-
- def intervalStart(self, when=0):
- """Get the start time of the interval that contains **when**.
-
- :param int when: The time which we're trying to determine the start of
- interval that contains it. This should be given in Unix seconds,
- for example, taken from :func:`calendar.timegm`.
- :rtype: int
- :returns: The Unix epoch timestamp for the start time of the interval
- that contains **when**.
- """
- # Convert `when`s which are floats, i.e. from time.time(), to ints:
- when = int(math.ceil(when))
-
- if self.intervalPeriod == 'month':
- # For months, we always start at the beginning of the month.
- date = fromUnixSeconds(when)
- months = (date.year * 12) + (date.month - 1)
- months -= (months % self.intervalCount)
- month = months % 12 + 1
- return toUnixSeconds((months // 12, month, 1, 0, 0, 0))
- elif self.intervalPeriod == 'day':
- # For days, we start at the beginning of a day.
- when -= when % (86400 * self.intervalCount)
- return when
- elif self.intervalPeriod == 'hour':
- # For hours, we start at the beginning of an hour.
- when -= when % (3600 * self.intervalCount)
- return when
- elif self.intervalPeriod == 'minute':
- when -= when % (60 * self.intervalCount)
- return when
- elif self.intervalPeriod == 'second':
- when -= when % self.intervalCount
- return when
-
- def getInterval(self, when=0):
- """Get the interval that contains the time **when**.
-
- >>> import calendar
- >>> from bridgedb.schedule import ScheduledInterval
- >>> sched = ScheduledInterval(1, 'month')
- >>> when = calendar.timegm((2007, 12, 12, 0, 0, 0))
- >>> sched.getInterval(when)
- '2007-12'
- >>> then = calendar.timegm((2014, 05, 13, 20, 25, 13))
- >>> sched.getInterval(then)
- '2014-05'
-
- :param int when: The time which we're trying to find the corresponding
- interval for. Given in Unix seconds, for example, taken from
- :func:`calendar.timegm`.
- :rtype: str
- :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
- specificity depends on what type of interval we're using. For
- example, if using ``"month"``, the return value would be something
- like ``"2013-12"``.
- """
- date = fromUnixSeconds(self.intervalStart(when))
-
- fstr = "%04Y-%02m"
- if self.intervalPeriod != 'month':
- fstr += "-%02d"
- if self.intervalPeriod != 'day':
- fstr += " %02H"
- if self.intervalPeriod != 'hour':
- fstr += ":%02M"
- if self.intervalPeriod == 'minute':
- fstr += ":%02S"
-
- return date.strftime(fstr)
-
- def nextIntervalStarts(self, when=0):
- """Return the start time of the interval starting _after_ when.
-
- :returns: The Unix epoch timestamp for the start time of the interval
- that contains **when**.
- """
- seconds = self.intervalStart(when)
-
- if self.intervalPeriod == 'month':
- date = fromUnixSeconds(seconds)
- year = date.year
- months = date.month + self.intervalCount
- if months > 12:
- year = date.year + 1
- months = months - 12
- return toUnixSeconds((year, months, 1, 0, 0, 0))
- elif self.intervalPeriod == 'day':
- return seconds + (86400 * self.intervalCount)
- elif self.intervalPeriod == 'hour':
- return seconds + (3600 * self.intervalCount)
- elif self.intervalPeriod == 'minute':
- return seconds + (60 * self.intervalCount)
- elif self.intervalPeriod == 'second':
- return seconds + self.intervalCount
diff --git a/lib/bridgedb/sitecustomize.py b/lib/bridgedb/sitecustomize.py
deleted file mode 100644
index 264e5e1..0000000
--- a/lib/bridgedb/sitecustomize.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-"""sitecustomize â Handles potential loading of extra code when Python starts.
-
-**Module Usage:**
-
-This is normally (this seems not to work with Twisted, for as-of-yet unknown
-reasons) useful for using :mod:`coverage` to measure code execution in spawned
-subprocesses in the following way:
-
- 1. Set the environment variable ``COVERAGE_PROCESS_START`` to the absolute
- path of the coverage config file. If you are in the top-level of the
- bridgedb repo, do:
-
- $ export COVERAGE_PROCESS_START="${PWD}/.coveragerc"
-
- 2. In that coverage config file, in the ``[run]`` section, set
- ``parallel = True``.
-
- 3. Run coverage. From the top-level of the bridgedb repo, try doing:
-
- $ make reinstall && \
- coverage run $(which trial) ./lib/bridgedb/test/test_* && \
- coverage combine && coverage report && coverage html
-
-If ``COVERAGE_PROCESS_START`` is not set, this code does nothing,
-``[run] parallel`` should be set to ``False``, and coverage can be run by
-leaving out the ``coverage combine`` portion of the above command.
-
-To view the output HTML coverage data, open
-``path/to/bridgedb_repo/doc/coverage_html/index.html`` in a browser.
-"""
-
-import coverage
-coverage.process_startup()
diff --git a/lib/bridgedb/strings.py b/lib/bridgedb/strings.py
deleted file mode 100644
index e108a88..0000000
--- a/lib/bridgedb/strings.py
+++ /dev/null
@@ -1,565 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_strings ; -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Commonly used string constants.
-
-.. todo:: The instructions for the OpenPGP keys in
- :data:`BRIDGEDB_OPENPGP_KEY` are not translated⦠should we translate them?
- Should we tell users where to obtain GPG4Win/GPGTools/gnupg? Should those
- instruction be that verbose? Or should we get rid of the instructions
- altogether, and assume that any encouragement towards using GPG will just
- make users more frustrated, and (possibly) (mis-)direct that frustration
- at Tor or BridgeDB?
-"""
-
-from __future__ import unicode_literals
-
-# This won't work on Python2.6, however
-# 1) We don't use Python2.6, and
-# 2) We don't care about supporting Python2.6, because Python 2.6 (and,
-# honestly, all of Python2) should die.
-from collections import OrderedDict
-
-
-def _(text):
- """This is necessary because strings are translated when they're imported.
- Otherwise this would make it impossible to switch languages more than
- once.
-
- :returns: The **text**.
- """
- return text
-
-
-# TRANSLATORS: Please do not translate the word "TYPE".
-EMAIL_MISC_TEXT = {
- 0: _("""\
-[This is an automated message; please do not reply.]"""),
- 1: _("""\
-Here are your bridges:"""),
- 2: _("""\
-You have exceeded the rate limit. Please slow down! The minimum time between
-emails is %s hours. All further emails during this time period will be ignored."""),
- 3: _("""\
-COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"""),
- # TRANSLATORS: Please DO NOT translate the word "BridgeDB".
- 4: _("Welcome to BridgeDB!"),
- # TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE".
- 5: _("Currently supported transport TYPEs:"),
- 6: _("Hey, %s!"),
- 7: _("Hello, friend!"),
- 8: _("Public Keys"),
- # TRANSLATORS: This string will end up saying something like:
- # "This email was generated with rainbows, unicorns, and sparkles
- # for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
- 9: _("""\
-This email was generated with rainbows, unicorns, and sparkles
-for %s on %s at %s."""),
-}
-
-WELCOME = {
- # TRANSLATORS: Please DO NOT translate "BridgeDB".
- # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
- # TRANSLATORS: Please DO NOT translate "Tor".
- # TRANSLATORS: Please DO NOT translate "Tor Network".
- 0: _("""\
-BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,
-which can help obfuscate your connections to the Tor Network, making it more
-difficult for anyone watching your internet traffic to determine that you are
-using Tor.\n\n"""),
-
- # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
- 1: _("""\
-Some bridges with IPv6 addresses are also available, though some Pluggable
-Transports aren't IPv6 compatible.\n\n"""),
-
- # TRANSLATORS: Please DO NOT translate "BridgeDB".
- # TRANSLATORS: The phrase "plain-ol'-vanilla" means "plain, boring,
- # regular, or unexciting". Like vanilla ice cream. It refers to bridges
- # which do not have Pluggable Transports, and only speak the regular,
- # boring Tor protocol. Translate it as you see fit. Have fun with it.
- 2: _("""\
-Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any
-Pluggable Transports %s which maybe doesn't sound as cool, but they can still
-help to circumvent internet censorship in many cases.\n\n"""),
-}
-"""These strings should go on the first "Welcome" email sent by the
-:mod:`~bridgedb.EmailServer`, as well as on the ``index.html`` template used
-by the :mod:`~bridgedb.https.server`. They are used as an introduction to
-explain what Tor bridges are, what bridges do, and why someone might want to
-use bridges.
-"""
-
-FAQ = {
- 0: _("What are bridges?"),
- 1: _("""\
-%s Bridges %s are Tor relays that help you circumvent censorship."""),
-}
-
-OTHER_DISTRIBUTORS = {
- 0: _("I need an alternative way of getting bridges!"),
- 1: _("""\
-Another way to get bridges is to send an email to %s. Please note that you must
-send the email using an address from one of the following email providers:
-%s, %s or %s."""),
-}
-
-HELP = {
- 0: _("My bridges don't work! I need help!"),
- # TRANSLATORS: Please DO NOT translate "Tor".
- 1: _("""If your Tor doesn't work, you should email %s."""),
- # TRANSLATORS: Please DO NOT translate "Pluggable Transports".
- # TRANSLATORS: Please DO NOT translate "Tor Browser".
- # TRANSLATORS: Please DO NOT translate "Tor".
- 2: _("""\
-Try including as much info about your case as you can, including the list of
-bridges and Pluggable Transports you tried to use, your Tor Browser version,
-and any messages which Tor gave out, etc."""),
-}
-
-BRIDGES = {
- 0: _("Here are your bridge lines:"),
- 1: _("Get Bridges!"),
-}
-
-OPTIONS = {
- 0: _("Please select options for bridge type:"),
- 1: _("Do you need IPv6 addresses?"),
- 2: _("Do you need a %s?"),
-}
-
-CAPTCHA = {
- 0: _('Your browser is not displaying images properly.'),
- 1: _('Enter the characters from the image above...'),
-}
-
-HOWTO_TBB = {
- 0: _("""How to start using your bridges"""),
- # TRANSLATORS: Please DO NOT translate "Tor Browser".
- 1: _("""\
-To enter bridges into Tor Browser, first go to the %s Tor Browser download
-page %s and then follow the instructions there for downloading and starting
-Tor Browser."""),
- # TRANSLATORS: Please DO NOT translate "Tor".
- 2: _("""\
-When the 'Tor Network Settings' dialogue pops up, click 'Configure' and follow
-the wizard until it asks:"""),
- # TRANSLATORS: Please DO NOT translate "Tor".
- 3: _("""\
-Does your Internet Service Provider (ISP) block or otherwise censor connections
-to the Tor network?"""),
- # TRANSLATORS: Please DO NOT translate "Tor".
- 4: _("""\
-Select 'Yes' and then click 'Next'. To configure your new bridges, copy and
-paste the bridge lines into the text input box. Finally, click 'Connect', and
-you should be good to go! If you experience trouble, try clicking the 'Help'
-button in the 'Tor Network Settings' wizard for further assistance."""),
-}
-
-EMAIL_COMMANDS = {
- "get help": _("Displays this message."),
-# TRANSLATORS: Please try to make it clear that "vanilla" here refers to the
-# same non-Pluggable Transport bridges described above as being
-# "plain-ol'-vanilla" bridges.
- "get bridges": _("Request vanilla bridges."),
- "get ipv6": _("Request IPv6 bridges."),
- # TRANSLATORS: Please DO NOT translate the word the word "TYPE".
- "get transport [TYPE]": _("Request a Pluggable Transport by TYPE."),
- # TRANSLATORS: Please DO NOT translate "BridgeDB".
- # TRANSLATORS: Please DO NOT translate "GnuPG".
- "get key": _("Get a copy of BridgeDB's public GnuPG key."),
- #"subscribe": _("Subscribe to receive new bridges once per week"),
- #"unsubscribe": _("Cancel a subscription to new bridges"),
-}
-
-#-----------------------------------------------------------------------------
-# All of the following containers are untranslated!
-#-----------------------------------------------------------------------------
-
-#: SUPPORTED TRANSPORTS is dictionary mapping all Pluggable Transports
-#: methodname to whether or not we actively distribute them. The ones which we
-#: distribute SHOULD have the following properties:
-#:
-#: 1. The PT is in a widely accepted, usable state for most Tor users.
-#: 2. The PT is currently publicly deployed *en masse*".
-#: 3. The PT is included within the transports which Tor Browser offers in
-#: the stable releases.
-#:
-#: These will be sorted by methodname in alphabetical order.
-#:
-#: ***Don't change this setting here; change it in :file:`bridgedb.conf`.***
-SUPPORTED_TRANSPORTS = {}
-
-#: DEFAULT_TRANSPORT is a string. It should be the PT methodname of the
-#: transport which is selected by default (e.g. in the webserver dropdown
-#: menu).
-#:
-#: ***Don't change this setting here; change it in :file:`bridgedb.conf`.***
-DEFAULT_TRANSPORT = ''
-
-def _getSupportedTransports():
- """Get the list of currently supported transports.
-
- :rtype: list
- :returns: A list of strings, one for each supported Pluggable Transport
- methodname, sorted in alphabetical order.
- """
- supported = [name.lower() for name,w00t in SUPPORTED_TRANSPORTS.items() if w00t]
- supported.sort()
- return supported
-
-def _setDefaultTransport(transport):
- global DEFAULT_TRANSPORT
- DEFAULT_TRANSPORT = transport
-
-def _getDefaultTransport():
- return DEFAULT_TRANSPORT
-
-def _setSupportedTransports(transports):
- """Set the list of currently supported transports.
-
- .. note: You shouldn't need to touch this. This is used by the config file
- parser. You should change the SUPPORTED_TRANSPORTS dictionary in
- :file:`bridgedb.conf`.
-
- :param dict transports: A mapping of Pluggable Transport methodnames
- (strings) to booleans. If the boolean is ``True``, then the Pluggable
- Transport is one which we will (more easily) distribute to clients.
- If ``False``, then we (sort of) don't distribute it.
- """
- global SUPPORTED_TRANSPORTS
- SUPPORTED_TRANSPORTS = transports
-
-def _getSupportedAndDefaultTransports():
- """Get a dictionary of currently supported transports, along with a boolean
- marking which transport is the default.
-
- It is returned as a :class:`collections.OrderedDict`, because if it is a
- regular dict, then the dropdown menu would populated in random order each
- time the page is rendered. It is sorted in alphabetical order.
-
- :rtype: :class:`collections.OrderedDict`
- :returns: An :class:`~collections.OrderedDict` of the Pluggable Transport
- methodnames from :data:`SUPPORTED_TRANSPORTS` whose value in
- ``SUPPORTED_TRANSPORTS`` is ``True``. If :data:`DEFAULT_TRANSPORT` is
- set, then the PT methodname in the ``DEFAULT_TRANSPORT`` setting is
- added to the :class:`~collections.OrderedDict`, with the value
- ``True``. Every other transport in the returned ``OrderedDict`` has
- its value set to ``False``, so that only the one which should be the
- default PT is ``True``.
- """
- supported = _getSupportedTransports()
- transports = OrderedDict(zip(supported, [False for _ in range(len(supported))]))
-
- if DEFAULT_TRANSPORT:
- transports[DEFAULT_TRANSPORT] = True
-
- return transports
-
-EMAIL_SPRINTF = {
- # Goes into the "%s types of Pluggable Transports %s" part of ``WELCOME[0]``
- "WELCOME0": ("", "[0]"),
- # Goes into the "%s without Pluggable Transport %s" part of ``WELCOME[2]``
- "WELCOME2": ("-", "-"),
- # For the "%s Tor Browser download page %s" part of ``HOWTO_TBB[1]``
- "HOWTO_TBB1": ("", "[0]"),
- # For the "you should email %s" in ``HELP[0]``
- "HELP0": ("help at rt.torproject.org"),
-}
-"""``EMAIL_SPRINTF`` is a dictionary that maps translated strings which
-contain format specifiers (i.e. ``%s``) to what those format specifiers should
-be replaced with in a given template system.
-
-For example, a string which needs a pair of HTML ``("<a href=''">, "</a>")``
-tags (for the templates used by :mod:`bridgedb.https.server`) would need some
-alternative replacements for the :mod:`EmailServer`, because the latter uses
-templates with a ``text/plain`` mimetype instead of HTML. For the
-``EmailServer``, the format strings specifiers are replaced with an empty
-string where the opening ``<a>`` tags would go, and a numbered Markdown link
-specifier where the closing ``</a>`` tags would go.
-
-The keys in this dictionary are the Python variable names of the corresponding
-strings which are being formatted, i.e. ``WELCOME0`` would be the string
-replacements for ``strings.WELCOME.get(0)``.
-
-
-For example, the ``0`` string in :data:`WELCOME` above has the substring::
-
- "%s without Pluggable Transport %s"
-
-and so to replace the two ``%s`` format specifiers, you would use this mapping
-like so::
-
->>> from bridgedb import strings
->>> welcome = strings.WELCOME[0] % strings.EMAIL_SPRINTF["WELCOME0"]
->>> print welcome.split('\n')[0]
-BridgeDB can provide bridges with several types of Pluggable Transports[0],
-
-"""
-
-EMAIL_REFERENCE_LINKS = {
- "WELCOME0": "[0]: https://www.torproject.org/docs/pluggable-transports.html",
- "HOWTO_TBB1": "[0]: https://www.torproject.org/projects/torbrowser.html",
-}
-
-BRIDGEDB_OPENPGP_KEY = """\
-# This keypair contains BridgeDB's online signing and encryption subkeys. This
-# keypair rotates because it is kept online. However, the current online
-# keypair will *ALWAYS* be certified by the offline keypair (at the bottom of
-# this file).
-#
-# If you receive an email from BridgeDB, it should be signed with the
-# 21B554E95938F4D0 subkey from the following keypair:
-
-# pub 4096R/8DC43A2848821E32 2013-09-11 [expires: 2015-09-11]
-# Key fingerprint = DF81 1109 E17C 8BF1 34B5 EEB6 8DC4 3A28 4882 1E32
-# uid BridgeDB <bridges at bridges.torproject.org>
-# sub 4096R/21B554E95938F4D0 2013-09-11 [expires: 2015-09-11]
-# Key fingerprint = 9FE3 9D1A 7438 9223 3B3F 66F2 21B5 54E9 5938 F4D0
-# sub 4096R/E7793047C5B54232 2013-09-11 [expires: 2015-09-11]
-# Key fingerprint = CFFB 8469 9048 37E7 8CAE 322C E779 3047 C5B5 4232
-
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQINBFIv8YABEADRqvfLB4xWj3Fz+HEmUUt/qbJnZhqIjo5WBHaBJOmrzx1c9fLN
-aYG36Hgo6A7NygI1oQmFnDinSrZAtrPaT63d1Jg49yZwr/OhMaxHYJElMFHGJ876
-kLZHmQTysquYKDHhv+fH51t7UVaZ9NkP5cI+V3pqck0DW5DwMsVJXNaU317kk9me
-mPJUDMb5FM4d2Vtk1N+54bHJgpgmnukNtpJmRyHRbZBqNMln5nWF7vdZ4u5PGPWj
-bA0rPZhayeE3FQ0MHiGL12kHAy30pfg54QfPJDQBCywjABetRE+xaM9TcS+R31Pf
-2VbLeb+Km7QpHMwOXI5xZLss9BAWm9EBbmXxuqaRBHyi830jjCrK9UYuzzOqKoUV
-Mk1BRelZTFnGPWeVTE+Ps+pwJ0Dwx4ghppJBCoArmEbkNliblxR/2wYOOFi/ZVA4
-Zc2ok9T3rBLVg07b7ezFUScGiTnc7ac7hp6r8Qsh09ZbhRr9erK/n194aEvkXTfr
-qepwrAE7YeF4YuR206UOFFWDhxWDLbRu0gIWgrevEQu/cvQPrO9uH5fL6Gw/+mNP
-Q/NIteejhkDyvyTUKyBu7x+Gls71zT2u/X13eOAJ8IxBkSVRKQ8tRD+oqJkWplOf
-+BpaGU+g6u4kT2AzFDxTOupfrYcPvORTAV/V3suys2YQE4x422GASXDivQARAQAB
-tClCcmlkZ2VEQiA8YnJpZGdlc0BicmlkZ2VzLnRvcnByb2plY3Qub3JnPokDJQQT
-AQoBD0gUgAAAAAAXACh2ZXJpZmllZEB0b3Jwcm9qZWN0Lm9yZ0RGODExMTA5RTE3
-QzhCRjEzNEI1RUVCNjhEQzQzQTI4NDg4MjFFMzJPFIAAAAAAHgAoYnJpZGdlc0Bi
-cmlkZ2VzLnRvcnByb2plY3Qub3JnREY4MTExMDlFMTdDOEJGMTM0QjVFRUI2OERD
-NDNBMjg0ODgyMUUzMioaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2plY3Qub3JnL3Bv
-bGljeS50eHQCGwEDCw0JBBUKCQgEFgIBAAIeAQIXgCcYaHR0cHM6Ly9icmlkZ2Vz
-LnRvcnByb2plY3Qub3JnL2tleS5hc2MFAlSKBKIFCQPDTiIACgkQjcQ6KEiCHjIs
-jg//bJ12eRnBMfIGzOGh+T4wz7/YyKLfARAMnqDnSxhTxuE+M5hWm3QbxP03R6eY
-x+PKwQaDJSmm7HhRhltb7QXUe8dqjnocFwwagpoLZ/81mBLxByqg5TKHGGIGy+DX
-omIzCq5ijx1IUkHlgh708a5alG7bjRTqedT4Wxxyl6psGzDhGQdS8bqx/f32nQaE
-h41l+A/EY1g2HVqky63ZHAP3S2v+mWCrk5DnkElc0229MXqaBuEr4nbYMXRkahMb
-E2gnCmdSoeD21AY6bNyz7IcJGpyKCx9+hVgPjpm3J23JEYyPL+s48jn6QcI/Q2gD
-yAtgU65y6IrdYn8SwkABI1FIq9WAwG7DaInxvkqkYqyBQLaZJEMyX8NTBvFoT5JS
-jnkxG0xu61Vxq0BLYBIOJE0VFHAJ40/jOvSxQJkQhu9G4BK7htnADbtstmMDMM3q
-xuuO5pcj2rl7YthNunyZ1yhPHXijUUyKrwR9piENpptztFBVN6+ijqU/TmWMOtbH
-X7p9F+3tXCHHqwO5U/JMtsb/9M39MR8BrdcLc7m6dCpeuSUuR2LLroh+MoMJGviI
-iesxHF95kFqkJAecW1Z3eKL9vrlbfO3waeuCi18k1TePnZuG5lmf2KjKDW5vHK4O
-WFqvvfK2kxkCUjvGdLeTOAVOV+X+PQ23jvBJO2bS7YbOb9C5Ag0EUi/ygQEQALZ/
-p7xRINHY7MMf3/bo/I0WRxWHd1AE9tRToyEg1S2u1YrWWL5M9D8saRsp9cpnpGEu
-hW3vu7G4nasY27qOz4bSKu1YMAVIC58v1tEnBqdo1zErNjhs38PrmJKbbs9tDfYY
-Oi2x0GlhMbIrNStcZpnCdLa6U6NLMbggDL1GxjMPYBMi4TtLgcIeRDUSjsZscZkg
-Kxs5QkSVc3SrYyraayIc8WtIpDLcxPt6/g90rbatZzBfO+93Rz7qUXHmgzuM0hy1
-Fvn619o3I5DsWrfOz9t/QuznoOBw4PfzDPNT7VlzZN4xHAcr5+7B+DH9IsvlCt5N
-kQFuYpFZCpXNaD2XOtmIqjTCeLNfcgTEj0qoUIEKyKbBIgfP+7S2tLXy8JKUTy5g
-9kxXQeHueLykQ4Mt18JH0nMHbHbQl0K3LGT4ucRDOmjNtlQCltVLkIk3GimyqKs/
-vdZ9c+dm4Akx1qsJcwvveX+imJe2e9RUodcxWXxWrYnuPa5b5nfR1i+GfV0on/Pt
-AQ8gc9CkJpMiq5TQDOFhFP6yQcq77sXuUkEl5qamptedz28E0I693ulnfwcsE80p
-xkpIG6n33DZJSEyqgtWjE1P2pnsVfO5ILs3mKLe7bO1v3qMXcCkMCGH/kwzvtowq
-YvY4gaZMDZtQFY8U7lI9FdRUvVdeHAB24y291nhzABEBAAGJBYMEGAEKANNIFIAA
-AAAAFwAodmVyaWZpZWRAdG9ycHJvamVjdC5vcmdERjgxMTEwOUUxN0M4QkYxMzRC
-NUVFQjY4REM0M0EyODQ4ODIxRTMyTxSAAAAAAB4AKGJyaWRnZXNAYnJpZGdlcy50
-b3Jwcm9qZWN0Lm9yZ0RGODExMTA5RTE3QzhCRjEzNEI1RUVCNjhEQzQzQTI4NDg4
-MjFFMzIqGmh0dHBzOi8vYnJpZGdlcy50b3Jwcm9qZWN0Lm9yZy9wb2xpY3kudHh0
-AhsCBQJUigTTBQkDw01SAqTB2CAEGQEKAIEFAlIv8oFPFIAAAAAAHgAoYnJpZGdl
-c0BicmlkZ2VzLnRvcnByb2plY3Qub3JnOUZFMzlEMUE3NDM4OTIyMzNCM0Y2NkYy
-MjFCNTU0RTk1OTM4RjREMCoaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2plY3Qub3Jn
-L3BvbGljeS50eHQACgkQIbVU6Vk49NDbPw/5ATe/T8+eaToC3v0TYNRH5nveQvzA
-WdnshD3lnvfsgDhbilwifKpc5LHKXU3rvb42HH2cu0ckuksdDTvICZD9cJjRq/F+
-Mzm0pNCAJg0pQnHaaWFQjw+CHYEoizai3S+iYxhNHeSdA6Ty7xm4+bHNf0Aqblbd
-6dKwq9EvjwAI6zZsAHtsmHRUMdrFwGdKae6CSchUT2JQFBPEWMhvzdpDGACWVaSP
-sxYKuYg9LgpswGcof+tprRjKRl8MtSh0ufjbVBlTeSKpL5Y+fcTRD3PI8w7Ocr3z
-jr6XpYG4SUNHsWwxyu/DTXg76Lk1/+BdaH25hDOAasLUOU7yRL8zD/c7M0FkGXdj
-r5I2DEEqwzJ9cPHWjpgb8N9fZLoPFP9JOmKGHINqxNe7TfwiTdD6uDKs/u/QK1U+
-o3iYBXBTREdopPUdBTM9wYRUhyGXTEKLhUP3MGpXYlgeYPrSdp76VyN3BzLTbMv+
-+7rxyKxL9cWYU0pnXHgPC5nyHX5nqXmhMnkxAD3Bnm8n9XDfgiyTDExqksEh2VXt
-yhVfLezylEP2fwtd8/mABBCsTjzZW6FRfRRAjUZWZGFpFg8no1x5JS9uiswRP5g1
-qHijNFWpGyTtJWl5VNd0d9+LtVUX1jRpDUpsjZcxqs3fsvw2p+H/zQ1wFvDrsoav
-hqOTq+AEnJc7ZG8JEI3EOihIgh4ych8P/3GTyWb29+43YVibbSPPvEv4gFqziC+9
-1p92FJ0V4XdaT7TW3qaZVp5edUNwB/jjF0SxBybwaMX2ZIGXOjnjF6/Zby4ynuTX
-vZkS1mKRA0KWupB3e9PSMY3ZtssnqpGna/+3qlpxtunW7HcW4nCF/f59WHhlVjaO
-MXjtuWj59yB56Dd1sNjwhcNCyp4/NpzGnRW97ZV3Pp4oqIOqcGzCQXkVPcnaqcOh
-Cs9vIDJlMtn/IWBzUGimuRllDSSVSWkYkyJcG2NUHUwgXYpLwQz7sScvmCPchf4K
-qarpX0FpkUDfqaVVuQ7A2XbPUAVFzIk930G1WzgOuOdg9vhWSEjou+SKrAoMz90w
-3xHwEvmPDTTVJQft9ytoRbwZkIPfzzhII3mr4agbORAfzDaj5g/f6CVRdg6D3ME1
-Etg9ZrfLgRY993g/arfIME6OOsiNcy5+PunN96Rw0o1xoD+97NmZuQrs/p4Mfn5o
-8EwXHutREhahin+3/SV3hz9ReeLYmClq+OVhjPzPdtwZsFoyQyUJoFVHPTuSdChZ
-FPaqN68FjlNMugmxnvski3ZDVT7pw3B6otjjaL3rr6q0PC2yhEb2ntb3IFUizHjn
-80SmfE1Bqwit7ZHu8r/Gt/0iecGk5h84VzSgiGZGF/7m1i5UMVlNSeWnsInGa5Su
-7HSzfMq+YmkzuQINBFIv8p4BEADTOLR5e5NKKRPpjCb4B/8YYkWh+orb70EogIZ6
-j5v8d/djLyhjqZ9BIkh41/hYKMwnsa4KkDkTaX0eNu3BFB2zGgZ1GSd7525ESxiq
-suXIlAg2pex7bysaFfua0nUx64tmaQm2XArdkj/wI0pbg+idWym3WQQmZLyTTbzl
-8rpTEtTt+S2m6z3EeAhEHuNFH16hEDUywlef3EotX3njuFiLqaNvnzUYDxhUvKd2
-2K1es1ggispgP+eb1bkMApxecf2rqmSUEcvsuTWip4oGZPBLGDQeNKHkCUVbj4wT
-yWDIRtto3wi+4CFPEVzw+htj1cQfTstPqUdG7NSOmLQggedoUdv7AJm4MJJiyEax
-l+IAf6Afwrrm3eOSv0PgoUxOrUb9vhIoL8ih8gtiqvQ9qYaRQfQA/w3Z0Su2Yfoc
-fQS8Uw99qG+oTgieG6F6ud8+hMZAYVZFqbU+ztzMyDE6h4Hflkt6VNJ0Hk0VoF38
-TTs77pHXXBbLD6SzR6tbNuR9r/lbmC8Qf2A1ZAThR0iuGhNRFtUPo28GxakxGdLZ
-9kHIxjl7EN/gsmYTwuEhr+yfNtLwtSH0ojeqbDmgufvgh+SITCtyNDAUspjrZYEt
-F0NHRpSom2NFVELMqMRydU/ncph1rGZgVp6/zVj6xIlhKmqj5P1y/9B0c4Tu1CzJ
-pkJ5wwARAQABiQLpBBgBCgDTSBSAAAAAABcAKHZlcmlmaWVkQHRvcnByb2plY3Qu
-b3JnREY4MTExMDlFMTdDOEJGMTM0QjVFRUI2OERDNDNBMjg0ODgyMUUzMk8UgAAA
-AAAeAChicmlkZ2VzQGJyaWRnZXMudG9ycHJvamVjdC5vcmdERjgxMTEwOUUxN0M4
-QkYxMzRCNUVFQjY4REM0M0EyODQ4ODIxRTMyKhpodHRwczovL2JyaWRnZXMudG9y
-cHJvamVjdC5vcmcvcG9saWN5LnR4dAIbDAUCVIoE4QUJA8NNQwAKCRCNxDooSIIe
-Mo7JEADDBtQpYxPhbj3MT0xpk96EDlon/5yHVf+cnk1pNisc+HkJsVe1nh7gAWOz
-wJKdeqOVpgxiJTxIQdl6mipKwwFi0DreP7h56s1WQkuSSWJzqssAwWHfVAsX13fV
-zWd0XyxN/OF9ZKQjX4qwpJ/na631PSwZLvHYhMaZnb9pjNwC5/PEKRmFqLbQT6Px
-12miZT6ToPDCczHxJ4BxbEGVU+PtRsHwmTRT3JhxFNDfeVd+uwsQIMidJbUoqVW7
-fe2zNd0TaWyz4Rw087oZE2OXdctjvtsu8fzXx6d/tkazI6cUOqoaMTR41KEu5X0T
-BpWSAMADBYjNs9QRWXX7ZlsJRUSCX1EKbMhgoL6KIGceIkjH61M/LF6HqDgSgSWt
-h+LIYGa+LrB/6819o32QSOSHHJ5+NJrbCSaLgKE/LKnf92V2QbZE8IGY6EOSjHqn
-n1+j+CLRKY/kUyvk+1TumTghjg/aDs/8Jv8PvgSWLQ0q1rxHYbX7q9ZJhYC/4LdR
-ya/Cho6w2l0N3tV/IMAwvFNHsaiIiiwfoOQbkBUvkyzBwjKt96Ai4I0QKt/63uH0
-drQhlJEgIyGkOrorBByVqZAQdnoLENYIu6tDUj0bTbGObKqua4iPlSK3/g40zCm4
-9OgcN7A8kFuNpgp2EHqj1/jrwd7mZYKsWTuGiR/7fwXf+4xbvg==
-=raCx
------END PGP PUBLIC KEY BLOCK-----
-
-# The following keypair is BridgeDB's offline certification-only keypair. It
-# is used to sign new online signing/encryption keypairs.
-#
-# If you import this key and mark it as trusted, emails from BridgeDB (if
-# signed correctly with the online keypair above) should always be trusted. To
-# do this, open a shell and do:
-#
-# $ curl -O https://bridges.torproject.org/keys
-# $ gpg --import keys
-# $ gpg --check-sigs 7B78437015E63DF47BB1270ACBD97AA24E8E472E
-# $ gpg --edit-key 7B78437015E63DF47BB1270ACBD97AA24E8E472E
-#
-# Then type 'trust' to set the trust level. Choose a number that you like.
-# Next type 'quit'. Finally, to create a local signature which will will not
-# be uploaded to keyservers:
-#
-# $ gpg --lsign-key 7B78437015E63DF47BB1270ACBD97AA24E8E472E
-#
-
-# pub 16384R/CBD97AA24E8E472E 2013-10-12
-# Key fingerprint = 7B78 4370 15E6 3DF4 7BB1 270A CBD9 7AA2 4E8E 472E
-# uid BridgeDB (Offline ID Key) <bridges at bridges.torproject.org>
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQgNBFJZB+QBQADcx7laikgZOZXLm6WH2mClm7KrRChmQAHOmzvRYTElk+hVZJ6g
-qSUTdl8fvfhifZPCd3g7nJBtOhQAGlrHmJRXfdf4cTRuD73nggbYQ0NRR9VZ3MIK
-ToJDELBhgmWeNKpLcPsTpi2t9qrHf3xxM06OdxOs9lCGtW7XVYnKx3vaRNk6c0ln
-De82ZWnZr1eMoPzcjslw7AxI94hIgV1GDwTSpBndv/VwgLeBC5XNCKv0adhO/RSt
-fuZOHGT/HfI0U0C3fSTiIu4lJqEd9Qe8LUFQ7wRMrf3KSWwyWNb/OtyMfZ52PEg9
-SMWEfpr6aGwQu6yGPsE4SeHsiew5IqCMi64TZ9IcgY0fveiDzMSIAqnWQcxSL0SH
-YbwQPxuOc4Rxj/b1umjigBG/Y4rkrxCKIw6M+CRaz203zs9ntOsWfnary/w+hepA
-XLjC0yb0cP/oBB6qRyaCk2UTdqq1uWmJ2R/XhZHdZIDabxby6mvQbUQA/NEMOE/B
-VrDonP1HNo1xpnY8lltbxdFD/jDikdjIazckMWl/0fri0pyPSdiJdAK2JrUniP9Q
-eNbgcx3XvNnfjYjiQjTdqfxCTKpSmnsBNyYng6c4viOr5weBFXwEJq2Nl7+rP5pm
-TF1PeiF769z4l2Mrx3X5sQqavTzd2VBMQ6/Kmk9Emxb8e1zyQD6odqJyTi1BBAes
-F2BuKLMCVgZWOFSNGDOMoAUMZh0c6sRQtwz3KRBAuxUYm3wQPqG3XpDDcNM5YXgF
-wVU8SYVwdFpPYT5XJIv2J2u45XbPma5aR0ynGuAmNptzELHta5cgeWIMVsKQbnPN
-M6YTOy5auxLts3FZvKpTDyjBd/VRK6ihkKNKFY3gbP6RbwEK3ws/zOxqFau7sA5i
-NGv4siQTWMG++pClz/exbgHPgs3f8yO34ZbocEBdS1sDl1Lsq4qJYo2Kn5MMHCGs
-dqd7Y+E+ep6b74njb1m2UsySEE2cjj/FAFH91jfFy5PedNb/2Hx6BsPJVb7+N4eI
-pehKQQ46XAbsMq6vUtI4Y0rFiBnqvpERqATQ2QhnEh0UmH7wKVQc4MREZfeEqazV
-G/JFt5Qnt3jq8p6/qbWlOPKTLGUqGq3RXiJgEy/5i22R2ZDjafiGoG1KsZIVZg39
-N25fT8abjPWme6JI3Jv+6gKY8tURoePZcMp/rw0NFs1HtCKUAU6FEOh6uJO7KNie
-eE8qG8ItRXVYnP4f8MFyFkHZcJw27d0PT3IrCM1vJwjqgb2j2xWM/8GJDDuUyims
-jvLDH1E7ek600H3FT5c9xPcgwfMM8BOdBNu0Evm9sdZBZFket+ytXo6GKyS/d91D
-FWE+YL+25+sZJS71dnvSUWVneJrTLFasefvPfIR9/aLJoLVFHnN9sUHfVMj0KlGl
-8AuxL7QfNQawvyjoV8rw/sJOQOwwhof1gZz0ZyjuTKj0WekjmDxcRzVY0eX6BzTm
-o7r4jrHl1Mi75svnKCpXi0Vu/1ZqSnKjCjhRTXDLm7tb8b18jogsgDfs7UkUNwD/
-XF8EfTTU4KotLOODAZIW+soFJZgf8rXQZLRShQmre+PUJfADEUd3yyE9h0JIunPQ
-CxR8R8hVhK4yqFn662Ou7fEl3q8FYBBi1Ahn+263S7+WaZGo7ElwzfRb97gP1e77
-eYd8JwY7UBIQku83CxQdahdGOpAfyvhYW2mxCHVZLXObwc18VgRMa7vjCbkGRPSN
-5NecU5KGW6jU1dXuZk0jRt/9mqtYPjJ7K/EVJD9Yxmz+UdxH+BtsSRp3/5fDmHtW
-CB39a7fetp0ixN503FXPKQUvKAKykETwevmWOzHH3t6BpY/ZSjDCC35Y3dWeB54H
-qNta1r0pSWV6IARUoVteAOcuOU/l3HNzY80rL+iR0HiaszioBsd8k8u0rWXzM3BP
-3vhTzccaldSWfqoT86Jfx0YLX6EoocVS8Ka5KUA8VlJWufnPPXDlF3dULrb+ds/l
-zLazt9hF49HCpU1rZc3doRgmBYxMjYyrfK/3uarDefpfdnjbAVIoP26VpVXhLTEM
-oaD+WoTpIyLYfJQUDn1Q06Nu393JqZb8nRngyMeTs73MDJTzqdL4rZXyweeTrtYe
-4yy+Kc3CZdPlZqpkbuxP0cO0ivaTLdXsTCHDnpk16u4sDukcsmlaTF5d75nu/KIQ
-o3nk0g9NvoschDcQiExuqCUOXCkKcUvYVHsuglAuT+AqK692562JrDOVoGwkUVvm
-Qfo0AQvBvXUzHY4YuBUdWbjWsC4sj6B+MW/TIs/OlKIbPE3MHeDhEGLl/8uBceVo
-kM36xm4F8wDwPK4GPyi/D+3piqBsrsjkgRlodQIUS7A9V19b8TWbUFeH4JGJ+5EH
-9WErBlrsQrnosojLchGGp7HxSxWLBiwdnltu6+/hwbBwydJT8ZxPUANIwTdB+mOE
-ILUXBkpIDfVSoZD7qWlntai53BDQr5pfMJhv15di4XAhtqv43vAmA57ifd+QJS2U
-AfYc4CdX0lk2BZ4jRD8jCZ4Uxw15E3RqhnXsWDRxtD4fwsb2ZFi0DDuPlwBdGgh5
-Rm2Bz9JjSV6gDEuXr/JtAzjSz1Jdh8wPkSofiHGTfxysVhlGlg+YPRziRlzML8A2
-0xY+9mPxEEin5ZQ9wmrDyiiOBvPTbG3O9+Sp5VZDuD4ivW/DHumPWGVSRdjcAQDe
-HMXUVGjhBDnj06XNrgJPhODdJeYq0EnGTt15ofZQSswD7TTTRPDOn0Cz/QARAQAB
-tDpCcmlkZ2VEQiAoT2ZmbGluZSBJRCBLZXkpIDxicmlkZ2VzQGJyaWRnZXMudG9y
-cHJvamVjdC5vcmc+iQkfBBMBCgEJBQJSWQfkSBSAAAAAABcAKHZlcmlmaWVkQHRv
-cnByb2plY3Qub3JnN0I3ODQzNzAxNUU2M0RGNDdCQjEyNzBBQ0JEOTdBQTI0RThF
-NDcyRU8UgAAAAAAeAChicmlkZ2VzQGJyaWRnZXMudG9ycHJvamVjdC5vcmc3Qjc4
-NDM3MDE1RTYzREY0N0JCMTI3MEFDQkQ5N0FBMjRFOEU0NzJFKhpodHRwczovL2Jy
-aWRnZXMudG9ycHJvamVjdC5vcmcvcG9saWN5LnR4dAIbAQMLDQkEFQoJCAQWAgEA
-Ah4BAheAJxhodHRwczovL2JyaWRnZXMudG9ycHJvamVjdC5vcmcva2V5LmFzYwAK
-CRDL2XqiTo5HLoqEP/48rFpJCVStn8xo+KkHSVvsqpxDRlb/nNheI+ov2UxILdwl
-NIU6kLsvKECKPe1AHKdS/MzANbkTF35Y4QgZsNpVXaCVL7adGBSzOdPFupDJJVOu
-wa+uFRc/FuNJyH/TIn56/+R5J5C54OxIYNxvW3WF4eHKLJYk/JZOMMfy4iWm7Sql
-0nDC5O435nK4F4Jb4GLPlUIzioIy2OWqGoFHXymbGhL1tWaqasYmED4n3AMqlYw6
-xnNhdWOc/KZelPl9nanybyh0IIdZqUKZleRt4BxSgIT8FqC2sZuZ8z7O9s987Naz
-Q32SKaP4i2M9lai/Y2QYfKo+wlG+egmxtujz7etQMGlpgBZzFLdJ8/w4U11ku1ai
-om74RIn8zl/LHjMQHnCKGoVlscTI1ZPt+p+p8hO2/9vOwTR8y8O/3DQSOfTSipwc
-a3obRkp5ndjfjefOoAnuYapLw72fhJ9+Co09miiHQu7vq4j5k05VpDQd0yxOAZnG
-vodPPhq7/zCG1K9Sb1rS9GvmQxGmgMBWVn+keTqJCZX7TSVgtgua9YjTJNVSiSLv
-rLslNkeRfvkfbAbU8379MDB+Bax04HcYTC4syf0uqUXYq6jRtX37Dfq5XkLCk2Bt
-WusH2NSpHuzZRWODM9PZb6U3vuBmU1nqY77DciuaeBqGGyrC/6UKrS0DrmVvF/0Z
-Sft3BY6Zb3q7Qm7xpzsfrhVlhlyQqZPhr6o7QnGuvwRr+gDwhRbpISKYo89KYwjK
-4Qr7sg/CyU2hWBCDOFPOcv/rtE0aD88M+EwRG/LCfEWU34Dc43Jk+dH56/3eVR58
-rISHRUcU0Y603Uc+/WM31iJmR/1PvGeal+mhI9YSWUIgIY8Mxt3cM2gYl/OErGbN
-4hWAPIFn4sM9Oo4BHpN7J2vkUatpW6v4Mdh+pNxzgE/V5S21SGaAldvM1SzCRz52
-xRt642Mhf6jqfrwzXf7kq7jpOlu1HkG1XhCZQPw7qyIKnX4tjaRd9HXhn9Jb2vA5
-Av+EOPoAx6Yii5X1RkDILOijvgVfSIFXnflHzs58AhwHztQOUWXDkfS5jVxbenbV
-X4DwgtrpzpdPBgBYNtCGBy9pqhWA2XnkH2vjchZy+xIAoaJNIVBfNkR8tflJWEfm
-i/2U0jJnhY8dEClbu3KQnbwKe5E9mTz1VmBsdWaK5rBVZamD/wssQzzyf3SXXXIU
-W6DUXOCzgWvxvqC09lc4izEAxwUktMY+aapplNs/kjOqHYVkW4zpWGp4PDAT/DW9
-/727CeoqY29HePaeGl0/PpR37CkquP69oQeJSU9CNeyAKnQtvaqxLBcEOohSaPtK
-Iy1q6yQgT4j+gVAsFDVyobCNkA8B0GfemDcEXA5dfriTHN72Br0koS0nvv6P5k7T
-7aaSNX++zdEnPauAZXPPjVt7R1sEvx0Oj+l1pu9hNX0nldaNs13bPU5DIOYv+5fN
-En6pqzYGj/0v8Qeb53Qv5de+lo7ZAu/truVa+GOT3ay4jZBaFh2mDZbA+t1V3GmB
-FtYGoVfou4iBQpx6tJLg3PKvtPj9g5B4LTxZVKrdfHXWyNNQOLzWSIgFj44+SmhU
-LVCXofEvJ0sOX2wtqy54Q4lMIc6BK1IB+hsFV6sSnIeI7YmrRXusWEG0wnroNlbq
-FiWg5+oeI1CnnCyj4FmDX/A/Bo0RxD0x3yqDximkOpcHMtLLfFjK3d5ltwBgDOOe
-pvgabxID01mZxh3OYKdGpW3Z1VKEhHjF5e9BhhEKQ8mG3raaDs2hQ2iuEqTzNLif
-aQdRCYd62jS14qSy2Dd+oZ0FbgzJNigWldvuwWzJCO2toF29pvfWtYRuqV/Vt3CK
-iO7en9bhOMRynPlCkAR7ZiZvT9dzStiMKf1v8mzgRjCIiLIwM1v/xNZWEZ/TOfSR
-E7dBMbDzaNjtCsMmNiyplqCjWbaj4irdIhKbtKJ02a1Jopo1/XNK0Y8AbK1xEHV0
-+mjBYU/Pfqnf0WFhkJgha+J17wqrUxf2/Y1b/pdDMGqVWe9+p8tvSP5FNddNyecZ
-0pojFH0jAzHpQen7eeIA3XupVe6cTEhNz4OjHBlZE6dN0q8UDdeG75yPunwShQiO
-kRXA/qxkID/2OLIInWJP0HG05hncGfWZKCLBc/dFg3dNo8VKpw/Q6uMBj2iGi8iB
-lnQGmHQa3j1ANPbcl3ljdJQDEnxk5TEVxNPYUw/BI58l3p+Z3vAZqC0Io7EgpuZ8
-qPuV6hJ2c/7VuFAXVs2mUExtWAjbgnYAfsJtn1yk3sphl65TjPnZwaBlP/ls/W/j
-mVjAx9d5b3mmMBJmNZDvY1QvcftDgfL5vYG5g7UwsbojuNxeM4rwn8qCKk5wC1/a
-Zl6Rh2DG4xS3/ef5tQWw28grjRRwv5phYKtedsKpYRscKAMhiOsChAiSYuCRczmI
-ErdO8ryK8QNzcpE4qVzFQMEtkG6V0RYYjMJzJuY5BW3hKt1UNNaqiGBpNKuf0GoO
-zK/vMgxoo+iFmOuaBdQEjlPLbK+3k+7j14KKVI655AXVKyAsOoSYPzOqfkdiu9W8
-34fOanH7S+lclkXwxTbXko9Jt6Ml64H4QKwd8ak2nCcX9FuMge7XP9VL/pBBMXcB
-WHUKdoqMJExcg5A4H2cyxZ6QgHzNFgqV/4+MGGP+TMc9owzrT3PBadVrMxnHnjc/
-/XYv48p2rRkjyjrtH+ZO9rlOsw0OmGgh9yoQPZn2tiNhG9piyvVxFKZflJm8I4kC
-4AQTAQoAygUCUlkPIkgUgAAAAAAXACh2ZXJpZmllZEB0b3Jwcm9qZWN0Lm9yZzdC
-Nzg0MzcwMTVFNjNERjQ3QkIxMjcwQUNCRDk3QUEyNEU4RTQ3MkVPFIAAAAAAHgAo
-YnJpZGdlc0BicmlkZ2VzLnRvcnByb2plY3Qub3JnREY4MTExMDlFMTdDOEJGMTM0
-QjVFRUI2OERDNDNBMjg0ODgyMUUzMioaaHR0cHM6Ly9icmlkZ2VzLnRvcnByb2pl
-Y3Qub3JnL3BvbGljeS50eHQACgkQjcQ6KEiCHjIaqBAA0BuEs7horx6iCq4cjAhv
-YPLrxuC4fKEfVyhAjCJMJSFFCPAlGgU+BjyPNDD57wzKAmUkdJG+Ss25mwWXa53w
-5R2kDqDnHocOdZGtxZ7zx/uUd2eWLNBfVuK7nHOk1d1Hs0OZBnckc+MCqnLtuYe5
-68pa9+jW6cNIjAnzMIListmoXWgYYWJvMKeBMG4DGtYJ8w7CJQjOHc5yar12DrX3
-wnQ7hXtFuuqQblpEUnLnZGvHf2NKMZfBBMcP96h9OmLGNa+vmNYsMyPKU7n5hPgX
-nTgmQ4xrv1G7JukjppZRA8SFoxupcaQeTixyWERGBhBiAbwZsbQz8L/TVZKierzg
-sdNngHcFzE8MyjuJDvTos7qXPmgSRXFqJLRn0ZxpR5V1V8BVZUqCGuSZT89TizsD
-z5vyv8c9r7HKD4pRjw32P2dgcEqyGRkqERAgSuFpObP+juty+kxYyfnadBNCyjgP
-s7u0GmsTt4CZi7BbowNRL6bynrwrmQI9LJI1bPhgqfdDUbqG3HXwHz80oRFfKou8
-JTYKxK4Iumfw2l/uAACma5ZyrwIDBX/H5XEQqch4sORzQnuhlTmZRf6ldVIIWjdJ
-ef+DpOt12s+cS2F4D5g8G6t9CprCLYyrXiHwM/U8N5ywL9IeYKSWJxa7si3l9A6o
-ZxOds8F/UJYDSIB97MQFzBo=
-=JdC7
------END PGP PUBLIC KEY BLOCK-----
-"""
diff --git a/lib/bridgedb/test/README b/lib/bridgedb/test/README
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/bridgedb/test/__init__.py b/lib/bridgedb/test/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/bridgedb/test/deprecated.py b/lib/bridgedb/test/deprecated.py
deleted file mode 100644
index 39920a2..0000000
--- a/lib/bridgedb/test/deprecated.py
+++ /dev/null
@@ -1,448 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""deprecated â functions and classes which have been removed from the
-production code but are kept in order to be used in regression testing.
-"""
-
-import ipaddr
-import re
-
-from twisted.python import deprecate
-from twisted.python.versions import Version
-
-
- at deprecate.deprecated(
- Version('bridgedb', 0, 2, 4),
- replacement='bridgedb.bridges.Bridge')
-class Bridge(object):
- """Holds information for a single bridge, along with any Pluggable
- Transports it is also running.
-
- :attr str nickname: The bridge's nickname. Not currently used.
- :attr ip: (:class:`ipaddr.IPAddress`) The bridge's IPv4 address, specified
- on the 'r'-line in a networkstatus document.
- :attr int orport: The bridge's OR port.
- :attr dict or_addresses: The bridges alternate IP addresses. The keys
- should be instances of ``ipaddr.IPAddress``, and the value should be a
- :class:`bridgedb.parse.addr.PortList` for the port(s) on which that
- address is listening.
- :attr list transports: List of :class:`PluggableTransport` instances for
- each PT which the bridge supports.
- :attr str fingerprint: The bridge's identity digest, in lowercase hex,
- without whitespace.
- :attr bool running: ``True``, if this bridge was given the ``Running`` flag.
- :attr bool stable: ``True``, if this bridge was given the ``Stable`` flag.
- :attr dict blockingCountries: A dictionary whose keys are strings of
- ``"IP:port"`` pairs, and the keys are lists of two letter country
- codes which block that IP:port. For example::
- {"1.2.3.4:9001": ['sk', 'us', 'ir', 'cn']}
- :attr str desc_digest: SHA1 hexdigest of the bridge's descriptor as
- defined in the networkstatus document.
- :attr str ei_digest: SHA1 hexdigest of the bridge's extra-info document as
- given in the bridge's descriptor, corresponding to desc_digest.
- :attr bool verified: Did we receive the descriptor for this bridge that
- was specified in the networkstatus?
- """
- def __init__(self, nickname, ip, orport, fingerprint=None, id_digest=None,
- or_addresses=None, transports=None):
- """Create a new Bridge. One of fingerprint and id_digest must be set.
- """
- self.nickname = nickname
- self.ip = ip
- self.orport = orport
- if not or_addresses: or_addresses = {}
- self.or_addresses = or_addresses
- if not transports: transports = []
- self.transports = transports
- self.running = self.stable = None
- self.blockingCountries = {}
- self.desc_digest = None
- self.ei_digest = None
- self.verified = False
-
- if id_digest is not None:
- assert fingerprint is None
- if len(id_digest) != DIGEST_LEN:
- raise TypeError("Bridge with invalid ID")
- self.fingerprint = toHex(id_digest)
- elif fingerprint is not None:
- if not isValidFingerprint(fingerprint):
- raise TypeError("Bridge with invalid fingerprint (%r)"%
- fingerprint)
- self.fingerprint = fingerprint.lower()
- else:
- raise TypeError("Bridge with no ID")
-
- def setDescriptorDigest(self, digest):
- """Set the descriptor digest, specified in the NS."""
- self.desc_digest = digest
-
- def setExtraInfoDigest(self, digest):
- """Set the extra-info digest, specified in the descriptor."""
- self.ei_digest = digest
-
- def setVerified(self):
- """Call when the bridge's descriptor is parsed"""
- self.verified = True
-
- def isVerified(self):
- """Returns the truthiness of ``verified``"""
- return self.verified
-
- def getID(self):
- """Return the bridge's identity digest."""
- return fromHex(self.fingerprint)
-
- def __repr__(self):
- """Return a piece of python that evaluates to this bridge."""
- if self.or_addresses:
- return "Bridge(%r,%r,%d,%r,or_addresses=%s)"%(
- self.nickname, self.ip, self.orport, self.fingerprint,
- self.or_addresses)
- return "Bridge(%r,%r,%d,%r)"%(
- self.nickname, self.ip, self.orport, self.fingerprint)
-
- def getConfigLine(self, includeFingerprint=False, addressClass=None,
- request=None, transport=None):
- """Returns a valid bridge line for inclusion in a torrc.
-
- :param bool includeFingerprint: If ``True``, include the
- ``fingerprint`` of this :class:`Bridge` in the returned bridge
- line.
- :param DOCDOC addressClass: Type of address to choose.
- :param str request: A string unique to this request e.g. email-address
- or ``uniformMap(ip)`` or ``'default'``.
- :param str transport: A pluggable transport method name.
- """
-
- if not request: request = 'default'
- digest = getHMACFunc('Order-Or-Addresses')(request)
- pos = long(digest[:8], 16) # lower 8 bytes -> long
-
- # default address type
- if not addressClass: addressClass = ipaddr.IPv4Address
-
- # pluggable transports
- if transport:
- # filter by 'methodname'
- transports = filter(lambda x: transport == x.methodname,
- self.transports)
- # filter by 'addressClass'
- transports = filter(lambda x: isinstance(x.address, addressClass),
- transports)
- if transports:
- pt = transports[pos % len(transports)]
- return pt.getTransportLine(includeFingerprint)
-
- # filter addresses by address class
- addresses = filter(lambda x: isinstance(x[0], addressClass),
- self.or_addresses.items())
-
- # default ip, orport should get a chance at being selected
- if isinstance(self.ip, addressClass):
- addresses.insert(0,(self.ip, addr.PortList(self.orport)))
-
- if addresses:
- address,portlist = addresses[pos % len(addresses)]
- if isinstance(address, ipaddr.IPv6Address): ip = "[%s]"%address
- else: ip = "%s"%address
- orport = portlist[pos % len(portlist)]
-
- if includeFingerprint:
- return "%s:%d %s" % (ip, orport, self.fingerprint)
- else:
- return "%s:%d" % (ip, orport)
-
- def getAllConfigLines(self,includeFingerprint=False):
- """Generator. Iterate over all valid config lines for this bridge."""
- for address,portlist in self.or_addresses.items():
- if type(address) is ipaddr.IPv6Address:
- ip = "[%s]" % address
- else:
- ip = "%s" % address
-
- for orport in portlist:
- if includeFingerprint:
- yield "bridge %s:%d %s" % (ip,orport,self.fingerprint)
- else:
- yield "bridge %s:%d" % (ip,orport)
- for pt in self.transports:
- yield pt.getTransportLine(includeFingerprints)
-
-
- def assertOK(self):
- assert is_valid_ip(self.ip)
- assert isValidFingerprint(self.fingerprint)
- assert 1 <= self.orport <= 65535
- if self.or_addresses:
- for address, portlist in self.or_addresses.items():
- assert is_valid_ip(address)
- for port in portlist:
- assert type(port) is int
- assert 1 <= port <= 65535
-
- def setStatus(self, running=None, stable=None):
- if running is not None:
- self.running = running
- if stable is not None:
- self.stable = stable
-
- def isBlocked(self, countryCode, addressClass, methodname=None):
- """ if at least one address:port of the selected addressClass and
- (optional) transport type is not blocked in countryCode, return True
- """
- # 1) transport is specified
- if methodname is not None:
- for transport in self.transports:
- key = "%s:%s" % (transport.address, transport.port)
- if (isinstance(transport.address, addressClass)
- and transport.methodname.lower() == methodname.lower()):
- try:
- if countryCode not in self.blockingCountries[key]:
- return False
- except KeyError:
- return False # no blocklist
- return True
- # 2) no transport specified (default)
- else:
- # 3) check primary ip, port
- # XXX: could be more elegant if ip,orport were not special case
- if isinstance(self.ip, addressClass):
- key = "%s:%s" % (self.ip, self.orport)
- try:
- if countryCode not in self.blockingCountries[key]:
- return False
- except KeyError: return False # no blocklist
-
- # 4) check or addresses
- for address,portlist in self.or_addresses.items():
- if isinstance(address, addressClass):
- # check each port
- for port in portlist:
- key = "%s:%s" % (address, port)
- try:
- if countryCode not in self.blockingCountries[key]:
- return False
- except KeyError: return False # no blocklist
- return True
-
- # Bridge Stability (#5482) properties.
- @property
- def familiar(self):
- """
- A bridge is 'familiar' if 1/8 of all active bridges have appeared
- more recently than it, or if it has been around for a Weighted Time of 8 days.
- """
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).familiar
-
- @property
- def wfu(self):
- """Weighted Fractional Uptime"""
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).weightedFractionalUptime
-
- @property
- def weightedTime(self):
- """Weighted Time"""
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).weightedTime
-
- @property
- def wmtbac(self):
- """Weighted Mean Time Between Address Change"""
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).wmtbac
-
- @property
- def tosa(self):
- """the Time On Same Address (TOSA)"""
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).tosa
-
- @property
- def weightedUptime(self):
- """Weighted Uptime"""
- with bridgedb.Storage.getDB() as db:
- return db.getBridgeHistory(self.fingerprint).weightedUptime
-
-
- at deprecate.deprecated(
- Version('bridgedb', 0, 2, 4),
- replacement='bridgedb.bridges.PluggableTransport')
-class PluggableTransport(object):
- """A PT with reference to the parent bridge on which it is running.
-
- Deprecated :class:`bridgedb.Bridges.PluggableTransport`, replaced in
- bridgedb-0.2.4, by :class:`bridgedb.bridges.PluggableTransport`.
- """
-
- def __init__(self, bridge, methodname, address, port, argdict=None):
- """Create a ``PluggableTransport`` describing a PT running on a bridge.
-
- Pluggable transports are described within a bridge's ``@type
- bridge-extrainfo`` descriptor, see the ``Specifications: Client
- behavior`` section and the ``TOR_PT_SERVER_TRANSPORT_OPTIONS``
- description in pt-spec.txt_ for additional specification.
-
- :type bridge: :class:`Bridge`
- :param bridge: The parent bridge running this pluggable transport
- instance, i.e. the main ORPort bridge whose
- ``@type bridge-server-descriptor`` contains a hash digest for a
- ``@type bridge-extrainfo-document``, the latter of which contains
- the parameter of this pluggable transport in its ``transport``
- line.
-
- :param str methodname: The canonical "name" for this pluggable
- transport, i.e. the one which would be specified in a torrc
- file. For example, ``"obfs2"``, ``"obfs3"``, ``"scramblesuit"``
- would all be pluggable transport method names.
-
- :param str address: The IP address of the transport. Currently (as of
- 20 March 2014), there are no known, widely-deployed pluggable
- transports which support IPv6. Ergo, this is very likely going to
- be an IPv4 address.
-
- :param int port: A integer specifying the port which this pluggable
- transport is listening on. (This should likely be whatever port the
- bridge specified in its ``ServerTransportPlugin`` torrc line,
- unless the pluggable transport is running in "managed" mode.)
-
- :param dict argdict: Some PTs can take additional arguments, which
- must be distributed to the client out-of-band. These are present
- in the ``@type bridge-extrainfo-document``, in the ``transport``
- line like so::
-
- METHOD SP ADDR ":" PORT SP [K=V[,K=V[,K=V[â¦]]]]
-
- where K is the **argdict** key, and V is the value. For example,
- in the case of ``scramblesuit``, for which the client must supply
- a shared secret to the ``scramblesuit`` instance running on the
- bridge, the **argdict** would be something like::
-
- {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
-
- .. _pt-spec.txt:
- https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
- """
- #XXX: assert are disabled with python -O
- assert isinstance(bridge, Bridge)
- assert type(address) in (ipaddr.IPv4Address, ipaddr.IPv6Address)
- assert type(port) is int
- assert (0 < port < 65536)
- assert type(methodname) is str
-
- self.bridge = bridge
- self.address = address
- self.port = port
- self.methodname = methodname
- if type(argdict) is dict:
- self.argdict = argdict
- else: self.argdict = {}
-
- def getTransportLine(self, includeFingerprint=False, bridgePrefix=False):
- """Get a torrc line for this pluggable transport.
-
- This method does not return lines which are prefixed with the word
- 'bridge', as they would be in a torrc file. Instead, lines returned
- look like this:
-
- obfs3 245.102.100.252:23619 59ca743e89b508e16b8c7c6d2290efdfd14eea98
-
- :param bool includeFingerprints: If ``True``, include the digest of
- this bridges public identity key in the torrc line.
- :param bool bridgePrefix: If ``True``, add ``'Bridge '`` to the
- beginning of each returned line (suitable for pasting directly
- into a torrc file).
- :rtype: str
- :returns: A configuration line for adding this pluggable transport
- into a torrc file.
- """
- sections = []
-
- if bridgePrefix:
- sections.append('Bridge')
-
- if isinstance(self.address, ipaddr.IPv6Address):
- host = "%s [%s]:%d" % (self.methodname, self.address, self.port)
- else:
- host = "%s %s:%d" % (self.methodname, self.address, self.port)
- sections.append(host)
-
- if includeFingerprint:
- sections.append(self.bridge.fingerprint)
-
- args = " ".join(["%s=%s" % (k, v) for k, v in self.argdict.items()])
- sections.append(args)
-
- line = ' '.join(sections)
- return line
-
-
- at deprecate.deprecated(
- Version('bridgedb', 0, 0, 1),
- replacement='bridgedb.parse.addr.PortList')
-class PortList:
- """Deprecated :class:`bridgedb.Bridges.PortList`, replaced in
- bridgedb-0.1.0, in commit 1f111e5, by
- :class:`bridgedb.parse.addr.PortList`.
-
- This class and the newer class from :mod:`bridgedb.parse.addr` are
- alternately :api:`~twisted.python.monkey.MonkeyPatcher.patch`ed into the
- :mod:`old unittests <bridgedb.Tests>`, so that the later functions as a
- suite of regression tests.
- """
- def __init__(self, *args, **kwargs):
- self.ports = set()
- self.add(*args)
-
- def _sanitycheck(self, val):
- #XXX: if debug=False this is disabled. bad!
- assert type(val) is int
- assert(0 < val <= 65535)
-
- def __contains__(self, val1):
- return val1 in self.ports
-
- def add(self, *args):
- PORTSPEC_LEN = 16
- for arg in args:
- try:
- if type(arg) is str:
- ports = set([int(p) for p in arg.split(',')][:PORTSPEC_LEN])
- [self._sanitycheck(p) for p in ports]
- self.ports.update(ports)
- if type(arg) is int:
- self._sanitycheck(arg)
- self.ports.update([arg])
- if type(arg) is PortList:
- self.add(list(arg.ports))
- except AssertionError: raise ValueError
- except ValueError: raise
-
- def __iter__(self):
- return self.ports.__iter__()
-
- def __str__(self):
- s = ""
- for p in self.ports:
- s += "".join(",%s"%p)
- return s.lstrip(",")
-
- def __repr__(self):
- return "PortList('%s')" % self.__str__()
-
- def __len__(self):
- return len(self.ports)
-
- def __getitem__(self, x):
- return list(self.ports)[x]
diff --git a/lib/bridgedb/test/email_helpers.py b/lib/bridgedb/test/email_helpers.py
deleted file mode 100644
index 14c86f4..0000000
--- a/lib/bridgedb/test/email_helpers.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-
-"""Helpers for testing the email distributor and its servers."""
-
-
-import io
-
-from bridgedb.persistent import Conf
-from bridgedb.email.distributor import IgnoreEmail
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.email.server import MailServerContext
-from bridgedb.schedule import Unscheduled
-from bridgedb.test import util
-
-
-EMAIL_DIST = True
-EMAIL_ROTATION_PERIOD = "1 day"
-EMAIL_INCLUDE_FINGERPRINTS = True
-EMAIL_GPG_SIGNING_ENABLED = True
-EMAIL_GPG_HOMEDIR = '.gnupg'
-EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = '0017098C5DF4197E3C884DCFF1B240D43F148C21'
-EMAIL_GPG_PASSPHRASE = None
-EMAIL_GPG_PASSPHRASE_FILE = None
-EMAIL_DOMAIN_MAP = {
- 'googlemail.com': 'gmail.com',
- 'mail.google.com': 'gmail.com',
-}
-EMAIL_DOMAIN_RULES = {
- 'gmail.com': ["ignore_dots", "dkim"],
- 'example.com': [],
- 'localhost': [],
-}
-EMAIL_DOMAINS = ["gmail.com", "example.com", "localhost"]
-EMAIL_WHITELIST = {'white at list.ed': 'ABCD1234ABCD1234ABCD1234ABCD1234ABCD1234'}
-EMAIL_BLACKLIST = ['feidanchaoren0001 at gmail.com']
-EMAIL_FUZZY_MATCH = 4
-EMAIL_USERNAME = "bridges"
-EMAIL_SMTP_HOST = "127.0.0.1"
-EMAIL_SMTP_PORT = 25
-EMAIL_SMTP_FROM_ADDR = "bridges at localhost"
-EMAIL_N_BRIDGES_PER_ANSWER = 3
-EMAIL_FROM_ADDR = "bridges at localhost"
-EMAIL_BIND_IP = "127.0.0.1"
-EMAIL_PORT = 5225
-
-TEST_CONFIG_FILE = io.StringIO(unicode("""\
-EMAIL_DIST = %s
-EMAIL_ROTATION_PERIOD = %s
-EMAIL_INCLUDE_FINGERPRINTS = %s
-EMAIL_GPG_SIGNING_ENABLED = %s
-EMAIL_GPG_HOMEDIR = %s
-EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = %s
-EMAIL_GPG_PASSPHRASE = %s
-EMAIL_GPG_PASSPHRASE_FILE = %s
-EMAIL_DOMAIN_MAP = %s
-EMAIL_DOMAIN_RULES = %s
-EMAIL_DOMAINS = %s
-EMAIL_WHITELIST = %s
-EMAIL_BLACKLIST = %s
-EMAIL_FUZZY_MATCH = %s
-EMAIL_USERNAME = %s
-EMAIL_SMTP_HOST = %s
-EMAIL_SMTP_PORT = %s
-EMAIL_SMTP_FROM_ADDR = %s
-EMAIL_N_BRIDGES_PER_ANSWER = %s
-EMAIL_FROM_ADDR = %s
-EMAIL_BIND_IP = %s
-EMAIL_PORT = %s
-""" % (repr(EMAIL_DIST),
- repr(EMAIL_ROTATION_PERIOD),
- repr(EMAIL_INCLUDE_FINGERPRINTS),
- repr(EMAIL_GPG_SIGNING_ENABLED),
- repr(EMAIL_GPG_HOMEDIR),
- repr(EMAIL_GPG_PRIMARY_KEY_FINGERPRINT),
- repr(EMAIL_GPG_PASSPHRASE),
- repr(EMAIL_GPG_PASSPHRASE_FILE),
- repr(EMAIL_DOMAIN_MAP),
- repr(EMAIL_DOMAIN_RULES),
- repr(EMAIL_DOMAINS),
- repr(EMAIL_WHITELIST),
- repr(EMAIL_BLACKLIST),
- repr(EMAIL_FUZZY_MATCH),
- repr(EMAIL_USERNAME),
- repr(EMAIL_SMTP_HOST),
- repr(EMAIL_SMTP_PORT),
- repr(EMAIL_SMTP_FROM_ADDR),
- repr(EMAIL_N_BRIDGES_PER_ANSWER),
- repr(EMAIL_FROM_ADDR),
- repr(EMAIL_BIND_IP),
- repr(EMAIL_PORT))))
-
-
-def _createConfig(configFile=TEST_CONFIG_FILE):
- configuration = {}
- TEST_CONFIG_FILE.seek(0)
- compiled = compile(configFile.read(), '<string>', 'exec')
- exec compiled in configuration
- config = Conf(**configuration)
- return config
-
-def _createMailServerContext(config=None, distributor=None):
- if not config:
- config = _createConfig()
-
- if not distributor:
- distributor = DummyEmailDistributor(
- domainmap=config.EMAIL_DOMAIN_MAP,
- domainrules=config.EMAIL_DOMAIN_RULES)
-
- context = MailServerContext(config, distributor, Unscheduled())
- return context
-
-
-class DummyEmailDistributor(object):
- """A mocked :class:`bridgedb.email.distributor.EmailDistributor` which is used
- to test :class:`bridgedb.EmailServer`.
- """
-
- _bridgesPerResponseMin = 3
-
- def __init__(self, key=None, domainmap=None, domainrules=None,
- answerParameters=None):
- """None of the parameters are really used, â they are just there to retain an
- identical method signature.
- """
- self.key = self.__class__.__name__
- self.domainmap = domainmap
- self.domainrules = domainrules
- self.answerParameters = answerParameters
-
- def getBridges(self, bridgeRequest, epoch):
- return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
-
- def cleanDatabase(self):
- pass
-
-
-class DummyEmailDistributorWithState(DummyEmailDistributor):
- """A mocked :class:`bridgedb.email.distributor.EmailDistributor` which raises
- :exc:`bridgedb.email.distributor.TooSoonEmail` on the second email and
- :exc:`bridgedb.email.distributor.IgnoreEmail` on the third.
-
- Note that the state tracking is done in a really dumb way. For example, we
- currently don't consider requests for help text or GnuPG keys to be a
- "real" request, so in the real email distributor they won't trigger either
- a TooSoonEmail or IgnoreEmail. Here we only track the total number of
- *any* type of request per client.
- """
-
- def __init__(self, *args, **kwargs):
- super(DummyEmailDistributorWithState, self).__init__()
- self.alreadySeen = {}
-
- def getBridges(self, bridgeRequest, epoch):
- # Keep track of the number of times we've seen a client.
- if not bridgeRequest.client in self.alreadySeen.keys():
- self.alreadySeen[bridgeRequest.client] = 0
- self.alreadySeen[bridgeRequest.client] += 1
-
- if self.alreadySeen[bridgeRequest.client] <= 1:
- return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
- elif self.alreadySeen[bridgeRequest.client] == 2:
- raise TooSoonEmail(
- "Seen client '%s' %d times"
- % (bridgeRequest.client, self.alreadySeen[bridgeRequest.client]),
- bridgeRequest.client)
- else:
- raise IgnoreEmail(
- "Seen client '%s' %d times"
- % (bridgeRequest.client, self.alreadySeen[bridgeRequest.client]),
- bridgeRequest.client)
diff --git a/lib/bridgedb/test/https_helpers.py b/lib/bridgedb/test/https_helpers.py
deleted file mode 100644
index e2c94ba..0000000
--- a/lib/bridgedb/test/https_helpers.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-
-"""Helpers for testing the HTTPS Distributor and its servers."""
-
-
-import io
-
-from twisted.web.test import requesthelper
-
-from bridgedb.test import util
-from bridgedb.persistent import Conf
-
-
-SERVER_PUBLIC_FQDN = 'bridges.torproject.org'
-SERVER_PUBLIC_EXTERNAL_IP = '38.229.72.19'
-HTTPS_DIST = True
-HTTPS_BIND_IP = None
-HTTPS_PORT = None
-HTTPS_N_BRIDGES_PER_ANSWER = 3
-HTTPS_INCLUDE_FINGERPRINTS = True
-HTTPS_KEY_FILE = 'privkey.pem'
-HTTPS_CERT_FILE = 'cert'
-N_IP_CLUSTERS = 4
-HTTPS_ROTATION_PERIOD = "3 hours"
-HTTP_UNENCRYPTED_BIND_IP = None
-HTTP_UNENCRYPTED_PORT = None
-HTTP_USE_IP_FROM_FORWARDED_HEADER = False
-RECAPTCHA_ENABLED = False
-RECAPTCHA_PUB_KEY = ''
-RECAPTCHA_SEC_KEY = ''
-RECAPTCHA_REMOTEIP = ''
-GIMP_CAPTCHA_ENABLED = True
-GIMP_CAPTCHA_DIR = 'captchas'
-GIMP_CAPTCHA_HMAC_KEYFILE = 'captcha_hmac_key'
-GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key'
-
-TEST_CONFIG_FILE = io.StringIO(unicode("""\
-SERVER_PUBLIC_FQDN = %r
-SERVER_PUBLIC_EXTERNAL_IP = %r
-HTTPS_DIST = %r
-HTTPS_BIND_IP = %r
-HTTPS_PORT = %r
-HTTPS_N_BRIDGES_PER_ANSWER = %r
-HTTPS_INCLUDE_FINGERPRINTS = %r
-HTTPS_KEY_FILE = %r
-HTTPS_CERT_FILE = %r
-N_IP_CLUSTERS = %r
-HTTPS_ROTATION_PERIOD = %r
-HTTP_UNENCRYPTED_BIND_IP = %r
-HTTP_UNENCRYPTED_PORT = %r
-HTTP_USE_IP_FROM_FORWARDED_HEADER = %r
-RECAPTCHA_ENABLED = %r
-RECAPTCHA_PUB_KEY = %r
-RECAPTCHA_SEC_KEY = %r
-RECAPTCHA_REMOTEIP = %r
-GIMP_CAPTCHA_ENABLED = %r
-GIMP_CAPTCHA_DIR = %r
-GIMP_CAPTCHA_HMAC_KEYFILE = %r
-GIMP_CAPTCHA_RSA_KEYFILE = %r
-""" % (SERVER_PUBLIC_FQDN,
- SERVER_PUBLIC_EXTERNAL_IP,
- HTTPS_DIST,
- HTTPS_BIND_IP,
- HTTPS_PORT,
- HTTPS_N_BRIDGES_PER_ANSWER,
- HTTPS_INCLUDE_FINGERPRINTS,
- HTTPS_KEY_FILE,
- HTTPS_CERT_FILE,
- N_IP_CLUSTERS,
- HTTPS_ROTATION_PERIOD,
- HTTP_UNENCRYPTED_BIND_IP,
- HTTP_UNENCRYPTED_PORT,
- HTTP_USE_IP_FROM_FORWARDED_HEADER,
- RECAPTCHA_ENABLED,
- RECAPTCHA_PUB_KEY,
- RECAPTCHA_SEC_KEY,
- RECAPTCHA_REMOTEIP,
- GIMP_CAPTCHA_ENABLED,
- GIMP_CAPTCHA_DIR,
- GIMP_CAPTCHA_HMAC_KEYFILE,
- GIMP_CAPTCHA_RSA_KEYFILE)))
-
-
-def _createConfig(configFile=TEST_CONFIG_FILE):
- configuration = {}
- TEST_CONFIG_FILE.seek(0)
- compiled = compile(configFile.read(), '<string>', 'exec')
- exec compiled in configuration
- config = Conf(**configuration)
- return config
-
-
-class DummyHTTPSDistributor(object):
- """A mocked :class:`bridgedb.https.distributor.HTTPSDistributor` which is
- used to test :class:`bridgedb.https.server.BridgesResource`.
- """
- _bridge_class = util.DummyBridge
- _bridgesPerResponseMin = 3
-
- def getBridges(self, bridgeRequest=None, epoch=None):
- """Needed because it's called in
- :meth:`BridgesResource.getBridgeRequestAnswer`."""
- return [self._bridge_class() for _ in range(self._bridgesPerResponseMin)]
-
-
-class DummyRequest(requesthelper.DummyRequest):
- """Wrapper for :api:`twisted.test.requesthelper.DummyRequest` to add
- redirect support.
- """
- def __init__(self, *args, **kwargs):
- requesthelper.DummyRequest.__init__(self, *args, **kwargs)
- self.redirect = self._redirect(self)
-
- def URLPath(self):
- """Fake the missing Request.URLPath too."""
- return self.uri
-
- def _redirect(self, request):
- """Stub method to add a redirect() method to DummyResponse."""
- newRequest = type(request)
- newRequest.uri = request.uri
- return newRequest
diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py
deleted file mode 100644
index 40f7ae4..0000000
--- a/lib/bridgedb/test/legacy_Tests.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# BridgeDB by Nick Mathewson.
-# Copyright (c) 2007-2009, The Tor Project, Inc.
-# See LICENSE for licensing information
-
-"""These are legacy integration and unittests which historically lived at
-``lib/bridgedb/Tests.py``. They have been moved here to keep the test code
-separate from the production codebase.
-"""
-
-from __future__ import print_function
-
-import os
-import random
-import tempfile
-import unittest
-import warnings
-import time
-from datetime import datetime
-
-import bridgedb.Bridges
-import bridgedb.Main
-import bridgedb.schedule
-import bridgedb.Storage
-import re
-import ipaddr
-
-from bridgedb.Stability import BridgeHistory
-
-from bridgedb.email.distributor import EmailDistributor
-from bridgedb.email.distributor import IgnoreEmail
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.parse import addr
-from bridgedb.test.util import bracketIPv6
-from bridgedb.test.util import randomIP
-from bridgedb.test.util import randomIPv4
-from bridgedb.test.util import randomIPv6
-from bridgedb.test.util import randomIPString
-from bridgedb.test.util import randomIPv4String
-from bridgedb.test.util import randomIPv6String
-from bridgedb.test.util import randomPort
-from bridgedb.test.util import randomValidIPv6
-
-from math import log
-
-warnings.filterwarnings('ignore', '.*tmpnam.*')
-
-
-def randomPortSpec():
- """
- returns a random list of ports
- """
- ports = [randomPort() for i in range(0,24)]
- ports.sort(reverse=True)
-
- portspec = ",".join(["%d" % random.choice(ports) for i in range(0,16)])
- return portspec
-
-def fakeBridge(orport=8080, running=True, stable=True, or_addresses=False,
- transports=False):
- ip = randomIPv4()
- nn = "bridge-%s" % int(ip)
- fp = "".join([random.choice("0123456789ABCDEF") for _ in xrange(40)])
- b = bridgedb.Bridges.Bridge(nn,ip,orport,fingerprint=fp)
- b.setStatus(running, stable)
-
- oraddrs = []
- if or_addresses:
- for i in xrange(8):
- b.orAddresses.append((randomValidIPv6(), randomPort(), 6))
-
- if transports:
- for i in xrange(0,8):
- b.transports.append(bridgedb.Bridges.PluggableTransport(b,
- random.choice(["obfs", "obfs2", "pt1"]),
- randomIP(), randomPort()))
- return b
-
-def fakeBridge6(orport=8080, running=True, stable=True, or_addresses=False,
- transports=False):
- ip = randomIPv6()
- nn = "bridge-%s" % int(ip)
- fp = "".join([random.choice("0123456789ABCDEF") for _ in xrange(40)])
- b = bridgedb.Bridges.Bridge(nn,ip,orport,fingerprint=fp)
- b.setStatus(running, stable)
-
- oraddrs = []
- if or_addresses:
- for i in xrange(8):
- b.orAddresses.append((randomValidIPv6(), randomPort(), 6))
-
- if transports:
- for i in xrange(0,8):
- b.transports.append(bridgedb.Bridges.PluggableTransport(b,
- random.choice(["obfs", "obfs2", "pt1"]),
- randomIP(), randomPort()))
- return b
-
-
-
-class SQLStorageTests(unittest.TestCase):
- def setUp(self):
- self.fd, self.fname = tempfile.mkstemp()
- self.db = bridgedb.Storage.Database(self.fname)
- self.cur = self.db._conn.cursor()
-
- def tearDown(self):
- self.db.close()
- os.close(self.fd)
- os.unlink(self.fname)
-
- def assertCloseTo(self, a, b, delta=60):
- self.assertTrue(abs(a-b) <= delta)
-
- def testBridgeStorage(self):
- db = self.db
- B = bridgedb.Bridges.Bridge
- t = time.time()
- cur = self.cur
-
- k1 = "AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBB"
- k2 = "ABABABABABABABABABABABABABABABABABABABAB"
- k3 = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
- b1 = B("serv1", "1.2.3.4", 999, fingerprint=k1)
- b1_v2 = B("serv1", "1.2.3.5", 9099, fingerprint=k1)
- b2 = B("serv2", "2.3.4.5", 9990, fingerprint=k2)
- b3 = B("serv3", "2.3.4.6", 9008, fingerprint=k3)
- validRings = ["ring1", "ring2", "ring3"]
-
- r = db.insertBridgeAndGetRing(b1, "ring1", t, validRings)
- self.assertEquals(r, "ring1")
- r = db.insertBridgeAndGetRing(b1, "ring10", t+500, validRings)
- self.assertEquals(r, "ring1")
-
- cur.execute("SELECT distributor, address, or_port, first_seen, "
- "last_seen FROM Bridges WHERE hex_key = ?", (k1,))
- v = cur.fetchone()
- self.assertEquals(v,
- ("ring1", "1.2.3.4", 999,
- bridgedb.Storage.timeToStr(t),
- bridgedb.Storage.timeToStr(t+500)))
-
- r = db.insertBridgeAndGetRing(b1_v2, "ring99", t+800, validRings)
- self.assertEquals(r, "ring1")
- cur.execute("SELECT distributor, address, or_port, first_seen, "
- "last_seen FROM Bridges WHERE hex_key = ?", (k1,))
- v = cur.fetchone()
- self.assertEquals(v,
- ("ring1", "1.2.3.5", 9099,
- bridgedb.Storage.timeToStr(t),
- bridgedb.Storage.timeToStr(t+800)))
-
- db.insertBridgeAndGetRing(b2, "ring2", t, validRings)
- db.insertBridgeAndGetRing(b3, "ring3", t, validRings)
-
- cur.execute("SELECT COUNT(distributor) FROM Bridges")
- v = cur.fetchone()
- self.assertEquals(v, (3,))
-
- r = db.getEmailTime("abc at example.com")
- self.assertEquals(r, None)
- db.setEmailTime("abc at example.com", t)
- db.setEmailTime("def at example.com", t+1000)
- r = db.getEmailTime("abc at example.com")
- self.assertCloseTo(r, t)
- r = db.getEmailTime("def at example.com")
- self.assertCloseTo(r, t+1000)
- r = db.getEmailTime("ghi at example.com")
- self.assertEquals(r, None)
-
- db.cleanEmailedBridges(t+200)
- db.setEmailTime("def at example.com", t+5000)
- r = db.getEmailTime("abc at example.com")
- self.assertEquals(r, None)
- r = db.getEmailTime("def at example.com")
- self.assertCloseTo(r, t+5000)
- cur.execute("SELECT * FROM EmailedBridges")
- self.assertEquals(len(cur.fetchall()), 1)
-
- self.assertEquals(db.getWarnedEmail("def at example.com"), False)
- db.setWarnedEmail("def at example.com")
- self.assertEquals(db.getWarnedEmail("def at example.com"), True)
- db.setWarnedEmail("def at example.com", False)
- self.assertEquals(db.getWarnedEmail("def at example.com"), False)
-
- db.setWarnedEmail("def at example.com")
- self.assertEquals(db.getWarnedEmail("def at example.com"), True)
- db.cleanWarnedEmails(t+200)
- self.assertEquals(db.getWarnedEmail("def at example.com"), False)
-
-
-class BridgeStabilityTests(unittest.TestCase):
- def setUp(self):
- self.fd, self.fname = tempfile.mkstemp()
- self.db = bridgedb.Storage.Database(self.fname)
- bridgedb.Storage.setDB(self.db)
- self.cur = self.db._conn.cursor()
-
- def tearDown(self):
- self.db.close()
- os.close(self.fd)
- os.unlink(self.fname)
-
- def testAddOrUpdateSingleBridgeHistory(self):
- db = self.db
- b = fakeBridge()
- timestamp = time.time()
- bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b, timestamp)
- assert isinstance(bhe, BridgeHistory)
- assert isinstance(db.getBridgeHistory(b.fingerprint), BridgeHistory)
- assert len([y for y in db.getAllBridgeHistory()]) == 1
-
- def testDeletingSingleBridgeHistory(self):
- db = self.db
- b = fakeBridge()
- timestamp = time.time()
- bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b, timestamp)
- assert isinstance(bhe, BridgeHistory)
- assert isinstance(db.getBridgeHistory(b.fingerprint), BridgeHistory)
- db.delBridgeHistory(b.fingerprint)
- assert db.getBridgeHistory(b.fingerprint) is None
- assert len([y for y in db.getAllBridgeHistory()]) == 0
-
- def testTOSA(self):
- db = self.db
- b = random.choice([fakeBridge,fakeBridge6])()
- def timestampSeries(x):
- for i in xrange(61):
- yield (i+1)*60*30 + x # 30 minute intervals
- now = time.time()
- time_on_address = long(60*30*60) # 30 hours
- downtime = 60*60*random.randint(0,4) # random hours of downtime
-
- for t in timestampSeries(now):
- bridgedb.Stability.addOrUpdateBridgeHistory(b,t)
- assert db.getBridgeHistory(b.fingerprint).tosa == time_on_address
-
- b.orport += 1
-
- for t in timestampSeries(now + time_on_address + downtime):
- bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b,t)
- assert db.getBridgeHistory(b.fingerprint).tosa == time_on_address + downtime
-
- def testLastSeenWithDifferentAddressAndPort(self):
- db = self.db
- for i in xrange(10):
- num_desc = 30
- time_start = time.time()
- ts = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
- b = random.choice([fakeBridge(), fakeBridge6()])
- [ bridgedb.Stability.addOrUpdateBridgeHistory(b, t) for t in ts ]
-
- # change the port
- b.orport = b.orport+1
- last_seen = ts[-1]
- ts = [ 60*30*(i+1) + last_seen for i in xrange(num_desc) ]
- [ bridgedb.Stability.addOrUpdateBridgeHistory(b, t) for t in ts ]
- b = db.getBridgeHistory(b.fingerprint)
- assert b.tosa == ts[-1] - last_seen
- assert (long(last_seen*1000) == b.lastSeenWithDifferentAddressAndPort)
- assert (long(ts[-1]*1000) == b.lastSeenWithThisAddressAndPort)
-
- def testFamiliar(self):
- # create some bridges
- # XXX: slow
- num_bridges = 10
- num_desc = 4*48 # 30m intervals, 48 per day
- time_start = time.time()
- bridges = [ fakeBridge() for x in xrange(num_bridges) ]
- t = time.time()
- ts = [ (i+1)*60*30+t for i in xrange(num_bridges) ]
- for b in bridges:
- time_series = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
- [ bridgedb.Stability.addOrUpdateBridgeHistory(b, i) for i in time_series ]
- assert None not in bridges
- # +1 to avoid rounding errors
- assert bridges[-(num_bridges/8 + 1)].familiar == True
-
- def testDiscountAndPruneBridgeHistory(self):
- """ Test pruning of old Bridge History """
- if os.environ.get('TRAVIS'):
- self.skipTest("Hangs on Travis-CI.")
-
- db = self.db
-
- # make a bunch of bridges
- num_bridges = 20
- time_start = time.time()
- bridges = [random.choice([fakeBridge, fakeBridge6])()
- for i in xrange(num_bridges)]
-
- # run some of the bridges for the full time series
- running = bridges[:num_bridges/2]
- # and some that are not
- expired = bridges[num_bridges/2:]
-
- for b in running: assert b not in expired
-
- # Solving:
- # 1 discount event per 12 hours, 24 descriptors 30m apart
- num_successful = random.randint(2,60)
- # figure out how many intervals it will take for weightedUptime to
- # decay to < 1
- num_desc = int(30*log(1/float(num_successful*30*60))/(-0.05))
- timeseries = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
-
- for i in timeseries:
- for b in running:
- bridgedb.Stability.addOrUpdateBridgeHistory(b, i)
-
- if num_successful > 0:
- for b in expired:
- bridgedb.Stability.addOrUpdateBridgeHistory(b, i)
- num_successful -= 1
-
- # now we expect to see the bridge has been removed from history
- for bridge in expired:
- b = db.getBridgeHistory(bridge.fingerprint)
- assert b is None
- # and make sure none of the others have
- for bridge in running:
- b = db.getBridgeHistory(bridge.fingerprint)
- assert b is not None
-
-def testSuite():
- suite = unittest.TestSuite()
- loader = unittest.TestLoader()
- for klass in [SQLStorageTests, BridgeStabilityTests]:
- suite.addTest(loader.loadTestsFromTestCase(klass))
- return suite
-
-def main():
- unittest.TextTestRunner(verbosity=1).run(testSuite())
diff --git a/lib/bridgedb/test/test_Bucket.py b/lib/bridgedb/test/test_Bucket.py
deleted file mode 100644
index a6e335f..0000000
--- a/lib/bridgedb/test/test_Bucket.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.Bucket` module.
-
-These tests are meant to ensure that the :mod:`bridgedb.Bucket` module is
-functioning as expected.
-"""
-
-from __future__ import print_function
-
-from io import StringIO
-
-import sure
-from sure import this
-from sure import the
-from sure import expect
-
-from bridgedb import Bucket
-
-from twisted.trial import unittest
-
-
-class BucketDataTest(unittest.TestCase):
- """Tests for :class:`bridgedb.Bucket.BucketData`."""
-
- def test_alloc_some_of_the_bridges(self):
- """Set the needed number of bridges"""
- needed = 10
- distname = "test-distributor"
- bucket = Bucket.BucketData(distname, needed)
- this(bucket.name).should.be.equal(distname)
- this(bucket.needed).should.be.equal(needed)
-
- def test_alloc_all_the_bridges(self):
- """Set the needed number of bridges to the default"""
- needed = '*'
- distname = "test-distributor"
- bucket = Bucket.BucketData(distname, needed)
- this(bucket.name).should.be.equal(distname)
- this(bucket.needed).should.be.equal(Bucket.BUCKET_MAX_BRIDGES)
-
-
-class BucketManagerTest(unittest.TestCase):
- """Tests for :class:`bridgedb.Bucket.BucketManager`."""
- TEST_CONFIG_FILE = StringIO(unicode("""\
- FILE_BUCKETS = { 'test1': 7, 'test2': 11 }
- COLLECT_TIMESTAMPS = False
- COUNTRY_BLOCK_FILE = []"""))
-
- def setUp(self):
- configuration = {}
- TEST_CONFIG_FILE.seek(0)
- compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
- exec compiled in configuration
- self.config = persistent.Conf(**configuration)
- self.state = persistent.State(**config.__dict__)
- self.bucket = Bucket.BucketManager(self.config)
diff --git a/lib/bridgedb/test/test_Main.py b/lib/bridgedb/test/test_Main.py
deleted file mode 100644
index e53256f..0000000
--- a/lib/bridgedb/test/test_Main.py
+++ /dev/null
@@ -1,437 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: see included LICENSE for information
-
-"""Tests for :mod:`bridgedb.Main`."""
-
-from __future__ import print_function
-
-import base64
-import logging
-import os
-import random
-import shutil
-import sys
-
-from datetime import datetime
-from time import sleep
-
-from twisted.internet.threads import deferToThread
-from twisted.trial import unittest
-
-from bridgedb import Main
-from bridgedb.parse.options import parseOptions
-
-
-logging.getLogger().disabled = True
-
-
-HERE = os.getcwd()
-TOPDIR = HERE.rstrip('_trial_temp')
-CI_RUNDIR = os.path.join(TOPDIR, 'run')
-
-# A networkstatus descriptor with two invalid ORAddress (127.0.0.1 and ::1)
-# and an invalid port number (70000).
-NETWORKSTATUS_MALFORMED = '''\
-r OracleLunacy LBXW03FIKvo9aXCEYbDdq1BbNtM E0yN8ofiBpg6JHW0iPX5gJ1gKFI 2014-09-05 21:39:24 127.0.0.1 70000 0
-a [::1]:70000
-s Fast Guard Running Stable Valid
-w Bandwidth=2094050
-p reject 1-65535
-'''
-
-def mockUpdateBridgeHistory(bridges, timestamps):
- """A mocked version of :func:`bridgedb.Stability.updateBridgeHistory`
- which doesn't access the database (so that we can test functions which
- call it, like :func:`bridgedb.Main.load`).
- """
- for fingerprint, stamps in timestamps.items()[:]:
- for timestamp in stamps:
- print("Pretending to update Bridge %s with timestamp %s..." %
- (fingerprint, timestamp))
-
-
-class MockHashring(object):
- def __init__(self):
- self._bridges = {}
- def __len__(self):
- return len(self._bridges.keys())
- def insert(self, bridge):
- self._bridges[bridge.fingerprint] = bridge
- def clear(self):
- pass
- def dumpAssignments(self):
- pass
-
-
-class MainTests(unittest.TestCase):
- """Integration tests for :func:`bridgedb.Main.load`."""
-
- def _appendToFile(self, file, data):
- """Append **data** to **file**."""
- fh = open(file, 'a')
- fh.write(data)
- fh.flush()
- fh.close()
-
- def _copyDescFilesHere(self, files):
- """Copy all the **files** to the _trial_tmp/ directory.
-
- :param list files: A list of strings representing the paths to
- descriptor files. This should probably be taken from a
- ``bridgedb.persistent.Conf`` object which has parsed the
- ``bridgedb.conf`` file in the top-level directory of this repo.
- :rtype: list
- :returns: A list of the new paths (in the ``_trial_tmp`` directory) to
- the copied descriptor files. This should be used to update the
- ``bridgedb.persistent.Conf`` object.
- """
- updatedPaths = []
-
- for f in files:
- base = os.path.basename(f)
- src = os.path.join(CI_RUNDIR, base)
- if os.path.isfile(src):
- dst = os.path.join(HERE, base)
- shutil.copy(src, dst)
- updatedPaths.append(dst)
- else:
- self.skip = True
- raise unittest.SkipTest(
- "Can't find mock descriptor files in %s directory" %
- CI_RUNDIR)
-
- return updatedPaths
-
- def _cbAssertFingerprints(self, d):
- """Assert that there are some bridges in the hashring."""
- self.assertGreater(len(self.hashring), 0)
- return d
-
- def _cbCallUpdateBridgeHistory(self, d, hashring):
- """Fake some timestamps for the bridges in the hashring, and then call
- Main.updateBridgeHistory().
- """
- def timestamp():
- return datetime.fromtimestamp(random.randint(1324285117, 1524285117))
-
- bridges = hashring._bridges
- timestamps = {}
-
- for fingerprint, _ in bridges.items():
- timestamps[fingerprint] = [timestamp(), timestamp(), timestamp()]
-
- return Main.updateBridgeHistory(bridges, timestamps)
-
- def _eb_Failure(self, failure):
- """If something produces a twisted.python.failure.Failure, fail the
- test with it.
- """
- self.fail(failure)
-
- def _writeConfig(self, config):
- """Write a config into the current working directory.
-
- :param str config: A big long multiline string that looks like the
- bridgedb.conf file.
- :rtype: str
- :returns: The pathname of the file that the **config** was written to.
- """
- configFile = os.path.join(os.getcwd(), 'bridgedb.conf')
- fh = open(configFile, 'w')
- fh.write(config)
- fh.flush()
- fh.close()
- return configFile
-
- def setUp(self):
- """Find the bridgedb.conf file in the top-level directory of this repo,
- copy it and the descriptor files it references to the current working
- directory, produce a state object from the loaded bridgedb.conf file,
- and make an HMAC key.
- """
- # Get the bridgedb.conf file in the top-level directory of this repo:
- self.configFile = os.path.join(TOPDIR, 'bridgedb.conf')
- self.config = Main.loadConfig(self.configFile)
-
- # Copy the referenced descriptor files from bridgedb/run/ to CWD:
- self.config.STATUS_FILE = self._copyDescFilesHere([self.config.STATUS_FILE])[0]
- self.config.BRIDGE_FILES = self._copyDescFilesHere(self.config.BRIDGE_FILES)
- self.config.EXTRA_INFO_FILES = self._copyDescFilesHere(self.config.EXTRA_INFO_FILES)
-
- # Initialise the state
- self.state = Main.persistent.State(**self.config.__dict__)
- self.key = base64.b64decode('TvPS1y36BFguBmSOvhChgtXB2Lt+BOw0mGfz9SZe12Y=')
-
- # Create a pseudo hashring
- self.hashring = MockHashring()
-
- # Functions which some tests mock, which we'll need to re-replace
- # later in tearDown():
- self._orig_updateBridgeHistory = Main.updateBridgeHistory
- self._orig_sys_argv = sys.argv
-
- def tearDown(self):
- """Replace the mocked mockUpdateBridgeHistory() function with the
- real function, Stability.updateBridgeHistory().
- """
- Main.updateBridgeHistory = self._orig_updateBridgeHistory
- sys.argv = self._orig_sys_argv
-
- def test_Main_updateBridgeHistory(self):
- """Main.updateBridgeHistory should update some timestamps for some
- bridges.
- """
- # Mock the updateBridgeHistory() function so that we don't try to
- # access the database:
- Main.updateBridgeHistory = mockUpdateBridgeHistory
-
- # Get the bridges into the mocked hashring
- d = deferToThread(Main.load, self.state, self.hashring)
- d.addCallback(self._cbAssertFingerprints)
- d.addErrback(self._eb_Failure)
- d.addCallback(self._cbCallUpdateBridgeHistory, self.hashring)
- d.addErrback(self._eb_Failure)
- return d
-
- def test_Main_load(self):
- """Main.load() should run without error."""
- d = deferToThread(Main.load, self.state, self.hashring)
- d.addCallback(self._cbAssertFingerprints)
- d.addErrback(self._eb_Failure)
- return d
-
- def test_Main_load_no_state(self):
- """Main.load() should raise SystemExit without a state object."""
- self.assertRaises(SystemExit, Main.load, None, self.hashring)
-
- def test_Main_load_clear(self):
- """When called with clear=True, load() should run and clear the
- hashrings.
- """
- d = deferToThread(Main.load, self.state, self.hashring, clear=True)
- d.addCallback(self._cbAssertFingerprints)
- d.addErrback(self._eb_Failure)
- return d
-
- def test_Main_load_collect_timestamps(self):
- """When COLLECT_TIMESTAMPS=True, Main.load() should call
- Main.updateBridgeHistory().
- """
- # Mock the addOrUpdateBridgeHistory() function so that we don't try to
- # access the database:
- Main.updateBridgeHistory = mockUpdateBridgeHistory
- state = self.state
- state.COLLECT_TIMESTAMPS = True
-
- # The reactor is deferring this to a thread, so the test execution
- # here isn't actually covering the Storage.updateBridgeHistory()
- # function:
- Main.load(state, self.hashring)
-
- def test_Main_load_malformed_networkstatus(self):
- """When called with a networkstatus file with an invalid descriptor,
- Main.load() should raise a ValueError.
- """
- self._appendToFile(self.state.STATUS_FILE, NETWORKSTATUS_MALFORMED)
- self.assertRaises(ValueError, Main.load, self.state, self.hashring)
-
- def test_Main_reloadFn(self):
- """Main._reloadFn() should return True."""
- self.assertTrue(Main._reloadFn())
-
- def test_Main_handleSIGHUP(self):
- """Main._handleSIGHUP() should return True."""
- raise unittest.SkipTest("_handleSIGHUP touches the reactor.")
-
- self.assertTrue(Main._handleSIGHUP())
-
- def test_Main_createBridgeRings(self):
- """Main.createBridgeRings() should add three hashrings to the
- hashring.
- """
- proxyList = None
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(self.config,
- proxyList,
- self.key)
- # Should have an HTTPSDistributor ring, an EmailDistributor ring,
- # and an UnallocatedHolder ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 3)
-
- def test_Main_createBridgeRings_with_proxyList(self):
- """Main.createBridgeRings() should add three hashrings to the
- hashring and add the proxyList to the IPBasedDistibutor.
- """
- exitRelays = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
- proxyList = Main.proxy.ProxySet()
- proxyList.addExitRelays(exitRelays)
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(self.config,
- proxyList,
- self.key)
- # Should have an HTTPSDistributor ring, an EmailDistributor ring,
- # and an UnallocatedHolder ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 3)
- self.assertGreater(len(httpsDist.proxies), 0)
- self.assertItemsEqual(exitRelays, httpsDist.proxies)
-
- def test_Main_createBridgeRings_no_https_dist(self):
- """When HTTPS_DIST=False, Main.createBridgeRings() should add only
- two hashrings to the hashring.
- """
- proxyList = Main.proxy.ProxySet()
- config = self.config
- config.HTTPS_DIST = False
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
- proxyList,
- self.key)
- # Should have an EmailDistributor ring, and an UnallocatedHolder ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 2)
- self.assertNotIn('https', hashring.rings)
- self.assertNotIn(httpsDist, hashring.ringsByName.values())
-
- def test_Main_createBridgeRings_no_email_dist(self):
- """When EMAIL_DIST=False, Main.createBridgeRings() should add only
- two hashrings to the hashring.
- """
- proxyList = Main.proxy.ProxySet()
- config = self.config
- config.EMAIL_DIST = False
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
- proxyList,
- self.key)
- # Should have an HTTPSDistributor ring, and an UnallocatedHolder ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 2)
- self.assertNotIn('email', hashring.rings)
- self.assertNotIn(emailDist, hashring.ringsByName.values())
-
- def test_Main_createBridgeRings_no_reserved_share(self):
- """When RESERVED_SHARE=0, Main.createBridgeRings() should add only
- two hashrings to the hashring.
- """
- proxyList = Main.proxy.ProxySet()
- config = self.config
- config.RESERVED_SHARE = 0
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
- proxyList,
- self.key)
- # Should have an HTTPSDistributor ring, and an EmailDistributor ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 2)
- self.assertNotIn('unallocated', hashring.rings)
-
- def test_Main_createBridgeRings_two_file_buckets(self):
- """When FILE_BUCKETS has two filenames in it, Main.createBridgeRings()
- should add three hashrings to the hashring, then add two
- "pseudo-rings".
- """
- proxyList = Main.proxy.ProxySet()
- config = self.config
- config.FILE_BUCKETS = {
- 'bridges-for-support-desk': 10,
- 'bridges-for-ooni-tests': 10,
- }
- (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
- proxyList,
- self.key)
- # Should have an HTTPSDistributor ring, an EmailDistributor, and an
- # UnallocatedHolder ring:
- self.assertEqual(len(hashring.ringsByName.keys()), 3)
-
- # Should have two pseudoRings:
- self.assertEqual(len(hashring.pseudoRings), 2)
- self.assertIn('pseudo_bridges-for-support-desk', hashring.pseudoRings)
- self.assertIn('pseudo_bridges-for-ooni-tests', hashring.pseudoRings)
-
- def test_Main_run(self):
- """Main.run() should run and then finally raise SystemExit."""
- config = """
-BRIDGE_FILES = ["../run/bridge-descriptors"]
-EXTRA_INFO_FILES = ["../run/cached-extrainfo", "../run/cached-extrainfo.new"]
-STATUS_FILE = "../run/networkstatus-bridges"
-HTTPS_CERT_FILE="cert"
-HTTPS_KEY_FILE="privkey.pem"
-LOGFILE = "bridgedb.log"
-PIDFILE = "bridgedb.pid"
-DB_FILE = "bridgedist.db"
-DB_LOG_FILE = "bridgedist.log"
-MASTER_KEY_FILE = "secret_key"
-ASSIGNMENTS_FILE = "assignments.log"
-LOGLEVEL = "DEBUG"
-SAFELOGGING = True
-LOGFILE_COUNT = 5
-LOGFILE_ROTATE_SIZE = 10000000
-LOG_THREADS = False
-LOG_TRACE = True
-LOG_TIME_FORMAT = "%H:%M:%S"
-COLLECT_TIMESTAMPS = False
-NO_DISTRIBUTION_COUNTRIES = ['IR', 'SY']
-PROXY_LIST_FILES = []
-N_IP_CLUSTERS = 3
-FORCE_PORTS = [(443, 1)]
-FORCE_FLAGS = [("Stable", 1)]
-BRIDGE_PURPOSE = "bridge"
-TASKS = {'GET_TOR_EXIT_LIST': 3 * 60 * 60,}
-SERVER_PUBLIC_FQDN = 'bridges.torproject.org'
-SERVER_PUBLIC_EXTERNAL_IP = '38.229.72.19'
-HTTPS_DIST = True
-HTTPS_BIND_IP = None
-HTTPS_PORT = None
-HTTPS_N_BRIDGES_PER_ANSWER = 3
-HTTPS_INCLUDE_FINGERPRINTS = True
-HTTPS_USE_IP_FROM_FORWARDED_HEADER = False
-HTTP_UNENCRYPTED_BIND_IP = "127.0.0.1"
-HTTP_UNENCRYPTED_PORT = 55555
-HTTP_USE_IP_FROM_FORWARDED_HEADER = False
-RECAPTCHA_ENABLED = False
-RECAPTCHA_PUB_KEY = ''
-RECAPTCHA_SEC_KEY = ''
-RECAPTCHA_REMOTEIP = ''
-GIMP_CAPTCHA_ENABLED = False
-GIMP_CAPTCHA_DIR = 'captchas'
-GIMP_CAPTCHA_HMAC_KEYFILE = 'captcha_hmac_key'
-GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key'
-EMAIL_DIST = True
-EMAIL_FROM_ADDR = "bridges at torproject.org"
-EMAIL_SMTP_FROM_ADDR = "bridges at torproject.org"
-EMAIL_SMTP_HOST = "127.0.0.1"
-EMAIL_SMTP_PORT = 55556
-EMAIL_USERNAME = "bridges"
-EMAIL_DOMAINS = ["somewhere.com", "somewhereelse.net"]
-EMAIL_DOMAIN_MAP = {
- "mail.somewhere.com": "somewhere.com",
- "mail.somewhereelse.net": "somewhereelse.net",
-}
-EMAIL_DOMAIN_RULES = {
- 'somewhere.com': ["ignore_dots", "dkim"],
- 'somewhereelse.net': ["dkim"],
-}
-EMAIL_WHITELIST = {}
-EMAIL_BLACKLIST = []
-EMAIL_FUZZY_MATCH = 4
-EMAIL_RESTRICT_IPS = []
-EMAIL_BIND_IP = "127.0.0.1"
-EMAIL_PORT = 55557
-EMAIL_N_BRIDGES_PER_ANSWER = 3
-EMAIL_INCLUDE_FINGERPRINTS = True
-EMAIL_GPG_SIGNING_ENABLED = False
-EMAIL_GPG_HOMEDIR = '../.gnupg'
-EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = '0017098C5DF4197E3C884DCFF1B240D43F148C21'
-EMAiL_GPG_PASSPHRASE = None
-EMAIL_GPG_PASSPHRASE_FILE = None
-HTTPS_SHARE = 10
-EMAIL_SHARE = 5
-RESERVED_SHARE = 2
-FILE_BUCKETS = {}"""
- configFile = self._writeConfig(config)
-
- # Fake some options:
- sys.argv = ['bridgedb', '-r', os.getcwd(), '-c', configFile]
- options = parseOptions()
-
- self.assertRaises(SystemExit, Main.run, options, reactor=None)
diff --git a/lib/bridgedb/test/test_Storage.py b/lib/bridgedb/test/test_Storage.py
deleted file mode 100644
index 8b0affd..0000000
--- a/lib/bridgedb/test/test_Storage.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-"""Unittests for the :mod:`bridgedb.Storage` module."""
-
-from twisted.python import log
-from twisted.trial import unittest
-import bridgedb.Storage as Storage
-from twisted.internet import reactor
-from twisted.internet.threads import deferToThread
-import os
-import threading
-from time import sleep
-
-class DatabaseTest(unittest.TestCase):
- def setUp(self):
- self.dbfname = 'test-bridgedb.sqlite'
- Storage.setDBFilename(self.dbfname)
-
- def tearDown(self):
- if os.path.isfile(self.dbfname):
- os.unlink(self.dbfname)
- Storage.clearGlobalDB()
-
- def _runAndDie(self, timeout, func):
- with func():
- sleep(timeout)
-
- def _cb_assertTrue(self, result):
- self.assertTrue(result)
-
- def _cb_assertFalse(self, result):
- self.assertFalse(result)
-
- def _eb_Failure(self, failure):
- self.fail(failure)
-
- def test_getDB_FalseWhenLocked(self):
- Storage._LOCK = threading.Lock()
- Storage._LOCK.acquire()
- self.assertFalse(Storage._LOCK.acquire(False))
-
- def test_getDB_AcquireLock(self):
- Storage.initializeDBLock()
- with Storage.getDB() as db:
- self.assertIsInstance(db, Storage.Database)
- self.assertTrue(Storage.dbIsLocked())
- self.assertEqual(db, Storage._OPENED_DB)
-
- def test_getDB_ConcurrencyLock(self):
- timeout = 1
- d1 = deferToThread(self._runAndDie, timeout, Storage.getDB)
- d1.addCallback(self._cb_assertFalse)
- d1.addErrback(self._eb_Failure)
- d2 = deferToThread(Storage.getDB, False)
- d2.addCallback(self._cb_assertFalse)
- d2.addErrback(self._eb_Failure)
- d2.addCallback(self._cb_assertTrue, Storage.getDB(False))
diff --git a/lib/bridgedb/test/test_Tests.py b/lib/bridgedb/test/test_Tests.py
deleted file mode 100644
index 84c3f8b..0000000
--- a/lib/bridgedb/test/test_Tests.py
+++ /dev/null
@@ -1,248 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Class wrappers to adapt BridgeDB old unittests in :mod:`bridgedb.Tests`
-(now kept in :mod:`bridgedb.test.legacy_Tests`) to be compatible with the
-newer :api:`twisted.trial` unittests in this directory.
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import binascii
-import doctest
-import glob
-import logging
-import os
-import warnings
-
-from twisted.python import monkey
-from twisted.trial import unittest
-
-from bridgedb.test import legacy_Tests as Tests
-from bridgedb.test import deprecated
-
-
-warnings.filterwarnings('ignore', module="bridgedb\.test\.legacy_Tests")
-pyunit = __import__('unittest')
-
-
-def generateTrialAdaptedDoctestsSuite():
- """Dynamically generates a :class:`unittest.TestSuite` all containing
- discovered doctests within the installed ``bridgedb`` package.
- """
- bridgedb = __import__('bridgedb')
- fakedGlobals = globals().update({'bridgedb': bridgedb})
- modulePath = bridgedb.__path__[0]
-
- #: The package directories to search for source code with doctests.
- packagePaths = [modulePath,
- os.path.join(modulePath, 'parse'),
- os.path.join(modulePath, 'test')]
- #: The source code files which will be searched for doctests.
- files = []
- #: The cls.testSuites which the test methods will be generated from.
- testSuites = []
-
- packages = [os.path.join(pkg, '*.py') for pkg in packagePaths]
- [files.extend(glob.glob(pkg)) for pkg in packages]
-
- for filename in files:
- testSuites.append(
- doctest.DocFileSuite(filename,
- module_relative=False,
- globs=fakedGlobals))
- return testSuites
-
-def monkeypatchTests():
- """Monkeypatch the old unittests, replacing new, refactored code with their
- original equivalents from :mod:`bridgedb.test.deprecated`.
-
- The first patch replaces the newer parsing function,
- :func:`~bridgedb.parse.networkstatus.parseALine`, with the older,
- :func:`deprecated one <bridgedb.test.deprecated.parseORAddressLine>` (the
- old function was previously located at
- ``bridgedb.Bridges.parseORAddressLine``).
-
- The second patch replaces the new :class:`~bridgedb.parse.addr.PortList`,
- with the :class:`older one <bridgedb.test.deprecated.PortList>` (which
- was previously located at ``bridgedb.Bridges.PortList``).
-
- The third, forth, and fifth monkeypatches add some module-level attributes
- back into :mod:`bridgedb.Bridges`.
-
- :rtype: :api:`~twisted.python.monkey.MonkeyPatcher`
- :returns: A :api:`~twisted.python.monkey.MonkeyPatcher`, preloaded with
- patches from :mod:`bridgedb.test.deprecated`.
- """
- patcher = monkey.MonkeyPatcher()
- patcher.addPatch(Tests.bridgedb.Bridges, 'PluggableTransport',
- deprecated.PluggableTransport)
- patcher.addPatch(Tests.bridgedb.Bridges, 'Bridge',
- deprecated.Bridge)
- return patcher
-
-
-class DynamicTestCaseMeta(type):
- """You know how scary the seemingly-arbitrary constants in elliptic curve
- cryptography seem? Well, I am over nine thousand times more scary. Dynamic
- warez⦠beware! Be afraid; be very afraid.
-
- :ivar testResult: An :class:`unittest.TestResult` adapted with
- :api:`twisted.trial.unittest.PyUnitResultAdapter`, for
- storing test failures and successes in.
-
- A base class which uses this metaclass should define the following class
- attributes:
-
- :ivar testSuites: A list of :class:`unittest.TestSuite`s (or their
- :mod:`doctest` or :api:`twisted.trial` equivalents).
- :ivar methodPrefix: A string to prefix the generated method names
- with. (default: 'test_')
- """
-
- testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
-
- def __new__(cls, name, bases, attrs):
- """Construct the initialiser for a new
- :api:`twisted.trial.unittest.TestCase`.
- """
- logging.debug("Metaclass __new__ constructor called for %r" % name)
-
- if not 'testSuites' in attrs:
- attrs['testSuites'] = list()
- if not 'methodPrefix' in attrs:
- attrs['methodPrefix'] = 'test_'
-
- testSuites = attrs['testSuites']
- methodPrefix = attrs['methodPrefix']
- logging.debug(
- "Metaclass __new__() class %r(testSuites=%r, methodPrefix=%r)" %
- (name, '\n\t'.join([str(ts) for ts in testSuites]), methodPrefix))
-
- generatedMethods = cls.generateTestMethods(testSuites, methodPrefix)
- attrs.update(generatedMethods)
- #attrs['init'] = cls.__init__ # call the standard initialiser
- return super(DynamicTestCaseMeta, cls).__new__(cls, name, bases, attrs)
-
- @classmethod
- def generateTestMethods(cls, testSuites, methodPrefix='test_'):
- """Dynamically generate methods and their names for a
- :api:`twisted.trial.unittest.TestCase`.
-
- :param list testSuites: A list of :class:`unittest.TestSuite`s (or
- their :mod:`doctest` or :api:`twisted.trial`
- equivalents).
- :param str methodPrefix: A string to prefix the generated method names
- with. (default: 'test_')
- :rtype: dict
- :returns: A dictionary of class attributes whose keys are dynamically
- generated method names (prefixed with **methodPrefix**), and
- whose corresponding values are dynamically generated methods
- (taken out of the class attribute ``testSuites``).
- """
- def testMethodFactory(test, name):
- def createTestMethod(test):
- def testMethod(*args, **kwargs):
- """When this function is generated, a methodname (beginning
- with whatever **methodPrefix** was set to) will also be
- generated, and the (methodname, method) pair will be
- assigned as attributes of the generated
- :api:`~twisted.trial.unittest.TestCase`.
- """
- # Get the number of failures before test.run():
- origFails = len(cls.testResult.original.failures)
- test.run(cls.testResult)
- # Fail the generated testMethod if the underlying failure
- # count has increased:
- if (len(cls.testResult.original.failures) > origFails):
- fail = cls.testResult.original.failures[origFails:][0]
- raise unittest.FailTest(''.join([str(fail[0]),
- str(fail[1])]))
- return cls.testResult
- testMethod.__name__ = str(name)
- return testMethod
- return createTestMethod(test)
-
- newAttrs = {}
- for testSuite in testSuites:
- for test in testSuite:
- origName = test.id()
- if origName.find('.') > 0:
- origFunc = origName.split('.')[-2:]
- origName = '_'.join(origFunc)
- if origName.endswith('_py'): # this happens with doctests
- origName = origName.strip('_py')
- methName = str(methodPrefix + origName).replace('.', '_')
- meth = testMethodFactory(test, methName)
- logging.debug("Set %s.%s=%r" % (cls.__name__, methName, meth))
- newAttrs[methName] = meth
- return newAttrs
-
-
-class OldUnittests(unittest.TestCase):
- """A wrapper around :mod:`bridgedb.Tests` to produce :api:`~twisted.trial`
- compatible output.
-
- Generates a :api:`twisted.trial.unittest.TestCase` containing a
- test for each of the individual tests in :mod:`bridgedb.Tests`.
-
- Each test in this :api:`~twisted.trial.unittest.TestCase`` is dynamically
- generated from one of the old unittests in :mod:`bridgedb.Tests`. Then,
- the class is wrapped to cause the results reporting mechanisms to be
- :api:`~twisted.trial` compatible.
-
- :returns: A :api:`twisted.trial.unittest.TestCase`.
- """
- __metaclass__ = DynamicTestCaseMeta
- testSuites = Tests.testSuite()
- testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
- methodPrefix = 'test_regressionsNewCode_'
-
-
-class MonkeypatchedOldUnittests(unittest.TestCase):
- """A wrapper around :mod:`bridgedb.Tests` to produce :api:`~twisted.trial`
- compatible output.
-
- For each test in this ``TestCase``, one of the old unittests in
- bridgedb/Tests.py is run. For all of the tests, some functions and classes
- are :api:`twisted.python.monkey.MonkeyPatcher.patch`ed with old,
- deprecated code from :mod:`bridgedb.test.deprecated` to ensure that any
- new code has not caused any regressions.
- """
- __metaclass__ = DynamicTestCaseMeta
- testSuites = Tests.testSuite()
- testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
- methodPrefix = 'test_regressionsOldCode_'
- patcher = monkeypatchTests()
-
- def runWithPatches(self, *args):
- """Replaces :api:`~twisted.trial.unittest.TestCase.run` as the default
- methodName to run. This method calls ``run()`` though
- ``self.patcher.runWithPatches``, using the class **testResult** object.
- """
- self.patcher.runWithPatches(self.run, self.testResult)
- self.patcher.restore()
-
- def __init__(self, methodName='runWithPatches'):
- super(MonkeypatchedOldUnittests, self).__init__(methodName=methodName)
-
-
-class TrialAdaptedDoctests(unittest.TestCase):
- """Discovers and runs all doctests within the ``bridgedb`` package.
-
- Finds all doctests from the directory that BridgeDB was installed in, in
- all Python modules and packages, and runs them with :api:`twisted.trial`.
- """
- __metaclass__ = DynamicTestCaseMeta
- testSuites = generateTrialAdaptedDoctestsSuite()
- testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
- methodPrefix = 'test_doctestsIn_'
diff --git a/lib/bridgedb/test/test_bridgedb.py b/lib/bridgedb/test/test_bridgedb.py
deleted file mode 100644
index 70c71f0..0000000
--- a/lib/bridgedb/test/test_bridgedb.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the `bridgedb` commandline script."""
-
-from __future__ import print_function
-
-import os
-import signal
-import time
-
-from twisted.trial import unittest
-from twisted.trial.unittest import FailTest
-from twisted.trial.unittest import SkipTest
-
-from bridgedb.test.util import processExists
-from bridgedb.test.util import getBridgeDBPID
-
-
-class BridgeDBCliTest(unittest.TestCase):
- """Test the `bridgedb` command."""
-
- def setUp(self):
- here = os.getcwd()
- topdir = here.rstrip('_trial_temp')
- self.rundir = os.path.join(topdir, 'run')
- self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
- self.pid = getBridgeDBPID(self.pidfile)
- self.assignmentsFile = os.path.join(self.rundir, 'assignments.log')
-
- def doSleep(self):
- """Sleep for some ammount of time.
-
- We usually have less fake bridge descriptors with CI runs than we do
- during other tests, so we can safely decrease the sleep time on CI
- machines.
- """
- if os.environ.get("TRAVIS"):
- time.sleep(10)
- else:
- time.sleep(20)
- return
-
- def test_bridgedb_assignments_log(self):
- """This test should only be run if a BridgeDB server has already been
- started in another process.
-
- To see how this is done for the Travis CI tests, see the
- 'before_script' section of the ``.travis.yml`` file in the top
- directory of this repository.
-
- This test ensures that an ``assignments.log`` file is created after a
- BridgeDB process was started.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- self.assertTrue(os.path.isfile(self.assignmentsFile))
-
- def test_bridgedb_SIGHUP_assignments_log(self):
- """Test that BridgeDB creates a new ``assignments.log`` file after
- receiving a SIGHUP.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- os.unlink(self.assignmentsFile)
- os.kill(self.pid, signal.SIGHUP)
- self.doSleep()
- self.assertTrue(os.path.isfile(self.assignmentsFile))
-
- def test_bridgedb_SIGUSR1_buckets(self):
- """Test that BridgeDB dumps buckets appropriately after a SIGUSR1."""
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- os.kill(self.pid, signal.SIGUSR1)
- self.doSleep()
- buckets = [['email', False], ['https', False], ['unallocated', False]]
- for rundirfile in os.listdir(self.rundir):
- for bucket in buckets:
- if rundirfile.startswith(bucket[0]):
- bucket[1] = True
- break
- for bucket in buckets:
- self.assertTrue(bucket[1], "%s bucket was not dumped!" % bucket[0])
diff --git a/lib/bridgedb/test/test_bridgerequest.py b/lib/bridgedb/test/test_bridgerequest.py
deleted file mode 100644
index ccbf406..0000000
--- a/lib/bridgedb/test/test_bridgerequest.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-from twisted.trial import unittest
-
-from bridgedb import bridgerequest
-from bridgedb.bridgerequest import IRequestBridges
-from bridgedb.bridgerequest import BridgeRequestBase
-
-
-class BridgeRequestBaseTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.bridgerequest.BridgeRequestBase`."""
-
- def setUp(self):
- """Setup test run."""
- self.request = BridgeRequestBase()
-
- def test_BridgeRequestBase_implements_IRequestBridges(self):
- """BridgeRequestBase should implement IRequestBridges interface."""
- self.assertTrue(IRequestBridges.implementedBy(BridgeRequestBase))
-
- def test_BridgeRequestBase_withoutBlockInCountry(self):
- """BridgeRequestBase.withoutBlockInCountry() should add the country CC
- to the ``notBlockedIn`` attribute.
- """
- self.request.withoutBlockInCountry('US')
- self.assertIn('us', self.request.notBlockedIn)
-
- def test_BridgeRequestBase_withPluggableTransportType(self):
- """BridgeRequestBase.withPluggableTransportType() should add the
- pluggable transport type to the ``transport`` attribute.
- """
- self.request.withPluggableTransportType('huggable_transport')
- self.assertIn('huggable_transport', self.request.transports)
-
- def test_BridgeRequestBase_getHashringPlacement_without_client(self):
- """BridgeRequestBase.getHashringPlacement() without a client parameter
- should use the default client identifier string.
- """
- self.assertEqual(self.request.getHashringPlacement('AAAA'),
- 3486762050L)
-
- def test_BridgeRequestBase_getHashringPlacement_with_client(self):
- """BridgeRequestBase.getHashringPlacement() with a client parameter
- should use the client identifier string.
- """
- self.assertEqual(self.request.getHashringPlacement('AAAA', client='you'),
- 2870307088L)
diff --git a/lib/bridgedb/test/test_bridges.py b/lib/bridgedb/test/test_bridges.py
deleted file mode 100644
index db662ae..0000000
--- a/lib/bridgedb/test/test_bridges.py
+++ /dev/null
@@ -1,1820 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.bridges` module."""
-
-from binascii import a2b_hex
-
-import datetime
-import ipaddr
-import io
-import hashlib
-import os
-import warnings
-
-from twisted.trial import unittest
-
-from bridgedb import bridges
-from bridgedb.Bridges import FilteredBridgeSplitter
-from bridgedb.bridgerequest import BridgeRequestBase
-from bridgedb.parse import descriptors
-from bridgedb.parse.addr import PortList
-from bridgedb.parse.nickname import InvalidRouterNickname
-
-
-# Don't print "WARNING:root: Couldn't parse K=V from PT arg: ''" a bunch of
-# times while running the tests.
-warnings.filterwarnings("ignore", ".*Couldn't parse K=V from PT arg.*", Warning)
-
-
-BRIDGE_NETWORKSTATUS = '''\
-r FourfoldQuirked LDIlxIBTMQJeIR9Lblv0XDM/3Sw c4EVu2rO/iD/DJYBX/Ll38DGQWI 2014-12-22 21:51:27 179.178.155.140 36489 0
-a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488
-s Fast Guard Running Stable Valid
-w Bandwidth=1585163
-p reject 1-65535
-'''
-
-BRIDGE_SERVER_DESCRIPTOR = '''\
- at purpose bridge
-router FourfoldQuirked 179.178.155.140 36489 0 0
-or-address [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488
-platform Tor 0.2.3.24-rc on Linux
-opt protocols Link 1 2 Circuit 1
-published 2014-12-22 21:51:27
-opt fingerprint 2C32 25C4 8053 3102 5E21 1F4B 6E5B F45C 333F DD2C
-uptime 33200687
-bandwidth 1866688205 2110169275 1623207134
-opt extra-info-digest 4497E81715D958105C6A39D348163AD8F3080FB2
-onion-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBANKicvIGWp9WGKOJV8Fs3YKdTDrgxlggzyKgkW+MZWEPQ9lLcrmXqBdW
-nVK5EABByHnnxJfk+sm+6yDYxY/lFVL1SEP84pAK1Z21f4+grNlwox1DLyntXDdz
-BCZuRszuBYK3ncsk+ePQeUzRKQ/GZt9s/oy0IjtNbAoAoq7DKUVzAgMBAAE=
------END RSA PUBLIC KEY-----
-signing-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBALnJK7A9aZIp2ry9ruVYzm4VfaXzNHdTcvkXTrETu/jLXsosEwj9viSe
-Ry3W/uctbjzdwlIY0ZBUuV20q9bh+/c7Q0T8LOHBZouOy+nhFOUX+Q5YCG9cRnY0
-hBebYTzyplh0tT8xyYwcS8y6esL+gjVDLo6Og3QPhWRFQ4CyCic9AgMBAAE=
------END RSA PUBLIC KEY-----
-contact Somebody <somebody at example.com>
-ntor-onion-key aVmfOm9C046wM8ktGnpfBHSNj1Jm30M/m2P7W3a7Xn8
-reject *:*
-router-signature
------BEGIN SIGNATURE-----
-nxml4rTyTrj8dHcsFt2B4ACz2AN5CuZ2t5UF1BtXUpuzHmqVlg7imy8Cp2xIwoDa
-4uv/tTG32macauVnMHt0hSbtBF5nHfxU9G1T/XzdtL+KD8REDGky4allXnmvF6In
-rFtSn2OeZewvi8oYPmVYKgzHL6tzZxs2Sn/bOTj5sRw=
------END SIGNATURE-----
-'''
-
-BRIDGE_EXTRAINFO = '''\
-extra-info FourfoldQuirked 2C3225C4805331025E211F4B6E5BF45C333FDD2C
-published 2014-12-22 21:51:27
-write-history 2014-12-22 21:51:27 (900 s) 3188736,2226176,2866176
-read-history 2014-12-22 21:51:27 (900 s) 3891200,2483200,2698240
-dirreq-write-history 2014-12-22 21:51:27 (900 s) 1024,0,2048
-dirreq-read-history 2014-12-22 21:51:27 (900 s) 0,0,0
-geoip-db-digest 51AE9611B53880B2BCF9C71E735D73F33FAD2DFE
-geoip6-db-digest 26B0D55B20BEB496A3ADE7C6FDD866F5A81027F7
-dirreq-stats-end 2014-12-22 21:51:27 (86400 s)
-dirreq-v3-ips
-dirreq-v3-reqs
-dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
-transport obfs3 179.178.155.140:36490
-transport obfs2 179.178.155.140:36491
-transport scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
-transport obfs4 179.178.155.140:36493 iat-mode=0,node-id=25293f2761d658cc70c19515861842d712751bdc,public-key=02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc
-bridge-stats-end 2014-12-22 21:51:27 (86400 s)
-bridge-ips ca=8
-bridge-ip-versions v4=8,v6=0
-bridge-ip-transports <OR>=8
-router-signature
------BEGIN SIGNATURE-----
-cn4+8pQwCMPnHcp1s8wm7ZYsnd9AXJH6ysNlvQ63jsPCG9JdE5E8BwCThEgUccJI
-XILT4o+SveEQUG72R4bENsKxqV4rRNh1g6CNAbYhAITqrU9B+jImDgrBBW+XWT5K
-78ECRPn6Y4KsxFb0TIn7ddv9QjApyBJNIDMihH80Yng=
------END SIGNATURE-----
-'''
-
-# The timestamps have changed, and the IP:port for obfs3 changed
-BRIDGE_EXTRAINFO_NEW = '''\
-extra-info FourfoldQuirked 2C3225C4805331025E211F4B6E5BF45C333FDD2C
-published 2014-12-22 22:51:27
-write-history 2014-12-22 22:51:27 (900 s) 3188736,2226176,2866176
-read-history 2014-12-22 22:51:27 (900 s) 3891200,2483200,2698240
-dirreq-write-history 2014-12-22 22:51:27 (900 s) 1024,0,2048
-dirreq-read-history 2014-12-22 22:51:27 (900 s) 0,0,0
-geoip-db-digest 51AE9611B53880B2BCF9C71E735D73F33FAD2DFE
-geoip6-db-digest 26B0D55B20BEB496A3ADE7C6FDD866F5A81027F7
-dirreq-stats-end 2014-12-22 22:51:27 (86400 s)
-dirreq-v3-ips
-dirreq-v3-reqs
-dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
-transport obfs3 11.11.11.11:1111
-transport obfs2 179.178.155.140:36491
-transport scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
-transport obfs4 179.178.155.140:36493 iat-mode=0,node-id=25293f2761d658cc70c19515861842d712751bdc,public-key=02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc
-bridge-stats-end 2014-12-22 22:51:27 (86400 s)
-bridge-ips ca=8
-bridge-ip-versions v4=8,v6=0
-bridge-ip-transports <OR>=8
-router-signature
------BEGIN SIGNATURE-----
-cn4+8pQwCMPnHcp1s8wm7ZYsnd9AXJH6ysNlvQ63jsPCG9JdE5E8BwCThEgUccJI
-XILT4o+SveEQUG72R4bENsKxqV4rRNh1g6CNAbYhAITqrU9B+jImDgrBBW+XWT5K
-78ECRPn6Y4KsxFb0TIn7ddv9QjApyBJNIDMihH80Yng=
------END SIGNATURE-----
-'''
-
-
-class BridgeIntegrationTests(unittest.TestCase):
- """Integration tests to ensure that the new :class:`bridgedb.bridges.Bridge`
- class has compatible behaviour with the expected behaviour of the old
- :class:`bridgedb.Bridges.Bridge` class.
-
- .. data: OldTest (enum)
-
- These tests were refactored from the old tests for
- :class:`~bridgedb.test.deprecated.Bridge`, which lived in
- ``lib/bridgedb/test/test_Bridges.py``. For the translations from the old
- tests in ``bridgedb.test.test_Bridges.BridgeClassTest`` to their new
- equivalents here in ``bridgedb.test.test_bridges.BridgeIntegrationTests``,
- which should test for the same things as their old equivalents, see the
- following table:
-
- ============================================== ========================
- OldTest Equivalent Test(s) Here
- ============================================== ========================
- test_init test_integration_init_[0-5]
- test_getID test_integration_getID
- test_setDescriptorDigest test_integration_setDescriptorDigest
- test_setExtraInfoDigest test_integration_setExtraInfoDigest
- test_setVerified test_integration_setVerified
- test_setRunningStable test_integration_setRunningStable
- test_getConfigLine_vanilla_withoutFingerprint test_integration_getConfigLine_vanilla_withoutFingerprint
- test_getConfigLine_vanilla_withFingerprint test_integration_getConfigLine_vanilla_withFingerprint
- test_getConfigLine_scramblesuit_withFingeprint test_integration_getConfigLine_scramblesuit_withFingerprint
- test_splitterBridgeInsertion test_integration_hashringBridgeInsertion
- ============================================== ========================
- ..
- """
-
- def setUp(self):
- self.nickname = 'unnamed'
- self.ip = ipaddr.IPAddress('127.0.0.1')
- self.orport = '9001'
- self.fingerprint = 'A1CC8DFEF1FA11AF9C40AF1054DF9DAF45250556'
- self.id_digest = a2b_hex(self.fingerprint)
- self.or_addresses = {ipaddr.IPAddress('6.6.6.6'): PortList(6666),
- ipaddr.IPAddress('42.1.42.1'): PortList(443)}
-
- def test_integration_init_0(self):
- """Ensure that we can initialise the new :class:`bridgedb.bridges.Bridge`
- class in the same manner as the old :class:`bridgedb.Bridges.Bridge`
- class. This test ensures that initialisation with a fingerprint is
- successful.
- """
- b = bridges.Bridge(self.nickname, self.ip, self.orport,
- fingerprint=self.fingerprint)
- self.assertIsInstance(b, bridges.Bridge)
-
- def test_integration_init_1(self):
- """Ensure that we can initialise the new :class:`bridgedb.bridges.Bridge`
- class in the same manner as the old :class:`bridgedb.Bridges.Bridge`
- class. This test ensures that initialisation with a digest of a
- bridge's ID key is successful.
- """
- b = bridges.Bridge(self.nickname, self.ip, self.orport,
- id_digest=self.id_digest)
- self.assertIsInstance(b, bridges.Bridge)
-
- def test_integration_init_2(self):
- """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
- ``id_digest`` should raise a TypeError.
- """
- self.failUnlessRaises(TypeError, bridges.Bridge,
- self.nickname, self.ip, self.orport,
- id_digest=self.id_digest[:-1])
-
- def test_integration_init_3(self):
- """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
- ``fingerprint`` should raise a TypeError.
- """
- self.failUnlessRaises(TypeError, bridges.Bridge,
- self.nickname, self.ip, self.orport,
- fingerprint=self.fingerprint[:-1])
-
- def test_integration_init_4(self):
- """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
- ``fingerprint`` should raise a TypeError.
- """
- invalid_fingerprint = self.fingerprint[:-1] + 'q'
- self.failUnlessRaises(TypeError, bridges.Bridge, self.nickname,
- self.ip, self.orport,
- fingerprint=invalid_fingerprint)
-
- def test_integration_init_5(self):
- """Initialisation of a :class:`bridgedb.bridges.Bridge` without either
- a ``fingerprint`` or an ``id_digest`` should raise a TypeError.
- """
- self.failUnlessRaises(TypeError, bridges.Bridge,
- self.nickname, self.ip, self.orport)
-
- def test_integration_getID(self):
- """Calling ``bridges.Bridge.getID()`` should return the binary encoded
- ``fingerprint``.
- """
- bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
- self.fingerprint)
- self.assertEqual(self.id_digest, bridge.getID())
-
- def test_integration_setDescriptorDigest(self):
- """Test setting the server-descriptor digest value."""
- bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
- self.fingerprint)
- testtext = 'thisisatest'
- bridge.setDescriptorDigest(testtext)
- self.assertEqual(bridge.desc_digest, testtext)
-
- def test_integration_setExtraInfoDigest(self):
- """Test setting the extra-info digest value."""
- bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
- self.fingerprint)
- testtext = 'thisisatest'
- bridge.setExtraInfoDigest(testtext)
- self.assertEqual(bridge.ei_digest, testtext)
-
- def test_integration_setVerified(self):
- """Test setting the `verified` attribute on a Bridge."""
- raise unittest.SkipTest(
- ("The setVerified() and isVerified() methods were not refactored "
- "into the new bridgedb.bridges.Bridge class, as it's not clear "
- "yet if they are necessary. Skip these tests for now."))
-
- bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
- self.fingerprint)
- bridge.setVerified()
- self.assertTrue(bridge.isVerified())
- self.assertTrue(bridge.verified)
- self.assertEqual(self.id_digest, bridge.getID())
-
- def test_integration_setRunningStable(self):
- """Test setting the `running` and `stable` attributes on a Bridge."""
- bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
- self.fingerprint)
- self.assertFalse(bridge.running)
- self.assertFalse(bridge.stable)
- bridge.setStatus(True, True)
- self.assertTrue(bridge.running)
- self.assertTrue(bridge.stable)
-
- def test_integration_getConfigLine_vanilla_withoutFingerprint(self):
- """Should return a config line without a fingerprint."""
- #self.skip = True
- bridge = bridges.Bridge('nofpr', '23.23.23.23', 2323, self.fingerprint,
- or_addresses=self.or_addresses)
- bridgeLine = bridge.getConfigLine()
- ip = bridgeLine.split(':')[0]
- self.assertTrue(ipaddr.IPAddress(ip))
-
- def test_integration_getConfigLine_vanilla_withFingerprint(self):
- """Should return a config line with a fingerprint."""
- bridge = bridges.Bridge('fpr', '23.23.23.23', 2323,
- id_digest=self.id_digest,
- or_addresses=self.or_addresses)
- bridgeLine = bridge.getConfigLine(includeFingerprint=True)
- self.assertIsNotNone(bridgeLine)
- self.assertSubstring(self.fingerprint, bridgeLine)
- ip = bridgeLine.split(':')[0]
- self.assertTrue(ipaddr.IPAddress(ip))
-
- def test_integration_getConfigLine_scramblesuit_withFingerprint(self):
- """Should return a scramblesuit config line with a fingerprint."""
- bridge = bridges.Bridge('philipkdick', '23.23.23.23', 2323,
- id_digest=self.id_digest,
- or_addresses=self.or_addresses)
- ptArgs = {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
- pt = bridges.PluggableTransport(bridge.fingerprint, 'scramblesuit',
- ipaddr.IPAddress('42.42.42.42'), 4242,
- ptArgs)
- bridge.transports.append(pt)
- bridgeLine = bridge.getConfigLine(includeFingerprint=True,
- transport='scramblesuit')
- ptArgsList = ' '.join(["{0}={1}".format(k,v) for k,v in ptArgs.items()])
- self.assertEqual("scramblesuit 42.42.42.42:4242 %s %s"
- % (self.fingerprint, ptArgsList),
- bridgeLine)
-
- def test_integration_hashringBridgeInsertion(self):
- key = "Testing-Bridges-To-Rings"
- hashring = FilteredBridgeSplitter(key)
-
- bridge1 = bridges.Bridge('unamed1', '1.2.3.5', 9100,
- 'a1cc8dfef1fa11af9c40af1054df9daf45250550')
- bridge1.setStatus(running = True)
- bridge2 = bridges.Bridge('unamed2', '1.2.3.4', 8080,
- 'a1cc8dfef1fa11af9c40af1054df9daf45250551')
- bridge2.setStatus(running = True)
- bridge3 = bridges.Bridge('unamed3', '5.2.3.4', 8080,
- 'b1cc8dfef1fa11af9c40af1054df9daf45250552')
- bridge3.setStatus(running = True)
- bridge4 = bridges.Bridge('unamed3', '5.2.3.4', 8080,
- 'b1cc8dfef1fa11af9c40af1054df9daf45250552')
- bridge4.setStatus(running = True)
-
- self.failUnlessEqual(len(hashring), 0)
- hashring.insert(bridge1)
- hashring.insert(bridge2)
- hashring.insert(bridge3)
- # Check that all were inserted
- self.failUnlessEqual(len(hashring), 3)
- hashring.insert(bridge1)
- # Check that the same bridge is not inserted twice
- self.failUnlessEqual(len(hashring), 3)
- hashring.insert(bridge4)
- # Check that identical bridges are not inserted twice
- self.failUnlessEqual(len(hashring), 3)
-
-
-class FlagsTests(unittest.TestCase):
- """Tests for :class:`bridgedb.bridges.Flags`."""
-
- def setUp(self):
- self.flags = bridges.Flags()
- self._all_flag_names = ["fast", "guard", "running", "stable", "valid"]
-
- def test_init(self):
- """Upon initialisation, all flags should be ``False``."""
- for flag in self._all_flag_names:
- f = getattr(self.flags, flag, None)
- self.assertFalse(f, "%s should be False" % flag)
-
- def test_settingStable(self):
- """Setting the Stable flag to ``True`` should result in Flags.stable
- being ``True``.
- """
- self.flags.stable = True
- self.assertTrue(self.flags.stable, "The Stable flag should be True")
-
- def test_settingRunning(self):
- """Setting the Running flag to ``True`` should result in Flags.running
- being ``True``.
- """
- self.flags.running = True
- self.assertTrue(self.flags.running, "The Running flag should be True")
-
- def test_changingFlags(self):
- """Setting a flag and then unsetting it should result in it being
- ``True`` and then ``False``.
- """
- self.flags.valid = True
- self.assertTrue(self.flags.valid, "The Valid flag should be True")
- self.flags.valid = False
- self.assertFalse(self.flags.valid, "The Valid flag should be False")
-
- def test_update_Fast_Stable(self):
- """Test changing flags with the update() method."""
- self.flags.update(["Fast", "Stable"])
- self.assertTrue(self.flags.fast)
- self.assertTrue(self.flags.stable)
-
- def test_update_Fast(self):
- """Test changing flags with the update() method."""
- self.flags.update(["Fast"])
- self.assertTrue(self.flags.fast)
- self.assertFalse(self.flags.stable)
-
- def test_update_Stable(self):
- """Test changing flags with the update() method."""
- self.flags.update(["Stable"])
- self.assertFalse(self.flags.fast)
- self.assertTrue(self.flags.stable)
-
-
-class BridgeAddressBaseTests(unittest.TestCase):
- """Tests for :class:`bridgedb.bridges.BridgeAddressBase`."""
-
- def setUp(self):
- self.fingerprint = '2C3225C4805331025E211F4B6E5BF45C333FDD2C'
- self.bab = bridges.BridgeAddressBase()
-
- def test_BridgeAddressBase_init(self):
- """The BridgeAddressBase's _address and _fingerprint should be None."""
- self.assertIsNone(self.bab._address)
- self.assertIsNone(self.bab._fingerprint)
-
- def test_BridgeAddressBase_fingerprint_del(self):
- """The del method for the fingerprint property should reset the
- fingerprint to None.
- """
- self.bab.fingerprint = self.fingerprint
- self.assertEqual(self.bab.fingerprint, self.fingerprint)
-
- del(self.bab.fingerprint)
- self.assertIsNone(self.bab.fingerprint)
- self.assertIsNone(self.bab._fingerprint)
-
- def test_BridgeAddressBase_address_del(self):
- """The del method for the address property should reset the
- address to None.
- """
- self.bab.address = '11.12.13.14'
- self.assertEqual(self.bab.address, ipaddr.IPv4Address('11.12.13.14'))
-
- del(self.bab.address)
- self.assertIsNone(self.bab.address)
- self.assertIsNone(self.bab._address)
-
- def test_BridgeAddressBase_country(self):
- """The getter method for the country property should get the
- address's geoIP country code.
- """
- self.bab.address = '11.12.13.14'
- self.assertEqual(self.bab.address, ipaddr.IPv4Address('11.12.13.14'))
-
- cc = self.bab.country
- self.assertIsNotNone(cc)
- self.assertIsInstance(cc, basestring)
- self.assertEqual(len(cc), 2)
-
-
-class PluggableTransportTests(unittest.TestCase):
- """Tests for :class:`bridgedb.bridges.PluggableTransport."""
-
- def setUp(self):
- self.fingerprint = "ABCDEF0123456789ABCDEF0123456789ABCDEF01"
-
- def test_PluggableTransport_init_with_parameters(self):
- """Initialising a PluggableTransport with args should work."""
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar'})
- self.assertIsInstance(pt, bridges.PluggableTransport)
-
- def test_PluggableTransport_init(self):
- """Initialising a PluggableTransport without args should work."""
- pt = bridges.PluggableTransport()
- self.assertIsInstance(pt, bridges.PluggableTransport)
-
- def test_PluggableTransport_port_del(self):
- """The del method for the port property should reset the port to None.
- """
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar'})
- self.assertEqual(pt.port, 443)
-
- del(pt.port)
- self.assertIsNone(pt.port)
- self.assertIsNone(pt._port)
-
- def test_PluggableTransport_parseArgumentsIntoDict_valid_list(self):
- """Parsing a valid list of PT args should return a dictionary."""
- pt = bridges.PluggableTransport()
- args = pt._parseArgumentsIntoDict(["sharedsecret=foobar",
- "publickey=1234"])
- self.assertIsInstance(args, dict)
- self.assertItemsEqual(args, {"sharedsecret": "foobar",
- "publickey": "1234"})
-
- def test_PluggableTransport_parseArgumentsIntoDict_valid_list_multi(self):
- """Parsing a valid list with multiple PT args in a single list element
- should return a dictionary.
- """
- pt = bridges.PluggableTransport()
- args = pt._parseArgumentsIntoDict(["sharedsecret=foobar,password=baz",
- "publickey=1234"])
- self.assertIsInstance(args, dict)
- self.assertItemsEqual(args, {"sharedsecret": "foobar",
- "password": "baz",
- "publickey": "1234"})
-
- def test_PluggableTransport_parseArgumentsIntoDict_invalid_missing_equals(self):
- """Parsing a string of PT args where one PT arg (K=V) is missing an
- ``=`` character should raise a ValueError.
- """
- pt = bridges.PluggableTransport()
- args = pt._parseArgumentsIntoDict(
- ["sharedsecret=foobar,password,publickey=1234"])
- self.assertItemsEqual(args, {"sharedsecret": "foobar",
- "publickey": "1234"})
-
- def test_PluggableTransport_checkArguments_scramblesuit_missing_password(self):
- """Calling _checkArguments on a scramblesuit PT without a password should
- raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'scramblesuit', ('34.230.223.87', 37341, []))
-
- def test_PluggableTransport_checkArguments_obfs4_missing_iatmode(self):
- """Calling _checkArguments on an obfs4 PT without an iat-mode argument
- should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
- 'cert=UXj/cWm0qolGrROYpkl0UyD/7PEhzkoZkZXrOpjRKwImvkpQZwmF0nSzBXfyfbT9afBZEw']))
-
- def test_PluggableTransport_checkArguments_obfs4_missing_cert(self):
- """Calling _checkArguments on an obfs4 PT without a cert argument
- should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', 37341, ['iat-mode=1']))
-
- def test_PluggableTransport_checkArguments_obfs4_missing_publickey(self):
- """Calling _checkArguments on an obfs4 PT without a public-key argument
- should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
- ('iat-mode=1,'
- 'node-id=2a79f14120945873482b7823caabe2fcde848722')]))
-
- def test_PluggableTransport_checkArguments_obfs4_missing_nodeid(self):
- """Calling _checkArguments on an obfs4 PT without a public-key argument
- should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
- ('iat-mode=1,'
- 'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]))
-
- def test_PluggableTransport_runChecks_invalid_fingerprint(self):
- """Calling _runChecks() on a PluggableTransport with an invalid
- fingerprint should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- "INVALIDFINGERPRINT", 'obfs4', ('34.230.223.87', 37341, [
- ('iat-mode=0,'
- 'node-id=2a79f14120945873482b7823caabe2fcde848722,'
- 'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]))
-
- def test_PluggableTransport_runChecks_invalid_ip(self):
- """Calling _runChecks() on a PluggableTransport with an invalid
- IP address should raise a InvalidPluggableTransportIP exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.InvalidPluggableTransportIP,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223', 37341, [
- ('iat-mode=0,'
- 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
-
- def test_PluggableTransport_runChecks_invalid_port_type(self):
- """Calling _runChecks() on a PluggableTransport with an invalid port
- should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', "anyport", [
- ('iat-mode=0,'
- 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
-
- def test_PluggableTransport_runChecks_invalid_port_range(self):
- """Calling _runChecks() on a PluggableTransport with an invalid port
- (too high) should raise a MalformedPluggableTransport exception.
- """
- pt = bridges.PluggableTransport()
- self.assertRaises(
- bridges.MalformedPluggableTransport,
- pt.updateFromStemTransport,
- self.fingerprint, 'obfs4', ('34.230.223.87', 65536, [
- ('iat-mode=0,'
- 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
-
- def test_PluggableTransport_runChecks_invalid_pt_args(self):
- """Calling _runChecks() on a PluggableTransport with an invalid PT
- args should raise a MalformedPluggableTransport exception.
- """
- try:
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- 'sharedsecret=foobar')
- except Exception as error:
- self.failUnlessIsInstance(error,
- bridges.MalformedPluggableTransport)
-
- def test_PluggableTransport_getTransportLine_bridge_prefix(self):
- """If the 'Bridge ' prefix was requested, then it should be at the
- beginning of the bridge line.
- """
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar',
- 'password': 'unicorns'})
- bridgeLine = pt.getTransportLine(bridgePrefix=True)
- self.assertTrue(bridgeLine.startswith("Bridge "))
-
- def test_PluggableTransport_getTransportLine_without_Fingerprint(self):
- """If no fingerprint was requested, then there shouldn't be a
- fingerprint in the bridge line.
- """
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar',
- 'password': 'unicorns'})
- bridgeLine = pt.getTransportLine(includeFingerprint=False)
- self.assertNotSubstring(self.fingerprint, bridgeLine)
-
- def test_PluggableTransport_getTransportLine_content_order(self):
- """Check the order and content of the bridge line string."""
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar',
- 'password': 'unicorns'})
- bridgeLine = pt.getTransportLine()
-
- # We have to check for substrings because we don't know which order
- # the PT arguments will end up in the bridge line. We also have to
- # check for the lowercased transport name. Fortunately, the following
- # three are the only ones which are important to have in order:
- self.assertTrue(bridgeLine.startswith("voltronpt"))
- self.assertSubstring("voltronpt 1.2.3.4:443 " + self.fingerprint,
- bridgeLine)
- # These ones can be in any order, but they should be at the end of the
- # bridge line:
- self.assertSubstring("password=unicorns", bridgeLine)
- self.assertSubstring("sharedsecret=foobar", bridgeLine)
-
- def test_PluggableTransport_getTransportLine_ptargs_space_delimited(self):
- """The PT arguments in a bridge line should be space-separated."""
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "1.2.3.4", 443,
- {'sharedsecret': 'foobar',
- 'password': 'unicorns'})
- bridgeLine = pt.getTransportLine()
- self.assertTrue(
- ("password=unicorns sharedsecret=foobar" in bridgeLine) or
- ("sharedsecret=foobar password=unicorns" in bridgeLine))
-
- def test_PluggableTransport_getTransportLine_IPv6(self):
- """The address portion of a bridge line with an IPv6 address should
- have square brackets around it.
- """
- pt = bridges.PluggableTransport(self.fingerprint,
- "voltronPT", "2006:42::1234", 443,
- {'sharedsecret': 'foobar',
- 'password': 'unicorns'})
- bridgeLine = pt.getTransportLine()
- self.assertEqual(pt.address.version, 6)
- self.assertIn("[2006:42::1234]:443", bridgeLine)
-
-
-class BridgeBackwardsCompatibilityTests(unittest.TestCase):
- """Tests for :class:`bridgedb.bridges.BridgeBackwardsCompatibility`."""
-
- def setUp(self):
- self.nickname = "RouterNickname"
- self.address = "23.23.23.23"
- self.orPort = 9001
- self.fingerprint = "0123456789ABCDEF0123456789ABCDEF01234567"
- self.orAddresses = {"2006:42::123F": PortList(443, 9001, 1337),
- "2006:42::123E": PortList(9001, 993)}
-
- def test_BridgeBackwardsCompatibility_init_with_PortList(self):
- """Test initialisation with the usual number of valid arguments and
- PortLists for the orAddresses.
- """
- bridge = bridges.BridgeBackwardsCompatibility(
- self.nickname,
- self.address,
- self.orPort,
- self.fingerprint,
- self.orAddresses)
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
-
- def test_BridgeBackwardsCompatibility_init_without_PortList(self):
- """Test initialisation with the usual number of valid arguments and
- integers for the orAddresses' ports.
- """
- bridge = bridges.BridgeBackwardsCompatibility(
- self.nickname,
- self.address,
- self.orPort,
- self.fingerprint,
- {"2006:42::123F": 443,
- "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
-
- def test_BridgeBackwardsCompatibility_init_without_address(self):
- """Test initialisation without an IP address."""
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- orport=self.orPort,
- fingerprint=self.fingerprint,
- or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
-
- def test_BridgeBackwardsCompatibility_init_invalid_orAddresses_address(self):
- """Test initialisation with an invalid ORAddress."""
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- ip=self.address,
- orport=self.orPort,
- fingerprint=self.fingerprint,
- or_addresses={"10.1.2.3": 443, "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
- self.assertEqual(len(bridge.orAddresses), 1)
-
- def test_BridgeBackwardsCompatibility_init_invalid_orAddresses_port(self):
- """Test initialisation with an invalid ORPort."""
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- ip=self.address,
- orport=self.orPort,
- fingerprint=self.fingerprint,
- or_addresses={"2006:42::123F": 443, "2006:42::123E": "anyport"})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
- self.assertEqual(len(bridge.orAddresses), 1)
-
- def test_BridgeBackwardsCompatibility_setStatus_stable(self):
- """Using setStatus() to set the Stable flag should set Bridge.stable
- and Bridge.flags.stable to True.
- """
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- ip=self.address,
- orport=self.orPort,
- fingerprint=self.fingerprint,
- or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
- self.assertFalse(bridge.stable)
- self.assertFalse(bridge.flags.stable)
-
- bridge.setStatus(stable=True)
- self.assertTrue(bridge.stable)
- self.assertTrue(bridge.flags.stable)
-
- def test_BridgeBackwardsCompatibility_setStatus_running(self):
- """Using setStatus() to set the Running flag should set Bridge.running
- and Bridge.flags.running to True.
- """
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- ip=self.address,
- orport="anyport",
- fingerprint=self.fingerprint,
- or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
- self.assertFalse(bridge.running)
- self.assertFalse(bridge.flags.running)
-
- bridge.setStatus(running=True)
- self.assertTrue(bridge.running)
- self.assertTrue(bridge.flags.running)
-
- def test_BridgeBackwardsCompatibility_setStatus_running_stable(self):
- """Using setStatus() to set the Running and Stable flags should set
- Bridge.running, Bridge.flags.running, Bridge.stable, and
- Bridge.flags.stable.
- """
- bridge = bridges.BridgeBackwardsCompatibility(
- nickname=self.nickname,
- ip=self.address,
- orport="anyport",
- fingerprint=self.fingerprint,
- or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
- self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
- self.assertFalse(bridge.running)
- self.assertFalse(bridge.flags.running)
- self.assertFalse(bridge.stable)
- self.assertFalse(bridge.flags.stable)
-
- bridge.setStatus(running=True, stable=True)
- self.assertTrue(bridge.running)
- self.assertTrue(bridge.flags.running)
- self.assertTrue(bridge.stable)
- self.assertTrue(bridge.flags.stable)
-
-
-class BridgeTests(unittest.TestCase):
- """Tests for :class:`bridgedb.bridges.Bridge`."""
-
- def _parseAllDescriptorFiles(self):
- self.networkstatus = descriptors.parseNetworkStatusFile(
- self._networkstatusFile)[0]
- self.serverdescriptor = descriptors.parseServerDescriptorsFile(
- self._serverDescriptorFile)[0]
- self.extrainfo = descriptors.parseExtraInfoFiles(
- self._extrainfoFile).values()[0]
- self.extrainfoNew = descriptors.parseExtraInfoFiles(
- self._extrainfoNewFile).values()[0]
-
- def _writeNetworkstatus(self, networkstatus):
- with open(self._networkstatusFile, 'w') as fh:
- fh.write(networkstatus)
- fh.flush()
-
- def _writeServerdesc(self, serverdesc):
- with open(self._serverDescriptorFile, 'w') as fh:
- fh.write(serverdesc)
- fh.flush()
-
- def _writeExtrainfo(self, extrainfo):
- with open(self._extrainfoFile, 'w') as fh:
- fh.write(extrainfo)
- fh.flush()
-
- def _writeExtrainfoNew(self, extrainfo):
- with open(self._extrainfoNewFile, 'w') as fh:
- fh.write(extrainfo)
- fh.flush()
-
- def _writeDescriptorFiles(self, networkstatus, serverdesc, extrainfo, extrainfoNew):
- self._writeNetworkstatus(networkstatus)
- self._writeServerdesc(serverdesc)
- self._writeExtrainfo(extrainfo)
- self._writeExtrainfoNew(extrainfoNew)
-
- def setUp(self):
- def _cwd(filename):
- return os.path.sep.join([os.getcwd(), filename])
-
- self._networkstatusFile = _cwd('BridgeTests-networkstatus-bridges')
- self._serverDescriptorFile = _cwd('BridgeTests-bridge-descriptors')
- self._extrainfoFile = _cwd('BridgeTests-cached-extrainfo')
- self._extrainfoNewFile = _cwd('BridgeTests-cached-extrainfo.new')
-
- self._writeDescriptorFiles(BRIDGE_NETWORKSTATUS,
- BRIDGE_SERVER_DESCRIPTOR,
- BRIDGE_EXTRAINFO,
- BRIDGE_EXTRAINFO_NEW)
- self._parseAllDescriptorFiles()
-
- self.bridge = bridges.Bridge()
-
- def tearDown(self):
- """Reset safelogging to its default (disabled) state, due to
- test_Bridge_str_with_safelogging changing it.
- """
- bridges.safelog.safe_logging = False
-
- def test_Bridge_nickname_del(self):
- """The del method for the nickname property should reset the nickname
- to None.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.assertEqual(self.bridge.nickname, "FourfoldQuirked")
-
- del(self.bridge.nickname)
- self.assertIsNone(self.bridge.nickname)
- self.assertIsNone(self.bridge._nickname)
-
- def test_Bridge_nickname_invalid(self):
- """The del method for the nickname property should reset the nickname
- to None.
- """
- # Create a networkstatus descriptor with an invalid nickname:
- filename = self._networkstatusFile + "-invalid"
- fh = open(filename, 'w')
- invalid = BRIDGE_NETWORKSTATUS.replace(
- "FourfoldQuirked",
- "ThisRouterNicknameContainsWayMoreThanNineteenBytes")
- fh.seek(0)
- fh.write(invalid)
- fh.flush()
- fh.close()
-
- self.assertRaises(InvalidRouterNickname,
- descriptors.parseNetworkStatusFile,
- filename)
-
- def test_Bridge_orport_del(self):
- """The del method for the orPort property should reset the orPort
- to None.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.assertEqual(self.bridge.orPort, 36489)
-
- del(self.bridge.orPort)
- self.assertIsNone(self.bridge.orPort)
- self.assertIsNone(self.bridge._orPort)
-
- def test_Bridge_str_without_safelogging(self):
- """The str() method of a Bridge should return an identifier for the
- Bridge, which should be different if safelogging is enabled.
- """
- bridges.safelog.safe_logging = False
-
- bridge = bridges.Bridge()
- bridge.updateFromNetworkStatus(self.networkstatus)
-
- identifier = str(bridge)
- self.assertEqual(identifier,
- ''.join(['$', bridge.fingerprint,
- '~', bridge.nickname]))
-
- def test_Bridge_str_with_safelogging(self):
- """The str() method of a Bridge should return an identifier for the
- Bridge, which should be different if safelogging is enabled.
- """
- bridges.safelog.safe_logging = True
-
- bridge = bridges.Bridge()
- bridge.updateFromNetworkStatus(self.networkstatus)
-
- identifier = str(bridge)
- self.assertEqual(
- identifier,
- ''.join(['$$',
- hashlib.sha1(bridge.fingerprint).hexdigest().upper(),
- '~', bridge.nickname]))
-
- def test_Bridge_str_without_fingerprint(self):
- """The str() method of a Bridge should return an identifier for the
- Bridge, which should be different if the fingerprint is unknown.
- """
- bridge = bridges.Bridge()
- bridge.updateFromNetworkStatus(self.networkstatus)
- del(bridge.fingerprint)
-
- identifier = str(bridge)
- self.assertEqual(identifier,
- ''.join(['$', '0'*40,
- '~', bridge.nickname]))
-
- def test_Bridge_str_without_fingerprint_without_nickname(self):
- """Calling str(Bridge) on a Bridge whose fingerprint and nickname were
- not set should return a Bridge identifier string where the fingerprint
- is all 0's and the nickname is "Unnamed".
- """
- bridge = bridges.Bridge()
- identifier = str(bridge)
- self.assertEqual(identifier, ''.join(['$', '0'*40, '~', 'Unnamed']))
-
- def test_Bridge_constructBridgeLine_IPv6(self):
- """Bridge._constructBridgeLine() called with an IPv6 address should
- wrap the IPv6 address in '[]' in the returned bridge line.
- """
- bridge = bridges.Bridge()
- addrport = (u'6bf3:806b:78cd::4ced:cfad:dad4', 36488, 6)
-
- bridgeline = bridge._constructBridgeLine(addrport,
- includeFingerprint=False,
- bridgePrefix=True)
- self.assertEqual(bridgeline, 'Bridge [6bf3:806b:78cd::4ced:cfad:dad4]:36488')
-
- def test_Bridge_allVanillaAddresses_idempotency_self(self):
- """Bridge.allVanillaAddresses should be idempotent, i.e. calling
- allVanillaAddresses should not affect the results of subsequent calls.
- """
- self.bridge.address = '1.1.1.1'
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
-
- def test_Bridge_allVanillaAddresses_idempotency_others(self):
- """Bridge.allVanillaAddresses should be idempotent, i.e. calling
- allVanillaAddresses should not affect any of the Bridge's other
- attributes (such as Bridge.orAddresses).
- """
- self.bridge.address = '1.1.1.1'
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.orAddresses, [])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.orAddresses, [])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- def test_Bridge_allVanillaAddresses_reentrancy_all(self):
- """Bridge.allVanillaAddresses should be reentrant, i.e. updating the
- Bridge's address, orPort, or orAddresses should update the value
- returned by allVanillaAddresses.
- """
- self.bridge.address = '1.1.1.1'
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), None, 4)])
- self.assertEqual(self.bridge.address, ipaddr.IPv4Address('1.1.1.1'))
- self.assertEqual(self.bridge.orPort, None)
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertEqual(self.bridge.address, ipaddr.IPv4Address('1.1.1.1'))
- self.assertEqual(self.bridge.orPort, 443)
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- self.bridge.address = '2.2.2.2'
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('2.2.2.2'), 443, 4)])
- self.assertEqual(self.bridge.address, ipaddr.IPv4Address('2.2.2.2'))
- self.assertEqual(self.bridge.orPort, 443)
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- self.bridge.orAddresses.append(
- (ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6))
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('2.2.2.2'), 443, 4),
- (ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6)])
- self.assertEqual(self.bridge.address, ipaddr.IPv4Address('2.2.2.2'))
- self.assertEqual(self.bridge.orPort, 443)
- self.assertItemsEqual(self.bridge.orAddresses,
- [(ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6)])
-
- def test_Bridge_allVanillaAddresses_reentrancy_orPort(self):
- """Calling Bridge.allVanillaAddresses before Bridge.orPort is set
- should return ``None`` for the port value, and after Bridge.orPort is
- set, it should return the orPort.
- """
- self.bridge.address = '1.1.1.1'
- self.assertItemsEqual(self.bridge.orAddresses, [])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), None, 4)])
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.orAddresses, [])
-
- def test_Bridge_allVanillaAddresses_reentrancy_address(self):
- """Calling Bridge.allVanillaAddresses before Bridge.address is set
- should return ``None`` for the address value, and after Bridge.address
- is set, it should return the address.
- """
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(None, 443, 4)])
- self.bridge.address = '1.1.1.1'
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
-
- def test_Bridge_allVanillaAddresses_reentrancy_orAddresses(self):
- """Calling Bridge.allVanillaAddresses before Bridge.orAddresses is set
- should return only the Bridge's address and orPort.
- """
- self.bridge.address = '1.1.1.1'
- self.bridge.orPort = 443
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
- self.assertItemsEqual(self.bridge.orAddresses, [])
- self.bridge.orAddresses.append(
- (ipaddr.IPv4Address('2.2.2.2'), 4443, 4))
- self.assertItemsEqual(self.bridge.orAddresses,
- [(ipaddr.IPv4Address('2.2.2.2'), 4443, 4)])
- self.assertItemsEqual(self.bridge.allVanillaAddresses,
- [(ipaddr.IPv4Address('2.2.2.2'), 4443, 4),
- (ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
-
- def test_Bridge_updateORAddresses_valid_and_invalid(self):
- """Bridge._updateORAddresses() called with a mixture of valid and
- invalid ORAddress tuples should only retain the valid ones.
- """
- orAddresses = [
- (u'1.1.1.1', 1111, False), # valid
- (u'127.0.0.1', 2222, False), # invalid IPv4 loopback
- (u'FE80::1234', 3333, True)] # invalid IPv6 link local
- bridge = bridges.Bridge()
- bridge._updateORAddresses(orAddresses)
-
- self.assertEqual(len(bridge.orAddresses), 1)
- addr, port, version = bridge.orAddresses[0]
- self.assertEqual(addr, ipaddr.IPAddress('1.1.1.1'))
- self.assertEqual(port, 1111)
- self.assertEqual(version, 4)
-
- def test_Bridge_updateFromNetworkStatus_IPv4_ORAddress(self):
- """Calling updateFromNetworkStatus() with a descriptor which has an
- IPv4 address as an additional ORAddress should result in a
- FutureWarning before continuing parsing.
- """
- # Add an additional IPv4 ORAddress:
- ns = BRIDGE_NETWORKSTATUS.replace(
- 'a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488',
- 'a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488\na 123.34.56.78:36488')
- self._writeNetworkstatus(ns)
- self._parseAllDescriptorFiles()
-
- self.assertWarns(
- FutureWarning,
- "Got IPv4 address in 'a'/'or-address' line! Descriptor format may have changed!",
- bridges.__file__, # filename
- self.bridge.updateFromNetworkStatus,
- self.networkstatus)
-
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
- self.assertIn((ipaddr.IPAddress('123.34.56.78'), 36488, 4),
- self.bridge.allVanillaAddresses)
-
- def test_Bridge_updateFromNetworkStatus_ignoreNetworkstatus(self):
- """Calling updateFromNetworkStatus([â¦], ignoreNetworkstatus=True)
- should update the Bridge's flags, its fingerprint, and its
- descriptorDigest, and nothing else.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus,
- ignoreNetworkstatus=True)
-
- self.assertTrue(self.bridge.flags.running)
- self.assertTrue(self.bridge.flags.stable)
- self.assertIsNotNone(self.bridge.descriptorDigest)
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
-
- self.assertIsNone(self.bridge.nickname)
- self.assertIsNone(self.bridge.address)
- self.assertIsNone(self.bridge.orPort)
- self.assertIsNone(self.bridge.orPort)
-
- def test_Bridge_updateFromServerDescriptor(self):
- """ """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
-
- def test_Bridge_updateFromServerDescriptor_no_networkstatus(self):
- """Parsing a server descriptor for a bridge which wasn't included in
- the networkstatus document from the BridgeAuthority should raise a
- ServerDescriptorWithoutNetworkstatus exception.
- """
- self.assertRaises(bridges.ServerDescriptorWithoutNetworkstatus,
- self.bridge.updateFromServerDescriptor,
- self.serverdescriptor)
-
- def test_Bridge_updateFromServerDescriptor_ignoreNetworkstatus_no_networkstatus(self):
- """Parsing a server descriptor for a bridge which wasn't included in
- the networkstatus document from the BridgeAuthority, when
- ignoreNetworkstatus=True, should not raise any warnings.
- """
- self.bridge.updateFromServerDescriptor(self.serverdescriptor,
- ignoreNetworkstatus=True)
- self.assertIsNone(self.bridge.descriptors['networkstatus'])
- self.assertIsNotNone(self.bridge.descriptors['server'])
-
- def test_Bridge_verifyExtraInfoSignature_good_signature(self):
- """Calling _verifyExtraInfoSignature() with a descriptor which has a
- good signature should return None.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.assertIsNone(self.bridge._verifyExtraInfoSignature(self.extrainfo))
-
- def test_Bridge_updateFromExtraInfoDescriptor(self):
- """Bridge.updateFromExtraInfoDescriptor() should add the expected
- number of pluggable transports.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
- self.assertEqual(self.bridge.bandwidthObserved, None)
- self.assertEqual(len(self.bridge.transports), 0)
-
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
- self.assertEqual(self.bridge.bandwidthObserved, 1623207134)
- self.assertEqual(len(self.bridge.transports), 0)
-
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
- self.assertEqual(self.bridge.fingerprint,
- '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
- self.assertEqual(self.bridge.bandwidthObserved, 1623207134)
- self.assertEqual(len(self.bridge.transports), 4)
-
- def test_Bridge_updateFromExtraInfoDescriptor_bad_signature_changed(self):
- """Calling updateFromExtraInfoDescriptor() with a descriptor which
- has a bad signature should not continue to process the descriptor.
- """
- # Make the signature uppercased
- BEGIN_SIG = '-----BEGIN SIGNATURE-----'
- doc, sig = BRIDGE_EXTRAINFO.split(BEGIN_SIG)
- ei = BEGIN_SIG.join([doc, sig.upper()])
- self._writeExtrainfo(ei)
- self._parseAllDescriptorFiles()
-
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertEqual(len(self.bridge.transports), 0)
- self.assertIsNone(self.bridge.descriptors['extrainfo'])
-
- def test_Bridge_updateFromExtraInfoDescriptor_pt_changed_port(self):
- """Calling updateFromExtraInfoDescriptor() with a descriptor which
- includes a different port for a known bridge with a known pluggable
- transport should update that transport.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertEqual(len(self.bridge.transports), 4)
-
- for pt in self.bridge.transports:
- if pt.methodname == 'obfs4':
- self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
- self.assertEqual(pt.port, 36493)
-
- # Change the port of obfs4 transport in the extrainfo descriptor:
- transportline = self.extrainfo.transport['obfs4']
- self.extrainfo.transport['obfs4'] = (transportline[0],
- 31337,
- transportline[2])
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- for pt in self.bridge.transports:
- if pt.methodname == 'obfs4':
- self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
- self.assertEqual(pt.port, 31337)
-
- def test_Bridge_updateFromExtraInfoDescriptor_pt_changed_args(self):
- """Calling updateFromExtraInfoDescriptor() with a descriptor which
- includes different PT args for a known bridge with a known pluggable
- transport should update that transport.
-
- scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertEqual(len(self.bridge.transports), 4)
-
- for pt in self.bridge.transports:
- if pt.methodname == 'scramblesuit':
- self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
- self.assertEqual(pt.port, 36492)
-
- # Change the args of scramblesuit transport in the extrainfo descriptor:
- transportline = self.extrainfo.transport['scramblesuit']
- self.extrainfo.transport['scramblesuit'] = (transportline[0],
- transportline[1],
- ['password=PASSWORD'])
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- for pt in self.bridge.transports:
- if pt.methodname == 'scramblesuit':
- self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
- self.assertEqual(pt.port, 36492)
- self.assertEqual(pt.arguments['password'], 'PASSWORD')
-
- def test_Bridge_updateFromExtraInfoDescriptor_pt_died(self):
- """Calling updateFromExtraInfoDescriptor() with a descriptor which
- doesn't include a previously-known transport should remove that
- transport.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertEqual(len(self.bridge.transports), 4)
-
- # Remove the obfs3 transport from the extrainfo descriptor:
- self.extrainfo.transport.pop('obfs3')
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertEqual(len(self.bridge.transports), 3)
-
- for pt in self.bridge.transports:
- self.failIfEqual(pt.methodname, 'obfs3')
-
- def test_Bridge_descriptorDigest(self):
- """Parsing a networkstatus descriptor should result in
- Bridge.descriptorDigest being set.
- """
- realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.assertEqual(self.bridge.descriptorDigest, realdigest)
-
- def test_Bridge_checkServerDescriptor(self):
- """Parsing a server descriptor when the bridge's networkstatus document
- didn't have a digest of the server descriptor should raise a
- MissingServerDescriptorDigest.
- """
- # Create a networkstatus descriptor without a server descriptor digest:
- filename = self._networkstatusFile + "-missing-digest"
- fh = open(filename, 'w')
- invalid = BRIDGE_NETWORKSTATUS.replace("c4EVu2rO/iD/DJYBX/Ll38DGQWI", "foo")
- fh.seek(0)
- fh.write(invalid)
- fh.flush()
- fh.close()
-
- realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
-
- #networkstatus = descriptors.parseNetworkStatusFile(filename)
- #self.bridge.updateFromNetworkStatus(networkstatus[0])
- #self.assertRaises(bridges.MissingServerDescriptorDigest,
- # self.bridge.updateFromNetworkStatus,
- # networkstatus[0])
-
- def test_Bridge_checkServerDescriptor_digest_mismatch_ns(self):
- """Parsing a server descriptor whose digest doesn't match the one given
- in the bridge's networkstatus document should raise a
- ServerDescriptorDigestMismatch.
- """
- # Create a networkstatus descriptor without a server descriptor digest:
- filename = self._networkstatusFile + "-mismatched-digest"
- fh = open(filename, 'w')
- invalid = BRIDGE_NETWORKSTATUS.replace("c4EVu2rO/iD/DJYBX/Ll38DGQWI",
- "c4EVu2r1/iD/DJYBX/Ll38DGQWI")
- fh.seek(0)
- fh.write(invalid)
- fh.flush()
- fh.close()
-
- realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
- networkstatus = descriptors.parseNetworkStatusFile(filename)
- self.bridge.updateFromNetworkStatus(networkstatus[0])
- #self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- self.assertRaises(bridges.ServerDescriptorDigestMismatch,
- self.bridge.updateFromServerDescriptor,
- self.serverdescriptor)
-
- def test_Bridge_checkServerDescriptor_digest_mismatch_sd(self):
- """Parsing a server descriptor when the corresponding networkstatus
- descriptor didn't include a server bridge.descriptorDigest that matches
- should raise a ServerDescriptorDigestMismatch exception.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
-
- self.bridge.descriptorDigest = 'deadbeef'
- self.assertRaises(bridges.ServerDescriptorDigestMismatch,
- self.bridge._checkServerDescriptor,
- self.serverdescriptor)
-
- def test_Bridge_checkServerDescriptor_digest_missing(self):
- """Parsing a server descriptor when the corresponding networkstatus
- descriptor didn't include a server bridge.descriptorDigest should raise
- a MissingServerDescriptorDigest exception.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
-
- self.bridge.descriptorDigest = None
- self.assertRaises(bridges.MissingServerDescriptorDigest,
- self.bridge._checkServerDescriptor,
- self.serverdescriptor)
-
- def test_Bridge_assertOK(self):
- """If all orAddresses are okay, then assertOK() should return None."""
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- self.assertIsNone(self.bridge.assertOK())
-
- def test_Bridge_assertOK_all_bad_values(self):
- """If an orAddress has an IP address of 999.999.999.999 and a port of
- -1 and claims to be IPv5, then everything about it is bad and it should
- fail all the checks in assertOK(), then a MalformedBridgeInfo should be
- raised.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- # All values are bad (even though IPv5 is a thing):
- self.bridge.orAddresses.append(('999.999.999.999', -1, 5))
- self.assertRaises(bridges.MalformedBridgeInfo, self.bridge.assertOK)
-
- def test_Bridge_getBridgeLine_request_valid(self):
- """Calling getBridgeLine with a valid request should return a bridge
- line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNotNone(line)
- self.assertIn('179.178.155.140:36489', line)
- self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getBridgeLine_request_invalid(self):
- """Calling getBridgeLine with an invalid request should return None."""
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(False)
-
- self.assertIsNone(self.bridge.getBridgeLine(request))
-
- def test_Bridge_getBridgeLine_no_vanilla_addresses(self):
- """Calling getBridgeLine() on a Bridge without any vanilla addresses
- should return None.
- """
- request = BridgeRequestBase()
- request.isValid(True)
-
- self.assertIsNone(self.bridge.getBridgeLine(request))
-
- def test_Bridge_getBridgeLine_request_without_block_in_IR(self):
- """Calling getBridgeLine() with a valid request for bridges not blocked
- in Iran should return a bridge line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withoutBlockInCountry('IR')
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNotNone(line)
- self.assertIn('179.178.155.140:36489', line)
- self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getBridgeLine_blocked_and_request_without_block(self):
- """Calling getBridgeLine() with a valid request for bridges not blocked in
- Iran, when the bridge is completely blocked in Iran, shouldn't return
- a bridge line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.bridge.setBlockedIn('ir')
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withoutBlockInCountry('IR')
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNone(line)
-
- def test_Bridge_getBridgeLine_blocked_pt_and_request_without_block_pt(self):
- """Calling getBridgeLine() with a valid request for obfs3 bridges not
- blocked in Iran, when the obfs3 line is blocked in Iran, shouldn't
- return a bridge line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.bridge.setBlockedIn('ir', methodname="obfs3")
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withoutBlockInCountry('IR')
- request.withPluggableTransportType('obfs3')
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNone(line)
-
- def test_Bridge_getBridgeLine_blocked_obfs3_and_request_without_block_obfs4(self):
- """Calling getBridgeLine() with a valid request for obfs4 bridges not
- blocked in Iran, when the obfs3 line is blocked in Iran, should return
- a bridge line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.bridge.setBlockedIn('ir', methodname="obfs3")
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withoutBlockInCountry('IR')
- request.withPluggableTransportType('obfs4')
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNotNone(line)
- self.assertIn('obfs4', line)
- self.assertIn('179.178.155.140:36493', line)
- self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getBridgeLine_IPv6(self):
- """Calling getBridgeLine() with a valid request for IPv6 bridges
- should return a bridge line.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withIPv6()
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNotNone(line)
- self.assertTrue(
- line.startswith('[6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488'))
- self.assertNotIn('179.178.155.140:36493', line)
- self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getBridgeLine_IPv6_no_fingerprint(self):
- """Calling getBridgeLine(includeFingerprint=False) with a valid request
- for IPv6 bridges should return a bridge line without the fingerprint.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withIPv6()
- line = self.bridge.getBridgeLine(request, includeFingerprint=False)
-
- self.assertIsNotNone(line)
- self.assertTrue(
- line.startswith('[6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488'))
- self.assertNotIn('179.178.155.140:36493', line)
- self.assertNotIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getBridgeLine_obfs4(self):
- """ """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withPluggableTransportType('obfs4')
- line = self.bridge.getBridgeLine(request)
-
- self.assertIsNotNone(line)
- self.assertIn('179.178.155.140:36493', line)
- self.assertTrue(line.startswith('obfs4'))
- self.assertIn('iat-mode', line)
- self.assertIn('public-key', line)
- self.assertIn('node-id', line)
-
- def test_Bridge_getBridgeLine_obfs3_IPv6(self):
- """Calling getBridgeLine() with a request for IPv6 obfs3 bridges (when
- the Bridge doesn't have any) should raise a
- PluggableTransportUnavailable exception.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withIPv6()
- request.withPluggableTransportType('obfs3')
-
- self.assertRaises(bridges.PluggableTransportUnavailable,
- self.bridge.getBridgeLine,
- request)
-
- def test_Bridge_getBridgeLine_googlygooglybegone(self):
- """Calling getBridgeLine() with a request for an unknown PT should
- raise a PluggableTransportUnavailable exception.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- request.withPluggableTransportType('googlygooglybegone')
-
- self.assertRaises(bridges.PluggableTransportUnavailable,
- self.bridge.getBridgeLine,
- request)
-
- def test_Bridge_getBridgeLine_bridge_prefix(self):
- """Calling getBridgeLine() with bridgePrefix=True should prefix the
- returned bridge line with 'Bridge '.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- line = self.bridge.getBridgeLine(request, bridgePrefix=True)
-
- self.assertIsNotNone(line)
- self.assertIn('179.178.155.140:36489', line)
- self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
- self.assertTrue(line.startswith('Bridge'))
-
- def test_Bridge_getBridgeLine_no_include_fingerprint(self):
- """Calling getBridgeLine() with includeFingerprint=False should return
- a bridge line without a fingerprint.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- request = BridgeRequestBase()
- request.isValid(True)
- line = self.bridge.getBridgeLine(request, includeFingerprint=False)
-
- self.assertIsNotNone(line)
- self.assertIn('179.178.155.140:36489', line)
- self.assertNotIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
-
- def test_Bridge_getNetworkstatusLastPublished(self):
- """Calling getNetworkstatusLastPublished() should tell us the last
- published time of the Bridge's server-descriptor.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
-
- published = self.bridge.getNetworkstatusLastPublished()
- self.assertIsNotNone(published)
- self.assertIsInstance(published, datetime.datetime)
- self.assertEqual(str(published), '2014-12-22 21:51:27')
-
- def test_Bridge_getDescriptorLastPublished(self):
- """Calling getDescriptorLastPublished() should tell us the last
- published time of the Bridge's server-descriptor.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- published = self.bridge.getDescriptorLastPublished()
- self.assertIsNotNone(published)
- self.assertIsInstance(published, datetime.datetime)
- self.assertEqual(str(published), '2014-12-22 21:51:27')
-
- def test_Bridge_getExtrainfoLastPublished(self):
- """Calling getNetworkstatusLastPublished() should tell us the last
- published time of the Bridge's server-descriptor.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- published = self.bridge.getExtrainfoLastPublished()
- self.assertIsNotNone(published)
- self.assertIsInstance(published, datetime.datetime)
- self.assertEqual(str(published), '2014-12-22 21:51:27')
-
- def test_Bridge_isBlockedIn_IS(self):
- """Calling isBlockedIn('IS') should return False when the bridge isn't
- blocked in Iceland.
- """
- self.assertFalse(self.bridge.isBlockedIn('IS'))
-
- def test_Bridge_setBlockedIn_CN_obfs2(self):
- """Calling setBlockedIn('CN', 'obfs2') should mark all obfs2 transports
- of the bridge as being blocked in CN.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.bridge.setBlockedIn('CN', methodname='obfs2')
- self.assertTrue(self.bridge.isBlockedIn('CN'))
-
- def test_Bridge_setBlockedIn_IR_address(self):
- """Calling setBlockedIn('IR', address) should mark all matching
- addresses of the bridge as being blocked in IR.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.bridge.setBlockedIn('IR', address='179.178.155.140')
- self.assertTrue(self.bridge.isBlockedIn('ir'))
- self.assertFalse(self.bridge.isBlockedIn('cn'))
-
- def test_Bridge_setBlockedIn_GB_address_port(self):
- """Calling setBlockedIn('GB', address, port) should mark all matching
- addresses:port pairs of the bridge as being blocked in GB.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- # Should block the obfs4 bridge:
- self.bridge.setBlockedIn('GB', address='179.178.155.140', port=36493)
- self.assertTrue(self.bridge.isBlockedIn('GB'))
- self.assertTrue(self.bridge.isBlockedIn('gb'))
- self.assertTrue(self.bridge.transportIsBlockedIn('GB', 'obfs4'))
- self.assertTrue(self.bridge.addressIsBlockedIn('GB', '179.178.155.140', 36493))
- self.assertFalse(self.bridge.addressIsBlockedIn('gb', '179.178.155.140', 36488))
-
- def test_Bridge_updateFromExtraInfoDescriptor_changed_no_verify(self):
- """A changed extrainfo descriptor should log that a transport's
- IP and/or port changed.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- changedExtrainfo = BRIDGE_EXTRAINFO
- changedExtrainfo.replace('transport obfs3 179.178.155.140:36490',
- 'transport obfs3 179.178.155.14:3649')
- self._writeExtrainfo(changedExtrainfo)
-
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo, verify=False)
-
- def test_Bridge_updateFromExtraInfoDescriptor_changed_verify(self):
- """A changed extrainfo descriptor with verify=True should raise an
- InvalidExtraInfoSignature exception.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfoNew)
-
- # We should have hit the return just after the
- # `except InvalidExtraInfoSignature` line, and so the
- # bridge.descriptors['extrainfo'] shouldn't have been updated.
- # Therefore, the one we stored should be older, that is, we shouldn't
- # have kept the new one.
- self.assertLess(self.bridge.descriptors['extrainfo'].published,
- self.extrainfoNew.published)
- # And the one we stored should be the older one, with the same
- # published timestamp:
- self.assertEqual(self.bridge.descriptors['extrainfo'], self.extrainfo)
- self.assertEqual(self.bridge.descriptors['extrainfo'].published,
- self.extrainfo.published)
-
- def test_Bridge_updateFromExtraInfoDescriptor_obfs4_no_iatmode(self):
- """An extrainfo descriptor with an obfs4 transport missing the
- `iat-mode=[â¦]` argument should not add the obfs4 transport.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- obfs4 = self.extrainfo.transport['obfs4']
- ptargs = [str(obfs4[-1][-1].replace('iat-mode=0', '')),]
- obfs4 = (u'1.1.1.1', 1111, ptargs)
-
- self.extrainfo.transport['obfs4'] = obfs4
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertTrue(len(self.bridge.transports), 3)
- self.assertNotIn('obfs4',
- [pt.methodname for pt in self.bridge.transports])
-
- def test_Bridge_updateFromExtraInfoDescriptor_scramblesuit_no_password(self):
- """An extrainfo descriptor with `transport scramblesuit 1.1.1.1:1111`
- (i.e. missing the `password=[â¦]` argument) should not add the
- scramblesuit transport.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
-
- self.extrainfo.transport['scramblesuit'] = (u'1.1.1.1', 1111, [])
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertTrue(len(self.bridge.transports), 3)
- self.assertNotIn('scramblesuit',
- [pt.methodname for pt in self.bridge.transports])
-
- def test_Bridge_updateFromExtraInfoDescriptor_changed_scramblesuit_no_password(self):
- """An extrainfo descriptor whose scramblesuit transport was previously
- valid and is now missing the `password=[â¦]` argument should be removed
- from the Bridge.transports list.
- """
- self.bridge.updateFromNetworkStatus(self.networkstatus)
- self.bridge.updateFromServerDescriptor(self.serverdescriptor)
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertTrue(len(self.bridge.transports), 4)
- self.assertIn('scramblesuit',
- [pt.methodname for pt in self.bridge.transports])
-
- self.extrainfo.transport['scramblesuit'] = (u'1.1.1.1', 1111, [])
- self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
-
- self.assertTrue(len(self.bridge.transports), 3)
- self.assertNotIn('scramblesuit',
- [pt.methodname for pt in self.bridge.transports])
diff --git a/lib/bridgedb/test/test_captcha.py b/lib/bridgedb/test/test_captcha.py
deleted file mode 100644
index 1000477..0000000
--- a/lib/bridgedb/test/test_captcha.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.captcha` module."""
-
-
-import os
-import shutil
-import time
-
-from base64 import urlsafe_b64decode
-
-from twisted.trial import unittest
-
-from zope.interface import implementedBy
-from zope.interface import providedBy
-
-from bridgedb import captcha
-from bridgedb import crypto
-
-
-class CaptchaTests(unittest.TestCase):
- """Tests for :class:`bridgedb.captcha.Captcha`."""
-
- def test_implementation(self):
- """Captcha class should implement ICaptcha interface."""
- self.assertTrue(captcha.ICaptcha.implementedBy(captcha.Captcha))
-
- def test_provider(self):
- """ICaptcha should be provided by instances of Captcha."""
- c = captcha.Captcha()
- self.assertTrue(captcha.ICaptcha.providedBy(c))
-
- def test_get(self):
- """Captcha.get() should return None."""
- c = captcha.Captcha()
- self.assertIsNone(c.get())
-
-
-class ReCaptchaTests(unittest.TestCase):
- """Tests for :class:`bridgedb.captcha.ReCaptcha`."""
-
- def setUp(self):
- self.c = captcha.ReCaptcha('publik', 'sekrit')
-
- def test_init(self):
- """Check the ReCaptcha class stored the private and public keys."""
- self.assertEquals(self.c.secretKey, 'sekrit')
- self.assertEquals(self.c.publicKey, 'publik')
-
- def test_get(self):
- """Test get() method."""
-
- # Force urllib2 to do anything less idiotic than the defaults:
- envkey = 'HTTPS_PROXY'
- oldkey = None
- if os.environ.has_key(envkey):
- oldkey = os.environ[envkey]
- os.environ[envkey] = '127.0.0.1:9150'
- # This stupid thing searches the environment for ``<protocol>_PROXY``
- # variables, hence the above 'HTTPS_PROXY' env setting:
- proxy = captcha.urllib2.ProxyHandler()
- opener = captcha.urllib2.build_opener(proxy)
- captcha.urllib2.install_opener(opener)
-
- try:
- # There isn't really a reliable way to test this function! :(
- self.c.get()
- except Exception as error:
- reason = "ReCaptcha.get() test requires an active network "
- reason += "connection.\nThis test failed with: %s" % error
- raise unittest.SkipTest(reason)
- else:
- self.assertIsInstance(self.c.image, basestring)
- self.assertIsInstance(self.c.challenge, basestring)
- finally:
- # Replace the original environment variable if there was one:
- if oldkey:
- os.environ[envkey] = oldkey
- else:
- os.environ.pop(envkey)
-
- def test_get_noKeys(self):
- """ReCaptcha.get() without API keys should fail."""
- c = captcha.ReCaptcha()
- self.assertRaises(captcha.CaptchaKeyError, c.get)
-
-
-class GimpCaptchaTests(unittest.TestCase):
- """Tests for :class:`bridgedb.captcha.GimpCaptcha`."""
-
- def setUp(self):
- here = os.getcwd()
- self.topDir = here.rstrip('_trial_temp')
- self.cacheDir = os.path.join(self.topDir, 'captchas')
- self.badCacheDir = os.path.join(here, 'capt')
-
- # Get keys for testing or create them:
- self.sekrit, self.publik = crypto.getRSAKey('test_gimpCaptcha_RSAkey')
- self.hmacKey = crypto.getKey('test_gimpCaptcha_HMACkey')
-
- def test_init_noSecretKey(self):
- """Calling GimpCaptcha.__init__() without a secret key parameter should raise
- a CaptchaKeyError.
- """
- self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
- self.publik, None, self.hmacKey, self.cacheDir)
-
- def test_init_noPublicKey(self):
- """__init__() without publicKey should raise a CaptchaKeyError."""
- self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
- None, self.sekrit, self.hmacKey, self.cacheDir)
-
- def test_init_noHMACKey(self):
- """__init__() without hmacKey should raise a CaptchaKeyError."""
- self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
- self.publik, self.sekrit, None, self.cacheDir)
-
- def test_init_noCacheDir(self):
- """__init__() without cacheDir should raise a CaptchaKeyError."""
- self.assertRaises(captcha.GimpCaptchaError, captcha.GimpCaptcha,
- self.publik, self.sekrit, self.hmacKey, None)
-
- def test_init_badCacheDir(self):
- """GimpCaptcha with bad cacheDir should raise GimpCaptchaError."""
- self.assertRaises(captcha.GimpCaptchaError, captcha.GimpCaptcha,
- self.publik, self.sekrit, self.hmacKey,
- self.cacheDir.rstrip('chas'))
-
- def test_init(self):
- """Test that __init__ correctly initialised all the values."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- self.assertIsNone(c.answer)
- self.assertIsNone(c.image)
- self.assertIsNone(c.challenge)
-
- def test_createChallenge(self):
- """createChallenge() should return the encrypted CAPTCHA answer."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- challenge = c.createChallenge('w00t')
- self.assertIsInstance(challenge, basestring)
-
- def test_createChallenge_base64(self):
- """createChallenge() return value should be urlsafe base64-encoded."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- challenge = c.createChallenge('w00t')
- decoded = urlsafe_b64decode(challenge)
- self.assertTrue(decoded)
-
- def test_createChallenge_hmacValid(self):
- """The HMAC in createChallenge() return value should be valid."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- challenge = c.createChallenge('ShouldHaveAValidHMAC')
- decoded = urlsafe_b64decode(challenge)
- hmac = decoded[:20]
- orig = decoded[20:]
- correctHMAC = crypto.getHMAC(self.hmacKey, orig)
- self.assertEquals(hmac, correctHMAC)
-
- def test_createChallenge_decryptedAnswerMatches(self):
- """The HMAC in createChallenge() return value should be valid."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- challenge = c.createChallenge('ThisAnswerShouldDecryptToThis')
- decoded = urlsafe_b64decode(challenge)
- hmac = decoded[:20]
- orig = decoded[20:]
- correctHMAC = crypto.getHMAC(self.hmacKey, orig)
- self.assertEqual(hmac, correctHMAC)
-
- decrypted = self.sekrit.decrypt(orig)
- timestamp = int(decrypted[:12].lstrip('0'))
- # The timestamp should be within 30 seconds of right now.
- self.assertApproximates(timestamp, int(time.time()), 30)
- self.assertEqual('ThisAnswerShouldDecryptToThis', decrypted[12:])
-
- def test_get(self):
- """GimpCaptcha.get() should return image and challenge strings."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- self.assertIsInstance(image, basestring)
- self.assertIsInstance(challenge, basestring)
-
- def test_get_emptyCacheDir(self):
- """An empty cacheDir should raise GimpCaptchaError."""
- os.makedirs(self.badCacheDir)
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.badCacheDir)
- self.assertRaises(captcha.GimpCaptchaError, c.get)
- shutil.rmtree(self.badCacheDir)
-
- def test_get_unreadableCaptchaFile(self):
- """An unreadable CAPTCHA file should raise GimpCaptchaError."""
- os.makedirs(self.badCacheDir)
- badFile = os.path.join(self.badCacheDir, 'uNr34dA81e.jpg')
- with open(badFile, 'w') as fh:
- fh.write(' ')
- fh.flush()
- os.chmod(badFile, 0266)
-
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.badCacheDir)
- # This should hit the second `except:` clause in get():
- self.assertRaises(captcha.GimpCaptchaError, c.get)
- shutil.rmtree(self.badCacheDir)
-
- def test_check(self):
- """A correct answer and valid challenge should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- self.assertEquals(
- c.check(challenge, c.answer, c.secretKey, c.hmacKey),
- True)
-
- def test_check_blankAnswer(self):
- """A blank answer and valid challenge should return False."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- self.assertEquals(
- c.check(challenge, None, c.secretKey, c.hmacKey),
- False)
-
- def test_check_nonBase64(self):
- """Valid answer and challenge with invalid base64 returns False."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- challengeBadB64 = challenge.rstrip('==') + "\x42\x42\x42"
- self.assertEquals(
- c.check(challenge, c.answer, c.secretKey, c.hmacKey),
- True)
- self.assertEquals(
- c.check(challengeBadB64, c.answer, c.secretKey, c.hmacKey),
- False)
-
- def test_check_caseInsensitive_lowercase(self):
- """A correct answer in lowercase characters should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- solution = c.answer.lower()
- self.assertEquals(
- c.check(challenge, solution, c.secretKey, c.hmacKey),
- True)
-
- def test_check_caseInsensitive_uppercase(self):
- """A correct answer in uppercase characters should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- solution = c.answer.upper()
- self.assertEquals(
- c.check(challenge, solution, c.secretKey, c.hmacKey),
- True)
-
- def test_check_encoding_utf8(self):
- """A correct answer in utf-8 lowercase should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- solution = c.answer.encode('utf8')
- self.assertEquals(
- c.check(challenge, solution, c.secretKey, c.hmacKey),
- True)
-
- def test_check_encoding_ascii(self):
- """A correct answer in utf-8 lowercase should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- solution = c.answer.encode('ascii')
- self.assertEquals(
- c.check(challenge, solution, c.secretKey, c.hmacKey),
- True)
-
- def test_check_encoding_unicode(self):
- """A correct answer in utf-8 lowercase should return True."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- solution = unicode(c.answer)
- self.assertEquals(
- c.check(challenge, solution, c.secretKey, c.hmacKey),
- True)
-
- def test_check_missingHMACbytes(self):
- """A challenge that is missing part of the HMAC should return False."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- challengeBadHMAC = challenge[:10] + challenge[20:]
- self.assertEquals(
- c.check(challengeBadHMAC, c.answer, c.secretKey, c.hmacKey),
- False)
-
- def test_check_missingAnswerbytes(self):
- """Partial encrypted answers in challenges should return False."""
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- challengeBadOrig = challenge[:20] + challenge[30:]
- self.assertEquals(
- c.check(challengeBadOrig, c.answer, c.secretKey, c.hmacKey),
- False)
-
- def test_check_badHMACkey(self):
- """A challenge with a bad HMAC key should return False."""
- hmacKeyBad = crypto.getKey('test_gimpCaptcha_badHMACkey')
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- self.assertEquals(
- c.check(challenge, c.answer, c.secretKey, hmacKeyBad),
- False)
-
- def test_check_badRSAkey(self):
- """A challenge with a bad RSA secret key should return False."""
- secretKeyBad, publicKeyBad = crypto.getRSAKey('test_gimpCaptcha_badRSAkey')
- c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
- self.cacheDir)
- image, challenge = c.get()
- self.assertEquals(
- c.check(challenge, c.answer, secretKeyBad, c.hmacKey),
- False)
diff --git a/lib/bridgedb/test/test_configure.py b/lib/bridgedb/test/test_configure.py
deleted file mode 100644
index ebac95a..0000000
--- a/lib/bridgedb/test/test_configure.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: please see the AUTHORS file for attributions
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""Tests for :mod:`bridgedb.configure`."""
-
-from __future__ import print_function
-
-import os
-
-from twisted.trial import unittest
-
-from bridgedb import configure
-
-
-class ConfigureTests(unittest.TestCase):
- """Tests for miscelaneous functions in :mod:`bridgedb.configure`."""
-
- def setUp(self):
- """Find the config file in the top directory of this repo."""
- here = os.getcwd()
- topdir = here.rstrip('_trial_temp')
- self.configFilename = os.path.join(topdir, 'bridgedb.conf')
-
- def test_loadConfig_with_file(self):
- """We should be able to load and parse the standard ``bridgedb.conf``
- file from the top directory of this repository.
- """
- config = configure.loadConfig(self.configFilename)
- self.assertTrue(config)
-
- def test_loadConfig_with_file_and_class(self):
- """We should be able to reload and parse the ``bridgedb.conf``
- file, if we have a config class as well.
- """
- config = configure.loadConfig(self.configFilename)
- newConfig = configure.loadConfig(self.configFilename, configCls=config)
- self.assertTrue(newConfig)
-
- def test_loadConfig_with_class(self):
- """We should be able to recreate a config, given its class."""
- config = configure.loadConfig(self.configFilename)
- newConfig = configure.loadConfig(configCls=config)
- self.assertTrue(newConfig)
-
- def test_loadConfig_set_EXTRA_INFO_FILES_when_None(self):
- """If certain options, like the ``EXTRA_INFO_FILES`` option in the
- config file weren't set, they should be made into lists so that our
- parsers don't choke on them later.
- """
- config = configure.loadConfig(self.configFilename)
- setattr(config, "EXTRA_INFO_FILES", None)
- self.assertTrue(config.EXTRA_INFO_FILES is None)
- newConfig = configure.loadConfig(configCls=config)
- self.assertIsInstance(newConfig.EXTRA_INFO_FILES, list)
-
- def test_loadConfig_returns_Conf(self):
- """After loading and parsing the ``bridgedb.conf`` file, we should have
- a :class:`bridgedb.configure.Conf`.
- """
- config = configure.loadConfig(self.configFilename)
- self.assertIsInstance(config, configure.Conf)
diff --git a/lib/bridgedb/test/test_crypto.py b/lib/bridgedb/test/test_crypto.py
deleted file mode 100644
index 3264ace..0000000
--- a/lib/bridgedb/test/test_crypto.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for :mod:`bridgedb.crypto`."""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import base64
-import io
-import logging
-import math
-import os
-import shutil
-
-import OpenSSL
-
-from twisted import version as _twistedversion
-from twisted.internet import defer
-from twisted.python.versions import Version
-from twisted.trial import unittest
-from twisted.test.proto_helpers import StringTransport
-from twisted.web.test import test_agent as txtagent
-
-from bridgedb import crypto
-from bridgedb import txrecaptcha
-from bridgedb.persistent import Conf
-from bridgedb.test.util import fileCheckDecorator
-from bridgedb.test.email_helpers import _createConfig
-
-
-logging.disable(50)
-
-SEKRIT_KEY = b'v\x16Xm\xfc\x1b}\x063\x85\xaa\xa5\xf9\xad\x18\xb2P\x93\xc6k\xf9'
-SEKRIT_KEY += b'\x8bI\xd9\xb8xw\xf5\xec\x1b\x7f\xa8'
-
-
-class DummyEndpoint(object):
- """An endpoint that uses a fake transport."""
-
- def connect(self, factory):
- """Returns a connection to a
- :api:`twisted.test.proto_helpers.StringTransport`.
- """
- protocol = factory.buildProtocol(None)
- protocol.makeConnection(StringTransport())
- return defer.succeed(protocol)
-
-
-class GetKeyTests(unittest.TestCase):
- """Tests for :func:`bridgedb.crypto.getKey`."""
-
- def test_getKey_nokey(self):
- """Test retrieving the secret_key from an empty file."""
- filename = os.path.join(os.getcwd(), 'sekrit')
- key = crypto.getKey(filename)
- self.failUnlessIsInstance(key, basestring,
- "key isn't a string! type=%r" % type(key))
-
- def test_getKey_tmpfile(self):
- """Test retrieving the secret_key from a new tmpfile."""
- filename = self.mktemp()
- key = crypto.getKey(filename)
- self.failUnlessIsInstance(key, basestring,
- "key isn't a string! type=%r" % type(key))
-
- def test_getKey_keyexists(self):
- """Write the example key to a file and test reading it back."""
- filename = self.mktemp()
- with open(filename, 'wb') as fh:
- fh.write(SEKRIT_KEY)
- fh.flush()
-
- key = crypto.getKey(filename)
- self.failUnlessIsInstance(key, basestring,
- "key isn't a string! type=%r" % type(key))
- self.assertEqual(SEKRIT_KEY, key,
- """The example key and the one read from file differ!
- key (in hex): %s
- SEKRIT_KEY (in hex): %s"""
- % (key.encode('hex'), SEKRIT_KEY.encode('hex')))
-
-
-class InitializeGnuPGTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.crypto.initializeGnupG`."""
-
- def _moveGnuPGHomedir(self):
- """Move the .gnupg/ directory from the top-level of this repo to the
- current working directory.
-
- :rtype: str
- :returns: The full path to the new gnupg home directory.
- """
- here = os.getcwd()
- topDir = here.rstrip('_trial_temp')
- gnupghome = os.path.join(topDir, '.gnupg')
- gnupghomeNew = os.path.join(here, '.gnupg')
-
- if os.path.isdir(gnupghomeNew):
- shutil.rmtree(gnupghomeNew)
-
- shutil.copytree(gnupghome, gnupghomeNew)
-
- return gnupghomeNew
-
- def _writePassphraseToFile(self, passphrase, filename):
- """Write **passphrase** to the file at **filename**.
-
- :param str passphrase: The GnuPG passphase.
- :param str filename: The file to write the passphrase to.
- """
- fh = open(filename, 'w')
- fh.write(passphrase)
- fh.flush()
- fh.close()
-
- def setUp(self):
- """Create a config object and setup our gnupg home directory."""
- self.config = _createConfig()
- self.gnupghome = self._moveGnuPGHomedir()
- self.config.EMAIL_GPG_HOMEDIR = self.gnupghome
-
- self.passphraseFile = 'gpg-passphrase-file'
- self._writePassphraseToFile('sekrit', self.passphraseFile)
-
- def test_crypto_initializeGnuPG(self):
- """crypto.initializeGnuPG() should return a 2-tuple with a gpg object
- and a signing function.
- """
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNotNone(gpg)
- self.assertIsNotNone(signfunc)
-
- def test_crypto_initializeGnuPG_disabled(self):
- """When EMAIL_GPG_SIGNING_ENABLED=False, crypto.initializeGnuPG()
- should return a 2-tuple of None.
- """
- self.config.EMAIL_GPG_SIGNING_ENABLED = False
- gpg, signfunc = crypto.initializeGnuPG(self.config)
-
- self.assertIsNone(gpg)
- self.assertIsNone(signfunc)
-
- def test_crypto_initializeGnuPG_no_secrets(self):
- """When the secring.gpg is missing, crypto.initializeGnuPG() should
- return a 2-tuple of None.
- """
- secring = os.path.join(self.gnupghome, 'secring.gpg')
- if os.path.isfile(secring):
- os.remove(secring)
-
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNone(gpg)
- self.assertIsNone(signfunc)
-
- def test_crypto_initializeGnuPG_no_publics(self):
- """When the pubring.gpg is missing, crypto.initializeGnuPG() should
- return a 2-tuple of None.
- """
- pubring = os.path.join(self.gnupghome, 'pubring.gpg')
- if os.path.isfile(pubring):
- os.remove(pubring)
-
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNone(gpg)
- self.assertIsNone(signfunc)
-
- def test_crypto_initializeGnuPG_with_passphrase(self):
- """crypto.initializeGnuPG() should initialize correctly when a
- passphrase is given but no passphrase is needed.
- """
- self.config.EMAIL_GPG_PASSPHRASE = 'password'
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNotNone(gpg)
- self.assertIsNotNone(signfunc)
-
- def test_crypto_initializeGnuPG_with_passphrase_file(self):
- """crypto.initializeGnuPG() should initialize correctly when a
- passphrase file is given but no passphrase is needed.
- """
- self.config.EMAIL_GPG_PASSPHRASE_FILE = self.passphraseFile
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNotNone(gpg)
- self.assertIsNotNone(signfunc)
-
- def test_crypto_initializeGnuPG_missing_passphrase_file(self):
- """crypto.initializeGnuPG() should initialize correctly if a passphrase
- file is given but that file is missing (when no passphrase is actually
- necessary).
- """
- self.config.EMAIL_GPG_PASSPHRASE_FILE = self.passphraseFile
- os.remove(self.passphraseFile)
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNotNone(gpg)
- self.assertIsNotNone(signfunc)
-
- def test_crypto_initializeGnuPG_signingFunc(self):
- """crypto.initializeGnuPG() should return a signing function which
- produces OpenPGP signatures.
- """
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNotNone(gpg)
- self.assertIsNotNone(signfunc)
-
- sig = signfunc("This is a test of the public broadcasting system.")
- print(sig)
- self.assertIsNotNone(sig)
- self.assertTrue(sig.startswith('-----BEGIN PGP SIGNED MESSAGE-----'))
-
- def test_crypto_initializeGnuPG_nonexistent_default_key(self):
- """When the key specified by EMAIL_GPG_PRIMARY_KEY_FINGERPRINT doesn't
- exist in the keyrings, crypto.initializeGnuPG() should return a 2-tuple
- of None.
- """
- self.config.EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = 'A' * 40
- gpg, signfunc = crypto.initializeGnuPG(self.config)
- self.assertIsNone(gpg)
- self.assertIsNone(signfunc)
-
-
-class RemovePKCS1PaddingTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.crypto.removePKCS1Padding`."""
-
- def setUp(self):
- """This blob *is* actually a correctly formed PKCS#1 padded signature
- on the descriptor::
-
- @purpose bridge
- router ExhalesPeppier 118.16.116.176 35665 0 0
- or-address [eef2:d52a:cf1b:552f:375d:f8d0:a72b:e794]:35664
- platform Tor 0.2.4.5-alpha on Linux
- protocols Link 1 2 Circuit 1
- published 2014-11-03 21:21:43
- fingerprint FA04 5CFF AB95 BA20 C994 FE28 9B23 583E F80F 34DA
- uptime 10327748
- bandwidth 2247108152 2540209215 1954007088
- extra-info-digest 571BF23D8F24F052483C1333EBAE9B91E4A6F422
- onion-key
- -----BEGIN RSA PUBLIC KEY-----
- MIGJAoGBAK7+a033aUqc97SWFVGFwR3ybQ0jG1HTPtsv2/fUfZPwCaf21ly4zIvH
- 9uNhtkcPH2p55X+n5M7OUaQawOzbwL4tSR9SLy9bGuZdWLbhu2GHQWmDkAB7BtHp
- UC+uGTN3jvQXEG2xlzpb+lOVUVNXLhL5kFmAXxL+iwN4TeEv/iCnAgMBAAE=
- -----END RSA PUBLIC KEY-----
- signing-key
- -----BEGIN RSA PUBLIC KEY-----
- MIGJAoGBANxmgJ6S3rBAGcvQu2tWBaHByJxeJkdGbxID2b8cITPaNmcl72e3Kd44
- GGIkoKhkX0SAO+i2U+Q41u/DPEBWLxhpl9GAFJZ10dcT18lL36yaK6FRDOcF9jx9
- 0A023/kwXd7QQDWqP7Fso+141bzit6ENvNmE1mvEeIoAR+EpJB1tAgMBAAE=
- -----END RSA PUBLIC KEY-----
- contact Somebody <somebody at example.com>
- ntor-onion-key 0Mfi/Af7zLmdNdrmJyPbZxPJe7TZU/hV4Z865g3g+k4
- reject *:*
- router-signature
- -----BEGIN SIGNATURE-----
- PsGGIP+V9ZXWIHjK943CMAPem3kFbO9kt9rvrPhd64u0f7ytB/qZGaOg1IEWki1I
- f6ZNjrthxicm3vnEUdhpRsyn7MUFiQmqLjBfqdzh0GyfrtU5HHr7CBV3tuhgVhik
- uY1kPNo1C8wkmuy31H3V7NXj+etZuzZN66qL3BiQwa8=
- -----END SIGNATURE-----
-
- However, for the blob to be valid it would need to be converted from
- base64-decoded bytes to a long, then raised by the power of the public
- exponent within the ASN.1 DER decoded signing-key (mod that key's
- public modulus), then re-converted back into bytes before attempting
- to remove the PKCS#1 padding. (See
- :meth:`bridedb.bridges.Bridge._verifyExtraInfoSignature`.)
- """
- blob = ('PsGGIP+V9ZXWIHjK943CMAPem3kFbO9kt9rvrPhd64u0f7ytB/qZGaOg1IEWk'
- 'i1If6ZNjrthxicm3vnEUdhpRsyn7MUFiQmqLjBfqdzh0GyfrtU5HHr7CBV3tu'
- 'hgVhikuY1kPNo1C8wkmuy31H3V7NXj+etZuzZN66qL3BiQwa8=')
- self.blob = base64.b64decode(blob)
-
- def test_crypto_removePKCS1Padding_bad_padding(self):
- """removePKCS1Padding() with a blob with a bad PKCS#1 identifier mark
- should raise PKCS1PaddingError.
- """
- self.assertRaises(crypto.PKCS1PaddingError,
- crypto.removePKCS1Padding,
- self.blob)
-
- def test_crypto_removePKCS1Padding_missing_padding(self):
- """removePKCS1Padding() with a blob with a missing PKCS#1 identifier
- mark should raise PKCS1PaddingError.
- """
- self.assertRaises(crypto.PKCS1PaddingError,
- crypto.removePKCS1Padding,
- b'\x99' + self.blob)
-
-
-class SSLVerifyingContextFactoryTests(unittest.TestCase,
- txtagent.FakeReactorAndConnectMixin):
- """Tests for :class:`bridgedb.crypto.SSLVerifyingContextFactory`."""
-
- _certificateText = (
- "-----BEGIN CERTIFICATE-----\n"
- "MIIEdjCCA16gAwIBAgIITcyHZlE/AhQwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE\n"
- "BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl\n"
- "cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMjEyMTUxMTE2WhcNMTQwNjEyMDAwMDAw\n"
- "WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN\n"
- "TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3\n"
- "Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt3TOf\n"
- "VOf4vfy4IROcEyiFzAJA+B3xkMccwA4anaD6VyGSFglRn5Oht3t+G0Mnu/LMuGba\n"
- "EE6NEBEUEbH8KMlAcVRj58LoFIzulaRCdkVX7JK9R+kU05sggvIl1Q2quaWSjiMQ\n"
- "SpyvKz1I2cmU5Gm4MfW/66M5ZJO323VrV19ydrgAtdbNnvVj85asrSyzwEBNxzNC\n"
- "N6OQtOmTt4I7KLXqkROtTmTFvhAGBsvhG0hJZWhoP1aVsFO+KcE2OaIIxWQ4ckW7\n"
- "BJEgYaXfgHo01LdR55aevGUqLfsdyT+GMZrG9k7eqAw4cq3ML2Y6RiyzskqoQL30\n"
- "3OdYjKTIcU+i3BoFAgMBAAGjggFBMIIBPTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI\n"
- "KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE\n"
- "XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0\n"
- "MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G\n"
- "A1UdDgQWBBQN7uQBzGDjvKRna111g9iPPtaXVTAMBgNVHRMBAf8EAjAAMB8GA1Ud\n"
- "IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMBcGA1UdIAQQMA4wDAYKKwYBBAHW\n"
- "eQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lB\n"
- "RzIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBrVp/xys2ABQvWPxpVrYaXiaoBXdxu\n"
- "RVVXp5Lyu8IipKqFJli81hOX9eqPG7biYeph9HiKnW31xsXebaVlWWL3NXOh5X83\n"
- "wpzozL0AkxskTMHQknrbIGLtmG67H71aKYyCthHEjawLmYjjvkcF6f9fKdYENM4C\n"
- "skz/yjtlPBQFAuT6J9w0b3qtc42sHNlpgIOdIRQc2YCD0p6jAo+wKjoRuRu3ILKj\n"
- "oCVrOPbDMPN4a2gSmK8Ur0aHuEpcNghg6HJsVSANokIIwQ/r4niqL5yotsangP/5\n"
- "rR97EIYKFz7C6LMy/PIe8xFTIyKMtM59IcpUDIwCLlM9JtNdwN4VpyKy\n"
- "-----END CERTIFICATE-----\n")
-
- def setUp(self):
- """Create a fake reactor for these tests."""
- self.reactor = self.Reactor()
- self.url = 'https://www.example.com/someresource.html#andatag'
-
- def test_getHostnameFromURL(self):
- """``getHostnameFromURL()`` should return a hostname from a URI."""
- if _twistedversion >= Version('twisted', 14, 0, 0):
- raise unittest.SkipTest(
- ("The SSLVerifyingContextFactory is no longer necessary in "
- "Twisted>=14.0.0, because the way in which TLS certificates "
- "are checked now includes certificate pinning, and the "
- "SSLVerifyingContextFactory only implemented strict hostname "
- "checking."))
-
- agent = txrecaptcha._getAgent(self.reactor, self.url)
- contextFactory = agent._contextFactory
- self.assertRegexpMatches(contextFactory.hostname,
- '.*www\.example\.com')
-
- def test_verifyHostname_mismatching(self):
- """Check that ``verifyHostname()`` returns ``False`` when the
- ``SSLVerifyingContextFactory.hostname`` does not match the one found
- in the level 0 certificate subject CN.
- """
- if _twistedversion >= Version('twisted', 14, 0, 0):
- raise unittest.SkipTest(
- ("The SSLVerifyingContextFactory is no longer necessary in "
- "Twisted>=14.0.0, because the way in which TLS certificates "
- "are checked now includes certificate pinning, and the "
- "SSLVerifyingContextFactory only implemented strict hostname "
- "checking."))
-
- agent = txrecaptcha._getAgent(self.reactor, self.url)
- contextFactory = agent._contextFactory
- x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
- self._certificateText)
- conn = DummyEndpoint()
- result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
- self.assertIs(result, False)
-
- def test_verifyHostname_matching(self):
- """Check that ``verifyHostname()`` returns ``True`` when the
- ``SSLVerifyingContextFactory.hostname`` matches the one found in the
- level 0 certificate subject CN.
- """
- hostname = 'www.google.com'
- url = 'https://' + hostname + '/recaptcha'
- contextFactory = crypto.SSLVerifyingContextFactory(url)
- self.assertEqual(contextFactory.hostname, hostname)
-
- x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
- self._certificateText)
- conn = DummyEndpoint()
- result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
- self.assertTrue(result)
-
- def test_getContext(self):
- """The context factory's ``getContext()`` method should produce an
- ``OpenSSL.SSL.Context`` object.
- """
- contextFactory = crypto.SSLVerifyingContextFactory(self.url)
- self.assertIsInstance(contextFactory.getContext(),
- OpenSSL.SSL.Context)
diff --git a/lib/bridgedb/test/test_distribute.py b/lib/bridgedb/test/test_distribute.py
deleted file mode 100644
index 95fda31..0000000
--- a/lib/bridgedb/test/test_distribute.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Unittests for :mod:`bridgedb.distribute`."""
-
-from __future__ import print_function
-
-from twisted.trial import unittest
-
-from zope.interface.verify import verifyObject
-
-from bridgedb.distribute import IDistribute
-from bridgedb.distribute import Distributor
-
-
-class DistributorTests(unittest.TestCase):
- """Tests for :class:`bridgedb.distribute.Distributor`."""
-
- def test_Distributor_implements_IDistribute(self):
- IDistribute.namesAndDescriptions()
- IDistribute.providedBy(Distributor)
- self.assertTrue(verifyObject(IDistribute, Distributor()))
-
- def test_Distributor_str_no_name(self):
- """str(dist) when the distributor doesn't have a name should return a
- blank string.
- """
- dist = Distributor()
- self.assertEqual(str(dist), "")
-
- def test_Distributor_str_with_name(self):
- """str(dist) when the distributor has a name should return the name."""
- dist = Distributor()
- dist.name = "foo"
- self.assertEqual(str(dist), "foo")
diff --git a/lib/bridgedb/test/test_email_autoresponder.py b/lib/bridgedb/test/test_email_autoresponder.py
deleted file mode 100644
index 98302ca..0000000
--- a/lib/bridgedb/test/test_email_autoresponder.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.email.autoresponder` module."""
-
-from __future__ import print_function
-
-import io
-import os
-import shutil
-
-from twisted.internet import defer
-from twisted.mail.smtp import Address
-from twisted.python.failure import Failure
-from twisted.trial import unittest
-from twisted.test import proto_helpers
-
-from bridgedb.email import autoresponder
-from bridgedb.email.server import SMTPMessage
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.test.email_helpers import _createConfig
-from bridgedb.test.email_helpers import _createMailServerContext
-from bridgedb.test.email_helpers import DummyEmailDistributorWithState
-
-
-class CreateResponseBodyTests(unittest.TestCase):
- """Tests for :func:`bridgedb.email.autoresponder.createResponseBody`."""
-
- def _moveGPGTestKeyfile(self):
- here = os.getcwd()
- topDir = here.rstrip('_trial_temp')
- self.gpgFile = os.path.join(topDir, '.gnupg', 'TESTING.subkeys.sec')
- self.gpgMoved = os.path.join(here, 'TESTING.subkeys.sec')
- shutil.copy(self.gpgFile, self.gpgMoved)
-
- def setUp(self):
- """Create fake email, distributor, and associated context data."""
- self._moveGPGTestKeyfile()
- self.toAddress = "user at example.com"
- self.config = _createConfig()
- self.ctx = _createMailServerContext(self.config)
- self.distributor = self.ctx.distributor
-
- def _getIncomingLines(self, clientAddress="user at example.com"):
- """Generate the lines of an incoming email from **clientAddress**."""
- self.toAddress = Address(clientAddress)
- lines = [
- "From: %s" % clientAddress,
- "To: bridges at localhost",
- "Subject: testing",
- "",
- "get bridges",
- ]
- return lines
-
- def test_createResponseBody_getKey(self):
- """A request for 'get key' should receive our GPG key."""
- lines = self._getIncomingLines()
- lines[4] = "get key"
- ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
- self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', ret)
-
- def test_createResponseBody_bridges_invalid(self):
- """An invalid request for 'transport obfs3' should get help text."""
- lines = self._getIncomingLines("testing at localhost")
- lines[4] = "transport obfs3"
- ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
- self.assertSubstring("COMMANDs", ret)
-
- def test_createResponseBody_bridges_obfs3(self):
- """A request for 'get transport obfs3' should receive a response."""
- lines = self._getIncomingLines("testing at localhost")
- lines[4] = "get transport obfs3"
- ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
- self.assertSubstring("Here are your bridges", ret)
- self.assertSubstring("obfs3", ret)
-
- def test_createResponseBody_bridges_obfsobfswebz(self):
- """We should only pay attention to the *last* in a crazy request."""
- lines = self._getIncomingLines("testing at localhost")
- lines[4] = "get unblocked webz"
- lines.append("get transport obfs2")
- lines.append("get transport obfs3")
- ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
- self.assertSubstring("Here are your bridges", ret)
- self.assertSubstring("obfs3", ret)
-
- def test_createResponseBody_bridges_obfsobfswebzipv6(self):
- """We should *still* only pay attention to the *last* request."""
- lines = self._getIncomingLines("testing at localhost")
- lines[4] = "transport obfs3"
- lines.append("get unblocked webz")
- lines.append("get ipv6")
- lines.append("get transport obfs2")
- ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
- self.assertSubstring("Here are your bridges", ret)
- self.assertSubstring("obfs2", ret)
-
- def test_createResponseBody_two_requests_TooSoonEmail(self):
- """The same client making two requests in a row should receive a
- rate-limit warning for the second response.
- """
- # Set up a mock distributor which keeps state:
- dist = DummyEmailDistributorWithState()
- ctx = _createMailServerContext(self.config, dist)
-
- lines = self._getIncomingLines("testing at localhost")
- first = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertSubstring("Here are your bridges", first)
- second = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertSubstring("Please slow down", second)
-
- def test_createResponseBody_three_requests_TooSoonEmail(self):
- """Alice making a request, next Bob making a request, and then Alice again,
- should result in both of their first requests getting them bridges,
- and then Alice's second request gets her a rate-limit warning email.
- """
- # Set up a mock distributor which keeps state:
- dist = DummyEmailDistributorWithState()
- ctx = _createMailServerContext(self.config, dist)
-
- aliceLines = self._getIncomingLines("alice at localhost")
- aliceFirst = autoresponder.createResponseBody(aliceLines, ctx,
- self.toAddress)
- self.assertSubstring("Here are your bridges", aliceFirst)
-
- bobLines = self._getIncomingLines("bob at localhost")
- bobFirst = autoresponder.createResponseBody(bobLines, ctx,
- self.toAddress)
- self.assertSubstring("Here are your bridges", bobFirst)
-
- aliceSecond = autoresponder.createResponseBody(aliceLines, ctx,
- self.toAddress)
- self.assertSubstring("Please slow down", aliceSecond)
-
- def test_createResponseBody_three_requests_IgnoreEmail(self):
- """The same client making three requests in a row should receive a
- rate-limit warning for the second response, and then nothing for every
- request thereafter.
- """
- # Set up a mock distributor which keeps state:
- dist = DummyEmailDistributorWithState()
- ctx = _createMailServerContext(self.config, dist)
-
- lines = self._getIncomingLines("testing at localhost")
- first = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertSubstring("Here are your bridges", first)
- second = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertSubstring("Please slow down", second)
- third = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertIsNone(third)
- fourth = autoresponder.createResponseBody(lines, ctx, self.toAddress)
- self.assertIsNone(fourth)
-
-
-class EmailResponseTests(unittest.TestCase):
- """Tests for ``generateResponse()`` and ``EmailResponse``."""
-
- def setUp(self):
- self.fromAddr = "bridges at torproject.org"
- self.clientAddr = "user at example.com"
- self.body = """\
-People think that time is strictly linear, but, in reality, it's actually just
-a ball of timey-wimey, wibbly-warbly... stuff."""
-
- def tearDown(self):
- autoresponder.safelog.safe_logging = True
-
- def test_EmailResponse_generateResponse(self):
- response = autoresponder.generateResponse(self.fromAddr,
- self.clientAddr,
- self.body)
- self.assertIsInstance(response, autoresponder.EmailResponse)
-
- def test_EmailResponse_generateResponse_noSafelog(self):
- autoresponder.safelog.safe_logging = False
- response = autoresponder.generateResponse(self.fromAddr,
- self.clientAddr,
- self.body)
- self.assertIsInstance(response, autoresponder.EmailResponse)
-
- def test_EmailResponse_generateResponse_mailfile(self):
- response = autoresponder.generateResponse(self.fromAddr,
- self.clientAddr,
- self.body)
- self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
-
- def test_EmailResponse_generateResponse_withInReplyTo(self):
- response = autoresponder.generateResponse(self.fromAddr,
- self.clientAddr,
- self.body,
- messageID="NSA")
- contents = str(response.readContents()).replace('\x00', '')
- self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
- self.assertSubstring("In-Reply-To: NSA", contents)
-
- def test_EmailResponse_generateResponse_readContents(self):
- response = autoresponder.generateResponse(self.fromAddr,
- self.clientAddr,
- self.body)
- contents = str(response.readContents()).replace('\x00', '')
- self.assertSubstring('timey-wimey, wibbly-warbly... stuff.', contents)
-
- def test_EmailResponse_additionalHeaders(self):
- response = autoresponder.EmailResponse()
- response.writeHeaders(self.fromAddr, self.clientAddr,
- subject="Re: echelon", inReplyTo="NSA",
- X_been_there="They were so 2004")
- contents = str(response.readContents()).replace('\x00', '')
- self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
- self.assertSubstring("In-Reply-To: NSA", contents)
- self.assertSubstring("X-been-there: They were so 2004", contents)
-
- def test_EmailResponse_close(self):
- """Calling EmailResponse.close() should close the ``mailfile`` and set
- ``closed=True``.
- """
- response = autoresponder.EmailResponse()
- self.assertEqual(response.closed, False)
- response.close()
- self.assertEqual(response.closed, True)
- self.assertRaises(ValueError, response.write, self.body)
-
- def test_EmailResponse_read(self):
- """Calling EmailResponse.read() should read bytes from the file."""
- response = autoresponder.EmailResponse()
- response.write(self.body)
- response.rewind()
- contents = str(response.read()).replace('\x00', '')
- # The newlines in the email body should have been replaced with
- # ``EmailResponse.delimiter``.
- delimited = self.body.replace('\n', response.delimiter) \
- + response.delimiter
- self.assertEqual(delimited, contents)
-
- def test_EmailResponse_read_three_bytes(self):
- """EmailResponse.read(3) should read three bytes from the file."""
- response = autoresponder.EmailResponse()
- response.write(self.body)
- response.rewind()
- contents = str(response.read(3)).replace('\x00', '')
- self.assertEqual(contents, self.body[:3])
-
- def test_EmailResponse_write(self):
- """Calling EmailResponse.write() should write to the mailfile."""
- response = autoresponder.EmailResponse()
- response.write(self.body)
- contents = str(response.readContents()).replace('\x00', '')
- # The newlines in the email body should have been replaced with
- # ``EmailResponse.delimiter``.
- delimited = self.body.replace('\n', response.delimiter) \
- + response.delimiter
- self.assertEqual(delimited, contents)
-
- def test_EmailResponse_write_withRetNewlines(self):
- """Calling EmailResponse.write() with '\r\n' in the lines should call
- writelines(), which splits up the lines and then calls write() again.
- """
- response = autoresponder.EmailResponse()
- response.write(self.body.replace('\n', '\r\n'))
- contents = str(response.readContents()).replace('\x00', '')
- # The newlines in the email body should have been replaced with
- # ``EmailResponse.delimiter``.
- delimited = self.body.replace('\n', response.delimiter) \
- + response.delimiter
- self.assertEqual(delimited, contents)
-
- def test_EmailResponse_writelines_list(self):
- """Calling EmailResponse.writelines() with a list should write the
- concatenated contents of the list into the mailfile.
- """
- response = autoresponder.EmailResponse()
- response.writelines(self.body.split('\n'))
- contents = str(response.readContents()).replace('\x00', '')
- # The newlines in the email body should have been replaced with
- # ``EmailResponse.delimiter``.
- delimited = self.body.replace('\n', response.delimiter) \
- + response.delimiter
- self.assertEqual(delimited, contents)
-
-
-class SMTPAutoresponderTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.email.autoresponder.SMTPAutoresponder`."""
-
- timeout = 10
-
- def setUp(self):
- self.config = _createConfig()
- self.context = _createMailServerContext(self.config)
- self.message = SMTPMessage(self.context)
-
- def _getIncomingLines(self, clientAddress="user at example.com"):
- """Generate the lines of an incoming email from **clientAddress**."""
- lines = [
- "From: %s" % clientAddress,
- "To: bridges at localhost",
- "Subject: testing",
- "",
- "get bridges",
- ]
- self.message.lines = lines
-
- def _setUpResponder(self):
- """Set up the incoming message of our autoresponder.
-
- This is necessary because normally our SMTP server acts as a line
- protocol, waiting for an EOM which sets off a chain of deferreds
- resulting in the autoresponder sending out the response. This should
- be called after :meth:`_getIncomingLines` so that we can hook into the
- SMTP protocol without actually triggering all the deferreds.
- """
- self.message.message = self.message.getIncomingMessage()
- self.responder = self.message.responder
- # The following are needed to provide client disconnection methods for
- # the call to ``twisted.mail.smtp.SMTPClient.sendError`` in
- # ``bridgedb.email.autoresponder.SMTPAutoresponder.sendError``:
- #protocol = proto_helpers.AccumulatingProtocol()
- #transport = proto_helpers.StringTransportWithDisconnection()
- self.tr = proto_helpers.StringTransportWithDisconnection()
- # Set the transport's protocol, because
- # StringTransportWithDisconnection is a bit janky:
- self.tr.protocol = self.responder
- self.responder.makeConnection(self.tr)
-
- def test_SMTPAutoresponder_getMailFrom_notbridgedb_at_yikezors_dot_net(self):
- """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
- address other than the one we're listening for should return our
- configured address, not the one in the incoming email.
- """
- self._getIncomingLines()
- self.message.lines[1] = 'To: notbridgedb at yikezors.net'
- self._setUpResponder()
- recipient = str(self.responder.getMailFrom())
- self.assertEqual(recipient, self.context.fromAddr)
-
- def test_SMTPAutoresponder_getMailFrom_givemebridges_at_seriously(self):
- """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
- address other than the one we're listening for should return our
- configured address, not the one in the incoming email.
- """
- self._getIncomingLines()
- self.message.lines[1] = 'To: givemebridges at serious.ly'
- self._setUpResponder()
- recipient = str(self.responder.getMailFrom())
- self.assertEqual(recipient, self.context.fromAddr)
-
- def test_SMTPAutoresponder_getMailFrom_bad_address(self):
- """SMTPAutoresponder.getMailFrom() for an incoming email sent to a malformed
- email address should log an smtp.AddressError and then return our
- configured email address.
- """
- self._getIncomingLines()
- self.message.lines[1] = 'To: ><@><<<>>.foo'
- self._setUpResponder()
- recipient = str(self.responder.getMailFrom())
- self.assertEqual(recipient, self.context.fromAddr)
-
- def test_SMTPAutoresponder_getMailFrom_plus_address(self):
- """SMTPAutoresponder.getMailFrom() for an incoming email sent with a valid
- plus address should respond.
- """
- self._getIncomingLines()
- ours = Address(self.context.fromAddr)
- plus = '@'.join([ours.local + '+zh_cn', ours.domain])
- self.message.lines[1] = 'To: {0}'.format(plus)
- self._setUpResponder()
- recipient = str(self.responder.getMailFrom())
- self.assertEqual(recipient, plus)
-
- def test_SMTPAutoresponder_getMailFrom_getbridges_at_localhost(self):
- """SMTPAutoresponder.getMailFrom() for an incoming email sent with
- 'getbridges+zh_cn at localhost' should be responded to from the default
- address.
- """
- self._getIncomingLines()
- ours = Address(self.context.fromAddr)
- plus = '@'.join(['get' + ours.local + '+zh_cn', ours.domain])
- self.message.lines[1] = 'To: {0}'.format(plus)
- self._setUpResponder()
- recipient = str(self.responder.getMailFrom())
- self.assertEqual(recipient, self.context.fromAddr)
-
- def test_SMTPAutoresponder_getMailTo_UnsupportedDomain(self):
- """getMailTo() should catch emails from UnsupportedDomains."""
- emailFrom = 'some.dude at un.support.ed'
- self._getIncomingLines(emailFrom)
- self._setUpResponder()
- clients = self.responder.getMailTo()
- self.assertIsInstance(clients, list, (
- "Returned value of SMTPAutoresponder.getMailTo() isn't a list! "
- "Type: %s" % type(clients)))
- self.assertTrue(emailFrom not in clients)
- # The client was from an unsupported domain; they shouldn't be in the
- # clients list:
- self.assertEqual(len(clients), 0,
- "clients = %s" % repr(clients))
-
- def test_SMTPAutoresponder_reply_noFrom(self):
- """A received email without a "From:" or "Sender:" header shouldn't
- receive a response.
- """
- self._getIncomingLines()
- self.message.lines[0] = ""
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_badAddress(self):
- """Don't respond to RFC2822 malformed source addresses."""
- self._getIncomingLines("testing*.?\"@example.com")
- self._setUpResponder()
- ret = self.responder.reply()
- # This will call ``self.responder.reply()``:
- #ret = self.responder.incoming.eomReceived()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_anotherBadAddress(self):
- """Don't respond to RFC2822 malformed source addresses."""
- self._getIncomingLines("Mallory <>>@example.com")
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_invalidDomain(self):
- """Don't respond to RFC2822 malformed source addresses."""
- self._getIncomingLines("testing at exa#mple.com")
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_anotherInvalidDomain(self):
- """Don't respond to RFC2822 malformed source addresses."""
- self._getIncomingLines("testing at exam+ple.com")
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_DKIM_badDKIMheader(self):
- """An email with an 'X-DKIM-Authentication-Result:' header appended
- after the body should not receive a response.
- """
- self._getIncomingLines("testing at gmail.com")
- self.message.lines.append("X-DKIM-Authentication-Result: ")
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_goodDKIMheader(self):
- """An email with a good DKIM header should be responded to."""
- self._getIncomingLines("testing at gmail.com")
- self.message.lines.insert(3, "X-DKIM-Authentication-Result: pass")
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
-
- def test_SMTPAutoresponder_reply_transport_invalid(self):
- """An invalid request for 'transport obfs3' should get help text."""
- #self.skip = True
- #raise unittest.SkipTest("We need to fake the reactor for this one")
-
- def cb(success):
- pass
- self._getIncomingLines("testing at example.com")
- self.message.lines[4] = "transport obfs3"
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
- #self.assertSubstring("COMMANDs", ret)
- print(self.tr.value())
- return ret
-
- def test_SMTPAutoresponder_reply_transport_valid(self):
- """An valid request for 'get transport obfs3' should get obfs3."""
- #self.skip = True
- #raise unittest.SkipTest("We need to fake the reactor for this one")
-
- self._getIncomingLines("testing at example.com")
- self.message.lines[4] = "transport obfs3"
- self._setUpResponder()
- ret = self.responder.reply()
- self.assertIsInstance(ret, defer.Deferred)
- #self.assertSubstring("obfs3", ret)
- print(self.tr.value())
- return ret
-
- def test_SMTPAutoresponder_sentMail(self):
- """``SMTPAutoresponder.sendMail()`` should handle successes from an
- :api:`twisted.mail.smtp.SMTPSenderFactory`.
- """
- success = (1, [('me at myaddress.com', 250, 'OK',)])
- self._getIncomingLines()
- self._setUpResponder()
- self.responder.sentMail(success)
-
- def test_SMTPAutoresponder_sendError_fail(self):
- """``SMTPAutoresponder.sendError()`` should handle failures."""
- fail = Failure(ValueError('This failure was sent on purpose.'))
- self._getIncomingLines()
- self._setUpResponder()
- self.responder.sendError(fail)
-
- def test_SMTPAutoresponder_sendError_exception(self):
- """``SMTPAutoresponder.sendError()`` should handle exceptions."""
- error = ValueError('This error was sent on purpose.')
- self._getIncomingLines()
- self._setUpResponder()
- self.responder.sendError(error)
-
- def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_domain(self):
- """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
- reported being from an email address at one supported domain and the
- email's 'From:' header reported another domain.
- """
- smtpFrom = 'not.an.evil.bot at yahoo.com'
- emailFrom = Address('not.an.evil.bot at gmail.com')
- self._getIncomingLines(str(emailFrom))
- self._setUpResponder()
- self.responder.incoming.canonicalFromSMTP = smtpFrom
- self.assertFalse(self.responder.runChecks(emailFrom))
-
- def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_username(self):
- """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
- reported being from an email address and the email's 'From:' header
- reported another email address, even if the only the username part is
- mismatched.
- """
- smtpFrom = 'feidanchaoren0001 at gmail.com'
- emailFrom = Address('feidanchaoren0038 at gmail.com')
- self._getIncomingLines(str(emailFrom))
- self._setUpResponder()
- self.responder.incoming.canonicalFromSMTP = smtpFrom
- self.assertFalse(self.responder.runChecks(emailFrom))
-
- def test_SMTPAutoresponder_runChecks_DKIM_dunno(self):
- """runChecks() should catch emails with bad DKIM headers
- (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
- which we're configured to check DKIM verification results for.
- """
- emailFrom = Address('dkimlikedunno at gmail.com')
- header = "X-DKIM-Authentication-Results: dunno"
- self._getIncomingLines(str(emailFrom))
- self.message.lines.insert(3, header)
- self._setUpResponder()
- self.assertFalse(self.responder.runChecks(emailFrom))
-
- def test_SMTPAutoresponder_runChecks_DKIM_bad(self):
- """runChecks() should catch emails with bad DKIM headers
- (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
- which we're configured to check DKIM verification results for.
- """
- emailFrom = Address('dkimlikewat at gmail.com')
- header = "X-DKIM-Authentication-Results: wowie zowie there's a sig here"
- self._getIncomingLines(str(emailFrom))
- self.message.lines.insert(3, header)
- self._setUpResponder()
- self.assertFalse(self.responder.runChecks(emailFrom))
-
- def test_SMTPAutoresponder_runChecks_blacklisted(self):
- """runChecks() on an blacklisted email address should return False."""
- emailFrom = Address('feidanchaoren0043 at gmail.com')
- self._getIncomingLines(str(emailFrom))
- self._setUpResponder()
- self.assertFalse(self.responder.runChecks(emailFrom))
diff --git a/lib/bridgedb/test/test_email_distributor.py b/lib/bridgedb/test/test_email_distributor.py
deleted file mode 100644
index b4d88b2..0000000
--- a/lib/bridgedb/test/test_email_distributor.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see included LICENSE for information
-
-"""Tests for :mod:`bridgedb.email.distributor`."""
-
-from __future__ import print_function
-
-import logging
-import tempfile
-import os
-
-from twisted.internet.task import Clock
-from twisted.trial import unittest
-
-import bridgedb.Storage
-
-from bridgedb.bridges import Bridge
-from bridgedb.email.distributor import EmailDistributor
-from bridgedb.email.distributor import IgnoreEmail
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.email.request import EmailBridgeRequest
-from bridgedb.parse.addr import BadEmail
-from bridgedb.parse.addr import UnsupportedDomain
-from bridgedb.parse.addr import normalizeEmail
-from bridgedb.test.util import generateFakeBridges
-
-logging.disable(50)
-
-
-BRIDGES = generateFakeBridges()
-
-
-class EmailDistributorTests(unittest.TestCase):
- """Tests for :class:`bridgedb.email.distributor.EmailDistributor`."""
-
- # Fail any tests which take longer than 15 seconds.
- timeout = 15
-
- def setUp(self):
- self.fd, self.fname = tempfile.mkstemp(suffix=".sqlite", dir=os.getcwd())
- bridgedb.Storage.initializeDBLock()
- self.db = bridgedb.Storage.openDatabase(self.fname)
- bridgedb.Storage.setDBFilename(self.fname)
-
- self.bridges = BRIDGES
- self.key = 'aQpeOFIj8q20s98awfoiq23rpOIjFaqpEWFoij1X'
- self.domainmap = {
- 'example.com': 'example.com',
- 'dkim.example.com': 'dkim.example.com',
- }
- self.domainrules = {
- 'example.com': ['ignore_dots'],
- 'dkim.example.com': ['dkim', 'ignore_dots']
- }
-
- def tearDown(self):
- self.db.close()
- os.close(self.fd)
- os.unlink(self.fname)
-
- def makeClientRequest(self, clientEmailAddress):
- bridgeRequest = EmailBridgeRequest()
- bridgeRequest.client = clientEmailAddress
- bridgeRequest.isValid(True)
- bridgeRequest.generateFilters()
- return bridgeRequest
-
- def test_EmailDistributor_getBridges_default_client(self):
- """If EmailBridgeRequest.client was not set, then getBridges() should
- raise a bridgedb.parse.addr.BadEmail exception.
- """
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- # The "default" client is literally the string "default", see
- # bridgedb.bridgerequest.BridgeRequestBase.
- bridgeRequest = self.makeClientRequest('default')
-
- self.assertRaises(BadEmail, dist.getBridges, bridgeRequest, 1)
-
- def test_EmailDistributor_getBridges_with_whitelist(self):
- """If an email address is in the whitelist, it should get a response
- every time it asks (i.e. no rate-limiting).
- """
- # The whitelist should be in the form {EMAIL: GPG_FINGERPRINT}
- whitelist = {'white at list.ed': '0123456789ABCDEF0123456789ABCDEF01234567'}
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules,
- whitelist=whitelist)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- # A request from a whitelisted address should always get a response.
- bridgeRequest = self.makeClientRequest('white at list.ed')
- for i in range(5):
- bridges = dist.getBridges(bridgeRequest, 1)
- self.assertEqual(len(bridges), 3)
-
- def test_EmailDistributor_getBridges_rate_limit_multiple_clients(self):
- """Each client should be rate-limited separately."""
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- bridgeRequest1 = self.makeClientRequest('abc at example.com')
- bridgeRequest2 = self.makeClientRequest('def at example.com')
- bridgeRequest3 = self.makeClientRequest('ghi at example.com')
-
- # The first request from 'abc' should get a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest1, 1)), 3)
- # The second from 'abc' gets a warning.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest1, 1)
- # The first request from 'def' should get a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest2, 1)), 3)
- # The third from 'abc' is ignored.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
- # The second from 'def' gets a warning.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest2, 1)
- # The third from 'def' is ignored.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest2, 1)
- # The fourth from 'abc' is ignored.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
- # The first request from 'ghi' should get a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest3, 1)), 3)
- # The second from 'ghi' gets a warning.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest3, 1)
- # The third from 'ghi' is ignored.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
- # The fourth from 'ghi' is ignored.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
-
- def test_EmailDistributor_getBridges_rate_limit(self):
- """A client's first email should return bridges. The second should
- return a warning, and the third should receive no response.
- """
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- bridgeRequest = self.makeClientRequest('abc at example.com')
-
- # The first request should get a response with bridges.
- bridges = dist.getBridges(bridgeRequest, 1)
- self.assertGreater(len(bridges), 0)
- [self.assertIsInstance(b, Bridge) for b in bridges]
- self.assertEqual(len(bridges), 3)
-
- # The second gets a warning, and the third is ignored.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
-
- def test_EmailDistributor_getBridges_rate_limit_expiry(self):
- """A client's first email should return bridges. The second should
- return a warning, and the third should receive no response. After the
- EmailDistributor.emailRateMax is up, the client should be able to
- receive a response again.
- """
- clock = Clock()
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- bridgeRequest = self.makeClientRequest('abc at example.com')
-
- # The first request should get a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest, 1, clock)), 3)
- # The second gets a warning, and the rest are ignored.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1, clock)
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
-
- clock.advance(2 * dist.emailRateMax)
-
- # The client should again a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3, clock)
-
- def test_EmailDistributor_cleanDatabase(self):
- """Calling cleanDatabase() should cleanup email times in database, but
- not allow clients who have been recently warned and/or ignored to
- receive a response again until the remainder of their MAX_EMAIL_RATE
- time is up.
- """
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- bridgeRequest = self.makeClientRequest('abc at example.com')
-
- # The first request should get a response with bridges.
- self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3)
- # The second gets a warning, and the third is ignored.
- self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
-
- dist.cleanDatabase()
-
- # Cleaning the warning email times in the database shouldn't cause
- # 'abc at example.com' to be able to email again, because only the times
- # which aren't older than EMAIL_MAX_RATE should be cleared.
- self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
-
- def test_EmailDistributor_prepopulateRings(self):
- """Calling prepopulateRings() should add two rings to the
- EmailDistributor.hashring.
- """
- dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
-
- # There shouldn't be any subrings yet.
- self.assertEqual(len(dist.hashring.filterRings), 0)
-
- dist.prepopulateRings()
-
- # There should now be two subrings, but the subrings should be empty.
- self.assertEqual(len(dist.hashring.filterRings), 2)
- for (filtre, subring) in dist.hashring.filterRings.values():
- self.assertEqual(len(subring), 0)
-
- # The subrings in this Distributor have gross names, because the
- # filter functions (including their addresses in memory!) are used as
- # the subring names. In this case, we should have something like:
- #
- # frozenset([<function byIPv6 at 0x7eff7ad7fc80>])
- #
- # and
- #
- # frozenset([<function byIPv4 at 0x7eff7ad7fc08>])
- #
- # So we have to join the strings together and check the whole thing,
- # since we have no other way to use these stupid subring names to
- # index into the dictionary they are stored in, because the memory
- # addresses are unknowable until runtime.
-
- # There should be an IPv4 subring and an IPv6 ring:
- ringnames = dist.hashring.filterRings.keys()
- self.failUnlessIn("IPv4", "".join([str(ringname) for ringname in ringnames]))
- self.failUnlessIn("IPv6", "".join([str(ringname) for ringname in ringnames]))
-
- [dist.hashring.insert(bridge) for bridge in self.bridges]
-
- # There should still be two subrings.
- self.assertEqual(len(dist.hashring.filterRings), 2)
- for (filtre, subring) in dist.hashring.filterRings.values():
- self.assertGreater(len(subring), 0)
-
- # Ugh, the hashring code is so gross looking.
- subrings = dist.hashring.filterRings
- subring1 = subrings.values()[0][1]
- subring2 = subrings.values()[1][1]
- # Each subring should have roughly the same number of bridges.
- # (Having ±10 bridges in either ring, out of 500 bridges total, should
- # be so bad.)
- self.assertApproximates(len(subring1), len(subring2), 10)
-
- def test_EmailDistributor_unsupported_domain(self):
- """An unsupported domain should raise an UnsupportedDomain exception."""
- self.assertRaises(UnsupportedDomain, normalizeEmail,
- 'bad at email.com', self.domainmap, self.domainrules)
diff --git a/lib/bridgedb/test/test_email_dkim.py b/lib/bridgedb/test/test_email_dkim.py
deleted file mode 100644
index 499a3c1..0000000
--- a/lib/bridgedb/test/test_email_dkim.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.email.dkim` module."""
-
-import io
-
-from twisted.mail.smtp import rfc822
-from twisted.trial import unittest
-
-from bridgedb.email import dkim
-
-
-class CheckDKIMTests(unittest.TestCase):
- """Tests for :func:`email.server.checkDKIM`."""
-
- def setUp(self):
- """Create fake email, distributor, and associated context data."""
- self.goodMessage = """\
-From: user at gmail.com
-To: bridges at localhost
-X-DKIM-Authentication-Results: pass
-Subject: testing
-
-get bridges
-"""
- self.badMessage = """\
-From: user at gmail.com
-To: bridges at localhost
-Subject: testing
-
-get bridges
-"""
- self.domainRules = {
- 'gmail.com': ["ignore_dots", "dkim"],
- 'example.com': [],
- 'localhost': [],
- }
-
- def _createMessage(self, messageString):
- """Create an ``rfc822.Message`` from a string."""
- messageIO = io.StringIO(unicode(messageString))
- return rfc822.Message(messageIO)
-
- def test_checkDKIM_good(self):
- message = self._createMessage(self.goodMessage)
- result = dkim.checkDKIM(message,
- self.domainRules.get("gmail.com"))
- self.assertTrue(result)
-
-
- def test_checkDKIM_bad(self):
- message = self._createMessage(self.badMessage)
- result = dkim.checkDKIM(message,
- self.domainRules.get("gmail.com"))
- self.assertIs(result, False)
-
- def test_checkDKIM_dunno(self):
- """A ``X-DKIM-Authentication-Results: dunno`` header should return
- False.
- """
- messageList = self.badMessage.split('\n')
- messageList[2] = "X-DKIM-Authentication-Results: dunno"
- message = self._createMessage('\n'.join(messageList))
- result = dkim.checkDKIM(message,
- self.domainRules.get("gmail.com"))
- self.assertIs(result, False)
-
- def test_checkDKIM_good_dunno(self):
- """A good DKIM verification header, *plus* an
- ``X-DKIM-Authentication-Results: dunno`` header should return False.
- """
- messageList = self.badMessage.split('\n')
- messageList.insert(2, "X-DKIM-Authentication-Results: dunno")
- message = self._createMessage('\n'.join(messageList))
- result = dkim.checkDKIM(message,
- self.domainRules.get("gmail.com"))
- self.assertIs(result, False)
diff --git a/lib/bridgedb/test/test_email_request.py b/lib/bridgedb/test/test_email_request.py
deleted file mode 100644
index 745ea71..0000000
--- a/lib/bridgedb/test/test_email_request.py
+++ /dev/null
@@ -1,276 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.email.request` module."""
-
-from __future__ import print_function
-
-import ipaddr
-
-from twisted.trial import unittest
-
-from bridgedb.email import request
-
-
-class DetermineBridgeRequestOptionsTests(unittest.TestCase):
- """Unittests for :func:`b.e.request.determineBridgeRequestOptions`."""
-
- def test_determineBridgeRequestOptions_get_help(self):
- """Requesting 'get help' should raise EmailRequestedHelp."""
- lines = ['',
- 'get help']
- self.assertRaises(request.EmailRequestedHelp,
- request.determineBridgeRequestOptions, lines)
-
- def test_determineBridgeRequestOptions_get_halp(self):
- """Requesting 'get halp' should raise EmailRequestedHelp."""
- lines = ['',
- 'get halp']
- self.assertRaises(request.EmailRequestedHelp,
- request.determineBridgeRequestOptions, lines)
-
- def test_determineBridgeRequestOptions_get_key(self):
- """Requesting 'get key' should raise EmailRequestedKey."""
- lines = ['',
- 'get key']
- self.assertRaises(request.EmailRequestedKey,
- request.determineBridgeRequestOptions, lines)
-
- def test_determineBridgeRequestOptions_multiline_invalid(self):
- """Requests without a 'get' anywhere should be considered invalid."""
- lines = ['',
- 'transport obfs3',
- 'ipv6 vanilla bridges',
- 'give me your gpgs']
- reqvest = request.determineBridgeRequestOptions(lines)
- # It's invalid because it didn't include a 'get' anywhere.
- self.assertEqual(reqvest.isValid(), False)
- self.assertFalse(reqvest.wantsKey())
- # Though they did request IPv6, technically.
- self.assertIs(reqvest.ipVersion, 6)
- # And they did request a transport, technically.
- self.assertEqual(len(reqvest.transports), 1)
- self.assertEqual(reqvest.transports[0], 'obfs3')
-
- def test_determineBridgeRequestOptions_multiline_valid(self):
- """Though requests with a 'get' are considered valid."""
- lines = ['',
- 'get transport obfs3',
- 'vanilla bridges',
- 'transport scramblesuit unblocked ca']
- reqvest = request.determineBridgeRequestOptions(lines)
- # It's valid because it included a 'get'.
- self.assertEqual(reqvest.isValid(), True)
- self.assertFalse(reqvest.wantsKey())
- # Though they didn't request IPv6, so it should default to IPv4.
- self.assertIs(reqvest.ipVersion, 4)
- # And they requested two transports.
- self.assertEqual(len(reqvest.transports), 2)
- self.assertEqual(reqvest.transports[0], 'obfs3')
- self.assertEqual(reqvest.transports[1], 'scramblesuit')
- # And they wanted this stuff to not be blocked in Canada.
- self.assertEqual(len(reqvest.notBlockedIn), 1)
- self.assertEqual(reqvest.notBlockedIn[0], 'ca')
-
- def test_determineBridgeRequestOptions_multiline_valid_OMG_CAPSLOCK(self):
- """Though requests with a 'get' are considered valid, even if they
- appear to not know the difference between Capslock and Shift.
- """
- lines = ['',
- 'get TRANSPORT obfs3',
- 'vanilla bridges',
- 'TRANSPORT SCRAMBLESUIT UNBLOCKED CA']
- reqvest = request.determineBridgeRequestOptions(lines)
- # It's valid because it included a 'get'.
- self.assertEqual(reqvest.isValid(), True)
- self.assertFalse(reqvest.wantsKey())
- # Though they didn't request IPv6, so it should default to IPv4.
- self.assertIs(reqvest.ipVersion, 4)
- # And they requested two transports.
- self.assertEqual(len(reqvest.transports), 2)
- self.assertEqual(reqvest.transports[0], 'obfs3')
- self.assertEqual(reqvest.transports[1], 'scramblesuit')
- # And they wanted this stuff to not be blocked in Canada.
- self.assertEqual(len(reqvest.notBlockedIn), 1)
- self.assertEqual(reqvest.notBlockedIn[0], 'ca')
-
- def test_determineBridgeRequestOptions_get_transport(self):
- """An invalid request for 'transport obfs3' (missing the 'get')."""
- lines = ['',
- 'transport obfs3']
- reqvest = request.determineBridgeRequestOptions(lines)
- self.assertEqual(len(reqvest.transports), 1)
- self.assertEqual(reqvest.transports[0], 'obfs3')
- self.assertEqual(reqvest.isValid(), False)
-
- def test_determineBridgeRequestOptions_get_ipv6(self):
- """An valid request for 'get ipv6'."""
- lines = ['',
- 'get ipv6']
- reqvest = request.determineBridgeRequestOptions(lines)
- self.assertIs(reqvest.ipVersion, 6)
- self.assertEqual(reqvest.isValid(), True)
-
-
-class EmailBridgeRequestTests(unittest.TestCase):
- """Unittests for :class:`b.e.request.EmailBridgeRequest`."""
-
- def setUp(self):
- """Create an EmailBridgeRequest instance to test."""
- self.request = request.EmailBridgeRequest()
-
- def tearDown(self):
- """Reset cached 'unblocked'/'transport' lists and ipVersion between
- tests.
- """
- self.request.withIPv4()
- self.request.notBlockedIn = []
- self.request.transports = []
-
- def test_EmailBridgeRequest_isValid_initial(self):
- """Initial value of EmailBridgeRequest.isValid() should be False."""
- self.request.isValid(None)
- self.assertEqual(self.request.isValid(), False)
-
- def test_EmailBridgeRequest_isValid_True(self):
- """The value of EmailBridgeRequest.isValid() should be True, after it
- has been called with ``True`` as an argument.
- """
- self.request.isValid(True)
- self.assertEqual(self.request.isValid(), True)
-
- def test_EmailBridgeRequest_isValid_False(self):
- """The value of EmailBridgeRequest.isValid() should be False, after it
- has been called with ``False`` as an argument.
- """
- self.request.isValid(False)
- self.assertEqual(self.request.isValid(), False)
-
- def test_EmailBridgeRequest_wantsKey_initial(self):
- """Initial value of EmailBridgeRequest.wantsKey() should be False."""
- self.request.wantsKey(None)
- self.assertEqual(self.request.wantsKey(), False)
-
- def test_EmailBridgeRequest_wantsKey_True(self):
- """The value of EmailBridgeRequest.wantsKey() should be True, after it
- has been called with ``True`` as an argument.
- """
- self.request.wantsKey(True)
- self.assertEqual(self.request.wantsKey(), True)
-
- def test_EmailBridgeRequest_wantsKey_False(self):
- """The value of EmailBridgeRequest.wantsKey() should be False, after
- it has been called with ``False`` as an argument.
- """
- self.request.wantsKey(False)
- self.assertEqual(self.request.wantsKey(), False)
-
- def test_EmailBridgeRequest_withIPv6(self):
- """IPv6 requests should have ``ipVersion == 6``."""
- self.assertEqual(self.request.ipVersion, 4)
- self.request.withIPv6()
- self.assertEqual(self.request.ipVersion, 6)
-
- def test_EmailBridgeRequest_withoutBlockInCountry_CN(self):
- """Country codes that aren't lowercase should be ignored."""
- self.request.withoutBlockInCountry('get unblocked CN')
- self.assertIsInstance(self.request.notBlockedIn, list)
- self.assertEqual(len(self.request.notBlockedIn), 0)
-
- def test_EmailBridgeRequest_withoutBlockInCountry_cn(self):
- """Lowercased country codes are okay though."""
- self.request.withoutBlockInCountry('get unblocked cn')
- self.assertIsInstance(self.request.notBlockedIn, list)
- self.assertEqual(len(self.request.notBlockedIn), 1)
-
- def test_EmailBridgeRequest_withoutBlockInCountry_cn_getMissing(self):
- """Lowercased country codes are still okay if the 'get' is missing."""
- self.request.withoutBlockInCountry('unblocked cn')
- self.assertIsInstance(self.request.notBlockedIn, list)
- self.assertEqual(len(self.request.notBlockedIn), 1)
-
- def test_EmailBridgeRequest_withoutBlockInCountry_multiline_cn_ir_li(self):
- """Requests for multiple unblocked countries should compound if they
- are on separate 'get unblocked' lines.
- """
- self.request.withoutBlockInCountry('get unblocked cn')
- self.request.withoutBlockInCountry('get unblocked ir')
- self.request.withoutBlockInCountry('get unblocked li')
- self.assertIsInstance(self.request.notBlockedIn, list)
- self.assertEqual(len(self.request.notBlockedIn), 3)
-
- def test_EmailBridgeRequest_withoutBlockInCountry_singleline_cn_ir_li(self):
- """Requests for multiple unblocked countries which are all on the same
- 'get unblocked' line will use only the *first* country code.
- """
- self.request.withoutBlockInCountry('get unblocked cn ir li')
- self.assertIsInstance(self.request.notBlockedIn, list)
- self.assertEqual(len(self.request.notBlockedIn), 1)
-
- def test_EmailBridgeRequest_withPluggableTransportType_SCRAMBLESUIT(self):
- """Transports which aren't in lowercase should be ignored."""
- self.request.withPluggableTransportType('get transport SCRAMBLESUIT')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 0)
-
- def test_EmailBridgeRequest_withPluggableTransportType_scramblesuit(self):
- """Lowercased transports are okay though."""
- self.request.withPluggableTransportType('get transport scramblesuit')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 1)
- self.assertEqual(self.request.transports[0], 'scramblesuit')
-
- def test_EmailBridgeRequest_withPluggableTransportType_scramblesuit_getMissing(self):
- """Lowercased transports are still okay if 'get' is missing."""
- self.request.withPluggableTransportType('transport scramblesuit')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 1)
- self.assertEqual(self.request.transports[0], 'scramblesuit')
-
- def test_EmailBridgeRequest_withPluggableTransportType_multiline_obfs3_obfs2_scramblesuit(self):
- """Requests for multiple pluggable transports should compound if they
- are on separate 'get transport' lines.
- """
- self.request.withPluggableTransportType('get transport obfs3')
- self.request.withPluggableTransportType('get transport obfs2')
- self.request.withPluggableTransportType('get transport scramblesuit')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 3)
- self.assertEqual(self.request.transports[0], 'obfs3')
-
- def test_EmailBridgeRequest_withPluggableTransportType_singleline_obfs3_obfs2_scramblesuit(self):
- """Requests for multiple transports which are all on the same
- 'get transport' line will use only the *first* transport.
- """
- self.request.withPluggableTransportType('get transport obfs3 obfs2 scramblesuit')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 1)
- self.assertEqual(self.request.transports[0], 'obfs3')
-
- def test_EmailBridgeRequest_withPluggableTransportType_whack(self):
- """Requests for whacky transports that don't exist are also okay."""
- self.request.withPluggableTransportType('get transport whack')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 1)
- self.assertEqual(self.request.transports[0], 'whack')
-
- def test_EmailBridgeRequest_justOnePTType_obfs3_obfs2_scramblesuit(self):
- """Requests for multiple transports when
- ``EmailBridgeRequest.justOneTransport()`` is used will use only the
- *last* transport.
- """
- self.request.withPluggableTransportType('get transport obfs3')
- self.request.withPluggableTransportType('get transport obfs2')
- self.request.withPluggableTransportType('get transport scramblesuit')
- self.assertIsInstance(self.request.transports, list)
- self.assertEqual(len(self.request.transports), 3)
- self.assertEqual(self.request.transports[0], 'obfs3')
- self.assertEqual(self.request.justOnePTType(), 'scramblesuit')
diff --git a/lib/bridgedb/test/test_email_server.py b/lib/bridgedb/test/test_email_server.py
deleted file mode 100644
index 9c4fabb..0000000
--- a/lib/bridgedb/test/test_email_server.py
+++ /dev/null
@@ -1,543 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.email.server` module."""
-
-from __future__ import print_function
-
-import socket
-import string
-import types
-
-from twisted.python import log
-from twisted.internet import defer
-from twisted.internet import reactor
-from twisted.mail.smtp import IMessage
-from twisted.mail.smtp import SMTPBadRcpt
-from twisted.mail.smtp import SMTPBadSender
-from twisted.mail.smtp import User
-from twisted.mail.smtp import Address
-from twisted.mail.smtp import rfc822
-from twisted.test import proto_helpers
-from twisted.trial import unittest
-
-from zope.interface import implementedBy
-
-from bridgedb.email import server
-from bridgedb.email.distributor import EmailDistributor
-from bridgedb.email.distributor import TooSoonEmail
-from bridgedb.parse.addr import BadEmail
-from bridgedb.schedule import Unscheduled
-from bridgedb.test import util
-from bridgedb.test.email_helpers import _createConfig
-from bridgedb.test.email_helpers import _createMailServerContext
-
-
-class SMTPMessageTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.email.server.SMTPMessage`."""
-
- def setUp(self):
- self.config = _createConfig()
- self.context = _createMailServerContext(self.config)
- self.message = server.SMTPMessage(self.context,
- canonicalFromSMTP='localhost')
- self.line = string.ascii_lowercase
-
- def tearDown(self):
- """Re-enable safelogging in between test runs."""
- server.safelog.setSafeLogging(True)
-
- def test_SMTPMessage_init(self):
- """Our ``message`` attribute should be a ``SMTPMessage`` object, and
- ``message.responder`` should be a
- :class:`bridgedb.email.autoresponder.SMTPAutoresponder`.
- """
- self.assertIsInstance(self.message, server.SMTPMessage)
- self.assertIsInstance(self.message.responder,
- server.autoresponder.SMTPAutoresponder)
-
- def test_SMTPMessage_IMessage_interface(self):
- """``SMTPMessage`` should implement ``twisted.mail.smtp.IMessage``."""
- self.assertTrue(IMessage.implementedBy(server.SMTPMessage))
-
- def test_SMTPMessage_lineReceived_withSafelog(self):
- """Test sending a line of text to ``SMTPMessage.lineReceived`` with
- safelogging enabled.
- """
- server.safelog.setSafeLogging(True)
- self.message.lineReceived(self.line)
- self.assertEqual(self.message.nBytes, 26)
- self.assertTrue(self.line in self.message.lines)
-
- def test_SMTPMessage_lineReceived_withoutSafelog(self):
- """Test sending a line of text to ``SMTPMessage.lineReceived`` with
- safelogging disnabled.
- """
- server.safelog.setSafeLogging(False)
- for _ in range(3):
- self.message.lineReceived(self.line)
- self.assertEqual(self.message.nBytes, 3*26)
- self.assertTrue(self.line in self.message.lines)
-
- def test_SMTPMessage_eomReceived(self):
- """Calling ``oemReceived`` should return a deferred."""
- self.message.lineReceived(self.line)
- self.assertIsInstance(self.message.eomReceived(),
- defer.Deferred)
-
- def test_SMTPMessage_getIncomingMessage(self):
- """``getIncomingMessage`` should return a ``rfc822.Message``."""
- self.message.lineReceived(self.line)
- self.assertIsInstance(self.message.getIncomingMessage(),
- rfc822.Message)
-
-
-class SMTPIncomingDeliveryTests(unittest.TestCase):
- """Unittests for :class:`email.server.SMTPIncomingDelivery`."""
-
- def setUp(self):
- """Set up our :class:`server.SMTPIncomingDelivery` instance, and reset the
- following ``TestCase`` attributes to ``None``:
- - ``helo``
- - ``proto``
- - ``origin``
- - ``user``
- """
- self.config = _createConfig()
- self.context = _createMailServerContext(self.config)
- self.delivery = server.SMTPIncomingDelivery()
-
- self.helo = None
- self.proto = None
- self.origin = None
- self.user = None
-
- def tearDown(self):
- """Reset all TestCase instance attributes between each test run."""
- self.helo = None
- self.proto = None
- self.origin = None
- self.user = None
-
- def _createProtocolWithHost(self, host):
- """Mock a Protocol which has a ``host`` attribute.
-
- We don't currently use any of the ``IProtocol`` methods of the
- returned ``twisted.test.proto_helpers.AccumulatingProtocol``, and so
- this could be any class, although a mocked ``IProtocol`` implementer
- was chosen for completeness and realism's sakes.
-
- :param str host: A domain name or IP address.
- :rtype: :api:`twisted.test.proto_helpers.AccumulatingProtocol`
- :returns: A Protocol instance which has its ``host`` attribute set to
- the given **host**, so that an :api:`twisted.mail.smtp.User` can
- be constructed with it.
- """
- self.proto = proto_helpers.AccumulatingProtocol()
- self.proto.host = host
-
- def _createUser(self, username, domain, ipaddress):
- """Create a ``twisted.mail.smtp.User`` for testing.
-
- :param str username: The local part of the client's email address.
- :param str domain: The host part of the client's email address.
- :param str ipaddress: The IP address of the client's mail server.
- """
- self.helo = (domain, ipaddress)
- self._createProtocolWithHost(domain)
- self.origin = Address('@'.join((username, domain,)))
- self.user = User(username, self.helo, self.proto, self.origin)
-
- def _setUpMAILFROM(self):
- """Set up the parameters for emulating a connected client sending a
- SMTP 'MAIL FROM:' command to us.
-
- The default is to emulate sending: ``MAIL FROM: client at example.com``.
- """
- self.helo = ('localhost', '127.0.0.1')
- self.origin = server.smtp.Address('client at example.com')
- self.delivery.setContext(self.context)
-
- def _setUpRCPTTO(self, username=None, domain=None, ip=None):
- """Set up the parameters for emulating a connected client sending a
- SMTP 'RCPT TO:' command to us.
-
- The default is to emulate sending: ``RCPT TO: bridges at localhost``.
- """
- name = username if username is not None else self.config.EMAIL_USERNAME
- host = domain if domain is not None else 'localhost'
- addr = ip if ip is not None else '127.0.0.1'
- self._createUser(name, host, ip)
- self.delivery.setContext(self.context)
-
- def test_SMTPIncomingDelivery_init(self):
- """After calling :meth:`server.SMTPIncomingDelivery.__init__`, we should have a
- :class:`server.SMTPIncomingDelivery` object instance.
- """
- self.assertIsInstance(self.delivery, server.SMTPIncomingDelivery)
-
- def test_SMTPIncomingDelivery_setContext(self):
- """Calling :meth:`server.SMTPIncomingDelivery.setContext` should set
- the :ivar:`SMTPIncomingDelivery.context` attribute.
-
- The ``SMTPIncomingDelivery.context`` should be a :class:`server.MailServerContext`,
- and it should have relevant settings from the config file stored
- within it.
- """
- self.delivery.setContext(self.context)
- self.assertIsInstance(self.delivery.context, server.MailServerContext)
- self.assertEqual(self.delivery.context.smtpFromAddr,
- self.config.EMAIL_SMTP_FROM_ADDR)
-
- def test_SMTPIncomingDelivery_receivedHeader(self):
- """The email resulting from a SMTPIncomingDelivery, the latter received from
- ``'client at example.com'`` should contain a header stating:
- ``'Received: from example.com'``.
- """
- self._createUser('client', 'example.com', '127.0.0.1')
- hdr = self.delivery.receivedHeader(self.helo, self.origin, [self.user,])
- self.assertSubstring("Received: from example.com", hdr)
-
- def test_SMTPIncomingDelivery_validateFrom(self):
- """A valid origin should be stored as ``SMTPIncomingDelivery.fromCanonical``."""
- self._setUpMAILFROM()
- self.delivery.validateFrom(self.helo, self.origin)
- self.assertEqual(self.delivery.fromCanonicalSMTP, 'example.com')
-
- def test_SMTPIncomingDelivery_validateFrom_unsupportedDomain(self):
- """A domain not in our canon should raise a SMTPBadSender."""
- self._setUpMAILFROM()
- origin = server.smtp.Address('throwing.pickles at yo.mama')
- self.assertRaises(SMTPBadSender,
- self.delivery.validateFrom, self.helo, origin)
-
- def test_SMTPIncomingDelivery_validateFrom_origin_notAdressType(self):
- """A non ``twisted.mail.smtp.Address`` origin should raise an
- AttributeError exception.
- """
- self._setUpMAILFROM()
- origin = 'throwing.pickles at yo.mama'
- self.delivery.validateFrom(self.helo, origin)
-
- def test_SMTPIncomingDelivery_validateTo(self):
- """Should return a callable that results in a SMTPMessage."""
- self._setUpRCPTTO()
- validated = self.delivery.validateTo(self.user)
- self.assertIsInstance(validated, types.FunctionType)
- self.assertIsInstance(validated(), server.SMTPMessage)
-
- def test_SMTPIncomingDelivery_validateTo_plusAddress(self):
- """Should return a callable that results in a SMTPMessage."""
- self._setUpRCPTTO('bridges+ar')
- validated = self.delivery.validateTo(self.user)
- self.assertIsInstance(validated, types.FunctionType)
- self.assertIsInstance(validated(), server.SMTPMessage)
-
- def test_SMTPIncomingDelivery_validateTo_badUsername_plusAddress(self):
- """'givemebridges+zh_cn at ...' should raise an SMTPBadRcpt exception."""
- self._setUpRCPTTO('givemebridges+zh_cn')
- self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
-
- def test_SMTPIncomingDelivery_validateTo_badUsername(self):
- """A :class:`server.SMTPIncomingDelivery` which sends a SMTP
- ``RCPT TO: yo.mama at localhost`` should raise a
- ``twisted.mail.smtp.SMTPBadRcpt`` exception.
- """
- self._setUpRCPTTO('yo.mama')
- self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
-
- def test_SMTPIncomingDelivery_validateTo_notOurDomain(self):
- """An SMTP ``RCPT TO: bridges at forealsi.es`` should raise an SMTPBadRcpt
- exception.
- """
- self._setUpRCPTTO('bridges', 'forealsi.es')
- self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
-
- def test_SMTPIncomingDelivery_validateTo_subdomain(self):
- """An SMTP ``RCPT TO: bridges at subdomain.localhost`` should be allowed.
- """
- self._setUpRCPTTO('bridges', 'subdomain.localhost')
- validated = self.delivery.validateTo(self.user)
- self.assertIsInstance(validated, types.FunctionType)
- self.assertIsInstance(validated(), server.SMTPMessage)
-
-
-class SMTPTestCaseMixin(util.TestCaseMixin):
- """Utility methods for use within any subclasses of
- :api:`twisted.trial.unittest.TestCase` which test SMTP.
-
- To use me, subclass :api:`twisted.trial.unittest.TestCase` and mix me into
- the middle of your class inheritance declarations, like so::
-
- class ExampleSMTPTests(SMTPTestCaseMixin, unittest.TestCase):
- pass
-
- and then make certain that your ``TestCase`` subclass has its ``proto``
- and attribute assigned properly::
-
- class ExampleSMTPTests(SMTPTestCaseMixin, unittest.TestCase):
- def setUp(self):
- factory = twisted.mail.smtp.SMTPIncomingServerFactory()
- self.proto = self.factory.buildProtocol(('127.0.0.1', 0))
-
-
- :ivar proto: A :api:`Protocol <twisted.internet.protocol.Protocol>`
- associated with a
- :api:`ServerFactory <twisted.internet.protocol.ServerFactory>`.
- :ivar transport: Anything that implements
- :api:`ITransport <twisted.internet.interfaces.ITransport>`. The default
- is a :api:`twisted.test.proto_helpers.StringTransportWithDisconection`.
- :ivar str smtpFromAddr: The default email address for the server.
- """
-
- proto = None
- transport = proto_helpers.StringTransportWithDisconnection()
- smtpFromAddr = None
-
- def tearDown(self):
- """Cleanup method after each ``test_*`` method runs; removes timed out
- connections on the reactor and clears the :ivar:`transport`.
- """
- self.transport.clear() # Clear bytes from the transport.
-
- for delay in reactor.getDelayedCalls():
- try:
- delay.cancel()
- except (AlreadyCalled, AlreadyCancelled):
- pass
-
- def _buildEmail(self, fromAddr=None, toAddr=None, subject=None, body=None):
- """Creates email text (including headers) for use in an SMTP DATA
- segment. Includes the SMTP DATA EOM command ('.') at the end. If no
- keyword arguments are given, the defaults are fairly sane.
-
- Suitable for testing a :class:`bridgedb.email.server.SMTPIncomingServerFactory`.
-
- :param str fromAddr: An email address for the 'From:' header.
- :param str toAddr: An email address for the 'To:' header.
- :param str subject: The contents of the 'Subject:' header.
- :param str body: The contents of the email body.
- :rtype: str
- :returns: The email text.
- """
- fromAddr = fromAddr if fromAddr else 'testing at localhost'
- toAddr = toAddr if toAddr else self.smtpFromAddr
- subject = subject if subject else 'testing testing one two three'
- body = body if body else 'get bridges'
-
- contents = ['From: %s' % fromAddr,
- 'To: %s' % toAddr,
- 'Subject: %s' % subject,
- '\r\n %s' % body,
- '.'] # SMTP DATA EOM command
- emailText = self.proto.delimiter.join(contents)
- return emailText
-
- def _buildSMTP(self, commands):
- """Format a list of SMTP protocol commands into a string, using the proper
- protocol delimiter.
-
- :param list commands: A list of raw SMTP-protocol command lines.
- :rtype: str
- :returns: The string for sending those **commands**, suitable for
- giving to a :api:`twisted.internet.Protocol.dataReceived` method.
- """
- data = self.proto.delimiter.join(commands) + self.proto.delimiter
- return data
-
- def _test(self, commands, expected, noisy=False):
- """Send the SMTP **commands** to the ``dataReceived`` method of your
- TestCase's protocol (this must be the `proto` attribute of your
- `TestCase`, i.e. this uses ``TestCase.proto.dataReceived``). Next,
- check that the substring which is **expected** to be within the
- server's output matches what was received from :ivar`transport`.
-
- :param list commands: A sequence of raw SMTP command lines. This will
- automatically be passed to :meth:`_buildSMTP`.
- :param str expected: A substring which should occur in the "server"
- output (taken from the :ivar:`transport`).
- :param bool noisy: If ``True``, print the conversation between the
- "client" and the "server" in a nicely formatted manner.
- """
- data = self._buildSMTP(commands)
- self.proto.dataReceived(data)
- recv = self.transport.value()
-
- if noisy:
- client = data.replace('\r\n', '\r\n ')
- server = recv.replace('\r\n', '\r\n\t\t ')
- print('\n CLIENT --------->', '\n %s' % client)
- print('\t\t', '<--------- SERVER', '\n\t\t %s' % server)
-
- self.assertSubstring(expected, recv)
-
-
-class SMTPIncomingServerFactoryTests(SMTPTestCaseMixin, unittest.TestCase):
- """Unittests for :class:`bridgedb.email.server.SMTPIncomingServerFactory`."""
-
- def setUp(self):
- """Set up a localhost SMTPIncomingServerFactory handler incoming SMTP
- connections.
- """
- config = _createConfig()
- context = _createMailServerContext(config)
- factory = server.SMTPIncomingServerFactory()
- factory.setContext(context)
- factory.protocol.timeout = None # Otherwise the reactor gets dirty
-
- self.smtpFromAddr = context.smtpFromAddr # 'bridges at localhost'
- self.proto = factory.buildProtocol(('127.0.0.1', 0))
- self.transport = proto_helpers.StringTransportWithDisconnection()
- self.proto.setTimeout(None)
- # Set the protocol; StringTransportWithDisconnection is a bit janky:
- self.transport.protocol = self.proto
- self.proto.makeConnection(self.transport)
-
- def test_SMTPIncomingServerFactory_HELO_localhost(self):
- """Send 'HELO localhost' to the server's transport."""
- ip = self.transport.getPeer().host
- self._test(['HELO localhost'],
- "Hello %s, nice to meet you" % ip)
-
- def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_localhost(self):
- """Send 'MAIL FROM: human at localhost'."""
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost'],
- "250 Sender address accepted")
-
- def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_gethostname(self):
- """Send 'MAIL FROM: human at hostname' for the local hostname."""
- hostname = socket.gethostname() or "computer"
- self._test(['HELO localhost',
- 'MAIL FROM: testing@%s' % hostname],
- "250 Sender address accepted")
-
- def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_ipaddress(self):
- """Send 'MAIL FROM: human at ipaddr' for the loopback IP address."""
- hostname = 'localhost'
- self._test(['HELO localhost',
- 'MAIL FROM: testing@%s' % hostname],
- "250 Sender address accepted")
-
- def test_SMTPIncomingServerFactory_RCPT_TO_context_smtpFromAddr(self):
- """Send 'RCPT TO:' with the context.smtpFromAddr."""
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % self.smtpFromAddr],
- "250 Recipient address accepted")
-
- def test_SMTPIncomingServerFactory_DATA_blank(self):
- """A DATA command with nothing after it should receive::
- '354 Continue'
- in response.
- """
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % self.smtpFromAddr,
- "DATA"],
- "354 Continue")
-
- def test_SMTPIncomingServerFactory_DATA_get_help(self):
- """A DATA command with ``'get help'`` in the email body should
- receive::
- '250 Delivery in progress'
- in response.
- """
- emailText = self._buildEmail(body="get help")
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % self.smtpFromAddr,
- "DATA", emailText],
- "250 Delivery in progress",
- noisy=True)
-
- def test_SMTPIncomingServerFactory_DATA_get_transport_obfs3(self):
- """A DATA command with ``'get transport obfs3'`` in the email body
- should receive::
- '250 Delivery in progress'
- in response.
- """
- emailText = self._buildEmail(body="get transport obfs3")
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % self.smtpFromAddr,
- "DATA", emailText],
- "250 Delivery in progress",
- noisy=True)
-
- def test_SMTPIncomingServerFactory_DATA_To_bridges_plus_zh_CN(self):
- """Test sending to 'bridges+zh_CN' address for Chinese translations."""
- # TODO: Add tests which use '+' syntax in mailTo in order to test
- # email translations. Do this when some strings have been translated.
- emailTo = list(self.smtpFromAddr.partition('@'))
- emailTo.insert(1, '+zh_CN')
- emailTo = ''.join(emailTo)
- emailText = self._buildEmail(toAddr=emailTo)
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % emailTo,
- "DATA", emailText],
- "250 Delivery in progress",
- noisy=True)
-
- def test_SMTPIncomingServerFactory_DATA_get_bridges_QUIT(self):
- """Test sending 'DATA' with 'get bridges', then sending 'QUIT'."""
- emailText = self._buildEmail()
- self._test(['HELO localhost',
- 'MAIL FROM: testing at localhost',
- 'RCPT TO: %s' % self.smtpFromAddr,
- "DATA", emailText,
- "QUIT"],
- "221 See you later",
- noisy=True)
-
-
-class EmailServerServiceTests(SMTPTestCaseMixin, unittest.TestCase):
- """Unittests for :func:`bridgedb.email.server.addServer`."""
-
- def setUp(self):
- """Create a MailServerContext and EmailDistributor."""
- self.config = _createConfig()
- self.context = _createMailServerContext(self.config)
- self.smtpFromAddr = self.context.smtpFromAddr # 'bridges at localhost'
- self.sched = Unscheduled()
- self.dist = self.context.distributor
-
- def tearDown(self):
- """Kill all connections with fire."""
- if self.transport:
- self.transport.loseConnection()
- super(EmailServerServiceTests, self).tearDown()
- # FIXME: this is definitely not how we're supposed to do this, but it
- # kills the DirtyReactorAggregateErrors.
- reactor.disconnectAll()
- reactor.runUntilCurrent()
-
- def test_addServer(self):
- """Call :func:`bridgedb.email.server.addServer` to test startup."""
- factory = server.addServer(self.config, self.dist)
- factory.timeout = None
- factory.protocol.timeout = None # Or else the reactor gets dirty
-
- self.proto = factory.buildProtocol(('127.0.0.1', 0))
- self.proto.setTimeout(None)
- # Set the transport's protocol, because
- # StringTransportWithDisconnection is a bit janky:
- self.transport.protocol = self.proto
- self.proto.makeConnection(self.transport)
-
- serverHost = socket.gethostname()
- self._test(['HELO %s' % serverHost,
- 'MAIL FROM: testing@%s' % serverHost,
- 'RCPT TO: %s' % self.smtpFromAddr,
- "DATA", self._buildEmail(body="get transport obfs3")],
- "250 Delivery in progress",
- noisy=True)
diff --git a/lib/bridgedb/test/test_email_templates.py b/lib/bridgedb/test/test_email_templates.py
deleted file mode 100644
index 0c74377..0000000
--- a/lib/bridgedb/test/test_email_templates.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.email.templates` module."""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-from io import StringIO
-from gettext import NullTranslations
-
-from twisted.mail.smtp import Address
-from twisted.trial import unittest
-
-from bridgedb.email import templates
-
-
-class EmailTemplatesTests(unittest.TestCase):
- """Unittests for :func:`b.e.templates`."""
-
- def setUp(self):
- self.t = NullTranslations(StringIO(unicode('test')))
- self.client = Address('blackhole at torproject.org')
- self.answer = 'obfs3 1.1.1.1:1111\nobfs3 2.2.2.2:2222'
- # This is the fingerprint of BridgeDB's offline, certification-only
- # GnuPG key. It should be present in any responses to requests for our
- # public keys.
- self.offlineFingerprint = '7B78437015E63DF47BB1270ACBD97AA24E8E472E'
-
- def shouldIncludeCommands(self, text):
- self.assertSubstring('COMMANDs', text)
-
- def shouldIncludeInstructions(self, text):
- self.assertSubstring('Tor Browser', text)
-
- def shouldIncludeBridges(self, text):
- self.assertSubstring(self.answer, text)
- self.assertSubstring('Here are your bridges:', text)
-
- def shouldIncludeGreeting(self, text):
- self.assertSubstring('Hey, blackhole!', text)
-
- def shouldIncludeAutomationNotice(self, text):
- self.assertSubstring('automated message', text)
-
- def shouldIncludeKey(self, text):
- self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', text)
-
- def shouldIncludeFooter(self, text):
- self.assertSubstring('rainbows, unicorns, and sparkles', text)
-
- def test_templates_addCommands(self):
- text = templates.addCommands(self.t)
- self.shouldIncludeCommands(text)
-
- def test_templates_addGreeting(self):
- text = templates.addGreeting(self.t, self.client.local)
- self.shouldIncludeGreeting(text)
-
- def test_templates_addGreeting_noClient(self):
- text = templates.addGreeting(self.t, None)
- self.assertSubstring('Hello, friend!', text)
-
- def test_templates_addGreeting_withWelcome(self):
- text = templates.addGreeting(self.t, self.client.local, welcome=True)
- self.shouldIncludeGreeting(text)
- self.assertSubstring('Welcome to BridgeDB!', text)
-
- def test_templates_addGreeting_trueClient(self):
- text = templates.addGreeting(self.t, True)
- self.assertSubstring('Hey', text)
-
- def test_templates_addGreeting_23Client(self):
- text = templates.addGreeting(self.t, 23)
- self.assertSubstring('Hey', text)
-
- def test_templates_addHowto(self):
- text = templates.addHowto(self.t)
- self.shouldIncludeInstructions(text)
-
- def test_templates_addBridgeAnswer(self):
- text = templates.addBridgeAnswer(self.t, self.answer)
- self.shouldIncludeBridges(text)
-
- def test_templates_addFooter(self):
- text = templates.addFooter(self.t, self.client)
- self.shouldIncludeFooter(text)
-
- def test_templates_buildAnswerMessage(self):
- text = templates.buildAnswerMessage(self.t, self.client, self.answer)
- self.assertSubstring(self.answer, text)
- self.shouldIncludeAutomationNotice(text)
- self.shouldIncludeCommands(text)
- self.shouldIncludeFooter(text)
-
- def test_templates_buildKeyMessage(self):
- text = templates.buildKeyMessage(self.t, self.client)
- self.assertSubstring(self.offlineFingerprint, text)
-
- def test_templates_buildWelcomeText(self):
- text = templates.buildWelcomeText(self.t, self.client)
- self.shouldIncludeGreeting(text)
- self.assertSubstring('Welcome to BridgeDB!', text)
- self.shouldIncludeCommands(text)
- self.shouldIncludeFooter(text)
-
- def test_templates_buildSpamWarning(self):
- text = templates.buildSpamWarning(self.t, self.client)
- self.shouldIncludeGreeting(text)
- self.shouldIncludeAutomationNotice(text)
- self.shouldIncludeFooter(text)
diff --git a/lib/bridgedb/test/test_filters.py b/lib/bridgedb/test/test_filters.py
deleted file mode 100644
index 73e5685..0000000
--- a/lib/bridgedb/test/test_filters.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see included LICENSE for information
-
-"""Tests for :mod:`bridgedb.filters`."""
-
-from __future__ import print_function
-
-import ipaddr
-
-from twisted.trial import unittest
-
-from bridgedb import filters
-from bridgedb.bridges import Bridge
-from bridgedb.bridges import PluggableTransport
-from bridgedb.crypto import getHMACFunc
-
-
-class FiltersTests(unittest.TestCase):
- """Tests for :mod:`bridgedb.filters`."""
-
- def setUp(self):
- """Create a Bridge whose address is 1.1.1.1, orPort is 1111, and
- fingerprint is 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'. Also,
- create an HMAC function whose key is 'plasma'.
- """
- self.bridge = Bridge()
- self.bridge.address = '1.1.1.1'
- self.bridge.orPort = 1111
- self.bridge.fingerprint = 'a' * 40
-
- self.hmac = getHMACFunc('plasma')
-
- def addIPv4VoltronPT(self):
- pt = PluggableTransport('a' * 40, 'voltron', '1.1.1.1', 1111, {})
- self.bridge.transports.append(pt)
-
- def addIPv6VoltronPT(self):
- pt = PluggableTransport('a' * 40, 'voltron', '2006:2222::2222', 1111, {})
- self.bridge.transports.append(pt)
-
- def test_bySubring_1_of_2(self):
- """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
- should be assigned to sub-hashring 1-of-2 (in this case, using a
- particular HMAC key), and therefore filters.bySubring(HMAC, 1, 2)
- should return that Bridge (because it is in the sub-hashring we asked
- for).
- """
- filtre = filters.bySubring(self.hmac, 1, 2)
- self.assertTrue(filtre(self.bridge))
-
- def test_bySubring_2_of_2(self):
- """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
- should be assigned to sub-hashring 1-of-2 (in this case, using a
- particular HMAC key), and therefore filters.bySubring(HMAC, 2, 2)
- should *not* return that Bridge (because it is in sub-hashring 1-of-2
- and we asked for Bridges which are in sub-hashring 2-of-2).
- """
- filtre = filters.bySubring(self.hmac, 2, 2)
- self.assertFalse(filtre(self.bridge))
-
- def test_byFilters_bySubring_byTransport_correct_subhashring_with_transport(self):
- """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
- Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
- should return True.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
- filters.byTransport('voltron')])
- self.assertTrue(filtre(self.bridge))
-
- def test_byFilters_bySubring_byTransport_wrong_subhashring_with_transport(self):
- """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
- Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
- should return False.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
- filters.byTransport('voltron')])
- self.assertFalse(filtre(self.bridge))
-
- def test_byFilters_bySubring_byTransport_correct_subhashring_no_transport(self):
- """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
- Bridge has no transports and is assigned to sub-hashring 1-of-2
- should return False.
- """
- filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
- filters.byTransport('voltron')])
- self.assertFalse(filtre(self.bridge))
-
- def test_byFilters_bySubring_byTransport_wrong_subhashring_no_transport(self):
- """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
- Bridge has no transports and is assigned to sub-hashring 1-of-2
- should return False.
- """
- filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
- filters.byTransport('voltron')])
- self.assertFalse(filtre(self.bridge))
-
- def test_byFilters_no_filters(self):
- self.addIPv4VoltronPT()
- filtre = filters.byFilters([])
- self.assertTrue(filtre(self.bridge))
-
- def test_byIPv_ipv5(self):
- """Calling byIPv(ipVersion=5) should default to filterint by IPv4."""
- filtre = filters.byIPv(5)
- self.assertTrue(filtre(self.bridge))
-
- def test_byIPv4_address(self):
- """A bridge with an IPv4 address for its main orPort address should
- cause filters.byIPv4() to return True.
- """
- self.assertTrue(filters.byIPv4(self.bridge))
-
- def test_byIPv4_orAddress(self):
- """A bridge with an IPv4 address in its orAddresses address should
- cause filters.byIPv4() to return True.
- """
- self.bridge.address = '2006:2222::2222'
- self.bridge.orAddresses = [(ipaddr.IPv4Address('2.2.2.2'), 2222, 4)]
- self.assertTrue(filters.byIPv4(self.bridge))
-
- def test_byIPv4_none(self):
- """A bridge with no IPv4 addresses should cause filters.byIPv4() to
- return False.
- """
- self.bridge.address = ipaddr.IPv6Address('2006:2222::2222')
- self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
- self.assertFalse(filters.byIPv4(self.bridge))
-
- def test_byIPv6_address(self):
- """A bridge with an IPv6 address for its main orPort address should
- cause filters.byIPv6() to return True.
- """
- self.bridge.address = '2006:2222::2222'
- self.assertTrue(filters.byIPv6(self.bridge))
-
- def test_byIPv6_orAddress(self):
- """A bridge with an IPv6 address in its orAddresses address should
- cause filters.byIPv6() to return True.
- """
- self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
- self.assertTrue(filters.byIPv6(self.bridge))
-
- def test_byIPv6_none(self):
- """A bridge with no IPv6 addresses should cause filters.byIPv6() to
- return False.
- """
- self.assertFalse(filters.byIPv6(self.bridge))
-
- def test_byTransport_with_transport_ipv4(self):
- """A bridge with an IPv4 voltron transport should cause
- byTransport('voltron') to return True.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byTransport('voltron')
- self.assertTrue(filtre(self.bridge))
-
- def test_byTransport_with_transport_ipv6(self):
- """A bridge with an IPv6 voltron transport should cause
- byTransport('voltron', ipVersion=6) to return True.
- """
- self.addIPv6VoltronPT()
- filtre = filters.byTransport('voltron', ipVersion=6)
- self.assertTrue(filtre(self.bridge))
-
- def test_byTransport_with_transport_ipv6_filtering_by_ipv4(self):
- """A bridge with an IPv6 voltron transport should cause
- byTransport('voltron') to return True.
- """
- self.addIPv6VoltronPT()
- filtre = filters.byTransport('voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byTransport_no_transports(self):
- """A bridge without any transports should cause
- byTransport('voltron') to return False.
- """
- filtre = filters.byTransport('voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byTransport_vanilla_ipv4(self):
- """byTransport() without namimg a transport to filter by should just
- return the bridge's IPv4 address.
- """
- filtre = filters.byTransport()
- self.assertTrue(filtre(self.bridge))
-
- def test_byTransport_vanilla_ipv6(self):
- """byTranspfort(ipVersion=6) without namimg a transport to filter by
- should just return the bridge's IPv4 address.
- """
- self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
- filtre = filters.byTransport(ipVersion=6)
- self.assertTrue(filtre(self.bridge))
-
- def test_byTransport_wrong_transport(self):
- """A bridge with only a Voltron transport should cause
- byTransport('obfs3') to return False.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byTransport('obfs3')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_no_countryCode_with_transport_ipv4(self):
- """A bridge with an IPv4 voltron transport should cause
- byNotBlockedIn('voltron') to return True (because it calls
- filters.byTransport).
- """
- self.addIPv4VoltronPT()
- filtre = filters.byNotBlockedIn(None, methodname='voltron')
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_no_countryCode_with_transport_ipv6(self):
- """A bridge with an IPv6 voltron transport should cause
- byNotBlockedIn('voltron') to return True (because it calls
- filters.byTransport).
- """
- self.addIPv6VoltronPT()
- filtre = filters.byNotBlockedIn(None, methodname='voltron', ipVersion=6)
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_with_transport_ipv4(self):
- """A bridge with an IPv4 voltron transport should cause
- byNotBlockedIn('voltron') to return True.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byNotBlockedIn('CN', methodname='voltron')
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_with_transport_ipv4_blocked(self):
- """A bridge with an IPv4 voltron transport which is blocked should
- cause byNotBlockedIn('voltron') to return False.
- """
- self.addIPv4VoltronPT()
- self.bridge.setBlockedIn('CN')
- filtre = filters.byNotBlockedIn('CN', methodname='voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_with_transport_ipv6(self):
- """A bridge with an IPv6 voltron transport should cause
- byNotBlockedIn('voltron') to return True.
- """
- self.addIPv6VoltronPT()
- filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_with_transport_ipv4_not_blocked_ipv4(self):
- """A bridge with an IPv6 voltron transport which is not blocked in China
- should cause byNotBlockedIn('cn', 'voltron') to return False, because
- the IP version is wrong.
- """
- self.addIPv6VoltronPT()
- filtre = filters.byNotBlockedIn('cn', 'voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_with_transport_ipv6_blocked(self):
- """A bridge with an IPv6 voltron transport which is blocked should
- cause byNotBlockedIn('voltron') to return False.
- """
- self.addIPv6VoltronPT()
- self.bridge.setBlockedIn('CN')
- filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_no_countryCode_no_transports(self):
- """A bridge without any transports should cause
- byNotBlockedIn('voltron') to return False (because it calls
- filters.byTransport('voltron')).
- """
- filtre = filters.byNotBlockedIn(None, methodname='voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_no_transports(self):
- """A bridge without any transports should cause
- byNotBlockedIn('cn', 'voltron') to return False.
- """
- filtre = filters.byNotBlockedIn('cn', methodname='voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_no_transports_blocked(self):
- """A bridge without any transports which is also blocked should cause
- byNotBlockedIn('voltron') to return False.
- """
- self.bridge.setBlockedIn('cn')
- filtre = filters.byNotBlockedIn('cn', methodname='voltron')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_wrong_transport(self):
- """A bridge with only a Voltron transport should cause
- byNotBlockedIn('obfs3') to return False.
- """
- self.addIPv4VoltronPT()
- filtre = filters.byNotBlockedIn('cn', methodname='obfs3')
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_ipv5(self):
- """Calling byNotBlockedIn([â¦], ipVersion=5) should default to IPv4."""
- self.bridge.setBlockedIn('ru')
- filtre = filters.byNotBlockedIn('cn', ipVersion=5)
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_vanilla_not_blocked(self):
- """Calling byNotBlockedIn('vanilla') should return the IPv4 vanilla
- address, if it is not blocked.
- """
- self.bridge.setBlockedIn('ru')
- filtre = filters.byNotBlockedIn('cn', methodname='vanilla')
- self.assertTrue(filtre(self.bridge))
-
- def test_byNotBlockedIn_vanilla_not_blocked_ipv6(self):
- """Calling byNotBlockedIn('vanilla', ipVersion=6) should not return the
- IPv4 vanilla address, even if it is not blocked, because it has the
- wrong IP version.
- """
- self.bridge.setBlockedIn('ru')
- filtre = filters.byNotBlockedIn('cn', methodname='vanilla', ipVersion=6)
- self.assertFalse(filtre(self.bridge))
-
- def test_byNotBlockedIn_vanilla_blocked(self):
- """Calling byNotBlockedIn('vanilla') should not return the IPv4 vanilla
- address, if it is blocked.
- """
- self.bridge.setBlockedIn('ru')
- filtre = filters.byNotBlockedIn('ru', methodname='vanilla')
- self.assertFalse(filtre(self.bridge))
diff --git a/lib/bridgedb/test/test_geo.py b/lib/bridgedb/test/test_geo.py
deleted file mode 100644
index 5e5547b..0000000
--- a/lib/bridgedb/test/test_geo.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.geo` module."""
-
-import ipaddr
-
-from twisted.trial import unittest
-
-from bridgedb import geo
-
-
-class GeoTests(unittest.TestCase):
- """Unittests for the :mod:`bridgedb.geo` module."""
-
- def setUp(self):
- self._orig_geoip = geo.geoip
- self._orig_geoipv6 = geo.geoipv6
-
- self.ipv4 = ipaddr.IPAddress('38.229.72.16')
- self.ipv6 = ipaddr.IPAddress('2620:0:6b0:b:1a1a:0:26e5:4810')
-
- # WARNING: this is going to fail if the torproject.org A and AAAA
- # records above are reassigned to another host, or are randomly
- # geolocated to somewhere other than where they are currently
- # geolocated (in the US):
- self.expectedCC = 'US'
-
- def tearDown(self):
- geo.geoip = self._orig_geoip
- geo.geoipv6 = self._orig_geoipv6
-
- def test_geo_getCountryCode_ipv4_str(self):
- """Should return None since the IP isn't an ``ipaddr.IPAddress``."""
- self.assertIsNone(geo.getCountryCode(str(self.ipv4)))
-
- def test_geo_getCountryCode_ipv4_no_geoip_loopback(self):
- """Should return None since this IP isn't geolocatable (hopefully ever)."""
- ipv4 = ipaddr.IPAddress('127.0.0.1')
- self.assertIsNone(geo.getCountryCode(ipv4))
-
- def test_geo_getCountryCode_ipv4_class(self):
- """Should return the CC since the IP is an ``ipaddr.IPAddress``."""
- cc = geo.getCountryCode(self.ipv4)
- self.assertIsNotNone(cc)
- self.assertIsInstance(cc, basestring)
- self.assertEqual(len(cc), 2)
- self.assertEqual(cc, self.expectedCC)
-
- def test_geo_getCountryCode_ipv6_str(self):
- """Should return None since the IP isn't an ``ipaddr.IPAddress``."""
- self.assertIsNone(geo.getCountryCode(str(self.ipv6)))
-
- def test_geo_getCountryCode_ipv6_no_geoip_record(self):
- """Should return None since this IP isn't geolocatable (yet)."""
- ipv6 = ipaddr.IPAddress('20::72a:e224:44d8:a606:4115')
- self.assertIsNone(geo.getCountryCode(ipv6))
-
- def test_geo_getCountryCode_ipv6_no_geoip_link_local(self):
- """Should return None since this IP isn't geolocatable (hopefully ever)."""
- ipv6 = ipaddr.IPAddress('ff02::')
- self.assertIsNone(geo.getCountryCode(ipv6))
-
- def test_geo_getCountryCode_ipv6_no_geoip_loopback(self):
- """Should return None since this IP isn't geolocatable (hopefully ever)."""
- ipv6 = ipaddr.IPAddress('::1')
- self.assertIsNone(geo.getCountryCode(ipv6))
-
- def test_geo_getCountryCode_ipv6_class(self):
- """Should return the CC since the IP is an ``ipaddr.IPAddress``."""
- cc = geo.getCountryCode(self.ipv6)
- self.assertIsNotNone(cc)
- self.assertIsInstance(cc, basestring)
- self.assertEqual(len(cc), 2)
- self.assertEqual(cc, self.expectedCC)
-
- def test_geo_getCountryCode_no_geoip(self):
- """When missing the geo.geoip database, getCountryCode() should return
- None.
- """
- geo.geoip = None
- self.assertIsNone(geo.getCountryCode(self.ipv4))
-
- def test_geo_getCountryCode_no_geoipv6(self):
- """When missing the geo.geoipv6 database, getCountryCode() should
- return None.
- """
- geo.geoipv6 = None
- self.assertIsNone(geo.getCountryCode(self.ipv4))
diff --git a/lib/bridgedb/test/test_https.py b/lib/bridgedb/test/test_https.py
deleted file mode 100644
index 1e0c778..0000000
--- a/lib/bridgedb/test/test_https.py
+++ /dev/null
@@ -1,415 +0,0 @@
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: trygve <tor-dev at lists.torproject.org>
-# :copyright: (c) 2014, trygve
-# (c) 2014-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Integration tests for BridgeDB's HTTPS Distributor.
-
-These tests use `mechanize`_ and `BeautifulSoup`_, and require a BridgeDB
-instance to have been started in a separate process. To see how a BridgeDB is
-started for our CI infrastructure from a fresh clone of this repository, see
-the "before_script" section of the `.travis.yml` file in the top level of this
-repository.
-
-.. _mechanize: https://pypi.python.org/pypi/mechanize/
- http://wwwsearch.sourceforge.net/mechanize/
-.. _BeautifulSoup:
- http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html
-"""
-
-from __future__ import print_function
-
-import ipaddr
-import mechanize
-import os
-
-from BeautifulSoup import BeautifulSoup
-
-from twisted.trial import unittest
-from twisted.trial.unittest import FailTest
-from twisted.trial.unittest import SkipTest
-
-from bridgedb.test.util import processExists
-from bridgedb.test.util import getBridgeDBPID
-
-HTTP_ROOT = 'http://127.0.0.1:6788'
-CAPTCHA_RESPONSE = 'Tvx74Pmy'
-
-
-class HTTPTests(unittest.TestCase):
- def setUp(self):
- here = os.getcwd()
- topdir = here.rstrip('_trial_temp')
- self.rundir = os.path.join(topdir, 'run')
- self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
- self.pid = getBridgeDBPID(self.pidfile)
- self.br = None
-
- def tearDown(self):
- self.br = None
-
- def openBrowser(self):
- # use mechanize to open the BridgeDB website in its browser
- self.br = mechanize.Browser()
- # prevents 'HTTP Error 403: request disallowed by robots.txt'
- self.br.set_handle_robots(False)
- self.br.open(HTTP_ROOT)
-
- # -------------- Home/Root page
- self.assertTrue(self.br.viewing_html())
- self.assertEquals(self.br.response().geturl(), HTTP_ROOT)
- self.assertEquals(self.br.title(), "BridgeDB")
- return self.br
-
- def goToOptionsPage(self):
- # check that we are on the root page
- self.assertTrue(self.br.viewing_html())
- self.assertEquals(self.br.response().geturl(), HTTP_ROOT)
-
- # follow the link with the word 'bridges' in it.
- # Could also use: text='bridges'
- # Could also use: url='/options'
- self.br.follow_link(text_regex='bridges')
-
- # ------------- Options
- self.assertEquals(self.br.response().geturl(), HTTP_ROOT + "/options")
- return self.br
-
- def submitOptions(self, transport, ipv6, captchaResponse):
- # check that we are on the options page
- self.assertEquals(self.br.response().geturl(), HTTP_ROOT + "/options")
-
- # At this point, we'd like to be able to set some values in
- # the 'advancedOptions' form. Unfortunately the HTML form
- # does not define a 'name' attribute, so the we have to rely on
- # the fact that this is the only form on the page and will therefore
- # always exist at index 0.
- #br.select_form(name="advancedOptions")
- self.br.select_form(nr=0)
-
- # change the pluggable transport to something else
- self.br.form['transport'] = [transport]
- if ipv6:
- self.br.form['ipv6'] = ['yes']
- self.br.submit()
-
- # ------------- Captcha
- EXPECTED_URL = HTTP_ROOT + "/bridges?transport=%s" % transport
- if ipv6:
- EXPECTED_URL += "&ipv6=yes"
- self.assertEquals(self.br.response().geturl(), EXPECTED_URL)
-
- # As on the previous page, the form does not define a 'name'
- # attribute, forcing us to use the index of the form, i.e. 0
- #self.br.select_form(name="captchaSubmission")
- self.br.select_form(nr=0)
-
- # input the required captcha response. There is only one captcha
- # defined by default, so this should always be accepted. Note this
- # will not be possible to automate if used with a third-party CAPTCHA
- # systems (e.g. reCAPTCHA)
- self.br.form['captcha_response_field'] = captchaResponse
- captcha_response = self.br.submit()
-
- # ------------- Results
- # URL should be the same as last time
- self.assertEquals(self.br.response().geturl(), EXPECTED_URL)
- soup = BeautifulSoup(captcha_response.read())
- return soup
-
- def getBridgeLinesFromSoup(self, soup, fieldsPerBridge):
- """We're looking for something like this in the response::
- <div class="bridge-lines">
- obfs2 175.213.252.207:11125 5c6da7d927460317c6ff5420b75c2d0f431f18dd
- </div>
- """
- bridges = []
- soup = soup.findAll(attrs={'class' : 'bridge-lines'})
- self.assertTrue(soup, "Could not find <div class='bridge-lines'>!")
-
- for portion in soup:
- br_tags = portion.findChildren('br')
- bridge_lines = set(portion.contents).difference(set(br_tags))
- for bridge_line in bridge_lines:
- bridge_line = bridge_line.strip()
- if bridge_line:
- fields = bridge_line.split()
- bridges.append(fields)
-
- self.assertTrue(len(bridges) > 0, "Found no bridge lines in %s" % soup)
-
- for bridge in bridges:
- self.assertEquals(len(bridge), fieldsPerBridge,
- "Expected %d fields in bridge line %s"
- % (fieldsPerBridge, bridge))
- return bridges
-
- def test_get_obfs2_ipv4(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs2'
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=3)
- for bridge in bridges:
- pt = bridge[0]
- self.assertEquals(PT, pt)
-
- def test_get_obfs3_ipv4(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs3'
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=3)
- for bridge in bridges:
- pt = bridge[0]
- self.assertEquals(PT, pt)
-
- def test_get_vanilla_ipv4(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = '0'
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=2)
- for bridge in bridges:
- self.assertTrue(bridge != None)
- addr = bridge[0].rsplit(':', 1)[0]
- self.assertIsInstance(ipaddr.IPAddress(addr), ipaddr.IPv4Address)
-
- def test_get_vanilla_ipv6(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = '0'
- soup = self.submitOptions(transport=PT, ipv6=True,
- captchaResponse=CAPTCHA_RESPONSE)
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=2)
- for bridge in bridges:
- self.assertTrue(bridge != None)
- addr = bridge[0].rsplit(':', 1)[0].strip('[]')
- self.assertIsInstance(ipaddr.IPAddress(addr), ipaddr.IPv6Address)
-
- def test_get_scramblesuit_ipv4(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'scramblesuit'
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=4)
- for bridge in bridges:
- pt = bridge[0]
- password = bridge[-1]
- self.assertEquals(PT, pt)
- self.assertTrue(password.find("password=") != -1,
- "Password field missing expected text")
-
- def test_get_obfs4_ipv4(self):
- """Try asking for obfs4 bridges, and check that the PT arguments in the
- returned bridge lines were space-separated.
-
- This is a regression test for #12932, see
- https://bugs.torproject.org/12932.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs4'
-
- try:
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- except ValueError as error:
- if 'non-disabled' in str(error):
- raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
-
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
- for bridge in bridges:
- pt = bridge[0]
- ptArgs = bridge[-3:]
- self.assertEquals(PT, pt)
- self.assertTrue(len(ptArgs) == 3,
- ("Expected obfs4 bridge line to have 3 PT args, "
- "found %d instead: %s") % (len(ptArgs), ptArgs))
-
- def test_get_obfs4_ipv4_iatmode(self):
- """Ask for obfs4 bridges and check that there is an 'iat-mode' PT
- argument in the bridge lines.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs4'
-
- try:
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- except ValueError as error:
- if 'non-disabled' in str(error):
- raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
-
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
- for bridge in bridges:
- ptArgs = bridge[-3:]
- hasIATMode = False
- for arg in ptArgs:
- if 'iat-mode' in arg:
- hasIATMode = True
-
- self.assertTrue(hasIATMode,
- "obfs4 bridge line is missing 'iat-mode' PT arg.")
-
- def test_get_obfs4_ipv4_publickey(self):
- """Ask for obfs4 bridges and check that there is an 'public-key' PT
- argument in the bridge lines.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs4'
-
- try:
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- except ValueError as error:
- if 'non-disabled' in str(error):
- raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
-
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
- for bridge in bridges:
- ptArgs = bridge[-3:]
- hasPublicKey = False
- for arg in ptArgs:
- if 'public-key' in arg:
- hasPublicKey = True
-
- self.assertTrue(hasPublicKey,
- "obfs4 bridge line is missing 'public-key' PT arg.")
-
- def test_get_obfs4_ipv4_nodeid(self):
- """Ask for obfs4 bridges and check that there is an 'node-id' PT
- argument in the bridge lines.
- """
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- else:
- raise SkipTest(("The mechanize tests cannot handle self-signed "
- "TLS certificates, and thus require opening "
- "another port for running a plaintext HTTP-only "
- "BridgeDB webserver. Because of this, these tests "
- "are only run on CI servers."))
-
- self.openBrowser()
- self.goToOptionsPage()
-
- PT = 'obfs4'
-
- try:
- soup = self.submitOptions(transport=PT, ipv6=False,
- captchaResponse=CAPTCHA_RESPONSE)
- except ValueError as error:
- if 'non-disabled' in str(error):
- raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
-
- bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
- for bridge in bridges:
- ptArgs = bridge[-3:]
- hasNodeID = False
- for arg in ptArgs:
- if 'node-id' in arg:
- hasNodeID = True
-
- self.assertTrue(hasNodeID,
- "obfs4 bridge line is missing 'node-id' PT arg.")
diff --git a/lib/bridgedb/test/test_https_distributor.py b/lib/bridgedb/test/test_https_distributor.py
deleted file mode 100644
index 83f2503..0000000
--- a/lib/bridgedb/test/test_https_distributor.py
+++ /dev/null
@@ -1,405 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Tests for :mod:`bridgedb.https.distributor`."""
-
-from __future__ import print_function
-
-import ipaddr
-import logging
-import random
-
-from twisted.trial import unittest
-
-from bridgedb.Bridges import BridgeRing
-from bridgedb.Bridges import BridgeRingParameters
-from bridgedb.filters import byIPv4
-from bridgedb.filters import byIPv6
-from bridgedb.https import distributor
-from bridgedb.https.request import HTTPSBridgeRequest
-from bridgedb.proxy import ProxySet
-from bridgedb.test.util import randomValidIPv4String
-from bridgedb.test.util import generateFakeBridges
-from bridgedb.test.https_helpers import DummyRequest
-
-logging.disable(50)
-
-
-BRIDGES = generateFakeBridges()
-
-
-class HTTPSDistributorTests(unittest.TestCase):
- """Tests for :class:`HTTPSDistributor`."""
-
- def setUp(self):
- self.key = 'aQpeOFIj8q20s98awfoiq23rpOIjFaqpEWFoij1X'
- self.bridges = BRIDGES
-
- def tearDown(self):
- """Reset all bridge blocks in between test method runs."""
- for bridge in self.bridges:
- bridge._blockedIn = {}
-
- def coinFlip(self):
- return bool(random.getrandbits(1))
-
- def randomClientRequest(self):
- bridgeRequest = HTTPSBridgeRequest(addClientCountryCode=False)
- bridgeRequest.client = randomValidIPv4String()
- bridgeRequest.isValid(True)
- bridgeRequest.generateFilters()
- return bridgeRequest
-
- def randomClientRequestForNotBlockedIn(self, cc):
- httpRequest = DummyRequest([''])
- httpRequest.args.update({'unblocked': [cc]})
- bridgeRequest = self.randomClientRequest()
- bridgeRequest.withoutBlockInCountry(httpRequest)
- bridgeRequest.generateFilters()
- return bridgeRequest
-
- def test_HTTPSDistributor_init_with_proxies(self):
- """The HTTPSDistributor, when initialised with proxies, should add an
- extra hashring for proxy users.
- """
- dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
- self.assertIsNotNone(dist.proxies)
- self.assertGreater(dist.proxySubring, 0)
- self.assertEqual(dist.proxySubring, 4)
- self.assertEqual(dist.totalSubrings, 4)
-
- def test_HTTPSDistributor_bridgesPerResponse_120(self):
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:120]]
- self.assertEqual(dist.bridgesPerResponse(), 3)
-
- def test_HTTPSDistributor_bridgesPerResponse_100(self):
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.hashring.insert(bridge) for bridge in self.bridges[:100]]
- self.assertEqual(dist.bridgesPerResponse(), 3)
-
- def test_HTTPSDistributor_bridgesPerResponse_50(self):
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:60]]
- self.assertEqual(dist.bridgesPerResponse(), 2)
-
- def test_HTTPSDistributor_bridgesPerResponse_15(self):
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:15]]
- self.assertEqual(dist.bridgesPerResponse(), 1)
-
- def test_HTTPSDistributor_bridgesPerResponse_100_max_5(self):
- dist = distributor.HTTPSDistributor(3, self.key)
- dist._bridgesPerResponseMax = 5
- [dist.insert(bridge) for bridge in self.bridges[:100]]
- self.assertEqual(dist.bridgesPerResponse(), 5)
-
- def test_HTTPSDistributor_getSubnet_usingProxy(self):
- """HTTPSDistributor.getSubnet(usingProxy=True) should return a proxy
- group number.
- """
- clientRequest = self.randomClientRequest()
- expectedGroup = (int(ipaddr.IPAddress(clientRequest.client)) % 4) + 1
- subnet = distributor.HTTPSDistributor.getSubnet(clientRequest.client, usingProxy=True)
- self.assertTrue(subnet.startswith('proxy-group-'))
- self.assertEqual(int(subnet[-1]), expectedGroup)
-
- def test_HTTPSDistributor_mapSubnetToSubring_usingProxy(self):
- """HTTPSDistributor.mapSubnetToSubring() when the client was using a
- proxy should map the client to the proxy subhashring.
- """
- dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
- subnet = 'proxy-group-3'
- subring = dist.mapSubnetToSubring(subnet, usingProxy=True)
- self.assertEqual(subring, dist.proxySubring)
-
- def test_HTTPSDistributor_mapSubnetToSubring_with_proxies(self):
- """HTTPSDistributor.mapSubnetToSubring() when the client wasn't using
- a proxy, but the distributor does have some known proxies and a
- proxySubring, should not map the client to the proxy subhashring.
- """
- dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
- # Note that if they were actually from a proxy, their subnet would be
- # something like "proxy-group-3".
- subnet = '15.1.0.0/16'
- subring = dist.mapSubnetToSubring(subnet, usingProxy=False)
- self.assertNotEqual(subring, dist.proxySubring)
-
- def test_HTTPSDistributor_prepopulateRings_with_proxies(self):
- """An HTTPSDistributor with proxies should prepopulate two extra
- subhashrings (one for each of HTTP-Proxy-IPv4 and HTTP-Proxy-IPv6).
- """
- dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
- [dist.insert(bridge) for bridge in self.bridges]
- dist.prepopulateRings()
- self.assertEqual(len(dist.hashring.filterRings), 8)
-
- def test_HTTPSDistributor_prepopulateRings_without_proxies(self):
- """An HTTPSDistributor without proxies should prepopulate
- totalSubrings * 2 subrings.
- """
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges]
- dist.prepopulateRings()
- self.assertEqual(len(dist.hashring.filterRings), 6)
-
- ipv4subrings = []
- ipv6subrings = []
-
- for subringName, (filters, subring) in dist.hashring.filterRings.items():
- if 'IPv4' in subringName:
- ipv6subrings.append(subring)
- if 'IPv6' in subringName:
- ipv6subrings.append(subring)
-
- self.assertEqual(len(ipv4subrings), len(ipv6subrings))
-
- def test_HTTPSDistributor_getBridges_with_blocked_bridges(self):
- dist = distributor.HTTPSDistributor(1, self.key)
- bridges = self.bridges[:]
-
- for bridge in bridges:
- bridge.setBlockedIn('cn')
-
- [dist.insert(bridge) for bridge in bridges]
-
- for _ in range(5):
- clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
- b = dist.getBridges(clientRequest1, 1)
- self.assertEqual(len(b), 0)
-
- clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
- b = dist.getBridges(clientRequest2, 1)
- self.assertEqual(len(b), 3)
-
- def test_HTTPSDistributor_getBridges_with_some_blocked_bridges(self):
- dist = distributor.HTTPSDistributor(1, self.key)
- bridges = self.bridges[:]
-
- blockedCN = []
- blockedIR = []
-
- for bridge in bridges:
- if self.coinFlip():
- bridge.setBlockedIn('cn')
- blockedCN.append(bridge.fingerprint)
-
- if self.coinFlip():
- bridge.setBlockedIn('ir')
- blockedIR.append(bridge.fingerprint)
-
- [dist.insert(bridge) for bridge in bridges]
-
- for _ in range(5):
- clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
- bridges = dist.getBridges(clientRequest1, 1)
- for b in bridges:
- self.assertFalse(b.isBlockedIn('cn'))
- self.assertNotIn(b.fingerprint, blockedCN)
- # The client *should* have gotten some bridges still.
- self.assertGreater(len(bridges), 0)
-
- clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
- bridges = dist.getBridges(clientRequest2, 1)
- for b in bridges:
- self.assertFalse(b.isBlockedIn('ir'))
- self.assertNotIn(b.fingerprint, blockedIR)
- self.assertGreater(len(bridges), 0)
-
- def test_HTTPSDistributor_getBridges_with_varied_blocked_bridges(self):
- dist = distributor.HTTPSDistributor(1, self.key)
- bridges = self.bridges[:]
-
- for bridge in bridges:
- # Pretend that China blocks all vanilla bridges:
- bridge.setBlockedIn('cn', methodname='vanilla')
- # Pretend that China blocks all obfs2:
- bridge.setBlockedIn('cn', methodname='obfs2')
- # Pretend that China blocks some obfs3:
- if self.coinFlip():
- bridge.setBlockedIn('cn', methodname='obfs3')
-
- [dist.insert(bridge) for bridge in bridges]
-
- for i in xrange(5):
- bridgeRequest1 = self.randomClientRequestForNotBlockedIn('cn')
- bridgeRequest1.transports.append('obfs2')
- bridgeRequest1.generateFilters()
- # We shouldn't get any obfs2 bridges, since they're all blocked in
- # China:
- bridges = dist.getBridges(bridgeRequest1, "faketimestamp")
- self.assertEqual(len(bridges), 0)
-
- bridgeRequest2 = self.randomClientRequestForNotBlockedIn('cn')
- bridgeRequest2.transports.append('obfs3')
- bridgeRequest2.generateFilters()
- # We probably will get at least one bridge back! It's pretty
- # unlikely to lose a coin flip 500 times in a row.
- bridges = dist.getBridges(bridgeRequest2, "faketimestamp")
- self.assertGreater(len(bridges), 0)
-
- bridgeRequest3 = self.randomClientRequestForNotBlockedIn('nl')
- bridgeRequest3.transports.append('obfs3')
- bridgeRequest3.generateFilters()
- # We should get bridges, since obfs3 isn't blocked in netherlands:
- bridges = dist.getBridges(bridgeRequest3, "faketimestamp")
- self.assertGreater(len(bridges), 0)
-
- def test_HTTPSDistributor_getBridges_with_proxy_and_nonproxy_users(self):
- """An HTTPSDistributor should give separate bridges to proxy users."""
- proxies = ProxySet(['.'.join(['1.1.1', str(x)]) for x in range(1, 256)])
- dist = distributor.HTTPSDistributor(3, self.key, proxies)
- [dist.insert(bridge) for bridge in self.bridges]
-
- for _ in range(10):
- bridgeRequest1 = self.randomClientRequest()
- bridgeRequest1.client = '.'.join(['1.1.1', str(random.randrange(1, 255))])
-
- bridgeRequest2 = self.randomClientRequest()
- bridgeRequest2.client = '.'.join(['9.9.9', str(random.randrange(1, 255))])
-
- n1 = dist.getBridges(bridgeRequest1, 1)
- n2 = dist.getBridges(bridgeRequest2, 1)
-
- self.assertGreater(len(n1), 0)
- self.assertGreater(len(n2), 0)
-
- for b in n1:
- self.assertNotIn(b, n2)
- for b in n2:
- self.assertNotIn(b, n1)
-
- def test_HTTPSDistributor_getBridges_same_bridges_to_same_client(self):
- """The same client asking for bridges from the HTTPSDistributor
- multiple times in a row should get the same bridges in response each
- time.
- """
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- bridgeRequest = self.randomClientRequest()
- responses = {}
- for i in range(5):
- responses[i] = dist.getBridges(bridgeRequest, 1)
- for i in range(4):
- self.assertItemsEqual(responses[i], responses[i+1])
-
- def test_HTTPSDistributor_getBridges_with_BridgeRingParameters(self):
- param = BridgeRingParameters(needPorts=[(443, 1)])
- dist = distributor.HTTPSDistributor(3, self.key, answerParameters=param)
-
- bridges = self.bridges[:32]
- for b in self.bridges:
- b.orPort = 443
-
- [dist.insert(bridge) for bridge in bridges]
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- for _ in xrange(32):
- bridgeRequest = self.randomClientRequest()
- answer = dist.getBridges(bridgeRequest, 1)
- count = 0
- fingerprints = {}
- for bridge in answer:
- fingerprints[bridge.identity] = 1
- if bridge.orPort == 443:
- count += 1
- self.assertEquals(len(fingerprints), len(answer))
- self.assertGreater(len(fingerprints), 0)
- self.assertTrue(count >= 1)
-
- def test_HTTPSDistributor_getBridges_ipv4_ipv6(self):
- """Asking for bridge addresses which are simultaneously IPv4 and IPv6
- (in that order) should return IPv4 bridges.
- """
- dist = distributor.HTTPSDistributor(1, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- bridgeRequest = self.randomClientRequest()
- bridgeRequest.withIPv4()
- bridgeRequest.filters.append(byIPv6)
- bridgeRequest.generateFilters()
-
- bridges = dist.getBridges(bridgeRequest, 1)
- self.assertEqual(len(bridges), 3)
-
- bridge = random.choice(bridges)
- bridgeLine = bridge.getBridgeLine(bridgeRequest)
- addrport, fingerprint = bridgeLine.split()
- address, port = addrport.rsplit(':', 1)
- address = address.strip('[]')
- self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
- self.assertIsNotNone(byIPv4(random.choice(bridges)))
-
- def test_HTTPSDistributor_getBridges_ipv6_ipv4(self):
- """Asking for bridge addresses which are simultaneously IPv6 and IPv4
- (in that order) should return IPv6 bridges.
- """
- dist = distributor.HTTPSDistributor(1, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- bridgeRequest = self.randomClientRequest()
- bridgeRequest.withIPv6()
- bridgeRequest.generateFilters()
- bridgeRequest.filters.append(byIPv4)
-
- bridges = dist.getBridges(bridgeRequest, 1)
- self.assertEqual(len(bridges), 3)
-
- bridge = random.choice(bridges)
- bridgeLine = bridge.getBridgeLine(bridgeRequest)
- addrport, fingerprint = bridgeLine.split()
- address, port = addrport.rsplit(':', 1)
- address = address.strip('[]')
- self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
- self.assertIsNotNone(byIPv6(random.choice(bridges)))
-
- def test_HTTPSDistributor_getBridges_ipv6(self):
- """A request for IPv6 bridges should return IPv6 bridges."""
- dist = distributor.HTTPSDistributor(3, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- for i in xrange(500):
- bridgeRequest = self.randomClientRequest()
- bridgeRequest.withIPv6()
- bridgeRequest.generateFilters()
-
- bridges = dist.getBridges(bridgeRequest, "faketimestamp")
- self.assertTrue(type(bridges) is list)
- self.assertGreater(len(bridges), 0)
-
- bridge = random.choice(bridges)
- bridgeLine = bridge.getBridgeLine(bridgeRequest)
- addrport, fingerprint = bridgeLine.split()
- address, port = addrport.rsplit(':', 1)
- address = address.strip('[]')
- self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
- self.assertIsNotNone(byIPv6(random.choice(bridges)))
-
- def test_HTTPSDistributor_getBridges_ipv4(self):
- """A request for IPv4 bridges should return IPv4 bridges."""
- dist = distributor.HTTPSDistributor(1, self.key)
- [dist.insert(bridge) for bridge in self.bridges[:250]]
-
- for i in xrange(500):
- bridgeRequest = self.randomClientRequest()
- bridgeRequest.generateFilters()
-
- bridges = dist.getBridges(bridgeRequest, "faketimestamp")
- self.assertTrue(type(bridges) is list)
- self.assertGreater(len(bridges), 0)
-
- bridge = random.choice(bridges)
- bridgeLine = bridge.getBridgeLine(bridgeRequest)
- addrport, fingerprint = bridgeLine.split()
- address, port = addrport.rsplit(':', 1)
- self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
- self.assertIsNotNone(byIPv4(random.choice(bridges)))
diff --git a/lib/bridgedb/test/test_https_request.py b/lib/bridgedb/test/test_https_request.py
deleted file mode 100644
index c39662f..0000000
--- a/lib/bridgedb/test/test_https_request.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-
-from twisted.trial import unittest
-
-from bridgedb.bridgerequest import IRequestBridges
-from bridgedb.https import request
-
-
-class MockRequest(object):
- def __init__(self, args):
- self.args = args
-
-
-class HTTPSBridgeRequestTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.https.request.HTTPSBridgeRequest`."""
-
- def setUp(self):
- """Setup test run."""
- self.request = request.HTTPSBridgeRequest()
-
- def test_HTTPSBridgeRequest_implements_IRequestBridges(self):
- """HTTPSBridgeRequest should implement IRequestBridges interface."""
- self.assertTrue(IRequestBridges.implementedBy(request.HTTPSBridgeRequest))
-
- def test_HTTPSBridgeRequest_withIPversion(self):
- """HTTPSBridgeRequest.withIPversion({ipv6=[â¦]}) should store that the
- client wanted IPv6 bridges."""
- parameters = {'ipv6': 'wooooooooo'}
- self.request.withIPversion(parameters)
-
- def test_HTTPSBridgeRequest_withoutBlockInCountry_IR(self):
- """HTTPSBridgeRequest.withoutBlockInCountry() should add the country CC
- to the ``notBlockedIn`` attribute.
- """
- httprequest = MockRequest({'unblocked': ['ir']})
- self.request.withoutBlockInCountry(httprequest)
- self.assertIn('ir', self.request.notBlockedIn)
-
- def test_HTTPSBridgeRequest_withoutBlockInCountry_US(self):
- """HTTPSBridgeRequest.withoutBlockInCountry() should add the country CC
- to the ``notBlockedIn`` attribute (and not any other countries).
- """
- httprequest = MockRequest({'unblocked': ['us']})
- self.request.withoutBlockInCountry(httprequest)
- self.assertNotIn('ir', self.request.notBlockedIn)
-
- def test_HTTPSBridgeRequest_withoutBlockInCountry_no_addClientCountryCode(self):
- """HTTPSBridgeRequest.withoutBlockInCountry(), when
- addClientCountryCode=False, shouldn't add the client's country code to the
- ``notBlockedIn`` attribute.
- """
- httprequest = MockRequest({'unblocked': ['nl']})
- self.request = request.HTTPSBridgeRequest(addClientCountryCode=False)
- self.request.client = '5.5.5.5'
- self.request.withoutBlockInCountry(httprequest)
- self.assertItemsEqual(['nl'], self.request.notBlockedIn)
-
- def test_HTTPSBridgeRequest_withoutBlockInCountry_bad_params(self):
- """HTTPSBridgeRequest.withoutBlockInCountry() should stop processing if
- the request had a bad "unblocked" parameter.
- """
- httprequest = MockRequest({'unblocked': [3,]})
- self.request.withoutBlockInCountry(httprequest)
- self.assertNotIn('IR', self.request.notBlockedIn)
-
- def test_HTTPSBridgeRequest_withPluggableTransportType(self):
- """HTTPSBridgeRequest.withPluggableTransportType() should add the
- pluggable transport type to the ``transport`` attribute.
- """
- httprequest = MockRequest({'transport': ['huggable_transport']})
- self.request.withPluggableTransportType(httprequest.args)
- self.assertIn('huggable_transport', self.request.transports)
-
- def test_HTTPSBridgeRequest_withPluggableTransportType_bad_param(self):
- """HTTPSBridgeRequest.withPluggableTransportType() should stop
- processing if the request had a bad "unblocked" parameter.
- """
- httprequest = MockRequest({'transport': [3,]})
- self.request.withPluggableTransportType(httprequest.args)
- self.assertNotIn('huggable_transport', self.request.transports)
diff --git a/lib/bridgedb/test/test_https_server.py b/lib/bridgedb/test/test_https_server.py
deleted file mode 100644
index e782135..0000000
--- a/lib/bridgedb/test/test_https_server.py
+++ /dev/null
@@ -1,838 +0,0 @@
-# -*- encoding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2014-2015, Isis Lovecruft
-# (c) 2014-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Unittests for :mod:`bridgedb.https.server`."""
-
-from __future__ import print_function
-
-import logging
-import os
-import shutil
-
-import ipaddr
-
-from BeautifulSoup import BeautifulSoup
-
-from twisted.internet import reactor
-from twisted.internet import task
-from twisted.trial import unittest
-from twisted.web.resource import Resource
-from twisted.web.test import requesthelper
-
-from bridgedb.https import server
-from bridgedb.schedule import ScheduledInterval
-from bridgedb.test.https_helpers import _createConfig
-from bridgedb.test.https_helpers import DummyRequest
-from bridgedb.test.https_helpers import DummyHTTPSDistributor
-from bridgedb.test.util import DummyBridge
-from bridgedb.test.util import DummyMaliciousBridge
-
-
-# For additional logger output for debugging, comment out the following:
-logging.disable(50)
-# and then uncomment the following line:
-#server.logging.getLogger().setLevel(10)
-
-
-class GetClientIPTests(unittest.TestCase):
- """Tests for :func:`bridgedb.https.server.getClientIP`."""
-
- def createRequestWithIPs(self):
- """Set the IP address returned from ``request.getClientIP()`` to
- '3.3.3.3', and the IP address reported in the 'X-Forwarded-For' header
- to '2.2.2.2'.
- """
- request = DummyRequest([''])
- request.headers.update({'x-forwarded-for': '2.2.2.2'})
- # See :api:`twisted.test.requesthelper.DummyRequest.getClientIP`
- request.client = requesthelper.IPv4Address('TCP', '3.3.3.3', 443)
- request.method = b'GET'
- return request
-
- def test_getClientIP_XForwardedFor(self):
- """getClientIP() should return the IP address from the
- 'X-Forwarded-For' header when ``useForwardedHeader=True``.
- """
- request = self.createRequestWithIPs()
- clientIP = server.getClientIP(request, useForwardedHeader=True)
- self.assertEqual(clientIP, '2.2.2.2')
-
- def test_getClientIP_XForwardedFor_bad_ip(self):
- """getClientIP() should return None if the IP address from the
- 'X-Forwarded-For' header is bad/invalid and
- ``useForwardedHeader=True``.
- """
- request = self.createRequestWithIPs()
- request.headers.update({'x-forwarded-for': 'pineapple'})
- clientIP = server.getClientIP(request, useForwardedHeader=True)
- self.assertEqual(clientIP, None)
-
- def test_getClientIP_fromRequest(self):
- """getClientIP() should return the IP address from the request instance
- when ``useForwardedHeader=False``.
- """
- request = self.createRequestWithIPs()
- clientIP = server.getClientIP(request)
- self.assertEqual(clientIP, '3.3.3.3')
-
-
-class ReplaceErrorPageTests(unittest.TestCase):
- """Tests for :func:`bridgedb.https.server.replaceErrorPage`."""
-
- def test_replaceErrorPage(self):
- """``replaceErrorPage`` should return the expected html."""
- exc = Exception("vegan gümmibären")
- errorPage = server.replaceErrorPage(exc)
- self.assertSubstring("Something went wrong", errorPage)
- self.assertNotSubstring("vegan gümmibären", errorPage)
-
-
-class IndexResourceTests(unittest.TestCase):
- """Test for :class:`bridgedb.https.server.IndexResource`."""
-
- def setUp(self):
- self.pagename = ''
- self.indexResource = server.IndexResource()
- self.root = Resource()
- self.root.putChild(self.pagename, self.indexResource)
-
- def test_IndexResource_render_GET(self):
- """renderGet() should return the index page."""
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- page = self.indexResource.render_GET(request)
- self.assertSubstring("add the bridges to Tor Browser", page)
-
- def test_IndexResource_render_GET_lang_ta(self):
- """renderGet() with ?lang=ta should return the index page in Tamil."""
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.addArg('lang', 'ta')
- page = self.indexResource.render_GET(request)
- self.assertSubstring("bridge-à®à®³à¯ Tor Browser-à®à®³à¯", page)
-
-
-class HowtoResourceTests(unittest.TestCase):
- """Test for :class:`bridgedb.https.server.HowtoResource`."""
-
- def setUp(self):
- self.pagename = 'howto.html'
- self.howtoResource = server.HowtoResource()
- self.root = Resource()
- self.root.putChild(self.pagename, self.howtoResource)
-
- def test_HowtoResource_render_GET(self):
- """renderGet() should return the howto page."""
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- page = self.howtoResource.render_GET(request)
- self.assertSubstring("the wizard", page)
-
- def test_HowtoResource_render_GET_lang_ru(self):
- """renderGet() with ?lang=ru should return the howto page in Russian."""
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.addArg('lang', 'ru')
- page = self.howtoResource.render_GET(request)
- self.assertSubstring("ÑледÑйÑе инÑÑÑÑкÑиÑм ÑÑÑановÑика", page)
-
-
-class CaptchaProtectedResourceTests(unittest.TestCase):
- """Tests for :class:`bridgedb.https.server.CaptchaProtectedResource`."""
-
- def setUp(self):
- self.dist = None
- self.sched = None
- self.pagename = b'bridges.html'
- self.root = Resource()
- self.protectedResource = server.BridgesResource(self.dist, self.sched)
- self.captchaResource = server.CaptchaProtectedResource(
- useForwardedHeader=True, protectedResource=self.protectedResource)
- self.root.putChild(self.pagename, self.captchaResource)
-
- def test_render_GET_noCaptcha(self):
- """render_GET() should return a page without a CAPTCHA, which has the
- image alt text.
- """
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- page = self.captchaResource.render_GET(request)
- self.assertSubstring(
- "Your browser is not displaying images properly", page)
-
- def test_render_GET_missingTemplate(self):
- """render_GET() with a missing template should raise an error and
- return the result of replaceErrorPage().
- """
- oldLookup = server.lookup
- try:
- server.lookup = None
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- page = self.captchaResource.render_GET(request)
- errorPage = server.replaceErrorPage(Exception('kablam'))
- self.assertEqual(page, errorPage)
- finally:
- server.lookup = oldLookup
-
- def createRequestWithIPs(self):
- """Set the IP address returned from ``request.getClientIP()`` to
- '3.3.3.3', and the IP address reported in the 'X-Forwarded-For' header
- to '2.2.2.2'.
- """
- request = DummyRequest([self.pagename])
- # Since we do not set ``request.getClientIP`` here like we do in some
- # of the other unittests, an exception would be raised here if
- # ``getBridgesForRequest()`` is unable to get the IP address from this
- # 'X-Forwarded-For' header (because ``ip`` would get set to ``None``).
- request.headers.update({'x-forwarded-for': '2.2.2.2'})
- # See :api:`twisted.test.requesthelper.DummyRequest.getClientIP`
- request.client = requesthelper.IPv4Address('TCP', '3.3.3.3', 443)
- request.method = b'GET'
- return request
-
- def test_getClientIP_XForwardedFor(self):
- """CaptchaProtectedResource.getClientIP() should return the IP address
- from the 'X-Forwarded-For' header when ``useForwardedHeader=True``.
- """
- self.captchaResource.useForwardedHeader = True
- request = self.createRequestWithIPs()
- clientIP = self.captchaResource.getClientIP(request)
- self.assertEqual(clientIP, '2.2.2.2')
-
- def test_getClientIP_fromRequest(self):
- """CaptchaProtectedResource.getClientIP() should return the IP address
- from the request instance when ``useForwardedHeader=False``.
- """
- self.captchaResource.useForwardedHeader = False
- request = self.createRequestWithIPs()
- clientIP = self.captchaResource.getClientIP(request)
- self.assertEqual(clientIP, '3.3.3.3')
-
- def test_render_POST(self):
- """render_POST() with a wrong 'captcha_response_field' should return
- a redirect to the CaptchaProtectedResource page.
- """
- request = DummyRequest([self.pagename])
- request.method = b'POST'
- page = self.captchaResource.render_POST(request)
- self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
- 'refresh')
-
-
-class GimpCaptchaProtectedResourceTests(unittest.TestCase):
- """Tests for :mod:`bridgedb.https.server.GimpCaptchaProtectedResource`."""
-
- def setUp(self):
- """Create a :class:`server.BridgesResource` and protect it with
- a :class:`GimpCaptchaProtectedResource`.
- """
- # Create our cached CAPTCHA directory:
- self.captchaDir = 'captchas'
- if not os.path.isdir(self.captchaDir):
- os.makedirs(self.captchaDir)
-
- # Set up our resources to fake a minimal HTTP(S) server:
- self.pagename = b'captcha.html'
- self.root = Resource()
- # (None, None) is the (distributor, scheduleInterval):
- self.protectedResource = server.BridgesResource(None, None)
- self.captchaResource = server.GimpCaptchaProtectedResource(
- secretKey='42',
- publicKey='23',
- hmacKey='abcdefghijklmnopqrstuvwxyz012345',
- captchaDir='captchas',
- useForwardedHeader=True,
- protectedResource=self.protectedResource)
-
- self.root.putChild(self.pagename, self.captchaResource)
-
- # Set up the basic parts of our faked request:
- self.request = DummyRequest([self.pagename])
-
- def tearDown(self):
- """Delete the cached CAPTCHA directory if it still exists."""
- if os.path.isdir(self.captchaDir):
- shutil.rmtree(self.captchaDir)
-
- def test_extractClientSolution(self):
- """A (challenge, sollution) pair extracted from a request resulting
- from a POST should have the same unmodified (challenge, sollution) as
- the client originally POSTed.
- """
- expectedChallenge = '23232323232323232323'
- expectedResponse = 'awefawefaefawefaewf'
-
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', expectedChallenge)
- self.request.addArg('captcha_response_field', expectedResponse)
-
- response = self.captchaResource.extractClientSolution(self.request)
- (challenge, response) = response
- self.assertEqual(challenge, expectedChallenge)
- self.assertEqual(response, expectedResponse)
-
- def test_checkSolution(self):
- """checkSolution() should return False is the solution is invalid."""
- expectedChallenge = '23232323232323232323'
- expectedResponse = 'awefawefaefawefaewf'
-
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', expectedChallenge)
- self.request.addArg('captcha_response_field', expectedResponse)
-
- valid = self.captchaResource.checkSolution(self.request)
- self.assertFalse(valid)
-
- def test_getCaptchaImage(self):
- """Retrieving a (captcha, challenge) pair with an empty captchaDir
- should return None for both of the (captcha, challenge) strings.
- """
- self.request.method = b'GET'
- response = self.captchaResource.getCaptchaImage(self.request)
- (image, challenge) = response
- # Because we created the directory, there weren't any CAPTCHAs to
- # retrieve from it:
- self.assertIs(image, None)
- self.assertIs(challenge, None)
-
- def test_getCaptchaImage_noCaptchaDir(self):
- """Retrieving a (captcha, challenge) with an missing captchaDir should
- raise a bridgedb.captcha.GimpCaptchaError.
- """
- shutil.rmtree(self.captchaDir)
- self.request.method = b'GET'
- self.assertRaises(server.captcha.GimpCaptchaError,
- self.captchaResource.getCaptchaImage, self.request)
-
- def test_render_GET_missingTemplate(self):
- """render_GET() with a missing template should raise an error and
- return the result of replaceErrorPage().
- """
- oldLookup = server.lookup
- try:
- server.lookup = None
- self.request.method = b'GET'
- page = self.captchaResource.render_GET(self.request)
- errorPage = server.replaceErrorPage(Exception('kablam'))
- self.assertEqual(page, errorPage)
- finally:
- server.lookup = oldLookup
-
- def test_render_POST_blankFields(self):
- """render_POST() with a blank 'captcha_response_field' should return
- a redirect to the CaptchaProtectedResource page.
- """
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', '')
- self.request.addArg('captcha_response_field', '')
-
- page = self.captchaResource.render_POST(self.request)
- self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
- 'refresh')
-
- def test_render_POST_wrongSolution(self):
- """render_POST() with a wrong 'captcha_response_field' should return
- a redirect to the CaptchaProtectedResource page.
- """
- expectedChallenge = '23232323232323232323'
- expectedResponse = 'awefawefaefawefaewf'
-
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', expectedChallenge)
- self.request.addArg('captcha_response_field', expectedResponse)
-
- page = self.captchaResource.render_POST(self.request)
- self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
- 'refresh')
-
-
-class ReCaptchaProtectedResourceTests(unittest.TestCase):
- """Tests for :mod:`bridgedb.https.server.ReCaptchaProtectedResource`."""
-
- def setUp(self):
- """Create a :class:`server.BridgesResource` and protect it with
- a :class:`ReCaptchaProtectedResource`.
- """
- self.timeout = 10.0 # Can't take longer than that, right?
- # Set up our resources to fake a minimal HTTP(S) server:
- self.pagename = b'captcha.html'
- self.root = Resource()
- # (None, None) is the (distributor, scheduleInterval):
- self.protectedResource = server.BridgesResource(None, None)
- self.captchaResource = server.ReCaptchaProtectedResource(
- publicKey='23',
- secretKey='42',
- remoteIP='111.111.111.111',
- useForwardedHeader=True,
- protectedResource=self.protectedResource)
-
- self.root.putChild(self.pagename, self.captchaResource)
-
- # Set up the basic parts of our faked request:
- self.request = DummyRequest([self.pagename])
-
- def tearDown(self):
- """Cleanup method for removing timed out connections on the reactor.
-
- This seems to be the solution for the dirty reactor due to
- ``DelayedCall``s which is mentioned at the beginning of this
- file. There doesn't seem to be any documentation anywhere which
- proposes this solution, although this seems to solve the problem.
- """
- for delay in reactor.getDelayedCalls():
- try:
- delay.cancel()
- except (AlreadyCalled, AlreadyCancelled):
- pass
-
- def test_renderDeferred_invalid(self):
- """:meth:`_renderDeferred` should redirect a ``Request`` (after the
- CAPTCHA was NOT xsuccessfully solved) which results from a
- ``Deferred``'s callback.
- """
- self.request.method = b'POST'
-
- def testCB(request):
- """Check the ``Request`` returned from ``_renderDeferred``."""
- self.assertIsInstance(request, DummyRequest)
- soup = BeautifulSoup(b''.join(request.written)).find('meta')['http-equiv']
- self.assertEqual(soup, 'refresh')
-
- d = task.deferLater(reactor, 0, lambda x: x, (False, self.request))
- d.addCallback(self.captchaResource._renderDeferred)
- d.addCallback(testCB)
- return d
-
- def test_renderDeferred_valid(self):
- """:meth:`_renderDeferred` should correctly render a ``Request`` (after
- the CAPTCHA has been successfully solved) which results from a
- ``Deferred``'s callback.
- """
- self.request.method = b'POST'
-
- def testCB(request):
- """Check the ``Request`` returned from ``_renderDeferred``."""
- self.assertIsInstance(request, DummyRequest)
- html = b''.join(request.written)
- self.assertSubstring('Uh oh, spaghettios!', html)
-
- d = task.deferLater(reactor, 0, lambda x: x, (True, self.request))
- d.addCallback(self.captchaResource._renderDeferred)
- d.addCallback(testCB)
- return d
-
- def test_renderDeferred_nontuple(self):
- """:meth:`_renderDeferred` should correctly render a ``Request`` (after
- the CAPTCHA has been successfully solved) which results from a
- ``Deferred``'s callback.
- """
- self.request.method = b'POST'
-
- def testCB(request):
- """Check the ``Request`` returned from ``_renderDeferred``."""
- self.assertIs(request, None)
-
- d = task.deferLater(reactor, 0, lambda x: x, (self.request))
- d.addCallback(self.captchaResource._renderDeferred)
- d.addCallback(testCB)
- return d
-
- def test_checkSolution_blankFields(self):
- """:meth:`server.ReCaptchaProtectedResource.checkSolution` should
- return a redirect if is the solution field is blank.
- """
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', '')
- self.request.addArg('captcha_response_field', '')
-
- self.assertEqual((False, self.request),
- self.successResultOf(
- self.captchaResource.checkSolution(self.request)))
-
- def test_getRemoteIP_useRandomIP(self):
- """Check that removing our remoteip setting produces a random IP."""
- self.captchaResource.remoteIP = None
- ip = self.captchaResource.getRemoteIP()
- realishIP = ipaddr.IPv4Address(ip).compressed
- self.assertTrue(realishIP)
- self.assertNotEquals(realishIP, '111.111.111.111')
-
- def test_getRemoteIP_useConfiguredIP(self):
- """Check that our remoteip setting is used if configured."""
- ip = self.captchaResource.getRemoteIP()
- realishIP = ipaddr.IPv4Address(ip).compressed
- self.assertTrue(realishIP)
- self.assertEquals(realishIP, '111.111.111.111')
-
- def test_render_GET_missingTemplate(self):
- """render_GET() with a missing template should raise an error and
- return the result of replaceErrorPage().
- """
- oldLookup = server.lookup
- try:
- server.lookup = None
- self.request.method = b'GET'
- page = self.captchaResource.render_GET(self.request)
- errorPage = server.replaceErrorPage(Exception('kablam'))
- self.assertEqual(page, errorPage)
- finally:
- server.lookup = oldLookup
-
- def test_render_POST_blankFields(self):
- """render_POST() with a blank 'captcha_response_field' should return
- a redirect to the CaptchaProtectedResource page.
- """
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', '')
- self.request.addArg('captcha_response_field', '')
-
- page = self.captchaResource.render_POST(self.request)
- self.assertEqual(page, server.NOT_DONE_YET)
-
- def test_render_POST_wrongSolution(self):
- """render_POST() with a wrong 'captcha_response_field' should return
- a redirect to the CaptchaProtectedResource page.
- """
- expectedChallenge = '23232323232323232323'
- expectedResponse = 'awefawefaefawefaewf'
-
- self.request.method = b'POST'
- self.request.addArg('captcha_challenge_field', expectedChallenge)
- self.request.addArg('captcha_response_field', expectedResponse)
-
- page = self.captchaResource.render_POST(self.request)
- self.assertEqual(page, server.NOT_DONE_YET)
-
-
-class BridgesResourceTests(unittest.TestCase):
- """Tests for :class:`https.server.BridgesResource`."""
-
- def setUp(self):
- """Set up our resources to fake a minimal HTTP(S) server."""
- self.pagename = b'bridges.html'
- self.root = Resource()
-
- self.dist = DummyHTTPSDistributor()
- self.sched = ScheduledInterval(1, 'hour')
- self.nBridgesPerRequest = 2
-
- def useBenignBridges(self):
- self.dist._bridge_class = DummyBridge
- self.bridgesResource = server.BridgesResource(
- self.dist, self.sched, N=self.nBridgesPerRequest,
- includeFingerprints=True)
- self.root.putChild(self.pagename, self.bridgesResource)
-
- def useMaliciousBridges(self):
- self.dist._bridge_class = DummyMaliciousBridge
- self.bridgesResource = server.BridgesResource(
- self.dist, self.sched, N=self.nBridgesPerRequest,
- includeFingerprints=True)
- self.root.putChild(self.pagename, self.bridgesResource)
-
- def parseBridgesFromHTMLPage(self, page):
- """Utility to pull the bridge lines out of an HTML response page.
-
- :param str page: A rendered HTML page, as a string.
- :raises: Any error which might occur.
- :rtype: list
- :returns: A list of the bridge lines contained on the **page**.
- """
- # The bridge lines are contained in a <div class='bridges'> tag:
- soup = BeautifulSoup(page)
- well = soup.find('div', {'class': 'bridge-lines'})
- content = well.renderContents().strip()
- lines = content.splitlines()
-
- bridges = []
- for line in lines:
- bridgelines = line.split('<br />')
- for bridge in bridgelines:
- if bridge: # It still could be an empty string at this point
- bridges.append(bridge)
-
- return bridges
-
- def test_render_GET_malicious_newlines(self):
- """Test rendering a request when the some of the bridges returned have
- malicious (HTML, Javascript, etc., in their) PT arguments.
- """
- self.useMaliciousBridges()
-
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.getClientIP = lambda: '1.1.1.1'
-
- page = self.bridgesResource.render(request)
- self.assertTrue(
- 'bad=Bridge 6.6.6.6:6666 0123456789abcdef0123456789abcdef01234567' in str(page),
- "Newlines in bridge lines should be removed.")
-
- def test_render_GET_malicious_returnchar(self):
- """Test rendering a request when the some of the bridges returned have
- malicious (HTML, Javascript, etc., in their) PT arguments.
- """
- self.useMaliciousBridges()
-
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.getClientIP = lambda: '1.1.1.1'
-
- page = self.bridgesResource.render(request)
- self.assertTrue(
- 'eww=Bridge 1.2.3.4:1234' in str(page),
- "Return characters in bridge lines should be removed.")
-
- def test_render_GET_malicious_javascript(self):
- """Test rendering a request when the some of the bridges returned have
- malicious (HTML, Javascript, etc., in their) PT arguments.
- """
- self.useMaliciousBridges()
-
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.getClientIP = lambda: '1.1.1.1'
-
- page = self.bridgesResource.render(request)
- self.assertTrue(
- "evil=<script>alert('fuuuu');</script>" in str(page),
- ("The characters &, <, >, ', and \" in bridge lines should be "
- "replaced with their corresponding HTML special characters."))
-
- def test_renderAnswer_GET_textplain_malicious(self):
- """If the request format specifies 'plain', we should return content
- with mimetype 'text/plain' and ASCII control characters replaced.
- """
- self.useMaliciousBridges()
-
- request = DummyRequest([self.pagename])
- request.args.update({'format': ['plain']})
- request.getClientIP = lambda: '4.4.4.4'
- request.method = b'GET'
-
- page = self.bridgesResource.render(request)
- self.assertTrue("html" not in str(page))
- self.assertTrue(
- 'eww=Bridge 1.2.3.4:1234' in str(page),
- "Return characters in bridge lines should be removed.")
- self.assertTrue(
- 'bad=Bridge 6.6.6.6:6666' in str(page),
- "Newlines in bridge lines should be removed.")
-
- def test_render_GET_vanilla(self):
- """Test rendering a request for normal, vanilla bridges."""
- self.useBenignBridges()
-
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- request.getClientIP = lambda: '1.1.1.1'
-
- page = self.bridgesResource.render(request)
-
- # The response should explain how to use the bridge lines:
- self.assertTrue("To enter bridges into Tor Browser" in str(page))
-
- for b in self.parseBridgesFromHTMLPage(page):
- # Check that each bridge line had the expected number of fields:
- fields = b.split(' ')
- self.assertEqual(len(fields), 2)
-
- # Check that the IP and port seem okay:
- ip, port = fields[0].rsplit(':')
- self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
- self.assertIsInstance(int(port), int)
- self.assertGreater(int(port), 0)
- self.assertLessEqual(int(port), 65535)
-
- def test_render_GET_XForwardedFor(self):
- """The client's IP address should be obtainable from the
- 'X-Forwarded-For' header in the request.
- """
- self.useBenignBridges()
-
- self.bridgesResource.useForwardedHeader = True
- request = DummyRequest([self.pagename])
- request.method = b'GET'
- # Since we do not set ``request.getClientIP`` here like we do in some
- # of the other unittests, an exception would be raised here if
- # ``getBridgesForRequest()`` is unable to get the IP address from this
- # 'X-Forwarded-For' header (because ``ip`` would get set to ``None``).
- request.headers.update({'x-forwarded-for': '2.2.2.2'})
-
- page = self.bridgesResource.render(request)
- self.bridgesResource.useForwardedHeader = False # Reset it
-
- # The response should explain how to use the bridge lines:
- self.assertTrue("To enter bridges into Tor Browser" in str(page))
-
- def test_render_GET_RTLlang(self):
- """Test rendering a request for plain bridges in Arabic."""
- self.useBenignBridges()
-
- request = DummyRequest([b"bridges?transport=obfs3"])
- request.method = b'GET'
- request.getClientIP = lambda: '3.3.3.3'
- # For some strange reason, the 'Accept-Language' value *should not* be
- # a list, unlike all the other headers and argsâ¦
- request.headers.update({'accept-language': 'ar,en,en_US,'})
-
- page = self.bridgesResource.render(request)
- self.assertSubstring("direction: rtl", page)
- self.assertSubstring(
- # "I need an alternative way to get bridges!"
- "Ø£Øتاج Ø¥ÙÙ ÙسÙÙØ© بدÙÙØ© ÙÙØصÙ٠عÙÙ bridges", page)
-
- for bridgeLine in self.parseBridgesFromHTMLPage(page):
- # Check that each bridge line had the expected number of fields:
- bridgeLine = bridgeLine.split(' ')
- self.assertEqual(len(bridgeLine), 2)
-
- def test_render_GET_RTLlang_obfs3(self):
- """Test rendering a request for obfs3 bridges in Farsi."""
- self.useBenignBridges()
-
- request = DummyRequest([b"bridges?transport=obfs3"])
- request.method = b'GET'
- request.getClientIP = lambda: '3.3.3.3'
- request.headers.update({'accept-language': 'fa,en,en_US,'})
- # We actually have to set the request args manually when using a
- # DummyRequest:
- request.args.update({'transport': ['obfs3']})
-
- page = self.bridgesResource.render(request)
- self.assertSubstring("direction: rtl", page)
- self.assertSubstring(
- # "How to use the above bridge lines" (since there should be
- # bridges in this response, we don't tell them about alternative
- # mechanisms for getting bridges)
- "ÚÚ¯ÙÙÚ¯Û Ø§Ø² Ù¾ÙâÙØ§Û Ø®Ùد استÙاد٠کÙÛد", page)
-
- for bridgeLine in self.parseBridgesFromHTMLPage(page):
- # Check that each bridge line had the expected number of fields:
- bridgeLine = bridgeLine.split(' ')
- self.assertEqual(len(bridgeLine), 3)
- self.assertEqual(bridgeLine[0], 'obfs3')
-
- # Check that the IP and port seem okay:
- ip, port = bridgeLine[1].rsplit(':')
- self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
- self.assertIsInstance(int(port), int)
- self.assertGreater(int(port), 0)
- self.assertLessEqual(int(port), 65535)
-
- def test_renderAnswer_textplain(self):
- """If the request format specifies 'plain', we should return content
- with mimetype 'text/plain'.
- """
- self.useBenignBridges()
-
- request = DummyRequest([self.pagename])
- request.args.update({'format': ['plain']})
- request.getClientIP = lambda: '4.4.4.4'
- request.method = b'GET'
-
- page = self.bridgesResource.render(request)
- self.assertTrue("html" not in str(page))
-
- # We just need to strip and split it because it looks like:
- #
- # 94.235.85.233:9492 0d9d0547c3471cddc473f7288a6abfb54562dc06
- # 255.225.204.145:9511 1fb89d618b3a12afe3529fd072127ea08fb50466
- #
- # (Yes, there are two leading spaces at the beginning of each line)
- #
- bridgeLines = [line.strip() for line in page.strip().split('\n')]
-
- for bridgeLine in bridgeLines:
- bridgeLine = bridgeLine.split(' ')
- self.assertEqual(len(bridgeLine), 2)
-
- # Check that the IP and port seem okay:
- ip, port = bridgeLine[0].rsplit(':')
- self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
- self.assertIsInstance(int(port), int)
- self.assertGreater(int(port), 0)
- self.assertLessEqual(int(port), 65535)
-
-
-class OptionsResourceTests(unittest.TestCase):
- """Tests for :class:`bridgedb.https.server.OptionsResource`."""
-
- def setUp(self):
- """Create a :class:`server.OptionsResource`."""
- # Set up our resources to fake a minimal HTTP(S) server:
- self.pagename = b'options.html'
- self.root = Resource()
- self.optionsResource = server.OptionsResource()
- self.root.putChild(self.pagename, self.optionsResource)
-
- def test_render_GET_RTLlang(self):
- """Test rendering a request for obfs3 bridges in Hebrew."""
- request = DummyRequest(["bridges?transport=obfs3"])
- request.method = b'GET'
- request.getClientIP = lambda: '3.3.3.3'
- request.headers.update({'accept-language': 'he'})
- # We actually have to set the request args manually when using a
- # DummyRequest:
- request.args.update({'transport': ['obfs2']})
-
- page = self.optionsResource.render(request)
- self.assertSubstring("direction: rtl", page)
- self.assertSubstring("××× ×שר××?", page)
-
-
-class HTTPSServerServiceTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.email.server.addWebServer`."""
-
- def setUp(self):
- """Create a config and an HTTPSDistributor."""
- self.config = _createConfig()
- self.distributor = DummyHTTPSDistributor()
-
- def tearDown(self):
- """Cleanup method after each ``test_*`` method runs; removes timed out
- connections on the reactor and clears the :ivar:`transport`.
-
- Basically, kill all connections with fire.
- """
- for delay in reactor.getDelayedCalls():
- try:
- delay.cancel()
- except (AlreadyCalled, AlreadyCancelled):
- pass
-
- # FIXME: this is definitely not how we're supposed to do this, but it
- # kills the DirtyReactorAggregateErrors.
- reactor.disconnectAll()
- reactor.runUntilCurrent()
-
- def test_addWebServer_GIMP_CAPTCHA_ENABLED(self):
- """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
- server.addWebServer(self.config, self.distributor)
-
- def test_addWebServer_RECAPTCHA_ENABLED(self):
- """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
- config = self.config
- config.RECAPTCHA_ENABLED = True
- server.addWebServer(config, self.distributor)
-
- def test_addWebServer_no_captchas(self):
- """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
- config = self.config
- config.GIMP_CAPTCHA_ENABLED = False
- server.addWebServer(config, self.distributor)
-
- def test_addWebServer_no_HTTPS_ROTATION_PERIOD(self):
- """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
- config = self.config
- config.HTTPS_ROTATION_PERIOD = None
- server.addWebServer(config, self.distributor)
diff --git a/lib/bridgedb/test/test_interfaces.py b/lib/bridgedb/test/test_interfaces.py
deleted file mode 100644
index 8a78291..0000000
--- a/lib/bridgedb/test/test_interfaces.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-# ____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-# ____________________________________________________________________________
-
-"""Unittests for :mod:`bridgedb.interfaces`."""
-
-from twisted.trial import unittest
-
-from bridgedb import interfaces
-
-
-class DummyNamedOtherThing(interfaces.Named):
- def __init__(self):
- self.name = "hipster"
-
-
-class DummyNamedThing(interfaces.Named):
- def __init__(self):
- self.whatever = DummyNamedOtherThing()
- self.name = "original"
-
-
-class NamedTests(unittest.TestCase):
- """Tests for :class:`bridgedb.interfaces.Named`."""
-
- def test_Named_init(self):
- """Initializing a Named() object should set its name to ''."""
- named = interfaces.Named()
- self.assertEqual(named.name, '')
-
- def test_Named_name(self):
- """For a Named object A without any other Named objects which have
- object A as an attribute, should just have its name set to whatever
- it was set to.
- """
- named = DummyNamedOtherThing()
- self.assertEqual(named.name, "hipster")
-
- def test_Named_with_named_object_for_attribute(self):
- """For a Named object A which has another Named object B as an
- attribute, object A should just have its name set to whatever
- it was set to, and object B should have its name set to object A's
- name plus whatever object B's name was set to.
- """
- named = DummyNamedThing()
- self.assertEqual(named.name, "original")
- self.assertEqual(named.whatever.name, "original hipster")
diff --git a/lib/bridgedb/test/test_parse_addr.py b/lib/bridgedb/test/test_parse_addr.py
deleted file mode 100644
index 4659efb..0000000
--- a/lib/bridgedb/test/test_parse_addr.py
+++ /dev/null
@@ -1,749 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.parse.addr` module.
-"""
-
-from __future__ import absolute_import
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import ipaddr
-import random
-
-from twisted.python import log
-from twisted.trial import unittest
-
-from bridgedb.parse import addr
-
-
-IP4LinkLocal = "169.254.0.0"
-IP6LinkLocal = "fe80::1234"
-IP4Loopback = "127.0.0.0"
-IP4Localhost = "127.0.0.1"
-IP6Localhost = "::1"
-IP4LimitedBroadcast = "255.255.255.0"
-IP4Multicast_224 = "224.0.0.1"
-IP4Multicast_239 = "239.0.0.1"
-IP4Unspecified = "0.0.0.0"
-IP6Unspecified = "::"
-IP4DefaultRoute = "0.0.0.0"
-IP6DefaultRoute = "::"
-IP4ReservedRFC1918_10 = "10.0.0.0"
-IP4ReservedRFC1918_172_16 = "172.16.0.0"
-IP4ReservedRFC1918_192_168 = "192.168.0.0"
-IP4ReservedRFC1700 = "240.0.0.0"
-IP6UniqueLocal = "fc00::"
-IP6SiteLocal = "fec0::"
-
-
-class CanonicalizeEmailDomainTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.canonicalizeEmailDomain`."""
-
- def test_nonDict(self):
- """Using a non-dict domainmap as a parameter to
- canonicalizeEmailDomain() should log an AttributeError and then raise
- an UnsupportedDomain error.
- """
- domainmap = 'example.com'
- domain = 'fubar.com'
- self.assertRaises(addr.UnsupportedDomain,
- addr.canonicalizeEmailDomain,
- domain, domainmap)
-
- def test_notPermitted(self):
- """A domain not in the domainmap of allowed domains should raise an
- UnsupportedDomain error.
- """
- domainmap = {'foo.example.com': 'example.com'}
- domain = 'bar.example.com'
- self.assertRaises(addr.UnsupportedDomain,
- addr.canonicalizeEmailDomain,
- domain, domainmap)
-
- def test_permitted(self):
- """A domain in the domainmap of allowed domains should return the
- canonical domain.
- """
- domainmap = {'foo.example.com': 'example.com'}
- domain = 'foo.example.com'
- canonical = addr.canonicalizeEmailDomain(domain, domainmap)
- self.assertEquals(canonical, 'example.com')
-
-
-class ExtractEmailAddressTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.extractEmailAddress`."""
-
- def test_23(self):
- """The email address int(23) should raise a BadEmail error."""
- self.assertRaises(addr.BadEmail,
- addr.extractEmailAddress,
- int(23))
-
- def test_lessThanChars(self):
- """The email address 'Alice <alice at riseup.net>' should return
- ('alice', 'riseup.net').
- """
- local, domain = addr.extractEmailAddress('Alice <alice at riseup.net>')
- self.assertEqual(local, 'alice')
- self.assertEqual(domain, 'riseup.net')
-
- def test_extraLessThanChars(self):
- """The email address 'Mallory <mal<lory at riseup.net>' should return
- ('lory', 'riseup.net')
- """
- local, domain = addr.extractEmailAddress('Mallory <mal<lory at riseup.net>')
- self.assertEqual(local, 'lory')
- self.assertEqual(domain, 'riseup.net')
-
- def test_extraLessAndGreaterThanChars(self):
- """The email address 'Mallory <mal><>>lory at riseup.net>' should raise a
- BadEmail error.
- """
- self.assertRaises(addr.BadEmail,
- addr.extractEmailAddress,
- 'Mallory <mal><>>lory at riseup.net>')
-
- def test_extraAppendedEmailAddress(self):
- """The email address 'Mallory <mallory at riseup.net><mallory at gmail.com>'
- should use the last address.
- """
- local, domain = addr.extractEmailAddress(
- 'Mallory <mallory at riseup.net><mallory at gmail.com>')
- self.assertEqual(local, 'mallory')
- self.assertEqual(domain, 'gmail.com')
-
-
-class NormalizeEmailTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.normalizeEmail`."""
-
- def test_permitted(self):
- """A valid email address from a permitted domain should return
- unchanged.
- """
- domainrules = {}
- domainmap = {'foo.example.com': 'example.com'}
- emailaddr = 'alice at foo.example.com'
- normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
- self.assertEqual(emailaddr, normalized)
-
- def test_notPermitted(self):
- """A valid email address from a non-permitted domain should raise an
- UnsupportedDomain error.
- """
- domainrules = {}
- domainmap = {'bar.example.com': 'example.com'}
- emailaddr = 'Alice <alice at foo.example.com>'
- self.assertRaises(addr.UnsupportedDomain,
- addr.normalizeEmail,
- emailaddr, domainmap, domainrules)
-
- def test_ignoreDots(self):
- """A valid email address with a '.' should remove the '.' if
- 'ignore_dots' is in domainrules.
- """
- domainrules = {'example.com': 'ignore_dots'}
- domainmap = {'foo.example.com': 'example.com'}
- emailaddr = 'alice.bridges at foo.example.com'
- normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
- self.assertEqual('alicebridges at foo.example.com', normalized)
-
- def test_ignorePlus(self):
- """A valid email address with a '+' and some extra stuff, from a
- permitted domain, should remove the '+' stuff if 'ignore_plus' is
- enabled.
- """
- domainrules = {}
- domainmap = {'foo.example.com': 'example.com'}
- emailaddr = 'alice+bridges at foo.example.com'
- normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
- self.assertEqual('alice at foo.example.com', normalized)
-
- def test_dontIgnorePlus(self):
- """A valid email address with a '+' and some extra stuff, from a
- permitted domain, should return unchanged if 'ignore_plus' is disabled.
- """
- domainrules = {}
- domainmap = {'foo.example.com': 'example.com'}
- emailaddr = 'alice+bridges at foo.example.com'
- normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules,
- ignorePlus=False)
- self.assertEqual(emailaddr, normalized)
-
-
-class ParseAddrIsIPAddressTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.isIPAddress`.
-
- .. note:: All of the ``test_isIPAddress_IP*`` methods in this class should
- get ``False`` as the **result** returned from :func:`addr.isIPAddress`,
- because all the ``IP*`` constants defined above are invalid addresses
- according to :func:`addr.isValidIP`.
- """
-
- def test_isIPAddress_randomIP4(self):
- """Test :func:`addr.isIPAddress` with a random IPv4 address.
-
- This test asserts that the returned IP address is not None (because
- the IP being tested is random, it *could* randomly be an invalid IP
- address and thus :func:`~bridgdb.addr.isIPAddress` would return
- ``False``).
- """
- randomAddress = ipaddr.IPv4Address(random.getrandbits(32))
- result = addr.isIPAddress(randomAddress)
- log.msg("Got addr.isIPAddress() result for random IPv4 address %r: %s"
- % (randomAddress, result))
- self.assertTrue(result is not None)
-
- def test_isIPAddress_randomIP6(self):
- """Test :func:`addr.isIPAddress` with a random IPv6 address.
-
- This test asserts that the returned IP address is not None (because
- the IP being tested is random, it *could* randomly be an invalid IP
- address and thus :func:`~bridgdb.addr.isIPAddress` would return
- ``False``).
- """
- randomAddress = ipaddr.IPv6Address(random.getrandbits(128))
- result = addr.isIPAddress(randomAddress)
- log.msg("Got addr.isIPAddress() result for random IPv6 address %r: %s"
- % (randomAddress, result))
- self.assertTrue(result is not None)
-
- def runTestForAddr(self, testAddress):
- """Test :func:`addr.isIPAddress` with the specified ``testAddress``.
-
- :param str testAddress: A string which specifies either an IPv4 or
- IPv6 address to test.
- """
- result = addr.isIPAddress(testAddress)
- log.msg("addr.isIPAddress(%r) => %s" % (testAddress, result))
- self.assertTrue(result is not None,
- "Got a None for testAddress: %r" % testAddress)
- self.assertFalse(isinstance(result, basestring),
- "Expected %r result from isIPAddress(%r): %r %r"
- % (bool, testAddress, result, type(result)))
-
- def test_isIPAddress_IP4LinkLocal(self):
- """Test :func:`addr.isIPAddress` with a link local IPv4 address."""
- self.runTestForAddr(IP4LinkLocal)
-
- def test_isIPAddress_IP6LinkLocal(self):
- """Test :func:`addr.isIPAddress` with a link local IPv6 address."""
- self.runTestForAddr(IP6LinkLocal)
-
- def test_isIPAddress_IP4Loopback(self):
- """Test :func:`addr.isIPAddress` with the loopback IPv4 address."""
- self.runTestForAddr(IP4Loopback)
-
- def test_isIPAddress_IP4Localhost(self):
- """Test :func:`addr.isIPAddress` with a localhost IPv4 address."""
- self.runTestForAddr(IP4Localhost)
-
- def test_isIPAddress_IP6LinkLocal(self):
- """Test :func:`addr.isIPAddress` with a localhost IPv6 address."""
- self.runTestForAddr(IP6Localhost)
-
- def test_isIPAddress_IP4LimitedBroadcast(self):
- """Test :func:`addr.isIPAddress` with a limited broadcast IPv4
- address.
- """
- self.runTestForAddr(IP4LimitedBroadcast)
-
- def test_isIPAddress_IP4Multicast_224(self):
- """Test :func:`addr.isIPAddress` with a multicast IPv4 address."""
- self.runTestForAddr(IP4Multicast_224)
-
- def test_isIPAddress_IP4Multicast_239(self):
- """Test :func:`addr.isIPAddress` with a multicast IPv4 address."""
- self.runTestForAddr(IP4Multicast_239)
-
- def test_isIPAddress_IP4Unspecified(self):
- """Test :func:`addr.isIPAddress` with an unspecified IPv4 address."""
- self.runTestForAddr(IP4Unspecified)
-
- def test_isIPAddress_IP6Unspecified(self):
- """Test :func:`addr.isIPAddress` with an unspecified IPv6 address."""
- self.runTestForAddr(IP6Unspecified)
-
- def test_isIPAddress_IP4DefaultRoute(self):
- """Test :func:`addr.isIPAddress` with a default route IPv4 address."""
- self.runTestForAddr(IP4DefaultRoute)
-
- def test_isIPAddress_IP6DefaultRoute(self):
- """Test :func:`addr.isIPAddress` with a default route IPv6 address."""
- self.runTestForAddr(IP6DefaultRoute)
-
- def test_isIPAddress_IP4ReservedRFC1918_10(self):
- """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
- self.runTestForAddr(IP4ReservedRFC1918_10)
-
- def test_isIPAddress_IP4ReservedRFC1918_172_16(self):
- """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
- self.runTestForAddr(IP4ReservedRFC1918_172_16)
-
- def test_isIPAddress_IP4ReservedRFC1918_192_168(self):
- """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
- self.runTestForAddr(IP4ReservedRFC1918_192_168)
-
- def test_isIPAddress_IP4ReservedRFC1700(self):
- """Test :func:`addr.isIPAddress` with a :rfc:`1700` reserved IPv4
- address.
- """
- self.runTestForAddr(IP4ReservedRFC1700)
-
- def test_isIPAddress_IP6UniqueLocal(self):
- """Test :func:`addr.isIPAddress` with an unique local IPv6 address."""
- self.runTestForAddr(IP6UniqueLocal)
-
- def test_isIPAddress_IP6SiteLocal(self):
- """Test :func:`addr.isIPAddress` with a site local IPv6 address."""
- self.runTestForAddr(IP6SiteLocal)
-
- def test_isIPAddress_withNonIP(self):
- """Test :func:`addr.isIPAddress` with non-IP input."""
- self.runTestForAddr('not an ip address')
-
- def test_filehandle(self):
- """Test :func:`addr.isIPAddress` with a file handle for input.
-
- Try to raise a non- :exc:`~exceptions.ValueError` exception in
- :func:`addr.isIPAddress`.
- """
- fh = open('{0}-filehandle'.format(self.__class__.__name__), 'wb')
- self.runTestForAddr(fh)
-
- def test_returnUncompressedIP(self):
- """Test returning a :class:`ipaddr.IPAddress`."""
- testAddress = '86.59.30.40'
- result = addr.isIPAddress(testAddress, compressed=False)
- log.msg("addr.isIPAddress(%r, compressed=False) => %r"
- % (testAddress, result))
- self.assertTrue(
- isinstance(result, ipaddr.IPv4Address),
- "Expected %r result from isIPAddress(%r, compressed=False): %r %r"
- % (ipaddr.IPv4Address, testAddress, result, type(result)))
-
- def test_unicode(self):
- """Test with unicode input."""
- self.runTestForAddr("ââââââââââââââââââ")
-
-
-class ParseAddrIsIPv4Tests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.isIPv4`."""
-
- def runTestForIPv4(self, testAddress):
- """Test :func:`addr.isIPv4` with the specified IPv4 **testAddress**.
-
- This test asserts that the returned value is ``True``.
-
- :param str testAddress: A string which specifies the IPv4 address to
- test, which should cause :func:`addr.isIPv4` to return ``True``.
- """
- result = addr.isIPv4(testAddress)
- log.msg("addr.isIPv4(%r) => %s" % (testAddress, result))
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv4() should be boolean: %r" % type(result))
- self.assertTrue(result,
- "addr.isIPv4(%r) should be True!" % testAddress)
-
- def runTestForIPv6(self, testAddress):
- """Test :func:`addr.isIPv4` with the specified IPv6 **testAddress**.
-
- This test asserts that the returned value is ``False``.
-
- :param str testAddress: A string which specifies the IPv6 address to
- test, which should cause :func:`addr.isIPv4` to return ``False``.
- """
- result = addr.isIPv4(testAddress)
- log.msg("addr.isIPv4(%r) => %s" % (testAddress, result))
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv4() should be boolean: %r" % type(result))
- self.assertFalse(result,
- "addr.isIPv4(%r) should be False!" % testAddress)
-
- def test_isIPv4_randomIP4(self):
- """Test :func:`addr.isIPv4` with a random IPv4 address.
-
- This test asserts that the returned value is a :obj:`bool`. Because
- the IP being tested is random, it *could* randomly be an invalid IP
- address and thus :func:`~bridgdb.addr.isIPv4` would return ``False``).
- """
- randomAddr = ipaddr.IPv4Address(random.getrandbits(32)).compressed
- log.msg("Testing randomly generated IPv4 address: %s" % randomAddr)
- result = addr.isIPv4(randomAddr)
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv4() should be boolean: %r" % type(result))
-
- def test_isIPv4_randomIP6(self):
- """Test :func:`addr.isIPv4` with a random IPv6 address."""
- randomAddr = ipaddr.IPv6Address(random.getrandbits(128)).compressed
- log.msg("Testing randomly generated IPv6 address: %s" % randomAddr)
- self.runTestForIPv6(randomAddr)
-
- def test_isIPv4_IP4LinkLocal(self):
- """Test :func:`addr.isIPv4` with a link local IPv4 address."""
- self.runTestForIPv4(IP4LinkLocal)
-
- def test_isIPv4_IP6LinkLocal(self):
- """Test :func:`addr.isIPv4` with a link local IPv6 address."""
- self.runTestForIPv6(IP6LinkLocal)
-
- def test_isIPv4_IP4Loopback(self):
- """Test :func:`addr.isIPv4` with the loopback IPv4 address."""
- self.runTestForIPv4(IP4Loopback)
-
- def test_isIPv4_IP4Localhost(self):
- """Test :func:`addr.isIPv4` with a localhost IPv4 address."""
- self.runTestForIPv4(IP4Localhost)
-
- def test_isIPv4_IP6Localhost(self):
- """Test :func:`addr.isIPv4` with a localhost IPv6 address."""
- self.runTestForIPv6(IP6Localhost)
-
- def test_isIPv4_IP4LimitedBroadcast(self):
- """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4LimitedBroadcast)
-
- def test_isIPv4_IP4Multicast_224(self):
- """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4Multicast_224)
-
- def test_isIPv4_IP4Multicast_239(self):
- """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4Multicast_239)
-
- def test_isIPv4_IP4Unspecified(self):
- """Test :func:`addr.isIPv4` with an unspecified IPv4 address."""
- self.runTestForIPv4(IP4Unspecified)
-
- def test_isIPv4_IP6Unspecified(self):
- """Test :func:`addr.isIPv4` with an unspecified IPv6 address."""
- self.runTestForIPv6(IP6Unspecified)
-
- def test_isIPv4_IP4DefaultRoute(self):
- """Test :func:`addr.isIPv4` with a default route IPv4 address."""
- self.runTestForIPv4(IP4DefaultRoute)
-
- def test_isIPv4_IP6DefaultRoute(self):
- """Test :func:`addr.isIPv4` with a default route IPv6 address."""
- self.runTestForIPv6(IP6DefaultRoute)
-
- def test_isIPv4_IP4ReservedRFC1918_10(self):
- """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_10)
-
- def test_isIPv4_IP4ReservedRFC1918_172_16(self):
- """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_172_16)
-
- def test_isIPv4_IP4ReservedRFC1918_192_168(self):
- """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_192_168)
-
- def test_isIPv4_IP4ReservedRFC1700(self):
- """Test :func:`addr.isIPv4` with a :rfc:`1700` reserved IPv4
- address.
- """
- self.runTestForIPv4(IP4ReservedRFC1700)
-
- def test_isIPv4_IP6UniqueLocal(self):
- """Test :func:`addr.isIPv4` with an unique local IPv6 address."""
- self.runTestForIPv6(IP6UniqueLocal)
-
- def test_isIPv4_IP6SiteLocal(self):
- """Test :func:`addr.isIPv4` with a site local IPv6 address."""
- self.runTestForIPv6(IP6SiteLocal)
-
- def test_isIPv4_withValidIPv4(self):
- """Test :func:`addr.isIPv4` with a valid IPv4 address."""
- self.runTestForIPv4('38.229.72.2')
-
- def test_isIPv4_withValidIPv4_2(self):
- """Test :func:`addr.isIPv4` with a valid IPv4 address."""
- self.runTestForIPv4('15.15.15.15')
-
- def test_isIPv4_withValidIPv4_3(self):
- """Test :func:`addr.isIPv4` with a valid IPv4 address."""
- self.runTestForIPv4('93.95.227.222')
-
- def test_isIPv4_withValidIPv6(self):
- """Test :func:`addr.isIPv4` with a valid IPv6 address."""
- self.runTestForIPv6("2a00:1450:4001:808::1010")
-
- def test_isIPv4_withNonIP(self):
- """Test :func:`addr.isIPv4` with non-IP input."""
- self.runTestForIPv6('not an ip address')
-
-
-class ParseAddrIsIPv6Tests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.addr.isIPv6`.
-
- .. note:: All of the ``test_isIPv6_IP*`` methods in this class should get
- ``False`` as their **result** value returned from :func:`addr.isIPv6`,
- because all of the ``IP*`` constants defined above are invalid
- according to :func:`addr.isValidIP`.
- """
-
- def runTestForIPv4(self, testAddress):
- """Test :func:`addr.isIPv6` with the specified IPv4 **testAddress**.
-
- This test asserts that the returned value is ``False``.
-
- :param str testAddress: A string which specifies the IPv4 address to
- test, which should cause :func:`addr.isIPv6` to return ``False``.
- """
- result = addr.isIPv6(testAddress)
- log.msg("addr.isIPv6(%r) => %s" % (testAddress, result))
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv6() should be boolean: %r" % type(result))
- self.assertFalse(result,
- "addr.isIPv6(%r) should be False!" % testAddress)
-
- def runTestForIPv6(self, testAddress):
- """Test :func:`addr.isIPv6` with the specified IPv6 **testAddress**.
-
- This test asserts that the returned value is ``True``.
-
- Random addresses should *not* be tested with this function, because
- :func:`~addr.isIPv6` uses :func:`~addr.isValidIP` internally, and will
- return False if the IP is invalid.
-
- :param str testAddress: A string which specifies the IPv6 address to
- test, which should cause :func:`addr.isIPv6` to return ``True``.
- """
- result = addr.isIPv6(testAddress)
- log.msg("addr.isIPv6(%r) => %s" % (testAddress, result))
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv6() should be boolean: %r" % type(result))
- self.assertTrue(result,
- "addr.isIPv6(%r) should be True!" % testAddress)
-
- def test_isIPv6_randomIP4(self):
- """Test :func:`addr.isIPv6` with a random IPv4 address."""
- randomAddr = ipaddr.IPv4Address(random.getrandbits(32)).compressed
- log.msg("Testing randomly generated IPv4 address: %s" % randomAddr)
- self.runTestForIPv4(randomAddr)
-
- def test_isIPv6_randomIP6(self):
- """Test :func:`addr.isIPv6` with a random IPv6 address.
-
- This test asserts that the returned IP address is a :obj:`bool`
- (because the IP being tested is random, it *could* randomly be an
- invalid IP address and thus :func:`~bridgdb.addr.isIPv6` would return
- ``False``).
- """
- randomAddr = ipaddr.IPv6Address(random.getrandbits(128)).compressed
- log.msg("Testing randomly generated IPv6 address: %s" % randomAddr)
- result = addr.isIPv6(randomAddr)
- self.assertTrue(isinstance(result, bool),
- "addr.isIPv6() should be boolean: %r" % type(result))
-
- def test_isIPv6_IP4LinkLocal(self):
- """Test :func:`addr.isIPv6` with a link local IPv4 address.
-
- :meth:`runTestForIPv4` is used because this address is invalid
- according to :func:`addr.isValidIP`; therefore, the result from
- :func:`addr.isIPv6` should be ``False``.
- """
- self.runTestForIPv4(IP4LinkLocal)
-
- def test_isIPv6_IP6LinkLocal(self):
- """Test :func:`addr.isIPv6` with a link local IPv6 address."""
- self.runTestForIPv6(IP6LinkLocal)
-
- def test_isIPv6_IP4Loopback(self):
- """Test :func:`addr.isIPv6` with the loopback IPv4 address."""
- self.runTestForIPv4(IP4Loopback)
-
- def test_isIPv6_IP4Localhost(self):
- """Test :func:`addr.isIPv6` with a localhost IPv4 address."""
- self.runTestForIPv4(IP4Localhost)
-
- def test_isIPv6_IP6Localhost(self):
- """Test :func:`addr.isIPv6` with a localhost IPv6 address."""
- self.runTestForIPv6(IP6Localhost)
-
- def test_isIPv6_IP4LimitedBroadcast(self):
- """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4LimitedBroadcast)
-
- def test_isIPv6_IP4Multicast_224(self):
- """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4Multicast_224)
-
- def test_isIPv6_IP4Multicast_239(self):
- """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
- self.runTestForIPv4(IP4Multicast_239)
-
- def test_isIPv6_IP4Unspecified(self):
- """Test :func:`addr.isIPv6` with an unspecified IPv4 address."""
- self.runTestForIPv4(IP4Unspecified)
-
- def test_isIPv6_IP6Unspecified(self):
- """Test :func:`addr.isIPv6` with an unspecified IPv6 address."""
- self.runTestForIPv6(IP6Unspecified)
-
- def test_isIPv6_IP4DefaultRoute(self):
- """Test :func:`addr.isIPv6` with a default route IPv4 address."""
- self.runTestForIPv4(IP4DefaultRoute)
-
- def test_isIPv6_IP6DefaultRoute(self):
- """Test :func:`addr.isIPv6` with a default route IPv6 address."""
- self.runTestForIPv6(IP6DefaultRoute)
-
- def test_isIPv6_IP4ReservedRFC1918_10(self):
- """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_10)
-
- def test_isIPv6_IP4ReservedRFC1918_172_16(self):
- """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_172_16)
-
- def test_isIPv6_IP4ReservedRFC1918_192_168(self):
- """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
- self.runTestForIPv4(IP4ReservedRFC1918_192_168)
-
- def test_isIPv6_IP4ReservedRFC1700(self):
- """Test :func:`addr.isIPv6` with a :rfc:`1700` reserved IPv4
- address.
- """
- self.runTestForIPv4(IP4ReservedRFC1700)
-
- def test_isIPv6_IP6UniqueLocal(self):
- """Test :func:`addr.isIPv6` with an unique local IPv6 address."""
- self.runTestForIPv6(IP6UniqueLocal)
-
- def test_isIPv6_IP6SiteLocal(self):
- """Test :func:`addr.isIPv6` with a site local IPv6 address."""
- self.runTestForIPv6(IP6SiteLocal)
-
- def test_isIPv6_withValidIPv4(self):
- """Test :func:`addr.isIPv6` with a valid IPv4 address."""
- self.runTestForIPv4('38.229.72.2')
-
- def test_isIPv6_withValidIPv4_2(self):
- """Test :func:`addr.isIPv6` with a valid IPv4 address."""
- self.runTestForIPv4('15.15.15.15')
-
- def test_isIPv6_withValidIPv4_3(self):
- """Test :func:`addr.isIPv6` with a valid IPv4 address."""
- self.runTestForIPv4('93.95.227.222')
-
- def test_isIPv6_withValidIPv6(self):
- """Test :func:`addr.isIPv6` with a valid IPv6 address."""
- self.runTestForIPv6("2a00:1450:4001:808::1010")
-
- def test_isIPv6_withNonIP(self):
- """Test :func:`addr.isIPv6` with non-IP input."""
- self.runTestForIPv4('not an ip address')
-
-
-class PortListTest(unittest.TestCase):
- """Unittests for :class:`bridgedb.parse.addr.PortList`."""
-
- def getRandomPort(self):
- """Get a port in the range [1, 65535] inclusive.
-
- :rtype: int
- :returns: A random port number.
- """
- return random.randint(1, 65535)
-
- def test_tooFewPorts(self):
- """Create a :class:`addr.PortList` with no ports at all."""
- portList = addr.PortList()
- self.assertEqual(len(portList), 0)
-
- def test_tooManyPorts(self):
- """Create a :class:`addr.PortList` with more than the maximum
- allowed ports, as given in ``PortList.PORTSPEC_LEN``.
-
- We don't currently do anything to deal with a PortList having too many
- ports.
- """
- tooMany = addr.PortList.PORTSPEC_LEN + 1
- ports = [self.getRandomPort() for x in xrange(tooMany)]
- log.msg("Testing addr.PortList(%s))"
- % ', '.join([type('')(port) for port in ports]))
- portList = addr.PortList(*ports)
- self.assertEqual(len(portList), tooMany)
-
- def test_invalidPortNumber(self):
- """Test creating a :class:`addr.PortList` with an invalid port.
-
- Should raise an InvalidPort error.
- """
- self.assertRaises(addr.InvalidPort, addr.PortList, 66666, 6666)
-
- def test_contains(self):
- """Test creating a :class:`addr.PortList` with valid ports.
-
- Then check that ``__contains__`` works properly.
- """
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertIn(443, portList)
-
- def test_iter(self):
- """Test creating a :class:`addr.PortList` with valid ports.
-
- Then check that ``__iter__`` works properly.
- """
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- iterator = iter(portList)
- for x in xrange(len(ports)):
- self.assertIn(iterator.next(), portList)
-
- def test_str(self):
- """Test creating a :class:`addr.PortList` with valid ports.
-
- Then check that ``__str__`` works properly.
- """
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertTrue(isinstance(str(portList), basestring))
- for port in ports:
- self.assertIn(str(port), str(portList))
-
- def test_getitem_shouldContain(self):
- """Test ``__getitem__`` with a port number in the PortList."""
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertEqual(portList.__getitem__(0), 9001)
-
- def test_getitem_shouldNotContain(self):
- """Test ``__getitem__`` with a port number not in the PortList."""
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertRaises(IndexError, portList.__getitem__, 555)
-
- def test_getitem_string(self):
- """Test ``__getitem__`` with a string."""
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertRaises(TypeError, portList.__getitem__, '443')
-
- def test_getitem_long(self):
- """Test ``__getitem__`` with a string."""
- ports = (443, 9001, 9030)
- portList = addr.PortList(*ports)
- self.assertEqual(portList.__getitem__(long(0)), 9001)
-
- def test_mixedArgs(self):
- """Create a :class:`addr.PortList` with mixed type parameters."""
- firstList = addr.PortList('1111,2222,3333')
- portList = addr.PortList(443, "9001,9030, 9050", firstList)
- self.assertTrue(portList)
-
- def test_invalidStringArgs(self):
- """Create a :class:`addr.PortList` with mixed type parameters."""
- self.assertRaises(addr.InvalidPort,
- addr.PortList, '1111, 666666, 3333')
diff --git a/lib/bridgedb/test/test_parse_descriptors.py b/lib/bridgedb/test/test_parse_descriptors.py
deleted file mode 100644
index dd6d146..0000000
--- a/lib/bridgedb/test/test_parse_descriptors.py
+++ /dev/null
@@ -1,855 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for :class:`bridgedb.parse.descriptors` module."""
-
-from __future__ import print_function
-
-import datetime
-import glob
-import hashlib
-import io
-import os
-import textwrap
-
-from twisted.trial import unittest
-from twisted.trial.unittest import SkipTest
-
-HAS_STEM = False
-
-try:
- from stem.descriptor.server_descriptor import RelayDescriptor
- from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor
- from stem.descriptor.router_status_entry import RouterStatusEntryV3
- from bridgedb.parse import descriptors
-except (ImportError, NameError), error:
- print("There was an error importing stem: %s" % error)
-else:
- HAS_STEM = True
-
-from bridgedb.test.util import Benchmarker
-
-
-BRIDGE_NETWORKSTATUS_0 = '''\
-r MiserLandfalls 4IsyTSCtChPhFPAnq5rD8yymlqA /GMC4lz8RXT/62v6kZNdmzSmopk 2014-11-04 06:23:22 2.215.61.223 4056 0
-a [c5fd:4467:98a7:90be:c76a:b449:8e6f:f0a7]:4055
-s Fast Guard Running Stable Valid
-w Bandwidth=1678904
-p reject 1-65535
-'''
-
-BRIDGE_NETWORKSTATUS_1 = '''\
-r Unmentionable BgOrX0ViP5hNsK5ZvixAuPZ6EY0 NTg9NoE5ls9KjF96Dp/UdrabZ9Y 2014-11-04 12:23:37 80.44.173.87 51691 0
-a [da14:7d1e:ba8e:60d0:b078:3f88:382b:5c70]:51690
-s Fast Guard Running Stable Valid
-w Bandwidth=24361
-p reject 1-65535
-'''
-
-BRIDGE_SERVER_DESCRIPTOR = '''\
- at purpose bridge
-router MiserLandfalls 2.215.61.223 4056 0 0
-or-address [c5fd:4467:98a7:90be:c76a:b449:8e6f:f0a7]:4055
-platform Tor 0.2.2.39 on Linux
-opt protocols Link 1 2 Circuit 1
-published 2014-11-04 06:23:22
-opt fingerprint E08B 324D 20AD 0A13 E114 F027 AB9A C3F3 2CA6 96A0
-uptime 24247659
-bandwidth 1977077890 2234957615 1719198165
-opt extra-info-digest 1CBBB3D6158F324476E6804B7EE25623899271CB
-onion-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBAOm4NX2wi8JmgcAyvOyiAEfq9UkzaNHK+VnSZBiPIrb5GAKFibR7S+Bb
-7+x7tsT8VBNbe9QmwML2GVah3xXg68gJAksMNIgFdpud+zMhduuGd0jr7V55aLmH
-ePGJYCh78B9RqfvmeTridp3pljwcAheKKH/YKi3nv1fPY0BwahurAgMBAAE=
------END RSA PUBLIC KEY-----
-signing-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBANd/JkrTZRT24EkK3DDc/E+Nj1QBnKIm/xXMyW0gkotFOVdewIWjwQ5z
-Tn3YbDhrFN0aFYVdVwNbRhW83e+jZDkpIQuxlQOx6bT13vrzmg8ff1tH8I9EePl7
-MO4v0DLPIEcu7Zfz90oC1bl36oqNsD4h0v4yK/XjVwLutIGiy3gTAgMBAAE=
------END RSA PUBLIC KEY-----
-contact Somebody <somebody at example.com>
-ntor-onion-key NBsk2O6ks5qnxLhhhKPd59zi0IzfjnakoOJP+Cm8OAE
-reject *:*
-router-signature
------BEGIN SIGNATURE-----
-YYA5wJTHcjqXk/QBaDXHX/4Fb8W2OctF4X4VHyxH9Hsou4Ip7nzdfWzbBTcBiIrt
-ybaaMO15L9Ctkli/capN+nCw2jWgivgiPnAmJNmLGeN6skTKjLPAau+839hBuQxu
-P2aB/+XQfzFBA5TaWF83coDng4OGodhwHaOx10Kn7Bg=
------END SIGNATURE-----
-'''
-
-BRIDGE_EXTRA_INFO_DESCRIPTOR = '''\
-extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
-published 2014-11-04 06:23:22
-write-history 2014-11-04 06:23:22 (900 s) 3188736,2226176,2866176
-read-history 2014-11-04 06:23:22 (900 s) 3891200,2483200,2698240
-dirreq-write-history 2014-11-04 06:23:22 (900 s) 1024,0,2048
-dirreq-read-history 2014-11-04 06:23:22 (900 s) 0,0,0
-geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
-geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
-dirreq-stats-end 2014-11-04 06:23:22 (86400 s)
-dirreq-v3-ips
-dirreq-v3-reqs
-dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
-transport obfs3 2.215.61.223:4057
-transport obfs2 2.215.61.223:4058
-transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
-transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
-bridge-stats-end 2014-11-04 06:23:22 (86400 s)
-bridge-ips ca=8
-bridge-ip-versions v4=8,v6=0
-bridge-ip-transports <OR>=8
-router-signature
------BEGIN SIGNATURE-----
-KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
-RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
-U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
------END SIGNATURE-----
-'''
-
-BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE = '''\
-extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
-published 2014-11-04 08:10:25
-write-history 2014-11-04 08:10:25 (900 s) 3188736,2226176,2866176,2226176
-read-history 2014-11-04 08:10:25 (900 s) 3891200,2483200,2698240,2483200
-dirreq-write-history 2014-11-04 08:10:25 (900 s) 1024,0,2048,3072
-dirreq-read-history 2014-11-04 08:10:25 (900 s) 0,0,0,0
-geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
-geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
-dirreq-stats-end 2014-11-04 08:10:25 (86400 s)
-dirreq-v3-ips
-dirreq-v3-reqs
-dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
-transport obfs3 2.215.61.223:4057
-transport obfs2 2.215.61.223:4058
-transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
-transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
-bridge-stats-end 2014-11-04 08:10:25 (86400 s)
-bridge-ips ca=8
-bridge-ip-versions v4=8,v6=0
-bridge-ip-transports <OR>=8
-router-signature
------BEGIN SIGNATURE-----
-KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
-RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
-U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
------END SIGNATURE-----
-'''
-
-BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE = '''\
-extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
-published 2014-12-04 03:10:25
-write-history 2014-12-04 03:10:25 (900 s) 3188736,2226176,2866176,2226176
-read-history 2014-12-04 03:10:25 (900 s) 3891200,2483200,2698240,2483200
-dirreq-write-history 2014-12-04 03:10:25 (900 s) 1024,0,2048,3072
-dirreq-read-history 2014-12-04 03:10:25 (900 s) 0,0,0,0
-geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
-geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
-dirreq-stats-end 2014-12-04 03:10:25 (86400 s)
-dirreq-v3-ips
-dirreq-v3-reqs
-dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
-transport obfs3 2.215.61.223:4057
-transport obfs2 2.215.61.223:4058
-transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
-transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
-bridge-stats-end 2014-12-04 03:10:25 (86400 s)
-bridge-ips ca=8
-bridge-ip-versions v4=8,v6=0
-bridge-ip-transports <OR>=8
-router-signature
------BEGIN SIGNATURE-----
-KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
-RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
-U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
------END SIGNATURE-----
-'''
-
-BRIDGE_SERVER_DESCRIPTOR_ED25519 = '''\
- at purpose bridge
-router piratepartei 80.92.79.70 80 0 0
-identity-ed25519
------BEGIN ED25519 CERT-----
-AQQABhauAccW2uNOkPPWU7h9x9FFWtUJXCnw423dKqL/89pTHFRcAQAgBADfGmFI
-//1tBiZZxZ2aXNvvLbEdS/0XHYCWY6Oz3lHCU2xHCJzW03U7htLpq95lWStr2bMm
-D9N1MJp8Zufal71nFV5dgCm0DvMoeCN0d1F6zYnrGvyq+2E6p32x/DG33Qs=
------END ED25519 CERT-----
-master-key-ed25519 3xphSP/9bQYmWcWdmlzb7y2xHUv9Fx2AlmOjs95RwlM
-platform Tor 0.2.7.1-alpha-dev on Linux
-protocols Link 1 2 Circuit 1
-published 2015-06-09 21:59:40
-fingerprint 312D 6427 4C29 1560 0584 3EEC B19C 6865 FA3C C10C
-uptime 0
-bandwidth 14971520 104857600 64512
-extra-info-digest 30E10A35CCEA6AA1E04C15FD5F99022F4CACEBC6 pph/KzxlcGa20Sl6/nQl7noyKctzcWkRkTbBX7aIapQ
-onion-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBALRXlkmBc96bz/WFSJ0/NoNYuOivpRBkMDqE0617x63EE9zA+BQGVk81
-5mbF50IQRS12J3F7x+m7USGF7xcUw+id9pe1jzyyqOTo2BPf2Wemif+CvVc9uD0v
-BLO38iImiret0yZtxq3RQ2KaCg2z0y+RPDudR6z/d6V3ASFSlPgBAgMBAAE=
------END RSA PUBLIC KEY-----
-signing-key
------BEGIN RSA PUBLIC KEY-----
-MIGJAoGBALGE2wcWNpWczHlLOa3MbRMKYGDMNe3MsTDKqxftImHuUdMV758q5/4c
-2d0znZ1k5zma7TIKXM1xblVWaHmSZ65jMyy0jgZl7SNbxibP3xM8mfHAJOoWfnQu
-LSj8tKSir2BdA8rncajrDmtQe0C8mxA/RgUHuB6ZF42kAB9lm/33AgMBAAE=
------END RSA PUBLIC KEY-----
-onion-key-crosscert
------BEGIN CROSSCERT-----
-EPpvZluK8YLLXU00HVskixVqpJfkCeKWXkQPv5Vq87n7E/gtzrVM9A0DasSPHgor
-0Y1jP2K/6G0nuloeDZuNNqPxxz7LEKom5q66UO0Tk4Xdnmj1yp/hSsqi/8sUGe9R
-BmZmuz45UJGmADiYwwFnwec/bKkX3al4BwuQRHwcZd0=
------END CROSSCERT-----
-ntor-onion-key-crosscert 1
------BEGIN ED25519 CERT-----
-AQoABhSGAd8aYUj//W0GJlnFnZpc2+8tsR1L/RcdgJZjo7PeUcJTABR4eqhKqYNN
-Sgpojtm7C+QRvD3mTk06EEbFly9VrXOaSK4BVxTlsHadm4ti7vdqGHbTWN7DRRu6
-nnUKJPMOsAk=
------END ED25519 CERT-----
-hidden-service-dir
-contact 0x02225522 Frenn vun der Enn (FVDE) <info AT enn DOT lu>
-ntor-onion-key ycFwQVUCqJlPaLwJNvlrpgNLwkU780t4pKiILLWZw0o=
-reject *:*
-router-sig-ed25519 /uWcpQeWcwywFwy+O1WGfLQFuxkLMsy8u+rTTum4CQd8uN7bt3VCHRG82X9sc18rMv2VHUs7b+WZcfX39ADMDw
-router-signature
------BEGIN SIGNATURE-----
-FpF1a2jF1gkVbSUEuuDrw8ggeyQl4HLqHGXJM/J3SPQDky0OhvqPEV8E0CpONG38
-YNumnkSJ0vjI0YUuVyOZKpODHS/dlXnz5F/Yz8vwQfC7IsNRQgNgf5tbT3iAF8yh
-VC4FdHgFlAkXbiqkpWtD0ojJJjLlEeXbmGILjC1Ls2I=
------END SIGNATURE-----
-'''
-
-BRIDGE_EXTRA_INFO_DESCRIPTOR_ED25519 = '''\
-extra-info piratepartei 312D64274C29156005843EECB19C6865FA3CC10C
-identity-ed25519
------BEGIN ED25519 CERT-----
-AQQABhauAccW2uNOkPPWU7h9x9FFWtUJXCnw423dKqL/89pTHFRcAQAgBADfGmFI
-//1tBiZZxZ2aXNvvLbEdS/0XHYCWY6Oz3lHCU2xHCJzW03U7htLpq95lWStr2bMm
-D9N1MJp8Zufal71nFV5dgCm0DvMoeCN0d1F6zYnrGvyq+2E6p32x/DG33Qs=
------END ED25519 CERT-----
-published 2015-06-09 21:59:40
-write-history 2015-06-09 19:41:54 (14400 s) 1093632,3138560,1309696,1641472,1064960,1799168
-read-history 2015-06-09 19:41:54 (14400 s) 4406272,6537216,5197824,5701632,5342208,5817344
-dirreq-write-history 2015-06-09 19:17:22 (14400 s) 28672,1727488,575488,589824,43008,618496
-dirreq-read-history 2015-06-09 19:17:22 (14400 s) 0,0,0,0,0,0
-geoip-db-digest 0A1F9C09E08F6F2490E8880664D4E863D1680A12
-geoip6-db-digest A6E9B5DE6F887315749B29F9C9F698215BE5240A
-dirreq-stats-end 2015-06-09 12:33:11 (86400 s)
-dirreq-v3-ips ir=8,us=8
-dirreq-v3-reqs ir=8,us=8
-dirreq-v3-resp ok=8,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
-dirreq-v3-direct-dl complete=0,timeout=0,running=0
-dirreq-v3-tunneled-dl complete=8,timeout=4,running=0
-transport scramblesuit 80.92.79.70:7333 password=S3DVRHWD5375I3AA5NMQBG4WED5MBIYD
-transport fte 80.92.79.70:7331
-transport websocket 80.92.79.70:9901
-transport obfs3 80.92.79.70:7332
-transport obfs4 80.92.79.70:7334 cert=/Q8QygIhLarhjvB+rKiFvSmXdjhO9AF6OXACR8JH+voMwKF0s5uMaG3H3uEBiZNQI79jPw,iat-mode=0
-bridge-stats-end 2015-06-09 12:33:17 (86400 s)
-bridge-ips us=24
-bridge-ip-versions v4=24,v6=0
-bridge-ip-transports obfs4=24
-router-sig-ed25519 O+yrUnkHXZ16Cf0+a3gfDl2ggygbxQUal4kRi5BD2v3NW8CrWjqGJLBjked8g5eJCThUXZuraHwkapeu8gtAAg
-router-signature
------BEGIN SIGNATURE-----
-HbZs8ckBwKbJ4vg0LJztGosNaDqSRD+pHiWgBAmx9ARbz7niJMY/ql+Qxh7NFifQ
-xa39dJvObxE65qeaZJvcznSyEkUDcHBFcHLWZev7XQjXf2no9vUL86JvwBKHHKC1
-GnoYumyiqlKn3MOiqVYN5KXhO5i6qN/W8SjMVvywxZI=
------END SIGNATURE-----
-extra-info Unnamed 9673B58C3A72BC279C4FADEA678DEDCF63E524D2
-published 2015-06-09 22:00:10
-router-signature
------BEGIN SIGNATURE-----
-S57LSzZy2ecjd9jqA5R7nzRUOWJBNVGA8TMLiuWMHXj4DY540ZgbObNtAIU/uzR5
-C3sfCVx39iQ39DKgi93zaeZ7s37KGKiUXwJkvDsY0+A2N/TNX5DyI0ZH8WAwCMNq
-EXVgdbhn8RrQiVT69evPXjdr6hmgllUofRT7LimvR60=
------END SIGNATURE-----
-'''
-
-
-class ParseDescriptorsTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.parse.descriptors` module."""
-
- skip = True if not HAS_STEM else False
-
- def setUp(self):
- """Test if we have Stem installed. Skip these tests if it's missing."""
- self.expectedIPBridge0 = '2.215.61.223'
- self.expectedIPBridge1 = '80.44.173.87'
-
- self.expectedFprBridge0 = 'E08B324D20AD0A13E114F027AB9AC3F32CA696A0'
-
- if self.skip:
- raise unittest.SkipTest("Couldn't import Stem.")
-
- def writeTestDescriptorsToFile(self, filename, *descriptors):
- """Write **descriptors** to **filename**.
-
- :param str filename: A filename. It will be appended to the current
- working directory automatically.
- :param str descriptors: Some optional strings containing
- descriptors. Each one will be written to **filename** as-is.
- :rtype: str
- :returns: The full path to the file which was written to.
- """
- descFilename = os.path.join(os.getcwd(), filename)
- with open(descFilename, 'w') as fh:
- for desc in descriptors:
- fh.write(desc)
- fh.flush()
- return descFilename
-
- def test_parse_descriptors_parseServerDescriptorsFile(self):
- """Test for ``b.p.descriptors.parseServerDescriptorsFile``."""
- descFile = io.BytesIO(BRIDGE_SERVER_DESCRIPTOR)
- routers = descriptors.parseServerDescriptorsFile(descFile)
- self.assertIsInstance(routers, list)
- bridge = routers[0]
- self.assertIsInstance(bridge, RelayDescriptor)
- self.assertEqual(bridge.address, self.expectedIPBridge0)
- self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
-
- def test_parse_descriptors_parseNetworkStatusFile_return_type(self):
- """``b.p.descriptors.parseNetworkStatusFile`` should return a dict."""
- # Write the descriptor to a file for testing. This is necessary
- # because the function opens the networkstatus file to read it.
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- BRIDGE_NETWORKSTATUS_0)
- routers = descriptors.parseNetworkStatusFile(descFile)
- self.assertIsInstance(routers, list)
-
- def test_parse_descriptors_parseNetworkStatusFile_has_RouterStatusEntryV2(self):
- """The items in the dict returned from
- ``b.p.descriptors.parseNetworkStatusFile`` should be
- ``RouterStatusEntryV2``s.
- """
- # Write the descriptor to a file for testing. This is necessary
- # because the function opens the networkstatus file to read it.
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- BRIDGE_NETWORKSTATUS_0)
- routers = descriptors.parseNetworkStatusFile(descFile)
- bridge = routers[0]
- self.assertIsInstance(bridge, RouterStatusEntryV3)
-
- def test_parse_descriptors_parseNetworkStatusFile_one_file(self):
- """Test ``b.p.descriptors.parseNetworkStatusFile`` with one bridge
- networkstatus descriptor.
- """
- # Write the descriptor to a file for testing. This is necessary
- # because the function opens the networkstatus file to read it.
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- BRIDGE_NETWORKSTATUS_0)
- routers = descriptors.parseNetworkStatusFile(descFile)
- bridge = routers[0]
- self.assertEqual(bridge.address, self.expectedIPBridge0)
- self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
-
- def test_parse_descriptors_parseNetworkStatusFile_two_files(self):
- """Test ``b.p.descriptors.parseNetworkStatusFile`` with two bridge
- networkstatus descriptors.
- """
- expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
-
- # Write the descriptor to a file for testing. This is necessary
- # because the function opens the networkstatus file to read it.
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- BRIDGE_NETWORKSTATUS_0,
- BRIDGE_NETWORKSTATUS_1)
- routers = descriptors.parseNetworkStatusFile(descFile)
- bridge = routers[0]
-
- self.assertIn(bridge.address, expectedIPs)
- self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
-
- def test_parse_descriptors_parseNetworkStatusFile_bad_nickname(self):
- """``b.p.descriptors.parseNetworkStatusFile`` with a bridge
- networkstatus descriptor which has a nickname that is too long should
- raise InvalidRouterNickname.
- """
- unparseable = BRIDGE_NETWORKSTATUS_0.replace(
- 'MiserLandfalls',
- 'MiserLandfallsWaterfallsSnowfallsAvalanche')
- # Write the descriptor to a file for testing. This is necessary
- # because the function opens the networkstatus file to read it.
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- unparseable)
- self.assertRaises(descriptors.InvalidRouterNickname,
- descriptors.parseNetworkStatusFile,
- descFile)
-
- def test_parse_descriptors_parseNetworkStatusFile_IPv6_ORAddress(self):
- """A Bridge can't have its primary ORAddress be IPv6 without raising
- a ValueError.
- """
- unparseable = BRIDGE_NETWORKSTATUS_0.replace(
- '2.215.61.223', '[2837:fcd2:387b:e376:34c:1ec7:11ff:1686]')
- descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
- unparseable)
- self.assertRaises(ValueError,
- descriptors.parseNetworkStatusFile,
- descFile)
-
- def test_parse_descriptors_parseNetworkStatusFile_with_annotations(self):
- """Test ``b.p.descriptors.parseNetworkStatusFile`` with some document
- headers before the first 'r'-line.
- """
- expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
- descFile = 'networkstatus-bridges'
-
- with open(descFile, 'w') as fh:
- fh.write('signature and stuff from the BridgeAuth would go here\n')
- fh.write('some more annotations with parameters and stuff\n')
- fh.write(BRIDGE_NETWORKSTATUS_0)
- fh.write(BRIDGE_NETWORKSTATUS_1)
- fh.flush()
-
- routers = descriptors.parseNetworkStatusFile(descFile)
- bridge = routers[0]
- self.assertIn(bridge.address, expectedIPs)
- self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
-
- def test_parse_descriptors_parseNetworkStatusFile_with_annotations_no_skipping(self):
- """Test ``b.p.descriptors.parseNetworkStatusFile`` with some
- document headers before the first 'r'-line, but without skipping said
- annotations.
- """
- expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
- descFile = 'networkstatus-bridges'
-
- with open(descFile, 'w') as fh:
- fh.write('signature and stuff from the BridgeAuth would go here\n')
- fh.write('some more annotations with parameters and stuff\n')
- fh.write(BRIDGE_NETWORKSTATUS_0)
- fh.write(BRIDGE_NETWORKSTATUS_1)
- fh.flush()
-
- self.assertRaises(ValueError,
- descriptors.parseNetworkStatusFile,
- descFile, skipAnnotations=False)
-
- def test_parse_descriptors_parseExtraInfoFiles_return_type(self):
- """The return type of ``b.p.descriptors.parseExtraInfoFiles``
- should be a dictionary (after deduplication).
- """
- descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFile)
- self.assertIsInstance(routers, dict)
-
- def test_parse_descriptors_parseExtraInfoFiles_has_BridgeExtraInfoDescriptor(self):
- """The return of ``b.p.descriptors.parseExtraInfoFiles`` should
- contain ``BridgeExtraInfoDescriptor``s.
- """
- descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFile)
- bridge = routers.values()[0]
- self.assertIsInstance(bridge, RelayExtraInfoDescriptor)
-
- def test_parse_descriptors_parseExtraInfoFiles_one_file(self):
- """Test for ``b.p.descriptors.parseExtraInfoFiles`` with only one
- bridge extrainfo file.
- """
- descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFile)
- bridge = routers.values()[0]
-
- # The number of transports we parsed should be equal to the number of
- # 'transport' lines in the descriptor:
- self.assertEqual(len(bridge.transport),
- BRIDGE_EXTRA_INFO_DESCRIPTOR.count('transport '))
-
- self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
-
- def test_parse_descriptors_deduplicate_identical_timestamps(self):
- """Parsing two descriptors for the same bridge with identical
- timestamps should log a ``b.p.descriptors.DescriptorWarning``
- and retain only one copy of the descriptor.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
-
- self.assertEqual(len(routers), 1)
-
- def test_parse_descriptors_parseExtraInfoFiles_two_files(self):
- """Test for ``b.p.descriptors.parseExtraInfoFiles`` with two
- bridge extrainfo files, and check that only the newest extrainfo
- descriptor is used.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
- routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
-
- # We shouldn't have duplicates:
- self.assertEqual(len(routers), 1,
- "We shouldn't have any duplicate descriptors.")
-
- # We should only have the newest descriptor:
- bridge = routers.values()[0]
- self.assertEqual(
- bridge.published,
- datetime.datetime.strptime("2014-11-04 08:10:25", "%Y-%m-%d %H:%M:%S"),
- "We should have the newest available descriptor for this router.")
-
- def test_parse_descriptors_parseExtraInfoFiles_two_files_reverse(self):
- """Test for ``b.p.descriptors.parseExtraInfoFiles`` with two bridge
- extrainfo files. This time, they are processed in reverse to ensure
- that we only keep the newer duplicates of descriptors, no matter what
- order they appeared in the files.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
-
- self.assertEqual(len(routers), 1,
- "We shouldn't have any duplicate descriptors.")
-
- bridge = routers.values()[0]
- self.assertEqual(
- bridge.published,
- datetime.datetime.strptime("2014-11-04 08:10:25", "%Y-%m-%d %H:%M:%S"),
- "We should have the newest available descriptor for this router.")
-
- def test_parse_descriptors_parseExtraInfoFiles_three_files(self):
- """Test for ``b.p.descriptors.parseExtraInfoFiles`` with three
- bridge extrainfo files, and check that only the newest extrainfo
- descriptor is used.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- descFileThree = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
- routers = descriptors.parseExtraInfoFiles(descFileOne,
- descFileTwo,
- descFileThree)
-
- # We shouldn't have duplicates:
- self.assertEqual(len(routers), 1,
- "We shouldn't have any duplicate descriptors.")
-
- # We should only have the newest descriptor:
- bridge = routers.values()[0]
- self.assertEqual(
- bridge.published,
- datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
- "We should have the newest available descriptor for this router.")
-
- def createDuplicatesForBenchmark(self, b=1, n=1200):
- """Create a bunch of duplicate extrainfos for benchmark tests.
-
- :param int b: The number of fake "bridges" to create **n** duplicate
- descriptors for.
- :param int n: The number of duplicate descriptors for each bridge
- **b**.
- """
- descFiles = []
-
- # The timestamp and fingerprint from BRIDGE_EXTRA_INFO_DESCRIPTOR:
- timestamp = "2014-11-04 06:23:22"
- Y, M, rest = timestamp.split("-")
- fpr = "E08B324D20AD0A13E114F027AB9AC3F32CA696A0"
- newerFpr = "E08B324D20AD0A13E114F027AB9AC3F32CA696A0"
-
- total = 0
- needed = b * n
- for x in range(b):
- if total >= needed:
- break
- # Re-digest the fingerprint to create a "new" bridge
- newerFpr = hashlib.sha1(newerFpr).hexdigest().upper()
- # Generate n extrainfos with different timestamps:
- count = 0
- for year in range(1, ((n + 1)/ 12) + 2): # Start from the next year
- if count >= n:
- break
- for month in range(1, 13):
- if count < n:
- newerTimestamp = "-".join([str(int(Y) + year), "%02d" % month, rest])
- newerDuplicate = BRIDGE_EXTRA_INFO_DESCRIPTOR[:].replace(
- fpr, newerFpr).replace(
- timestamp, newerTimestamp)
- descFiles.append(io.BytesIO(newerDuplicate))
- count += 1
- total += 1
- else:
- break
-
- print("Deduplicating %5d total descriptors (%4d per bridge; %3d bridges):"
- % (len(descFiles), n, b), end='\t')
- return descFiles
-
- def test_parse_descriptors_parseExtraInfoFiles_benchmark_100_bridges(self):
- """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``."""
- print()
- for i in range(1, 6):
- descFiles = self.createDuplicatesForBenchmark(b=100, n=i)
- with Benchmarker():
- routers = descriptors.parseExtraInfoFiles(*descFiles)
-
- def test_parse_descriptors_parseExtraInfoFiles_benchmark_1000_bridges(self):
- """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``."""
- raise SkipTest(("This test can take several minutes to complete. "
- "Run it on your own free time."))
-
- print()
- for i in range(1, 6):
- descFiles = self.createDuplicatesForBenchmark(b=1000, n=i)
- with Benchmarker():
- routers = descriptors.parseExtraInfoFiles(*descFiles)
-
- def test_parse_descriptors_parseExtraInfoFiles_benchmark_10000_bridges(self):
- """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``.
- The algorithm should grow linearly in the number of duplicates.
- """
- raise SkipTest(("This test can take several minutes to complete. "
- "Run it on your own free time."))
-
- print()
- for i in range(1, 6):
- descFiles = self.createDuplicatesForBenchmark(b=10000, n=i)
- with Benchmarker():
- routers = descriptors.parseExtraInfoFiles(*descFiles)
-
- def test_parse_descriptors_parseExtraInfoFiles_no_validate(self):
- """Test for ``b.p.descriptors.parseExtraInfoFiles`` with
- descriptor validation disabled.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- routers = descriptors.parseExtraInfoFiles(descFileOne,
- validate=False)
- self.assertGreaterEqual(len(routers), 1)
-
- def test_parse_descriptors_parseExtraInfoFiles_unparseable(self):
- """Test parsing three extrainfo descriptors: one is a valid descriptor,
- one is an older duplicate, and one is unparseable (it has a bad
- geoip-db-digest line). There should be only one descriptor returned
- after parsing.
- """
- # Give it a bad geoip-db-digest:
- unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
- "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
- "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
- "geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F",
- "geoip-db-digest FOOOOOOOOOOOOOOOOOOBAAAAAAAAAAAAAAAAAARR")
-
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- # This must be a "real" file or _copyUnparseableDescriptorFile() will
- # raise an AttributeError saying:
- # '_io.BytesIO' object has no attribute 'rpartition'"
- descFileThree = self.writeTestDescriptorsToFile(
- "unparseable-descriptor", unparseable)
- routers = descriptors.parseExtraInfoFiles(descFileOne,
- descFileTwo,
- descFileThree)
- self.assertIsInstance(routers, dict)
- self.assertEqual(len(routers), 1, (
- "There were three extrainfo descriptors: one was a duplicate, "
- "and one was unparseable, so that should only leave one "
- "descriptor remaining."))
-
- bridge = routers.values()[0]
- self.assertEqual(
- bridge.fingerprint,
- "E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
- ("It looks like the (supposedly) unparseable bridge was returned "
- "instead of the valid one!"))
- self.assertEqual(
- bridge.published,
- datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
- "We should have the newest available descriptor for this router.")
-
- def test_parse_descriptors_parseExtraInfoFiles_unparseable_and_parseable(self):
- """Test parsing four extrainfo descriptors: two are valid descriptors,
- one is an older duplicate of one of the valid descriptors, and one is
- unparseable (it has a line we shouldn't recognise). There should be
- only two descriptors returned after parsing.
- """
- # Mess up the bridge-ip-transports line:
- unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
- "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
- "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
- "bridge-ip-transports <OR>=8",
- "bridge-ip-transports <OR>")
-
- parseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
- "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
- "ImOkWithBeingParsed 2B5DA67FBA13A6449DE625673B7AE9E3AA7DF75F")
-
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
- # This must be a "real" file or _copyUnparseableDescriptorFile() will
- # raise an AttributeError saying:
- # '_io.BytesIO' object has no attribute 'rpartition'"
- descFileThree = self.writeTestDescriptorsToFile(
- "unparseable-descriptor.new", unparseable)
- descFileFour = io.BytesIO(parseable)
- routers = descriptors.parseExtraInfoFiles(descFileOne,
- descFileTwo,
- descFileThree,
- descFileFour)
- self.assertIsInstance(routers, dict)
- self.assertEqual(len(routers), 2, (
- "There were four extrainfo descriptors: one was a duplicate, "
- "and one was unparseable, so that should only leave two "
- "descriptors remaining."))
-
- self.assertNotIn("F373CC1D86D82267F1F1F5D39470F0E0A022122E", routers.keys(),
- "The 'unparseable' descriptor was returned by the parser.")
-
- self.assertIn("E08B324D20AD0A13E114F027AB9AC3F32CA696A0", routers.keys(),
- ("A bridge extrainfo which had duplicates was completely missing "
- "from the data which the parser returned."))
- self.assertEqual(
- routers["E08B324D20AD0A13E114F027AB9AC3F32CA696A0"].published,
- datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
- "We should have the newest available descriptor for this router.")
-
- self.assertIn("2B5DA67FBA13A6449DE625673B7AE9E3AA7DF75F", routers.keys(),
- "The 'parseable' descriptor wasn't returned by the parser.")
-
- def test_parse_descriptors_parseExtraInfoFiles_bad_signature_footer(self):
- """Calling parseExtraInfoFiles() with a descriptor which has a
- signature with a bad "-----END SIGNATURE-----" footer should return
- zero parsed descriptors.
- """
- unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
- '-----END SIGNATURE-----',
- '-----END SIGNATURE FOR REALZ-----')
- # This must be a "real" file or _copyUnparseableDescriptorFile() will
- # raise an AttributeError saying:
- # '_io.BytesIO' object has no attribute 'rpartition'"
- descFileOne = self.writeTestDescriptorsToFile(
- "bad-signature-footer", unparseable)
- routers = descriptors.parseExtraInfoFiles(descFileOne)
-
- self.assertEqual(len(routers), 0)
-
- def test_parse_descriptors_parseExtraInfoFiles_missing_signature(self):
- """Calling parseExtraInfoFiles() with a descriptor which is
- missing the signature should return zero parsed descriptors.
- """
- # Remove the signature
- BEGIN_SIG = '-----BEGIN SIGNATURE-----'
- unparseable, _ = BRIDGE_EXTRA_INFO_DESCRIPTOR.split(BEGIN_SIG)
- # This must be a "real" file or _copyUnparseableDescriptorFile() will
- # raise an AttributeError saying:
- # '_io.BytesIO' object has no attribute 'rpartition'"
- descFileOne = self.writeTestDescriptorsToFile(
- "missing-signature", unparseable)
- routers = descriptors.parseExtraInfoFiles(descFileOne)
-
- self.assertEqual(len(routers), 0)
-
- def test_parse_descriptors_parseExtraInfoFiles_bad_signature_too_short(self):
- """Calling _verifyExtraInfoSignature() with a descriptor which has a
- bad signature should raise an InvalidExtraInfoSignature exception.
- """
- # Truncate the signature to 50 bytes
- BEGIN_SIG = '-----BEGIN SIGNATURE-----'
- doc, sig = BRIDGE_EXTRA_INFO_DESCRIPTOR.split(BEGIN_SIG)
- unparseable = BEGIN_SIG.join([doc, sig[:50]])
- # This must be a "real" file or _copyUnparseableDescriptorFile() will
- # raise an AttributeError saying:
- # '_io.BytesIO' object has no attribute 'rpartition'"
- descFileOne = self.writeTestDescriptorsToFile(
- "truncated-signature", unparseable)
- routers = descriptors.parseExtraInfoFiles(descFileOne)
-
- self.assertEqual(len(routers), 0)
-
- def test_parse_descriptors_parseExtraInfoFiles_unparseable_BytesIO(self):
- """Test parsing three extrainfo descriptors: one is a valid descriptor,
- one is an older duplicate, and one is unparseable (it has a bad
- geoip-db-digest line). The parsing should raise an unhandled
- AttributeError because _copyUnparseableDescriptorFile() tries to
- manipulate the io.BytesIO object's filename, and it doesn't have one.
- """
- # Give it a bad geoip-db-digest:
- unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
- "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
- "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
- "geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F",
- "geoip-db-digest FOOOOOOOOOOOOOOOOOOBAAAAAAAAAAAAAAAAAARR")
-
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
- descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
- descFileThree = io.BytesIO(unparseable)
- self.assertRaises(AttributeError,
- descriptors.parseExtraInfoFiles,
- descFileOne, descFileTwo, descFileThree)
-
- def test_parse_descriptors_parseExtraInfoFiles_empty_file(self):
- """Test parsing an empty extrainfo descriptors file."""
- routers = descriptors.parseExtraInfoFiles(io.BytesIO(''))
- self.assertIsInstance(routers, dict)
- self.assertEqual(len(routers), 0)
-
- def test_parse_descriptors_parseExtraInfoFiles_ed25519(self):
- """Test parsing an extrainfo descriptor with Ed25519 keys/certificates.
- """
- descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_ED25519)
- routers = descriptors.parseExtraInfoFiles(descFileOne)
- self.assertEqual(len(routers), 1)
-
- def test_parse_descriptors_parseExtraInfoFiles_ed25519(self):
- """Test parsing an extrainfo descriptor with Ed25519 keys/certificates.
- """
- descFileOne = io.BytesIO(BRIDGE_SERVER_DESCRIPTOR_ED25519)
- routers = descriptors.parseServerDescriptorsFile(descFileOne)
- self.assertIsInstance(routers, list)
- self.assertEqual(len(routers), 1)
-
- bridge = routers[0]
- self.assertIsInstance(bridge, RelayDescriptor)
- self.assertEqual(bridge.address, u'80.92.79.70')
- self.assertEqual(bridge.fingerprint, u'312D64274C29156005843EECB19C6865FA3CC10C')
-
- def test_parse_descriptors_copyUnparseableDescriptorFile_return_value(self):
- """``b.p.descriptors._copyUnparseableDescriptorFile()`` should return
- True when the new file is successfully created.
- """
- filename = "bridge-descriptors"
- with open(filename, 'w') as fh:
- fh.write(BRIDGE_SERVER_DESCRIPTOR)
- fh.flush()
-
- result = descriptors._copyUnparseableDescriptorFile(filename)
- self.assertTrue(result) # should return True
-
- def test_parse_descriptors_copyUnparseableDescriptorFile_new_filename(self):
- """``b.p.descriptors._copyUnparseableDescriptorFile()`` should create a
- copy of the bad file with a specific filename format.
- """
- filename = "bridge-descriptors"
- with open(filename, 'w') as fh:
- fh.write(BRIDGE_SERVER_DESCRIPTOR)
- fh.flush()
-
- descriptors._copyUnparseableDescriptorFile(filename)
- matchingFiles = glob.glob("*_bridge-descriptors.unparseable")
- self.assertEqual(len(matchingFiles), 1)
-
- newFile = matchingFiles[-1]
- self.assertTrue(os.path.isfile(newFile))
-
- timestamp = datetime.datetime.strptime(newFile.split("_")[0],
- "%Y-%m-%d-%H:%M:%S")
- # The timestamp should be roughly today (unless we just passed
- # midnight, then it might be +/- 1):
- self.assertApproximates(timestamp.now().day, timestamp.day, 1)
-
- # The timestamp should be roughly this hour (+/- 1):
- self.assertApproximates(timestamp.now().hour, timestamp.hour, 1)
diff --git a/lib/bridgedb/test/test_parse_headers.py b/lib/bridgedb/test/test_parse_headers.py
deleted file mode 100644
index 278e721..0000000
--- a/lib/bridgedb/test/test_parse_headers.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2014-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.parse.headers` module."""
-
-from __future__ import absolute_import
-from __future__ import print_function
-from __future__ import unicode_literals
-
-from twisted.trial import unittest
-
-from bridgedb.parse import headers
-
-
-class ParseAcceptLanguageTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.headers.parseAcceptLanguage`."""
-
- def test_noHeaders(self):
- """No header should return an empty list."""
- header = None
- langs = headers.parseAcceptLanguage(header)
- self.assertIsInstance(langs, list)
- self.assertEqual(len(langs), 0)
-
- def test_defaultTBBHeader(self):
- """The header 'en-us,en;q=0.5' should return ['en_us', 'en']."""
- header = 'en-us,en;q=0.5'
- langs = headers.parseAcceptLanguage(header)
- self.assertIsInstance(langs, list)
- self.assertEqual(len(langs), 2)
- self.assertEqual(langs[0], 'en_us')
- self.assertEqual(langs[1], 'en')
-
- def test_addNonLocalizedVariant(self):
- """The header 'en-us,en-gb;q=0.5' should return
- ['en_us', 'en', 'en_gb'].
- """
- header = 'en-us,en-gb;q=0.5'
- langs = headers.parseAcceptLanguage(header)
- self.assertIsInstance(langs, list)
- self.assertEqual(len(langs), 3)
- self.assertEqual(langs[0], 'en_us')
- self.assertEqual(langs[1], 'en')
- self.assertEqual(langs[2], 'en_gb')
diff --git a/lib/bridgedb/test/test_parse_nickname.py b/lib/bridgedb/test/test_parse_nickname.py
deleted file mode 100644
index fea000a..0000000
--- a/lib/bridgedb/test/test_parse_nickname.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# (c) 2013-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.parse.nickname` module."""
-
-from twisted.trial import unittest
-
-from bridgedb.parse.nickname import isValidRouterNickname
-
-
-class IsValidRouterNicknameTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.parse.nickname.isValidRouterNickname`."""
-
- def test_parse_nickname_isValidRouterNickname_valid(self):
- """isValidRouterNickname() should return True for a valid nickname."""
- name = 'Unmentionable'
- self.assertTrue(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_valid_1(self):
- """isValidRouterNickname() should return True for a valid nickname."""
- name = 'maketotaldestroy'
- self.assertTrue(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_invalid_symbols(self):
- """isValidRouterNickname() should return False for an invalid nickname
- (with symbols in it).
- """
- name = 'what_the_bl#@p?!'
- self.assertFalse(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_invalid_too_long(self):
- """isValidRouterNickname() should return False for an invalid nickname
- (too long).
- """
- name = 'ThisIsReallyMoreOfANovellaRatherThanAnOnionRouterNickname'
- self.assertFalse(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_invalid_too_short(self):
- """isValidRouterNickname() should return False for an invalid nickname
- (empty string).
- """
- name = ''
- self.assertFalse(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_invalid_None(self):
- """isValidRouterNickname(None) should return False."""
- name = None
- self.assertFalse(isValidRouterNickname(name))
-
- def test_parse_nickname_isValidRouterNickname_invalid_spaces(self):
- """isValidRouterNickname() should return False for an invalid nickname
- (contains spaces).
- """
- name = 'As you wish'
- self.assertFalse(isValidRouterNickname(name))
diff --git a/lib/bridgedb/test/test_parse_options.py b/lib/bridgedb/test/test_parse_options.py
deleted file mode 100644
index c505c36..0000000
--- a/lib/bridgedb/test/test_parse_options.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2014-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Unittests for :mod:`bridgedb.parse.options`."""
-
-
-from __future__ import print_function
-
-import os
-import sys
-
-from twisted.python.usage import UsageError
-from twisted.trial import unittest
-
-from bridgedb.parse import options
-
-
-class ParseOptionsTests(unittest.TestCase):
- """Unittests for :mod:`bridgedb.parse.options`."""
-
- def setUp(self):
- """Replace the current sys.argv's for the run of this test, and
- redirect sys.stdout to os.devnull to prevent the options parser from
- printing the --help a bunch of times.
- """
- # Make sure a config file is in the current directory, or else the
- # argument parser will get angry and throw another SystemExit
- # exception.
- with open(os.path.join(os.getcwd(), 'bridgedb.conf'), 'a+') as fh:
- fh.write('\n')
-
- self.oldSysArgv = sys.argv
- self.oldStdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
-
- def tearDown(self):
- """Put the original sys.argv's back."""
- sys.stdout.close() # Actually closes the FD we opened for /dev/null
- sys.argv = self.oldSysArgv
- sys.stdout = self.oldStdout
- self.oldSysArgv = None
- self.oldStdout = None
-
- def test_parse_options_parseOptions_with_invalid_options(self):
- """:func:`options.parseOptions` should raise SystemExit because
- the args 'somearg anotherarg' are invalid commands.
- """
- fakeSysArgv = ['somearg', 'anotherarg']
- sys.argv = fakeSysArgv
- self.assertRaises(SystemExit, options.parseOptions)
-
- def test_parse_options_parseOptions_with_valid_options(self):
- """:func:`options.parseOptions` should return a
- :class:`options.MainOptions` when given valid commandline arguments.
- """
- fakeSysArgv = ['bridgedb', 'mock', '-n', '-1']
- sys.argv = fakeSysArgv
- opts = options.parseOptions()
- self.assertIsInstance(opts, options.MainOptions)
-
- def test_parse_options_parseOptions_verbosity_quiet_quiet(self):
- """If we use `-q` twice on the commandline, ``opts['verbosity']``
- should equal ``10``.
- """
- fakeSysArgv = ['bridgedb', '-q', '-q', 'mock', '-n', '-1']
- sys.argv = fakeSysArgv
- opts = options.parseOptions()
- self.assertEqual(opts['verbosity'], 10)
-
- def test_parse_options_parseOptions_verbosity_verbose(self):
- """If we use `-v` once on the commandline, ``opts['verbosity']``
- should equal ``50``.
- """
- fakeSysArgv = ['bridgedb', '-v', '-v', 'mock', '-n', '-1']
- sys.argv = fakeSysArgv
- opts = options.parseOptions()
- self.assertEqual(opts['verbosity'], 50)
-
- def test_parse_options_parseOptions_rundir(self):
- """The automatic rundir should be our current directory."""
- fakeSysArgv = ['bridgedb', 'mock', '-n', '-1']
- sys.argv = fakeSysArgv
- opts = options.parseOptions()
- self.assertEqual(opts['rundir'], os.getcwd())
-
- def test_parse_options_parseOptions_version(self):
- """:func:`options.parseOptions` when given a `--version` argument on
- the commandline, should raise SystemExit (after printing some stuff,
- but we don't care what it prints).
- """
- fakeSysArgv = ['bridgedb', '--version']
- sys.argv = fakeSysArgv
- self.assertRaises(SystemExit, options.parseOptions)
diff --git a/lib/bridgedb/test/test_parse_versions.py b/lib/bridgedb/test/test_parse_versions.py
deleted file mode 100644
index becdd20..0000000
--- a/lib/bridgedb/test/test_parse_versions.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2014-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Unittests for :mod:`bridgedb.parse.versions`."""
-
-
-from __future__ import print_function
-
-from twisted.trial import unittest
-
-from bridgedb.parse import versions
-
-
-class ParseVersionTests(unittest.TestCase):
- """Unitests for :class:`bridgedb.parse.versions.Version`."""
-
- def test_Version_with_bad_delimiter(self):
- """Test parsing a version number which uses '-' as a delimiter."""
- self.assertRaises(versions.InvalidVersionStringFormat,
- versions.Version, '2-6-0', package='tor')
-
- def test_Version_really_long_version_string(self):
- """Parsing a version number which is way too long should raise
- an IndexError which is ignored.
- """
- v = versions.Version('2.6.0.0.beta', package='tor')
- self.assertEqual(v.prerelease, 'beta')
- self.assertEqual(v.major, 6)
-
- def test_Version_string(self):
- """Test converting a valid Version object into string form."""
- v = versions.Version('0.2.5.4', package='tor')
- self.assertEqual(v.base(), '0.2.5.4')
diff --git a/lib/bridgedb/test/test_persistent.py b/lib/bridgedb/test/test_persistent.py
deleted file mode 100644
index 95abf61..0000000
--- a/lib/bridgedb/test/test_persistent.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.persistent` module.
-
-These tests are meant to ensure that the :mod:`bridgedb.persistent` module is
-functioning as expected.
-"""
-
-from __future__ import print_function
-
-import os.path
-
-from copy import deepcopy
-from io import StringIO
-
-from bridgedb import persistent
-from bridgedb.parse.options import MainOptions
-from twisted.python import log
-from twisted.trial import unittest
-
-import sure
-from sure import this
-from sure import the
-from sure import expect
-
-
-TEST_CONFIG_FILE = StringIO(unicode("""\
-BRIDGE_FILES = ['bridge-descriptors', 'bridge-descriptors.new']
-LOGFILE = 'bridgedb.log'"""))
-
-
-class StateTest(unittest.TestCase):
- """Tests for :class:`bridgedb.persistent.State`."""
-
- timeout = 15
-
- def setUp(self):
- configuration = {}
- TEST_CONFIG_FILE.seek(0)
- compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
- exec compiled in configuration
- config = persistent.Conf(**configuration)
-
- fakeArgs = ['-c', os.path.join(os.getcwdu(), '..', 'bridgedb.conf')]
- options = MainOptions()
- options.parseOptions(fakeArgs)
-
- self.options = options
- self.config = config
- self.state = persistent.State(**config.__dict__)
- self.state.options = options
- self.state.config = config
-
- def test_configCreation(self):
- this(self.config).should.be.ok
- this(self.config).should.be.a(persistent.Conf)
-
- def test_optionsCreation(self):
- this(self.options).should.be.ok
- this(self.options).should.be.a(dict)
-
- def test_stateCreation(self):
- this(self.state).should.be.ok
-
- this(self.state).should.have.property('config').being.ok
- this(self.state).should.have.property('config').being.equal(self.config)
-
- this(self.state.options).should.be.ok
- this(self.state.options).should.equal(self.options)
-
- def test_docstring_persistent(self):
- persistent.should.have.property('__doc__').being.a(str)
-
- def test_docstring_persistentState(self):
- the(self.state).should.have.property('__doc__').being.a(str)
-
- def test_state_init(self):
- this(self.state).should.have.property('config')
- this(self.state).should.have.property('proxyList')
- this(self.state).should.have.property('statefile')
-
- def test_persistent_getState(self):
- persistent.should.have.property('_getState').being(callable)
- this(persistent._getState()).should.be.a(persistent.State)
-
- def test_getStateFor(self):
- jellyState = self.state.getStateFor(self)
- expect(jellyState).to.be.a(dict)
- expect(jellyState.keys()).to.contain('LOGFILE')
-
- def test_STATEFILE(self):
- this(self.state).should.have.property('statefile')
- the(self.state.statefile).should.be.a(str)
-
- def test_existsSave(self):
- this(self.state).should.have.property('save').being(callable)
-
- def test_existsLoad(self):
- persistent.should.have.property('load').being(callable)
-
- def test_persistent_state(self):
- the(persistent._state).should.be.a(persistent.State)
-
- def test_before_useChangedSettings_state(self):
- this(self.state).shouldnt.have.property('FOO')
- this(self.state).shouldnt.have.property('BAR')
- this(self.state).should.have.property('LOGFILE').being.a(str)
- this(self.state).should.have.property(
- 'BRIDGE_FILES').being.a(list)
-
- def test_before_useChangedSettings_config(self):
- this(self.config).shouldnt.have.property('FOO')
- this(self.config).shouldnt.have.property('BAR')
- this(self.config).should.have.property('LOGFILE').being.a(str)
- this(self.config).should.have.property(
- 'BRIDGE_FILES').being.a(list)
-
- def test_before_useChangedSettings_stateConfig(self):
- this(self.state.config).shouldnt.have.property('FOO')
- this(self.state.config).shouldnt.have.property('BAR')
- this(self.state.config).should.have.property('LOGFILE').being.a(str)
- this(self.state.config).should.have.property(
- 'BRIDGE_FILES').being.a(list)
-
- def test_useChangedSettings(self):
- # This deepcopying must be done to avoid changing the State object
- # which is used for the rest of the tests.
-
- thatConfig = deepcopy(self.config)
- thatState = deepcopy(self.state)
-
- setattr(thatConfig, 'FOO', 'fuuuuu')
- setattr(thatConfig, 'BAR', 'all of the things')
- setattr(thatConfig, 'LOGFILE', 42)
-
- this(thatConfig).should.have.property('FOO').being.a(basestring)
- this(thatConfig).should.have.property('BAR').being.a(basestring)
- this(thatConfig).should.have.property('LOGFILE').being.an(int)
- this(thatConfig).should.have.property('BRIDGE_FILES').being.a(list)
-
- the(thatConfig.FOO).must.equal('fuuuuu')
- the(thatConfig.BAR).must.equal('all of the things')
- the(thatConfig.LOGFILE).must.equal(42)
-
- the(thatState).should.have.property('useChangedSettings')
- the(thatState.useChangedSettings).should.be(callable)
- thatState.useChangedSettings(thatConfig)
-
- the(thatState.FOO).should.equal('fuuuuu')
- the(thatState).should.have.property('FOO').being.a(basestring)
- the(thatState).should.have.property('BAR').being.a(basestring)
- the(thatState).should.have.property('LOGFILE').being.an(int)
- the(thatState.FOO).must.equal(thatConfig.FOO)
- the(thatState.BAR).must.equal(thatConfig.BAR)
- the(thatState.LOGFILE).must.equal(thatConfig.LOGFILE)
-
- this(thatState.config).should.have.property('FOO')
- this(thatState.config).should.have.property('BAR')
- this(thatState.config).should.have.property('LOGFILE').being.an(int)
- this(thatState.config).should.have.property(
- 'BRIDGE_FILES').being.a(list)
diff --git a/lib/bridgedb/test/test_persistentSaveAndLoad.py b/lib/bridgedb/test/test_persistentSaveAndLoad.py
deleted file mode 100644
index 6d6584d..0000000
--- a/lib/bridgedb/test/test_persistentSaveAndLoad.py
+++ /dev/null
@@ -1,163 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.persistent` module.
-
-These tests ensure that :meth:`bridgedb.persistent.State.save`,
-:func:`bridgedb.persistent.load`, and :meth:`bridgedb.persistent.State.load`
-are all functioning as expected.
-
-This module should not import :mod:`sure`.
-"""
-
-import os
-
-from copy import deepcopy
-from io import StringIO
-
-from twisted.trial import unittest
-
-from bridgedb import persistent
-
-
-TEST_CONFIG_FILE = StringIO(unicode("""\
-BRIDGE_FILES = ['bridge-descriptors', 'bridge-descriptors.new']
-LOGFILE = 'bridgedb.log'"""))
-
-
-class StateSaveAndLoadTests(unittest.TestCase):
- """Test save() and load() of :mod:`~bridgedb.persistent`."""
-
- timeout = 15
-
- def setUp(self):
- configuration = {}
- TEST_CONFIG_FILE.seek(0)
- compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
- exec compiled in configuration
- config = persistent.Conf(**configuration)
-
- self.config = config
- self.state = persistent.State(**config.__dict__)
- self.state.config = config
- self.state.statefile = os.path.abspath('bridgedb.state')
-
- def loadedStateAssertions(self, loadedState):
- # For some reason, twisted.trial.unittest.TestCase in Python2.6
- # doesn't have an 'assertIsNotNone' attribute...
- self.assertTrue(loadedState is not None)
- self.assertIsInstance(loadedState, persistent.State)
- self.assertNotIdentical(self.state, loadedState)
- self.assertNotEqual(self.state, loadedState)
- # For some reason, twisted.trial.unittest.TestCase in Python2.6
- # doesn't have an 'assertItemsEqual' attribute...
- self.assertEqual(self.state.__dict__.keys().sort(),
- loadedState.__dict__.keys().sort())
-
- def savedStateAssertions(self, savedStatefile=None):
- self.assertTrue(os.path.isfile(str(self.state.statefile)))
- if savedStatefile:
- self.assertTrue(os.path.isfile(str(savedStatefile)))
-
- def test_init_with_STATEFILE(self):
- config = self.config
- setattr(config, 'STATEFILE', '~/foo.state')
- state = persistent.State(**config.__dict__)
- self.loadedStateAssertions(state)
- statefile = state.statefile
- self.assertTrue(statefile.endswith('foo.state'))
-
- def test_init_without_config(self):
- state = persistent.State(None)
- self.loadedStateAssertions(state)
-
- def test_init_with_config(self):
- state = persistent.State(self.config)
- self.loadedStateAssertions(state)
-
- def test_get_statefile(self):
- statefile = self.state._get_statefile()
- self.assertIsInstance(statefile, basestring)
-
- def test_set_statefile(self):
- self.state._set_statefile('bar.state')
- statefile = self.state._get_statefile()
- self.assertIsInstance(statefile, basestring)
-
- def test_set_statefile_new_dir(self):
- config = self.config
- setattr(config, 'STATEFILE', 'statefiles/foo.state')
- state = persistent.State(**config.__dict__)
- self.loadedStateAssertions(state)
- statefile = state.statefile
- self.assertTrue(statefile.endswith('foo.state'))
-
- def test_del_statefile(self):
- self.state._set_statefile('baz.state')
- self.state._del_statefile()
- statefile = self.state._get_statefile()
- self.assertIsNone(statefile)
-
- def test_save(self):
- self.state.save()
- self.savedStateAssertions()
-
- def test_stateSaveTempfile(self):
- savefile = self.mktemp()
- self.state.statefile = savefile
- self.state.save(savefile)
- savedStatefile = str(self.state.statefile)
-
- def test_stateLoadTempfile(self):
- savefile = self.mktemp()
- self.state.statefile = savefile
- self.assertTrue(self.state.statefile.endswith(savefile))
- self.state.save(savefile)
- self.savedStateAssertions(savefile)
- loadedState = self.state.load(savefile)
- self.loadedStateAssertions(loadedState)
-
- def test_stateSaveAndLoad(self):
- self.state.save()
- loadedState = self.state.load()
- self.loadedStateAssertions(loadedState)
-
- def test_load(self):
- self.state.save()
- loadedState = persistent.load()
- self.loadedStateAssertions(loadedState)
-
- def test_load_with_state(self):
- loadedState = persistent.load(self.state)
- self.loadedStateAssertions(loadedState)
-
- def test_load_with_None(self):
- persistent._setState(None)
- self.assertRaises(persistent.MissingState,
- persistent.load, None)
-
- def test_load_with_statefile(self):
- self.assertRaises(persistent.MissingState,
- self.state.load, 'quux.state')
-
- def test_load_with_statefile_opened(self):
- fh = open('quux.state', 'w+')
- self.assertRaises(persistent.MissingState, self.state.load, fh)
- fh.close()
-
- def test_load_with_statefile_object(self):
- self.assertRaises(persistent.MissingState, self.state.load, object)
-
- def test_load_without_statefile(self):
- persistent._setState(None)
- self.state.statefile = None
- self.assertRaises(persistent.MissingState,
- persistent.load)
diff --git a/lib/bridgedb/test/test_proxy.py b/lib/bridgedb/test/test_proxy.py
deleted file mode 100644
index 909218d..0000000
--- a/lib/bridgedb/test/test_proxy.py
+++ /dev/null
@@ -1,590 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013-2015 Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: 3-clause BSD, see included LICENSE for information
-
-"""Tests for :mod:`bridgedb.proxy`."""
-
-import sure
-
-from twisted.internet import defer
-from twisted.internet import reactor
-from twisted.trial import unittest
-
-from bridgedb import proxy
-
-
-EXIT_LIST_0 = """\
-11.11.11.11
-22.22.22.22
-123.45.67.89"""
-
-EXIT_LIST_1 = """\
-33.33.33.33
-44.44.44.44
-55.55.55.55
-66.66.66.66
-77.77.77.77"""
-
-EXIT_LIST_BAD = """\
-foo
-bar
-baz"""
-
-
-class MockExitListProtocol(proxy.ExitListProtocol):
- """A mocked version of :class:`~bridgedb.proxy.ExitListProtocol`."""
-
- def __init__(self):
- proxy.ExitListProtocol.__init__(self)
- self._data = EXIT_LIST_0
- self.script = '/bin/echo'
- print()
-
- def _log(self, msg):
- print("%s: %s" % (self.__class__.__name__, msg))
-
- def childConnectionLost(self, childFD):
- self._log("childConnectionLost() called with childFD=%s" % childFD)
- proxy.ExitListProtocol.childConnectionLost(self, childFD)
-
- def connectionMade(self):
- self._log("connectionMade() called")
- proxy.ExitListProtocol.connectionMade(self)
-
- def errReceived(self, data):
- self._log("errReceived() called with %s" % data)
- proxy.ExitListProtocol.errReceived(self, data)
-
- def outReceivedData(self, data):
- self._log("outReceivedData() called with %s" % data)
- proxy.ExitListProtocol.outReceivedData(self, data)
-
- def outConnectionLost(self):
- self._log("outConnectionLost() called")
- proxy.ExitListProtocol.outConnectionLost(self)
-
- def parseData(self):
- data = self._data.split('\n')
- for line in data:
- line = line.strip()
- if not line: continue
- if line.startswith('<'): break
- if line.startswith('#'): continue
- ip = proxy.isIPAddress(line)
- if ip:
- self._log("adding IP %s to exitlist..." % ip)
- self.exitlist.add(ip)
-
- def processEnded(self, reason):
- self._log("processEnded() called with %s" % reason)
- proxy.ExitListProtocol.processEnded(self, reason)
-
- def processExited(self, reason):
- self._log("processExited() called with %s" % reason)
- proxy.ExitListProtocol.processExited(self, reason)
-
-
-class ProxySetImplementationTest(unittest.TestCase):
-
- def setUp(self):
- # We have to put something in it, otherwise self.ps.should.be.ok won't
- # think it's truthy:
- self.ps = proxy.ProxySet(['1.1.1.1'])
-
- def test_instantiation(self):
- self.ps.should.be.ok
- self.ps.should.have.property('__contains__').being.callable
- self.ps.should.have.property('__hash__').being.callable
- self.ps.should.have.property('__iter__').being.callable
- self.ps.should.have.property('__len__').being.callable
- self.ps.should.have.property('add').being.callable
- self.ps.should.have.property('copy').being.callable
- self.ps.should.have.property('contains').being.callable
- self.ps.should.have.property('discard').being.callable
- self.ps.should.have.property('remove').being.callable
-
- self.ps.should.have.property('difference').being.callable
- self.ps.should.have.property('issubset').being.callable
- self.ps.should.have.property('issuperset').being.callable
- self.ps.should.have.property('intersection').being.callable
- self.ps.should.have.property('symmetric_difference').being.callable
- self.ps.should.have.property('union').being.callable
-
- def test_attributes(self):
- self.ps.should.have.property('proxies').being.a(list)
- self.ps.should.have.property('exitRelays').being.a(set)
- self.ps.should.have.property('_proxies').being.a(set)
- self.ps.should.have.property('_proxydict').being.a(dict)
-
-
-class LoadProxiesFromFileIntegrationTests(unittest.TestCase):
- """Unittests for :class:`~bridgedb.proxy.loadProxiesFromFile()`."""
-
- def setUp(self):
- self.fn0 = '%s-0' % self.__class__.__name__
- self.fn1 = '%s-1' % self.__class__.__name__
- self.badfile = '%s-badfile' % self.__class__.__name__
- self.writeFiles()
-
- def writeFiles(self):
- with open(self.fn0, 'w') as fh:
- fh.write(EXIT_LIST_0)
- fh.flush()
- with open(self.fn1, 'w') as fh:
- fh.write(EXIT_LIST_1)
- fh.flush()
- with open(self.badfile, 'w') as fh:
- fh.write(EXIT_LIST_BAD)
- fh.flush()
-
- def emptyFile(self, filename):
- """We have to do this is a separate method, otherwise Twisted doesn't
- actually do it.
- """
- fh = open(filename, 'w')
- fh.truncate()
- fh.close()
-
- def test_proxy_loadProxiesFromFile_1_file(self):
- """Test loading proxies from one file."""
- proxies = proxy.loadProxiesFromFile(self.fn0)
- self.assertEqual(len(proxies), 3)
-
- def test_proxy_loadProxiesFromFile_1_file_missing(self):
- """Test loading proxies from one file that doesn't exist."""
- proxies = proxy.loadProxiesFromFile('%s-missing' % self.__class__.__name__)
- self.assertEqual(len(proxies), 0)
-
- def test_proxy_loadProxiesFromFile_1_file_and_proxyset(self):
- """Test loading proxies from one file."""
- proxyList = proxy.ProxySet(['1.1.1.1'])
- proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
- self.assertEqual(len(proxies), 3)
- self.assertEqual(len(proxyList), 4)
-
- def test_proxy_loadProxiesFromFile_2_files_and_proxyset(self):
- """Test loading proxies from two files."""
- proxyList = proxy.ProxySet(['1.1.1.1'])
- proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
- proxies = proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
- self.assertEqual(len(proxies), 5)
- self.assertEqual(len(proxyList), 9)
-
- def test_proxy_loadProxiesFromFile_removeStale(self):
- """Test loading proxies from two files and removing the stale ones."""
- proxyList = proxy.ProxySet(['1.1.1.1'])
- self.assertEqual(len(proxyList), 1)
- proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
- self.assertEqual(len(proxies), 3)
- self.assertEqual(len(proxyList), 4)
- proxies = proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
- self.assertEqual(len(proxies), 5)
- self.assertEqual(len(proxyList), 9)
-
- self.emptyFile(self.fn0)
- proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList,
- removeStale=True)
- self.assertEqual(len(proxies), 0)
- self.assertEqual(len(proxyList), 6)
-
- def test_proxy_loadProxiesFromFile_duplicates(self):
- """Loading proxies from the same file twice shouldn't store
- duplicates.
- """
- proxyList = proxy.ProxySet(['1.1.1.1'])
- proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
- self.assertEqual(len(proxyList), 6)
- proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
- self.assertEqual(len(proxyList), 6)
-
- def test_proxy_loadProxiesFromFile_bad_file(self):
- """Loading proxies from a file with invalid IPs in it should do
- nothing.
- """
- proxyList = proxy.ProxySet()
- proxy.loadProxiesFromFile(self.badfile, proxySet=proxyList)
- self.assertEqual(len(proxyList), 0)
-
-
-class DownloadTorExitsTests(unittest.TestCase):
- """Tests for `~bridgedb.proxy.downloadTorExits()`."""
-
- def setUp(self):
- self.protocol = MockExitListProtocol
- self.proxyList = proxy.ProxySet()
-
- def tearDown(self):
- """Cleanup method after each ``test_*`` method runs; removes all
- selectable readers and writers from the reactor.
- """
- reactor.removeAll()
-
- def test_proxy_downloadTorExits(self):
- def do_test():
- return proxy.downloadTorExits(self.proxyList,
- 'OurIPWouldGoHere',
- protocol=self.protocol)
- d = do_test()
-
-
-class ProxySetUnittests(unittest.TestCase):
- """Unittests for :class:`~bridgedb.proxy.ProxySet`."""
-
- def setUp(self):
- self.proxies = EXIT_LIST_1.split('\n')
- self.moarProxies = EXIT_LIST_0.split('\n')
-
- self.proxyList = proxy.ProxySet()
- for p in self.proxies:
- self.proxyList.add(p)
-
- def test_ProxySet_init(self):
- """When initialised (after setUp() has run), the ProxySet should
- contain a number of proxies equal to the number we added in the setUp()
- method.
- """
- self.assertEquals(len(self.proxyList), len(self.proxies))
-
- def test_ProxySet_proxies_getter(self):
- """ProxySet.proxies should list all proxies."""
- self.assertItemsEqual(self.proxyList.proxies, set(self.proxies))
-
- def test_ProxySet_proxies_setter(self):
- """``ProxySet.proxies = ['foo']`` should raise an ``AttributeError``."""
- self.assertRaises(AttributeError, self.proxyList.__setattr__, 'proxies', ['foo'])
-
- def test_ProxySet_proxies_deleter(self):
- """``del(ProxySet.proxies)`` should raise an AttributeError."""
- self.assertRaises(AttributeError, self.proxyList.__delattr__, 'proxies')
-
- def test_ProxySet_exitRelays_issubset_proxies(self):
- """ProxySet.exitRelays should always be a subset of ProxySet.proxies."""
- self.assertTrue(self.proxyList.exitRelays.issubset(self.proxyList.proxies))
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertTrue(self.proxyList.exitRelays.issubset(self.proxyList.proxies))
-
- def test_ProxySet_exitRelays_getter(self):
- """ProxySet.exitRelays should list all exit relays."""
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertItemsEqual(self.proxyList.exitRelays, set(self.moarProxies))
-
- def test_ProxySet_exitRelays_setter(self):
- """``ProxySet.exitRelays = ['foo']`` should raise an ``AttributeError``."""
- self.assertRaises(AttributeError, self.proxyList.__setattr__, 'exitRelays', ['foo'])
-
- def test_ProxySet_exitRelays_deleter(self):
- """``del(ProxySet.exitRelays)`` should raise an AttributeError."""
- self.assertRaises(AttributeError, self.proxyList.__delattr__, 'exitRelays')
-
- def test_ProxySet_add_new(self):
- """ProxySet.add() should add a new proxy."""
- self.proxyList.add('110.110.110.110')
- self.assertEquals(len(self.proxyList), len(self.proxies) + 1)
- self.assertIn('110.110.110.110', self.proxyList)
-
- def test_ProxySet_add_None(self):
- """ProxySet.add() called with None should return False."""
- self.assertFalse(self.proxyList.add(None))
- self.assertEquals(len(self.proxyList), len(self.proxies))
-
- def test_ProxySet_add_duplicate(self):
- """ProxySet.add() shouldn't add the same proxy twice."""
- self.proxyList.add(self.proxies[0])
- self.assertEquals(len(self.proxyList), len(self.proxies))
- self.assertIn(self.proxies[0], self.proxyList)
-
- def test_ProxySet_addExitRelays(self):
- """ProxySet.addExitRelays() should add the new proxies."""
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertIn(self.moarProxies[0], self.proxyList)
-
- def test_ProxySet_radd_new(self):
- """ProxySet.radd() should add a new proxy."""
- self.proxyList.__radd__('110.110.110.110')
- self.assertEquals(len(self.proxyList), len(self.proxies) + 1)
- self.assertIn('110.110.110.110', self.proxyList)
-
- def test_ProxySet_addExitRelays_tagged(self):
- """ProxySet.addExitRelays() should add the new proxies, and they should
- be tagged as being Tor exit relays.
- """
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertTrue(self.proxyList.isExitRelay(self.moarProxies[0]))
- self.assertEquals(self.proxyList.getTag(self.moarProxies[0]),
- self.proxyList._exitTag)
-
- def test_ProxySet_addExitRelays_length(self):
- """ProxySet.addExitRelays() should add the new proxies and then the
- total number should be equal to the previous number of proxies plus the
- new exit relays added.
- """
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
-
- def test_ProxySet_addExitRelays_previous_proxies_kept(self):
- """ProxySet.addExitRelays() should add the new proxies and keep ones that
- were already in the ProxySet.
- """
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertIn(self.proxies[0], self.proxyList)
-
- def test_ProxySet_addExitRelays_previous_proxies_not_tagged(self):
- """ProxySet.addExitRelays() should add the new proxies and tag them,
- but any previous non-exit relays in the ProxySet shouldn't be tagged as
- being Tor exit relays.
- """
- self.proxyList.addExitRelays(self.moarProxies)
- self.assertFalse(self.proxyList.isExitRelay(self.proxies[0]))
- self.assertNotEquals(self.proxyList.getTag(self.proxies[0]),
- self.proxyList._exitTag)
-
- def test_ProxySet_addProxies_tuple_individual_tags(self):
- """ProxySet.addProxies() should add the new proxies and tag them with
- whatever tags we want.
- """
- tags = ['foo', 'bar', 'baz']
- extraProxies = zip(self.moarProxies, tags)
- self.proxyList.addProxies(extraProxies)
- self.assertEquals(len(self.proxyList), len(self.proxies) + len(extraProxies))
- self.assertIn(extraProxies[0][0], self.proxyList)
- self.assertEquals(self.proxyList._proxydict[extraProxies[0][0]], extraProxies[0][1])
- self.assertEquals(self.proxyList._proxydict[extraProxies[1][0]], extraProxies[1][1])
- self.assertEquals(self.proxyList._proxydict[extraProxies[2][0]], extraProxies[2][1])
-
- def test_ProxySet_addProxies_tuple_too_many_items(self):
- """``ProxySet.addProxies()`` where the tuples have >2 items should
- raise a ValueError.
- """
- extraProxies = zip(self.moarProxies,
- ['sometag' for _ in range(len(self.moarProxies))],
- ['othertag' for _ in range(len(self.moarProxies))])
- self.assertRaises(ValueError, self.proxyList.addProxies, extraProxies)
-
- def test_ProxySet_addProxies_list(self):
- """``ProxySet.addProxies(..., tag='sometag')`` should add the new
- proxies and tag them all with the same tag.
- """
- self.proxyList.addProxies(self.moarProxies, tag='sometag')
- self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
- self.assertIn(self.moarProxies[0], self.proxyList)
- for p in self.moarProxies:
- self.assertEquals(self.proxyList.getTag(p), 'sometag')
- for p in self.proxies:
- self.assertNotEqual(self.proxyList.getTag(p), 'sometag')
-
- def test_ProxySet_addProxies_set(self):
- """``ProxySet.addProxies(..., tag=None)`` should add the new
- proxies and tag them all with timestamps.
- """
- self.proxyList.addProxies(set(self.moarProxies))
- self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
- self.assertIn(self.moarProxies[0], self.proxyList)
- for p in self.moarProxies:
- self.assertIsInstance(self.proxyList.getTag(p), float)
- for p in self.proxies:
- self.assertNotEqual(self.proxyList.getTag(p), 'sometag')
-
- def test_ProxySet_addProxies_bad_type(self):
- """``ProxySet.addProxies()`` called with something which is neither an
- iterable, a basestring, or an int should raise a ValueError.
- """
- self.assertRaises(ValueError, self.proxyList.addProxies, object)
-
- def test_ProxySet_addProxies_list_of_bad_types(self):
- """``ProxySet.addProxies()`` called with something which is neither an
- iterable, a basestring, or an int should raise a ValueError.
- """
- self.assertRaises(ValueError, self.proxyList.addProxies, [object, object, object])
-
- def test_ProxySet_getTag(self):
- """ProxySet.getTag() should get the tag for a proxy in the set."""
- self.proxyList.add('1.1.1.1', 'bestproxyevar')
- self.assertEquals(self.proxyList.getTag('1.1.1.1'), 'bestproxyevar')
-
- def test_ProxySet_getTag_nonexistent(self):
- """ProxySet.getTag() should get None for a proxy not in the set."""
- self.assertIsNone(self.proxyList.getTag('1.1.1.1'))
-
- def test_ProxySet_clear(self):
- """ProxySet.clear() should clear the set of proxies."""
- self.proxyList.clear()
- self.assertEquals(len(self.proxyList), 0)
- self.assertEquals(len(self.proxyList.proxies), 0)
- self.assertEquals(len(self.proxyList._proxies), 0)
- self.assertEquals(len(self.proxyList._proxydict.items()), 0)
-
- def test_ProxySet_contains_list(self):
- """Calling ``list() is in ProxySet()`` should return False."""
- self.assertFalse(self.proxyList.contains(list(self.proxies[0],)))
-
- def test_ProxySet_contains_nonexistent(self):
- """``ProxySet().contains()`` with a proxy not in the set should
- return False.
- """
- self.assertFalse(self.proxyList.contains(self.moarProxies[0]))
-
- def test_ProxySet_contains_nonexistent(self):
- """``ProxySet().contains()`` with a proxy in the set should
- return True.
- """
- self.assertTrue(self.proxyList.contains(self.proxies[0]))
-
- def test_ProxySet_copy(self):
- """ProxySet.copy() should create an exact copy."""
- newProxyList = self.proxyList.copy()
- self.assertEquals(newProxyList, self.proxyList)
-
- def test_ProxySet_difference(self):
- """ProxySet.difference() should list the items in ProxySetA which
- aren't in ProxySetB.
- """
- proxySetA = self.proxyList
- proxySetB = proxy.ProxySet(self.moarProxies)
- self.assertItemsEqual(proxySetA.difference(proxySetB),
- set(self.proxies))
- self.assertItemsEqual(proxySetB.difference(proxySetA),
- set(self.moarProxies))
-
- def test_ProxySet_firstSeen_returns_timestamp(self):
- """ProxySet.firstSeen() should return a timestamp for a proxy with a
- timestamp tag.
- """
- self.proxyList.add(self.moarProxies[0])
- self.assertIsNotNone(self.proxyList.firstSeen(self.moarProxies[0]))
-
- def test_ProxySet_firstSeen_returns_float(self):
- """ProxySet.firstSeen() should return a timestamp for a proxy with a
- timestamp tag.
- """
- self.proxyList.add(self.moarProxies[1])
- self.assertIsInstance(self.proxyList.firstSeen(self.moarProxies[1]), float)
-
- def test_ProxySet_firstSeen_other_tags(self):
- """ProxySet.firstSeen() should return None when a proxy doesn't have a
- timestamp.
- """
- self.proxyList.add(self.moarProxies[2], 'sometag')
- self.assertIsNone(self.proxyList.firstSeen(self.moarProxies[2]))
-
- def test_ProxySet_issubset(self):
- """ProxySet.issubset() on a superset should return True."""
- self.assertTrue(self.proxyList.issubset(set(self.proxies + self.moarProxies[:0])))
-
- def test_ProxySet_issuperset(self):
- """ProxySet.issubset() on a subset should return True."""
- self.assertTrue(self.proxyList.issuperset(set(self.proxies[:1])))
-
- def test_ProxySet_intersection(self):
- """ProxySet.intersection() should return the combination of two
- disjoint sets.
- """
- raise unittest.SkipTest(
- ("FIXME: bridgedb.proxy.ProxySet.intersection() is broken and "
- "always returns an empty set()."))
-
- a = self.proxies
- a.extend(self.moarProxies)
- a = set(a)
- b = self.proxyList.intersection(set(self.moarProxies))
- self.assertItemsEqual(a, b)
-
- def test_ProxySet_remove(self):
- """ProxySet.remove() should subtract proxies which were already added
- to the set.
- """
- self.proxyList.remove(self.proxies[0])
- self.assertEquals(len(self.proxyList), len(self.proxies) - 1)
- self.assertNotIn(self.proxies[0], self.proxyList)
-
- def test_ProxySet_remove_nonexistent(self):
- """ProxySet.remove() shouldn't subtract proxies which aren't already in
- the set.
- """
- self.proxyList.remove('110.110.110.110')
- self.assertEquals(len(self.proxyList), len(self.proxies))
- self.assertNotIn('110.110.110.110', self.proxyList)
-
- def test_ProxySet_replaceProxyList(self):
- """ProxySet.replaceProxyList should remove all the current proxies and
- add all the new ones.
- """
- self.proxyList.replaceProxyList(self.moarProxies, 'seven proxies')
- for p in self.moarProxies:
- self.assertIn(p, self.proxyList)
- self.assertEqual(self.proxyList.getTag(p), 'seven proxies')
- for p in self.proxies:
- self.assertNotIn(p, self.proxyList)
-
- def test_ProxySet_replaceProxyList_bad_type(self):
- """ProxySet.replaceProxyList should remove all the current proxies and
- then since we're giving it a bad type it should do nothing else.
- """
- self.proxyList.replaceProxyList([object, object, object])
- self.assertEqual(len(self.proxyList), 0)
-
- def test_ProxySet_hash(self):
- """Two equal ProxySets should return the same hash."""
- proxyListA = proxy.ProxySet(self.proxies)
- proxyListB = proxy.ProxySet(self.proxies)
- self.assertEqual(proxyListA, proxyListB)
- self.assertItemsEqual(proxyListA, proxyListB)
- self.assertEqual(hash(proxyListA), hash(proxyListB))
-
-
-class ExitListProtocolTests(unittest.TestCase):
- """Unittests for :class:`~bridgedb.proxy.ExitListProtocol`."""
-
- def setUp(self):
- self.proto = proxy.ExitListProtocol()
-
- def test_ExitListProtocol_parseData_error_page(self):
- """ """
- self.proto.data = """\
-<!doctype html>
-<html lang="en">
-<body>
- <div class="content">
- <img src="/torcheck/img/tor-on.png" class="onion" />
- <h4>Welcome to the Tor Bulk Exit List exporting tool.</h4>
- </div>
-</body>
-</html>"""
- self.proto.parseData()
- self.assertEqual(len(self.proto.exitlist), 0)
-
- def test_ExitListProtocol_parseData_page_with_3_ips_with_comments(self):
- """ """
- self.proto.data = """\
-# This is a list of all Tor exit nodes from the past 16 hours that can contact 1.1.1.1 on port 443 #
-# You can update this list by visiting https://check.torproject.org/cgi-bin/TorBulkExitList.py?ip=1.1.1.1&port=443 #
-# This file was generated on Fri Feb 6 02:04:27 UTC 2015 #
-101.99.64.150
-103.10.197.50
-103.240.91.7"""
- self.proto.parseData()
- self.assertEqual(len(self.proto.exitlist), 3)
-
- def test_ExitListProtocol_parseData_page_with_3_ips(self):
- """ """
- self.proto.data = """
-101.99.64.150
-103.10.197.50
-103.240.91.7"""
- self.proto.parseData()
- self.assertEqual(len(self.proto.exitlist), 3)
-
- def test_ExitListProtocol_parseData_page_with_bad_ip(self):
- """ """
- self.proto.data = """
-192.168.0.1
-127.0.0.1
-103.240.91.7"""
- self.proto.parseData()
- self.assertEqual(len(self.proto.exitlist), 1)
diff --git a/lib/bridgedb/test/test_qrcodes.py b/lib/bridgedb/test/test_qrcodes.py
deleted file mode 100644
index 4abe8cc..0000000
--- a/lib/bridgedb/test/test_qrcodes.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-#_____________________________________________________________________________
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2007-2015, The Tor Project, Inc.
-# (c) 2014-2015, Isis Lovecruft
-# :license: see LICENSE for licensing information
-#_____________________________________________________________________________
-
-"""Tests for :mod:`bridgedb.qrcodes`."""
-
-
-from twisted.trial import unittest
-
-from bridgedb import qrcodes
-
-
-class GenerateQRTests(unittest.TestCase):
- """Unittests for :func:`bridgedb.qrcodes.generateQR`."""
-
- def setUp(self):
- self.qrcodeModule = qrcodes.qrcode
- bridgelines = [
- "obfs4 63.125.48.205:26573 441a151632806a3cc42adfecc2e6e823299db7b2 iat-mode=1 public-key=8d27ba37c5d810106b55f3fd6cdb35842007e88754184bfc0e6035f9bcede633 node-id=42d2a6ad49f93ab4b987b1a9e738425aacb8d2af",
- "obfs4 103.111.131.45:43288 cb1362f8eaf5d3c2b6fad5da300f66c4197f23e5 iat-mode=0 public-key=c75cb66ae28d8ebc6eded002c28a8ba0d06d3a78c6b5cbf9b2ade051f0775ac4 node-id=4cd66dfabbd964f8c6c4414b07cdb45dae692e19",
- "obfs4 17.194.28.21:5530 86ef8ab343e76bcd3c57ee32febe4482c98141c7 iat-mode=0 public-key=36ebe205bcdfc499a25e6923f4450fa8d48196ceb4fa0ce077d9d8ec4a36926d node-id=6b6277afcb65d33525545904e95c2fa240632660",
- ]
- self.bridgelines = '\n'.join(bridgelines)
-
- def tearDown(self):
- """Replace the qrcode module to its original form."""
- qrcodes.qrcode = self.qrcodeModule
-
- def test_generateQR(self):
- """Calling generateQR() should generate an image."""
- self.assertTrue(qrcodes.generateQR(self.bridgelines))
-
- def test_generateQR_bad_bridgelines(self):
- """Calling generateQR() with a bad type for the bridgelines should
- return None.
- """
- self.assertIsNone(qrcodes.generateQR(list()))
-
- def test_generateQR_no_bridgelines(self):
- """Calling generateQR() without bridgelines should return None."""
- self.assertIsNone(qrcodes.generateQR(""))
-
- def test_generateQR_no_qrcode_module(self):
- """Calling generateQR() without the qrcode module installed should
- return None.
- """
- qrcodes.qrcode = None
- self.assertIsNone(qrcodes.generateQR(self.bridgelines))
-
- def test_generateQR_bridgeSchema(self):
- """Calling generateQR() with bridgeSchema=True should prepend
- ``'bridge://`` to each of the QR encoded bridge lines.
- """
- # If we were to install the python-qrtools Debian package, we'd be
- # able to decode the resulting QRCode to check that it contains the
- # 'bridge://' prefix for each bridge line⦠but that would add another
- # Debian dependency just to unittest 5 lines of code.
- #
- # Instead:
- self.assertTrue(qrcodes.generateQR(self.bridgelines, bridgeSchema=True))
-
- def test_generateQR_save_nonexistent_format(self):
- """Calling generateQR() with imageFormat=u'FOOBAR' should return None.
- """
- self.assertIsNone(qrcodes.generateQR(self.bridgelines, imageFormat=u'FOOBAR'))
diff --git a/lib/bridgedb/test/test_safelog.py b/lib/bridgedb/test/test_safelog.py
deleted file mode 100644
index 247db06..0000000
--- a/lib/bridgedb/test/test_safelog.py
+++ /dev/null
@@ -1,335 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""Unittests for :mod:`bridgedb.safelog`."""
-
-import re
-
-from twisted.test.proto_helpers import StringTransport
-from twisted.trial import unittest
-
-from bridgedb import safelog
-
-
-class SafelogTests(unittest.TestCase):
- """Tests for functions and attributes in :mod:`bridgedb.safelog`."""
-
- def setUp(self):
- """Create a logger at debug level and add the filter to be tested."""
- self.logfile = StringTransport()
- self.handler = safelog.logging.StreamHandler(self.logfile)
- self.logger = safelog.logging.getLogger(self.__class__.__name__)
- self.logger.setLevel(10)
- self.logger.addHandler(self.handler)
- self.sensitiveData = 'Nicholas Bourbaki'
-
- def tearDown(self):
- """Rewind and truncate the logfile so that we have an empty one."""
- self.logfile.clear()
-
- def test_setSafeLogging_off(self):
- """Calls to ``logSafely()`` should return the original data when
- ``safe_logging`` is disabled.
- """
- safelog.setSafeLogging(False)
- self.logger.warn("Got a connection from %s..."
- % safelog.logSafely(self.sensitiveData))
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- #self.assertSubstring("Got a connection from", contents)
- #self.assertSubstring(self.sensitiveData, contents)
- #self.failIfSubstring("[scrubbed]", contents)
-
- def test_setSafeLogging_on(self):
- """Calls to ``logSafely()`` should return ``"[scrubbed]"`` for any
- arbitrary data when ``safe_logging`` is enabled.
- """
- safelog.setSafeLogging(True)
- self.logger.warn("Got a connection from %s..."
- % safelog.logSafely(self.sensitiveData))
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- #self.assertSubstring("Got a connection from", contents)
- #self.failIfSubstring(self.sensitiveData, contents)
- #self.assertSubstring("[scrubbed]", contents)
-
-
-class BaseSafelogFilterTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.safelog.BaseSafelogFilter`."""
-
- def setUp(self):
- safelog.setSafeLogging(True)
- self.logfile = StringTransport()
- self.handler = safelog.logging.StreamHandler(self.logfile)
- self.logger = safelog.logging.getLogger(self.__class__.__name__)
- self.logger.setLevel(10)
- self.logger.addHandler(self.handler)
- self.filter = safelog.BaseSafelogFilter()
- self.logger.addFilter(self.filter)
-
- self.logMessage = "testing 1 2 3"
- self.record = safelog.logging.LogRecord('name', 10, __file__, 1337,
- self.logMessage, {}, None)
-
- def test_doubleCheck(self):
- """BaseSafelogFilter.doubleCheck() should always return True."""
- checked = self.filter.doubleCheck(self.logMessage)
- self.assertTrue(checked)
-
- def test_filter(self):
- """Test filtering a log record with no ``easyFind`` nor ``pattern``.
-
- The ``LogRecord.message`` shouldn't change.
- """
- filtered = self.filter.filter(self.record)
- self.assertEqual(filtered.getMessage(), self.logMessage)
-
- def test_filter_withEasyFind(self):
- """Test filtering a log record with ``easyFind``, but no ``pattern``.
-
- The ``LogRecord.message`` shouldn't change.
- """
- self.filter.easyFind = "2"
- filtered = self.filter.filter(self.record)
- self.assertEqual(filtered.getMessage(), self.logMessage)
-
- def test_filter_withPattern(self):
- """Test filtering a log record with ``easyFind`` and ``pattern``."""
- self.filter.easyFind = "2"
- self.filter.pattern = re.compile("1 2 3")
- filtered = self.filter.filter(self.record)
- self.assertEqual(filtered.msg, "testing [scrubbed]")
-
-
-class SafelogEmailFilterTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.safelog.SafelogEmailFilter`."""
-
- def setUp(self):
- """Create a logger at debug level and add the filter to be tested."""
- self.logfile = StringTransport()
- self.handler = safelog.logging.StreamHandler(self.logfile)
- self.filter = safelog.SafelogEmailFilter()
- self.logger = safelog.logging.getLogger(self.__class__.__name__)
- self.logger.setLevel(10)
- self.logger.addHandler(self.handler)
- self.logger.addFilter(self.filter)
- self.s1 = "Here is an email address: "
- self.s2 = "blackhole at torproject.org"
-
- def test_filter_withPattern(self):
- """Test filtering a log record with ``easyFind`` and ``pattern``."""
- record = safelog.logging.LogRecord('name', 10, __file__, 1337,
- "testing blackhole at torproject.org",
- {}, None)
- filtered = self.filter.filter(record)
- self.assertEqual(filtered.msg, "testing [scrubbed]")
-
- def test_debugLevel(self):
- self.logger.debug("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- # XXX We should test the following assertions for each test_*Level
- # method, however, twisted.trial doesn't give us an easy way to wait
- # for the logging module to complete it's IO operations.
- #self.assertSubstring(self.s1, contents)
- #self.failIfSubstring(self.s2, contents)
-
- def test_infoLevel(self):
- self.logger.info("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_warnLevel(self):
- self.logger.warn("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_errorLevel(self):
- self.logger.error("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_exceptionLevel(self):
- try:
- raise Exception("%s %s" % (self.s1, self.s2))
- except Exception as error:
- self.logger.exception(error)
-
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- #self.assertSubstring(self.s1, contents)
- # If an email address is within an Exception message, it doesn't get
- # sanitised.
- #self.assertSubstring(self.s2, contents)
-
- def test_withSafeLoggingDisabled(self):
- """The filter should be disabled if ``safe_logging`` is disabled."""
- safelog.setSafeLogging(False)
- self.logger.info("%s %s" % (self.s1, self.s2))
- self.logfile.io.flush()
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- #self.assertSubstring(self.s1, contents)
- #self.assertSubstring(self.s2, contents)
-
-
-class SafelogIPv4FilterTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.safelog.SafelogIPv4Filter`."""
-
- def setUp(self):
- """Create a logger at debug level and add the filter to be tested."""
- self.logfile = StringTransport()
- self.handler = safelog.logging.StreamHandler(self.logfile)
- self.filter = safelog.SafelogIPv4Filter()
- self.logger = safelog.logging.getLogger(str(self.__class__.__name__))
- self.logger.addHandler(self.handler)
- self.logger.addFilter(self.filter)
- self.logger.setLevel(10)
- self.s1 = "There's an IPv4 address at the end of this book: "
- self.s2 = "1.2.3.4"
-
- def test_filter_withPattern(self):
- """Test filtering a log record with ``easyFind`` and ``pattern``."""
- record = safelog.logging.LogRecord('name', 10, __file__, 1337,
- "testing 1.2.3.4",
- {}, None)
- filtered = self.filter.filter(record)
- self.assertIsInstance(filtered, safelog.logging.LogRecord)
-
- def test_doubleCheck_IPv4(self):
- checked = self.filter.doubleCheck("1.2.3.4")
- self.assertIs(checked, True)
-
- def test_doubleCheck_IPv6(self):
- checked = self.filter.doubleCheck("2af1:a470:9b36::a1:3:82")
- self.assertIsNot(checked, True)
-
- def test_debugLevel(self):
- self.logger.debug("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_infoLevel(self):
- self.logger.info("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_warnLevel(self):
- self.logger.warn("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_errorLevel(self):
- self.logger.error("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_exceptionLevel(self):
- try:
- raise Exception("%s %s" % (self.s1, self.s2))
- except Exception as error:
- self.logger.exception(error)
-
- self.logfile.io.flush()
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_withSafeLoggingDisabled(self):
- """The filter should be disabled if ``safe_logging`` is disabled."""
- safelog.setSafeLogging(False)
- self.logger.info("%s %s" % (self.s1, self.s2))
- self.logfile.io.flush()
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
-
-class SafelogIPv6FilterTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.safelog.SafelogIPv6Filter`."""
-
- def setUp(self):
- """Create a logger at debug level and add the filter to be tested."""
- self.logfile = StringTransport()
- self.handler = safelog.logging.StreamHandler(self.logfile)
- self.filter = safelog.SafelogIPv6Filter()
- self.logger = safelog.logging.getLogger(str(self.__class__.__name__))
- self.logger.addHandler(self.handler)
- self.logger.addFilter(self.filter)
- self.logger.setLevel(10)
- self.s1 = "There's an IPv6 address at the end of this book: "
- self.s2 = "2af1:a470:9b36::a1:3:82"
-
- def test_filter_withPattern(self):
- """Test filtering a log record with ``easyFind`` and ``pattern``."""
- record = safelog.logging.LogRecord('name', 10, __file__, 1337,
- "2af1:a470:9b36::a1:3:82",
- {}, None)
- filtered = self.filter.filter(record)
- self.assertIsInstance(filtered, safelog.logging.LogRecord)
-
- def test_doubleCheck_IPv4(self):
- checked = self.filter.doubleCheck("1.2.3.4")
- self.assertIsNot(checked, True)
-
- def test_doubleCheck_IPv6(self):
- checked = self.filter.doubleCheck("2af1:a470:9b36::a1:3:82")
- self.assertIs(checked, True)
-
- def test_debugLevel(self):
- self.logger.debug("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_infoLevel(self):
- self.logger.info("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_warnLevel(self):
- self.logger.warn("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_errorLevel(self):
- self.logger.error("%s %s" % (self.s1, self.s2))
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
-
- def test_exceptionLevel(self):
- try:
- raise Exception("%s %s" % (self.s1, self.s2))
- except Exception as error:
- self.logger.exception(error)
-
- self.logfile.io.flush()
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
- #self.assertSubstring(self.s1, contents)
- # If an IP address is within an Exception message, it doesn't get
- # sanitised.
- #self.assertSubstring(self.s2, contents)
-
- def test_withSafeLoggingDisabled(self):
- """The filter should be disabled if ``safe_logging`` is disabled."""
- safelog.setSafeLogging(False)
- self.logger.info("%s %s" % (self.s1, self.s2))
-
- self.logfile.io.flush()
- self.logfile.io.seek(0)
- contents = self.logfile.value()
- self.assertIsNotNone(contents)
diff --git a/lib/bridgedb/test/test_schedule.py b/lib/bridgedb/test/test_schedule.py
deleted file mode 100644
index 6a38871..0000000
--- a/lib/bridgedb/test/test_schedule.py
+++ /dev/null
@@ -1,224 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2014-2015, Isis Lovecruft
-# (c) 2014-2015, The Tor Project, Inc.
-# :license: see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.schedule` module."""
-
-from __future__ import print_function
-
-from twisted.trial import unittest
-
-from bridgedb import schedule
-
-
-class UnscheduledTests(unittest.TestCase):
- """Tests for :class:`bridgedb.scheduled.Unscheduled`."""
-
- def setUp(self):
- self.sched = schedule.Unscheduled()
-
- def test_Unscheduled_init(self):
- """The instance should be an instance of its class."""
- self.assertIsInstance(self.sched, schedule.Unscheduled)
-
- def test_Unscheduled_providesISchedule(self):
- """Unscheduled should implement the ISchedule interface."""
- schedule.ISchedule.namesAndDescriptions()
- self.assertTrue(schedule.ISchedule.providedBy(self.sched))
-
- def test_Unscheduled_intervalStart_noargs(self):
- time = self.sched.intervalStart()
- self.assertIsInstance(time, int)
- self.assertEquals(time, -62135596800)
-
- def test_Unscheduled_getInterval_is_constant(self):
- import time
- now = time.time()
-
- interval_default = self.sched.getInterval()
- self.assertIsInstance(interval_default, str)
-
- interval_zero = self.sched.getInterval(0)
- self.assertIsInstance(interval_zero, str)
-
- interval_now = self.sched.getInterval(now)
- self.assertIsInstance(interval_now, str)
-
- self.assertEquals(interval_default, interval_zero)
- self.assertEquals(interval_default, interval_now)
-
- def test_Unscheduled_nextIntervalStarts_noargs(self):
- time = self.sched.nextIntervalStarts()
- self.assertIsInstance(time, int)
- self.assertEquals(time, 253402300799)
-
-
-class ScheduledIntervalTests(unittest.TestCase):
- """Tests for :class:`bridgedb.scheduled.ScheduledInterval`."""
-
- def setUp(self):
- import time
- self.now = time.time
- self.sched = schedule.ScheduledInterval
-
- def test_ScheduledInterval_providesISchedule(self):
- """ScheduledInterval should implement the ISchedule interface."""
- self.assertTrue(schedule.ISchedule.providedBy(self.sched(1, 'month')))
-
- def _check_init(self, sched):
- """The instance should be an instance of its class."""
- self.assertIsInstance(sched, schedule.ScheduledInterval)
-
- def test_ScheduledInterval_init_month(self):
- self._check_init(self.sched(1, 'month'))
-
- def test_ScheduledInterval_init_week(self):
- self._check_init(self.sched(2, 'week'))
-
- def test_ScheduledInterval_init_day(self):
- self._check_init(self.sched(5, 'days'))
-
- def test_ScheduledInterval_init_hour(self):
- self._check_init(self.sched(12, 'hours'))
-
- def test_ScheduledInterval_init_minute(self):
- self._check_init(self.sched(10, 'minute'))
-
- def test_ScheduledInterval_init_seconds(self):
- self._check_init(self.sched(30, 'seconds'))
-
- def test_ScheduledInterval_init_badIntervalPeriod(self):
- self.assertRaises(schedule.UnknownInterval,
- self.sched, 2, 'decades')
-
- def test_ScheduledInterval_init_badIntervalCount(self):
- self.assertRaises(schedule.UnknownInterval,
- self.sched, 'd20', 'minutes')
-
- def test_ScheduledInterval_init_negativeIntervalCount(self):
- sched = self.sched(-100000, 'days')
- self.assertEquals(sched.intervalCount, 1)
- self.assertEquals(sched.intervalPeriod, 'day')
-
- def test_ScheduledInterval_init_noargs(self):
- """Check that the defaults parameters function as expected."""
- sched = self.sched()
- self.assertEquals(sched.intervalCount, 1)
- self.assertEquals(sched.intervalPeriod, 'hour')
-
- def _check_intervalStart(self, count=30, period='second', variance=30):
- """Test the ScheduledInterval.intervalStart() method.
-
- :param int count: The number of **period**s within an interval.
- :param str period: The interval type for the period.
- :param int variance: The amount of variance (in seconds) to tolerate
- between the start of the interval containing now, and now.
- """
- now = int(self.now())
- sched = self.sched(count, period)
- time = sched.intervalStart(now)
- self.assertIsInstance(time, int)
- self.assertApproximates(now, time, variance)
-
- def test_ScheduledInterval_intervalStart_month(self):
- self._check_intervalStart(1, 'month', 31*24*60*60)
-
- def test_ScheduledInterval_intervalStart_week(self):
- self._check_intervalStart(2, 'week', 14*24*60*60)
-
- def test_ScheduledInterval_intervalStart_day(self):
- self._check_intervalStart(5, 'days', 5*24*60*60)
-
- def test_ScheduledInterval_intervalStart_hour(self):
- self._check_intervalStart(12, 'hours', 12*60*60)
-
- def test_ScheduledInterval_intervalStart_minute(self):
- self._check_intervalStart(10, 'minute', 10*60)
-
- def test_ScheduledInterval_intervalStart_seconds(self):
- self._check_intervalStart(30, 'seconds', 30)
-
- def test_ScheduledInterval_intervalStart_time_time(self):
- """Calling ScheduledInterval.intervalStart(time.time()) should only
- return ints, not floats.
- """
- import time
-
- timestamp = time.time()
- sched = self.sched(5, 'minutes')
-
- self.assertIsInstance(timestamp, float)
- self.assertIsInstance(sched.intervalStart(timestamp), int)
-
- def _check_getInterval(self, count=30, period='second', variance=30):
- """Test the ScheduledInterval.getInterval() method.
-
- :param int count: The number of **period**s within an interval.
- :param str period: The interval type for the period.
- :param int variance: The amount of variance (in seconds) to tolerate
- between the start of the interval containing now, and now.
- """
- now = int(self.now())
- sched = self.sched(count, period)
- ts = sched.getInterval(now)
- self.assertIsInstance(ts, str)
- secs = [int(x) for x in ts.replace('-', ' ').replace(':', ' ').split()]
- [secs.append(0) for _ in xrange(6-len(secs))]
- secs = schedule.calendar.timegm(secs)
- self.assertApproximates(now, secs, variance)
-
- def test_ScheduledInterval_getInterval_month(self):
- self._check_getInterval(2, 'month', 2*31*24*60*60)
-
- def test_ScheduledInterval_getInterval_week(self):
- self._check_getInterval(1, 'week', 7*24*60*60)
-
- def test_ScheduledInterval_getInterval_day(self):
- self._check_getInterval(4, 'days', 4*24*60*60)
-
- def test_ScheduledInterval_getInterval_hour(self):
- self._check_getInterval(23, 'hours', 23*60*60)
-
- def test_ScheduledInterval_getInterval_minute(self):
- self._check_getInterval(15, 'minutes', 15*60)
-
- def test_ScheduledInterval_getInterval_seconds(self):
- self._check_getInterval(10, 'seconds', 60)
-
- def _check_nextIntervalStarts(self, count=30, period='second', variance=30):
- """Test the ScheduledInterval.nextIntervalStarts() method.
-
- :param int count: The number of **period**s within an interval.
- :param str period: The interval type for the period.
- :param int variance: The amount of variance (in seconds) to tolerate
- between the start of the interval containing now, and now.
- """
- now = int(self.now())
- sched = self.sched(count, period)
- time = sched.nextIntervalStarts(now)
- self.assertIsInstance(time, int)
- # (now + variance - time) should be > variance
- self.assertApproximates(now + variance, time, variance)
-
- def test_ScheduledInterval_nextIntervalStarts_month(self):
- self._check_nextIntervalStarts(2, 'month', 2*31*24*60*60)
-
- def test_ScheduledInterval_nextIntervalStarts_week(self):
- self._check_nextIntervalStarts(1, 'week', 7*24*60*60)
-
- def test_ScheduledInterval_nextIntervalStarts_day(self):
- self._check_nextIntervalStarts(4, 'days', 4*24*60*60)
-
- def test_ScheduledInterval_nextIntervalStarts_hour(self):
- self._check_nextIntervalStarts(23, 'hours', 23*60*60)
-
- def test_ScheduledInterval_nextIntervalStarts_minute(self):
- self._check_nextIntervalStarts(15, 'minutes', 15*60)
-
- def test_ScheduledInterval_nextIntervalStarts_seconds(self):
- self._check_nextIntervalStarts(10, 'seconds', 10)
diff --git a/lib/bridgedb/test/test_smtp.py b/lib/bridgedb/test/test_smtp.py
deleted file mode 100644
index de443b3..0000000
--- a/lib/bridgedb/test/test_smtp.py
+++ /dev/null
@@ -1,206 +0,0 @@
-"""integration tests for BridgeDB ."""
-
-from __future__ import print_function
-
-import smtplib
-import asyncore
-import threading
-import Queue
-import random
-import os
-
-from smtpd import SMTPServer
-
-from twisted.trial import unittest
-from twisted.trial.unittest import FailTest
-from twisted.trial.unittest import SkipTest
-
-from bridgedb.test.util import processExists
-from bridgedb.test.util import getBridgeDBPID
-
-# ------------- SMTP Client Config
-SMTP_DEBUG_LEVEL = 0 # set to 1 to see SMTP message exchange
-BRIDGEDB_SMTP_SERVER_ADDRESS = "localhost"
-BRIDGEDB_SMTP_SERVER_PORT = 6725
-# %d is parameterised with a random integer to make the sender unique
-FROM_ADDRESS_TEMPLATE = "test%d at 127.0.0.1"
-# Minimum value used to parameterise FROM_ADDRESS_TEMPLATE
-MIN_FROM_ADDRESS = 1
-# Max value used to parameterise FROM_ADDRESS_TEMPLATE. Needs to be pretty big
-# to reduce the chance of collisions
-MAX_FROM_ADDRESS = 10**8
-TO_ADDRESS = "bridges at torproject.org"
-MESSAGE_TEMPLATE = """From: %s
-To: %s
-Subject: testing
-
-get bridges"""
-
-# ------------- SMTP Server Setup
-# Setup an SMTP server which we use to check for responses
-# from bridgedb. This needs to be done before sending the actual mail
-LOCAL_SMTP_SERVER_ADDRESS = 'localhost'
-LOCAL_SMTP_SERVER_PORT = 2525 # Must be the same as bridgedb's EMAIL_SMTP_PORT
-
-
-class EmailServer(SMTPServer):
- def process_message(self, peer, mailfrom, rcpttos, data):
- ''' Overridden from SMTP server, called whenever a message is received'''
- self.message_queue.put(data)
-
- def thread_proc(self):
- ''' This function runs in thread, and will continue looping
- until the _stop Event object is set by the stop() function'''
- while self._stop.is_set() == False:
- asyncore.loop(timeout=0.0, count=1)
- # Must close, or asyncore will hold on to the socket and subsequent
- # tests will fail with 'Address not in use'.
- self.close()
-
- def start(self):
- self.message_queue = Queue.Queue()
- self._stop = threading.Event()
- self._thread = threading.Thread(target=self.thread_proc)
- # Ensures that if any tests do fail, then threads will exit when the
- # parent exits.
- self._thread.setDaemon(True)
- self._thread.start()
-
- @classmethod
- def startServer(cls):
- #print("Starting SMTP server on %s:%s"
- # % (LOCAL_SMTP_SERVER_ADDRESS, LOCAL_SMTP_SERVER_PORT))
- server = EmailServer((LOCAL_SMTP_SERVER_ADDRESS,
- LOCAL_SMTP_SERVER_PORT),
- None)
- server.start()
- return server
-
- def stop(self):
- # Signal thread_proc to stop:
- self._stop.set()
- # Wait for thread_proc to return (shouldn't take long)
- self._thread.join()
- assert self._thread.is_alive() == False, "Thread is alive and kicking"
-
- def getAndCheckMessageContains(self, text, timeoutInSecs=2.0):
- try:
- message = self.message_queue.get(block=True, timeout=timeoutInSecs)
- # Queue.Empty, according to its documentation, is only supposed to be
- # raised when Queue.get(block=False) or Queue.get_nowait() are called.
- # I've no idea why it's getting raised here, when we're blocking for
- # it, but nonetheless it causes occasional, non-deterministic CI
- # failures:
- #
- # https://travis-ci.org/isislovecruft/bridgedb/jobs/58996136#L3281
- except Queue.Empty:
- pass
- else:
- assert message.find(text) != -1, ("Message did not contain text '%s'."
- "Full message is:\n %s"
- % (text, message))
-
- def checkNoMessageReceived(self, timeoutInSecs=2.0):
- try:
- self.message_queue.get(block=True, timeout=timeoutInSecs)
- except Queue.Empty:
- return True
- assert False, "Found a message in the queue, but expected none"
-
-def sendMail(fromAddress):
- #print("Connecting to %s:%d"
- # % (BRIDGEDB_SMTP_SERVER_ADDRESS, BRIDGEDB_SMTP_SERVER_PORT))
- client = smtplib.SMTP(BRIDGEDB_SMTP_SERVER_ADDRESS,
- BRIDGEDB_SMTP_SERVER_PORT)
- client.set_debuglevel(SMTP_DEBUG_LEVEL)
-
- #print("Sending mail TO:%s, FROM:%s"
- # % (TO_ADDRESS, fromAddress))
- result = client.sendmail(fromAddress, TO_ADDRESS,
- MESSAGE_TEMPLATE % (fromAddress, TO_ADDRESS))
- assert result == {}, "Failed to send mail"
- client.quit()
-
-
-class SMTPTests(unittest.TestCase):
- def setUp(self):
- '''Called at the start of each test, ensures that the SMTP server is
- running.
- '''
- here = os.getcwd()
- topdir = here.rstrip('_trial_temp')
- self.rundir = os.path.join(topdir, 'run')
- self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
- self.pid = getBridgeDBPID(self.pidfile)
- self.server = EmailServer.startServer()
-
- def tearDown(self):
- '''Called after each test, ensures that the SMTP server is cleaned up.
- '''
- self.server.stop()
-
- def test_getBridges(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- # send the mail to bridgedb, choosing a random email address
- sendMail(fromAddress=FROM_ADDRESS_TEMPLATE
- % random.randint(MIN_FROM_ADDRESS, MAX_FROM_ADDRESS))
-
- # then check that our local SMTP server received a response
- # and that response contained some bridges
- self.server.getAndCheckMessageContains("Here are your bridges")
-
- def test_getBridges_rateLimitExceeded(self):
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- # send the mail to bridgedb, choosing a random email address
- FROM_ADDRESS = FROM_ADDRESS_TEMPLATE % random.randint(
- MIN_FROM_ADDRESS, MAX_FROM_ADDRESS)
- sendMail(FROM_ADDRESS)
-
- # then check that our local SMTP server received a response
- # and that response contained some bridges
- self.server.getAndCheckMessageContains("Here are your bridges")
-
- # send another request from the same email address
- sendMail(FROM_ADDRESS)
-
- # this time, the email response should not contain any bridges
- self.server.getAndCheckMessageContains(
- "You have exceeded the rate limit. Please slow down!")
-
- # then we send another request from the same email address
- sendMail(FROM_ADDRESS)
-
- # now there should be no response at all (wait 1 second to make sure)
- self.server.checkNoMessageReceived(timeoutInSecs=1.0)
-
- def test_getBridges_stressTest(self):
- '''Sends a large number of emails in a short period of time, and checks
- that a response is received for each message.
- '''
- if os.environ.get("CI"):
- if not self.pid or not processExists(self.pid):
- raise FailTest("Could not start BridgeDB process on CI server!")
- if not self.pid or not processExists(self.pid):
- raise SkipTest("Can't run test: no BridgeDB process running.")
-
- NUM_MAILS = 100
- for i in range(NUM_MAILS):
- # Note: if by chance two emails with the same FROM_ADDRESS are
- # generated, this test will fail Setting 'MAX_FROM_ADDRESS' to be
- # a high value reduces the probability of this occuring, but does
- # not rule it out
- sendMail(fromAddress=FROM_ADDRESS_TEMPLATE
- % random.randint(MIN_FROM_ADDRESS, MAX_FROM_ADDRESS))
-
- for i in range(NUM_MAILS):
- self.server.getAndCheckMessageContains("Here are your bridges")
diff --git a/lib/bridgedb/test/test_translations.py b/lib/bridgedb/test/test_translations.py
deleted file mode 100644
index 48229c6..0000000
--- a/lib/bridgedb/test/test_translations.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2014-2015, Isis Lovecruft
-# (c) 2014-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-
-from twisted.trial import unittest
-
-from bridgedb import translations
-from bridgedb.test.test_https_server import DummyRequest
-
-
-REALISH_HEADERS = {
- b'Accept-Encoding': [b'gzip, deflate'],
- b'User-Agent': [
- b'Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0'],
- b'Accept': [
- b'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'],
-}
-
-# Add this to the above REALISH_HEADERS to use it:
-ACCEPT_LANGUAGE_HEADER = {
- b'Accept-Language': [b'de-de,en-gb;q=0.8,en;q=0.5,en-us;q=0.3'],
-}
-
-
-class TranslationsMiscTests(unittest.TestCase):
- """Tests for module-level code in ``bridgedb.translations`` module."""
-
- def test_getLocaleFromHTTPRequest_withLangParam(self):
- """This request uses a '?lang=ar' param, without an 'Accept-Language'
- header.
-
- The request result should be: ['ar', 'en', 'en-US'].
- """
- request = DummyRequest([b"bridges"])
- request.headers.update(REALISH_HEADERS)
- request.args.update({
- b'transport': [b'obfs3',],
- b'lang': [b'ar',],
- })
-
- parsed = translations.getLocaleFromHTTPRequest(request)
- self.assertEqual(parsed[0], 'ar')
- self.assertEqual(parsed[1], 'en')
- self.assertEqual(parsed[2], 'en_US')
- self.assertEqual(len(parsed), 3)
-
- def test_getLocaleFromHTTPRequest_withLangParam_AcceptLanguage(self):
- """This request uses a '?lang=ar' param, with an 'Accept-Language'
- header which includes: ['de-de', 'en-gb', 'en', 'en-us'].
-
- The request result should be: ['fa', 'de-de', 'en-gb', 'en', 'en-us'].
- """
- request = DummyRequest([b"options"])
- request.headers.update(ACCEPT_LANGUAGE_HEADER)
- request.args.update({b'lang': [b'fa']})
-
- parsed = translations.getLocaleFromHTTPRequest(request)
- self.assertEqual(parsed[0], 'fa')
- self.assertEqual(parsed[1], 'en')
- self.assertEqual(parsed[2], 'en_US')
- #self.assertEqual(parsed[3], 'en-gb')
- self.assertEqual(len(parsed), 3)
-
- def test_getLocaleFromPlusAddr(self):
- emailAddr = 'bridges at torproject.org'
- replyLocale = translations.getLocaleFromPlusAddr(emailAddr)
- self.assertEqual('en', replyLocale)
-
- def test_getLocaleFromPlusAddr_ar(self):
- emailAddr = 'bridges+ar at torproject.org'
- replyLocale = translations.getLocaleFromPlusAddr(emailAddr)
- self.assertEqual('ar', replyLocale)
diff --git a/lib/bridgedb/test/test_txrecaptcha.py b/lib/bridgedb/test/test_txrecaptcha.py
deleted file mode 100644
index 4704d49..0000000
--- a/lib/bridgedb/test/test_txrecaptcha.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests for the bridgedb.txrecaptcha module."""
-
-import logging
-
-from twisted.internet import defer
-from twisted.internet import reactor
-from twisted.internet.base import DelayedCall
-from twisted.internet.error import ConnectionDone
-from twisted.internet.error import ConnectionLost
-from twisted.internet.error import ConnectionRefusedError
-from twisted.test import proto_helpers
-from twisted.trial import unittest
-from twisted.python import failure
-from twisted.web.client import ResponseDone
-from twisted.web.http_headers import Headers
-from twisted.web.iweb import IBodyProducer
-
-from zope.interface.verify import verifyObject
-
-from bridgedb import txrecaptcha
-
-
-logging.disable(50)
-
-# Set ``DelayedCall.debug=True``, because the following traceback was occuring:
-#
-# Traceback (most recent call last):
-# Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
-# DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
-# <DelayedCall 0x1ba5b90 [29.991571188s] called=0 cancelled=0
-# Client.failIfNotConnected(TimeoutError('',))>
-# <DelayedCall 0x1baa3f8 [59.9993360043s] called=0 cancelled=0
-# ThreadedResolver._cleanup('www.google.com', <Deferred at 0x1baa320>)>
-DelayedCall.debug = True
-
-
-class MockResponse(object):
- """Fake :api:`twisted.internet.interfaces.IResponse` for testing readBody
- that just captures the protocol passed to deliverBody.
-
- :ivar protocol: After :meth:`deliverBody` is called, the protocol it was
- called with.
- """
- code = 200
- phrase = "OK"
-
- def __init__(self, headers=None):
- """Create a mock response.
-
- :type headers: :api:`twisted.web.http_headers.Headers`
- :param headers: The headers for this response. If ``None``, an empty
- ``Headers`` instance will be used.
- """
- if headers is None:
- headers = Headers()
- self.headers = headers
-
- def deliverBody(self, protocol):
- """Just record the given protocol without actually delivering anything
- to it.
- """
- self.protocol = protocol
-
-
-class RecaptchaResponseProtocolTests(unittest.TestCase):
- """Tests for bridgedb.txrecaptcha.RecaptchaResponseProtocol."""
-
- def setUp(self):
- """Setup the tests."""
- self.finished = defer.Deferred()
- self.proto = txrecaptcha.RecaptchaResponseProtocol(self.finished)
-
- def _test(self, responseBody, connCloseError):
- """Deliver the **responseBody** to
- ``RecaptchaResponseProtocol.dataReceived``, and then lose the transport
- connection with a **connCloseError**.
-
- The resulting ``RecaptchaResponseProtocol.response`` should be equal
- to the original **responseBody**.
- """
- self.proto.dataReceived(responseBody)
- self.proto.connectionLost(failure.Failure(connCloseError()))
- self.assertEqual(responseBody, self.proto.response)
- response = self.successResultOf(self.finished)
- return response
-
- def test_trueResponse(self):
- """A valid API response which states 'true' should result in
- ``RecaptchaResponse.is_valid`` being ``True`` after receiving a
- ``ConnectionDone``.
- """
- responseBody = "true\nsome-reason-or-another\n"
- response = self._test(responseBody, ConnectionDone)
- self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
- self.assertTrue(response.is_valid)
- self.assertEqual(response.error_code, "some-reason-or-another")
-
- def test_falseResponse(self):
- """A valid API response which states 'false' should result in
- ``RecaptchaResponse.is_valid`` being ``false``.
- """
- responseBody = "false\nsome-reason-or-another\n"
- response = self._test(responseBody, ResponseDone)
- self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
- self.assertIs(response.is_valid, False)
- self.assertEqual(response.error_code, "some-reason-or-another")
-
- def test_responseDone(self):
- """A valid response body with a ``ResponseDone`` should result in
- ``RecaptchaResponse.is_valid`` which is ``True``.
- """
- responseBody = "true\nsome-reason-or-another\n"
- response = self._test(responseBody, ResponseDone)
- self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
- self.assertTrue(response.is_valid)
- self.assertEqual(response.error_code, "some-reason-or-another")
-
- def test_incompleteResponse(self):
- """ConnectionLost with an incomplete response should produce a specific
- RecaptchaResponse.error_code message.
- """
- responseBody = "true"
- response = self._test(responseBody, ConnectionLost)
- self.assertIs(response.is_valid, False)
- self.assertEqual(response.error_code,
- "Couldn't parse response from reCaptcha API server")
-
-
-class BodyProducerTests(unittest.TestCase):
- """Test for :class:`bridgedb.txrecaptcha.BodyProducer`."""
-
- def setUp(self):
- """Setup the tests."""
- self.content = 'Line 1\r\nLine 2\r\n'
- self.producer = txrecaptcha._BodyProducer(self.content)
-
- def test_interface(self):
- """BodyProducer should correctly implement IBodyProducer interface."""
- self.assertTrue(verifyObject(IBodyProducer, self.producer))
-
- def test_length(self):
- """BodyProducer.length should be equal to the total contect length."""
- self.assertEqual(self.producer.length, len(self.content))
-
- def test_body(self):
- """BodyProducer.body should be the content."""
- self.assertEqual(self.producer.body, self.content)
-
- def test_startProducing(self):
- """:func:`txrecaptcha.BodyProducer.startProducing` should deliver the
- original content to an IConsumer implementation.
- """
- consumer = proto_helpers.StringTransport()
- consumer.registerProducer(self.producer, False)
- self.producer.startProducing(consumer)
- self.assertEqual(consumer.value(), self.content)
- consumer.clear()
-
-
-class SubmitTests(unittest.TestCase):
- """Tests for :func:`bridgedb.txrecaptcha.submit`."""
-
- def setUp(self):
- """Setup the tests."""
- self.challenge = (
- "03AHJ_Vutbkv3jolF5JXfJTFf5wtbdkwIJF7WA77WYjLfOUEvKW7eHBiEDKQB__7"
- "GHtUOmXC13GFYIt09HuS-ZN1j5EuDmC7bzHpHUAlpI5rbOvByypYt1vtskwnN24g"
- "zwWkrtKj8yGBWRNFljFMvtqYqHeHwJitRktSfKmV4q9VVgLBwkwlbvGUICmGaDrx"
- "dg5lYV3hpijIkmnwXygWIwoqQ0VeCgPQQ1Yw")
- self.response = "cknwnlym+ullyHLy"
- self.key = '6BdkT-18FFHAAA349auGabiqntjRJAiEM2cqPMaM8'
- self.ip = "1.2.3.4"
-
- def test_submit_emptyResponseField(self):
- """An empty 'recaptcha_response_field' should return a deferred which
- callbacks with a RecaptchaResponse whose error_code is
- 'incorrect-captcha-sol'.
- """
- def checkResponse(response):
- """Check that the response is a
- :class:`txcaptcha.RecaptchaResponse`.
- """
- self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
- self.assertIs(response.is_valid, False)
- self.assertEqual(response.error_code, 'incorrect-captcha-sol')
-
- d = txrecaptcha.submit(self.challenge, '', self.key, self.ip)
- d.addCallback(checkResponse)
- return d
-
- def test_submit_returnsDeferred(self):
- """:func:`txrecaptcha.submit` should return a deferred."""
- response = txrecaptcha.submit(self.challenge, self.response, self.key,
- self.ip)
- self.assertIsInstance(response, defer.Deferred)
-
- def test_submit_resultIsRecaptchaResponse(self):
- """Regardless of success or failure, the deferred returned from
- :func:`txrecaptcha.submit` should be a
- :class:`txcaptcha.RecaptchaResponse`.
- """
- def checkResponse(response):
- """Check that the response is a
- :class:`txcaptcha.RecaptchaResponse`.
- """
- self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
- self.assertIsInstance(response.is_valid, bool)
- self.assertIsInstance(response.error_code, basestring)
-
- d = txrecaptcha.submit(self.challenge, self.response, self.key,
- self.ip)
- d.addCallback(checkResponse)
- return d
-
- def tearDown(self):
- """Cleanup method for removing timed out connections on the reactor.
-
- This seems to be the solution for the dirty reactor due to
- ``DelayedCall``s which is mentioned at the beginning of this
- file. There doesn't seem to be any documentation anywhere which
- proposes this solution, although this seems to solve the problem.
- """
- for delay in reactor.getDelayedCalls():
- try:
- delay.cancel()
- except (AlreadyCalled, AlreadyCancelled):
- pass
-
-
-class MiscTests(unittest.TestCase):
- """Tests for miscellaneous functions in :mod:`~bridgedb.txrecaptcha`."""
-
- def test_cbRequest(self):
- """Send a :class:`MockResponse` and check that the resulting protocol
- is a :class:`~bridgedb.txrecaptcha.RecaptchaResponseProtocol`.
- """
- response = MockResponse()
- result = txrecaptcha._cbRequest(response)
- self.assertIsInstance(result, defer.Deferred)
- self.assertIsInstance(response.protocol,
- txrecaptcha.RecaptchaResponseProtocol)
-
- def test_ebRequest(self):
- """Send a :api:`twisted.python.failure.Failure` and check that the
- resulting protocol is a
- :class:`~bridgedb.txrecaptcha.RecaptchaResponseProtocol`.
- """
- msg = "Einhorn"
- fail = failure.Failure(ConnectionRefusedError(msg))
- result = txrecaptcha._ebRequest(fail)
- self.assertIsInstance(result, txrecaptcha.RecaptchaResponse)
- self.assertRegexpMatches(result.error_code, msg)
-
- def test_encodeIfNecessary(self):
- """:func:`txrecapcha._encodeIfNecessary` should convert unicode objects
- into strings.
- """
- origString = unicode('abc')
- self.assertIsInstance(origString, unicode)
- newString = txrecaptcha._encodeIfNecessary(origString)
- self.assertIsInstance(newString, str)
diff --git a/lib/bridgedb/test/test_util.py b/lib/bridgedb/test/test_util.py
deleted file mode 100644
index da4ddf4..0000000
--- a/lib/bridgedb/test/test_util.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2014-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# (c) 2007-2015, all entities within the AUTHORS file
-# :license: see LICENSE for licensing information
-
-"""Unittests for the :mod:`bridgedb.util` module."""
-
-from __future__ import absolute_import
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import logging
-import os
-
-from twisted.mail.smtp import Address
-from twisted.trial import unittest
-
-from bridgedb import util
-
-
-class MiscLoggingUtilTests(unittest.TestCase):
- """Unittests for miscellaneous logging functions in :mod:`bridgedb.util`."""
-
- def test_getLogHandlers(self):
- """util._getLogHandlers() should return ['rotating', 'console'] if
- both stderr and logfile logging are enabled.
- """
- logHandlers = util._getLogHandlers()
- self.assertIsInstance(logHandlers, list)
- self.assertEqual(len(logHandlers), 2)
-
- def test_getLogHandlers_disableStderr(self):
- """util._getLogHandlers() should return ['rotating'] if stderr logging
- is disabled.
- """
- logHandlers = util._getLogHandlers(logToStderr=False)
- self.assertIsInstance(logHandlers, list)
- self.assertEqual(len(logHandlers), 1)
- self.assertTrue('console' not in logHandlers)
-
- def test_getLogHandlers_disable_logfile(self):
- """util._getLogHandlers() should return ['console'] if stderr logging
- is disabled.
- """
- logHandlers = util._getLogHandlers(logToFile=False)
- self.assertIsInstance(logHandlers, list)
- self.assertEqual(len(logHandlers), 1)
- self.assertTrue('rotating' not in logHandlers)
-
- def test_getRotatingFileHandler(self):
- """_getRotatingFileHandler() should create a file with 0600
- permissions (os.ST_WRITE | os.ST_APPEND).
- """
- filename = str(self.id()) + '.log'
- logHandler = util._getRotatingFileHandler(filename)
- self.assertTrue(os.path.isfile(filename))
- self.assertEqual(os.stat_result(os.stat(filename)).st_mode, 33152)
- self.assertIsInstance(logHandler(),
- util.logging.handlers.RotatingFileHandler)
-
- def test_configureLogging(self):
- """Configure logging should be callable without borking anything."""
- from bridgedb.persistent import Conf
- util.configureLogging(Conf())
- util.logging.info("BridgeDB's email address: bridges at torproject.org")
-
-
-class LevenshteinDistanceTests(unittest.TestCase):
- """Unittests for `bridgedb.util.levenshteinDistance."""
-
- def test_levenshteinDistance_blank_blank(self):
- """The Levenshtein Distance between '' and '' should be 0."""
- distance = util.levenshteinDistance('', '')
- self.assertEqual(distance, 0)
-
- def test_levenshteinDistance_cat_cat(self):
- """The Levenshtein Distance between 'cat' and 'cat' should be 0."""
- distance = util.levenshteinDistance('cat', 'cat')
- self.assertEqual(distance, 0)
-
- def test_levenshteinDistance_bat_cat(self):
- """The Levenshtein Distance between 'bat' and 'cat' should be 1."""
- distance = util.levenshteinDistance('bat', 'cat')
- self.assertEqual(distance, 1)
-
- def test_levenshteinDistance_bar_cat(self):
- """The Levenshtein Distance between 'bar' and 'cat' should be 2."""
- distance = util.levenshteinDistance('bar', 'cat')
- self.assertEqual(distance, 2)
-
- def test_levenshteinDistance_bridgedb_doge(self):
- """The Levenshtein Distance between 'bridgedb' and 'doge' should be 6."""
- distance = util.levenshteinDistance('bridgedb', 'doge')
- self.assertEqual(distance, 6)
-
- def test_levenshteinDistance_feidanchaoren0043_feidanchaoren0011(self):
- """The Levenshtein Distance between the usernames in
- 'feidanchaoren0043 at gmail.com' and 'feidanchaoren0011 at gmail.com' should
- be less than an EMAIL_FUZZY_MATCH parameter.
- """
- email1 = Address('feidanchaoren0043 at gmail.com')
- email2 = Address('feidanchaoren0011 at gmail.com')
- # Fuzzy match if the Levenshtein Distance is less than or equal to:
- fuzzyMatch = 4
- distance = util.levenshteinDistance(email1.local, email2.local)
- self.assertLessEqual(distance, fuzzyMatch)
-
-
-class JustifiedLogFormatterTests(unittest.TestCase):
- """Unittests for :class:`bridgedb.util.JustifiedLogFormatter`."""
-
- def setUp(self):
- # name, level, path, lineno, message, args, exc_info
- self.record = logging.LogRecord('name', logging.INFO, '/foo/bar/baz',
- 12345, 'This is a message', None, None)
-
- def test_util_JustifiedLogFormatter(self):
- formatter = util.JustifiedLogFormatter()
- self.assertIsInstance(formatter, logging.Formatter)
-
- def test_util_JustifiedLogFormatter_logThreads(self):
- formatter = util.JustifiedLogFormatter(logThreads=True)
- self.assertIsInstance(formatter, logging.Formatter)
-
- def test_util_JustifiedLogFormatter_formatCallingFuncName(self):
- formatter = util.JustifiedLogFormatter()
- record = formatter._formatCallingFuncName(self.record)
- self.assertIsInstance(formatter, logging.Formatter)
- self.assertIsInstance(record, logging.LogRecord)
-
- def test_util_JustifiedLogFormatter_format(self):
- formatter = util.JustifiedLogFormatter()
- formatted = formatter.format(self.record)
- self.assertIsInstance(formatter, logging.Formatter)
- self.assertIsInstance(formatted, basestring)
- self.assertNotEqual(formatted, '')
- self.assertTrue('INFO' in formatted)
- self.assertTrue('This is a message' in formatted)
diff --git a/lib/bridgedb/test/util.py b/lib/bridgedb/test/util.py
deleted file mode 100644
index 0f1e0f9..0000000
--- a/lib/bridgedb/test/util.py
+++ /dev/null
@@ -1,301 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# please also see AUTHORS file
-# :copyright: (c) 2013, Isis Lovecruft
-# (c) 2007-2013, The Tor Project, Inc.
-# (c) 2007-2013, all entities within the AUTHORS file
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Unittests utilitys the `bridgedb.test` package."""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import errno
-import ipaddr
-import os
-import random
-import time
-
-from functools import wraps
-
-from twisted.trial import unittest
-
-from bridgedb import util as bdbutil
-from bridgedb.bridges import IBridge
-from bridgedb.parse.addr import isIPAddress
-
-from zope.interface import implementer
-
-
-def fileCheckDecorator(func):
- """Method decorator for a t.t.unittest.TestCase test_* method.
-
- .. codeblock:: python
-
- import shutil
- from twisted.trial import unittest
-
- pyunit = __import__('unittest')
-
- class TestTests(unittest.TestCase):
- @fileCheckDecorator
- def doCopyFile(src, dst, description=None):
- shutil.copy(src, dst)
- def test_doCopyFile(self):
- srcfile = self.mktemp()
- dstfile = self.mktemp()
- with open(srcfile, 'wb') as fh:
- fh.write('testing TestCase method decorator utility')
- fh.flush()
- self.doCopyFile(srcfile, dstfile, 'asparagus')
-
- testtest = TestTests()
- testtest.runTest()
-
- ..
-
- :type func: callable
- :param func: The ``test_*`` method, from a
- :api:`twisted.trial.unittest.TestCase` instance, to wrap.
- """
- @wraps(func)
- def wrapper(self, src, dst, description):
- self.assertTrue(os.path.isfile(src),
- "Couldn't find original %s file: %r"
- % (str(description), src))
- func(self, src, dst, description)
- self.assertTrue(os.path.isfile(dst),
- "Couldn't find new %s file: %r. Original: %r"
- % (str(description), dst, src))
- return wrapper
-
-def processExists(pid):
- """Test if the process with **pid** exists.
-
- :param int pid: An integer specifying the process ID.
- :raises: OSError, if ``OSError.errno`` wasn't an expected errno (according
- to the "ERRORS" section from ``man 2 kill``).
- :rtype: bool
- :returns: ``True`` if a process with **pid** exists, ``False`` otherwise.
- """
- try:
- os.kill(pid, 0)
- except OSError as err:
- if err.errno == errno.ESRCH: # ESRCH: No such process
- return False
- if err.errno == errno.EPERM: # EPERM: Operation not permitted
- # If we're not allowed to signal the process, then there exists a
- # process that we don't have permissions to access.
- return True
- else:
- raise
- else:
- return True
-
-def getBridgeDBPID(pidfile="bridgedb.pid"):
- """Read the ``bridgedb.pid`` file in **rundir**, if it exists, to get the
- PID.
-
- :param str pidfile: The path to the BridgeDB pidfile.
- :rtype: int
- :returns: The process ID, if available, otherwise ``0``.
- """
- fh = None
- try:
- fh = open(pidfile)
- except (IOError, OSError) as err:
- print(err)
- pid = 0
- else:
- pid = int(fh.read())
-
- if fh:
- fh.close()
-
- return pid
-
-def bracketIPv6(ip):
- """Put brackets around an IPv6 address, just as tor does."""
- return "[%s]" % ip
-
-def randomPort():
- return random.randint(1, 65535)
-
-def randomHighPort():
- return random.randint(1024, 65535)
-
-def randomIPv4():
- return ipaddr.IPv4Address(random.getrandbits(32))
-
-def randomIPv6():
- return ipaddr.IPv6Address(random.getrandbits(128))
-
-def randomIP():
- if random.choice(xrange(2)):
- return randomIPv4()
- return randomIPv6()
-
-def randomIPv4String():
- return randomIPv4().compressed
-
-def randomIPv6String():
- return bracketIPv6(randomIPv6().compressed)
-
-def randomIPString():
- if random.choice(xrange(2)):
- return randomIPv4String()
- return randomIPv6String()
-
-def valid(func):
- """Wrapper for the above ``randomIPv*`` functions to ensure they only
- return addresses which BridgeDB considers "valid".
-
- .. seealso:: :func:`bridgedb.parse.addr.isIPAddress`
- """
- @wraps(func)
- def wrapper():
- ip = None
- while not isIPAddress(ip):
- ip = func()
- return ip
- return wrapper
-
-randomValidIPv4 = valid(randomIPv4)
-randomValidIPv6 = valid(randomIPv6)
-randomValidIP = valid(randomIP)
-randomValidIPv4String = valid(randomIPv4String)
-randomValidIPv6String = valid(randomIPv6String)
-randomValidIPString = valid(randomIPString)
-
-_FAKE_BRIDGES = []
-
-def generateFakeBridges(n=500):
- """Generate a set of **n** :class:`~bridgedb.bridges.Bridges` with random
- data.
- """
- from bridgedb.bridges import Bridge
- from bridgedb.bridges import PluggableTransport
-
- global _FAKE_BRIDGES
-
- if _FAKE_BRIDGES:
- return _FAKE_BRIDGES
-
- bridges = []
-
- for i in range(n):
- addr = randomValidIPv4String()
- nick = 'bridge-%d' % i
- port = randomHighPort()
- # Real tor currently only supports one extra ORAddress, and it can
- # only be IPv6.
- addrs = [(randomValidIPv6(), randomHighPort(), 6)]
- fpr = "".join(random.choice('abcdef0123456789') for _ in xrange(40))
-
- # We only support the ones without PT args, because they're easier to fake.
- supported = ["obfs2", "obfs3", "fte"]
- transports = []
- for j, method in zip(range(1, len(supported) + 1), supported):
- pt = PluggableTransport(fpr, method, addr, port - j, {})
- transports.append(pt)
-
- bridge = Bridge(nick, addr, port, fpr)
- bridge.flags.update("Running Stable")
- bridge.transports = transports
- bridge.orAddresses = addrs
- bridges.append(bridge)
-
- _FAKE_BRIDGES = bridges
- return bridges
-
-
-#: Mixin class for use with :api:`~twisted.trial.unittest.TestCase`. A
-#: ``TestCaseMixin`` can be used to add additional methods, which should be
-#: common to multiple ``TestCase`` subclasses, without the ``TestCaseMixin``
-#: being run as a ``TestCase`` by ``twisted.trial``.
-TestCaseMixin = bdbutil.mixin
-TestCaseMixin.register(unittest.TestCase)
-
-
-class Benchmarker(object):
- """Wrap a context with a timer to benchmark execution time.
-
- .. hint:: Use like so::
-
- with Benchmarker():
- foo(bar, baz)
-
- Once the ``with`` context exits, something like::
-
- Benchmark: 180.269957ms (0s)
-
- will be printed to stdout (if **verbose** is set to ``True``).
- """
-
- def __init__(self, verbose=True):
- self.verbose = verbose
-
- def __enter__(self):
- self.start = time.time()
- return self
-
- def __exit__(self, *args):
- self.end = time.time()
- self.seconds = self.end - self.start
- self.milliseconds = self.seconds * 1000
- if self.verbose:
- print("Benchmark: %12fms %12fs" % (self.milliseconds, self.seconds))
-
-
- at implementer(IBridge)
-class DummyBridge(object):
- """A mock :class:`bridgedb.bridges.Bridge` which only supports a mocked
- ``getBridgeLine`` method."""
-
- ptArgs = {}
-
- def __init__(self):
- """Create a mocked bridge suitable for testing distributors and web
- resource rendering.
- """
- ipv4 = randomIPv4()
- self.nickname = "bridge-{0}".format(ipv4)
- self.address = ipaddr.IPv4Address(ipv4)
- self.orPort = randomPort()
- self.fingerprint = "".join(random.choice('abcdef0123456789')
- for _ in xrange(40))
- self.orAddresses = [(randomIPv6(), randomPort(), 6)]
-
- def getBridgeLine(self, bridgeRequest, includeFingerprint=True):
- """Get a "torrc" bridge config line to give to a client."""
- if not bridgeRequest.isValid():
- return
- line = []
- if bridgeRequest.transports:
- line.append(bridgeRequest.transports[-1]) # Just the last PT
- if bridgeRequest.ipVersion is 6:
- line.append("[%s]:%s" % self.orAddresses[0][:2])
- else:
- line.append("%s:%s" % (self.address, self.orPort))
- if includeFingerprint is True:
- line.append(self.fingerprint)
- if self.ptArgs:
- line.append(','.join(['='.join(x) for x in self.ptArgs.items()]))
- return " ".join([item for item in line])
-
-
- at implementer(IBridge)
-class DummyMaliciousBridge(DummyBridge):
- """A mock :class:`bridgedb.Bridges.Bridge` which only supports a mocked
- ``getConfigLine`` method and which maliciously insert an additional fake
- bridgeline and some javascript into its PT arguments.
- """
- ptArgs = {
- "eww": "\rBridge 1.2.3.4:1234",
- "bad": "\nBridge 6.6.6.6:6666 0123456789abcdef0123456789abcdef01234567",
- "evil": "<script>alert('fuuuu');</script>",
- }
diff --git a/lib/bridgedb/translations.py b/lib/bridgedb/translations.py
deleted file mode 100644
index 2c9d56f..0000000
--- a/lib/bridgedb/translations.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_translations -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-import gettext
-import logging
-import os
-import re
-
-from bridgedb import _langs
-from bridgedb import safelog
-from bridgedb.parse import headers
-
-
-TRANSLATIONS_DIR = os.path.join(os.path.dirname(__file__), 'i18n')
-
-
-def getFirstSupportedLang(langs):
- """Return the first language in **langs** that we support.
-
- :param list langs: All requested languages
- :rtype: str
- :returns: A country code for the client's preferred language.
- """
- lang = 'en-US'
- supported = _langs.get_langs()
-
- for l in langs:
- if l in supported:
- lang = l
- break
- return lang
-
-def getLocaleFromHTTPRequest(request):
- """Retrieve the languages from an HTTP ``Accept-Language:`` header.
-
- Parse the languages from the header, use them to install a
- ``gettext.translation`` chain via :func:`installTranslations`, and lastly
- return the requested languages.
-
- :type request: :api:`twisted.web.server.Request`
- :param request: An incoming request from a client.
- :rtype: list
- :returns: All requested languages.
- """
- header = request.getHeader('accept-language')
- if header is None:
- logging.debug("Client sent no 'Accept-Language' header. Using fallback.")
- header = 'en,en-US'
-
- langs = headers.parseAcceptLanguage(header)
- if not safelog.safe_logging: # pragma: no cover
- logging.debug("Client Accept-Language (top 5): %s" % langs[:5])
-
- # Check if we got a ?lang=foo argument, and if we did, insert it first
- chosenLang = request.args.get("lang", [None,])[0]
- if chosenLang:
- logging.debug("Client requested language: %r" % chosenLang)
- langs.insert(0, chosenLang)
-
- installTranslations(langs)
- return langs
-
-def getLocaleFromPlusAddr(address):
- """See whether the user sent his email to a 'plus' address, for instance to
- bridges+fa at bridges.torproject.org. Plus addresses are the current
- mechanism to set the reply language.
- """
- replyLocale = "en"
- r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
- match = re.match(r, address)
- if match:
- replyLocale = match.group(3)
-
- return replyLocale
-
-def installTranslations(langs):
- """Create a ``gettext.translation`` chain for all **langs**.
-
- Attempt to install the first language in the **langs** list. If that
- fails, we receive a ``gettext.NullTranslation`` object, and if it worked
- then we have a ``gettext.GNUTranslation`` object. Whichever one we end up
- with, get the other languages and add them as fallbacks to the
- first. Lastly, install this chain of translations.
-
- :param list langs: A list of language codes.
- :returns: A ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` with
- fallback languages set.
- """
- try:
- language = gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
- languages=langs, fallback=True)
- for lang in langs:
- language.add_fallback(
- gettext.translation("bridgedb", localedir=TRANSLATIONS_DIR,
- languages=langs, fallback=True))
- except IOError as error:
- logging.error(error.message)
-
- language.install(unicode=True)
- return language
-
-def usingRTLLang(langs):
- """Check if we should translate the text into a RTL language.
-
- Choose the first language from the **langs** list that we support and
- return True if it is a RTL language, else return False.
-
- :param list langs: An incoming request.
- :rtype: bool
- :returns: ``True`` if the preferred language is right-to-left; ``False``
- otherwise.
- """
- lang = getFirstSupportedLang(langs)
- if lang in _langs.RTL_LANGS:
- return True
- return False
diff --git a/lib/bridgedb/txrecaptcha.py b/lib/bridgedb/txrecaptcha.py
deleted file mode 100644
index 3666904..0000000
--- a/lib/bridgedb/txrecaptcha.py
+++ /dev/null
@@ -1,299 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_txrecaptcha -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Twisted-based reCAPTCHA client.
-
-This client *always* uses TLS with strict hostname checking, unlike the
-official Google Python recaptcha-client_, which is harcoded_ to use plaintext
-HTTP.
-
-Small portions of this code were taken from the official Google Python
-recaptcha-client_ module, version 1.0.6. Those portions are
-:class:`RecaptchaResponse`, :data:`API_SERVER`, They total 5 lines of code,
-which are copyright the authors of the recaptcha-client_ package.
-
-.. _hardcoded: https://code.google.com/p/recaptcha/source/browse/trunk/recaptcha-plugins/python/recaptcha/client/captcha.py#76
-.. _recaptcha-client: https://pypi.python.org/pypi/recaptcha-client/1.0.6
-"""
-
-import logging
-import urllib
-
-from OpenSSL.crypto import FILETYPE_PEM
-from OpenSSL.crypto import load_certificate
-
-from twisted import version as _twistedversion
-from twisted.internet import defer
-from twisted.internet import protocol
-from twisted.internet import reactor
-from twisted.python import failure
-from twisted.python.versions import Version
-from twisted.web import client
-from twisted.web.http_headers import Headers
-from twisted.web.iweb import IBodyProducer
-
-from zope.interface import implements
-
-from bridgedb.crypto import SSLVerifyingContextFactory
-
-#: This was taken from recaptcha.client.captcha.API_SSL_SERVER.
-API_SSL_SERVER = API_SERVER = "https://www.google.com/recaptcha/api"
-API_SSL_VERIFY_URL = "%s/verify" % API_SSL_SERVER
-
-#: (type: `OpenSSL.crypto.X509`) Only trust certificate for the reCAPTCHA
-#: :data:`API_SSL_SERVER` which were signed by the Google Internet Authority CA.
-GOOGLE_INTERNET_AUTHORITY_CA_CERT = load_certificate(FILETYPE_PEM, bytes("""\
------BEGIN CERTIFICATE-----
-MIICsDCCAhmgAwIBAgIDFXfhMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
-MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
-aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMjEyMTU1ODUwWhcNMTMxMjMxMTU1ODUw
-WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
-R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
-gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
-NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
-qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
-oDAfBgNVHSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHQ4EFgQUv8Aw
-6/VDET5nup6R+/xq2uNrEiQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E
-BAMCAQYwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
-Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAvprjecFG+iJsxzEF
-ZUNgujFQodUovxOWZshcnDW7fZ7mTlk3zpeVJrGPZzhaDhvuJjIfKqHweFB7gwB+
-ARlIjNvrPq86fpVg0NOTawALkSqOUMl3MynBQO+spR7EHcRbADQ/JemfTEh2Ycfl
-vZqhEFBfurZkX0eTANq98ZvVfpg=
------END CERTIFICATE-----"""))
-
-# `t.w.client.HTTPConnectionPool` isn't available in Twisted-12.0.0
-# (see ticket #11219: https://bugs.torproject.org/11219):
-_connectionPoolAvailable = _twistedversion >= Version('twisted', 12, 1, 0)
-if _connectionPoolAvailable:
- logging.info("Using HTTPConnectionPool for reCaptcha API server.")
- _pool = client.HTTPConnectionPool(reactor, persistent=False)
- _pool.maxPersistentPerHost = 5
- _pool.cachedConnectionTimeout = 30
- _agent = client.Agent(reactor, pool=_pool)
-else:
- logging.warn("Twisted-%s is too old for HTTPConnectionPool! Disabling..."
- % _twistedversion.short())
- _pool = None
- _agent = client.Agent(reactor)
-
-
-# Twisted>=14.0.0 changed the way in which hostname verification works.
-if _twistedversion >= Version('twisted', 14, 0, 0):
- from twisted.internet._sslverify import OpenSSLCertificateAuthorities
-
- class RecaptchaOpenSSLCertificateAuthorities(OpenSSLCertificateAuthorities):
- """The trusted CAs for connecting to reCAPTCHA servers."""
- #: A list of `OpenSSL.crypto.X509` objects.
- caCerts = [GOOGLE_INTERNET_AUTHORITY_CA_CERT,]
- def __init__(self):
- super(RecaptchaOpenSSLCertificateAuthorities, self).__init__(self.caCerts)
-
- class RecaptchaPolicyForHTTPS(client.BrowserLikePolicyForHTTPS):
- _trustRoot = RecaptchaOpenSSLCertificateAuthorities()
- def __init__(self):
- super(RecaptchaPolicyForHTTPS, self).__init__(trustRoot=self._trustRoot)
-
-
-def _setAgent(agent):
- """Set the global :attr:`agent`.
-
- :param agent: An :api:`twisted.web.client.Agent` for issuing requests.
- """
- global _agent
- _agent = agent
-
-def _getAgent(reactor=reactor, url=API_SSL_VERIFY_URL, connectTimeout=30,
- **kwargs):
- """Create a :api:`twisted.web.client.Agent` which will verify the
- certificate chain and hostname for the given **url**.
-
- :param reactor: A provider of the
- :api:`twisted.internet.interface.IReactorTCP` interface.
- :param str url: The full URL which will be requested with the
- ``Agent``. (default: :attr:`API_SSL_VERIFY_URL`)
- :param pool: An :api:`twisted.web.client.HTTPConnectionPool`
- instance. (default: :attr:`_pool`)
- :type connectTimeout: None or int
- :param connectTimeout: If not ``None``, the timeout passed to
- :api:`twisted.internet.reactor.connectTCP` or
- :api:`twisted.internet.reactor.connectSSL` for specifying the
- connection timeout. (default: ``30``)
- """
- # Twisted>=14.0.0 changed the way in which hostname verification works.
- if _twistedversion >= Version('twisted', 14, 0, 0):
- contextFactory = RecaptchaPolicyForHTTPS()
- else:
- contextFactory = SSLVerifyingContextFactory(url)
-
- if _connectionPoolAvailable:
- return client.Agent(reactor,
- contextFactory=contextFactory,
- connectTimeout=connectTimeout,
- pool=_pool,
- **kwargs)
- else:
- return client.Agent(reactor,
- contextFactory=contextFactory,
- connectTimeout=connectTimeout,
- **kwargs)
-
-_setAgent(_getAgent())
-
-
-class RecaptchaResponseError(ValueError):
- """There was an error with the reCaptcha API server's response."""
-
-
-class RecaptchaResponse(object):
- """Taken from recaptcha.client.captcha.`RecaptchaResponse`_.
- .. RecaptchaResponse: https://code.google.com/p/recaptcha/source/browse/trunk/recaptcha-plugins/python/recaptcha/client/captcha.py#7
- """
- def __init__(self, is_valid, error_code=None):
- self.is_valid = is_valid
- self.error_code = error_code
-
-
-class RecaptchaResponseProtocol(protocol.Protocol):
- """HTML parser which creates a :class:`RecaptchaResponse` from the body of
- the reCaptcha API server's response.
- """
- def __init__(self, finished):
- """Create a protocol for creating :class:`RecaptchaResponse`s.
-
- :type finished: :api:`~twisted.internet.defer.Deferred`
- :param finished: A deferred which will have its ``callback()`` called
- with a :class:`RecaptchaResponse`.
- """
- self.finished = finished
- self.remaining = 1024 * 10
- self.response = ''
-
- def dataReceived(self, data):
- """Called when some data is received from the connection."""
- if self.remaining:
- received = data[:self.remaining]
- self.response += received
- self.remaining -= len(received)
-
- def connectionLost(self, reason):
- """Called when the connection was closed.
-
- :type reason: :api:`twisted.python.failure.Failure`
- :param reason: A string explaning why the connection was closed,
- wrapped in a ``Failure`` instance.
-
- :raises: A :api:`twisted.internet.error.ConnectError` if the
- """
- valid = False
- error = reason.getErrorMessage()
- try:
- (valid, error) = self.response.strip().split('\n', 1)
- except ValueError:
- error = "Couldn't parse response from reCaptcha API server"
-
- valid = bool(valid == "true")
- result = RecaptchaResponse(is_valid=valid, error_code=error)
- logging.debug(
- "ReCaptcha API server response: %s(is_valid=%s, error_code=%s)"
- % (result.__class__.__name__, valid, error))
- self.finished.callback(result)
-
-
-class _BodyProducer(object):
- """I write a string into the HTML body of an open request."""
- implements(IBodyProducer)
-
- def __init__(self, body):
- self.body = body
- self.length = len(body)
-
- def startProducing(self, consumer):
- """Start writing the HTML body."""
- consumer.write(self.body)
- return defer.succeed(None)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
-
- def resumeProducing(self):
- pass
-
-
-def _cbRequest(response):
- """Callback for a :api:`twisted.web.client.Agent.request` which delivers
- the result to a :class:`RecaptchaResponseProtocol`.
-
- :returns: A :api:`~twisted.internet.defer.Deferred` which will callback
- with a ``recaptcha.RecaptchaResponse`` for the request.
- """
- finished = defer.Deferred()
- response.deliverBody(RecaptchaResponseProtocol(finished))
- return finished
-
-def _ebRequest(fail):
- """Errback for a :api:`twisted.web.client.Agent.request`.
-
- :param fail: A :api:`twisted.python.failure.Failure` which occurred during
- the request.
- """
- logging.debug("txrecaptcha._ebRequest() called with %r" % fail)
- error = fail.getErrorMessage() or "possible problem in _ebRequest()"
- return RecaptchaResponse(is_valid=False, error_code=error)
-
-def _encodeIfNecessary(string):
- """Encode unicode objects in utf-8 if necessary."""
- if isinstance(string, unicode):
- return string.encode('utf-8')
- return string
-
-def submit(recaptcha_challenge_field, recaptcha_response_field,
- private_key, remoteip, agent=_agent):
- """Submits a reCaptcha request for verification. This function is a patched
- version of the ``recaptcha.client.captcha.submit()`` function in
- reCaptcha's Python API.
-
- It does two things differently:
- 1. It uses Twisted for everything.
- 2. It uses SSL/TLS for everything.
-
- This function returns a :api:`~twisted.internet.defer.Deferred`. If you
- need a ``recaptcha.client.captcha.RecaptchaResponse`` to be returned, use
- the :func:`submit` function, which is an ``@inlineCallbacks`` wrapper for
- this function.
-
- :param str recaptcha_challenge_field: The value of the HTTP POST
- ``recaptcha_challenge_field`` argument from the form.
- :param recaptcha_response_field: The value of the HTTP POST
- ``recaptcha_response_field`` argument from the form.
- :param private_key: The reCAPTCHA API private key.
- :param remoteip: An IP address to give to the reCaptcha API server.
- :returns: A :api:`~twisted.internet.defer.Deferred` which will callback
- with a ``recaptcha.RecaptchaResponse`` for the request.
- """
- if not (recaptcha_response_field and len(recaptcha_response_field) and
- recaptcha_challenge_field and len(recaptcha_challenge_field)):
- d = defer.Deferred()
- d.addBoth(_ebRequest) # We want `is_valid=False`
- d.errback(failure.Failure(ValueError('incorrect-captcha-sol')))
- return d
-
- params = urllib.urlencode({
- 'privatekey': _encodeIfNecessary(private_key),
- 'remoteip': _encodeIfNecessary(remoteip),
- 'challenge': _encodeIfNecessary(recaptcha_challenge_field),
- 'response': _encodeIfNecessary(recaptcha_response_field)})
- body = _BodyProducer(params)
- headers = Headers({"Content-type": ["application/x-www-form-urlencoded"],
- "User-agent": ["reCAPTCHA Python"]})
- d = agent.request('POST', API_SSL_VERIFY_URL, headers, body)
- d.addCallbacks(_cbRequest, _ebRequest)
- return d
diff --git a/lib/bridgedb/util.py b/lib/bridgedb/util.py
deleted file mode 100644
index f15e08b..0000000
--- a/lib/bridgedb/util.py
+++ /dev/null
@@ -1,385 +0,0 @@
-# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_util -*-
-#
-# This file is part of BridgeDB, a Tor bridge distribution system.
-#
-# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
-# Matthew Finkel 0x017DD169EA793BE2 <sysrqb at torproject.org>
-# :copyright: (c) 2013-2015, Isis Lovecruft
-# (c) 2013-2015, Matthew Finkel
-# (c) 2007-2015, The Tor Project, Inc.
-# :license: 3-Clause BSD, see LICENSE for licensing information
-
-"""Common utilities for BridgeDB."""
-
-from functools import partial
-
-import abc
-import logging
-import logging.config
-import logging.handlers
-import os
-
-from twisted.python import components
-
-
-def _getLogHandlers(logToFile=True, logToStderr=True):
- """Get the appropriate list of log handlers.
-
- :param bool logToFile: If ``True``, add a logfile handler.
- :param bool logToStderr: If ``True``, add a stream handler to stderr.
- :rtype: list
- :returns: A list containing the appropriate log handler names from the
- :class:`logging.config.dictConfigClass`.
- """
- logHandlers = []
- if logToFile:
- logHandlers.append('rotating')
- if logToStderr:
- logHandlers.append('console')
- return logHandlers
-
-def _getRotatingFileHandler(filename, mode='a', maxBytes=1000000, backupCount=0,
- encoding='utf-8', uid=None, gid=None):
- """Get a :class:`logging.RotatingFileHandler` with a logfile which is
- readable+writable only by the given **uid** and **gid**.
-
- :param str filename: The full path to the log file.
- :param str mode: The mode to open **filename** with. (default: ``'a'``)
- :param int maxBytes: Rotate logfiles after they have grown to this size in
- bytes.
- :param int backupCount: The number of logfiles to keep in rotation.
- :param str encoding: The encoding for the logfile.
- :param int uid: The owner UID to set on the logfile.
- :param int gid: The GID to set on the logfile.
- :rtype: :class:`logging.handlers.RotatingFileHandler`
- :returns: A logfile handler which will rotate files and chown/chmod newly
- created files.
- """
- # Default to the current process owner's uid and gid:
- uid = os.getuid() if not uid else uid
- gid = os.getgid() if not gid else gid
-
- if not os.path.exists(filename):
- open(filename, 'a').close()
- os.chown(filename, uid, gid)
- try:
- os.chmod(filename, os.ST_WRITE | os.ST_APPEND)
- except AttributeError: # pragma: no cover
- logging.error("""
- XXX FIXME: Travis chokes on `os.ST_WRITE` saying that the module doesn't
- have that attribute, for some reason:
- https://travis-ci.org/isislovecruft/bridgedb/builds/24145963#L1601""")
- os.chmod(filename, 384)
-
- fileHandler = partial(logging.handlers.RotatingFileHandler,
- filename,
- mode,
- maxBytes=maxBytes,
- backupCount=backupCount,
- encoding=encoding)
- return fileHandler
-
-def configureLogging(cfg):
- """Set up Python's logging subsystem based on the configuration.
-
- :type cfg: :class:`~bridgedb.persistent.Conf`
- :param cfg: The current configuration, including any in-memory settings.
- """
- from bridgedb import safelog
-
- # Turn on safe logging by default:
- safelogging = getattr(cfg, 'SAFELOGGING', True)
- safelog.setSafeLogging(safelogging)
-
- level = getattr(cfg, 'LOGLEVEL', 'WARNING')
- logLevel = getattr(logging, level, 0)
- logStderr = getattr(cfg, 'LOG_TO_STDERR', False)
- logfileName = getattr(cfg, 'LOGFILE', "bridgedb.log")
- logfileCount = getattr(cfg, 'LOGFILE_COUNT', 3) - 1
- logfileRotateSize = getattr(cfg, 'LOGFILE_ROTATE_SIZE', 10000000)
- logThreads = getattr(cfg, 'LOG_THREADS', False)
- logTrace = getattr(cfg, 'LOG_TRACE', False)
- logTimeFormat = getattr(cfg, 'LOG_TIME_FORMAT', "%H:%M:%S")
-
- logFilters = []
- if safelogging:
- logFilters = ['safelogEmail', 'safelogIPv4', 'safelogIPv6']
-
- logConfig = {
- 'version': 1,
- 'filters': {
- 'safelogEmail': {'()': safelog.SafelogEmailFilter},
- 'safelogIPv4': {'()': safelog.SafelogIPv4Filter},
- 'safelogIPv6': {'()': safelog.SafelogIPv6Filter},
- },
- 'formatters': {
- 'default': {'()': JustifiedLogFormatter,
- # These values below are kwargs passed to
- # :class:`JustifiedFormatter`:
- 'logThreads': logThreads,
- 'logTrace': logTrace,
- 'datefmt': logTimeFormat},
- },
- 'handlers': {
- 'console': {'class': 'logging.StreamHandler',
- 'level': logLevel,
- 'formatter': 'default',
- 'filters': logFilters},
- 'rotating': {'()': _getRotatingFileHandler(logfileName, 'a',
- logfileRotateSize,
- logfileCount),
- 'level': logLevel,
- 'formatter': 'default',
- 'filters': logFilters},
- },
- 'root': {
- 'handlers': _getLogHandlers(logfileName, logStderr),
- 'level': logLevel,
- },
- }
-
- logging.config.dictConfig(logConfig)
-
- logging.info("Logger Started.")
- logging.info("Level: %s", logLevel)
- logging.info("Safe Logging: %sabled" % ("En" if safelogging else "Dis"))
-
-def levenshteinDistance(s1, s2, len1=None, len2=None,
- offset1=0, offset2=0, memo=None):
- """Compute the Levenstein Distance between two strings.
-
- The `Levenshtein String Distance Algorithm
- <https://en.wikipedia.org/wiki/Levenshtein_distance>` efficiently computes
- the number of characters which must be changed in **s1** to make it
- identical to **s2**.
-
- >>> levenshteinDistance('cat', 'cat')
- 0
- >>> levenshteinDistance('cat', 'hat')
- 1
- >>> levenshteinDistance('arma', 'armadillo')
- 5
-
- :param str s1: The string which should be changed.
- :param str s2: The string which **stringOne** should be compared to.
- """
- len1 = len(s1) if len1 is None else len1
- len2 = len(s2) if len2 is None else len2
- memo = {} if memo is None else memo
-
- key = ','.join([str(offset1), str(len1), str(offset2), str(len2)])
- if memo.get(key) is not None: return memo[key]
-
- if len1 == 0: return len2
- elif len2 == 0: return len1
-
- cost = 0 if (s1[offset1] == s2[offset2]) else 1
- distance = min(
- levenshteinDistance(s1, s2, len1-1, len2, offset1+1, offset2, memo) + 1,
- levenshteinDistance(s1, s2, len1, len2-1, offset1, offset2+1, memo) + 1,
- levenshteinDistance(s1, s2, len1-1, len2-1, offset1+1, offset2+1, memo) + cost,
- )
- memo[key] = distance
- return distance
-
-def isascii(s):
- """Return True if there are no non-ASCII characters in s, False otherwise.
-
- Note that this function differs from the str.is* methods in that
- it returns True for the empty string, rather than False.
-
- >>> isascii('\x80')
- False
- >>> isascii('foo\tbar\rbaz\n')
- True
- >>> isascii('foo bar')
- True
-
- :param str s: The string to check for non-ASCII characters.
- """
- return all(map((lambda ch: ord(ch) < 128), s))
-
-def isascii_noncontrol(s):
- """Return True if there are no non-ASCII or control characters in
- s, False otherwise.
-
- Note that this function differs from the str.is* methods in that
- it returns True for the empty string, rather than False.
-
- >>> isascii_noncontrol('\x80')
- False
- >>> isascii_noncontrol('foo\tbar\rbaz\n')
- False
- >>> isascii_noncontrol('foo bar')
- True
-
- :param str s: The string to check for non-ASCII or control characters.
- """
- return all(map((lambda ch: 32 <= ord(ch) < 127), s))
-
-def replaceControlChars(text, replacement=None, encoding="utf-8"):
- """Remove ASCII control characters [0-31, 92, 127].
-
- >>> replaceControlChars('foo\n bar\\ baz\r \t\0quux\n')
- 'foo bar baz quux'
- >>> replaceControlChars("\bI wonder if I'm outside the quotes now")
- "I wonder if I'm outside the quotes now"
-
- :param str text: Some text to remove ASCII control characters from.
- :param int replacement: If given, the **replacement** should be an integer
- representing the decimal representation of the byte to replace
- occurences of ASCII control characters with. For example, if they
- should be replaced with the character ``'a'``, then ``97`` should be
- used as the **replacement**, because ``ord('a') == 97``.
- :param str encoding: The encoding of the **text**.
- :rtype: str
- :returns: The sanitized **text**.
- """
- escaped = bytearray()
-
- for byte in bytearray(text, encoding):
- if byte in range(0, 32) + [92, 127]:
- if replacement:
- byte = replacement
- else:
- continue
- escaped += bytearray([byte])
-
- return str(escaped)
-
-def registerAdapter(adapter, adapted, interface):
- """Register a Zope interface adapter for global use.
-
- See :api:`twisted.python.components.registerAdapter` and the Twisted
- Matrix Labs `howto documentation for components`_.
-
- .. howto documentation for components:
- https://twistedmatrix.com/documents/current/core/howto/components.html
- """
- try:
- components.registerAdapter(adapter, adapted, interface)
- except ValueError: # An adapter class was already registered
- pass
-
-
-class JustifiedLogFormatter(logging.Formatter):
- """A logging formatter which pretty prints thread and calling function
- information, in addition to the normal timestamp, log level, and log
- message.
-
- :ivar int width: The width of the column for the calling function
- information, if the latter is to be included.
- """
- width = 30
-
- def __init__(self, logThreads=False, logTrace=False,
- datefmt="%H:%M:%s"):
- """If **logTrace** is ``True``, the line number, module name, and
- function name where the logger was called will be included in the
- message, and the width of this information will always equal ``width``.
-
- :param bool logThreads: If ``True``, include the current thread name
- and ID in formatted log messages.
- :param bool logTrace: If ``True``, include information on the calling
- function in formatted log messages.
- """
- super(JustifiedLogFormatter, self).__init__(datefmt=datefmt)
- self.logThreads = logThreads
- self.logTrace = logTrace
-
- _fmt = ["%(asctime)s %(levelname)-7.7s"]
- if self.logThreads:
- _fmt.append("[%(threadName)s id:%(thread)d]")
- _fmt.append("%(callingFunc)s")
- _fmt.append("%(message)s")
-
- self._fmt = " ".join(_fmt)
-
- def _formatCallingFuncName(self, record):
- """Format the combined module name and function name of the place where
- the log message/record was recorded, so that the formatted string is
- left-justified and not longer than the :cvar:`width`.
-
- :type record: :class:`logging.LogRecord`
- :param record: A record of an event created by calling a logger.
- :returns: The :class:`logging.LogRecord` with its ``message``
- attribute rewritten to contain the module and function name,
- truncated to ``width``, or padded on the right with spaces as is
- necessary.
- """
- callingFunc = ""
- if self.logTrace:
- # The '.' character between the module name and function name
- # would otherwise be interpreted as a format string specifier, so
- # we must specify ``chr(46)``:
- lineno = "L%s:" % record.lineno
- caller = "%s%-s%s" % (lineno.rjust(6), record.module, chr(46))
- maxFuncNameWidth = self.width - 2 - len(caller)
- funcName = record.funcName
- if len(funcName) > maxFuncNameWidth:
- funcName = record.funcName[:maxFuncNameWidth]
- caller += "%s()" % (funcName)
- callingFunc = caller.ljust(self.width)
-
- record.callingFunc = callingFunc
- return record
-
- def format(self, record):
- """Reformat this log **record** to neatly print thread and function
- traces, if configured to do so.
-
- :type record: :class:`logging.LogRecord`
- :param record: A record of an event created by calling a logger.
- """
- record = self._formatCallingFuncName(record)
- return super(JustifiedLogFormatter, self).format(record)
-
-
-class mixin:
- """Subclasses of me can be used as a mixin class by registering another
- class, ``ClassA``, which should be mixed with the ``mixin`` subclass, in
- order to provide simple, less error-prone, multiple inheritance models::
-
- >>> from __future__ import print_function
- >>> from bridgedb.util import mixin
- >>>
- >>> class ClassA(object):
- >>> def sayWhich(self):
- >>> print("ClassA.sayWhich() called.")
- >>> def doSuperThing(self):
- >>> super(ClassA, self).__repr__()
- >>> def doThing(self):
- >>> print("ClassA is doing a thing.")
- >>>
- >>> class ClassB(ClassA):
- >>> def sayWhich(self):
- >>> print("ClassB.sayWhich() called.")
- >>> def doSuperThing(self):
- >>> super(ClassB, self).__repr__()
- >>> def doOtherThing(self):
- >>> print("ClassB is doing something else.")
- >>>
- >>> class ClassM(mixin):
- >>> def sayWhich(self):
- >>> print("ClassM.sayWhich() called.")
- >>>
- >>> ClassM.register(ClassA)
- >>>
- >>> class ClassC(ClassM, ClassB):
- >>> def sayWhich(self):
- >>> super(ClassC, self).sayWhich()
- >>>
- >>> c = ClassC()
- >>> c.sayWhich()
- ClassM.saywhich() called.
- >>> c.doSuperThing()
- <super: <class 'ClassA'>, NULL>
- >>> c.doThing()
- ClassA is doing a thing.
- >>> c.doOtherThing()
- ClassB is doing something else.
-
- .. info:: This class' name is lowercased because pylint is hardcoded to
- expect mixin classes to end in ``'mixin'``.
- """
- __metaclass__ = abc.ABCMeta
diff --git a/scripts/bridgedb b/scripts/bridgedb
index 7afbed9..2ec37b1 100644
--- a/scripts/bridgedb
+++ b/scripts/bridgedb
@@ -26,6 +26,6 @@ if option.subCommand is not None:
# Hack to set the PYTHONPATH:
sys.path[:] = map(os.path.abspath, sys.path)
sys.path.insert(0, os.path.abspath(getcwd()))
- sys.path.insert(0, os.path.abspath(os.path.join(getcwd(), '../lib')))
+ sys.path.insert(0, os.path.abspath(os.path.join(getcwd(), '..')))
run(option)
diff --git a/setup.cfg b/setup.cfg
index 1ce1f67..074147d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -10,7 +10,7 @@
[compile_catalog]
domain = bridgedb
-directory = lib/bridgedb/i18n
+directory = bridgedb/i18n
statistics = true
[extract_messages]
@@ -18,20 +18,20 @@ strip_comments = false
add_comments = TRANSLATORS,TRANSLATOR,TRANSLATOR:,
msgid-bugs-address = 'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&keywords=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'
copyright-holder = 'The Tor Project, Inc.'
-input-dirs = lib/bridgedb
-output_file = lib/bridgedb/i18n/templates/bridgedb.pot
+input-dirs = bridgedb
+output_file = bridgedb/i18n/templates/bridgedb.pot
sort-by-file = true
width = 80
[init_catalog]
domain = bridgedb
-input_file = lib/bridgedb/i18n/templates/bridgedb.pot
-output_dir = lib/bridgedb/i18n
+input_file = bridgedb/i18n/templates/bridgedb.pot
+output_dir = bridgedb/i18n
[update_catalog]
domain = bridgedb
-input_file = lib/bridgedb/i18n/templates/bridgedb.pot
-output_dir = lib/bridgedb/i18n
+input_file = bridgedb/i18n/templates/bridgedb.pot
+output_dir = bridgedb/i18n
previous = true
[build_sphinx]
diff --git a/setup.py b/setup.py
index cf54a3e..2b132a8 100644
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@ versioneer.tag_prefix = 'bridgedb-'
# source tarballs should unpack to a directory like 'bridgedb-6.6.6'
versioneer.parentdir_prefix = 'bridgedb-'
-pkgpath = os.path.join('lib', 'bridgedb')
+pkgpath = 'bridgedb'
# Repo directory that contains translations; this directory should contain
# both uncompiled translations (.po files) as well as compiled ones (.mo
@@ -126,7 +126,7 @@ def get_supported_langs():
The two-letter country code of each language which is going to be
installed will be added to a list, and this list will be written to
- :attr:`repo_langs`, so that lib/bridgedb/__init__.py can store a
+ :attr:`repo_langs`, so that bridgedb/__init__.py can store a
package-level attribute ``bridgedb.__langs__``, which will be a list of
any languages which were installed.
@@ -161,7 +161,7 @@ def get_supported_langs():
'LC_MESSAGES', 'bridgedb.mo'))
supported.sort()
- # Write our list of supported languages to 'lib/bridgedb/_langs.py':
+ # Write our list of supported languages to 'bridgedb/_langs.py':
new_langs_lines = []
with open(repo_langs, 'r') as langsfile:
for line in langsfile.readlines():
@@ -389,12 +389,11 @@ setuptools.setup(
maintainer_email='isis at torproject.org 0xA3ADB67A2CDB8B35',
url='https://www.torproject.org',
download_url='https://gitweb.torproject.org/bridgedb.git',
- package_dir={'': 'lib'},
+ package_dir={'bridgedb': 'bridgedb'},
packages=['bridgedb',
'bridgedb.email',
'bridgedb.https',
- 'bridgedb.parse',
- 'bridgedb.test'],
+ 'bridgedb.parse'],
scripts=['scripts/bridgedb',
'scripts/get-tor-exits'],
extras_require={'test': ["sure==1.2.2",
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..e69de29
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/deprecated.py b/test/deprecated.py
new file mode 100644
index 0000000..39920a2
--- /dev/null
+++ b/test/deprecated.py
@@ -0,0 +1,448 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""deprecated â functions and classes which have been removed from the
+production code but are kept in order to be used in regression testing.
+"""
+
+import ipaddr
+import re
+
+from twisted.python import deprecate
+from twisted.python.versions import Version
+
+
+ at deprecate.deprecated(
+ Version('bridgedb', 0, 2, 4),
+ replacement='bridgedb.bridges.Bridge')
+class Bridge(object):
+ """Holds information for a single bridge, along with any Pluggable
+ Transports it is also running.
+
+ :attr str nickname: The bridge's nickname. Not currently used.
+ :attr ip: (:class:`ipaddr.IPAddress`) The bridge's IPv4 address, specified
+ on the 'r'-line in a networkstatus document.
+ :attr int orport: The bridge's OR port.
+ :attr dict or_addresses: The bridges alternate IP addresses. The keys
+ should be instances of ``ipaddr.IPAddress``, and the value should be a
+ :class:`bridgedb.parse.addr.PortList` for the port(s) on which that
+ address is listening.
+ :attr list transports: List of :class:`PluggableTransport` instances for
+ each PT which the bridge supports.
+ :attr str fingerprint: The bridge's identity digest, in lowercase hex,
+ without whitespace.
+ :attr bool running: ``True``, if this bridge was given the ``Running`` flag.
+ :attr bool stable: ``True``, if this bridge was given the ``Stable`` flag.
+ :attr dict blockingCountries: A dictionary whose keys are strings of
+ ``"IP:port"`` pairs, and the keys are lists of two letter country
+ codes which block that IP:port. For example::
+ {"1.2.3.4:9001": ['sk', 'us', 'ir', 'cn']}
+ :attr str desc_digest: SHA1 hexdigest of the bridge's descriptor as
+ defined in the networkstatus document.
+ :attr str ei_digest: SHA1 hexdigest of the bridge's extra-info document as
+ given in the bridge's descriptor, corresponding to desc_digest.
+ :attr bool verified: Did we receive the descriptor for this bridge that
+ was specified in the networkstatus?
+ """
+ def __init__(self, nickname, ip, orport, fingerprint=None, id_digest=None,
+ or_addresses=None, transports=None):
+ """Create a new Bridge. One of fingerprint and id_digest must be set.
+ """
+ self.nickname = nickname
+ self.ip = ip
+ self.orport = orport
+ if not or_addresses: or_addresses = {}
+ self.or_addresses = or_addresses
+ if not transports: transports = []
+ self.transports = transports
+ self.running = self.stable = None
+ self.blockingCountries = {}
+ self.desc_digest = None
+ self.ei_digest = None
+ self.verified = False
+
+ if id_digest is not None:
+ assert fingerprint is None
+ if len(id_digest) != DIGEST_LEN:
+ raise TypeError("Bridge with invalid ID")
+ self.fingerprint = toHex(id_digest)
+ elif fingerprint is not None:
+ if not isValidFingerprint(fingerprint):
+ raise TypeError("Bridge with invalid fingerprint (%r)"%
+ fingerprint)
+ self.fingerprint = fingerprint.lower()
+ else:
+ raise TypeError("Bridge with no ID")
+
+ def setDescriptorDigest(self, digest):
+ """Set the descriptor digest, specified in the NS."""
+ self.desc_digest = digest
+
+ def setExtraInfoDigest(self, digest):
+ """Set the extra-info digest, specified in the descriptor."""
+ self.ei_digest = digest
+
+ def setVerified(self):
+ """Call when the bridge's descriptor is parsed"""
+ self.verified = True
+
+ def isVerified(self):
+ """Returns the truthiness of ``verified``"""
+ return self.verified
+
+ def getID(self):
+ """Return the bridge's identity digest."""
+ return fromHex(self.fingerprint)
+
+ def __repr__(self):
+ """Return a piece of python that evaluates to this bridge."""
+ if self.or_addresses:
+ return "Bridge(%r,%r,%d,%r,or_addresses=%s)"%(
+ self.nickname, self.ip, self.orport, self.fingerprint,
+ self.or_addresses)
+ return "Bridge(%r,%r,%d,%r)"%(
+ self.nickname, self.ip, self.orport, self.fingerprint)
+
+ def getConfigLine(self, includeFingerprint=False, addressClass=None,
+ request=None, transport=None):
+ """Returns a valid bridge line for inclusion in a torrc.
+
+ :param bool includeFingerprint: If ``True``, include the
+ ``fingerprint`` of this :class:`Bridge` in the returned bridge
+ line.
+ :param DOCDOC addressClass: Type of address to choose.
+ :param str request: A string unique to this request e.g. email-address
+ or ``uniformMap(ip)`` or ``'default'``.
+ :param str transport: A pluggable transport method name.
+ """
+
+ if not request: request = 'default'
+ digest = getHMACFunc('Order-Or-Addresses')(request)
+ pos = long(digest[:8], 16) # lower 8 bytes -> long
+
+ # default address type
+ if not addressClass: addressClass = ipaddr.IPv4Address
+
+ # pluggable transports
+ if transport:
+ # filter by 'methodname'
+ transports = filter(lambda x: transport == x.methodname,
+ self.transports)
+ # filter by 'addressClass'
+ transports = filter(lambda x: isinstance(x.address, addressClass),
+ transports)
+ if transports:
+ pt = transports[pos % len(transports)]
+ return pt.getTransportLine(includeFingerprint)
+
+ # filter addresses by address class
+ addresses = filter(lambda x: isinstance(x[0], addressClass),
+ self.or_addresses.items())
+
+ # default ip, orport should get a chance at being selected
+ if isinstance(self.ip, addressClass):
+ addresses.insert(0,(self.ip, addr.PortList(self.orport)))
+
+ if addresses:
+ address,portlist = addresses[pos % len(addresses)]
+ if isinstance(address, ipaddr.IPv6Address): ip = "[%s]"%address
+ else: ip = "%s"%address
+ orport = portlist[pos % len(portlist)]
+
+ if includeFingerprint:
+ return "%s:%d %s" % (ip, orport, self.fingerprint)
+ else:
+ return "%s:%d" % (ip, orport)
+
+ def getAllConfigLines(self,includeFingerprint=False):
+ """Generator. Iterate over all valid config lines for this bridge."""
+ for address,portlist in self.or_addresses.items():
+ if type(address) is ipaddr.IPv6Address:
+ ip = "[%s]" % address
+ else:
+ ip = "%s" % address
+
+ for orport in portlist:
+ if includeFingerprint:
+ yield "bridge %s:%d %s" % (ip,orport,self.fingerprint)
+ else:
+ yield "bridge %s:%d" % (ip,orport)
+ for pt in self.transports:
+ yield pt.getTransportLine(includeFingerprints)
+
+
+ def assertOK(self):
+ assert is_valid_ip(self.ip)
+ assert isValidFingerprint(self.fingerprint)
+ assert 1 <= self.orport <= 65535
+ if self.or_addresses:
+ for address, portlist in self.or_addresses.items():
+ assert is_valid_ip(address)
+ for port in portlist:
+ assert type(port) is int
+ assert 1 <= port <= 65535
+
+ def setStatus(self, running=None, stable=None):
+ if running is not None:
+ self.running = running
+ if stable is not None:
+ self.stable = stable
+
+ def isBlocked(self, countryCode, addressClass, methodname=None):
+ """ if at least one address:port of the selected addressClass and
+ (optional) transport type is not blocked in countryCode, return True
+ """
+ # 1) transport is specified
+ if methodname is not None:
+ for transport in self.transports:
+ key = "%s:%s" % (transport.address, transport.port)
+ if (isinstance(transport.address, addressClass)
+ and transport.methodname.lower() == methodname.lower()):
+ try:
+ if countryCode not in self.blockingCountries[key]:
+ return False
+ except KeyError:
+ return False # no blocklist
+ return True
+ # 2) no transport specified (default)
+ else:
+ # 3) check primary ip, port
+ # XXX: could be more elegant if ip,orport were not special case
+ if isinstance(self.ip, addressClass):
+ key = "%s:%s" % (self.ip, self.orport)
+ try:
+ if countryCode not in self.blockingCountries[key]:
+ return False
+ except KeyError: return False # no blocklist
+
+ # 4) check or addresses
+ for address,portlist in self.or_addresses.items():
+ if isinstance(address, addressClass):
+ # check each port
+ for port in portlist:
+ key = "%s:%s" % (address, port)
+ try:
+ if countryCode not in self.blockingCountries[key]:
+ return False
+ except KeyError: return False # no blocklist
+ return True
+
+ # Bridge Stability (#5482) properties.
+ @property
+ def familiar(self):
+ """
+ A bridge is 'familiar' if 1/8 of all active bridges have appeared
+ more recently than it, or if it has been around for a Weighted Time of 8 days.
+ """
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).familiar
+
+ @property
+ def wfu(self):
+ """Weighted Fractional Uptime"""
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).weightedFractionalUptime
+
+ @property
+ def weightedTime(self):
+ """Weighted Time"""
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).weightedTime
+
+ @property
+ def wmtbac(self):
+ """Weighted Mean Time Between Address Change"""
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).wmtbac
+
+ @property
+ def tosa(self):
+ """the Time On Same Address (TOSA)"""
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).tosa
+
+ @property
+ def weightedUptime(self):
+ """Weighted Uptime"""
+ with bridgedb.Storage.getDB() as db:
+ return db.getBridgeHistory(self.fingerprint).weightedUptime
+
+
+ at deprecate.deprecated(
+ Version('bridgedb', 0, 2, 4),
+ replacement='bridgedb.bridges.PluggableTransport')
+class PluggableTransport(object):
+ """A PT with reference to the parent bridge on which it is running.
+
+ Deprecated :class:`bridgedb.Bridges.PluggableTransport`, replaced in
+ bridgedb-0.2.4, by :class:`bridgedb.bridges.PluggableTransport`.
+ """
+
+ def __init__(self, bridge, methodname, address, port, argdict=None):
+ """Create a ``PluggableTransport`` describing a PT running on a bridge.
+
+ Pluggable transports are described within a bridge's ``@type
+ bridge-extrainfo`` descriptor, see the ``Specifications: Client
+ behavior`` section and the ``TOR_PT_SERVER_TRANSPORT_OPTIONS``
+ description in pt-spec.txt_ for additional specification.
+
+ :type bridge: :class:`Bridge`
+ :param bridge: The parent bridge running this pluggable transport
+ instance, i.e. the main ORPort bridge whose
+ ``@type bridge-server-descriptor`` contains a hash digest for a
+ ``@type bridge-extrainfo-document``, the latter of which contains
+ the parameter of this pluggable transport in its ``transport``
+ line.
+
+ :param str methodname: The canonical "name" for this pluggable
+ transport, i.e. the one which would be specified in a torrc
+ file. For example, ``"obfs2"``, ``"obfs3"``, ``"scramblesuit"``
+ would all be pluggable transport method names.
+
+ :param str address: The IP address of the transport. Currently (as of
+ 20 March 2014), there are no known, widely-deployed pluggable
+ transports which support IPv6. Ergo, this is very likely going to
+ be an IPv4 address.
+
+ :param int port: A integer specifying the port which this pluggable
+ transport is listening on. (This should likely be whatever port the
+ bridge specified in its ``ServerTransportPlugin`` torrc line,
+ unless the pluggable transport is running in "managed" mode.)
+
+ :param dict argdict: Some PTs can take additional arguments, which
+ must be distributed to the client out-of-band. These are present
+ in the ``@type bridge-extrainfo-document``, in the ``transport``
+ line like so::
+
+ METHOD SP ADDR ":" PORT SP [K=V[,K=V[,K=V[â¦]]]]
+
+ where K is the **argdict** key, and V is the value. For example,
+ in the case of ``scramblesuit``, for which the client must supply
+ a shared secret to the ``scramblesuit`` instance running on the
+ bridge, the **argdict** would be something like::
+
+ {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
+
+ .. _pt-spec.txt:
+ https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
+ """
+ #XXX: assert are disabled with python -O
+ assert isinstance(bridge, Bridge)
+ assert type(address) in (ipaddr.IPv4Address, ipaddr.IPv6Address)
+ assert type(port) is int
+ assert (0 < port < 65536)
+ assert type(methodname) is str
+
+ self.bridge = bridge
+ self.address = address
+ self.port = port
+ self.methodname = methodname
+ if type(argdict) is dict:
+ self.argdict = argdict
+ else: self.argdict = {}
+
+ def getTransportLine(self, includeFingerprint=False, bridgePrefix=False):
+ """Get a torrc line for this pluggable transport.
+
+ This method does not return lines which are prefixed with the word
+ 'bridge', as they would be in a torrc file. Instead, lines returned
+ look like this:
+
+ obfs3 245.102.100.252:23619 59ca743e89b508e16b8c7c6d2290efdfd14eea98
+
+ :param bool includeFingerprints: If ``True``, include the digest of
+ this bridges public identity key in the torrc line.
+ :param bool bridgePrefix: If ``True``, add ``'Bridge '`` to the
+ beginning of each returned line (suitable for pasting directly
+ into a torrc file).
+ :rtype: str
+ :returns: A configuration line for adding this pluggable transport
+ into a torrc file.
+ """
+ sections = []
+
+ if bridgePrefix:
+ sections.append('Bridge')
+
+ if isinstance(self.address, ipaddr.IPv6Address):
+ host = "%s [%s]:%d" % (self.methodname, self.address, self.port)
+ else:
+ host = "%s %s:%d" % (self.methodname, self.address, self.port)
+ sections.append(host)
+
+ if includeFingerprint:
+ sections.append(self.bridge.fingerprint)
+
+ args = " ".join(["%s=%s" % (k, v) for k, v in self.argdict.items()])
+ sections.append(args)
+
+ line = ' '.join(sections)
+ return line
+
+
+ at deprecate.deprecated(
+ Version('bridgedb', 0, 0, 1),
+ replacement='bridgedb.parse.addr.PortList')
+class PortList:
+ """Deprecated :class:`bridgedb.Bridges.PortList`, replaced in
+ bridgedb-0.1.0, in commit 1f111e5, by
+ :class:`bridgedb.parse.addr.PortList`.
+
+ This class and the newer class from :mod:`bridgedb.parse.addr` are
+ alternately :api:`~twisted.python.monkey.MonkeyPatcher.patch`ed into the
+ :mod:`old unittests <bridgedb.Tests>`, so that the later functions as a
+ suite of regression tests.
+ """
+ def __init__(self, *args, **kwargs):
+ self.ports = set()
+ self.add(*args)
+
+ def _sanitycheck(self, val):
+ #XXX: if debug=False this is disabled. bad!
+ assert type(val) is int
+ assert(0 < val <= 65535)
+
+ def __contains__(self, val1):
+ return val1 in self.ports
+
+ def add(self, *args):
+ PORTSPEC_LEN = 16
+ for arg in args:
+ try:
+ if type(arg) is str:
+ ports = set([int(p) for p in arg.split(',')][:PORTSPEC_LEN])
+ [self._sanitycheck(p) for p in ports]
+ self.ports.update(ports)
+ if type(arg) is int:
+ self._sanitycheck(arg)
+ self.ports.update([arg])
+ if type(arg) is PortList:
+ self.add(list(arg.ports))
+ except AssertionError: raise ValueError
+ except ValueError: raise
+
+ def __iter__(self):
+ return self.ports.__iter__()
+
+ def __str__(self):
+ s = ""
+ for p in self.ports:
+ s += "".join(",%s"%p)
+ return s.lstrip(",")
+
+ def __repr__(self):
+ return "PortList('%s')" % self.__str__()
+
+ def __len__(self):
+ return len(self.ports)
+
+ def __getitem__(self, x):
+ return list(self.ports)[x]
diff --git a/test/email_helpers.py b/test/email_helpers.py
new file mode 100644
index 0000000..14c86f4
--- /dev/null
+++ b/test/email_helpers.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+
+"""Helpers for testing the email distributor and its servers."""
+
+
+import io
+
+from bridgedb.persistent import Conf
+from bridgedb.email.distributor import IgnoreEmail
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.email.server import MailServerContext
+from bridgedb.schedule import Unscheduled
+from bridgedb.test import util
+
+
+EMAIL_DIST = True
+EMAIL_ROTATION_PERIOD = "1 day"
+EMAIL_INCLUDE_FINGERPRINTS = True
+EMAIL_GPG_SIGNING_ENABLED = True
+EMAIL_GPG_HOMEDIR = '.gnupg'
+EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = '0017098C5DF4197E3C884DCFF1B240D43F148C21'
+EMAIL_GPG_PASSPHRASE = None
+EMAIL_GPG_PASSPHRASE_FILE = None
+EMAIL_DOMAIN_MAP = {
+ 'googlemail.com': 'gmail.com',
+ 'mail.google.com': 'gmail.com',
+}
+EMAIL_DOMAIN_RULES = {
+ 'gmail.com': ["ignore_dots", "dkim"],
+ 'example.com': [],
+ 'localhost': [],
+}
+EMAIL_DOMAINS = ["gmail.com", "example.com", "localhost"]
+EMAIL_WHITELIST = {'white at list.ed': 'ABCD1234ABCD1234ABCD1234ABCD1234ABCD1234'}
+EMAIL_BLACKLIST = ['feidanchaoren0001 at gmail.com']
+EMAIL_FUZZY_MATCH = 4
+EMAIL_USERNAME = "bridges"
+EMAIL_SMTP_HOST = "127.0.0.1"
+EMAIL_SMTP_PORT = 25
+EMAIL_SMTP_FROM_ADDR = "bridges at localhost"
+EMAIL_N_BRIDGES_PER_ANSWER = 3
+EMAIL_FROM_ADDR = "bridges at localhost"
+EMAIL_BIND_IP = "127.0.0.1"
+EMAIL_PORT = 5225
+
+TEST_CONFIG_FILE = io.StringIO(unicode("""\
+EMAIL_DIST = %s
+EMAIL_ROTATION_PERIOD = %s
+EMAIL_INCLUDE_FINGERPRINTS = %s
+EMAIL_GPG_SIGNING_ENABLED = %s
+EMAIL_GPG_HOMEDIR = %s
+EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = %s
+EMAIL_GPG_PASSPHRASE = %s
+EMAIL_GPG_PASSPHRASE_FILE = %s
+EMAIL_DOMAIN_MAP = %s
+EMAIL_DOMAIN_RULES = %s
+EMAIL_DOMAINS = %s
+EMAIL_WHITELIST = %s
+EMAIL_BLACKLIST = %s
+EMAIL_FUZZY_MATCH = %s
+EMAIL_USERNAME = %s
+EMAIL_SMTP_HOST = %s
+EMAIL_SMTP_PORT = %s
+EMAIL_SMTP_FROM_ADDR = %s
+EMAIL_N_BRIDGES_PER_ANSWER = %s
+EMAIL_FROM_ADDR = %s
+EMAIL_BIND_IP = %s
+EMAIL_PORT = %s
+""" % (repr(EMAIL_DIST),
+ repr(EMAIL_ROTATION_PERIOD),
+ repr(EMAIL_INCLUDE_FINGERPRINTS),
+ repr(EMAIL_GPG_SIGNING_ENABLED),
+ repr(EMAIL_GPG_HOMEDIR),
+ repr(EMAIL_GPG_PRIMARY_KEY_FINGERPRINT),
+ repr(EMAIL_GPG_PASSPHRASE),
+ repr(EMAIL_GPG_PASSPHRASE_FILE),
+ repr(EMAIL_DOMAIN_MAP),
+ repr(EMAIL_DOMAIN_RULES),
+ repr(EMAIL_DOMAINS),
+ repr(EMAIL_WHITELIST),
+ repr(EMAIL_BLACKLIST),
+ repr(EMAIL_FUZZY_MATCH),
+ repr(EMAIL_USERNAME),
+ repr(EMAIL_SMTP_HOST),
+ repr(EMAIL_SMTP_PORT),
+ repr(EMAIL_SMTP_FROM_ADDR),
+ repr(EMAIL_N_BRIDGES_PER_ANSWER),
+ repr(EMAIL_FROM_ADDR),
+ repr(EMAIL_BIND_IP),
+ repr(EMAIL_PORT))))
+
+
+def _createConfig(configFile=TEST_CONFIG_FILE):
+ configuration = {}
+ TEST_CONFIG_FILE.seek(0)
+ compiled = compile(configFile.read(), '<string>', 'exec')
+ exec compiled in configuration
+ config = Conf(**configuration)
+ return config
+
+def _createMailServerContext(config=None, distributor=None):
+ if not config:
+ config = _createConfig()
+
+ if not distributor:
+ distributor = DummyEmailDistributor(
+ domainmap=config.EMAIL_DOMAIN_MAP,
+ domainrules=config.EMAIL_DOMAIN_RULES)
+
+ context = MailServerContext(config, distributor, Unscheduled())
+ return context
+
+
+class DummyEmailDistributor(object):
+ """A mocked :class:`bridgedb.email.distributor.EmailDistributor` which is used
+ to test :class:`bridgedb.EmailServer`.
+ """
+
+ _bridgesPerResponseMin = 3
+
+ def __init__(self, key=None, domainmap=None, domainrules=None,
+ answerParameters=None):
+ """None of the parameters are really used, â they are just there to retain an
+ identical method signature.
+ """
+ self.key = self.__class__.__name__
+ self.domainmap = domainmap
+ self.domainrules = domainrules
+ self.answerParameters = answerParameters
+
+ def getBridges(self, bridgeRequest, epoch):
+ return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
+
+ def cleanDatabase(self):
+ pass
+
+
+class DummyEmailDistributorWithState(DummyEmailDistributor):
+ """A mocked :class:`bridgedb.email.distributor.EmailDistributor` which raises
+ :exc:`bridgedb.email.distributor.TooSoonEmail` on the second email and
+ :exc:`bridgedb.email.distributor.IgnoreEmail` on the third.
+
+ Note that the state tracking is done in a really dumb way. For example, we
+ currently don't consider requests for help text or GnuPG keys to be a
+ "real" request, so in the real email distributor they won't trigger either
+ a TooSoonEmail or IgnoreEmail. Here we only track the total number of
+ *any* type of request per client.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(DummyEmailDistributorWithState, self).__init__()
+ self.alreadySeen = {}
+
+ def getBridges(self, bridgeRequest, epoch):
+ # Keep track of the number of times we've seen a client.
+ if not bridgeRequest.client in self.alreadySeen.keys():
+ self.alreadySeen[bridgeRequest.client] = 0
+ self.alreadySeen[bridgeRequest.client] += 1
+
+ if self.alreadySeen[bridgeRequest.client] <= 1:
+ return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
+ elif self.alreadySeen[bridgeRequest.client] == 2:
+ raise TooSoonEmail(
+ "Seen client '%s' %d times"
+ % (bridgeRequest.client, self.alreadySeen[bridgeRequest.client]),
+ bridgeRequest.client)
+ else:
+ raise IgnoreEmail(
+ "Seen client '%s' %d times"
+ % (bridgeRequest.client, self.alreadySeen[bridgeRequest.client]),
+ bridgeRequest.client)
diff --git a/test/https_helpers.py b/test/https_helpers.py
new file mode 100644
index 0000000..e2c94ba
--- /dev/null
+++ b/test/https_helpers.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+
+"""Helpers for testing the HTTPS Distributor and its servers."""
+
+
+import io
+
+from twisted.web.test import requesthelper
+
+from bridgedb.test import util
+from bridgedb.persistent import Conf
+
+
+SERVER_PUBLIC_FQDN = 'bridges.torproject.org'
+SERVER_PUBLIC_EXTERNAL_IP = '38.229.72.19'
+HTTPS_DIST = True
+HTTPS_BIND_IP = None
+HTTPS_PORT = None
+HTTPS_N_BRIDGES_PER_ANSWER = 3
+HTTPS_INCLUDE_FINGERPRINTS = True
+HTTPS_KEY_FILE = 'privkey.pem'
+HTTPS_CERT_FILE = 'cert'
+N_IP_CLUSTERS = 4
+HTTPS_ROTATION_PERIOD = "3 hours"
+HTTP_UNENCRYPTED_BIND_IP = None
+HTTP_UNENCRYPTED_PORT = None
+HTTP_USE_IP_FROM_FORWARDED_HEADER = False
+RECAPTCHA_ENABLED = False
+RECAPTCHA_PUB_KEY = ''
+RECAPTCHA_SEC_KEY = ''
+RECAPTCHA_REMOTEIP = ''
+GIMP_CAPTCHA_ENABLED = True
+GIMP_CAPTCHA_DIR = 'captchas'
+GIMP_CAPTCHA_HMAC_KEYFILE = 'captcha_hmac_key'
+GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key'
+
+TEST_CONFIG_FILE = io.StringIO(unicode("""\
+SERVER_PUBLIC_FQDN = %r
+SERVER_PUBLIC_EXTERNAL_IP = %r
+HTTPS_DIST = %r
+HTTPS_BIND_IP = %r
+HTTPS_PORT = %r
+HTTPS_N_BRIDGES_PER_ANSWER = %r
+HTTPS_INCLUDE_FINGERPRINTS = %r
+HTTPS_KEY_FILE = %r
+HTTPS_CERT_FILE = %r
+N_IP_CLUSTERS = %r
+HTTPS_ROTATION_PERIOD = %r
+HTTP_UNENCRYPTED_BIND_IP = %r
+HTTP_UNENCRYPTED_PORT = %r
+HTTP_USE_IP_FROM_FORWARDED_HEADER = %r
+RECAPTCHA_ENABLED = %r
+RECAPTCHA_PUB_KEY = %r
+RECAPTCHA_SEC_KEY = %r
+RECAPTCHA_REMOTEIP = %r
+GIMP_CAPTCHA_ENABLED = %r
+GIMP_CAPTCHA_DIR = %r
+GIMP_CAPTCHA_HMAC_KEYFILE = %r
+GIMP_CAPTCHA_RSA_KEYFILE = %r
+""" % (SERVER_PUBLIC_FQDN,
+ SERVER_PUBLIC_EXTERNAL_IP,
+ HTTPS_DIST,
+ HTTPS_BIND_IP,
+ HTTPS_PORT,
+ HTTPS_N_BRIDGES_PER_ANSWER,
+ HTTPS_INCLUDE_FINGERPRINTS,
+ HTTPS_KEY_FILE,
+ HTTPS_CERT_FILE,
+ N_IP_CLUSTERS,
+ HTTPS_ROTATION_PERIOD,
+ HTTP_UNENCRYPTED_BIND_IP,
+ HTTP_UNENCRYPTED_PORT,
+ HTTP_USE_IP_FROM_FORWARDED_HEADER,
+ RECAPTCHA_ENABLED,
+ RECAPTCHA_PUB_KEY,
+ RECAPTCHA_SEC_KEY,
+ RECAPTCHA_REMOTEIP,
+ GIMP_CAPTCHA_ENABLED,
+ GIMP_CAPTCHA_DIR,
+ GIMP_CAPTCHA_HMAC_KEYFILE,
+ GIMP_CAPTCHA_RSA_KEYFILE)))
+
+
+def _createConfig(configFile=TEST_CONFIG_FILE):
+ configuration = {}
+ TEST_CONFIG_FILE.seek(0)
+ compiled = compile(configFile.read(), '<string>', 'exec')
+ exec compiled in configuration
+ config = Conf(**configuration)
+ return config
+
+
+class DummyHTTPSDistributor(object):
+ """A mocked :class:`bridgedb.https.distributor.HTTPSDistributor` which is
+ used to test :class:`bridgedb.https.server.BridgesResource`.
+ """
+ _bridge_class = util.DummyBridge
+ _bridgesPerResponseMin = 3
+
+ def getBridges(self, bridgeRequest=None, epoch=None):
+ """Needed because it's called in
+ :meth:`BridgesResource.getBridgeRequestAnswer`."""
+ return [self._bridge_class() for _ in range(self._bridgesPerResponseMin)]
+
+
+class DummyRequest(requesthelper.DummyRequest):
+ """Wrapper for :api:`twisted.test.requesthelper.DummyRequest` to add
+ redirect support.
+ """
+ def __init__(self, *args, **kwargs):
+ requesthelper.DummyRequest.__init__(self, *args, **kwargs)
+ self.redirect = self._redirect(self)
+
+ def URLPath(self):
+ """Fake the missing Request.URLPath too."""
+ return self.uri
+
+ def _redirect(self, request):
+ """Stub method to add a redirect() method to DummyResponse."""
+ newRequest = type(request)
+ newRequest.uri = request.uri
+ return newRequest
diff --git a/test/legacy_Tests.py b/test/legacy_Tests.py
new file mode 100644
index 0000000..40f7ae4
--- /dev/null
+++ b/test/legacy_Tests.py
@@ -0,0 +1,332 @@
+# BridgeDB by Nick Mathewson.
+# Copyright (c) 2007-2009, The Tor Project, Inc.
+# See LICENSE for licensing information
+
+"""These are legacy integration and unittests which historically lived at
+``lib/bridgedb/Tests.py``. They have been moved here to keep the test code
+separate from the production codebase.
+"""
+
+from __future__ import print_function
+
+import os
+import random
+import tempfile
+import unittest
+import warnings
+import time
+from datetime import datetime
+
+import bridgedb.Bridges
+import bridgedb.Main
+import bridgedb.schedule
+import bridgedb.Storage
+import re
+import ipaddr
+
+from bridgedb.Stability import BridgeHistory
+
+from bridgedb.email.distributor import EmailDistributor
+from bridgedb.email.distributor import IgnoreEmail
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.parse import addr
+from bridgedb.test.util import bracketIPv6
+from bridgedb.test.util import randomIP
+from bridgedb.test.util import randomIPv4
+from bridgedb.test.util import randomIPv6
+from bridgedb.test.util import randomIPString
+from bridgedb.test.util import randomIPv4String
+from bridgedb.test.util import randomIPv6String
+from bridgedb.test.util import randomPort
+from bridgedb.test.util import randomValidIPv6
+
+from math import log
+
+warnings.filterwarnings('ignore', '.*tmpnam.*')
+
+
+def randomPortSpec():
+ """
+ returns a random list of ports
+ """
+ ports = [randomPort() for i in range(0,24)]
+ ports.sort(reverse=True)
+
+ portspec = ",".join(["%d" % random.choice(ports) for i in range(0,16)])
+ return portspec
+
+def fakeBridge(orport=8080, running=True, stable=True, or_addresses=False,
+ transports=False):
+ ip = randomIPv4()
+ nn = "bridge-%s" % int(ip)
+ fp = "".join([random.choice("0123456789ABCDEF") for _ in xrange(40)])
+ b = bridgedb.Bridges.Bridge(nn,ip,orport,fingerprint=fp)
+ b.setStatus(running, stable)
+
+ oraddrs = []
+ if or_addresses:
+ for i in xrange(8):
+ b.orAddresses.append((randomValidIPv6(), randomPort(), 6))
+
+ if transports:
+ for i in xrange(0,8):
+ b.transports.append(bridgedb.Bridges.PluggableTransport(b,
+ random.choice(["obfs", "obfs2", "pt1"]),
+ randomIP(), randomPort()))
+ return b
+
+def fakeBridge6(orport=8080, running=True, stable=True, or_addresses=False,
+ transports=False):
+ ip = randomIPv6()
+ nn = "bridge-%s" % int(ip)
+ fp = "".join([random.choice("0123456789ABCDEF") for _ in xrange(40)])
+ b = bridgedb.Bridges.Bridge(nn,ip,orport,fingerprint=fp)
+ b.setStatus(running, stable)
+
+ oraddrs = []
+ if or_addresses:
+ for i in xrange(8):
+ b.orAddresses.append((randomValidIPv6(), randomPort(), 6))
+
+ if transports:
+ for i in xrange(0,8):
+ b.transports.append(bridgedb.Bridges.PluggableTransport(b,
+ random.choice(["obfs", "obfs2", "pt1"]),
+ randomIP(), randomPort()))
+ return b
+
+
+
+class SQLStorageTests(unittest.TestCase):
+ def setUp(self):
+ self.fd, self.fname = tempfile.mkstemp()
+ self.db = bridgedb.Storage.Database(self.fname)
+ self.cur = self.db._conn.cursor()
+
+ def tearDown(self):
+ self.db.close()
+ os.close(self.fd)
+ os.unlink(self.fname)
+
+ def assertCloseTo(self, a, b, delta=60):
+ self.assertTrue(abs(a-b) <= delta)
+
+ def testBridgeStorage(self):
+ db = self.db
+ B = bridgedb.Bridges.Bridge
+ t = time.time()
+ cur = self.cur
+
+ k1 = "AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBB"
+ k2 = "ABABABABABABABABABABABABABABABABABABABAB"
+ k3 = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
+ b1 = B("serv1", "1.2.3.4", 999, fingerprint=k1)
+ b1_v2 = B("serv1", "1.2.3.5", 9099, fingerprint=k1)
+ b2 = B("serv2", "2.3.4.5", 9990, fingerprint=k2)
+ b3 = B("serv3", "2.3.4.6", 9008, fingerprint=k3)
+ validRings = ["ring1", "ring2", "ring3"]
+
+ r = db.insertBridgeAndGetRing(b1, "ring1", t, validRings)
+ self.assertEquals(r, "ring1")
+ r = db.insertBridgeAndGetRing(b1, "ring10", t+500, validRings)
+ self.assertEquals(r, "ring1")
+
+ cur.execute("SELECT distributor, address, or_port, first_seen, "
+ "last_seen FROM Bridges WHERE hex_key = ?", (k1,))
+ v = cur.fetchone()
+ self.assertEquals(v,
+ ("ring1", "1.2.3.4", 999,
+ bridgedb.Storage.timeToStr(t),
+ bridgedb.Storage.timeToStr(t+500)))
+
+ r = db.insertBridgeAndGetRing(b1_v2, "ring99", t+800, validRings)
+ self.assertEquals(r, "ring1")
+ cur.execute("SELECT distributor, address, or_port, first_seen, "
+ "last_seen FROM Bridges WHERE hex_key = ?", (k1,))
+ v = cur.fetchone()
+ self.assertEquals(v,
+ ("ring1", "1.2.3.5", 9099,
+ bridgedb.Storage.timeToStr(t),
+ bridgedb.Storage.timeToStr(t+800)))
+
+ db.insertBridgeAndGetRing(b2, "ring2", t, validRings)
+ db.insertBridgeAndGetRing(b3, "ring3", t, validRings)
+
+ cur.execute("SELECT COUNT(distributor) FROM Bridges")
+ v = cur.fetchone()
+ self.assertEquals(v, (3,))
+
+ r = db.getEmailTime("abc at example.com")
+ self.assertEquals(r, None)
+ db.setEmailTime("abc at example.com", t)
+ db.setEmailTime("def at example.com", t+1000)
+ r = db.getEmailTime("abc at example.com")
+ self.assertCloseTo(r, t)
+ r = db.getEmailTime("def at example.com")
+ self.assertCloseTo(r, t+1000)
+ r = db.getEmailTime("ghi at example.com")
+ self.assertEquals(r, None)
+
+ db.cleanEmailedBridges(t+200)
+ db.setEmailTime("def at example.com", t+5000)
+ r = db.getEmailTime("abc at example.com")
+ self.assertEquals(r, None)
+ r = db.getEmailTime("def at example.com")
+ self.assertCloseTo(r, t+5000)
+ cur.execute("SELECT * FROM EmailedBridges")
+ self.assertEquals(len(cur.fetchall()), 1)
+
+ self.assertEquals(db.getWarnedEmail("def at example.com"), False)
+ db.setWarnedEmail("def at example.com")
+ self.assertEquals(db.getWarnedEmail("def at example.com"), True)
+ db.setWarnedEmail("def at example.com", False)
+ self.assertEquals(db.getWarnedEmail("def at example.com"), False)
+
+ db.setWarnedEmail("def at example.com")
+ self.assertEquals(db.getWarnedEmail("def at example.com"), True)
+ db.cleanWarnedEmails(t+200)
+ self.assertEquals(db.getWarnedEmail("def at example.com"), False)
+
+
+class BridgeStabilityTests(unittest.TestCase):
+ def setUp(self):
+ self.fd, self.fname = tempfile.mkstemp()
+ self.db = bridgedb.Storage.Database(self.fname)
+ bridgedb.Storage.setDB(self.db)
+ self.cur = self.db._conn.cursor()
+
+ def tearDown(self):
+ self.db.close()
+ os.close(self.fd)
+ os.unlink(self.fname)
+
+ def testAddOrUpdateSingleBridgeHistory(self):
+ db = self.db
+ b = fakeBridge()
+ timestamp = time.time()
+ bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b, timestamp)
+ assert isinstance(bhe, BridgeHistory)
+ assert isinstance(db.getBridgeHistory(b.fingerprint), BridgeHistory)
+ assert len([y for y in db.getAllBridgeHistory()]) == 1
+
+ def testDeletingSingleBridgeHistory(self):
+ db = self.db
+ b = fakeBridge()
+ timestamp = time.time()
+ bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b, timestamp)
+ assert isinstance(bhe, BridgeHistory)
+ assert isinstance(db.getBridgeHistory(b.fingerprint), BridgeHistory)
+ db.delBridgeHistory(b.fingerprint)
+ assert db.getBridgeHistory(b.fingerprint) is None
+ assert len([y for y in db.getAllBridgeHistory()]) == 0
+
+ def testTOSA(self):
+ db = self.db
+ b = random.choice([fakeBridge,fakeBridge6])()
+ def timestampSeries(x):
+ for i in xrange(61):
+ yield (i+1)*60*30 + x # 30 minute intervals
+ now = time.time()
+ time_on_address = long(60*30*60) # 30 hours
+ downtime = 60*60*random.randint(0,4) # random hours of downtime
+
+ for t in timestampSeries(now):
+ bridgedb.Stability.addOrUpdateBridgeHistory(b,t)
+ assert db.getBridgeHistory(b.fingerprint).tosa == time_on_address
+
+ b.orport += 1
+
+ for t in timestampSeries(now + time_on_address + downtime):
+ bhe = bridgedb.Stability.addOrUpdateBridgeHistory(b,t)
+ assert db.getBridgeHistory(b.fingerprint).tosa == time_on_address + downtime
+
+ def testLastSeenWithDifferentAddressAndPort(self):
+ db = self.db
+ for i in xrange(10):
+ num_desc = 30
+ time_start = time.time()
+ ts = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
+ b = random.choice([fakeBridge(), fakeBridge6()])
+ [ bridgedb.Stability.addOrUpdateBridgeHistory(b, t) for t in ts ]
+
+ # change the port
+ b.orport = b.orport+1
+ last_seen = ts[-1]
+ ts = [ 60*30*(i+1) + last_seen for i in xrange(num_desc) ]
+ [ bridgedb.Stability.addOrUpdateBridgeHistory(b, t) for t in ts ]
+ b = db.getBridgeHistory(b.fingerprint)
+ assert b.tosa == ts[-1] - last_seen
+ assert (long(last_seen*1000) == b.lastSeenWithDifferentAddressAndPort)
+ assert (long(ts[-1]*1000) == b.lastSeenWithThisAddressAndPort)
+
+ def testFamiliar(self):
+ # create some bridges
+ # XXX: slow
+ num_bridges = 10
+ num_desc = 4*48 # 30m intervals, 48 per day
+ time_start = time.time()
+ bridges = [ fakeBridge() for x in xrange(num_bridges) ]
+ t = time.time()
+ ts = [ (i+1)*60*30+t for i in xrange(num_bridges) ]
+ for b in bridges:
+ time_series = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
+ [ bridgedb.Stability.addOrUpdateBridgeHistory(b, i) for i in time_series ]
+ assert None not in bridges
+ # +1 to avoid rounding errors
+ assert bridges[-(num_bridges/8 + 1)].familiar == True
+
+ def testDiscountAndPruneBridgeHistory(self):
+ """ Test pruning of old Bridge History """
+ if os.environ.get('TRAVIS'):
+ self.skipTest("Hangs on Travis-CI.")
+
+ db = self.db
+
+ # make a bunch of bridges
+ num_bridges = 20
+ time_start = time.time()
+ bridges = [random.choice([fakeBridge, fakeBridge6])()
+ for i in xrange(num_bridges)]
+
+ # run some of the bridges for the full time series
+ running = bridges[:num_bridges/2]
+ # and some that are not
+ expired = bridges[num_bridges/2:]
+
+ for b in running: assert b not in expired
+
+ # Solving:
+ # 1 discount event per 12 hours, 24 descriptors 30m apart
+ num_successful = random.randint(2,60)
+ # figure out how many intervals it will take for weightedUptime to
+ # decay to < 1
+ num_desc = int(30*log(1/float(num_successful*30*60))/(-0.05))
+ timeseries = [ 60*30*(i+1) + time_start for i in xrange(num_desc) ]
+
+ for i in timeseries:
+ for b in running:
+ bridgedb.Stability.addOrUpdateBridgeHistory(b, i)
+
+ if num_successful > 0:
+ for b in expired:
+ bridgedb.Stability.addOrUpdateBridgeHistory(b, i)
+ num_successful -= 1
+
+ # now we expect to see the bridge has been removed from history
+ for bridge in expired:
+ b = db.getBridgeHistory(bridge.fingerprint)
+ assert b is None
+ # and make sure none of the others have
+ for bridge in running:
+ b = db.getBridgeHistory(bridge.fingerprint)
+ assert b is not None
+
+def testSuite():
+ suite = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ for klass in [SQLStorageTests, BridgeStabilityTests]:
+ suite.addTest(loader.loadTestsFromTestCase(klass))
+ return suite
+
+def main():
+ unittest.TextTestRunner(verbosity=1).run(testSuite())
diff --git a/test/test_Bucket.py b/test/test_Bucket.py
new file mode 100644
index 0000000..a6e335f
--- /dev/null
+++ b/test/test_Bucket.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.Bucket` module.
+
+These tests are meant to ensure that the :mod:`bridgedb.Bucket` module is
+functioning as expected.
+"""
+
+from __future__ import print_function
+
+from io import StringIO
+
+import sure
+from sure import this
+from sure import the
+from sure import expect
+
+from bridgedb import Bucket
+
+from twisted.trial import unittest
+
+
+class BucketDataTest(unittest.TestCase):
+ """Tests for :class:`bridgedb.Bucket.BucketData`."""
+
+ def test_alloc_some_of_the_bridges(self):
+ """Set the needed number of bridges"""
+ needed = 10
+ distname = "test-distributor"
+ bucket = Bucket.BucketData(distname, needed)
+ this(bucket.name).should.be.equal(distname)
+ this(bucket.needed).should.be.equal(needed)
+
+ def test_alloc_all_the_bridges(self):
+ """Set the needed number of bridges to the default"""
+ needed = '*'
+ distname = "test-distributor"
+ bucket = Bucket.BucketData(distname, needed)
+ this(bucket.name).should.be.equal(distname)
+ this(bucket.needed).should.be.equal(Bucket.BUCKET_MAX_BRIDGES)
+
+
+class BucketManagerTest(unittest.TestCase):
+ """Tests for :class:`bridgedb.Bucket.BucketManager`."""
+ TEST_CONFIG_FILE = StringIO(unicode("""\
+ FILE_BUCKETS = { 'test1': 7, 'test2': 11 }
+ COLLECT_TIMESTAMPS = False
+ COUNTRY_BLOCK_FILE = []"""))
+
+ def setUp(self):
+ configuration = {}
+ TEST_CONFIG_FILE.seek(0)
+ compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
+ exec compiled in configuration
+ self.config = persistent.Conf(**configuration)
+ self.state = persistent.State(**config.__dict__)
+ self.bucket = Bucket.BucketManager(self.config)
diff --git a/test/test_Main.py b/test/test_Main.py
new file mode 100644
index 0000000..e53256f
--- /dev/null
+++ b/test/test_Main.py
@@ -0,0 +1,437 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.Main`."""
+
+from __future__ import print_function
+
+import base64
+import logging
+import os
+import random
+import shutil
+import sys
+
+from datetime import datetime
+from time import sleep
+
+from twisted.internet.threads import deferToThread
+from twisted.trial import unittest
+
+from bridgedb import Main
+from bridgedb.parse.options import parseOptions
+
+
+logging.getLogger().disabled = True
+
+
+HERE = os.getcwd()
+TOPDIR = HERE.rstrip('_trial_temp')
+CI_RUNDIR = os.path.join(TOPDIR, 'run')
+
+# A networkstatus descriptor with two invalid ORAddress (127.0.0.1 and ::1)
+# and an invalid port number (70000).
+NETWORKSTATUS_MALFORMED = '''\
+r OracleLunacy LBXW03FIKvo9aXCEYbDdq1BbNtM E0yN8ofiBpg6JHW0iPX5gJ1gKFI 2014-09-05 21:39:24 127.0.0.1 70000 0
+a [::1]:70000
+s Fast Guard Running Stable Valid
+w Bandwidth=2094050
+p reject 1-65535
+'''
+
+def mockUpdateBridgeHistory(bridges, timestamps):
+ """A mocked version of :func:`bridgedb.Stability.updateBridgeHistory`
+ which doesn't access the database (so that we can test functions which
+ call it, like :func:`bridgedb.Main.load`).
+ """
+ for fingerprint, stamps in timestamps.items()[:]:
+ for timestamp in stamps:
+ print("Pretending to update Bridge %s with timestamp %s..." %
+ (fingerprint, timestamp))
+
+
+class MockHashring(object):
+ def __init__(self):
+ self._bridges = {}
+ def __len__(self):
+ return len(self._bridges.keys())
+ def insert(self, bridge):
+ self._bridges[bridge.fingerprint] = bridge
+ def clear(self):
+ pass
+ def dumpAssignments(self):
+ pass
+
+
+class MainTests(unittest.TestCase):
+ """Integration tests for :func:`bridgedb.Main.load`."""
+
+ def _appendToFile(self, file, data):
+ """Append **data** to **file**."""
+ fh = open(file, 'a')
+ fh.write(data)
+ fh.flush()
+ fh.close()
+
+ def _copyDescFilesHere(self, files):
+ """Copy all the **files** to the _trial_tmp/ directory.
+
+ :param list files: A list of strings representing the paths to
+ descriptor files. This should probably be taken from a
+ ``bridgedb.persistent.Conf`` object which has parsed the
+ ``bridgedb.conf`` file in the top-level directory of this repo.
+ :rtype: list
+ :returns: A list of the new paths (in the ``_trial_tmp`` directory) to
+ the copied descriptor files. This should be used to update the
+ ``bridgedb.persistent.Conf`` object.
+ """
+ updatedPaths = []
+
+ for f in files:
+ base = os.path.basename(f)
+ src = os.path.join(CI_RUNDIR, base)
+ if os.path.isfile(src):
+ dst = os.path.join(HERE, base)
+ shutil.copy(src, dst)
+ updatedPaths.append(dst)
+ else:
+ self.skip = True
+ raise unittest.SkipTest(
+ "Can't find mock descriptor files in %s directory" %
+ CI_RUNDIR)
+
+ return updatedPaths
+
+ def _cbAssertFingerprints(self, d):
+ """Assert that there are some bridges in the hashring."""
+ self.assertGreater(len(self.hashring), 0)
+ return d
+
+ def _cbCallUpdateBridgeHistory(self, d, hashring):
+ """Fake some timestamps for the bridges in the hashring, and then call
+ Main.updateBridgeHistory().
+ """
+ def timestamp():
+ return datetime.fromtimestamp(random.randint(1324285117, 1524285117))
+
+ bridges = hashring._bridges
+ timestamps = {}
+
+ for fingerprint, _ in bridges.items():
+ timestamps[fingerprint] = [timestamp(), timestamp(), timestamp()]
+
+ return Main.updateBridgeHistory(bridges, timestamps)
+
+ def _eb_Failure(self, failure):
+ """If something produces a twisted.python.failure.Failure, fail the
+ test with it.
+ """
+ self.fail(failure)
+
+ def _writeConfig(self, config):
+ """Write a config into the current working directory.
+
+ :param str config: A big long multiline string that looks like the
+ bridgedb.conf file.
+ :rtype: str
+ :returns: The pathname of the file that the **config** was written to.
+ """
+ configFile = os.path.join(os.getcwd(), 'bridgedb.conf')
+ fh = open(configFile, 'w')
+ fh.write(config)
+ fh.flush()
+ fh.close()
+ return configFile
+
+ def setUp(self):
+ """Find the bridgedb.conf file in the top-level directory of this repo,
+ copy it and the descriptor files it references to the current working
+ directory, produce a state object from the loaded bridgedb.conf file,
+ and make an HMAC key.
+ """
+ # Get the bridgedb.conf file in the top-level directory of this repo:
+ self.configFile = os.path.join(TOPDIR, 'bridgedb.conf')
+ self.config = Main.loadConfig(self.configFile)
+
+ # Copy the referenced descriptor files from bridgedb/run/ to CWD:
+ self.config.STATUS_FILE = self._copyDescFilesHere([self.config.STATUS_FILE])[0]
+ self.config.BRIDGE_FILES = self._copyDescFilesHere(self.config.BRIDGE_FILES)
+ self.config.EXTRA_INFO_FILES = self._copyDescFilesHere(self.config.EXTRA_INFO_FILES)
+
+ # Initialise the state
+ self.state = Main.persistent.State(**self.config.__dict__)
+ self.key = base64.b64decode('TvPS1y36BFguBmSOvhChgtXB2Lt+BOw0mGfz9SZe12Y=')
+
+ # Create a pseudo hashring
+ self.hashring = MockHashring()
+
+ # Functions which some tests mock, which we'll need to re-replace
+ # later in tearDown():
+ self._orig_updateBridgeHistory = Main.updateBridgeHistory
+ self._orig_sys_argv = sys.argv
+
+ def tearDown(self):
+ """Replace the mocked mockUpdateBridgeHistory() function with the
+ real function, Stability.updateBridgeHistory().
+ """
+ Main.updateBridgeHistory = self._orig_updateBridgeHistory
+ sys.argv = self._orig_sys_argv
+
+ def test_Main_updateBridgeHistory(self):
+ """Main.updateBridgeHistory should update some timestamps for some
+ bridges.
+ """
+ # Mock the updateBridgeHistory() function so that we don't try to
+ # access the database:
+ Main.updateBridgeHistory = mockUpdateBridgeHistory
+
+ # Get the bridges into the mocked hashring
+ d = deferToThread(Main.load, self.state, self.hashring)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ d.addCallback(self._cbCallUpdateBridgeHistory, self.hashring)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load(self):
+ """Main.load() should run without error."""
+ d = deferToThread(Main.load, self.state, self.hashring)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load_no_state(self):
+ """Main.load() should raise SystemExit without a state object."""
+ self.assertRaises(SystemExit, Main.load, None, self.hashring)
+
+ def test_Main_load_clear(self):
+ """When called with clear=True, load() should run and clear the
+ hashrings.
+ """
+ d = deferToThread(Main.load, self.state, self.hashring, clear=True)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load_collect_timestamps(self):
+ """When COLLECT_TIMESTAMPS=True, Main.load() should call
+ Main.updateBridgeHistory().
+ """
+ # Mock the addOrUpdateBridgeHistory() function so that we don't try to
+ # access the database:
+ Main.updateBridgeHistory = mockUpdateBridgeHistory
+ state = self.state
+ state.COLLECT_TIMESTAMPS = True
+
+ # The reactor is deferring this to a thread, so the test execution
+ # here isn't actually covering the Storage.updateBridgeHistory()
+ # function:
+ Main.load(state, self.hashring)
+
+ def test_Main_load_malformed_networkstatus(self):
+ """When called with a networkstatus file with an invalid descriptor,
+ Main.load() should raise a ValueError.
+ """
+ self._appendToFile(self.state.STATUS_FILE, NETWORKSTATUS_MALFORMED)
+ self.assertRaises(ValueError, Main.load, self.state, self.hashring)
+
+ def test_Main_reloadFn(self):
+ """Main._reloadFn() should return True."""
+ self.assertTrue(Main._reloadFn())
+
+ def test_Main_handleSIGHUP(self):
+ """Main._handleSIGHUP() should return True."""
+ raise unittest.SkipTest("_handleSIGHUP touches the reactor.")
+
+ self.assertTrue(Main._handleSIGHUP())
+
+ def test_Main_createBridgeRings(self):
+ """Main.createBridgeRings() should add three hashrings to the
+ hashring.
+ """
+ proxyList = None
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(self.config,
+ proxyList,
+ self.key)
+ # Should have an HTTPSDistributor ring, an EmailDistributor ring,
+ # and an UnallocatedHolder ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 3)
+
+ def test_Main_createBridgeRings_with_proxyList(self):
+ """Main.createBridgeRings() should add three hashrings to the
+ hashring and add the proxyList to the IPBasedDistibutor.
+ """
+ exitRelays = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
+ proxyList = Main.proxy.ProxySet()
+ proxyList.addExitRelays(exitRelays)
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(self.config,
+ proxyList,
+ self.key)
+ # Should have an HTTPSDistributor ring, an EmailDistributor ring,
+ # and an UnallocatedHolder ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 3)
+ self.assertGreater(len(httpsDist.proxies), 0)
+ self.assertItemsEqual(exitRelays, httpsDist.proxies)
+
+ def test_Main_createBridgeRings_no_https_dist(self):
+ """When HTTPS_DIST=False, Main.createBridgeRings() should add only
+ two hashrings to the hashring.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.HTTPS_DIST = False
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an EmailDistributor ring, and an UnallocatedHolder ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 2)
+ self.assertNotIn('https', hashring.rings)
+ self.assertNotIn(httpsDist, hashring.ringsByName.values())
+
+ def test_Main_createBridgeRings_no_email_dist(self):
+ """When EMAIL_DIST=False, Main.createBridgeRings() should add only
+ two hashrings to the hashring.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.EMAIL_DIST = False
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an HTTPSDistributor ring, and an UnallocatedHolder ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 2)
+ self.assertNotIn('email', hashring.rings)
+ self.assertNotIn(emailDist, hashring.ringsByName.values())
+
+ def test_Main_createBridgeRings_no_reserved_share(self):
+ """When RESERVED_SHARE=0, Main.createBridgeRings() should add only
+ two hashrings to the hashring.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.RESERVED_SHARE = 0
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an HTTPSDistributor ring, and an EmailDistributor ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 2)
+ self.assertNotIn('unallocated', hashring.rings)
+
+ def test_Main_createBridgeRings_two_file_buckets(self):
+ """When FILE_BUCKETS has two filenames in it, Main.createBridgeRings()
+ should add three hashrings to the hashring, then add two
+ "pseudo-rings".
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.FILE_BUCKETS = {
+ 'bridges-for-support-desk': 10,
+ 'bridges-for-ooni-tests': 10,
+ }
+ (hashring, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an HTTPSDistributor ring, an EmailDistributor, and an
+ # UnallocatedHolder ring:
+ self.assertEqual(len(hashring.ringsByName.keys()), 3)
+
+ # Should have two pseudoRings:
+ self.assertEqual(len(hashring.pseudoRings), 2)
+ self.assertIn('pseudo_bridges-for-support-desk', hashring.pseudoRings)
+ self.assertIn('pseudo_bridges-for-ooni-tests', hashring.pseudoRings)
+
+ def test_Main_run(self):
+ """Main.run() should run and then finally raise SystemExit."""
+ config = """
+BRIDGE_FILES = ["../run/bridge-descriptors"]
+EXTRA_INFO_FILES = ["../run/cached-extrainfo", "../run/cached-extrainfo.new"]
+STATUS_FILE = "../run/networkstatus-bridges"
+HTTPS_CERT_FILE="cert"
+HTTPS_KEY_FILE="privkey.pem"
+LOGFILE = "bridgedb.log"
+PIDFILE = "bridgedb.pid"
+DB_FILE = "bridgedist.db"
+DB_LOG_FILE = "bridgedist.log"
+MASTER_KEY_FILE = "secret_key"
+ASSIGNMENTS_FILE = "assignments.log"
+LOGLEVEL = "DEBUG"
+SAFELOGGING = True
+LOGFILE_COUNT = 5
+LOGFILE_ROTATE_SIZE = 10000000
+LOG_THREADS = False
+LOG_TRACE = True
+LOG_TIME_FORMAT = "%H:%M:%S"
+COLLECT_TIMESTAMPS = False
+NO_DISTRIBUTION_COUNTRIES = ['IR', 'SY']
+PROXY_LIST_FILES = []
+N_IP_CLUSTERS = 3
+FORCE_PORTS = [(443, 1)]
+FORCE_FLAGS = [("Stable", 1)]
+BRIDGE_PURPOSE = "bridge"
+TASKS = {'GET_TOR_EXIT_LIST': 3 * 60 * 60,}
+SERVER_PUBLIC_FQDN = 'bridges.torproject.org'
+SERVER_PUBLIC_EXTERNAL_IP = '38.229.72.19'
+HTTPS_DIST = True
+HTTPS_BIND_IP = None
+HTTPS_PORT = None
+HTTPS_N_BRIDGES_PER_ANSWER = 3
+HTTPS_INCLUDE_FINGERPRINTS = True
+HTTPS_USE_IP_FROM_FORWARDED_HEADER = False
+HTTP_UNENCRYPTED_BIND_IP = "127.0.0.1"
+HTTP_UNENCRYPTED_PORT = 55555
+HTTP_USE_IP_FROM_FORWARDED_HEADER = False
+RECAPTCHA_ENABLED = False
+RECAPTCHA_PUB_KEY = ''
+RECAPTCHA_SEC_KEY = ''
+RECAPTCHA_REMOTEIP = ''
+GIMP_CAPTCHA_ENABLED = False
+GIMP_CAPTCHA_DIR = 'captchas'
+GIMP_CAPTCHA_HMAC_KEYFILE = 'captcha_hmac_key'
+GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key'
+EMAIL_DIST = True
+EMAIL_FROM_ADDR = "bridges at torproject.org"
+EMAIL_SMTP_FROM_ADDR = "bridges at torproject.org"
+EMAIL_SMTP_HOST = "127.0.0.1"
+EMAIL_SMTP_PORT = 55556
+EMAIL_USERNAME = "bridges"
+EMAIL_DOMAINS = ["somewhere.com", "somewhereelse.net"]
+EMAIL_DOMAIN_MAP = {
+ "mail.somewhere.com": "somewhere.com",
+ "mail.somewhereelse.net": "somewhereelse.net",
+}
+EMAIL_DOMAIN_RULES = {
+ 'somewhere.com': ["ignore_dots", "dkim"],
+ 'somewhereelse.net': ["dkim"],
+}
+EMAIL_WHITELIST = {}
+EMAIL_BLACKLIST = []
+EMAIL_FUZZY_MATCH = 4
+EMAIL_RESTRICT_IPS = []
+EMAIL_BIND_IP = "127.0.0.1"
+EMAIL_PORT = 55557
+EMAIL_N_BRIDGES_PER_ANSWER = 3
+EMAIL_INCLUDE_FINGERPRINTS = True
+EMAIL_GPG_SIGNING_ENABLED = False
+EMAIL_GPG_HOMEDIR = '../.gnupg'
+EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = '0017098C5DF4197E3C884DCFF1B240D43F148C21'
+EMAiL_GPG_PASSPHRASE = None
+EMAIL_GPG_PASSPHRASE_FILE = None
+HTTPS_SHARE = 10
+EMAIL_SHARE = 5
+RESERVED_SHARE = 2
+FILE_BUCKETS = {}"""
+ configFile = self._writeConfig(config)
+
+ # Fake some options:
+ sys.argv = ['bridgedb', '-r', os.getcwd(), '-c', configFile]
+ options = parseOptions()
+
+ self.assertRaises(SystemExit, Main.run, options, reactor=None)
diff --git a/test/test_Storage.py b/test/test_Storage.py
new file mode 100644
index 0000000..8b0affd
--- /dev/null
+++ b/test/test_Storage.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+"""Unittests for the :mod:`bridgedb.Storage` module."""
+
+from twisted.python import log
+from twisted.trial import unittest
+import bridgedb.Storage as Storage
+from twisted.internet import reactor
+from twisted.internet.threads import deferToThread
+import os
+import threading
+from time import sleep
+
+class DatabaseTest(unittest.TestCase):
+ def setUp(self):
+ self.dbfname = 'test-bridgedb.sqlite'
+ Storage.setDBFilename(self.dbfname)
+
+ def tearDown(self):
+ if os.path.isfile(self.dbfname):
+ os.unlink(self.dbfname)
+ Storage.clearGlobalDB()
+
+ def _runAndDie(self, timeout, func):
+ with func():
+ sleep(timeout)
+
+ def _cb_assertTrue(self, result):
+ self.assertTrue(result)
+
+ def _cb_assertFalse(self, result):
+ self.assertFalse(result)
+
+ def _eb_Failure(self, failure):
+ self.fail(failure)
+
+ def test_getDB_FalseWhenLocked(self):
+ Storage._LOCK = threading.Lock()
+ Storage._LOCK.acquire()
+ self.assertFalse(Storage._LOCK.acquire(False))
+
+ def test_getDB_AcquireLock(self):
+ Storage.initializeDBLock()
+ with Storage.getDB() as db:
+ self.assertIsInstance(db, Storage.Database)
+ self.assertTrue(Storage.dbIsLocked())
+ self.assertEqual(db, Storage._OPENED_DB)
+
+ def test_getDB_ConcurrencyLock(self):
+ timeout = 1
+ d1 = deferToThread(self._runAndDie, timeout, Storage.getDB)
+ d1.addCallback(self._cb_assertFalse)
+ d1.addErrback(self._eb_Failure)
+ d2 = deferToThread(Storage.getDB, False)
+ d2.addCallback(self._cb_assertFalse)
+ d2.addErrback(self._eb_Failure)
+ d2.addCallback(self._cb_assertTrue, Storage.getDB(False))
diff --git a/test/test_Tests.py b/test/test_Tests.py
new file mode 100644
index 0000000..84c3f8b
--- /dev/null
+++ b/test/test_Tests.py
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Class wrappers to adapt BridgeDB old unittests in :mod:`bridgedb.Tests`
+(now kept in :mod:`bridgedb.test.legacy_Tests`) to be compatible with the
+newer :api:`twisted.trial` unittests in this directory.
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import binascii
+import doctest
+import glob
+import logging
+import os
+import warnings
+
+from twisted.python import monkey
+from twisted.trial import unittest
+
+from bridgedb.test import legacy_Tests as Tests
+from bridgedb.test import deprecated
+
+
+warnings.filterwarnings('ignore', module="bridgedb\.test\.legacy_Tests")
+pyunit = __import__('unittest')
+
+
+def generateTrialAdaptedDoctestsSuite():
+ """Dynamically generates a :class:`unittest.TestSuite` all containing
+ discovered doctests within the installed ``bridgedb`` package.
+ """
+ bridgedb = __import__('bridgedb')
+ fakedGlobals = globals().update({'bridgedb': bridgedb})
+ modulePath = bridgedb.__path__[0]
+
+ #: The package directories to search for source code with doctests.
+ packagePaths = [modulePath,
+ os.path.join(modulePath, 'parse'),
+ os.path.join(modulePath, 'test')]
+ #: The source code files which will be searched for doctests.
+ files = []
+ #: The cls.testSuites which the test methods will be generated from.
+ testSuites = []
+
+ packages = [os.path.join(pkg, '*.py') for pkg in packagePaths]
+ [files.extend(glob.glob(pkg)) for pkg in packages]
+
+ for filename in files:
+ testSuites.append(
+ doctest.DocFileSuite(filename,
+ module_relative=False,
+ globs=fakedGlobals))
+ return testSuites
+
+def monkeypatchTests():
+ """Monkeypatch the old unittests, replacing new, refactored code with their
+ original equivalents from :mod:`bridgedb.test.deprecated`.
+
+ The first patch replaces the newer parsing function,
+ :func:`~bridgedb.parse.networkstatus.parseALine`, with the older,
+ :func:`deprecated one <bridgedb.test.deprecated.parseORAddressLine>` (the
+ old function was previously located at
+ ``bridgedb.Bridges.parseORAddressLine``).
+
+ The second patch replaces the new :class:`~bridgedb.parse.addr.PortList`,
+ with the :class:`older one <bridgedb.test.deprecated.PortList>` (which
+ was previously located at ``bridgedb.Bridges.PortList``).
+
+ The third, forth, and fifth monkeypatches add some module-level attributes
+ back into :mod:`bridgedb.Bridges`.
+
+ :rtype: :api:`~twisted.python.monkey.MonkeyPatcher`
+ :returns: A :api:`~twisted.python.monkey.MonkeyPatcher`, preloaded with
+ patches from :mod:`bridgedb.test.deprecated`.
+ """
+ patcher = monkey.MonkeyPatcher()
+ patcher.addPatch(Tests.bridgedb.Bridges, 'PluggableTransport',
+ deprecated.PluggableTransport)
+ patcher.addPatch(Tests.bridgedb.Bridges, 'Bridge',
+ deprecated.Bridge)
+ return patcher
+
+
+class DynamicTestCaseMeta(type):
+ """You know how scary the seemingly-arbitrary constants in elliptic curve
+ cryptography seem? Well, I am over nine thousand times more scary. Dynamic
+ warez⦠beware! Be afraid; be very afraid.
+
+ :ivar testResult: An :class:`unittest.TestResult` adapted with
+ :api:`twisted.trial.unittest.PyUnitResultAdapter`, for
+ storing test failures and successes in.
+
+ A base class which uses this metaclass should define the following class
+ attributes:
+
+ :ivar testSuites: A list of :class:`unittest.TestSuite`s (or their
+ :mod:`doctest` or :api:`twisted.trial` equivalents).
+ :ivar methodPrefix: A string to prefix the generated method names
+ with. (default: 'test_')
+ """
+
+ testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
+
+ def __new__(cls, name, bases, attrs):
+ """Construct the initialiser for a new
+ :api:`twisted.trial.unittest.TestCase`.
+ """
+ logging.debug("Metaclass __new__ constructor called for %r" % name)
+
+ if not 'testSuites' in attrs:
+ attrs['testSuites'] = list()
+ if not 'methodPrefix' in attrs:
+ attrs['methodPrefix'] = 'test_'
+
+ testSuites = attrs['testSuites']
+ methodPrefix = attrs['methodPrefix']
+ logging.debug(
+ "Metaclass __new__() class %r(testSuites=%r, methodPrefix=%r)" %
+ (name, '\n\t'.join([str(ts) for ts in testSuites]), methodPrefix))
+
+ generatedMethods = cls.generateTestMethods(testSuites, methodPrefix)
+ attrs.update(generatedMethods)
+ #attrs['init'] = cls.__init__ # call the standard initialiser
+ return super(DynamicTestCaseMeta, cls).__new__(cls, name, bases, attrs)
+
+ @classmethod
+ def generateTestMethods(cls, testSuites, methodPrefix='test_'):
+ """Dynamically generate methods and their names for a
+ :api:`twisted.trial.unittest.TestCase`.
+
+ :param list testSuites: A list of :class:`unittest.TestSuite`s (or
+ their :mod:`doctest` or :api:`twisted.trial`
+ equivalents).
+ :param str methodPrefix: A string to prefix the generated method names
+ with. (default: 'test_')
+ :rtype: dict
+ :returns: A dictionary of class attributes whose keys are dynamically
+ generated method names (prefixed with **methodPrefix**), and
+ whose corresponding values are dynamically generated methods
+ (taken out of the class attribute ``testSuites``).
+ """
+ def testMethodFactory(test, name):
+ def createTestMethod(test):
+ def testMethod(*args, **kwargs):
+ """When this function is generated, a methodname (beginning
+ with whatever **methodPrefix** was set to) will also be
+ generated, and the (methodname, method) pair will be
+ assigned as attributes of the generated
+ :api:`~twisted.trial.unittest.TestCase`.
+ """
+ # Get the number of failures before test.run():
+ origFails = len(cls.testResult.original.failures)
+ test.run(cls.testResult)
+ # Fail the generated testMethod if the underlying failure
+ # count has increased:
+ if (len(cls.testResult.original.failures) > origFails):
+ fail = cls.testResult.original.failures[origFails:][0]
+ raise unittest.FailTest(''.join([str(fail[0]),
+ str(fail[1])]))
+ return cls.testResult
+ testMethod.__name__ = str(name)
+ return testMethod
+ return createTestMethod(test)
+
+ newAttrs = {}
+ for testSuite in testSuites:
+ for test in testSuite:
+ origName = test.id()
+ if origName.find('.') > 0:
+ origFunc = origName.split('.')[-2:]
+ origName = '_'.join(origFunc)
+ if origName.endswith('_py'): # this happens with doctests
+ origName = origName.strip('_py')
+ methName = str(methodPrefix + origName).replace('.', '_')
+ meth = testMethodFactory(test, methName)
+ logging.debug("Set %s.%s=%r" % (cls.__name__, methName, meth))
+ newAttrs[methName] = meth
+ return newAttrs
+
+
+class OldUnittests(unittest.TestCase):
+ """A wrapper around :mod:`bridgedb.Tests` to produce :api:`~twisted.trial`
+ compatible output.
+
+ Generates a :api:`twisted.trial.unittest.TestCase` containing a
+ test for each of the individual tests in :mod:`bridgedb.Tests`.
+
+ Each test in this :api:`~twisted.trial.unittest.TestCase`` is dynamically
+ generated from one of the old unittests in :mod:`bridgedb.Tests`. Then,
+ the class is wrapped to cause the results reporting mechanisms to be
+ :api:`~twisted.trial` compatible.
+
+ :returns: A :api:`twisted.trial.unittest.TestCase`.
+ """
+ __metaclass__ = DynamicTestCaseMeta
+ testSuites = Tests.testSuite()
+ testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
+ methodPrefix = 'test_regressionsNewCode_'
+
+
+class MonkeypatchedOldUnittests(unittest.TestCase):
+ """A wrapper around :mod:`bridgedb.Tests` to produce :api:`~twisted.trial`
+ compatible output.
+
+ For each test in this ``TestCase``, one of the old unittests in
+ bridgedb/Tests.py is run. For all of the tests, some functions and classes
+ are :api:`twisted.python.monkey.MonkeyPatcher.patch`ed with old,
+ deprecated code from :mod:`bridgedb.test.deprecated` to ensure that any
+ new code has not caused any regressions.
+ """
+ __metaclass__ = DynamicTestCaseMeta
+ testSuites = Tests.testSuite()
+ testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
+ methodPrefix = 'test_regressionsOldCode_'
+ patcher = monkeypatchTests()
+
+ def runWithPatches(self, *args):
+ """Replaces :api:`~twisted.trial.unittest.TestCase.run` as the default
+ methodName to run. This method calls ``run()`` though
+ ``self.patcher.runWithPatches``, using the class **testResult** object.
+ """
+ self.patcher.runWithPatches(self.run, self.testResult)
+ self.patcher.restore()
+
+ def __init__(self, methodName='runWithPatches'):
+ super(MonkeypatchedOldUnittests, self).__init__(methodName=methodName)
+
+
+class TrialAdaptedDoctests(unittest.TestCase):
+ """Discovers and runs all doctests within the ``bridgedb`` package.
+
+ Finds all doctests from the directory that BridgeDB was installed in, in
+ all Python modules and packages, and runs them with :api:`twisted.trial`.
+ """
+ __metaclass__ = DynamicTestCaseMeta
+ testSuites = generateTrialAdaptedDoctestsSuite()
+ testResult = unittest.PyUnitResultAdapter(pyunit.TestResult())
+ methodPrefix = 'test_doctestsIn_'
diff --git a/test/test_bridgedb.py b/test/test_bridgedb.py
new file mode 100644
index 0000000..70c71f0
--- /dev/null
+++ b/test/test_bridgedb.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the `bridgedb` commandline script."""
+
+from __future__ import print_function
+
+import os
+import signal
+import time
+
+from twisted.trial import unittest
+from twisted.trial.unittest import FailTest
+from twisted.trial.unittest import SkipTest
+
+from bridgedb.test.util import processExists
+from bridgedb.test.util import getBridgeDBPID
+
+
+class BridgeDBCliTest(unittest.TestCase):
+ """Test the `bridgedb` command."""
+
+ def setUp(self):
+ here = os.getcwd()
+ topdir = here.rstrip('_trial_temp')
+ self.rundir = os.path.join(topdir, 'run')
+ self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
+ self.pid = getBridgeDBPID(self.pidfile)
+ self.assignmentsFile = os.path.join(self.rundir, 'assignments.log')
+
+ def doSleep(self):
+ """Sleep for some ammount of time.
+
+ We usually have less fake bridge descriptors with CI runs than we do
+ during other tests, so we can safely decrease the sleep time on CI
+ machines.
+ """
+ if os.environ.get("TRAVIS"):
+ time.sleep(10)
+ else:
+ time.sleep(20)
+ return
+
+ def test_bridgedb_assignments_log(self):
+ """This test should only be run if a BridgeDB server has already been
+ started in another process.
+
+ To see how this is done for the Travis CI tests, see the
+ 'before_script' section of the ``.travis.yml`` file in the top
+ directory of this repository.
+
+ This test ensures that an ``assignments.log`` file is created after a
+ BridgeDB process was started.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ self.assertTrue(os.path.isfile(self.assignmentsFile))
+
+ def test_bridgedb_SIGHUP_assignments_log(self):
+ """Test that BridgeDB creates a new ``assignments.log`` file after
+ receiving a SIGHUP.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ os.unlink(self.assignmentsFile)
+ os.kill(self.pid, signal.SIGHUP)
+ self.doSleep()
+ self.assertTrue(os.path.isfile(self.assignmentsFile))
+
+ def test_bridgedb_SIGUSR1_buckets(self):
+ """Test that BridgeDB dumps buckets appropriately after a SIGUSR1."""
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ os.kill(self.pid, signal.SIGUSR1)
+ self.doSleep()
+ buckets = [['email', False], ['https', False], ['unallocated', False]]
+ for rundirfile in os.listdir(self.rundir):
+ for bucket in buckets:
+ if rundirfile.startswith(bucket[0]):
+ bucket[1] = True
+ break
+ for bucket in buckets:
+ self.assertTrue(bucket[1], "%s bucket was not dumped!" % bucket[0])
diff --git a/test/test_bridgerequest.py b/test/test_bridgerequest.py
new file mode 100644
index 0000000..ccbf406
--- /dev/null
+++ b/test/test_bridgerequest.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+from twisted.trial import unittest
+
+from bridgedb import bridgerequest
+from bridgedb.bridgerequest import IRequestBridges
+from bridgedb.bridgerequest import BridgeRequestBase
+
+
+class BridgeRequestBaseTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.bridgerequest.BridgeRequestBase`."""
+
+ def setUp(self):
+ """Setup test run."""
+ self.request = BridgeRequestBase()
+
+ def test_BridgeRequestBase_implements_IRequestBridges(self):
+ """BridgeRequestBase should implement IRequestBridges interface."""
+ self.assertTrue(IRequestBridges.implementedBy(BridgeRequestBase))
+
+ def test_BridgeRequestBase_withoutBlockInCountry(self):
+ """BridgeRequestBase.withoutBlockInCountry() should add the country CC
+ to the ``notBlockedIn`` attribute.
+ """
+ self.request.withoutBlockInCountry('US')
+ self.assertIn('us', self.request.notBlockedIn)
+
+ def test_BridgeRequestBase_withPluggableTransportType(self):
+ """BridgeRequestBase.withPluggableTransportType() should add the
+ pluggable transport type to the ``transport`` attribute.
+ """
+ self.request.withPluggableTransportType('huggable_transport')
+ self.assertIn('huggable_transport', self.request.transports)
+
+ def test_BridgeRequestBase_getHashringPlacement_without_client(self):
+ """BridgeRequestBase.getHashringPlacement() without a client parameter
+ should use the default client identifier string.
+ """
+ self.assertEqual(self.request.getHashringPlacement('AAAA'),
+ 3486762050L)
+
+ def test_BridgeRequestBase_getHashringPlacement_with_client(self):
+ """BridgeRequestBase.getHashringPlacement() with a client parameter
+ should use the client identifier string.
+ """
+ self.assertEqual(self.request.getHashringPlacement('AAAA', client='you'),
+ 2870307088L)
diff --git a/test/test_bridges.py b/test/test_bridges.py
new file mode 100644
index 0000000..db662ae
--- /dev/null
+++ b/test/test_bridges.py
@@ -0,0 +1,1820 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.bridges` module."""
+
+from binascii import a2b_hex
+
+import datetime
+import ipaddr
+import io
+import hashlib
+import os
+import warnings
+
+from twisted.trial import unittest
+
+from bridgedb import bridges
+from bridgedb.Bridges import FilteredBridgeSplitter
+from bridgedb.bridgerequest import BridgeRequestBase
+from bridgedb.parse import descriptors
+from bridgedb.parse.addr import PortList
+from bridgedb.parse.nickname import InvalidRouterNickname
+
+
+# Don't print "WARNING:root: Couldn't parse K=V from PT arg: ''" a bunch of
+# times while running the tests.
+warnings.filterwarnings("ignore", ".*Couldn't parse K=V from PT arg.*", Warning)
+
+
+BRIDGE_NETWORKSTATUS = '''\
+r FourfoldQuirked LDIlxIBTMQJeIR9Lblv0XDM/3Sw c4EVu2rO/iD/DJYBX/Ll38DGQWI 2014-12-22 21:51:27 179.178.155.140 36489 0
+a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488
+s Fast Guard Running Stable Valid
+w Bandwidth=1585163
+p reject 1-65535
+'''
+
+BRIDGE_SERVER_DESCRIPTOR = '''\
+ at purpose bridge
+router FourfoldQuirked 179.178.155.140 36489 0 0
+or-address [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488
+platform Tor 0.2.3.24-rc on Linux
+opt protocols Link 1 2 Circuit 1
+published 2014-12-22 21:51:27
+opt fingerprint 2C32 25C4 8053 3102 5E21 1F4B 6E5B F45C 333F DD2C
+uptime 33200687
+bandwidth 1866688205 2110169275 1623207134
+opt extra-info-digest 4497E81715D958105C6A39D348163AD8F3080FB2
+onion-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBANKicvIGWp9WGKOJV8Fs3YKdTDrgxlggzyKgkW+MZWEPQ9lLcrmXqBdW
+nVK5EABByHnnxJfk+sm+6yDYxY/lFVL1SEP84pAK1Z21f4+grNlwox1DLyntXDdz
+BCZuRszuBYK3ncsk+ePQeUzRKQ/GZt9s/oy0IjtNbAoAoq7DKUVzAgMBAAE=
+-----END RSA PUBLIC KEY-----
+signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBALnJK7A9aZIp2ry9ruVYzm4VfaXzNHdTcvkXTrETu/jLXsosEwj9viSe
+Ry3W/uctbjzdwlIY0ZBUuV20q9bh+/c7Q0T8LOHBZouOy+nhFOUX+Q5YCG9cRnY0
+hBebYTzyplh0tT8xyYwcS8y6esL+gjVDLo6Og3QPhWRFQ4CyCic9AgMBAAE=
+-----END RSA PUBLIC KEY-----
+contact Somebody <somebody at example.com>
+ntor-onion-key aVmfOm9C046wM8ktGnpfBHSNj1Jm30M/m2P7W3a7Xn8
+reject *:*
+router-signature
+-----BEGIN SIGNATURE-----
+nxml4rTyTrj8dHcsFt2B4ACz2AN5CuZ2t5UF1BtXUpuzHmqVlg7imy8Cp2xIwoDa
+4uv/tTG32macauVnMHt0hSbtBF5nHfxU9G1T/XzdtL+KD8REDGky4allXnmvF6In
+rFtSn2OeZewvi8oYPmVYKgzHL6tzZxs2Sn/bOTj5sRw=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_EXTRAINFO = '''\
+extra-info FourfoldQuirked 2C3225C4805331025E211F4B6E5BF45C333FDD2C
+published 2014-12-22 21:51:27
+write-history 2014-12-22 21:51:27 (900 s) 3188736,2226176,2866176
+read-history 2014-12-22 21:51:27 (900 s) 3891200,2483200,2698240
+dirreq-write-history 2014-12-22 21:51:27 (900 s) 1024,0,2048
+dirreq-read-history 2014-12-22 21:51:27 (900 s) 0,0,0
+geoip-db-digest 51AE9611B53880B2BCF9C71E735D73F33FAD2DFE
+geoip6-db-digest 26B0D55B20BEB496A3ADE7C6FDD866F5A81027F7
+dirreq-stats-end 2014-12-22 21:51:27 (86400 s)
+dirreq-v3-ips
+dirreq-v3-reqs
+dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
+transport obfs3 179.178.155.140:36490
+transport obfs2 179.178.155.140:36491
+transport scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+transport obfs4 179.178.155.140:36493 iat-mode=0,node-id=25293f2761d658cc70c19515861842d712751bdc,public-key=02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc
+bridge-stats-end 2014-12-22 21:51:27 (86400 s)
+bridge-ips ca=8
+bridge-ip-versions v4=8,v6=0
+bridge-ip-transports <OR>=8
+router-signature
+-----BEGIN SIGNATURE-----
+cn4+8pQwCMPnHcp1s8wm7ZYsnd9AXJH6ysNlvQ63jsPCG9JdE5E8BwCThEgUccJI
+XILT4o+SveEQUG72R4bENsKxqV4rRNh1g6CNAbYhAITqrU9B+jImDgrBBW+XWT5K
+78ECRPn6Y4KsxFb0TIn7ddv9QjApyBJNIDMihH80Yng=
+-----END SIGNATURE-----
+'''
+
+# The timestamps have changed, and the IP:port for obfs3 changed
+BRIDGE_EXTRAINFO_NEW = '''\
+extra-info FourfoldQuirked 2C3225C4805331025E211F4B6E5BF45C333FDD2C
+published 2014-12-22 22:51:27
+write-history 2014-12-22 22:51:27 (900 s) 3188736,2226176,2866176
+read-history 2014-12-22 22:51:27 (900 s) 3891200,2483200,2698240
+dirreq-write-history 2014-12-22 22:51:27 (900 s) 1024,0,2048
+dirreq-read-history 2014-12-22 22:51:27 (900 s) 0,0,0
+geoip-db-digest 51AE9611B53880B2BCF9C71E735D73F33FAD2DFE
+geoip6-db-digest 26B0D55B20BEB496A3ADE7C6FDD866F5A81027F7
+dirreq-stats-end 2014-12-22 22:51:27 (86400 s)
+dirreq-v3-ips
+dirreq-v3-reqs
+dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
+transport obfs3 11.11.11.11:1111
+transport obfs2 179.178.155.140:36491
+transport scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+transport obfs4 179.178.155.140:36493 iat-mode=0,node-id=25293f2761d658cc70c19515861842d712751bdc,public-key=02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc
+bridge-stats-end 2014-12-22 22:51:27 (86400 s)
+bridge-ips ca=8
+bridge-ip-versions v4=8,v6=0
+bridge-ip-transports <OR>=8
+router-signature
+-----BEGIN SIGNATURE-----
+cn4+8pQwCMPnHcp1s8wm7ZYsnd9AXJH6ysNlvQ63jsPCG9JdE5E8BwCThEgUccJI
+XILT4o+SveEQUG72R4bENsKxqV4rRNh1g6CNAbYhAITqrU9B+jImDgrBBW+XWT5K
+78ECRPn6Y4KsxFb0TIn7ddv9QjApyBJNIDMihH80Yng=
+-----END SIGNATURE-----
+'''
+
+
+class BridgeIntegrationTests(unittest.TestCase):
+ """Integration tests to ensure that the new :class:`bridgedb.bridges.Bridge`
+ class has compatible behaviour with the expected behaviour of the old
+ :class:`bridgedb.Bridges.Bridge` class.
+
+ .. data: OldTest (enum)
+
+ These tests were refactored from the old tests for
+ :class:`~bridgedb.test.deprecated.Bridge`, which lived in
+ ``lib/bridgedb/test/test_Bridges.py``. For the translations from the old
+ tests in ``bridgedb.test.test_Bridges.BridgeClassTest`` to their new
+ equivalents here in ``bridgedb.test.test_bridges.BridgeIntegrationTests``,
+ which should test for the same things as their old equivalents, see the
+ following table:
+
+ ============================================== ========================
+ OldTest Equivalent Test(s) Here
+ ============================================== ========================
+ test_init test_integration_init_[0-5]
+ test_getID test_integration_getID
+ test_setDescriptorDigest test_integration_setDescriptorDigest
+ test_setExtraInfoDigest test_integration_setExtraInfoDigest
+ test_setVerified test_integration_setVerified
+ test_setRunningStable test_integration_setRunningStable
+ test_getConfigLine_vanilla_withoutFingerprint test_integration_getConfigLine_vanilla_withoutFingerprint
+ test_getConfigLine_vanilla_withFingerprint test_integration_getConfigLine_vanilla_withFingerprint
+ test_getConfigLine_scramblesuit_withFingeprint test_integration_getConfigLine_scramblesuit_withFingerprint
+ test_splitterBridgeInsertion test_integration_hashringBridgeInsertion
+ ============================================== ========================
+ ..
+ """
+
+ def setUp(self):
+ self.nickname = 'unnamed'
+ self.ip = ipaddr.IPAddress('127.0.0.1')
+ self.orport = '9001'
+ self.fingerprint = 'A1CC8DFEF1FA11AF9C40AF1054DF9DAF45250556'
+ self.id_digest = a2b_hex(self.fingerprint)
+ self.or_addresses = {ipaddr.IPAddress('6.6.6.6'): PortList(6666),
+ ipaddr.IPAddress('42.1.42.1'): PortList(443)}
+
+ def test_integration_init_0(self):
+ """Ensure that we can initialise the new :class:`bridgedb.bridges.Bridge`
+ class in the same manner as the old :class:`bridgedb.Bridges.Bridge`
+ class. This test ensures that initialisation with a fingerprint is
+ successful.
+ """
+ b = bridges.Bridge(self.nickname, self.ip, self.orport,
+ fingerprint=self.fingerprint)
+ self.assertIsInstance(b, bridges.Bridge)
+
+ def test_integration_init_1(self):
+ """Ensure that we can initialise the new :class:`bridgedb.bridges.Bridge`
+ class in the same manner as the old :class:`bridgedb.Bridges.Bridge`
+ class. This test ensures that initialisation with a digest of a
+ bridge's ID key is successful.
+ """
+ b = bridges.Bridge(self.nickname, self.ip, self.orport,
+ id_digest=self.id_digest)
+ self.assertIsInstance(b, bridges.Bridge)
+
+ def test_integration_init_2(self):
+ """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
+ ``id_digest`` should raise a TypeError.
+ """
+ self.failUnlessRaises(TypeError, bridges.Bridge,
+ self.nickname, self.ip, self.orport,
+ id_digest=self.id_digest[:-1])
+
+ def test_integration_init_3(self):
+ """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
+ ``fingerprint`` should raise a TypeError.
+ """
+ self.failUnlessRaises(TypeError, bridges.Bridge,
+ self.nickname, self.ip, self.orport,
+ fingerprint=self.fingerprint[:-1])
+
+ def test_integration_init_4(self):
+ """Initialisation of a :class:`bridgedb.bridges.Bridge` with a bad
+ ``fingerprint`` should raise a TypeError.
+ """
+ invalid_fingerprint = self.fingerprint[:-1] + 'q'
+ self.failUnlessRaises(TypeError, bridges.Bridge, self.nickname,
+ self.ip, self.orport,
+ fingerprint=invalid_fingerprint)
+
+ def test_integration_init_5(self):
+ """Initialisation of a :class:`bridgedb.bridges.Bridge` without either
+ a ``fingerprint`` or an ``id_digest`` should raise a TypeError.
+ """
+ self.failUnlessRaises(TypeError, bridges.Bridge,
+ self.nickname, self.ip, self.orport)
+
+ def test_integration_getID(self):
+ """Calling ``bridges.Bridge.getID()`` should return the binary encoded
+ ``fingerprint``.
+ """
+ bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
+ self.fingerprint)
+ self.assertEqual(self.id_digest, bridge.getID())
+
+ def test_integration_setDescriptorDigest(self):
+ """Test setting the server-descriptor digest value."""
+ bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
+ self.fingerprint)
+ testtext = 'thisisatest'
+ bridge.setDescriptorDigest(testtext)
+ self.assertEqual(bridge.desc_digest, testtext)
+
+ def test_integration_setExtraInfoDigest(self):
+ """Test setting the extra-info digest value."""
+ bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
+ self.fingerprint)
+ testtext = 'thisisatest'
+ bridge.setExtraInfoDigest(testtext)
+ self.assertEqual(bridge.ei_digest, testtext)
+
+ def test_integration_setVerified(self):
+ """Test setting the `verified` attribute on a Bridge."""
+ raise unittest.SkipTest(
+ ("The setVerified() and isVerified() methods were not refactored "
+ "into the new bridgedb.bridges.Bridge class, as it's not clear "
+ "yet if they are necessary. Skip these tests for now."))
+
+ bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
+ self.fingerprint)
+ bridge.setVerified()
+ self.assertTrue(bridge.isVerified())
+ self.assertTrue(bridge.verified)
+ self.assertEqual(self.id_digest, bridge.getID())
+
+ def test_integration_setRunningStable(self):
+ """Test setting the `running` and `stable` attributes on a Bridge."""
+ bridge = bridges.Bridge(self.nickname, self.ip, self.orport,
+ self.fingerprint)
+ self.assertFalse(bridge.running)
+ self.assertFalse(bridge.stable)
+ bridge.setStatus(True, True)
+ self.assertTrue(bridge.running)
+ self.assertTrue(bridge.stable)
+
+ def test_integration_getConfigLine_vanilla_withoutFingerprint(self):
+ """Should return a config line without a fingerprint."""
+ #self.skip = True
+ bridge = bridges.Bridge('nofpr', '23.23.23.23', 2323, self.fingerprint,
+ or_addresses=self.or_addresses)
+ bridgeLine = bridge.getConfigLine()
+ ip = bridgeLine.split(':')[0]
+ self.assertTrue(ipaddr.IPAddress(ip))
+
+ def test_integration_getConfigLine_vanilla_withFingerprint(self):
+ """Should return a config line with a fingerprint."""
+ bridge = bridges.Bridge('fpr', '23.23.23.23', 2323,
+ id_digest=self.id_digest,
+ or_addresses=self.or_addresses)
+ bridgeLine = bridge.getConfigLine(includeFingerprint=True)
+ self.assertIsNotNone(bridgeLine)
+ self.assertSubstring(self.fingerprint, bridgeLine)
+ ip = bridgeLine.split(':')[0]
+ self.assertTrue(ipaddr.IPAddress(ip))
+
+ def test_integration_getConfigLine_scramblesuit_withFingerprint(self):
+ """Should return a scramblesuit config line with a fingerprint."""
+ bridge = bridges.Bridge('philipkdick', '23.23.23.23', 2323,
+ id_digest=self.id_digest,
+ or_addresses=self.or_addresses)
+ ptArgs = {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
+ pt = bridges.PluggableTransport(bridge.fingerprint, 'scramblesuit',
+ ipaddr.IPAddress('42.42.42.42'), 4242,
+ ptArgs)
+ bridge.transports.append(pt)
+ bridgeLine = bridge.getConfigLine(includeFingerprint=True,
+ transport='scramblesuit')
+ ptArgsList = ' '.join(["{0}={1}".format(k,v) for k,v in ptArgs.items()])
+ self.assertEqual("scramblesuit 42.42.42.42:4242 %s %s"
+ % (self.fingerprint, ptArgsList),
+ bridgeLine)
+
+ def test_integration_hashringBridgeInsertion(self):
+ key = "Testing-Bridges-To-Rings"
+ hashring = FilteredBridgeSplitter(key)
+
+ bridge1 = bridges.Bridge('unamed1', '1.2.3.5', 9100,
+ 'a1cc8dfef1fa11af9c40af1054df9daf45250550')
+ bridge1.setStatus(running = True)
+ bridge2 = bridges.Bridge('unamed2', '1.2.3.4', 8080,
+ 'a1cc8dfef1fa11af9c40af1054df9daf45250551')
+ bridge2.setStatus(running = True)
+ bridge3 = bridges.Bridge('unamed3', '5.2.3.4', 8080,
+ 'b1cc8dfef1fa11af9c40af1054df9daf45250552')
+ bridge3.setStatus(running = True)
+ bridge4 = bridges.Bridge('unamed3', '5.2.3.4', 8080,
+ 'b1cc8dfef1fa11af9c40af1054df9daf45250552')
+ bridge4.setStatus(running = True)
+
+ self.failUnlessEqual(len(hashring), 0)
+ hashring.insert(bridge1)
+ hashring.insert(bridge2)
+ hashring.insert(bridge3)
+ # Check that all were inserted
+ self.failUnlessEqual(len(hashring), 3)
+ hashring.insert(bridge1)
+ # Check that the same bridge is not inserted twice
+ self.failUnlessEqual(len(hashring), 3)
+ hashring.insert(bridge4)
+ # Check that identical bridges are not inserted twice
+ self.failUnlessEqual(len(hashring), 3)
+
+
+class FlagsTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.bridges.Flags`."""
+
+ def setUp(self):
+ self.flags = bridges.Flags()
+ self._all_flag_names = ["fast", "guard", "running", "stable", "valid"]
+
+ def test_init(self):
+ """Upon initialisation, all flags should be ``False``."""
+ for flag in self._all_flag_names:
+ f = getattr(self.flags, flag, None)
+ self.assertFalse(f, "%s should be False" % flag)
+
+ def test_settingStable(self):
+ """Setting the Stable flag to ``True`` should result in Flags.stable
+ being ``True``.
+ """
+ self.flags.stable = True
+ self.assertTrue(self.flags.stable, "The Stable flag should be True")
+
+ def test_settingRunning(self):
+ """Setting the Running flag to ``True`` should result in Flags.running
+ being ``True``.
+ """
+ self.flags.running = True
+ self.assertTrue(self.flags.running, "The Running flag should be True")
+
+ def test_changingFlags(self):
+ """Setting a flag and then unsetting it should result in it being
+ ``True`` and then ``False``.
+ """
+ self.flags.valid = True
+ self.assertTrue(self.flags.valid, "The Valid flag should be True")
+ self.flags.valid = False
+ self.assertFalse(self.flags.valid, "The Valid flag should be False")
+
+ def test_update_Fast_Stable(self):
+ """Test changing flags with the update() method."""
+ self.flags.update(["Fast", "Stable"])
+ self.assertTrue(self.flags.fast)
+ self.assertTrue(self.flags.stable)
+
+ def test_update_Fast(self):
+ """Test changing flags with the update() method."""
+ self.flags.update(["Fast"])
+ self.assertTrue(self.flags.fast)
+ self.assertFalse(self.flags.stable)
+
+ def test_update_Stable(self):
+ """Test changing flags with the update() method."""
+ self.flags.update(["Stable"])
+ self.assertFalse(self.flags.fast)
+ self.assertTrue(self.flags.stable)
+
+
+class BridgeAddressBaseTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.bridges.BridgeAddressBase`."""
+
+ def setUp(self):
+ self.fingerprint = '2C3225C4805331025E211F4B6E5BF45C333FDD2C'
+ self.bab = bridges.BridgeAddressBase()
+
+ def test_BridgeAddressBase_init(self):
+ """The BridgeAddressBase's _address and _fingerprint should be None."""
+ self.assertIsNone(self.bab._address)
+ self.assertIsNone(self.bab._fingerprint)
+
+ def test_BridgeAddressBase_fingerprint_del(self):
+ """The del method for the fingerprint property should reset the
+ fingerprint to None.
+ """
+ self.bab.fingerprint = self.fingerprint
+ self.assertEqual(self.bab.fingerprint, self.fingerprint)
+
+ del(self.bab.fingerprint)
+ self.assertIsNone(self.bab.fingerprint)
+ self.assertIsNone(self.bab._fingerprint)
+
+ def test_BridgeAddressBase_address_del(self):
+ """The del method for the address property should reset the
+ address to None.
+ """
+ self.bab.address = '11.12.13.14'
+ self.assertEqual(self.bab.address, ipaddr.IPv4Address('11.12.13.14'))
+
+ del(self.bab.address)
+ self.assertIsNone(self.bab.address)
+ self.assertIsNone(self.bab._address)
+
+ def test_BridgeAddressBase_country(self):
+ """The getter method for the country property should get the
+ address's geoIP country code.
+ """
+ self.bab.address = '11.12.13.14'
+ self.assertEqual(self.bab.address, ipaddr.IPv4Address('11.12.13.14'))
+
+ cc = self.bab.country
+ self.assertIsNotNone(cc)
+ self.assertIsInstance(cc, basestring)
+ self.assertEqual(len(cc), 2)
+
+
+class PluggableTransportTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.bridges.PluggableTransport."""
+
+ def setUp(self):
+ self.fingerprint = "ABCDEF0123456789ABCDEF0123456789ABCDEF01"
+
+ def test_PluggableTransport_init_with_parameters(self):
+ """Initialising a PluggableTransport with args should work."""
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar'})
+ self.assertIsInstance(pt, bridges.PluggableTransport)
+
+ def test_PluggableTransport_init(self):
+ """Initialising a PluggableTransport without args should work."""
+ pt = bridges.PluggableTransport()
+ self.assertIsInstance(pt, bridges.PluggableTransport)
+
+ def test_PluggableTransport_port_del(self):
+ """The del method for the port property should reset the port to None.
+ """
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar'})
+ self.assertEqual(pt.port, 443)
+
+ del(pt.port)
+ self.assertIsNone(pt.port)
+ self.assertIsNone(pt._port)
+
+ def test_PluggableTransport_parseArgumentsIntoDict_valid_list(self):
+ """Parsing a valid list of PT args should return a dictionary."""
+ pt = bridges.PluggableTransport()
+ args = pt._parseArgumentsIntoDict(["sharedsecret=foobar",
+ "publickey=1234"])
+ self.assertIsInstance(args, dict)
+ self.assertItemsEqual(args, {"sharedsecret": "foobar",
+ "publickey": "1234"})
+
+ def test_PluggableTransport_parseArgumentsIntoDict_valid_list_multi(self):
+ """Parsing a valid list with multiple PT args in a single list element
+ should return a dictionary.
+ """
+ pt = bridges.PluggableTransport()
+ args = pt._parseArgumentsIntoDict(["sharedsecret=foobar,password=baz",
+ "publickey=1234"])
+ self.assertIsInstance(args, dict)
+ self.assertItemsEqual(args, {"sharedsecret": "foobar",
+ "password": "baz",
+ "publickey": "1234"})
+
+ def test_PluggableTransport_parseArgumentsIntoDict_invalid_missing_equals(self):
+ """Parsing a string of PT args where one PT arg (K=V) is missing an
+ ``=`` character should raise a ValueError.
+ """
+ pt = bridges.PluggableTransport()
+ args = pt._parseArgumentsIntoDict(
+ ["sharedsecret=foobar,password,publickey=1234"])
+ self.assertItemsEqual(args, {"sharedsecret": "foobar",
+ "publickey": "1234"})
+
+ def test_PluggableTransport_checkArguments_scramblesuit_missing_password(self):
+ """Calling _checkArguments on a scramblesuit PT without a password should
+ raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'scramblesuit', ('34.230.223.87', 37341, []))
+
+ def test_PluggableTransport_checkArguments_obfs4_missing_iatmode(self):
+ """Calling _checkArguments on an obfs4 PT without an iat-mode argument
+ should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
+ 'cert=UXj/cWm0qolGrROYpkl0UyD/7PEhzkoZkZXrOpjRKwImvkpQZwmF0nSzBXfyfbT9afBZEw']))
+
+ def test_PluggableTransport_checkArguments_obfs4_missing_cert(self):
+ """Calling _checkArguments on an obfs4 PT without a cert argument
+ should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', 37341, ['iat-mode=1']))
+
+ def test_PluggableTransport_checkArguments_obfs4_missing_publickey(self):
+ """Calling _checkArguments on an obfs4 PT without a public-key argument
+ should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
+ ('iat-mode=1,'
+ 'node-id=2a79f14120945873482b7823caabe2fcde848722')]))
+
+ def test_PluggableTransport_checkArguments_obfs4_missing_nodeid(self):
+ """Calling _checkArguments on an obfs4 PT without a public-key argument
+ should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', 37341, [
+ ('iat-mode=1,'
+ 'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]))
+
+ def test_PluggableTransport_runChecks_invalid_fingerprint(self):
+ """Calling _runChecks() on a PluggableTransport with an invalid
+ fingerprint should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ "INVALIDFINGERPRINT", 'obfs4', ('34.230.223.87', 37341, [
+ ('iat-mode=0,'
+ 'node-id=2a79f14120945873482b7823caabe2fcde848722,'
+ 'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]))
+
+ def test_PluggableTransport_runChecks_invalid_ip(self):
+ """Calling _runChecks() on a PluggableTransport with an invalid
+ IP address should raise a InvalidPluggableTransportIP exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.InvalidPluggableTransportIP,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223', 37341, [
+ ('iat-mode=0,'
+ 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
+
+ def test_PluggableTransport_runChecks_invalid_port_type(self):
+ """Calling _runChecks() on a PluggableTransport with an invalid port
+ should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', "anyport", [
+ ('iat-mode=0,'
+ 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
+
+ def test_PluggableTransport_runChecks_invalid_port_range(self):
+ """Calling _runChecks() on a PluggableTransport with an invalid port
+ (too high) should raise a MalformedPluggableTransport exception.
+ """
+ pt = bridges.PluggableTransport()
+ self.assertRaises(
+ bridges.MalformedPluggableTransport,
+ pt.updateFromStemTransport,
+ self.fingerprint, 'obfs4', ('34.230.223.87', 65536, [
+ ('iat-mode=0,'
+ 'node-id=2a79f14120945873482b7823caabe2fcde848722,')]))
+
+ def test_PluggableTransport_runChecks_invalid_pt_args(self):
+ """Calling _runChecks() on a PluggableTransport with an invalid PT
+ args should raise a MalformedPluggableTransport exception.
+ """
+ try:
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ 'sharedsecret=foobar')
+ except Exception as error:
+ self.failUnlessIsInstance(error,
+ bridges.MalformedPluggableTransport)
+
+ def test_PluggableTransport_getTransportLine_bridge_prefix(self):
+ """If the 'Bridge ' prefix was requested, then it should be at the
+ beginning of the bridge line.
+ """
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar',
+ 'password': 'unicorns'})
+ bridgeLine = pt.getTransportLine(bridgePrefix=True)
+ self.assertTrue(bridgeLine.startswith("Bridge "))
+
+ def test_PluggableTransport_getTransportLine_without_Fingerprint(self):
+ """If no fingerprint was requested, then there shouldn't be a
+ fingerprint in the bridge line.
+ """
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar',
+ 'password': 'unicorns'})
+ bridgeLine = pt.getTransportLine(includeFingerprint=False)
+ self.assertNotSubstring(self.fingerprint, bridgeLine)
+
+ def test_PluggableTransport_getTransportLine_content_order(self):
+ """Check the order and content of the bridge line string."""
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar',
+ 'password': 'unicorns'})
+ bridgeLine = pt.getTransportLine()
+
+ # We have to check for substrings because we don't know which order
+ # the PT arguments will end up in the bridge line. We also have to
+ # check for the lowercased transport name. Fortunately, the following
+ # three are the only ones which are important to have in order:
+ self.assertTrue(bridgeLine.startswith("voltronpt"))
+ self.assertSubstring("voltronpt 1.2.3.4:443 " + self.fingerprint,
+ bridgeLine)
+ # These ones can be in any order, but they should be at the end of the
+ # bridge line:
+ self.assertSubstring("password=unicorns", bridgeLine)
+ self.assertSubstring("sharedsecret=foobar", bridgeLine)
+
+ def test_PluggableTransport_getTransportLine_ptargs_space_delimited(self):
+ """The PT arguments in a bridge line should be space-separated."""
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "1.2.3.4", 443,
+ {'sharedsecret': 'foobar',
+ 'password': 'unicorns'})
+ bridgeLine = pt.getTransportLine()
+ self.assertTrue(
+ ("password=unicorns sharedsecret=foobar" in bridgeLine) or
+ ("sharedsecret=foobar password=unicorns" in bridgeLine))
+
+ def test_PluggableTransport_getTransportLine_IPv6(self):
+ """The address portion of a bridge line with an IPv6 address should
+ have square brackets around it.
+ """
+ pt = bridges.PluggableTransport(self.fingerprint,
+ "voltronPT", "2006:42::1234", 443,
+ {'sharedsecret': 'foobar',
+ 'password': 'unicorns'})
+ bridgeLine = pt.getTransportLine()
+ self.assertEqual(pt.address.version, 6)
+ self.assertIn("[2006:42::1234]:443", bridgeLine)
+
+
+class BridgeBackwardsCompatibilityTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.bridges.BridgeBackwardsCompatibility`."""
+
+ def setUp(self):
+ self.nickname = "RouterNickname"
+ self.address = "23.23.23.23"
+ self.orPort = 9001
+ self.fingerprint = "0123456789ABCDEF0123456789ABCDEF01234567"
+ self.orAddresses = {"2006:42::123F": PortList(443, 9001, 1337),
+ "2006:42::123E": PortList(9001, 993)}
+
+ def test_BridgeBackwardsCompatibility_init_with_PortList(self):
+ """Test initialisation with the usual number of valid arguments and
+ PortLists for the orAddresses.
+ """
+ bridge = bridges.BridgeBackwardsCompatibility(
+ self.nickname,
+ self.address,
+ self.orPort,
+ self.fingerprint,
+ self.orAddresses)
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+
+ def test_BridgeBackwardsCompatibility_init_without_PortList(self):
+ """Test initialisation with the usual number of valid arguments and
+ integers for the orAddresses' ports.
+ """
+ bridge = bridges.BridgeBackwardsCompatibility(
+ self.nickname,
+ self.address,
+ self.orPort,
+ self.fingerprint,
+ {"2006:42::123F": 443,
+ "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+
+ def test_BridgeBackwardsCompatibility_init_without_address(self):
+ """Test initialisation without an IP address."""
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ orport=self.orPort,
+ fingerprint=self.fingerprint,
+ or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+
+ def test_BridgeBackwardsCompatibility_init_invalid_orAddresses_address(self):
+ """Test initialisation with an invalid ORAddress."""
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ ip=self.address,
+ orport=self.orPort,
+ fingerprint=self.fingerprint,
+ or_addresses={"10.1.2.3": 443, "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+ self.assertEqual(len(bridge.orAddresses), 1)
+
+ def test_BridgeBackwardsCompatibility_init_invalid_orAddresses_port(self):
+ """Test initialisation with an invalid ORPort."""
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ ip=self.address,
+ orport=self.orPort,
+ fingerprint=self.fingerprint,
+ or_addresses={"2006:42::123F": 443, "2006:42::123E": "anyport"})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+ self.assertEqual(len(bridge.orAddresses), 1)
+
+ def test_BridgeBackwardsCompatibility_setStatus_stable(self):
+ """Using setStatus() to set the Stable flag should set Bridge.stable
+ and Bridge.flags.stable to True.
+ """
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ ip=self.address,
+ orport=self.orPort,
+ fingerprint=self.fingerprint,
+ or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+ self.assertFalse(bridge.stable)
+ self.assertFalse(bridge.flags.stable)
+
+ bridge.setStatus(stable=True)
+ self.assertTrue(bridge.stable)
+ self.assertTrue(bridge.flags.stable)
+
+ def test_BridgeBackwardsCompatibility_setStatus_running(self):
+ """Using setStatus() to set the Running flag should set Bridge.running
+ and Bridge.flags.running to True.
+ """
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ ip=self.address,
+ orport="anyport",
+ fingerprint=self.fingerprint,
+ or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+ self.assertFalse(bridge.running)
+ self.assertFalse(bridge.flags.running)
+
+ bridge.setStatus(running=True)
+ self.assertTrue(bridge.running)
+ self.assertTrue(bridge.flags.running)
+
+ def test_BridgeBackwardsCompatibility_setStatus_running_stable(self):
+ """Using setStatus() to set the Running and Stable flags should set
+ Bridge.running, Bridge.flags.running, Bridge.stable, and
+ Bridge.flags.stable.
+ """
+ bridge = bridges.BridgeBackwardsCompatibility(
+ nickname=self.nickname,
+ ip=self.address,
+ orport="anyport",
+ fingerprint=self.fingerprint,
+ or_addresses={"2006:42::123F": 443, "2006:42::123E": 9001})
+ self.assertIsInstance(bridge, bridges.BridgeBackwardsCompatibility)
+ self.assertFalse(bridge.running)
+ self.assertFalse(bridge.flags.running)
+ self.assertFalse(bridge.stable)
+ self.assertFalse(bridge.flags.stable)
+
+ bridge.setStatus(running=True, stable=True)
+ self.assertTrue(bridge.running)
+ self.assertTrue(bridge.flags.running)
+ self.assertTrue(bridge.stable)
+ self.assertTrue(bridge.flags.stable)
+
+
+class BridgeTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.bridges.Bridge`."""
+
+ def _parseAllDescriptorFiles(self):
+ self.networkstatus = descriptors.parseNetworkStatusFile(
+ self._networkstatusFile)[0]
+ self.serverdescriptor = descriptors.parseServerDescriptorsFile(
+ self._serverDescriptorFile)[0]
+ self.extrainfo = descriptors.parseExtraInfoFiles(
+ self._extrainfoFile).values()[0]
+ self.extrainfoNew = descriptors.parseExtraInfoFiles(
+ self._extrainfoNewFile).values()[0]
+
+ def _writeNetworkstatus(self, networkstatus):
+ with open(self._networkstatusFile, 'w') as fh:
+ fh.write(networkstatus)
+ fh.flush()
+
+ def _writeServerdesc(self, serverdesc):
+ with open(self._serverDescriptorFile, 'w') as fh:
+ fh.write(serverdesc)
+ fh.flush()
+
+ def _writeExtrainfo(self, extrainfo):
+ with open(self._extrainfoFile, 'w') as fh:
+ fh.write(extrainfo)
+ fh.flush()
+
+ def _writeExtrainfoNew(self, extrainfo):
+ with open(self._extrainfoNewFile, 'w') as fh:
+ fh.write(extrainfo)
+ fh.flush()
+
+ def _writeDescriptorFiles(self, networkstatus, serverdesc, extrainfo, extrainfoNew):
+ self._writeNetworkstatus(networkstatus)
+ self._writeServerdesc(serverdesc)
+ self._writeExtrainfo(extrainfo)
+ self._writeExtrainfoNew(extrainfoNew)
+
+ def setUp(self):
+ def _cwd(filename):
+ return os.path.sep.join([os.getcwd(), filename])
+
+ self._networkstatusFile = _cwd('BridgeTests-networkstatus-bridges')
+ self._serverDescriptorFile = _cwd('BridgeTests-bridge-descriptors')
+ self._extrainfoFile = _cwd('BridgeTests-cached-extrainfo')
+ self._extrainfoNewFile = _cwd('BridgeTests-cached-extrainfo.new')
+
+ self._writeDescriptorFiles(BRIDGE_NETWORKSTATUS,
+ BRIDGE_SERVER_DESCRIPTOR,
+ BRIDGE_EXTRAINFO,
+ BRIDGE_EXTRAINFO_NEW)
+ self._parseAllDescriptorFiles()
+
+ self.bridge = bridges.Bridge()
+
+ def tearDown(self):
+ """Reset safelogging to its default (disabled) state, due to
+ test_Bridge_str_with_safelogging changing it.
+ """
+ bridges.safelog.safe_logging = False
+
+ def test_Bridge_nickname_del(self):
+ """The del method for the nickname property should reset the nickname
+ to None.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.assertEqual(self.bridge.nickname, "FourfoldQuirked")
+
+ del(self.bridge.nickname)
+ self.assertIsNone(self.bridge.nickname)
+ self.assertIsNone(self.bridge._nickname)
+
+ def test_Bridge_nickname_invalid(self):
+ """The del method for the nickname property should reset the nickname
+ to None.
+ """
+ # Create a networkstatus descriptor with an invalid nickname:
+ filename = self._networkstatusFile + "-invalid"
+ fh = open(filename, 'w')
+ invalid = BRIDGE_NETWORKSTATUS.replace(
+ "FourfoldQuirked",
+ "ThisRouterNicknameContainsWayMoreThanNineteenBytes")
+ fh.seek(0)
+ fh.write(invalid)
+ fh.flush()
+ fh.close()
+
+ self.assertRaises(InvalidRouterNickname,
+ descriptors.parseNetworkStatusFile,
+ filename)
+
+ def test_Bridge_orport_del(self):
+ """The del method for the orPort property should reset the orPort
+ to None.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.assertEqual(self.bridge.orPort, 36489)
+
+ del(self.bridge.orPort)
+ self.assertIsNone(self.bridge.orPort)
+ self.assertIsNone(self.bridge._orPort)
+
+ def test_Bridge_str_without_safelogging(self):
+ """The str() method of a Bridge should return an identifier for the
+ Bridge, which should be different if safelogging is enabled.
+ """
+ bridges.safelog.safe_logging = False
+
+ bridge = bridges.Bridge()
+ bridge.updateFromNetworkStatus(self.networkstatus)
+
+ identifier = str(bridge)
+ self.assertEqual(identifier,
+ ''.join(['$', bridge.fingerprint,
+ '~', bridge.nickname]))
+
+ def test_Bridge_str_with_safelogging(self):
+ """The str() method of a Bridge should return an identifier for the
+ Bridge, which should be different if safelogging is enabled.
+ """
+ bridges.safelog.safe_logging = True
+
+ bridge = bridges.Bridge()
+ bridge.updateFromNetworkStatus(self.networkstatus)
+
+ identifier = str(bridge)
+ self.assertEqual(
+ identifier,
+ ''.join(['$$',
+ hashlib.sha1(bridge.fingerprint).hexdigest().upper(),
+ '~', bridge.nickname]))
+
+ def test_Bridge_str_without_fingerprint(self):
+ """The str() method of a Bridge should return an identifier for the
+ Bridge, which should be different if the fingerprint is unknown.
+ """
+ bridge = bridges.Bridge()
+ bridge.updateFromNetworkStatus(self.networkstatus)
+ del(bridge.fingerprint)
+
+ identifier = str(bridge)
+ self.assertEqual(identifier,
+ ''.join(['$', '0'*40,
+ '~', bridge.nickname]))
+
+ def test_Bridge_str_without_fingerprint_without_nickname(self):
+ """Calling str(Bridge) on a Bridge whose fingerprint and nickname were
+ not set should return a Bridge identifier string where the fingerprint
+ is all 0's and the nickname is "Unnamed".
+ """
+ bridge = bridges.Bridge()
+ identifier = str(bridge)
+ self.assertEqual(identifier, ''.join(['$', '0'*40, '~', 'Unnamed']))
+
+ def test_Bridge_constructBridgeLine_IPv6(self):
+ """Bridge._constructBridgeLine() called with an IPv6 address should
+ wrap the IPv6 address in '[]' in the returned bridge line.
+ """
+ bridge = bridges.Bridge()
+ addrport = (u'6bf3:806b:78cd::4ced:cfad:dad4', 36488, 6)
+
+ bridgeline = bridge._constructBridgeLine(addrport,
+ includeFingerprint=False,
+ bridgePrefix=True)
+ self.assertEqual(bridgeline, 'Bridge [6bf3:806b:78cd::4ced:cfad:dad4]:36488')
+
+ def test_Bridge_allVanillaAddresses_idempotency_self(self):
+ """Bridge.allVanillaAddresses should be idempotent, i.e. calling
+ allVanillaAddresses should not affect the results of subsequent calls.
+ """
+ self.bridge.address = '1.1.1.1'
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+
+ def test_Bridge_allVanillaAddresses_idempotency_others(self):
+ """Bridge.allVanillaAddresses should be idempotent, i.e. calling
+ allVanillaAddresses should not affect any of the Bridge's other
+ attributes (such as Bridge.orAddresses).
+ """
+ self.bridge.address = '1.1.1.1'
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ def test_Bridge_allVanillaAddresses_reentrancy_all(self):
+ """Bridge.allVanillaAddresses should be reentrant, i.e. updating the
+ Bridge's address, orPort, or orAddresses should update the value
+ returned by allVanillaAddresses.
+ """
+ self.bridge.address = '1.1.1.1'
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), None, 4)])
+ self.assertEqual(self.bridge.address, ipaddr.IPv4Address('1.1.1.1'))
+ self.assertEqual(self.bridge.orPort, None)
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertEqual(self.bridge.address, ipaddr.IPv4Address('1.1.1.1'))
+ self.assertEqual(self.bridge.orPort, 443)
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ self.bridge.address = '2.2.2.2'
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('2.2.2.2'), 443, 4)])
+ self.assertEqual(self.bridge.address, ipaddr.IPv4Address('2.2.2.2'))
+ self.assertEqual(self.bridge.orPort, 443)
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ self.bridge.orAddresses.append(
+ (ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6))
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('2.2.2.2'), 443, 4),
+ (ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6)])
+ self.assertEqual(self.bridge.address, ipaddr.IPv4Address('2.2.2.2'))
+ self.assertEqual(self.bridge.orPort, 443)
+ self.assertItemsEqual(self.bridge.orAddresses,
+ [(ipaddr.IPv6Address('200::6ffb:11bb:a129'), 4443, 6)])
+
+ def test_Bridge_allVanillaAddresses_reentrancy_orPort(self):
+ """Calling Bridge.allVanillaAddresses before Bridge.orPort is set
+ should return ``None`` for the port value, and after Bridge.orPort is
+ set, it should return the orPort.
+ """
+ self.bridge.address = '1.1.1.1'
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), None, 4)])
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+
+ def test_Bridge_allVanillaAddresses_reentrancy_address(self):
+ """Calling Bridge.allVanillaAddresses before Bridge.address is set
+ should return ``None`` for the address value, and after Bridge.address
+ is set, it should return the address.
+ """
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(None, 443, 4)])
+ self.bridge.address = '1.1.1.1'
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+
+ def test_Bridge_allVanillaAddresses_reentrancy_orAddresses(self):
+ """Calling Bridge.allVanillaAddresses before Bridge.orAddresses is set
+ should return only the Bridge's address and orPort.
+ """
+ self.bridge.address = '1.1.1.1'
+ self.bridge.orPort = 443
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+ self.assertItemsEqual(self.bridge.orAddresses, [])
+ self.bridge.orAddresses.append(
+ (ipaddr.IPv4Address('2.2.2.2'), 4443, 4))
+ self.assertItemsEqual(self.bridge.orAddresses,
+ [(ipaddr.IPv4Address('2.2.2.2'), 4443, 4)])
+ self.assertItemsEqual(self.bridge.allVanillaAddresses,
+ [(ipaddr.IPv4Address('2.2.2.2'), 4443, 4),
+ (ipaddr.IPv4Address('1.1.1.1'), 443, 4)])
+
+ def test_Bridge_updateORAddresses_valid_and_invalid(self):
+ """Bridge._updateORAddresses() called with a mixture of valid and
+ invalid ORAddress tuples should only retain the valid ones.
+ """
+ orAddresses = [
+ (u'1.1.1.1', 1111, False), # valid
+ (u'127.0.0.1', 2222, False), # invalid IPv4 loopback
+ (u'FE80::1234', 3333, True)] # invalid IPv6 link local
+ bridge = bridges.Bridge()
+ bridge._updateORAddresses(orAddresses)
+
+ self.assertEqual(len(bridge.orAddresses), 1)
+ addr, port, version = bridge.orAddresses[0]
+ self.assertEqual(addr, ipaddr.IPAddress('1.1.1.1'))
+ self.assertEqual(port, 1111)
+ self.assertEqual(version, 4)
+
+ def test_Bridge_updateFromNetworkStatus_IPv4_ORAddress(self):
+ """Calling updateFromNetworkStatus() with a descriptor which has an
+ IPv4 address as an additional ORAddress should result in a
+ FutureWarning before continuing parsing.
+ """
+ # Add an additional IPv4 ORAddress:
+ ns = BRIDGE_NETWORKSTATUS.replace(
+ 'a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488',
+ 'a [6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488\na 123.34.56.78:36488')
+ self._writeNetworkstatus(ns)
+ self._parseAllDescriptorFiles()
+
+ self.assertWarns(
+ FutureWarning,
+ "Got IPv4 address in 'a'/'or-address' line! Descriptor format may have changed!",
+ bridges.__file__, # filename
+ self.bridge.updateFromNetworkStatus,
+ self.networkstatus)
+
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+ self.assertIn((ipaddr.IPAddress('123.34.56.78'), 36488, 4),
+ self.bridge.allVanillaAddresses)
+
+ def test_Bridge_updateFromNetworkStatus_ignoreNetworkstatus(self):
+ """Calling updateFromNetworkStatus([â¦], ignoreNetworkstatus=True)
+ should update the Bridge's flags, its fingerprint, and its
+ descriptorDigest, and nothing else.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus,
+ ignoreNetworkstatus=True)
+
+ self.assertTrue(self.bridge.flags.running)
+ self.assertTrue(self.bridge.flags.stable)
+ self.assertIsNotNone(self.bridge.descriptorDigest)
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+
+ self.assertIsNone(self.bridge.nickname)
+ self.assertIsNone(self.bridge.address)
+ self.assertIsNone(self.bridge.orPort)
+ self.assertIsNone(self.bridge.orPort)
+
+ def test_Bridge_updateFromServerDescriptor(self):
+ """ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+
+ def test_Bridge_updateFromServerDescriptor_no_networkstatus(self):
+ """Parsing a server descriptor for a bridge which wasn't included in
+ the networkstatus document from the BridgeAuthority should raise a
+ ServerDescriptorWithoutNetworkstatus exception.
+ """
+ self.assertRaises(bridges.ServerDescriptorWithoutNetworkstatus,
+ self.bridge.updateFromServerDescriptor,
+ self.serverdescriptor)
+
+ def test_Bridge_updateFromServerDescriptor_ignoreNetworkstatus_no_networkstatus(self):
+ """Parsing a server descriptor for a bridge which wasn't included in
+ the networkstatus document from the BridgeAuthority, when
+ ignoreNetworkstatus=True, should not raise any warnings.
+ """
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor,
+ ignoreNetworkstatus=True)
+ self.assertIsNone(self.bridge.descriptors['networkstatus'])
+ self.assertIsNotNone(self.bridge.descriptors['server'])
+
+ def test_Bridge_verifyExtraInfoSignature_good_signature(self):
+ """Calling _verifyExtraInfoSignature() with a descriptor which has a
+ good signature should return None.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.assertIsNone(self.bridge._verifyExtraInfoSignature(self.extrainfo))
+
+ def test_Bridge_updateFromExtraInfoDescriptor(self):
+ """Bridge.updateFromExtraInfoDescriptor() should add the expected
+ number of pluggable transports.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+ self.assertEqual(self.bridge.bandwidthObserved, None)
+ self.assertEqual(len(self.bridge.transports), 0)
+
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+ self.assertEqual(self.bridge.bandwidthObserved, 1623207134)
+ self.assertEqual(len(self.bridge.transports), 0)
+
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+ self.assertEqual(self.bridge.fingerprint,
+ '2C3225C4805331025E211F4B6E5BF45C333FDD2C')
+ self.assertEqual(self.bridge.bandwidthObserved, 1623207134)
+ self.assertEqual(len(self.bridge.transports), 4)
+
+ def test_Bridge_updateFromExtraInfoDescriptor_bad_signature_changed(self):
+ """Calling updateFromExtraInfoDescriptor() with a descriptor which
+ has a bad signature should not continue to process the descriptor.
+ """
+ # Make the signature uppercased
+ BEGIN_SIG = '-----BEGIN SIGNATURE-----'
+ doc, sig = BRIDGE_EXTRAINFO.split(BEGIN_SIG)
+ ei = BEGIN_SIG.join([doc, sig.upper()])
+ self._writeExtrainfo(ei)
+ self._parseAllDescriptorFiles()
+
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertEqual(len(self.bridge.transports), 0)
+ self.assertIsNone(self.bridge.descriptors['extrainfo'])
+
+ def test_Bridge_updateFromExtraInfoDescriptor_pt_changed_port(self):
+ """Calling updateFromExtraInfoDescriptor() with a descriptor which
+ includes a different port for a known bridge with a known pluggable
+ transport should update that transport.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertEqual(len(self.bridge.transports), 4)
+
+ for pt in self.bridge.transports:
+ if pt.methodname == 'obfs4':
+ self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
+ self.assertEqual(pt.port, 36493)
+
+ # Change the port of obfs4 transport in the extrainfo descriptor:
+ transportline = self.extrainfo.transport['obfs4']
+ self.extrainfo.transport['obfs4'] = (transportline[0],
+ 31337,
+ transportline[2])
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ for pt in self.bridge.transports:
+ if pt.methodname == 'obfs4':
+ self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
+ self.assertEqual(pt.port, 31337)
+
+ def test_Bridge_updateFromExtraInfoDescriptor_pt_changed_args(self):
+ """Calling updateFromExtraInfoDescriptor() with a descriptor which
+ includes different PT args for a known bridge with a known pluggable
+ transport should update that transport.
+
+ scramblesuit 179.178.155.140:36492 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertEqual(len(self.bridge.transports), 4)
+
+ for pt in self.bridge.transports:
+ if pt.methodname == 'scramblesuit':
+ self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
+ self.assertEqual(pt.port, 36492)
+
+ # Change the args of scramblesuit transport in the extrainfo descriptor:
+ transportline = self.extrainfo.transport['scramblesuit']
+ self.extrainfo.transport['scramblesuit'] = (transportline[0],
+ transportline[1],
+ ['password=PASSWORD'])
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ for pt in self.bridge.transports:
+ if pt.methodname == 'scramblesuit':
+ self.assertEqual(pt.address, ipaddr.IPv4Address('179.178.155.140'))
+ self.assertEqual(pt.port, 36492)
+ self.assertEqual(pt.arguments['password'], 'PASSWORD')
+
+ def test_Bridge_updateFromExtraInfoDescriptor_pt_died(self):
+ """Calling updateFromExtraInfoDescriptor() with a descriptor which
+ doesn't include a previously-known transport should remove that
+ transport.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertEqual(len(self.bridge.transports), 4)
+
+ # Remove the obfs3 transport from the extrainfo descriptor:
+ self.extrainfo.transport.pop('obfs3')
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertEqual(len(self.bridge.transports), 3)
+
+ for pt in self.bridge.transports:
+ self.failIfEqual(pt.methodname, 'obfs3')
+
+ def test_Bridge_descriptorDigest(self):
+ """Parsing a networkstatus descriptor should result in
+ Bridge.descriptorDigest being set.
+ """
+ realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.assertEqual(self.bridge.descriptorDigest, realdigest)
+
+ def test_Bridge_checkServerDescriptor(self):
+ """Parsing a server descriptor when the bridge's networkstatus document
+ didn't have a digest of the server descriptor should raise a
+ MissingServerDescriptorDigest.
+ """
+ # Create a networkstatus descriptor without a server descriptor digest:
+ filename = self._networkstatusFile + "-missing-digest"
+ fh = open(filename, 'w')
+ invalid = BRIDGE_NETWORKSTATUS.replace("c4EVu2rO/iD/DJYBX/Ll38DGQWI", "foo")
+ fh.seek(0)
+ fh.write(invalid)
+ fh.flush()
+ fh.close()
+
+ realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
+
+ #networkstatus = descriptors.parseNetworkStatusFile(filename)
+ #self.bridge.updateFromNetworkStatus(networkstatus[0])
+ #self.assertRaises(bridges.MissingServerDescriptorDigest,
+ # self.bridge.updateFromNetworkStatus,
+ # networkstatus[0])
+
+ def test_Bridge_checkServerDescriptor_digest_mismatch_ns(self):
+ """Parsing a server descriptor whose digest doesn't match the one given
+ in the bridge's networkstatus document should raise a
+ ServerDescriptorDigestMismatch.
+ """
+ # Create a networkstatus descriptor without a server descriptor digest:
+ filename = self._networkstatusFile + "-mismatched-digest"
+ fh = open(filename, 'w')
+ invalid = BRIDGE_NETWORKSTATUS.replace("c4EVu2rO/iD/DJYBX/Ll38DGQWI",
+ "c4EVu2r1/iD/DJYBX/Ll38DGQWI")
+ fh.seek(0)
+ fh.write(invalid)
+ fh.flush()
+ fh.close()
+
+ realdigest = "738115BB6ACEFE20FF0C96015FF2E5DFC0C64162"
+ networkstatus = descriptors.parseNetworkStatusFile(filename)
+ self.bridge.updateFromNetworkStatus(networkstatus[0])
+ #self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ self.assertRaises(bridges.ServerDescriptorDigestMismatch,
+ self.bridge.updateFromServerDescriptor,
+ self.serverdescriptor)
+
+ def test_Bridge_checkServerDescriptor_digest_mismatch_sd(self):
+ """Parsing a server descriptor when the corresponding networkstatus
+ descriptor didn't include a server bridge.descriptorDigest that matches
+ should raise a ServerDescriptorDigestMismatch exception.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+
+ self.bridge.descriptorDigest = 'deadbeef'
+ self.assertRaises(bridges.ServerDescriptorDigestMismatch,
+ self.bridge._checkServerDescriptor,
+ self.serverdescriptor)
+
+ def test_Bridge_checkServerDescriptor_digest_missing(self):
+ """Parsing a server descriptor when the corresponding networkstatus
+ descriptor didn't include a server bridge.descriptorDigest should raise
+ a MissingServerDescriptorDigest exception.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+
+ self.bridge.descriptorDigest = None
+ self.assertRaises(bridges.MissingServerDescriptorDigest,
+ self.bridge._checkServerDescriptor,
+ self.serverdescriptor)
+
+ def test_Bridge_assertOK(self):
+ """If all orAddresses are okay, then assertOK() should return None."""
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ self.assertIsNone(self.bridge.assertOK())
+
+ def test_Bridge_assertOK_all_bad_values(self):
+ """If an orAddress has an IP address of 999.999.999.999 and a port of
+ -1 and claims to be IPv5, then everything about it is bad and it should
+ fail all the checks in assertOK(), then a MalformedBridgeInfo should be
+ raised.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ # All values are bad (even though IPv5 is a thing):
+ self.bridge.orAddresses.append(('999.999.999.999', -1, 5))
+ self.assertRaises(bridges.MalformedBridgeInfo, self.bridge.assertOK)
+
+ def test_Bridge_getBridgeLine_request_valid(self):
+ """Calling getBridgeLine with a valid request should return a bridge
+ line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNotNone(line)
+ self.assertIn('179.178.155.140:36489', line)
+ self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getBridgeLine_request_invalid(self):
+ """Calling getBridgeLine with an invalid request should return None."""
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(False)
+
+ self.assertIsNone(self.bridge.getBridgeLine(request))
+
+ def test_Bridge_getBridgeLine_no_vanilla_addresses(self):
+ """Calling getBridgeLine() on a Bridge without any vanilla addresses
+ should return None.
+ """
+ request = BridgeRequestBase()
+ request.isValid(True)
+
+ self.assertIsNone(self.bridge.getBridgeLine(request))
+
+ def test_Bridge_getBridgeLine_request_without_block_in_IR(self):
+ """Calling getBridgeLine() with a valid request for bridges not blocked
+ in Iran should return a bridge line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withoutBlockInCountry('IR')
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNotNone(line)
+ self.assertIn('179.178.155.140:36489', line)
+ self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getBridgeLine_blocked_and_request_without_block(self):
+ """Calling getBridgeLine() with a valid request for bridges not blocked in
+ Iran, when the bridge is completely blocked in Iran, shouldn't return
+ a bridge line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.bridge.setBlockedIn('ir')
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withoutBlockInCountry('IR')
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNone(line)
+
+ def test_Bridge_getBridgeLine_blocked_pt_and_request_without_block_pt(self):
+ """Calling getBridgeLine() with a valid request for obfs3 bridges not
+ blocked in Iran, when the obfs3 line is blocked in Iran, shouldn't
+ return a bridge line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.bridge.setBlockedIn('ir', methodname="obfs3")
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withoutBlockInCountry('IR')
+ request.withPluggableTransportType('obfs3')
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNone(line)
+
+ def test_Bridge_getBridgeLine_blocked_obfs3_and_request_without_block_obfs4(self):
+ """Calling getBridgeLine() with a valid request for obfs4 bridges not
+ blocked in Iran, when the obfs3 line is blocked in Iran, should return
+ a bridge line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.bridge.setBlockedIn('ir', methodname="obfs3")
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withoutBlockInCountry('IR')
+ request.withPluggableTransportType('obfs4')
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNotNone(line)
+ self.assertIn('obfs4', line)
+ self.assertIn('179.178.155.140:36493', line)
+ self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getBridgeLine_IPv6(self):
+ """Calling getBridgeLine() with a valid request for IPv6 bridges
+ should return a bridge line.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withIPv6()
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNotNone(line)
+ self.assertTrue(
+ line.startswith('[6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488'))
+ self.assertNotIn('179.178.155.140:36493', line)
+ self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getBridgeLine_IPv6_no_fingerprint(self):
+ """Calling getBridgeLine(includeFingerprint=False) with a valid request
+ for IPv6 bridges should return a bridge line without the fingerprint.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withIPv6()
+ line = self.bridge.getBridgeLine(request, includeFingerprint=False)
+
+ self.assertIsNotNone(line)
+ self.assertTrue(
+ line.startswith('[6bf3:806b:78cd:d4b4:f6a7:4ced:cfad:dad4]:36488'))
+ self.assertNotIn('179.178.155.140:36493', line)
+ self.assertNotIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getBridgeLine_obfs4(self):
+ """ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withPluggableTransportType('obfs4')
+ line = self.bridge.getBridgeLine(request)
+
+ self.assertIsNotNone(line)
+ self.assertIn('179.178.155.140:36493', line)
+ self.assertTrue(line.startswith('obfs4'))
+ self.assertIn('iat-mode', line)
+ self.assertIn('public-key', line)
+ self.assertIn('node-id', line)
+
+ def test_Bridge_getBridgeLine_obfs3_IPv6(self):
+ """Calling getBridgeLine() with a request for IPv6 obfs3 bridges (when
+ the Bridge doesn't have any) should raise a
+ PluggableTransportUnavailable exception.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withIPv6()
+ request.withPluggableTransportType('obfs3')
+
+ self.assertRaises(bridges.PluggableTransportUnavailable,
+ self.bridge.getBridgeLine,
+ request)
+
+ def test_Bridge_getBridgeLine_googlygooglybegone(self):
+ """Calling getBridgeLine() with a request for an unknown PT should
+ raise a PluggableTransportUnavailable exception.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ request.withPluggableTransportType('googlygooglybegone')
+
+ self.assertRaises(bridges.PluggableTransportUnavailable,
+ self.bridge.getBridgeLine,
+ request)
+
+ def test_Bridge_getBridgeLine_bridge_prefix(self):
+ """Calling getBridgeLine() with bridgePrefix=True should prefix the
+ returned bridge line with 'Bridge '.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ line = self.bridge.getBridgeLine(request, bridgePrefix=True)
+
+ self.assertIsNotNone(line)
+ self.assertIn('179.178.155.140:36489', line)
+ self.assertIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+ self.assertTrue(line.startswith('Bridge'))
+
+ def test_Bridge_getBridgeLine_no_include_fingerprint(self):
+ """Calling getBridgeLine() with includeFingerprint=False should return
+ a bridge line without a fingerprint.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ request = BridgeRequestBase()
+ request.isValid(True)
+ line = self.bridge.getBridgeLine(request, includeFingerprint=False)
+
+ self.assertIsNotNone(line)
+ self.assertIn('179.178.155.140:36489', line)
+ self.assertNotIn('2C3225C4805331025E211F4B6E5BF45C333FDD2C', line)
+
+ def test_Bridge_getNetworkstatusLastPublished(self):
+ """Calling getNetworkstatusLastPublished() should tell us the last
+ published time of the Bridge's server-descriptor.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+
+ published = self.bridge.getNetworkstatusLastPublished()
+ self.assertIsNotNone(published)
+ self.assertIsInstance(published, datetime.datetime)
+ self.assertEqual(str(published), '2014-12-22 21:51:27')
+
+ def test_Bridge_getDescriptorLastPublished(self):
+ """Calling getDescriptorLastPublished() should tell us the last
+ published time of the Bridge's server-descriptor.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ published = self.bridge.getDescriptorLastPublished()
+ self.assertIsNotNone(published)
+ self.assertIsInstance(published, datetime.datetime)
+ self.assertEqual(str(published), '2014-12-22 21:51:27')
+
+ def test_Bridge_getExtrainfoLastPublished(self):
+ """Calling getNetworkstatusLastPublished() should tell us the last
+ published time of the Bridge's server-descriptor.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ published = self.bridge.getExtrainfoLastPublished()
+ self.assertIsNotNone(published)
+ self.assertIsInstance(published, datetime.datetime)
+ self.assertEqual(str(published), '2014-12-22 21:51:27')
+
+ def test_Bridge_isBlockedIn_IS(self):
+ """Calling isBlockedIn('IS') should return False when the bridge isn't
+ blocked in Iceland.
+ """
+ self.assertFalse(self.bridge.isBlockedIn('IS'))
+
+ def test_Bridge_setBlockedIn_CN_obfs2(self):
+ """Calling setBlockedIn('CN', 'obfs2') should mark all obfs2 transports
+ of the bridge as being blocked in CN.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.bridge.setBlockedIn('CN', methodname='obfs2')
+ self.assertTrue(self.bridge.isBlockedIn('CN'))
+
+ def test_Bridge_setBlockedIn_IR_address(self):
+ """Calling setBlockedIn('IR', address) should mark all matching
+ addresses of the bridge as being blocked in IR.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.bridge.setBlockedIn('IR', address='179.178.155.140')
+ self.assertTrue(self.bridge.isBlockedIn('ir'))
+ self.assertFalse(self.bridge.isBlockedIn('cn'))
+
+ def test_Bridge_setBlockedIn_GB_address_port(self):
+ """Calling setBlockedIn('GB', address, port) should mark all matching
+ addresses:port pairs of the bridge as being blocked in GB.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ # Should block the obfs4 bridge:
+ self.bridge.setBlockedIn('GB', address='179.178.155.140', port=36493)
+ self.assertTrue(self.bridge.isBlockedIn('GB'))
+ self.assertTrue(self.bridge.isBlockedIn('gb'))
+ self.assertTrue(self.bridge.transportIsBlockedIn('GB', 'obfs4'))
+ self.assertTrue(self.bridge.addressIsBlockedIn('GB', '179.178.155.140', 36493))
+ self.assertFalse(self.bridge.addressIsBlockedIn('gb', '179.178.155.140', 36488))
+
+ def test_Bridge_updateFromExtraInfoDescriptor_changed_no_verify(self):
+ """A changed extrainfo descriptor should log that a transport's
+ IP and/or port changed.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ changedExtrainfo = BRIDGE_EXTRAINFO
+ changedExtrainfo.replace('transport obfs3 179.178.155.140:36490',
+ 'transport obfs3 179.178.155.14:3649')
+ self._writeExtrainfo(changedExtrainfo)
+
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo, verify=False)
+
+ def test_Bridge_updateFromExtraInfoDescriptor_changed_verify(self):
+ """A changed extrainfo descriptor with verify=True should raise an
+ InvalidExtraInfoSignature exception.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfoNew)
+
+ # We should have hit the return just after the
+ # `except InvalidExtraInfoSignature` line, and so the
+ # bridge.descriptors['extrainfo'] shouldn't have been updated.
+ # Therefore, the one we stored should be older, that is, we shouldn't
+ # have kept the new one.
+ self.assertLess(self.bridge.descriptors['extrainfo'].published,
+ self.extrainfoNew.published)
+ # And the one we stored should be the older one, with the same
+ # published timestamp:
+ self.assertEqual(self.bridge.descriptors['extrainfo'], self.extrainfo)
+ self.assertEqual(self.bridge.descriptors['extrainfo'].published,
+ self.extrainfo.published)
+
+ def test_Bridge_updateFromExtraInfoDescriptor_obfs4_no_iatmode(self):
+ """An extrainfo descriptor with an obfs4 transport missing the
+ `iat-mode=[â¦]` argument should not add the obfs4 transport.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ obfs4 = self.extrainfo.transport['obfs4']
+ ptargs = [str(obfs4[-1][-1].replace('iat-mode=0', '')),]
+ obfs4 = (u'1.1.1.1', 1111, ptargs)
+
+ self.extrainfo.transport['obfs4'] = obfs4
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertTrue(len(self.bridge.transports), 3)
+ self.assertNotIn('obfs4',
+ [pt.methodname for pt in self.bridge.transports])
+
+ def test_Bridge_updateFromExtraInfoDescriptor_scramblesuit_no_password(self):
+ """An extrainfo descriptor with `transport scramblesuit 1.1.1.1:1111`
+ (i.e. missing the `password=[â¦]` argument) should not add the
+ scramblesuit transport.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+
+ self.extrainfo.transport['scramblesuit'] = (u'1.1.1.1', 1111, [])
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertTrue(len(self.bridge.transports), 3)
+ self.assertNotIn('scramblesuit',
+ [pt.methodname for pt in self.bridge.transports])
+
+ def test_Bridge_updateFromExtraInfoDescriptor_changed_scramblesuit_no_password(self):
+ """An extrainfo descriptor whose scramblesuit transport was previously
+ valid and is now missing the `password=[â¦]` argument should be removed
+ from the Bridge.transports list.
+ """
+ self.bridge.updateFromNetworkStatus(self.networkstatus)
+ self.bridge.updateFromServerDescriptor(self.serverdescriptor)
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertTrue(len(self.bridge.transports), 4)
+ self.assertIn('scramblesuit',
+ [pt.methodname for pt in self.bridge.transports])
+
+ self.extrainfo.transport['scramblesuit'] = (u'1.1.1.1', 1111, [])
+ self.bridge.updateFromExtraInfoDescriptor(self.extrainfo)
+
+ self.assertTrue(len(self.bridge.transports), 3)
+ self.assertNotIn('scramblesuit',
+ [pt.methodname for pt in self.bridge.transports])
diff --git a/test/test_captcha.py b/test/test_captcha.py
new file mode 100644
index 0000000..1000477
--- /dev/null
+++ b/test/test_captcha.py
@@ -0,0 +1,337 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.captcha` module."""
+
+
+import os
+import shutil
+import time
+
+from base64 import urlsafe_b64decode
+
+from twisted.trial import unittest
+
+from zope.interface import implementedBy
+from zope.interface import providedBy
+
+from bridgedb import captcha
+from bridgedb import crypto
+
+
+class CaptchaTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.captcha.Captcha`."""
+
+ def test_implementation(self):
+ """Captcha class should implement ICaptcha interface."""
+ self.assertTrue(captcha.ICaptcha.implementedBy(captcha.Captcha))
+
+ def test_provider(self):
+ """ICaptcha should be provided by instances of Captcha."""
+ c = captcha.Captcha()
+ self.assertTrue(captcha.ICaptcha.providedBy(c))
+
+ def test_get(self):
+ """Captcha.get() should return None."""
+ c = captcha.Captcha()
+ self.assertIsNone(c.get())
+
+
+class ReCaptchaTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.captcha.ReCaptcha`."""
+
+ def setUp(self):
+ self.c = captcha.ReCaptcha('publik', 'sekrit')
+
+ def test_init(self):
+ """Check the ReCaptcha class stored the private and public keys."""
+ self.assertEquals(self.c.secretKey, 'sekrit')
+ self.assertEquals(self.c.publicKey, 'publik')
+
+ def test_get(self):
+ """Test get() method."""
+
+ # Force urllib2 to do anything less idiotic than the defaults:
+ envkey = 'HTTPS_PROXY'
+ oldkey = None
+ if os.environ.has_key(envkey):
+ oldkey = os.environ[envkey]
+ os.environ[envkey] = '127.0.0.1:9150'
+ # This stupid thing searches the environment for ``<protocol>_PROXY``
+ # variables, hence the above 'HTTPS_PROXY' env setting:
+ proxy = captcha.urllib2.ProxyHandler()
+ opener = captcha.urllib2.build_opener(proxy)
+ captcha.urllib2.install_opener(opener)
+
+ try:
+ # There isn't really a reliable way to test this function! :(
+ self.c.get()
+ except Exception as error:
+ reason = "ReCaptcha.get() test requires an active network "
+ reason += "connection.\nThis test failed with: %s" % error
+ raise unittest.SkipTest(reason)
+ else:
+ self.assertIsInstance(self.c.image, basestring)
+ self.assertIsInstance(self.c.challenge, basestring)
+ finally:
+ # Replace the original environment variable if there was one:
+ if oldkey:
+ os.environ[envkey] = oldkey
+ else:
+ os.environ.pop(envkey)
+
+ def test_get_noKeys(self):
+ """ReCaptcha.get() without API keys should fail."""
+ c = captcha.ReCaptcha()
+ self.assertRaises(captcha.CaptchaKeyError, c.get)
+
+
+class GimpCaptchaTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.captcha.GimpCaptcha`."""
+
+ def setUp(self):
+ here = os.getcwd()
+ self.topDir = here.rstrip('_trial_temp')
+ self.cacheDir = os.path.join(self.topDir, 'captchas')
+ self.badCacheDir = os.path.join(here, 'capt')
+
+ # Get keys for testing or create them:
+ self.sekrit, self.publik = crypto.getRSAKey('test_gimpCaptcha_RSAkey')
+ self.hmacKey = crypto.getKey('test_gimpCaptcha_HMACkey')
+
+ def test_init_noSecretKey(self):
+ """Calling GimpCaptcha.__init__() without a secret key parameter should raise
+ a CaptchaKeyError.
+ """
+ self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
+ self.publik, None, self.hmacKey, self.cacheDir)
+
+ def test_init_noPublicKey(self):
+ """__init__() without publicKey should raise a CaptchaKeyError."""
+ self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
+ None, self.sekrit, self.hmacKey, self.cacheDir)
+
+ def test_init_noHMACKey(self):
+ """__init__() without hmacKey should raise a CaptchaKeyError."""
+ self.assertRaises(captcha.CaptchaKeyError, captcha.GimpCaptcha,
+ self.publik, self.sekrit, None, self.cacheDir)
+
+ def test_init_noCacheDir(self):
+ """__init__() without cacheDir should raise a CaptchaKeyError."""
+ self.assertRaises(captcha.GimpCaptchaError, captcha.GimpCaptcha,
+ self.publik, self.sekrit, self.hmacKey, None)
+
+ def test_init_badCacheDir(self):
+ """GimpCaptcha with bad cacheDir should raise GimpCaptchaError."""
+ self.assertRaises(captcha.GimpCaptchaError, captcha.GimpCaptcha,
+ self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir.rstrip('chas'))
+
+ def test_init(self):
+ """Test that __init__ correctly initialised all the values."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ self.assertIsNone(c.answer)
+ self.assertIsNone(c.image)
+ self.assertIsNone(c.challenge)
+
+ def test_createChallenge(self):
+ """createChallenge() should return the encrypted CAPTCHA answer."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ challenge = c.createChallenge('w00t')
+ self.assertIsInstance(challenge, basestring)
+
+ def test_createChallenge_base64(self):
+ """createChallenge() return value should be urlsafe base64-encoded."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ challenge = c.createChallenge('w00t')
+ decoded = urlsafe_b64decode(challenge)
+ self.assertTrue(decoded)
+
+ def test_createChallenge_hmacValid(self):
+ """The HMAC in createChallenge() return value should be valid."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ challenge = c.createChallenge('ShouldHaveAValidHMAC')
+ decoded = urlsafe_b64decode(challenge)
+ hmac = decoded[:20]
+ orig = decoded[20:]
+ correctHMAC = crypto.getHMAC(self.hmacKey, orig)
+ self.assertEquals(hmac, correctHMAC)
+
+ def test_createChallenge_decryptedAnswerMatches(self):
+ """The HMAC in createChallenge() return value should be valid."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ challenge = c.createChallenge('ThisAnswerShouldDecryptToThis')
+ decoded = urlsafe_b64decode(challenge)
+ hmac = decoded[:20]
+ orig = decoded[20:]
+ correctHMAC = crypto.getHMAC(self.hmacKey, orig)
+ self.assertEqual(hmac, correctHMAC)
+
+ decrypted = self.sekrit.decrypt(orig)
+ timestamp = int(decrypted[:12].lstrip('0'))
+ # The timestamp should be within 30 seconds of right now.
+ self.assertApproximates(timestamp, int(time.time()), 30)
+ self.assertEqual('ThisAnswerShouldDecryptToThis', decrypted[12:])
+
+ def test_get(self):
+ """GimpCaptcha.get() should return image and challenge strings."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ self.assertIsInstance(image, basestring)
+ self.assertIsInstance(challenge, basestring)
+
+ def test_get_emptyCacheDir(self):
+ """An empty cacheDir should raise GimpCaptchaError."""
+ os.makedirs(self.badCacheDir)
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.badCacheDir)
+ self.assertRaises(captcha.GimpCaptchaError, c.get)
+ shutil.rmtree(self.badCacheDir)
+
+ def test_get_unreadableCaptchaFile(self):
+ """An unreadable CAPTCHA file should raise GimpCaptchaError."""
+ os.makedirs(self.badCacheDir)
+ badFile = os.path.join(self.badCacheDir, 'uNr34dA81e.jpg')
+ with open(badFile, 'w') as fh:
+ fh.write(' ')
+ fh.flush()
+ os.chmod(badFile, 0266)
+
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.badCacheDir)
+ # This should hit the second `except:` clause in get():
+ self.assertRaises(captcha.GimpCaptchaError, c.get)
+ shutil.rmtree(self.badCacheDir)
+
+ def test_check(self):
+ """A correct answer and valid challenge should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ self.assertEquals(
+ c.check(challenge, c.answer, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_blankAnswer(self):
+ """A blank answer and valid challenge should return False."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ self.assertEquals(
+ c.check(challenge, None, c.secretKey, c.hmacKey),
+ False)
+
+ def test_check_nonBase64(self):
+ """Valid answer and challenge with invalid base64 returns False."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ challengeBadB64 = challenge.rstrip('==') + "\x42\x42\x42"
+ self.assertEquals(
+ c.check(challenge, c.answer, c.secretKey, c.hmacKey),
+ True)
+ self.assertEquals(
+ c.check(challengeBadB64, c.answer, c.secretKey, c.hmacKey),
+ False)
+
+ def test_check_caseInsensitive_lowercase(self):
+ """A correct answer in lowercase characters should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ solution = c.answer.lower()
+ self.assertEquals(
+ c.check(challenge, solution, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_caseInsensitive_uppercase(self):
+ """A correct answer in uppercase characters should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ solution = c.answer.upper()
+ self.assertEquals(
+ c.check(challenge, solution, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_encoding_utf8(self):
+ """A correct answer in utf-8 lowercase should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ solution = c.answer.encode('utf8')
+ self.assertEquals(
+ c.check(challenge, solution, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_encoding_ascii(self):
+ """A correct answer in utf-8 lowercase should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ solution = c.answer.encode('ascii')
+ self.assertEquals(
+ c.check(challenge, solution, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_encoding_unicode(self):
+ """A correct answer in utf-8 lowercase should return True."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ solution = unicode(c.answer)
+ self.assertEquals(
+ c.check(challenge, solution, c.secretKey, c.hmacKey),
+ True)
+
+ def test_check_missingHMACbytes(self):
+ """A challenge that is missing part of the HMAC should return False."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ challengeBadHMAC = challenge[:10] + challenge[20:]
+ self.assertEquals(
+ c.check(challengeBadHMAC, c.answer, c.secretKey, c.hmacKey),
+ False)
+
+ def test_check_missingAnswerbytes(self):
+ """Partial encrypted answers in challenges should return False."""
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ challengeBadOrig = challenge[:20] + challenge[30:]
+ self.assertEquals(
+ c.check(challengeBadOrig, c.answer, c.secretKey, c.hmacKey),
+ False)
+
+ def test_check_badHMACkey(self):
+ """A challenge with a bad HMAC key should return False."""
+ hmacKeyBad = crypto.getKey('test_gimpCaptcha_badHMACkey')
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ self.assertEquals(
+ c.check(challenge, c.answer, c.secretKey, hmacKeyBad),
+ False)
+
+ def test_check_badRSAkey(self):
+ """A challenge with a bad RSA secret key should return False."""
+ secretKeyBad, publicKeyBad = crypto.getRSAKey('test_gimpCaptcha_badRSAkey')
+ c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
+ self.cacheDir)
+ image, challenge = c.get()
+ self.assertEquals(
+ c.check(challenge, c.answer, secretKeyBad, c.hmacKey),
+ False)
diff --git a/test/test_configure.py b/test/test_configure.py
new file mode 100644
index 0000000..ebac95a
--- /dev/null
+++ b/test/test_configure.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: please see the AUTHORS file for attributions
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""Tests for :mod:`bridgedb.configure`."""
+
+from __future__ import print_function
+
+import os
+
+from twisted.trial import unittest
+
+from bridgedb import configure
+
+
+class ConfigureTests(unittest.TestCase):
+ """Tests for miscelaneous functions in :mod:`bridgedb.configure`."""
+
+ def setUp(self):
+ """Find the config file in the top directory of this repo."""
+ here = os.getcwd()
+ topdir = here.rstrip('_trial_temp')
+ self.configFilename = os.path.join(topdir, 'bridgedb.conf')
+
+ def test_loadConfig_with_file(self):
+ """We should be able to load and parse the standard ``bridgedb.conf``
+ file from the top directory of this repository.
+ """
+ config = configure.loadConfig(self.configFilename)
+ self.assertTrue(config)
+
+ def test_loadConfig_with_file_and_class(self):
+ """We should be able to reload and parse the ``bridgedb.conf``
+ file, if we have a config class as well.
+ """
+ config = configure.loadConfig(self.configFilename)
+ newConfig = configure.loadConfig(self.configFilename, configCls=config)
+ self.assertTrue(newConfig)
+
+ def test_loadConfig_with_class(self):
+ """We should be able to recreate a config, given its class."""
+ config = configure.loadConfig(self.configFilename)
+ newConfig = configure.loadConfig(configCls=config)
+ self.assertTrue(newConfig)
+
+ def test_loadConfig_set_EXTRA_INFO_FILES_when_None(self):
+ """If certain options, like the ``EXTRA_INFO_FILES`` option in the
+ config file weren't set, they should be made into lists so that our
+ parsers don't choke on them later.
+ """
+ config = configure.loadConfig(self.configFilename)
+ setattr(config, "EXTRA_INFO_FILES", None)
+ self.assertTrue(config.EXTRA_INFO_FILES is None)
+ newConfig = configure.loadConfig(configCls=config)
+ self.assertIsInstance(newConfig.EXTRA_INFO_FILES, list)
+
+ def test_loadConfig_returns_Conf(self):
+ """After loading and parsing the ``bridgedb.conf`` file, we should have
+ a :class:`bridgedb.configure.Conf`.
+ """
+ config = configure.loadConfig(self.configFilename)
+ self.assertIsInstance(config, configure.Conf)
diff --git a/test/test_crypto.py b/test/test_crypto.py
new file mode 100644
index 0000000..3264ace
--- /dev/null
+++ b/test/test_crypto.py
@@ -0,0 +1,392 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for :mod:`bridgedb.crypto`."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import base64
+import io
+import logging
+import math
+import os
+import shutil
+
+import OpenSSL
+
+from twisted import version as _twistedversion
+from twisted.internet import defer
+from twisted.python.versions import Version
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+from twisted.web.test import test_agent as txtagent
+
+from bridgedb import crypto
+from bridgedb import txrecaptcha
+from bridgedb.persistent import Conf
+from bridgedb.test.util import fileCheckDecorator
+from bridgedb.test.email_helpers import _createConfig
+
+
+logging.disable(50)
+
+SEKRIT_KEY = b'v\x16Xm\xfc\x1b}\x063\x85\xaa\xa5\xf9\xad\x18\xb2P\x93\xc6k\xf9'
+SEKRIT_KEY += b'\x8bI\xd9\xb8xw\xf5\xec\x1b\x7f\xa8'
+
+
+class DummyEndpoint(object):
+ """An endpoint that uses a fake transport."""
+
+ def connect(self, factory):
+ """Returns a connection to a
+ :api:`twisted.test.proto_helpers.StringTransport`.
+ """
+ protocol = factory.buildProtocol(None)
+ protocol.makeConnection(StringTransport())
+ return defer.succeed(protocol)
+
+
+class GetKeyTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.crypto.getKey`."""
+
+ def test_getKey_nokey(self):
+ """Test retrieving the secret_key from an empty file."""
+ filename = os.path.join(os.getcwd(), 'sekrit')
+ key = crypto.getKey(filename)
+ self.failUnlessIsInstance(key, basestring,
+ "key isn't a string! type=%r" % type(key))
+
+ def test_getKey_tmpfile(self):
+ """Test retrieving the secret_key from a new tmpfile."""
+ filename = self.mktemp()
+ key = crypto.getKey(filename)
+ self.failUnlessIsInstance(key, basestring,
+ "key isn't a string! type=%r" % type(key))
+
+ def test_getKey_keyexists(self):
+ """Write the example key to a file and test reading it back."""
+ filename = self.mktemp()
+ with open(filename, 'wb') as fh:
+ fh.write(SEKRIT_KEY)
+ fh.flush()
+
+ key = crypto.getKey(filename)
+ self.failUnlessIsInstance(key, basestring,
+ "key isn't a string! type=%r" % type(key))
+ self.assertEqual(SEKRIT_KEY, key,
+ """The example key and the one read from file differ!
+ key (in hex): %s
+ SEKRIT_KEY (in hex): %s"""
+ % (key.encode('hex'), SEKRIT_KEY.encode('hex')))
+
+
+class InitializeGnuPGTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.crypto.initializeGnupG`."""
+
+ def _moveGnuPGHomedir(self):
+ """Move the .gnupg/ directory from the top-level of this repo to the
+ current working directory.
+
+ :rtype: str
+ :returns: The full path to the new gnupg home directory.
+ """
+ here = os.getcwd()
+ topDir = here.rstrip('_trial_temp')
+ gnupghome = os.path.join(topDir, '.gnupg')
+ gnupghomeNew = os.path.join(here, '.gnupg')
+
+ if os.path.isdir(gnupghomeNew):
+ shutil.rmtree(gnupghomeNew)
+
+ shutil.copytree(gnupghome, gnupghomeNew)
+
+ return gnupghomeNew
+
+ def _writePassphraseToFile(self, passphrase, filename):
+ """Write **passphrase** to the file at **filename**.
+
+ :param str passphrase: The GnuPG passphase.
+ :param str filename: The file to write the passphrase to.
+ """
+ fh = open(filename, 'w')
+ fh.write(passphrase)
+ fh.flush()
+ fh.close()
+
+ def setUp(self):
+ """Create a config object and setup our gnupg home directory."""
+ self.config = _createConfig()
+ self.gnupghome = self._moveGnuPGHomedir()
+ self.config.EMAIL_GPG_HOMEDIR = self.gnupghome
+
+ self.passphraseFile = 'gpg-passphrase-file'
+ self._writePassphraseToFile('sekrit', self.passphraseFile)
+
+ def test_crypto_initializeGnuPG(self):
+ """crypto.initializeGnuPG() should return a 2-tuple with a gpg object
+ and a signing function.
+ """
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNotNone(gpg)
+ self.assertIsNotNone(signfunc)
+
+ def test_crypto_initializeGnuPG_disabled(self):
+ """When EMAIL_GPG_SIGNING_ENABLED=False, crypto.initializeGnuPG()
+ should return a 2-tuple of None.
+ """
+ self.config.EMAIL_GPG_SIGNING_ENABLED = False
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+
+ self.assertIsNone(gpg)
+ self.assertIsNone(signfunc)
+
+ def test_crypto_initializeGnuPG_no_secrets(self):
+ """When the secring.gpg is missing, crypto.initializeGnuPG() should
+ return a 2-tuple of None.
+ """
+ secring = os.path.join(self.gnupghome, 'secring.gpg')
+ if os.path.isfile(secring):
+ os.remove(secring)
+
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNone(gpg)
+ self.assertIsNone(signfunc)
+
+ def test_crypto_initializeGnuPG_no_publics(self):
+ """When the pubring.gpg is missing, crypto.initializeGnuPG() should
+ return a 2-tuple of None.
+ """
+ pubring = os.path.join(self.gnupghome, 'pubring.gpg')
+ if os.path.isfile(pubring):
+ os.remove(pubring)
+
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNone(gpg)
+ self.assertIsNone(signfunc)
+
+ def test_crypto_initializeGnuPG_with_passphrase(self):
+ """crypto.initializeGnuPG() should initialize correctly when a
+ passphrase is given but no passphrase is needed.
+ """
+ self.config.EMAIL_GPG_PASSPHRASE = 'password'
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNotNone(gpg)
+ self.assertIsNotNone(signfunc)
+
+ def test_crypto_initializeGnuPG_with_passphrase_file(self):
+ """crypto.initializeGnuPG() should initialize correctly when a
+ passphrase file is given but no passphrase is needed.
+ """
+ self.config.EMAIL_GPG_PASSPHRASE_FILE = self.passphraseFile
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNotNone(gpg)
+ self.assertIsNotNone(signfunc)
+
+ def test_crypto_initializeGnuPG_missing_passphrase_file(self):
+ """crypto.initializeGnuPG() should initialize correctly if a passphrase
+ file is given but that file is missing (when no passphrase is actually
+ necessary).
+ """
+ self.config.EMAIL_GPG_PASSPHRASE_FILE = self.passphraseFile
+ os.remove(self.passphraseFile)
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNotNone(gpg)
+ self.assertIsNotNone(signfunc)
+
+ def test_crypto_initializeGnuPG_signingFunc(self):
+ """crypto.initializeGnuPG() should return a signing function which
+ produces OpenPGP signatures.
+ """
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNotNone(gpg)
+ self.assertIsNotNone(signfunc)
+
+ sig = signfunc("This is a test of the public broadcasting system.")
+ print(sig)
+ self.assertIsNotNone(sig)
+ self.assertTrue(sig.startswith('-----BEGIN PGP SIGNED MESSAGE-----'))
+
+ def test_crypto_initializeGnuPG_nonexistent_default_key(self):
+ """When the key specified by EMAIL_GPG_PRIMARY_KEY_FINGERPRINT doesn't
+ exist in the keyrings, crypto.initializeGnuPG() should return a 2-tuple
+ of None.
+ """
+ self.config.EMAIL_GPG_PRIMARY_KEY_FINGERPRINT = 'A' * 40
+ gpg, signfunc = crypto.initializeGnuPG(self.config)
+ self.assertIsNone(gpg)
+ self.assertIsNone(signfunc)
+
+
+class RemovePKCS1PaddingTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.crypto.removePKCS1Padding`."""
+
+ def setUp(self):
+ """This blob *is* actually a correctly formed PKCS#1 padded signature
+ on the descriptor::
+
+ @purpose bridge
+ router ExhalesPeppier 118.16.116.176 35665 0 0
+ or-address [eef2:d52a:cf1b:552f:375d:f8d0:a72b:e794]:35664
+ platform Tor 0.2.4.5-alpha on Linux
+ protocols Link 1 2 Circuit 1
+ published 2014-11-03 21:21:43
+ fingerprint FA04 5CFF AB95 BA20 C994 FE28 9B23 583E F80F 34DA
+ uptime 10327748
+ bandwidth 2247108152 2540209215 1954007088
+ extra-info-digest 571BF23D8F24F052483C1333EBAE9B91E4A6F422
+ onion-key
+ -----BEGIN RSA PUBLIC KEY-----
+ MIGJAoGBAK7+a033aUqc97SWFVGFwR3ybQ0jG1HTPtsv2/fUfZPwCaf21ly4zIvH
+ 9uNhtkcPH2p55X+n5M7OUaQawOzbwL4tSR9SLy9bGuZdWLbhu2GHQWmDkAB7BtHp
+ UC+uGTN3jvQXEG2xlzpb+lOVUVNXLhL5kFmAXxL+iwN4TeEv/iCnAgMBAAE=
+ -----END RSA PUBLIC KEY-----
+ signing-key
+ -----BEGIN RSA PUBLIC KEY-----
+ MIGJAoGBANxmgJ6S3rBAGcvQu2tWBaHByJxeJkdGbxID2b8cITPaNmcl72e3Kd44
+ GGIkoKhkX0SAO+i2U+Q41u/DPEBWLxhpl9GAFJZ10dcT18lL36yaK6FRDOcF9jx9
+ 0A023/kwXd7QQDWqP7Fso+141bzit6ENvNmE1mvEeIoAR+EpJB1tAgMBAAE=
+ -----END RSA PUBLIC KEY-----
+ contact Somebody <somebody at example.com>
+ ntor-onion-key 0Mfi/Af7zLmdNdrmJyPbZxPJe7TZU/hV4Z865g3g+k4
+ reject *:*
+ router-signature
+ -----BEGIN SIGNATURE-----
+ PsGGIP+V9ZXWIHjK943CMAPem3kFbO9kt9rvrPhd64u0f7ytB/qZGaOg1IEWki1I
+ f6ZNjrthxicm3vnEUdhpRsyn7MUFiQmqLjBfqdzh0GyfrtU5HHr7CBV3tuhgVhik
+ uY1kPNo1C8wkmuy31H3V7NXj+etZuzZN66qL3BiQwa8=
+ -----END SIGNATURE-----
+
+ However, for the blob to be valid it would need to be converted from
+ base64-decoded bytes to a long, then raised by the power of the public
+ exponent within the ASN.1 DER decoded signing-key (mod that key's
+ public modulus), then re-converted back into bytes before attempting
+ to remove the PKCS#1 padding. (See
+ :meth:`bridedb.bridges.Bridge._verifyExtraInfoSignature`.)
+ """
+ blob = ('PsGGIP+V9ZXWIHjK943CMAPem3kFbO9kt9rvrPhd64u0f7ytB/qZGaOg1IEWk'
+ 'i1If6ZNjrthxicm3vnEUdhpRsyn7MUFiQmqLjBfqdzh0GyfrtU5HHr7CBV3tu'
+ 'hgVhikuY1kPNo1C8wkmuy31H3V7NXj+etZuzZN66qL3BiQwa8=')
+ self.blob = base64.b64decode(blob)
+
+ def test_crypto_removePKCS1Padding_bad_padding(self):
+ """removePKCS1Padding() with a blob with a bad PKCS#1 identifier mark
+ should raise PKCS1PaddingError.
+ """
+ self.assertRaises(crypto.PKCS1PaddingError,
+ crypto.removePKCS1Padding,
+ self.blob)
+
+ def test_crypto_removePKCS1Padding_missing_padding(self):
+ """removePKCS1Padding() with a blob with a missing PKCS#1 identifier
+ mark should raise PKCS1PaddingError.
+ """
+ self.assertRaises(crypto.PKCS1PaddingError,
+ crypto.removePKCS1Padding,
+ b'\x99' + self.blob)
+
+
+class SSLVerifyingContextFactoryTests(unittest.TestCase,
+ txtagent.FakeReactorAndConnectMixin):
+ """Tests for :class:`bridgedb.crypto.SSLVerifyingContextFactory`."""
+
+ _certificateText = (
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIEdjCCA16gAwIBAgIITcyHZlE/AhQwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE\n"
+ "BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl\n"
+ "cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMjEyMTUxMTE2WhcNMTQwNjEyMDAwMDAw\n"
+ "WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN\n"
+ "TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3\n"
+ "Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt3TOf\n"
+ "VOf4vfy4IROcEyiFzAJA+B3xkMccwA4anaD6VyGSFglRn5Oht3t+G0Mnu/LMuGba\n"
+ "EE6NEBEUEbH8KMlAcVRj58LoFIzulaRCdkVX7JK9R+kU05sggvIl1Q2quaWSjiMQ\n"
+ "SpyvKz1I2cmU5Gm4MfW/66M5ZJO323VrV19ydrgAtdbNnvVj85asrSyzwEBNxzNC\n"
+ "N6OQtOmTt4I7KLXqkROtTmTFvhAGBsvhG0hJZWhoP1aVsFO+KcE2OaIIxWQ4ckW7\n"
+ "BJEgYaXfgHo01LdR55aevGUqLfsdyT+GMZrG9k7eqAw4cq3ML2Y6RiyzskqoQL30\n"
+ "3OdYjKTIcU+i3BoFAgMBAAGjggFBMIIBPTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI\n"
+ "KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE\n"
+ "XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0\n"
+ "MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G\n"
+ "A1UdDgQWBBQN7uQBzGDjvKRna111g9iPPtaXVTAMBgNVHRMBAf8EAjAAMB8GA1Ud\n"
+ "IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMBcGA1UdIAQQMA4wDAYKKwYBBAHW\n"
+ "eQIFATAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lB\n"
+ "RzIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBrVp/xys2ABQvWPxpVrYaXiaoBXdxu\n"
+ "RVVXp5Lyu8IipKqFJli81hOX9eqPG7biYeph9HiKnW31xsXebaVlWWL3NXOh5X83\n"
+ "wpzozL0AkxskTMHQknrbIGLtmG67H71aKYyCthHEjawLmYjjvkcF6f9fKdYENM4C\n"
+ "skz/yjtlPBQFAuT6J9w0b3qtc42sHNlpgIOdIRQc2YCD0p6jAo+wKjoRuRu3ILKj\n"
+ "oCVrOPbDMPN4a2gSmK8Ur0aHuEpcNghg6HJsVSANokIIwQ/r4niqL5yotsangP/5\n"
+ "rR97EIYKFz7C6LMy/PIe8xFTIyKMtM59IcpUDIwCLlM9JtNdwN4VpyKy\n"
+ "-----END CERTIFICATE-----\n")
+
+ def setUp(self):
+ """Create a fake reactor for these tests."""
+ self.reactor = self.Reactor()
+ self.url = 'https://www.example.com/someresource.html#andatag'
+
+ def test_getHostnameFromURL(self):
+ """``getHostnameFromURL()`` should return a hostname from a URI."""
+ if _twistedversion >= Version('twisted', 14, 0, 0):
+ raise unittest.SkipTest(
+ ("The SSLVerifyingContextFactory is no longer necessary in "
+ "Twisted>=14.0.0, because the way in which TLS certificates "
+ "are checked now includes certificate pinning, and the "
+ "SSLVerifyingContextFactory only implemented strict hostname "
+ "checking."))
+
+ agent = txrecaptcha._getAgent(self.reactor, self.url)
+ contextFactory = agent._contextFactory
+ self.assertRegexpMatches(contextFactory.hostname,
+ '.*www\.example\.com')
+
+ def test_verifyHostname_mismatching(self):
+ """Check that ``verifyHostname()`` returns ``False`` when the
+ ``SSLVerifyingContextFactory.hostname`` does not match the one found
+ in the level 0 certificate subject CN.
+ """
+ if _twistedversion >= Version('twisted', 14, 0, 0):
+ raise unittest.SkipTest(
+ ("The SSLVerifyingContextFactory is no longer necessary in "
+ "Twisted>=14.0.0, because the way in which TLS certificates "
+ "are checked now includes certificate pinning, and the "
+ "SSLVerifyingContextFactory only implemented strict hostname "
+ "checking."))
+
+ agent = txrecaptcha._getAgent(self.reactor, self.url)
+ contextFactory = agent._contextFactory
+ x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
+ self._certificateText)
+ conn = DummyEndpoint()
+ result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
+ self.assertIs(result, False)
+
+ def test_verifyHostname_matching(self):
+ """Check that ``verifyHostname()`` returns ``True`` when the
+ ``SSLVerifyingContextFactory.hostname`` matches the one found in the
+ level 0 certificate subject CN.
+ """
+ hostname = 'www.google.com'
+ url = 'https://' + hostname + '/recaptcha'
+ contextFactory = crypto.SSLVerifyingContextFactory(url)
+ self.assertEqual(contextFactory.hostname, hostname)
+
+ x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
+ self._certificateText)
+ conn = DummyEndpoint()
+ result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
+ self.assertTrue(result)
+
+ def test_getContext(self):
+ """The context factory's ``getContext()`` method should produce an
+ ``OpenSSL.SSL.Context`` object.
+ """
+ contextFactory = crypto.SSLVerifyingContextFactory(self.url)
+ self.assertIsInstance(contextFactory.getContext(),
+ OpenSSL.SSL.Context)
diff --git a/test/test_distribute.py b/test/test_distribute.py
new file mode 100644
index 0000000..95fda31
--- /dev/null
+++ b/test/test_distribute.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.distribute`."""
+
+from __future__ import print_function
+
+from twisted.trial import unittest
+
+from zope.interface.verify import verifyObject
+
+from bridgedb.distribute import IDistribute
+from bridgedb.distribute import Distributor
+
+
+class DistributorTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.distribute.Distributor`."""
+
+ def test_Distributor_implements_IDistribute(self):
+ IDistribute.namesAndDescriptions()
+ IDistribute.providedBy(Distributor)
+ self.assertTrue(verifyObject(IDistribute, Distributor()))
+
+ def test_Distributor_str_no_name(self):
+ """str(dist) when the distributor doesn't have a name should return a
+ blank string.
+ """
+ dist = Distributor()
+ self.assertEqual(str(dist), "")
+
+ def test_Distributor_str_with_name(self):
+ """str(dist) when the distributor has a name should return the name."""
+ dist = Distributor()
+ dist.name = "foo"
+ self.assertEqual(str(dist), "foo")
diff --git a/test/test_email_autoresponder.py b/test/test_email_autoresponder.py
new file mode 100644
index 0000000..98302ca
--- /dev/null
+++ b/test/test_email_autoresponder.py
@@ -0,0 +1,571 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.email.autoresponder` module."""
+
+from __future__ import print_function
+
+import io
+import os
+import shutil
+
+from twisted.internet import defer
+from twisted.mail.smtp import Address
+from twisted.python.failure import Failure
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+from bridgedb.email import autoresponder
+from bridgedb.email.server import SMTPMessage
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.test.email_helpers import _createConfig
+from bridgedb.test.email_helpers import _createMailServerContext
+from bridgedb.test.email_helpers import DummyEmailDistributorWithState
+
+
+class CreateResponseBodyTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.email.autoresponder.createResponseBody`."""
+
+ def _moveGPGTestKeyfile(self):
+ here = os.getcwd()
+ topDir = here.rstrip('_trial_temp')
+ self.gpgFile = os.path.join(topDir, '.gnupg', 'TESTING.subkeys.sec')
+ self.gpgMoved = os.path.join(here, 'TESTING.subkeys.sec')
+ shutil.copy(self.gpgFile, self.gpgMoved)
+
+ def setUp(self):
+ """Create fake email, distributor, and associated context data."""
+ self._moveGPGTestKeyfile()
+ self.toAddress = "user at example.com"
+ self.config = _createConfig()
+ self.ctx = _createMailServerContext(self.config)
+ self.distributor = self.ctx.distributor
+
+ def _getIncomingLines(self, clientAddress="user at example.com"):
+ """Generate the lines of an incoming email from **clientAddress**."""
+ self.toAddress = Address(clientAddress)
+ lines = [
+ "From: %s" % clientAddress,
+ "To: bridges at localhost",
+ "Subject: testing",
+ "",
+ "get bridges",
+ ]
+ return lines
+
+ def test_createResponseBody_getKey(self):
+ """A request for 'get key' should receive our GPG key."""
+ lines = self._getIncomingLines()
+ lines[4] = "get key"
+ ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
+ self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', ret)
+
+ def test_createResponseBody_bridges_invalid(self):
+ """An invalid request for 'transport obfs3' should get help text."""
+ lines = self._getIncomingLines("testing at localhost")
+ lines[4] = "transport obfs3"
+ ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
+ self.assertSubstring("COMMANDs", ret)
+
+ def test_createResponseBody_bridges_obfs3(self):
+ """A request for 'get transport obfs3' should receive a response."""
+ lines = self._getIncomingLines("testing at localhost")
+ lines[4] = "get transport obfs3"
+ ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
+ self.assertSubstring("Here are your bridges", ret)
+ self.assertSubstring("obfs3", ret)
+
+ def test_createResponseBody_bridges_obfsobfswebz(self):
+ """We should only pay attention to the *last* in a crazy request."""
+ lines = self._getIncomingLines("testing at localhost")
+ lines[4] = "get unblocked webz"
+ lines.append("get transport obfs2")
+ lines.append("get transport obfs3")
+ ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
+ self.assertSubstring("Here are your bridges", ret)
+ self.assertSubstring("obfs3", ret)
+
+ def test_createResponseBody_bridges_obfsobfswebzipv6(self):
+ """We should *still* only pay attention to the *last* request."""
+ lines = self._getIncomingLines("testing at localhost")
+ lines[4] = "transport obfs3"
+ lines.append("get unblocked webz")
+ lines.append("get ipv6")
+ lines.append("get transport obfs2")
+ ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress)
+ self.assertSubstring("Here are your bridges", ret)
+ self.assertSubstring("obfs2", ret)
+
+ def test_createResponseBody_two_requests_TooSoonEmail(self):
+ """The same client making two requests in a row should receive a
+ rate-limit warning for the second response.
+ """
+ # Set up a mock distributor which keeps state:
+ dist = DummyEmailDistributorWithState()
+ ctx = _createMailServerContext(self.config, dist)
+
+ lines = self._getIncomingLines("testing at localhost")
+ first = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertSubstring("Here are your bridges", first)
+ second = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertSubstring("Please slow down", second)
+
+ def test_createResponseBody_three_requests_TooSoonEmail(self):
+ """Alice making a request, next Bob making a request, and then Alice again,
+ should result in both of their first requests getting them bridges,
+ and then Alice's second request gets her a rate-limit warning email.
+ """
+ # Set up a mock distributor which keeps state:
+ dist = DummyEmailDistributorWithState()
+ ctx = _createMailServerContext(self.config, dist)
+
+ aliceLines = self._getIncomingLines("alice at localhost")
+ aliceFirst = autoresponder.createResponseBody(aliceLines, ctx,
+ self.toAddress)
+ self.assertSubstring("Here are your bridges", aliceFirst)
+
+ bobLines = self._getIncomingLines("bob at localhost")
+ bobFirst = autoresponder.createResponseBody(bobLines, ctx,
+ self.toAddress)
+ self.assertSubstring("Here are your bridges", bobFirst)
+
+ aliceSecond = autoresponder.createResponseBody(aliceLines, ctx,
+ self.toAddress)
+ self.assertSubstring("Please slow down", aliceSecond)
+
+ def test_createResponseBody_three_requests_IgnoreEmail(self):
+ """The same client making three requests in a row should receive a
+ rate-limit warning for the second response, and then nothing for every
+ request thereafter.
+ """
+ # Set up a mock distributor which keeps state:
+ dist = DummyEmailDistributorWithState()
+ ctx = _createMailServerContext(self.config, dist)
+
+ lines = self._getIncomingLines("testing at localhost")
+ first = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertSubstring("Here are your bridges", first)
+ second = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertSubstring("Please slow down", second)
+ third = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertIsNone(third)
+ fourth = autoresponder.createResponseBody(lines, ctx, self.toAddress)
+ self.assertIsNone(fourth)
+
+
+class EmailResponseTests(unittest.TestCase):
+ """Tests for ``generateResponse()`` and ``EmailResponse``."""
+
+ def setUp(self):
+ self.fromAddr = "bridges at torproject.org"
+ self.clientAddr = "user at example.com"
+ self.body = """\
+People think that time is strictly linear, but, in reality, it's actually just
+a ball of timey-wimey, wibbly-warbly... stuff."""
+
+ def tearDown(self):
+ autoresponder.safelog.safe_logging = True
+
+ def test_EmailResponse_generateResponse(self):
+ response = autoresponder.generateResponse(self.fromAddr,
+ self.clientAddr,
+ self.body)
+ self.assertIsInstance(response, autoresponder.EmailResponse)
+
+ def test_EmailResponse_generateResponse_noSafelog(self):
+ autoresponder.safelog.safe_logging = False
+ response = autoresponder.generateResponse(self.fromAddr,
+ self.clientAddr,
+ self.body)
+ self.assertIsInstance(response, autoresponder.EmailResponse)
+
+ def test_EmailResponse_generateResponse_mailfile(self):
+ response = autoresponder.generateResponse(self.fromAddr,
+ self.clientAddr,
+ self.body)
+ self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
+
+ def test_EmailResponse_generateResponse_withInReplyTo(self):
+ response = autoresponder.generateResponse(self.fromAddr,
+ self.clientAddr,
+ self.body,
+ messageID="NSA")
+ contents = str(response.readContents()).replace('\x00', '')
+ self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
+ self.assertSubstring("In-Reply-To: NSA", contents)
+
+ def test_EmailResponse_generateResponse_readContents(self):
+ response = autoresponder.generateResponse(self.fromAddr,
+ self.clientAddr,
+ self.body)
+ contents = str(response.readContents()).replace('\x00', '')
+ self.assertSubstring('timey-wimey, wibbly-warbly... stuff.', contents)
+
+ def test_EmailResponse_additionalHeaders(self):
+ response = autoresponder.EmailResponse()
+ response.writeHeaders(self.fromAddr, self.clientAddr,
+ subject="Re: echelon", inReplyTo="NSA",
+ X_been_there="They were so 2004")
+ contents = str(response.readContents()).replace('\x00', '')
+ self.assertIsInstance(response.mailfile, (io.BytesIO, io.StringIO))
+ self.assertSubstring("In-Reply-To: NSA", contents)
+ self.assertSubstring("X-been-there: They were so 2004", contents)
+
+ def test_EmailResponse_close(self):
+ """Calling EmailResponse.close() should close the ``mailfile`` and set
+ ``closed=True``.
+ """
+ response = autoresponder.EmailResponse()
+ self.assertEqual(response.closed, False)
+ response.close()
+ self.assertEqual(response.closed, True)
+ self.assertRaises(ValueError, response.write, self.body)
+
+ def test_EmailResponse_read(self):
+ """Calling EmailResponse.read() should read bytes from the file."""
+ response = autoresponder.EmailResponse()
+ response.write(self.body)
+ response.rewind()
+ contents = str(response.read()).replace('\x00', '')
+ # The newlines in the email body should have been replaced with
+ # ``EmailResponse.delimiter``.
+ delimited = self.body.replace('\n', response.delimiter) \
+ + response.delimiter
+ self.assertEqual(delimited, contents)
+
+ def test_EmailResponse_read_three_bytes(self):
+ """EmailResponse.read(3) should read three bytes from the file."""
+ response = autoresponder.EmailResponse()
+ response.write(self.body)
+ response.rewind()
+ contents = str(response.read(3)).replace('\x00', '')
+ self.assertEqual(contents, self.body[:3])
+
+ def test_EmailResponse_write(self):
+ """Calling EmailResponse.write() should write to the mailfile."""
+ response = autoresponder.EmailResponse()
+ response.write(self.body)
+ contents = str(response.readContents()).replace('\x00', '')
+ # The newlines in the email body should have been replaced with
+ # ``EmailResponse.delimiter``.
+ delimited = self.body.replace('\n', response.delimiter) \
+ + response.delimiter
+ self.assertEqual(delimited, contents)
+
+ def test_EmailResponse_write_withRetNewlines(self):
+ """Calling EmailResponse.write() with '\r\n' in the lines should call
+ writelines(), which splits up the lines and then calls write() again.
+ """
+ response = autoresponder.EmailResponse()
+ response.write(self.body.replace('\n', '\r\n'))
+ contents = str(response.readContents()).replace('\x00', '')
+ # The newlines in the email body should have been replaced with
+ # ``EmailResponse.delimiter``.
+ delimited = self.body.replace('\n', response.delimiter) \
+ + response.delimiter
+ self.assertEqual(delimited, contents)
+
+ def test_EmailResponse_writelines_list(self):
+ """Calling EmailResponse.writelines() with a list should write the
+ concatenated contents of the list into the mailfile.
+ """
+ response = autoresponder.EmailResponse()
+ response.writelines(self.body.split('\n'))
+ contents = str(response.readContents()).replace('\x00', '')
+ # The newlines in the email body should have been replaced with
+ # ``EmailResponse.delimiter``.
+ delimited = self.body.replace('\n', response.delimiter) \
+ + response.delimiter
+ self.assertEqual(delimited, contents)
+
+
+class SMTPAutoresponderTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.email.autoresponder.SMTPAutoresponder`."""
+
+ timeout = 10
+
+ def setUp(self):
+ self.config = _createConfig()
+ self.context = _createMailServerContext(self.config)
+ self.message = SMTPMessage(self.context)
+
+ def _getIncomingLines(self, clientAddress="user at example.com"):
+ """Generate the lines of an incoming email from **clientAddress**."""
+ lines = [
+ "From: %s" % clientAddress,
+ "To: bridges at localhost",
+ "Subject: testing",
+ "",
+ "get bridges",
+ ]
+ self.message.lines = lines
+
+ def _setUpResponder(self):
+ """Set up the incoming message of our autoresponder.
+
+ This is necessary because normally our SMTP server acts as a line
+ protocol, waiting for an EOM which sets off a chain of deferreds
+ resulting in the autoresponder sending out the response. This should
+ be called after :meth:`_getIncomingLines` so that we can hook into the
+ SMTP protocol without actually triggering all the deferreds.
+ """
+ self.message.message = self.message.getIncomingMessage()
+ self.responder = self.message.responder
+ # The following are needed to provide client disconnection methods for
+ # the call to ``twisted.mail.smtp.SMTPClient.sendError`` in
+ # ``bridgedb.email.autoresponder.SMTPAutoresponder.sendError``:
+ #protocol = proto_helpers.AccumulatingProtocol()
+ #transport = proto_helpers.StringTransportWithDisconnection()
+ self.tr = proto_helpers.StringTransportWithDisconnection()
+ # Set the transport's protocol, because
+ # StringTransportWithDisconnection is a bit janky:
+ self.tr.protocol = self.responder
+ self.responder.makeConnection(self.tr)
+
+ def test_SMTPAutoresponder_getMailFrom_notbridgedb_at_yikezors_dot_net(self):
+ """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
+ address other than the one we're listening for should return our
+ configured address, not the one in the incoming email.
+ """
+ self._getIncomingLines()
+ self.message.lines[1] = 'To: notbridgedb at yikezors.net'
+ self._setUpResponder()
+ recipient = str(self.responder.getMailFrom())
+ self.assertEqual(recipient, self.context.fromAddr)
+
+ def test_SMTPAutoresponder_getMailFrom_givemebridges_at_seriously(self):
+ """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
+ address other than the one we're listening for should return our
+ configured address, not the one in the incoming email.
+ """
+ self._getIncomingLines()
+ self.message.lines[1] = 'To: givemebridges at serious.ly'
+ self._setUpResponder()
+ recipient = str(self.responder.getMailFrom())
+ self.assertEqual(recipient, self.context.fromAddr)
+
+ def test_SMTPAutoresponder_getMailFrom_bad_address(self):
+ """SMTPAutoresponder.getMailFrom() for an incoming email sent to a malformed
+ email address should log an smtp.AddressError and then return our
+ configured email address.
+ """
+ self._getIncomingLines()
+ self.message.lines[1] = 'To: ><@><<<>>.foo'
+ self._setUpResponder()
+ recipient = str(self.responder.getMailFrom())
+ self.assertEqual(recipient, self.context.fromAddr)
+
+ def test_SMTPAutoresponder_getMailFrom_plus_address(self):
+ """SMTPAutoresponder.getMailFrom() for an incoming email sent with a valid
+ plus address should respond.
+ """
+ self._getIncomingLines()
+ ours = Address(self.context.fromAddr)
+ plus = '@'.join([ours.local + '+zh_cn', ours.domain])
+ self.message.lines[1] = 'To: {0}'.format(plus)
+ self._setUpResponder()
+ recipient = str(self.responder.getMailFrom())
+ self.assertEqual(recipient, plus)
+
+ def test_SMTPAutoresponder_getMailFrom_getbridges_at_localhost(self):
+ """SMTPAutoresponder.getMailFrom() for an incoming email sent with
+ 'getbridges+zh_cn at localhost' should be responded to from the default
+ address.
+ """
+ self._getIncomingLines()
+ ours = Address(self.context.fromAddr)
+ plus = '@'.join(['get' + ours.local + '+zh_cn', ours.domain])
+ self.message.lines[1] = 'To: {0}'.format(plus)
+ self._setUpResponder()
+ recipient = str(self.responder.getMailFrom())
+ self.assertEqual(recipient, self.context.fromAddr)
+
+ def test_SMTPAutoresponder_getMailTo_UnsupportedDomain(self):
+ """getMailTo() should catch emails from UnsupportedDomains."""
+ emailFrom = 'some.dude at un.support.ed'
+ self._getIncomingLines(emailFrom)
+ self._setUpResponder()
+ clients = self.responder.getMailTo()
+ self.assertIsInstance(clients, list, (
+ "Returned value of SMTPAutoresponder.getMailTo() isn't a list! "
+ "Type: %s" % type(clients)))
+ self.assertTrue(emailFrom not in clients)
+ # The client was from an unsupported domain; they shouldn't be in the
+ # clients list:
+ self.assertEqual(len(clients), 0,
+ "clients = %s" % repr(clients))
+
+ def test_SMTPAutoresponder_reply_noFrom(self):
+ """A received email without a "From:" or "Sender:" header shouldn't
+ receive a response.
+ """
+ self._getIncomingLines()
+ self.message.lines[0] = ""
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_badAddress(self):
+ """Don't respond to RFC2822 malformed source addresses."""
+ self._getIncomingLines("testing*.?\"@example.com")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ # This will call ``self.responder.reply()``:
+ #ret = self.responder.incoming.eomReceived()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_anotherBadAddress(self):
+ """Don't respond to RFC2822 malformed source addresses."""
+ self._getIncomingLines("Mallory <>>@example.com")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_invalidDomain(self):
+ """Don't respond to RFC2822 malformed source addresses."""
+ self._getIncomingLines("testing at exa#mple.com")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_anotherInvalidDomain(self):
+ """Don't respond to RFC2822 malformed source addresses."""
+ self._getIncomingLines("testing at exam+ple.com")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_DKIM_badDKIMheader(self):
+ """An email with an 'X-DKIM-Authentication-Result:' header appended
+ after the body should not receive a response.
+ """
+ self._getIncomingLines("testing at gmail.com")
+ self.message.lines.append("X-DKIM-Authentication-Result: ")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_goodDKIMheader(self):
+ """An email with a good DKIM header should be responded to."""
+ self._getIncomingLines("testing at gmail.com")
+ self.message.lines.insert(3, "X-DKIM-Authentication-Result: pass")
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+
+ def test_SMTPAutoresponder_reply_transport_invalid(self):
+ """An invalid request for 'transport obfs3' should get help text."""
+ #self.skip = True
+ #raise unittest.SkipTest("We need to fake the reactor for this one")
+
+ def cb(success):
+ pass
+ self._getIncomingLines("testing at example.com")
+ self.message.lines[4] = "transport obfs3"
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+ #self.assertSubstring("COMMANDs", ret)
+ print(self.tr.value())
+ return ret
+
+ def test_SMTPAutoresponder_reply_transport_valid(self):
+ """An valid request for 'get transport obfs3' should get obfs3."""
+ #self.skip = True
+ #raise unittest.SkipTest("We need to fake the reactor for this one")
+
+ self._getIncomingLines("testing at example.com")
+ self.message.lines[4] = "transport obfs3"
+ self._setUpResponder()
+ ret = self.responder.reply()
+ self.assertIsInstance(ret, defer.Deferred)
+ #self.assertSubstring("obfs3", ret)
+ print(self.tr.value())
+ return ret
+
+ def test_SMTPAutoresponder_sentMail(self):
+ """``SMTPAutoresponder.sendMail()`` should handle successes from an
+ :api:`twisted.mail.smtp.SMTPSenderFactory`.
+ """
+ success = (1, [('me at myaddress.com', 250, 'OK',)])
+ self._getIncomingLines()
+ self._setUpResponder()
+ self.responder.sentMail(success)
+
+ def test_SMTPAutoresponder_sendError_fail(self):
+ """``SMTPAutoresponder.sendError()`` should handle failures."""
+ fail = Failure(ValueError('This failure was sent on purpose.'))
+ self._getIncomingLines()
+ self._setUpResponder()
+ self.responder.sendError(fail)
+
+ def test_SMTPAutoresponder_sendError_exception(self):
+ """``SMTPAutoresponder.sendError()`` should handle exceptions."""
+ error = ValueError('This error was sent on purpose.')
+ self._getIncomingLines()
+ self._setUpResponder()
+ self.responder.sendError(error)
+
+ def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_domain(self):
+ """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
+ reported being from an email address at one supported domain and the
+ email's 'From:' header reported another domain.
+ """
+ smtpFrom = 'not.an.evil.bot at yahoo.com'
+ emailFrom = Address('not.an.evil.bot at gmail.com')
+ self._getIncomingLines(str(emailFrom))
+ self._setUpResponder()
+ self.responder.incoming.canonicalFromSMTP = smtpFrom
+ self.assertFalse(self.responder.runChecks(emailFrom))
+
+ def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_username(self):
+ """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
+ reported being from an email address and the email's 'From:' header
+ reported another email address, even if the only the username part is
+ mismatched.
+ """
+ smtpFrom = 'feidanchaoren0001 at gmail.com'
+ emailFrom = Address('feidanchaoren0038 at gmail.com')
+ self._getIncomingLines(str(emailFrom))
+ self._setUpResponder()
+ self.responder.incoming.canonicalFromSMTP = smtpFrom
+ self.assertFalse(self.responder.runChecks(emailFrom))
+
+ def test_SMTPAutoresponder_runChecks_DKIM_dunno(self):
+ """runChecks() should catch emails with bad DKIM headers
+ (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
+ which we're configured to check DKIM verification results for.
+ """
+ emailFrom = Address('dkimlikedunno at gmail.com')
+ header = "X-DKIM-Authentication-Results: dunno"
+ self._getIncomingLines(str(emailFrom))
+ self.message.lines.insert(3, header)
+ self._setUpResponder()
+ self.assertFalse(self.responder.runChecks(emailFrom))
+
+ def test_SMTPAutoresponder_runChecks_DKIM_bad(self):
+ """runChecks() should catch emails with bad DKIM headers
+ (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
+ which we're configured to check DKIM verification results for.
+ """
+ emailFrom = Address('dkimlikewat at gmail.com')
+ header = "X-DKIM-Authentication-Results: wowie zowie there's a sig here"
+ self._getIncomingLines(str(emailFrom))
+ self.message.lines.insert(3, header)
+ self._setUpResponder()
+ self.assertFalse(self.responder.runChecks(emailFrom))
+
+ def test_SMTPAutoresponder_runChecks_blacklisted(self):
+ """runChecks() on an blacklisted email address should return False."""
+ emailFrom = Address('feidanchaoren0043 at gmail.com')
+ self._getIncomingLines(str(emailFrom))
+ self._setUpResponder()
+ self.assertFalse(self.responder.runChecks(emailFrom))
diff --git a/test/test_email_distributor.py b/test/test_email_distributor.py
new file mode 100644
index 0000000..b4d88b2
--- /dev/null
+++ b/test/test_email_distributor.py
@@ -0,0 +1,258 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.email.distributor`."""
+
+from __future__ import print_function
+
+import logging
+import tempfile
+import os
+
+from twisted.internet.task import Clock
+from twisted.trial import unittest
+
+import bridgedb.Storage
+
+from bridgedb.bridges import Bridge
+from bridgedb.email.distributor import EmailDistributor
+from bridgedb.email.distributor import IgnoreEmail
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.email.request import EmailBridgeRequest
+from bridgedb.parse.addr import BadEmail
+from bridgedb.parse.addr import UnsupportedDomain
+from bridgedb.parse.addr import normalizeEmail
+from bridgedb.test.util import generateFakeBridges
+
+logging.disable(50)
+
+
+BRIDGES = generateFakeBridges()
+
+
+class EmailDistributorTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.email.distributor.EmailDistributor`."""
+
+ # Fail any tests which take longer than 15 seconds.
+ timeout = 15
+
+ def setUp(self):
+ self.fd, self.fname = tempfile.mkstemp(suffix=".sqlite", dir=os.getcwd())
+ bridgedb.Storage.initializeDBLock()
+ self.db = bridgedb.Storage.openDatabase(self.fname)
+ bridgedb.Storage.setDBFilename(self.fname)
+
+ self.bridges = BRIDGES
+ self.key = 'aQpeOFIj8q20s98awfoiq23rpOIjFaqpEWFoij1X'
+ self.domainmap = {
+ 'example.com': 'example.com',
+ 'dkim.example.com': 'dkim.example.com',
+ }
+ self.domainrules = {
+ 'example.com': ['ignore_dots'],
+ 'dkim.example.com': ['dkim', 'ignore_dots']
+ }
+
+ def tearDown(self):
+ self.db.close()
+ os.close(self.fd)
+ os.unlink(self.fname)
+
+ def makeClientRequest(self, clientEmailAddress):
+ bridgeRequest = EmailBridgeRequest()
+ bridgeRequest.client = clientEmailAddress
+ bridgeRequest.isValid(True)
+ bridgeRequest.generateFilters()
+ return bridgeRequest
+
+ def test_EmailDistributor_getBridges_default_client(self):
+ """If EmailBridgeRequest.client was not set, then getBridges() should
+ raise a bridgedb.parse.addr.BadEmail exception.
+ """
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ # The "default" client is literally the string "default", see
+ # bridgedb.bridgerequest.BridgeRequestBase.
+ bridgeRequest = self.makeClientRequest('default')
+
+ self.assertRaises(BadEmail, dist.getBridges, bridgeRequest, 1)
+
+ def test_EmailDistributor_getBridges_with_whitelist(self):
+ """If an email address is in the whitelist, it should get a response
+ every time it asks (i.e. no rate-limiting).
+ """
+ # The whitelist should be in the form {EMAIL: GPG_FINGERPRINT}
+ whitelist = {'white at list.ed': '0123456789ABCDEF0123456789ABCDEF01234567'}
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules,
+ whitelist=whitelist)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ # A request from a whitelisted address should always get a response.
+ bridgeRequest = self.makeClientRequest('white at list.ed')
+ for i in range(5):
+ bridges = dist.getBridges(bridgeRequest, 1)
+ self.assertEqual(len(bridges), 3)
+
+ def test_EmailDistributor_getBridges_rate_limit_multiple_clients(self):
+ """Each client should be rate-limited separately."""
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ bridgeRequest1 = self.makeClientRequest('abc at example.com')
+ bridgeRequest2 = self.makeClientRequest('def at example.com')
+ bridgeRequest3 = self.makeClientRequest('ghi at example.com')
+
+ # The first request from 'abc' should get a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest1, 1)), 3)
+ # The second from 'abc' gets a warning.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest1, 1)
+ # The first request from 'def' should get a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest2, 1)), 3)
+ # The third from 'abc' is ignored.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
+ # The second from 'def' gets a warning.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest2, 1)
+ # The third from 'def' is ignored.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest2, 1)
+ # The fourth from 'abc' is ignored.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
+ # The first request from 'ghi' should get a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest3, 1)), 3)
+ # The second from 'ghi' gets a warning.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest3, 1)
+ # The third from 'ghi' is ignored.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
+ # The fourth from 'ghi' is ignored.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
+
+ def test_EmailDistributor_getBridges_rate_limit(self):
+ """A client's first email should return bridges. The second should
+ return a warning, and the third should receive no response.
+ """
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ bridgeRequest = self.makeClientRequest('abc at example.com')
+
+ # The first request should get a response with bridges.
+ bridges = dist.getBridges(bridgeRequest, 1)
+ self.assertGreater(len(bridges), 0)
+ [self.assertIsInstance(b, Bridge) for b in bridges]
+ self.assertEqual(len(bridges), 3)
+
+ # The second gets a warning, and the third is ignored.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
+
+ def test_EmailDistributor_getBridges_rate_limit_expiry(self):
+ """A client's first email should return bridges. The second should
+ return a warning, and the third should receive no response. After the
+ EmailDistributor.emailRateMax is up, the client should be able to
+ receive a response again.
+ """
+ clock = Clock()
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ bridgeRequest = self.makeClientRequest('abc at example.com')
+
+ # The first request should get a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest, 1, clock)), 3)
+ # The second gets a warning, and the rest are ignored.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1, clock)
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1, clock)
+
+ clock.advance(2 * dist.emailRateMax)
+
+ # The client should again a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3, clock)
+
+ def test_EmailDistributor_cleanDatabase(self):
+ """Calling cleanDatabase() should cleanup email times in database, but
+ not allow clients who have been recently warned and/or ignored to
+ receive a response again until the remainder of their MAX_EMAIL_RATE
+ time is up.
+ """
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ bridgeRequest = self.makeClientRequest('abc at example.com')
+
+ # The first request should get a response with bridges.
+ self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3)
+ # The second gets a warning, and the third is ignored.
+ self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
+
+ dist.cleanDatabase()
+
+ # Cleaning the warning email times in the database shouldn't cause
+ # 'abc at example.com' to be able to email again, because only the times
+ # which aren't older than EMAIL_MAX_RATE should be cleared.
+ self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
+
+ def test_EmailDistributor_prepopulateRings(self):
+ """Calling prepopulateRings() should add two rings to the
+ EmailDistributor.hashring.
+ """
+ dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
+
+ # There shouldn't be any subrings yet.
+ self.assertEqual(len(dist.hashring.filterRings), 0)
+
+ dist.prepopulateRings()
+
+ # There should now be two subrings, but the subrings should be empty.
+ self.assertEqual(len(dist.hashring.filterRings), 2)
+ for (filtre, subring) in dist.hashring.filterRings.values():
+ self.assertEqual(len(subring), 0)
+
+ # The subrings in this Distributor have gross names, because the
+ # filter functions (including their addresses in memory!) are used as
+ # the subring names. In this case, we should have something like:
+ #
+ # frozenset([<function byIPv6 at 0x7eff7ad7fc80>])
+ #
+ # and
+ #
+ # frozenset([<function byIPv4 at 0x7eff7ad7fc08>])
+ #
+ # So we have to join the strings together and check the whole thing,
+ # since we have no other way to use these stupid subring names to
+ # index into the dictionary they are stored in, because the memory
+ # addresses are unknowable until runtime.
+
+ # There should be an IPv4 subring and an IPv6 ring:
+ ringnames = dist.hashring.filterRings.keys()
+ self.failUnlessIn("IPv4", "".join([str(ringname) for ringname in ringnames]))
+ self.failUnlessIn("IPv6", "".join([str(ringname) for ringname in ringnames]))
+
+ [dist.hashring.insert(bridge) for bridge in self.bridges]
+
+ # There should still be two subrings.
+ self.assertEqual(len(dist.hashring.filterRings), 2)
+ for (filtre, subring) in dist.hashring.filterRings.values():
+ self.assertGreater(len(subring), 0)
+
+ # Ugh, the hashring code is so gross looking.
+ subrings = dist.hashring.filterRings
+ subring1 = subrings.values()[0][1]
+ subring2 = subrings.values()[1][1]
+ # Each subring should have roughly the same number of bridges.
+ # (Having ±10 bridges in either ring, out of 500 bridges total, should
+ # be so bad.)
+ self.assertApproximates(len(subring1), len(subring2), 10)
+
+ def test_EmailDistributor_unsupported_domain(self):
+ """An unsupported domain should raise an UnsupportedDomain exception."""
+ self.assertRaises(UnsupportedDomain, normalizeEmail,
+ 'bad at email.com', self.domainmap, self.domainrules)
diff --git a/test/test_email_dkim.py b/test/test_email_dkim.py
new file mode 100644
index 0000000..499a3c1
--- /dev/null
+++ b/test/test_email_dkim.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.email.dkim` module."""
+
+import io
+
+from twisted.mail.smtp import rfc822
+from twisted.trial import unittest
+
+from bridgedb.email import dkim
+
+
+class CheckDKIMTests(unittest.TestCase):
+ """Tests for :func:`email.server.checkDKIM`."""
+
+ def setUp(self):
+ """Create fake email, distributor, and associated context data."""
+ self.goodMessage = """\
+From: user at gmail.com
+To: bridges at localhost
+X-DKIM-Authentication-Results: pass
+Subject: testing
+
+get bridges
+"""
+ self.badMessage = """\
+From: user at gmail.com
+To: bridges at localhost
+Subject: testing
+
+get bridges
+"""
+ self.domainRules = {
+ 'gmail.com': ["ignore_dots", "dkim"],
+ 'example.com': [],
+ 'localhost': [],
+ }
+
+ def _createMessage(self, messageString):
+ """Create an ``rfc822.Message`` from a string."""
+ messageIO = io.StringIO(unicode(messageString))
+ return rfc822.Message(messageIO)
+
+ def test_checkDKIM_good(self):
+ message = self._createMessage(self.goodMessage)
+ result = dkim.checkDKIM(message,
+ self.domainRules.get("gmail.com"))
+ self.assertTrue(result)
+
+
+ def test_checkDKIM_bad(self):
+ message = self._createMessage(self.badMessage)
+ result = dkim.checkDKIM(message,
+ self.domainRules.get("gmail.com"))
+ self.assertIs(result, False)
+
+ def test_checkDKIM_dunno(self):
+ """A ``X-DKIM-Authentication-Results: dunno`` header should return
+ False.
+ """
+ messageList = self.badMessage.split('\n')
+ messageList[2] = "X-DKIM-Authentication-Results: dunno"
+ message = self._createMessage('\n'.join(messageList))
+ result = dkim.checkDKIM(message,
+ self.domainRules.get("gmail.com"))
+ self.assertIs(result, False)
+
+ def test_checkDKIM_good_dunno(self):
+ """A good DKIM verification header, *plus* an
+ ``X-DKIM-Authentication-Results: dunno`` header should return False.
+ """
+ messageList = self.badMessage.split('\n')
+ messageList.insert(2, "X-DKIM-Authentication-Results: dunno")
+ message = self._createMessage('\n'.join(messageList))
+ result = dkim.checkDKIM(message,
+ self.domainRules.get("gmail.com"))
+ self.assertIs(result, False)
diff --git a/test/test_email_request.py b/test/test_email_request.py
new file mode 100644
index 0000000..745ea71
--- /dev/null
+++ b/test/test_email_request.py
@@ -0,0 +1,276 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.email.request` module."""
+
+from __future__ import print_function
+
+import ipaddr
+
+from twisted.trial import unittest
+
+from bridgedb.email import request
+
+
+class DetermineBridgeRequestOptionsTests(unittest.TestCase):
+ """Unittests for :func:`b.e.request.determineBridgeRequestOptions`."""
+
+ def test_determineBridgeRequestOptions_get_help(self):
+ """Requesting 'get help' should raise EmailRequestedHelp."""
+ lines = ['',
+ 'get help']
+ self.assertRaises(request.EmailRequestedHelp,
+ request.determineBridgeRequestOptions, lines)
+
+ def test_determineBridgeRequestOptions_get_halp(self):
+ """Requesting 'get halp' should raise EmailRequestedHelp."""
+ lines = ['',
+ 'get halp']
+ self.assertRaises(request.EmailRequestedHelp,
+ request.determineBridgeRequestOptions, lines)
+
+ def test_determineBridgeRequestOptions_get_key(self):
+ """Requesting 'get key' should raise EmailRequestedKey."""
+ lines = ['',
+ 'get key']
+ self.assertRaises(request.EmailRequestedKey,
+ request.determineBridgeRequestOptions, lines)
+
+ def test_determineBridgeRequestOptions_multiline_invalid(self):
+ """Requests without a 'get' anywhere should be considered invalid."""
+ lines = ['',
+ 'transport obfs3',
+ 'ipv6 vanilla bridges',
+ 'give me your gpgs']
+ reqvest = request.determineBridgeRequestOptions(lines)
+ # It's invalid because it didn't include a 'get' anywhere.
+ self.assertEqual(reqvest.isValid(), False)
+ self.assertFalse(reqvest.wantsKey())
+ # Though they did request IPv6, technically.
+ self.assertIs(reqvest.ipVersion, 6)
+ # And they did request a transport, technically.
+ self.assertEqual(len(reqvest.transports), 1)
+ self.assertEqual(reqvest.transports[0], 'obfs3')
+
+ def test_determineBridgeRequestOptions_multiline_valid(self):
+ """Though requests with a 'get' are considered valid."""
+ lines = ['',
+ 'get transport obfs3',
+ 'vanilla bridges',
+ 'transport scramblesuit unblocked ca']
+ reqvest = request.determineBridgeRequestOptions(lines)
+ # It's valid because it included a 'get'.
+ self.assertEqual(reqvest.isValid(), True)
+ self.assertFalse(reqvest.wantsKey())
+ # Though they didn't request IPv6, so it should default to IPv4.
+ self.assertIs(reqvest.ipVersion, 4)
+ # And they requested two transports.
+ self.assertEqual(len(reqvest.transports), 2)
+ self.assertEqual(reqvest.transports[0], 'obfs3')
+ self.assertEqual(reqvest.transports[1], 'scramblesuit')
+ # And they wanted this stuff to not be blocked in Canada.
+ self.assertEqual(len(reqvest.notBlockedIn), 1)
+ self.assertEqual(reqvest.notBlockedIn[0], 'ca')
+
+ def test_determineBridgeRequestOptions_multiline_valid_OMG_CAPSLOCK(self):
+ """Though requests with a 'get' are considered valid, even if they
+ appear to not know the difference between Capslock and Shift.
+ """
+ lines = ['',
+ 'get TRANSPORT obfs3',
+ 'vanilla bridges',
+ 'TRANSPORT SCRAMBLESUIT UNBLOCKED CA']
+ reqvest = request.determineBridgeRequestOptions(lines)
+ # It's valid because it included a 'get'.
+ self.assertEqual(reqvest.isValid(), True)
+ self.assertFalse(reqvest.wantsKey())
+ # Though they didn't request IPv6, so it should default to IPv4.
+ self.assertIs(reqvest.ipVersion, 4)
+ # And they requested two transports.
+ self.assertEqual(len(reqvest.transports), 2)
+ self.assertEqual(reqvest.transports[0], 'obfs3')
+ self.assertEqual(reqvest.transports[1], 'scramblesuit')
+ # And they wanted this stuff to not be blocked in Canada.
+ self.assertEqual(len(reqvest.notBlockedIn), 1)
+ self.assertEqual(reqvest.notBlockedIn[0], 'ca')
+
+ def test_determineBridgeRequestOptions_get_transport(self):
+ """An invalid request for 'transport obfs3' (missing the 'get')."""
+ lines = ['',
+ 'transport obfs3']
+ reqvest = request.determineBridgeRequestOptions(lines)
+ self.assertEqual(len(reqvest.transports), 1)
+ self.assertEqual(reqvest.transports[0], 'obfs3')
+ self.assertEqual(reqvest.isValid(), False)
+
+ def test_determineBridgeRequestOptions_get_ipv6(self):
+ """An valid request for 'get ipv6'."""
+ lines = ['',
+ 'get ipv6']
+ reqvest = request.determineBridgeRequestOptions(lines)
+ self.assertIs(reqvest.ipVersion, 6)
+ self.assertEqual(reqvest.isValid(), True)
+
+
+class EmailBridgeRequestTests(unittest.TestCase):
+ """Unittests for :class:`b.e.request.EmailBridgeRequest`."""
+
+ def setUp(self):
+ """Create an EmailBridgeRequest instance to test."""
+ self.request = request.EmailBridgeRequest()
+
+ def tearDown(self):
+ """Reset cached 'unblocked'/'transport' lists and ipVersion between
+ tests.
+ """
+ self.request.withIPv4()
+ self.request.notBlockedIn = []
+ self.request.transports = []
+
+ def test_EmailBridgeRequest_isValid_initial(self):
+ """Initial value of EmailBridgeRequest.isValid() should be False."""
+ self.request.isValid(None)
+ self.assertEqual(self.request.isValid(), False)
+
+ def test_EmailBridgeRequest_isValid_True(self):
+ """The value of EmailBridgeRequest.isValid() should be True, after it
+ has been called with ``True`` as an argument.
+ """
+ self.request.isValid(True)
+ self.assertEqual(self.request.isValid(), True)
+
+ def test_EmailBridgeRequest_isValid_False(self):
+ """The value of EmailBridgeRequest.isValid() should be False, after it
+ has been called with ``False`` as an argument.
+ """
+ self.request.isValid(False)
+ self.assertEqual(self.request.isValid(), False)
+
+ def test_EmailBridgeRequest_wantsKey_initial(self):
+ """Initial value of EmailBridgeRequest.wantsKey() should be False."""
+ self.request.wantsKey(None)
+ self.assertEqual(self.request.wantsKey(), False)
+
+ def test_EmailBridgeRequest_wantsKey_True(self):
+ """The value of EmailBridgeRequest.wantsKey() should be True, after it
+ has been called with ``True`` as an argument.
+ """
+ self.request.wantsKey(True)
+ self.assertEqual(self.request.wantsKey(), True)
+
+ def test_EmailBridgeRequest_wantsKey_False(self):
+ """The value of EmailBridgeRequest.wantsKey() should be False, after
+ it has been called with ``False`` as an argument.
+ """
+ self.request.wantsKey(False)
+ self.assertEqual(self.request.wantsKey(), False)
+
+ def test_EmailBridgeRequest_withIPv6(self):
+ """IPv6 requests should have ``ipVersion == 6``."""
+ self.assertEqual(self.request.ipVersion, 4)
+ self.request.withIPv6()
+ self.assertEqual(self.request.ipVersion, 6)
+
+ def test_EmailBridgeRequest_withoutBlockInCountry_CN(self):
+ """Country codes that aren't lowercase should be ignored."""
+ self.request.withoutBlockInCountry('get unblocked CN')
+ self.assertIsInstance(self.request.notBlockedIn, list)
+ self.assertEqual(len(self.request.notBlockedIn), 0)
+
+ def test_EmailBridgeRequest_withoutBlockInCountry_cn(self):
+ """Lowercased country codes are okay though."""
+ self.request.withoutBlockInCountry('get unblocked cn')
+ self.assertIsInstance(self.request.notBlockedIn, list)
+ self.assertEqual(len(self.request.notBlockedIn), 1)
+
+ def test_EmailBridgeRequest_withoutBlockInCountry_cn_getMissing(self):
+ """Lowercased country codes are still okay if the 'get' is missing."""
+ self.request.withoutBlockInCountry('unblocked cn')
+ self.assertIsInstance(self.request.notBlockedIn, list)
+ self.assertEqual(len(self.request.notBlockedIn), 1)
+
+ def test_EmailBridgeRequest_withoutBlockInCountry_multiline_cn_ir_li(self):
+ """Requests for multiple unblocked countries should compound if they
+ are on separate 'get unblocked' lines.
+ """
+ self.request.withoutBlockInCountry('get unblocked cn')
+ self.request.withoutBlockInCountry('get unblocked ir')
+ self.request.withoutBlockInCountry('get unblocked li')
+ self.assertIsInstance(self.request.notBlockedIn, list)
+ self.assertEqual(len(self.request.notBlockedIn), 3)
+
+ def test_EmailBridgeRequest_withoutBlockInCountry_singleline_cn_ir_li(self):
+ """Requests for multiple unblocked countries which are all on the same
+ 'get unblocked' line will use only the *first* country code.
+ """
+ self.request.withoutBlockInCountry('get unblocked cn ir li')
+ self.assertIsInstance(self.request.notBlockedIn, list)
+ self.assertEqual(len(self.request.notBlockedIn), 1)
+
+ def test_EmailBridgeRequest_withPluggableTransportType_SCRAMBLESUIT(self):
+ """Transports which aren't in lowercase should be ignored."""
+ self.request.withPluggableTransportType('get transport SCRAMBLESUIT')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 0)
+
+ def test_EmailBridgeRequest_withPluggableTransportType_scramblesuit(self):
+ """Lowercased transports are okay though."""
+ self.request.withPluggableTransportType('get transport scramblesuit')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 1)
+ self.assertEqual(self.request.transports[0], 'scramblesuit')
+
+ def test_EmailBridgeRequest_withPluggableTransportType_scramblesuit_getMissing(self):
+ """Lowercased transports are still okay if 'get' is missing."""
+ self.request.withPluggableTransportType('transport scramblesuit')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 1)
+ self.assertEqual(self.request.transports[0], 'scramblesuit')
+
+ def test_EmailBridgeRequest_withPluggableTransportType_multiline_obfs3_obfs2_scramblesuit(self):
+ """Requests for multiple pluggable transports should compound if they
+ are on separate 'get transport' lines.
+ """
+ self.request.withPluggableTransportType('get transport obfs3')
+ self.request.withPluggableTransportType('get transport obfs2')
+ self.request.withPluggableTransportType('get transport scramblesuit')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 3)
+ self.assertEqual(self.request.transports[0], 'obfs3')
+
+ def test_EmailBridgeRequest_withPluggableTransportType_singleline_obfs3_obfs2_scramblesuit(self):
+ """Requests for multiple transports which are all on the same
+ 'get transport' line will use only the *first* transport.
+ """
+ self.request.withPluggableTransportType('get transport obfs3 obfs2 scramblesuit')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 1)
+ self.assertEqual(self.request.transports[0], 'obfs3')
+
+ def test_EmailBridgeRequest_withPluggableTransportType_whack(self):
+ """Requests for whacky transports that don't exist are also okay."""
+ self.request.withPluggableTransportType('get transport whack')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 1)
+ self.assertEqual(self.request.transports[0], 'whack')
+
+ def test_EmailBridgeRequest_justOnePTType_obfs3_obfs2_scramblesuit(self):
+ """Requests for multiple transports when
+ ``EmailBridgeRequest.justOneTransport()`` is used will use only the
+ *last* transport.
+ """
+ self.request.withPluggableTransportType('get transport obfs3')
+ self.request.withPluggableTransportType('get transport obfs2')
+ self.request.withPluggableTransportType('get transport scramblesuit')
+ self.assertIsInstance(self.request.transports, list)
+ self.assertEqual(len(self.request.transports), 3)
+ self.assertEqual(self.request.transports[0], 'obfs3')
+ self.assertEqual(self.request.justOnePTType(), 'scramblesuit')
diff --git a/test/test_email_server.py b/test/test_email_server.py
new file mode 100644
index 0000000..9c4fabb
--- /dev/null
+++ b/test/test_email_server.py
@@ -0,0 +1,543 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.email.server` module."""
+
+from __future__ import print_function
+
+import socket
+import string
+import types
+
+from twisted.python import log
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.mail.smtp import IMessage
+from twisted.mail.smtp import SMTPBadRcpt
+from twisted.mail.smtp import SMTPBadSender
+from twisted.mail.smtp import User
+from twisted.mail.smtp import Address
+from twisted.mail.smtp import rfc822
+from twisted.test import proto_helpers
+from twisted.trial import unittest
+
+from zope.interface import implementedBy
+
+from bridgedb.email import server
+from bridgedb.email.distributor import EmailDistributor
+from bridgedb.email.distributor import TooSoonEmail
+from bridgedb.parse.addr import BadEmail
+from bridgedb.schedule import Unscheduled
+from bridgedb.test import util
+from bridgedb.test.email_helpers import _createConfig
+from bridgedb.test.email_helpers import _createMailServerContext
+
+
+class SMTPMessageTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.email.server.SMTPMessage`."""
+
+ def setUp(self):
+ self.config = _createConfig()
+ self.context = _createMailServerContext(self.config)
+ self.message = server.SMTPMessage(self.context,
+ canonicalFromSMTP='localhost')
+ self.line = string.ascii_lowercase
+
+ def tearDown(self):
+ """Re-enable safelogging in between test runs."""
+ server.safelog.setSafeLogging(True)
+
+ def test_SMTPMessage_init(self):
+ """Our ``message`` attribute should be a ``SMTPMessage`` object, and
+ ``message.responder`` should be a
+ :class:`bridgedb.email.autoresponder.SMTPAutoresponder`.
+ """
+ self.assertIsInstance(self.message, server.SMTPMessage)
+ self.assertIsInstance(self.message.responder,
+ server.autoresponder.SMTPAutoresponder)
+
+ def test_SMTPMessage_IMessage_interface(self):
+ """``SMTPMessage`` should implement ``twisted.mail.smtp.IMessage``."""
+ self.assertTrue(IMessage.implementedBy(server.SMTPMessage))
+
+ def test_SMTPMessage_lineReceived_withSafelog(self):
+ """Test sending a line of text to ``SMTPMessage.lineReceived`` with
+ safelogging enabled.
+ """
+ server.safelog.setSafeLogging(True)
+ self.message.lineReceived(self.line)
+ self.assertEqual(self.message.nBytes, 26)
+ self.assertTrue(self.line in self.message.lines)
+
+ def test_SMTPMessage_lineReceived_withoutSafelog(self):
+ """Test sending a line of text to ``SMTPMessage.lineReceived`` with
+ safelogging disnabled.
+ """
+ server.safelog.setSafeLogging(False)
+ for _ in range(3):
+ self.message.lineReceived(self.line)
+ self.assertEqual(self.message.nBytes, 3*26)
+ self.assertTrue(self.line in self.message.lines)
+
+ def test_SMTPMessage_eomReceived(self):
+ """Calling ``oemReceived`` should return a deferred."""
+ self.message.lineReceived(self.line)
+ self.assertIsInstance(self.message.eomReceived(),
+ defer.Deferred)
+
+ def test_SMTPMessage_getIncomingMessage(self):
+ """``getIncomingMessage`` should return a ``rfc822.Message``."""
+ self.message.lineReceived(self.line)
+ self.assertIsInstance(self.message.getIncomingMessage(),
+ rfc822.Message)
+
+
+class SMTPIncomingDeliveryTests(unittest.TestCase):
+ """Unittests for :class:`email.server.SMTPIncomingDelivery`."""
+
+ def setUp(self):
+ """Set up our :class:`server.SMTPIncomingDelivery` instance, and reset the
+ following ``TestCase`` attributes to ``None``:
+ - ``helo``
+ - ``proto``
+ - ``origin``
+ - ``user``
+ """
+ self.config = _createConfig()
+ self.context = _createMailServerContext(self.config)
+ self.delivery = server.SMTPIncomingDelivery()
+
+ self.helo = None
+ self.proto = None
+ self.origin = None
+ self.user = None
+
+ def tearDown(self):
+ """Reset all TestCase instance attributes between each test run."""
+ self.helo = None
+ self.proto = None
+ self.origin = None
+ self.user = None
+
+ def _createProtocolWithHost(self, host):
+ """Mock a Protocol which has a ``host`` attribute.
+
+ We don't currently use any of the ``IProtocol`` methods of the
+ returned ``twisted.test.proto_helpers.AccumulatingProtocol``, and so
+ this could be any class, although a mocked ``IProtocol`` implementer
+ was chosen for completeness and realism's sakes.
+
+ :param str host: A domain name or IP address.
+ :rtype: :api:`twisted.test.proto_helpers.AccumulatingProtocol`
+ :returns: A Protocol instance which has its ``host`` attribute set to
+ the given **host**, so that an :api:`twisted.mail.smtp.User` can
+ be constructed with it.
+ """
+ self.proto = proto_helpers.AccumulatingProtocol()
+ self.proto.host = host
+
+ def _createUser(self, username, domain, ipaddress):
+ """Create a ``twisted.mail.smtp.User`` for testing.
+
+ :param str username: The local part of the client's email address.
+ :param str domain: The host part of the client's email address.
+ :param str ipaddress: The IP address of the client's mail server.
+ """
+ self.helo = (domain, ipaddress)
+ self._createProtocolWithHost(domain)
+ self.origin = Address('@'.join((username, domain,)))
+ self.user = User(username, self.helo, self.proto, self.origin)
+
+ def _setUpMAILFROM(self):
+ """Set up the parameters for emulating a connected client sending a
+ SMTP 'MAIL FROM:' command to us.
+
+ The default is to emulate sending: ``MAIL FROM: client at example.com``.
+ """
+ self.helo = ('localhost', '127.0.0.1')
+ self.origin = server.smtp.Address('client at example.com')
+ self.delivery.setContext(self.context)
+
+ def _setUpRCPTTO(self, username=None, domain=None, ip=None):
+ """Set up the parameters for emulating a connected client sending a
+ SMTP 'RCPT TO:' command to us.
+
+ The default is to emulate sending: ``RCPT TO: bridges at localhost``.
+ """
+ name = username if username is not None else self.config.EMAIL_USERNAME
+ host = domain if domain is not None else 'localhost'
+ addr = ip if ip is not None else '127.0.0.1'
+ self._createUser(name, host, ip)
+ self.delivery.setContext(self.context)
+
+ def test_SMTPIncomingDelivery_init(self):
+ """After calling :meth:`server.SMTPIncomingDelivery.__init__`, we should have a
+ :class:`server.SMTPIncomingDelivery` object instance.
+ """
+ self.assertIsInstance(self.delivery, server.SMTPIncomingDelivery)
+
+ def test_SMTPIncomingDelivery_setContext(self):
+ """Calling :meth:`server.SMTPIncomingDelivery.setContext` should set
+ the :ivar:`SMTPIncomingDelivery.context` attribute.
+
+ The ``SMTPIncomingDelivery.context`` should be a :class:`server.MailServerContext`,
+ and it should have relevant settings from the config file stored
+ within it.
+ """
+ self.delivery.setContext(self.context)
+ self.assertIsInstance(self.delivery.context, server.MailServerContext)
+ self.assertEqual(self.delivery.context.smtpFromAddr,
+ self.config.EMAIL_SMTP_FROM_ADDR)
+
+ def test_SMTPIncomingDelivery_receivedHeader(self):
+ """The email resulting from a SMTPIncomingDelivery, the latter received from
+ ``'client at example.com'`` should contain a header stating:
+ ``'Received: from example.com'``.
+ """
+ self._createUser('client', 'example.com', '127.0.0.1')
+ hdr = self.delivery.receivedHeader(self.helo, self.origin, [self.user,])
+ self.assertSubstring("Received: from example.com", hdr)
+
+ def test_SMTPIncomingDelivery_validateFrom(self):
+ """A valid origin should be stored as ``SMTPIncomingDelivery.fromCanonical``."""
+ self._setUpMAILFROM()
+ self.delivery.validateFrom(self.helo, self.origin)
+ self.assertEqual(self.delivery.fromCanonicalSMTP, 'example.com')
+
+ def test_SMTPIncomingDelivery_validateFrom_unsupportedDomain(self):
+ """A domain not in our canon should raise a SMTPBadSender."""
+ self._setUpMAILFROM()
+ origin = server.smtp.Address('throwing.pickles at yo.mama')
+ self.assertRaises(SMTPBadSender,
+ self.delivery.validateFrom, self.helo, origin)
+
+ def test_SMTPIncomingDelivery_validateFrom_origin_notAdressType(self):
+ """A non ``twisted.mail.smtp.Address`` origin should raise an
+ AttributeError exception.
+ """
+ self._setUpMAILFROM()
+ origin = 'throwing.pickles at yo.mama'
+ self.delivery.validateFrom(self.helo, origin)
+
+ def test_SMTPIncomingDelivery_validateTo(self):
+ """Should return a callable that results in a SMTPMessage."""
+ self._setUpRCPTTO()
+ validated = self.delivery.validateTo(self.user)
+ self.assertIsInstance(validated, types.FunctionType)
+ self.assertIsInstance(validated(), server.SMTPMessage)
+
+ def test_SMTPIncomingDelivery_validateTo_plusAddress(self):
+ """Should return a callable that results in a SMTPMessage."""
+ self._setUpRCPTTO('bridges+ar')
+ validated = self.delivery.validateTo(self.user)
+ self.assertIsInstance(validated, types.FunctionType)
+ self.assertIsInstance(validated(), server.SMTPMessage)
+
+ def test_SMTPIncomingDelivery_validateTo_badUsername_plusAddress(self):
+ """'givemebridges+zh_cn at ...' should raise an SMTPBadRcpt exception."""
+ self._setUpRCPTTO('givemebridges+zh_cn')
+ self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
+
+ def test_SMTPIncomingDelivery_validateTo_badUsername(self):
+ """A :class:`server.SMTPIncomingDelivery` which sends a SMTP
+ ``RCPT TO: yo.mama at localhost`` should raise a
+ ``twisted.mail.smtp.SMTPBadRcpt`` exception.
+ """
+ self._setUpRCPTTO('yo.mama')
+ self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
+
+ def test_SMTPIncomingDelivery_validateTo_notOurDomain(self):
+ """An SMTP ``RCPT TO: bridges at forealsi.es`` should raise an SMTPBadRcpt
+ exception.
+ """
+ self._setUpRCPTTO('bridges', 'forealsi.es')
+ self.assertRaises(SMTPBadRcpt, self.delivery.validateTo, self.user)
+
+ def test_SMTPIncomingDelivery_validateTo_subdomain(self):
+ """An SMTP ``RCPT TO: bridges at subdomain.localhost`` should be allowed.
+ """
+ self._setUpRCPTTO('bridges', 'subdomain.localhost')
+ validated = self.delivery.validateTo(self.user)
+ self.assertIsInstance(validated, types.FunctionType)
+ self.assertIsInstance(validated(), server.SMTPMessage)
+
+
+class SMTPTestCaseMixin(util.TestCaseMixin):
+ """Utility methods for use within any subclasses of
+ :api:`twisted.trial.unittest.TestCase` which test SMTP.
+
+ To use me, subclass :api:`twisted.trial.unittest.TestCase` and mix me into
+ the middle of your class inheritance declarations, like so::
+
+ class ExampleSMTPTests(SMTPTestCaseMixin, unittest.TestCase):
+ pass
+
+ and then make certain that your ``TestCase`` subclass has its ``proto``
+ and attribute assigned properly::
+
+ class ExampleSMTPTests(SMTPTestCaseMixin, unittest.TestCase):
+ def setUp(self):
+ factory = twisted.mail.smtp.SMTPIncomingServerFactory()
+ self.proto = self.factory.buildProtocol(('127.0.0.1', 0))
+
+
+ :ivar proto: A :api:`Protocol <twisted.internet.protocol.Protocol>`
+ associated with a
+ :api:`ServerFactory <twisted.internet.protocol.ServerFactory>`.
+ :ivar transport: Anything that implements
+ :api:`ITransport <twisted.internet.interfaces.ITransport>`. The default
+ is a :api:`twisted.test.proto_helpers.StringTransportWithDisconection`.
+ :ivar str smtpFromAddr: The default email address for the server.
+ """
+
+ proto = None
+ transport = proto_helpers.StringTransportWithDisconnection()
+ smtpFromAddr = None
+
+ def tearDown(self):
+ """Cleanup method after each ``test_*`` method runs; removes timed out
+ connections on the reactor and clears the :ivar:`transport`.
+ """
+ self.transport.clear() # Clear bytes from the transport.
+
+ for delay in reactor.getDelayedCalls():
+ try:
+ delay.cancel()
+ except (AlreadyCalled, AlreadyCancelled):
+ pass
+
+ def _buildEmail(self, fromAddr=None, toAddr=None, subject=None, body=None):
+ """Creates email text (including headers) for use in an SMTP DATA
+ segment. Includes the SMTP DATA EOM command ('.') at the end. If no
+ keyword arguments are given, the defaults are fairly sane.
+
+ Suitable for testing a :class:`bridgedb.email.server.SMTPIncomingServerFactory`.
+
+ :param str fromAddr: An email address for the 'From:' header.
+ :param str toAddr: An email address for the 'To:' header.
+ :param str subject: The contents of the 'Subject:' header.
+ :param str body: The contents of the email body.
+ :rtype: str
+ :returns: The email text.
+ """
+ fromAddr = fromAddr if fromAddr else 'testing at localhost'
+ toAddr = toAddr if toAddr else self.smtpFromAddr
+ subject = subject if subject else 'testing testing one two three'
+ body = body if body else 'get bridges'
+
+ contents = ['From: %s' % fromAddr,
+ 'To: %s' % toAddr,
+ 'Subject: %s' % subject,
+ '\r\n %s' % body,
+ '.'] # SMTP DATA EOM command
+ emailText = self.proto.delimiter.join(contents)
+ return emailText
+
+ def _buildSMTP(self, commands):
+ """Format a list of SMTP protocol commands into a string, using the proper
+ protocol delimiter.
+
+ :param list commands: A list of raw SMTP-protocol command lines.
+ :rtype: str
+ :returns: The string for sending those **commands**, suitable for
+ giving to a :api:`twisted.internet.Protocol.dataReceived` method.
+ """
+ data = self.proto.delimiter.join(commands) + self.proto.delimiter
+ return data
+
+ def _test(self, commands, expected, noisy=False):
+ """Send the SMTP **commands** to the ``dataReceived`` method of your
+ TestCase's protocol (this must be the `proto` attribute of your
+ `TestCase`, i.e. this uses ``TestCase.proto.dataReceived``). Next,
+ check that the substring which is **expected** to be within the
+ server's output matches what was received from :ivar`transport`.
+
+ :param list commands: A sequence of raw SMTP command lines. This will
+ automatically be passed to :meth:`_buildSMTP`.
+ :param str expected: A substring which should occur in the "server"
+ output (taken from the :ivar:`transport`).
+ :param bool noisy: If ``True``, print the conversation between the
+ "client" and the "server" in a nicely formatted manner.
+ """
+ data = self._buildSMTP(commands)
+ self.proto.dataReceived(data)
+ recv = self.transport.value()
+
+ if noisy:
+ client = data.replace('\r\n', '\r\n ')
+ server = recv.replace('\r\n', '\r\n\t\t ')
+ print('\n CLIENT --------->', '\n %s' % client)
+ print('\t\t', '<--------- SERVER', '\n\t\t %s' % server)
+
+ self.assertSubstring(expected, recv)
+
+
+class SMTPIncomingServerFactoryTests(SMTPTestCaseMixin, unittest.TestCase):
+ """Unittests for :class:`bridgedb.email.server.SMTPIncomingServerFactory`."""
+
+ def setUp(self):
+ """Set up a localhost SMTPIncomingServerFactory handler incoming SMTP
+ connections.
+ """
+ config = _createConfig()
+ context = _createMailServerContext(config)
+ factory = server.SMTPIncomingServerFactory()
+ factory.setContext(context)
+ factory.protocol.timeout = None # Otherwise the reactor gets dirty
+
+ self.smtpFromAddr = context.smtpFromAddr # 'bridges at localhost'
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.transport = proto_helpers.StringTransportWithDisconnection()
+ self.proto.setTimeout(None)
+ # Set the protocol; StringTransportWithDisconnection is a bit janky:
+ self.transport.protocol = self.proto
+ self.proto.makeConnection(self.transport)
+
+ def test_SMTPIncomingServerFactory_HELO_localhost(self):
+ """Send 'HELO localhost' to the server's transport."""
+ ip = self.transport.getPeer().host
+ self._test(['HELO localhost'],
+ "Hello %s, nice to meet you" % ip)
+
+ def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_localhost(self):
+ """Send 'MAIL FROM: human at localhost'."""
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost'],
+ "250 Sender address accepted")
+
+ def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_gethostname(self):
+ """Send 'MAIL FROM: human at hostname' for the local hostname."""
+ hostname = socket.gethostname() or "computer"
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing@%s' % hostname],
+ "250 Sender address accepted")
+
+ def test_SMTPIncomingServerFactory_MAIL_FROM_testing_at_ipaddress(self):
+ """Send 'MAIL FROM: human at ipaddr' for the loopback IP address."""
+ hostname = 'localhost'
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing@%s' % hostname],
+ "250 Sender address accepted")
+
+ def test_SMTPIncomingServerFactory_RCPT_TO_context_smtpFromAddr(self):
+ """Send 'RCPT TO:' with the context.smtpFromAddr."""
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % self.smtpFromAddr],
+ "250 Recipient address accepted")
+
+ def test_SMTPIncomingServerFactory_DATA_blank(self):
+ """A DATA command with nothing after it should receive::
+ '354 Continue'
+ in response.
+ """
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % self.smtpFromAddr,
+ "DATA"],
+ "354 Continue")
+
+ def test_SMTPIncomingServerFactory_DATA_get_help(self):
+ """A DATA command with ``'get help'`` in the email body should
+ receive::
+ '250 Delivery in progress'
+ in response.
+ """
+ emailText = self._buildEmail(body="get help")
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % self.smtpFromAddr,
+ "DATA", emailText],
+ "250 Delivery in progress",
+ noisy=True)
+
+ def test_SMTPIncomingServerFactory_DATA_get_transport_obfs3(self):
+ """A DATA command with ``'get transport obfs3'`` in the email body
+ should receive::
+ '250 Delivery in progress'
+ in response.
+ """
+ emailText = self._buildEmail(body="get transport obfs3")
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % self.smtpFromAddr,
+ "DATA", emailText],
+ "250 Delivery in progress",
+ noisy=True)
+
+ def test_SMTPIncomingServerFactory_DATA_To_bridges_plus_zh_CN(self):
+ """Test sending to 'bridges+zh_CN' address for Chinese translations."""
+ # TODO: Add tests which use '+' syntax in mailTo in order to test
+ # email translations. Do this when some strings have been translated.
+ emailTo = list(self.smtpFromAddr.partition('@'))
+ emailTo.insert(1, '+zh_CN')
+ emailTo = ''.join(emailTo)
+ emailText = self._buildEmail(toAddr=emailTo)
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % emailTo,
+ "DATA", emailText],
+ "250 Delivery in progress",
+ noisy=True)
+
+ def test_SMTPIncomingServerFactory_DATA_get_bridges_QUIT(self):
+ """Test sending 'DATA' with 'get bridges', then sending 'QUIT'."""
+ emailText = self._buildEmail()
+ self._test(['HELO localhost',
+ 'MAIL FROM: testing at localhost',
+ 'RCPT TO: %s' % self.smtpFromAddr,
+ "DATA", emailText,
+ "QUIT"],
+ "221 See you later",
+ noisy=True)
+
+
+class EmailServerServiceTests(SMTPTestCaseMixin, unittest.TestCase):
+ """Unittests for :func:`bridgedb.email.server.addServer`."""
+
+ def setUp(self):
+ """Create a MailServerContext and EmailDistributor."""
+ self.config = _createConfig()
+ self.context = _createMailServerContext(self.config)
+ self.smtpFromAddr = self.context.smtpFromAddr # 'bridges at localhost'
+ self.sched = Unscheduled()
+ self.dist = self.context.distributor
+
+ def tearDown(self):
+ """Kill all connections with fire."""
+ if self.transport:
+ self.transport.loseConnection()
+ super(EmailServerServiceTests, self).tearDown()
+ # FIXME: this is definitely not how we're supposed to do this, but it
+ # kills the DirtyReactorAggregateErrors.
+ reactor.disconnectAll()
+ reactor.runUntilCurrent()
+
+ def test_addServer(self):
+ """Call :func:`bridgedb.email.server.addServer` to test startup."""
+ factory = server.addServer(self.config, self.dist)
+ factory.timeout = None
+ factory.protocol.timeout = None # Or else the reactor gets dirty
+
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.proto.setTimeout(None)
+ # Set the transport's protocol, because
+ # StringTransportWithDisconnection is a bit janky:
+ self.transport.protocol = self.proto
+ self.proto.makeConnection(self.transport)
+
+ serverHost = socket.gethostname()
+ self._test(['HELO %s' % serverHost,
+ 'MAIL FROM: testing@%s' % serverHost,
+ 'RCPT TO: %s' % self.smtpFromAddr,
+ "DATA", self._buildEmail(body="get transport obfs3")],
+ "250 Delivery in progress",
+ noisy=True)
diff --git a/test/test_email_templates.py b/test/test_email_templates.py
new file mode 100644
index 0000000..0c74377
--- /dev/null
+++ b/test/test_email_templates.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.email.templates` module."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from io import StringIO
+from gettext import NullTranslations
+
+from twisted.mail.smtp import Address
+from twisted.trial import unittest
+
+from bridgedb.email import templates
+
+
+class EmailTemplatesTests(unittest.TestCase):
+ """Unittests for :func:`b.e.templates`."""
+
+ def setUp(self):
+ self.t = NullTranslations(StringIO(unicode('test')))
+ self.client = Address('blackhole at torproject.org')
+ self.answer = 'obfs3 1.1.1.1:1111\nobfs3 2.2.2.2:2222'
+ # This is the fingerprint of BridgeDB's offline, certification-only
+ # GnuPG key. It should be present in any responses to requests for our
+ # public keys.
+ self.offlineFingerprint = '7B78437015E63DF47BB1270ACBD97AA24E8E472E'
+
+ def shouldIncludeCommands(self, text):
+ self.assertSubstring('COMMANDs', text)
+
+ def shouldIncludeInstructions(self, text):
+ self.assertSubstring('Tor Browser', text)
+
+ def shouldIncludeBridges(self, text):
+ self.assertSubstring(self.answer, text)
+ self.assertSubstring('Here are your bridges:', text)
+
+ def shouldIncludeGreeting(self, text):
+ self.assertSubstring('Hey, blackhole!', text)
+
+ def shouldIncludeAutomationNotice(self, text):
+ self.assertSubstring('automated message', text)
+
+ def shouldIncludeKey(self, text):
+ self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', text)
+
+ def shouldIncludeFooter(self, text):
+ self.assertSubstring('rainbows, unicorns, and sparkles', text)
+
+ def test_templates_addCommands(self):
+ text = templates.addCommands(self.t)
+ self.shouldIncludeCommands(text)
+
+ def test_templates_addGreeting(self):
+ text = templates.addGreeting(self.t, self.client.local)
+ self.shouldIncludeGreeting(text)
+
+ def test_templates_addGreeting_noClient(self):
+ text = templates.addGreeting(self.t, None)
+ self.assertSubstring('Hello, friend!', text)
+
+ def test_templates_addGreeting_withWelcome(self):
+ text = templates.addGreeting(self.t, self.client.local, welcome=True)
+ self.shouldIncludeGreeting(text)
+ self.assertSubstring('Welcome to BridgeDB!', text)
+
+ def test_templates_addGreeting_trueClient(self):
+ text = templates.addGreeting(self.t, True)
+ self.assertSubstring('Hey', text)
+
+ def test_templates_addGreeting_23Client(self):
+ text = templates.addGreeting(self.t, 23)
+ self.assertSubstring('Hey', text)
+
+ def test_templates_addHowto(self):
+ text = templates.addHowto(self.t)
+ self.shouldIncludeInstructions(text)
+
+ def test_templates_addBridgeAnswer(self):
+ text = templates.addBridgeAnswer(self.t, self.answer)
+ self.shouldIncludeBridges(text)
+
+ def test_templates_addFooter(self):
+ text = templates.addFooter(self.t, self.client)
+ self.shouldIncludeFooter(text)
+
+ def test_templates_buildAnswerMessage(self):
+ text = templates.buildAnswerMessage(self.t, self.client, self.answer)
+ self.assertSubstring(self.answer, text)
+ self.shouldIncludeAutomationNotice(text)
+ self.shouldIncludeCommands(text)
+ self.shouldIncludeFooter(text)
+
+ def test_templates_buildKeyMessage(self):
+ text = templates.buildKeyMessage(self.t, self.client)
+ self.assertSubstring(self.offlineFingerprint, text)
+
+ def test_templates_buildWelcomeText(self):
+ text = templates.buildWelcomeText(self.t, self.client)
+ self.shouldIncludeGreeting(text)
+ self.assertSubstring('Welcome to BridgeDB!', text)
+ self.shouldIncludeCommands(text)
+ self.shouldIncludeFooter(text)
+
+ def test_templates_buildSpamWarning(self):
+ text = templates.buildSpamWarning(self.t, self.client)
+ self.shouldIncludeGreeting(text)
+ self.shouldIncludeAutomationNotice(text)
+ self.shouldIncludeFooter(text)
diff --git a/test/test_filters.py b/test/test_filters.py
new file mode 100644
index 0000000..73e5685
--- /dev/null
+++ b/test/test_filters.py
@@ -0,0 +1,333 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.filters`."""
+
+from __future__ import print_function
+
+import ipaddr
+
+from twisted.trial import unittest
+
+from bridgedb import filters
+from bridgedb.bridges import Bridge
+from bridgedb.bridges import PluggableTransport
+from bridgedb.crypto import getHMACFunc
+
+
+class FiltersTests(unittest.TestCase):
+ """Tests for :mod:`bridgedb.filters`."""
+
+ def setUp(self):
+ """Create a Bridge whose address is 1.1.1.1, orPort is 1111, and
+ fingerprint is 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'. Also,
+ create an HMAC function whose key is 'plasma'.
+ """
+ self.bridge = Bridge()
+ self.bridge.address = '1.1.1.1'
+ self.bridge.orPort = 1111
+ self.bridge.fingerprint = 'a' * 40
+
+ self.hmac = getHMACFunc('plasma')
+
+ def addIPv4VoltronPT(self):
+ pt = PluggableTransport('a' * 40, 'voltron', '1.1.1.1', 1111, {})
+ self.bridge.transports.append(pt)
+
+ def addIPv6VoltronPT(self):
+ pt = PluggableTransport('a' * 40, 'voltron', '2006:2222::2222', 1111, {})
+ self.bridge.transports.append(pt)
+
+ def test_bySubring_1_of_2(self):
+ """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+ should be assigned to sub-hashring 1-of-2 (in this case, using a
+ particular HMAC key), and therefore filters.bySubring(HMAC, 1, 2)
+ should return that Bridge (because it is in the sub-hashring we asked
+ for).
+ """
+ filtre = filters.bySubring(self.hmac, 1, 2)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_bySubring_2_of_2(self):
+ """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+ should be assigned to sub-hashring 1-of-2 (in this case, using a
+ particular HMAC key), and therefore filters.bySubring(HMAC, 2, 2)
+ should *not* return that Bridge (because it is in sub-hashring 1-of-2
+ and we asked for Bridges which are in sub-hashring 2-of-2).
+ """
+ filtre = filters.bySubring(self.hmac, 2, 2)
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byFilters_bySubring_byTransport_correct_subhashring_with_transport(self):
+ """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
+ Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
+ should return True.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
+ filters.byTransport('voltron')])
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byFilters_bySubring_byTransport_wrong_subhashring_with_transport(self):
+ """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
+ Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
+ should return False.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
+ filters.byTransport('voltron')])
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byFilters_bySubring_byTransport_correct_subhashring_no_transport(self):
+ """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
+ Bridge has no transports and is assigned to sub-hashring 1-of-2
+ should return False.
+ """
+ filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
+ filters.byTransport('voltron')])
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byFilters_bySubring_byTransport_wrong_subhashring_no_transport(self):
+ """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
+ Bridge has no transports and is assigned to sub-hashring 1-of-2
+ should return False.
+ """
+ filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
+ filters.byTransport('voltron')])
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byFilters_no_filters(self):
+ self.addIPv4VoltronPT()
+ filtre = filters.byFilters([])
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byIPv_ipv5(self):
+ """Calling byIPv(ipVersion=5) should default to filterint by IPv4."""
+ filtre = filters.byIPv(5)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byIPv4_address(self):
+ """A bridge with an IPv4 address for its main orPort address should
+ cause filters.byIPv4() to return True.
+ """
+ self.assertTrue(filters.byIPv4(self.bridge))
+
+ def test_byIPv4_orAddress(self):
+ """A bridge with an IPv4 address in its orAddresses address should
+ cause filters.byIPv4() to return True.
+ """
+ self.bridge.address = '2006:2222::2222'
+ self.bridge.orAddresses = [(ipaddr.IPv4Address('2.2.2.2'), 2222, 4)]
+ self.assertTrue(filters.byIPv4(self.bridge))
+
+ def test_byIPv4_none(self):
+ """A bridge with no IPv4 addresses should cause filters.byIPv4() to
+ return False.
+ """
+ self.bridge.address = ipaddr.IPv6Address('2006:2222::2222')
+ self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+ self.assertFalse(filters.byIPv4(self.bridge))
+
+ def test_byIPv6_address(self):
+ """A bridge with an IPv6 address for its main orPort address should
+ cause filters.byIPv6() to return True.
+ """
+ self.bridge.address = '2006:2222::2222'
+ self.assertTrue(filters.byIPv6(self.bridge))
+
+ def test_byIPv6_orAddress(self):
+ """A bridge with an IPv6 address in its orAddresses address should
+ cause filters.byIPv6() to return True.
+ """
+ self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+ self.assertTrue(filters.byIPv6(self.bridge))
+
+ def test_byIPv6_none(self):
+ """A bridge with no IPv6 addresses should cause filters.byIPv6() to
+ return False.
+ """
+ self.assertFalse(filters.byIPv6(self.bridge))
+
+ def test_byTransport_with_transport_ipv4(self):
+ """A bridge with an IPv4 voltron transport should cause
+ byTransport('voltron') to return True.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byTransport('voltron')
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byTransport_with_transport_ipv6(self):
+ """A bridge with an IPv6 voltron transport should cause
+ byTransport('voltron', ipVersion=6) to return True.
+ """
+ self.addIPv6VoltronPT()
+ filtre = filters.byTransport('voltron', ipVersion=6)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byTransport_with_transport_ipv6_filtering_by_ipv4(self):
+ """A bridge with an IPv6 voltron transport should cause
+ byTransport('voltron') to return True.
+ """
+ self.addIPv6VoltronPT()
+ filtre = filters.byTransport('voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byTransport_no_transports(self):
+ """A bridge without any transports should cause
+ byTransport('voltron') to return False.
+ """
+ filtre = filters.byTransport('voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byTransport_vanilla_ipv4(self):
+ """byTransport() without namimg a transport to filter by should just
+ return the bridge's IPv4 address.
+ """
+ filtre = filters.byTransport()
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byTransport_vanilla_ipv6(self):
+ """byTranspfort(ipVersion=6) without namimg a transport to filter by
+ should just return the bridge's IPv4 address.
+ """
+ self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+ filtre = filters.byTransport(ipVersion=6)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byTransport_wrong_transport(self):
+ """A bridge with only a Voltron transport should cause
+ byTransport('obfs3') to return False.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byTransport('obfs3')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_no_countryCode_with_transport_ipv4(self):
+ """A bridge with an IPv4 voltron transport should cause
+ byNotBlockedIn('voltron') to return True (because it calls
+ filters.byTransport).
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byNotBlockedIn(None, methodname='voltron')
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_no_countryCode_with_transport_ipv6(self):
+ """A bridge with an IPv6 voltron transport should cause
+ byNotBlockedIn('voltron') to return True (because it calls
+ filters.byTransport).
+ """
+ self.addIPv6VoltronPT()
+ filtre = filters.byNotBlockedIn(None, methodname='voltron', ipVersion=6)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_with_transport_ipv4(self):
+ """A bridge with an IPv4 voltron transport should cause
+ byNotBlockedIn('voltron') to return True.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byNotBlockedIn('CN', methodname='voltron')
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_with_transport_ipv4_blocked(self):
+ """A bridge with an IPv4 voltron transport which is blocked should
+ cause byNotBlockedIn('voltron') to return False.
+ """
+ self.addIPv4VoltronPT()
+ self.bridge.setBlockedIn('CN')
+ filtre = filters.byNotBlockedIn('CN', methodname='voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_with_transport_ipv6(self):
+ """A bridge with an IPv6 voltron transport should cause
+ byNotBlockedIn('voltron') to return True.
+ """
+ self.addIPv6VoltronPT()
+ filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_with_transport_ipv4_not_blocked_ipv4(self):
+ """A bridge with an IPv6 voltron transport which is not blocked in China
+ should cause byNotBlockedIn('cn', 'voltron') to return False, because
+ the IP version is wrong.
+ """
+ self.addIPv6VoltronPT()
+ filtre = filters.byNotBlockedIn('cn', 'voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_with_transport_ipv6_blocked(self):
+ """A bridge with an IPv6 voltron transport which is blocked should
+ cause byNotBlockedIn('voltron') to return False.
+ """
+ self.addIPv6VoltronPT()
+ self.bridge.setBlockedIn('CN')
+ filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_no_countryCode_no_transports(self):
+ """A bridge without any transports should cause
+ byNotBlockedIn('voltron') to return False (because it calls
+ filters.byTransport('voltron')).
+ """
+ filtre = filters.byNotBlockedIn(None, methodname='voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_no_transports(self):
+ """A bridge without any transports should cause
+ byNotBlockedIn('cn', 'voltron') to return False.
+ """
+ filtre = filters.byNotBlockedIn('cn', methodname='voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_no_transports_blocked(self):
+ """A bridge without any transports which is also blocked should cause
+ byNotBlockedIn('voltron') to return False.
+ """
+ self.bridge.setBlockedIn('cn')
+ filtre = filters.byNotBlockedIn('cn', methodname='voltron')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_wrong_transport(self):
+ """A bridge with only a Voltron transport should cause
+ byNotBlockedIn('obfs3') to return False.
+ """
+ self.addIPv4VoltronPT()
+ filtre = filters.byNotBlockedIn('cn', methodname='obfs3')
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_ipv5(self):
+ """Calling byNotBlockedIn([â¦], ipVersion=5) should default to IPv4."""
+ self.bridge.setBlockedIn('ru')
+ filtre = filters.byNotBlockedIn('cn', ipVersion=5)
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_vanilla_not_blocked(self):
+ """Calling byNotBlockedIn('vanilla') should return the IPv4 vanilla
+ address, if it is not blocked.
+ """
+ self.bridge.setBlockedIn('ru')
+ filtre = filters.byNotBlockedIn('cn', methodname='vanilla')
+ self.assertTrue(filtre(self.bridge))
+
+ def test_byNotBlockedIn_vanilla_not_blocked_ipv6(self):
+ """Calling byNotBlockedIn('vanilla', ipVersion=6) should not return the
+ IPv4 vanilla address, even if it is not blocked, because it has the
+ wrong IP version.
+ """
+ self.bridge.setBlockedIn('ru')
+ filtre = filters.byNotBlockedIn('cn', methodname='vanilla', ipVersion=6)
+ self.assertFalse(filtre(self.bridge))
+
+ def test_byNotBlockedIn_vanilla_blocked(self):
+ """Calling byNotBlockedIn('vanilla') should not return the IPv4 vanilla
+ address, if it is blocked.
+ """
+ self.bridge.setBlockedIn('ru')
+ filtre = filters.byNotBlockedIn('ru', methodname='vanilla')
+ self.assertFalse(filtre(self.bridge))
diff --git a/test/test_geo.py b/test/test_geo.py
new file mode 100644
index 0000000..5e5547b
--- /dev/null
+++ b/test/test_geo.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.geo` module."""
+
+import ipaddr
+
+from twisted.trial import unittest
+
+from bridgedb import geo
+
+
+class GeoTests(unittest.TestCase):
+ """Unittests for the :mod:`bridgedb.geo` module."""
+
+ def setUp(self):
+ self._orig_geoip = geo.geoip
+ self._orig_geoipv6 = geo.geoipv6
+
+ self.ipv4 = ipaddr.IPAddress('38.229.72.16')
+ self.ipv6 = ipaddr.IPAddress('2620:0:6b0:b:1a1a:0:26e5:4810')
+
+ # WARNING: this is going to fail if the torproject.org A and AAAA
+ # records above are reassigned to another host, or are randomly
+ # geolocated to somewhere other than where they are currently
+ # geolocated (in the US):
+ self.expectedCC = 'US'
+
+ def tearDown(self):
+ geo.geoip = self._orig_geoip
+ geo.geoipv6 = self._orig_geoipv6
+
+ def test_geo_getCountryCode_ipv4_str(self):
+ """Should return None since the IP isn't an ``ipaddr.IPAddress``."""
+ self.assertIsNone(geo.getCountryCode(str(self.ipv4)))
+
+ def test_geo_getCountryCode_ipv4_no_geoip_loopback(self):
+ """Should return None since this IP isn't geolocatable (hopefully ever)."""
+ ipv4 = ipaddr.IPAddress('127.0.0.1')
+ self.assertIsNone(geo.getCountryCode(ipv4))
+
+ def test_geo_getCountryCode_ipv4_class(self):
+ """Should return the CC since the IP is an ``ipaddr.IPAddress``."""
+ cc = geo.getCountryCode(self.ipv4)
+ self.assertIsNotNone(cc)
+ self.assertIsInstance(cc, basestring)
+ self.assertEqual(len(cc), 2)
+ self.assertEqual(cc, self.expectedCC)
+
+ def test_geo_getCountryCode_ipv6_str(self):
+ """Should return None since the IP isn't an ``ipaddr.IPAddress``."""
+ self.assertIsNone(geo.getCountryCode(str(self.ipv6)))
+
+ def test_geo_getCountryCode_ipv6_no_geoip_record(self):
+ """Should return None since this IP isn't geolocatable (yet)."""
+ ipv6 = ipaddr.IPAddress('20::72a:e224:44d8:a606:4115')
+ self.assertIsNone(geo.getCountryCode(ipv6))
+
+ def test_geo_getCountryCode_ipv6_no_geoip_link_local(self):
+ """Should return None since this IP isn't geolocatable (hopefully ever)."""
+ ipv6 = ipaddr.IPAddress('ff02::')
+ self.assertIsNone(geo.getCountryCode(ipv6))
+
+ def test_geo_getCountryCode_ipv6_no_geoip_loopback(self):
+ """Should return None since this IP isn't geolocatable (hopefully ever)."""
+ ipv6 = ipaddr.IPAddress('::1')
+ self.assertIsNone(geo.getCountryCode(ipv6))
+
+ def test_geo_getCountryCode_ipv6_class(self):
+ """Should return the CC since the IP is an ``ipaddr.IPAddress``."""
+ cc = geo.getCountryCode(self.ipv6)
+ self.assertIsNotNone(cc)
+ self.assertIsInstance(cc, basestring)
+ self.assertEqual(len(cc), 2)
+ self.assertEqual(cc, self.expectedCC)
+
+ def test_geo_getCountryCode_no_geoip(self):
+ """When missing the geo.geoip database, getCountryCode() should return
+ None.
+ """
+ geo.geoip = None
+ self.assertIsNone(geo.getCountryCode(self.ipv4))
+
+ def test_geo_getCountryCode_no_geoipv6(self):
+ """When missing the geo.geoipv6 database, getCountryCode() should
+ return None.
+ """
+ geo.geoipv6 = None
+ self.assertIsNone(geo.getCountryCode(self.ipv4))
diff --git a/test/test_https.py b/test/test_https.py
new file mode 100644
index 0000000..1e0c778
--- /dev/null
+++ b/test/test_https.py
@@ -0,0 +1,415 @@
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: trygve <tor-dev at lists.torproject.org>
+# :copyright: (c) 2014, trygve
+# (c) 2014-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Integration tests for BridgeDB's HTTPS Distributor.
+
+These tests use `mechanize`_ and `BeautifulSoup`_, and require a BridgeDB
+instance to have been started in a separate process. To see how a BridgeDB is
+started for our CI infrastructure from a fresh clone of this repository, see
+the "before_script" section of the `.travis.yml` file in the top level of this
+repository.
+
+.. _mechanize: https://pypi.python.org/pypi/mechanize/
+ http://wwwsearch.sourceforge.net/mechanize/
+.. _BeautifulSoup:
+ http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html
+"""
+
+from __future__ import print_function
+
+import ipaddr
+import mechanize
+import os
+
+from BeautifulSoup import BeautifulSoup
+
+from twisted.trial import unittest
+from twisted.trial.unittest import FailTest
+from twisted.trial.unittest import SkipTest
+
+from bridgedb.test.util import processExists
+from bridgedb.test.util import getBridgeDBPID
+
+HTTP_ROOT = 'http://127.0.0.1:6788'
+CAPTCHA_RESPONSE = 'Tvx74Pmy'
+
+
+class HTTPTests(unittest.TestCase):
+ def setUp(self):
+ here = os.getcwd()
+ topdir = here.rstrip('_trial_temp')
+ self.rundir = os.path.join(topdir, 'run')
+ self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
+ self.pid = getBridgeDBPID(self.pidfile)
+ self.br = None
+
+ def tearDown(self):
+ self.br = None
+
+ def openBrowser(self):
+ # use mechanize to open the BridgeDB website in its browser
+ self.br = mechanize.Browser()
+ # prevents 'HTTP Error 403: request disallowed by robots.txt'
+ self.br.set_handle_robots(False)
+ self.br.open(HTTP_ROOT)
+
+ # -------------- Home/Root page
+ self.assertTrue(self.br.viewing_html())
+ self.assertEquals(self.br.response().geturl(), HTTP_ROOT)
+ self.assertEquals(self.br.title(), "BridgeDB")
+ return self.br
+
+ def goToOptionsPage(self):
+ # check that we are on the root page
+ self.assertTrue(self.br.viewing_html())
+ self.assertEquals(self.br.response().geturl(), HTTP_ROOT)
+
+ # follow the link with the word 'bridges' in it.
+ # Could also use: text='bridges'
+ # Could also use: url='/options'
+ self.br.follow_link(text_regex='bridges')
+
+ # ------------- Options
+ self.assertEquals(self.br.response().geturl(), HTTP_ROOT + "/options")
+ return self.br
+
+ def submitOptions(self, transport, ipv6, captchaResponse):
+ # check that we are on the options page
+ self.assertEquals(self.br.response().geturl(), HTTP_ROOT + "/options")
+
+ # At this point, we'd like to be able to set some values in
+ # the 'advancedOptions' form. Unfortunately the HTML form
+ # does not define a 'name' attribute, so the we have to rely on
+ # the fact that this is the only form on the page and will therefore
+ # always exist at index 0.
+ #br.select_form(name="advancedOptions")
+ self.br.select_form(nr=0)
+
+ # change the pluggable transport to something else
+ self.br.form['transport'] = [transport]
+ if ipv6:
+ self.br.form['ipv6'] = ['yes']
+ self.br.submit()
+
+ # ------------- Captcha
+ EXPECTED_URL = HTTP_ROOT + "/bridges?transport=%s" % transport
+ if ipv6:
+ EXPECTED_URL += "&ipv6=yes"
+ self.assertEquals(self.br.response().geturl(), EXPECTED_URL)
+
+ # As on the previous page, the form does not define a 'name'
+ # attribute, forcing us to use the index of the form, i.e. 0
+ #self.br.select_form(name="captchaSubmission")
+ self.br.select_form(nr=0)
+
+ # input the required captcha response. There is only one captcha
+ # defined by default, so this should always be accepted. Note this
+ # will not be possible to automate if used with a third-party CAPTCHA
+ # systems (e.g. reCAPTCHA)
+ self.br.form['captcha_response_field'] = captchaResponse
+ captcha_response = self.br.submit()
+
+ # ------------- Results
+ # URL should be the same as last time
+ self.assertEquals(self.br.response().geturl(), EXPECTED_URL)
+ soup = BeautifulSoup(captcha_response.read())
+ return soup
+
+ def getBridgeLinesFromSoup(self, soup, fieldsPerBridge):
+ """We're looking for something like this in the response::
+ <div class="bridge-lines">
+ obfs2 175.213.252.207:11125 5c6da7d927460317c6ff5420b75c2d0f431f18dd
+ </div>
+ """
+ bridges = []
+ soup = soup.findAll(attrs={'class' : 'bridge-lines'})
+ self.assertTrue(soup, "Could not find <div class='bridge-lines'>!")
+
+ for portion in soup:
+ br_tags = portion.findChildren('br')
+ bridge_lines = set(portion.contents).difference(set(br_tags))
+ for bridge_line in bridge_lines:
+ bridge_line = bridge_line.strip()
+ if bridge_line:
+ fields = bridge_line.split()
+ bridges.append(fields)
+
+ self.assertTrue(len(bridges) > 0, "Found no bridge lines in %s" % soup)
+
+ for bridge in bridges:
+ self.assertEquals(len(bridge), fieldsPerBridge,
+ "Expected %d fields in bridge line %s"
+ % (fieldsPerBridge, bridge))
+ return bridges
+
+ def test_get_obfs2_ipv4(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs2'
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=3)
+ for bridge in bridges:
+ pt = bridge[0]
+ self.assertEquals(PT, pt)
+
+ def test_get_obfs3_ipv4(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs3'
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=3)
+ for bridge in bridges:
+ pt = bridge[0]
+ self.assertEquals(PT, pt)
+
+ def test_get_vanilla_ipv4(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = '0'
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=2)
+ for bridge in bridges:
+ self.assertTrue(bridge != None)
+ addr = bridge[0].rsplit(':', 1)[0]
+ self.assertIsInstance(ipaddr.IPAddress(addr), ipaddr.IPv4Address)
+
+ def test_get_vanilla_ipv6(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = '0'
+ soup = self.submitOptions(transport=PT, ipv6=True,
+ captchaResponse=CAPTCHA_RESPONSE)
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=2)
+ for bridge in bridges:
+ self.assertTrue(bridge != None)
+ addr = bridge[0].rsplit(':', 1)[0].strip('[]')
+ self.assertIsInstance(ipaddr.IPAddress(addr), ipaddr.IPv6Address)
+
+ def test_get_scramblesuit_ipv4(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'scramblesuit'
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=4)
+ for bridge in bridges:
+ pt = bridge[0]
+ password = bridge[-1]
+ self.assertEquals(PT, pt)
+ self.assertTrue(password.find("password=") != -1,
+ "Password field missing expected text")
+
+ def test_get_obfs4_ipv4(self):
+ """Try asking for obfs4 bridges, and check that the PT arguments in the
+ returned bridge lines were space-separated.
+
+ This is a regression test for #12932, see
+ https://bugs.torproject.org/12932.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs4'
+
+ try:
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ except ValueError as error:
+ if 'non-disabled' in str(error):
+ raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
+
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
+ for bridge in bridges:
+ pt = bridge[0]
+ ptArgs = bridge[-3:]
+ self.assertEquals(PT, pt)
+ self.assertTrue(len(ptArgs) == 3,
+ ("Expected obfs4 bridge line to have 3 PT args, "
+ "found %d instead: %s") % (len(ptArgs), ptArgs))
+
+ def test_get_obfs4_ipv4_iatmode(self):
+ """Ask for obfs4 bridges and check that there is an 'iat-mode' PT
+ argument in the bridge lines.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs4'
+
+ try:
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ except ValueError as error:
+ if 'non-disabled' in str(error):
+ raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
+
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
+ for bridge in bridges:
+ ptArgs = bridge[-3:]
+ hasIATMode = False
+ for arg in ptArgs:
+ if 'iat-mode' in arg:
+ hasIATMode = True
+
+ self.assertTrue(hasIATMode,
+ "obfs4 bridge line is missing 'iat-mode' PT arg.")
+
+ def test_get_obfs4_ipv4_publickey(self):
+ """Ask for obfs4 bridges and check that there is an 'public-key' PT
+ argument in the bridge lines.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs4'
+
+ try:
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ except ValueError as error:
+ if 'non-disabled' in str(error):
+ raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
+
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
+ for bridge in bridges:
+ ptArgs = bridge[-3:]
+ hasPublicKey = False
+ for arg in ptArgs:
+ if 'public-key' in arg:
+ hasPublicKey = True
+
+ self.assertTrue(hasPublicKey,
+ "obfs4 bridge line is missing 'public-key' PT arg.")
+
+ def test_get_obfs4_ipv4_nodeid(self):
+ """Ask for obfs4 bridges and check that there is an 'node-id' PT
+ argument in the bridge lines.
+ """
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ else:
+ raise SkipTest(("The mechanize tests cannot handle self-signed "
+ "TLS certificates, and thus require opening "
+ "another port for running a plaintext HTTP-only "
+ "BridgeDB webserver. Because of this, these tests "
+ "are only run on CI servers."))
+
+ self.openBrowser()
+ self.goToOptionsPage()
+
+ PT = 'obfs4'
+
+ try:
+ soup = self.submitOptions(transport=PT, ipv6=False,
+ captchaResponse=CAPTCHA_RESPONSE)
+ except ValueError as error:
+ if 'non-disabled' in str(error):
+ raise SkipTest("Pluggable Transport obfs4 is currently disabled.")
+
+ bridges = self.getBridgeLinesFromSoup(soup, fieldsPerBridge=6)
+ for bridge in bridges:
+ ptArgs = bridge[-3:]
+ hasNodeID = False
+ for arg in ptArgs:
+ if 'node-id' in arg:
+ hasNodeID = True
+
+ self.assertTrue(hasNodeID,
+ "obfs4 bridge line is missing 'node-id' PT arg.")
diff --git a/test/test_https_distributor.py b/test/test_https_distributor.py
new file mode 100644
index 0000000..83f2503
--- /dev/null
+++ b/test/test_https_distributor.py
@@ -0,0 +1,405 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.https.distributor`."""
+
+from __future__ import print_function
+
+import ipaddr
+import logging
+import random
+
+from twisted.trial import unittest
+
+from bridgedb.Bridges import BridgeRing
+from bridgedb.Bridges import BridgeRingParameters
+from bridgedb.filters import byIPv4
+from bridgedb.filters import byIPv6
+from bridgedb.https import distributor
+from bridgedb.https.request import HTTPSBridgeRequest
+from bridgedb.proxy import ProxySet
+from bridgedb.test.util import randomValidIPv4String
+from bridgedb.test.util import generateFakeBridges
+from bridgedb.test.https_helpers import DummyRequest
+
+logging.disable(50)
+
+
+BRIDGES = generateFakeBridges()
+
+
+class HTTPSDistributorTests(unittest.TestCase):
+ """Tests for :class:`HTTPSDistributor`."""
+
+ def setUp(self):
+ self.key = 'aQpeOFIj8q20s98awfoiq23rpOIjFaqpEWFoij1X'
+ self.bridges = BRIDGES
+
+ def tearDown(self):
+ """Reset all bridge blocks in between test method runs."""
+ for bridge in self.bridges:
+ bridge._blockedIn = {}
+
+ def coinFlip(self):
+ return bool(random.getrandbits(1))
+
+ def randomClientRequest(self):
+ bridgeRequest = HTTPSBridgeRequest(addClientCountryCode=False)
+ bridgeRequest.client = randomValidIPv4String()
+ bridgeRequest.isValid(True)
+ bridgeRequest.generateFilters()
+ return bridgeRequest
+
+ def randomClientRequestForNotBlockedIn(self, cc):
+ httpRequest = DummyRequest([''])
+ httpRequest.args.update({'unblocked': [cc]})
+ bridgeRequest = self.randomClientRequest()
+ bridgeRequest.withoutBlockInCountry(httpRequest)
+ bridgeRequest.generateFilters()
+ return bridgeRequest
+
+ def test_HTTPSDistributor_init_with_proxies(self):
+ """The HTTPSDistributor, when initialised with proxies, should add an
+ extra hashring for proxy users.
+ """
+ dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
+ self.assertIsNotNone(dist.proxies)
+ self.assertGreater(dist.proxySubring, 0)
+ self.assertEqual(dist.proxySubring, 4)
+ self.assertEqual(dist.totalSubrings, 4)
+
+ def test_HTTPSDistributor_bridgesPerResponse_120(self):
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:120]]
+ self.assertEqual(dist.bridgesPerResponse(), 3)
+
+ def test_HTTPSDistributor_bridgesPerResponse_100(self):
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.hashring.insert(bridge) for bridge in self.bridges[:100]]
+ self.assertEqual(dist.bridgesPerResponse(), 3)
+
+ def test_HTTPSDistributor_bridgesPerResponse_50(self):
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:60]]
+ self.assertEqual(dist.bridgesPerResponse(), 2)
+
+ def test_HTTPSDistributor_bridgesPerResponse_15(self):
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:15]]
+ self.assertEqual(dist.bridgesPerResponse(), 1)
+
+ def test_HTTPSDistributor_bridgesPerResponse_100_max_5(self):
+ dist = distributor.HTTPSDistributor(3, self.key)
+ dist._bridgesPerResponseMax = 5
+ [dist.insert(bridge) for bridge in self.bridges[:100]]
+ self.assertEqual(dist.bridgesPerResponse(), 5)
+
+ def test_HTTPSDistributor_getSubnet_usingProxy(self):
+ """HTTPSDistributor.getSubnet(usingProxy=True) should return a proxy
+ group number.
+ """
+ clientRequest = self.randomClientRequest()
+ expectedGroup = (int(ipaddr.IPAddress(clientRequest.client)) % 4) + 1
+ subnet = distributor.HTTPSDistributor.getSubnet(clientRequest.client, usingProxy=True)
+ self.assertTrue(subnet.startswith('proxy-group-'))
+ self.assertEqual(int(subnet[-1]), expectedGroup)
+
+ def test_HTTPSDistributor_mapSubnetToSubring_usingProxy(self):
+ """HTTPSDistributor.mapSubnetToSubring() when the client was using a
+ proxy should map the client to the proxy subhashring.
+ """
+ dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
+ subnet = 'proxy-group-3'
+ subring = dist.mapSubnetToSubring(subnet, usingProxy=True)
+ self.assertEqual(subring, dist.proxySubring)
+
+ def test_HTTPSDistributor_mapSubnetToSubring_with_proxies(self):
+ """HTTPSDistributor.mapSubnetToSubring() when the client wasn't using
+ a proxy, but the distributor does have some known proxies and a
+ proxySubring, should not map the client to the proxy subhashring.
+ """
+ dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
+ # Note that if they were actually from a proxy, their subnet would be
+ # something like "proxy-group-3".
+ subnet = '15.1.0.0/16'
+ subring = dist.mapSubnetToSubring(subnet, usingProxy=False)
+ self.assertNotEqual(subring, dist.proxySubring)
+
+ def test_HTTPSDistributor_prepopulateRings_with_proxies(self):
+ """An HTTPSDistributor with proxies should prepopulate two extra
+ subhashrings (one for each of HTTP-Proxy-IPv4 and HTTP-Proxy-IPv6).
+ """
+ dist = distributor.HTTPSDistributor(3, self.key, ProxySet(['1.1.1.1', '2.2.2.2']))
+ [dist.insert(bridge) for bridge in self.bridges]
+ dist.prepopulateRings()
+ self.assertEqual(len(dist.hashring.filterRings), 8)
+
+ def test_HTTPSDistributor_prepopulateRings_without_proxies(self):
+ """An HTTPSDistributor without proxies should prepopulate
+ totalSubrings * 2 subrings.
+ """
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges]
+ dist.prepopulateRings()
+ self.assertEqual(len(dist.hashring.filterRings), 6)
+
+ ipv4subrings = []
+ ipv6subrings = []
+
+ for subringName, (filters, subring) in dist.hashring.filterRings.items():
+ if 'IPv4' in subringName:
+ ipv6subrings.append(subring)
+ if 'IPv6' in subringName:
+ ipv6subrings.append(subring)
+
+ self.assertEqual(len(ipv4subrings), len(ipv6subrings))
+
+ def test_HTTPSDistributor_getBridges_with_blocked_bridges(self):
+ dist = distributor.HTTPSDistributor(1, self.key)
+ bridges = self.bridges[:]
+
+ for bridge in bridges:
+ bridge.setBlockedIn('cn')
+
+ [dist.insert(bridge) for bridge in bridges]
+
+ for _ in range(5):
+ clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
+ b = dist.getBridges(clientRequest1, 1)
+ self.assertEqual(len(b), 0)
+
+ clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
+ b = dist.getBridges(clientRequest2, 1)
+ self.assertEqual(len(b), 3)
+
+ def test_HTTPSDistributor_getBridges_with_some_blocked_bridges(self):
+ dist = distributor.HTTPSDistributor(1, self.key)
+ bridges = self.bridges[:]
+
+ blockedCN = []
+ blockedIR = []
+
+ for bridge in bridges:
+ if self.coinFlip():
+ bridge.setBlockedIn('cn')
+ blockedCN.append(bridge.fingerprint)
+
+ if self.coinFlip():
+ bridge.setBlockedIn('ir')
+ blockedIR.append(bridge.fingerprint)
+
+ [dist.insert(bridge) for bridge in bridges]
+
+ for _ in range(5):
+ clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
+ bridges = dist.getBridges(clientRequest1, 1)
+ for b in bridges:
+ self.assertFalse(b.isBlockedIn('cn'))
+ self.assertNotIn(b.fingerprint, blockedCN)
+ # The client *should* have gotten some bridges still.
+ self.assertGreater(len(bridges), 0)
+
+ clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
+ bridges = dist.getBridges(clientRequest2, 1)
+ for b in bridges:
+ self.assertFalse(b.isBlockedIn('ir'))
+ self.assertNotIn(b.fingerprint, blockedIR)
+ self.assertGreater(len(bridges), 0)
+
+ def test_HTTPSDistributor_getBridges_with_varied_blocked_bridges(self):
+ dist = distributor.HTTPSDistributor(1, self.key)
+ bridges = self.bridges[:]
+
+ for bridge in bridges:
+ # Pretend that China blocks all vanilla bridges:
+ bridge.setBlockedIn('cn', methodname='vanilla')
+ # Pretend that China blocks all obfs2:
+ bridge.setBlockedIn('cn', methodname='obfs2')
+ # Pretend that China blocks some obfs3:
+ if self.coinFlip():
+ bridge.setBlockedIn('cn', methodname='obfs3')
+
+ [dist.insert(bridge) for bridge in bridges]
+
+ for i in xrange(5):
+ bridgeRequest1 = self.randomClientRequestForNotBlockedIn('cn')
+ bridgeRequest1.transports.append('obfs2')
+ bridgeRequest1.generateFilters()
+ # We shouldn't get any obfs2 bridges, since they're all blocked in
+ # China:
+ bridges = dist.getBridges(bridgeRequest1, "faketimestamp")
+ self.assertEqual(len(bridges), 0)
+
+ bridgeRequest2 = self.randomClientRequestForNotBlockedIn('cn')
+ bridgeRequest2.transports.append('obfs3')
+ bridgeRequest2.generateFilters()
+ # We probably will get at least one bridge back! It's pretty
+ # unlikely to lose a coin flip 500 times in a row.
+ bridges = dist.getBridges(bridgeRequest2, "faketimestamp")
+ self.assertGreater(len(bridges), 0)
+
+ bridgeRequest3 = self.randomClientRequestForNotBlockedIn('nl')
+ bridgeRequest3.transports.append('obfs3')
+ bridgeRequest3.generateFilters()
+ # We should get bridges, since obfs3 isn't blocked in netherlands:
+ bridges = dist.getBridges(bridgeRequest3, "faketimestamp")
+ self.assertGreater(len(bridges), 0)
+
+ def test_HTTPSDistributor_getBridges_with_proxy_and_nonproxy_users(self):
+ """An HTTPSDistributor should give separate bridges to proxy users."""
+ proxies = ProxySet(['.'.join(['1.1.1', str(x)]) for x in range(1, 256)])
+ dist = distributor.HTTPSDistributor(3, self.key, proxies)
+ [dist.insert(bridge) for bridge in self.bridges]
+
+ for _ in range(10):
+ bridgeRequest1 = self.randomClientRequest()
+ bridgeRequest1.client = '.'.join(['1.1.1', str(random.randrange(1, 255))])
+
+ bridgeRequest2 = self.randomClientRequest()
+ bridgeRequest2.client = '.'.join(['9.9.9', str(random.randrange(1, 255))])
+
+ n1 = dist.getBridges(bridgeRequest1, 1)
+ n2 = dist.getBridges(bridgeRequest2, 1)
+
+ self.assertGreater(len(n1), 0)
+ self.assertGreater(len(n2), 0)
+
+ for b in n1:
+ self.assertNotIn(b, n2)
+ for b in n2:
+ self.assertNotIn(b, n1)
+
+ def test_HTTPSDistributor_getBridges_same_bridges_to_same_client(self):
+ """The same client asking for bridges from the HTTPSDistributor
+ multiple times in a row should get the same bridges in response each
+ time.
+ """
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ bridgeRequest = self.randomClientRequest()
+ responses = {}
+ for i in range(5):
+ responses[i] = dist.getBridges(bridgeRequest, 1)
+ for i in range(4):
+ self.assertItemsEqual(responses[i], responses[i+1])
+
+ def test_HTTPSDistributor_getBridges_with_BridgeRingParameters(self):
+ param = BridgeRingParameters(needPorts=[(443, 1)])
+ dist = distributor.HTTPSDistributor(3, self.key, answerParameters=param)
+
+ bridges = self.bridges[:32]
+ for b in self.bridges:
+ b.orPort = 443
+
+ [dist.insert(bridge) for bridge in bridges]
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ for _ in xrange(32):
+ bridgeRequest = self.randomClientRequest()
+ answer = dist.getBridges(bridgeRequest, 1)
+ count = 0
+ fingerprints = {}
+ for bridge in answer:
+ fingerprints[bridge.identity] = 1
+ if bridge.orPort == 443:
+ count += 1
+ self.assertEquals(len(fingerprints), len(answer))
+ self.assertGreater(len(fingerprints), 0)
+ self.assertTrue(count >= 1)
+
+ def test_HTTPSDistributor_getBridges_ipv4_ipv6(self):
+ """Asking for bridge addresses which are simultaneously IPv4 and IPv6
+ (in that order) should return IPv4 bridges.
+ """
+ dist = distributor.HTTPSDistributor(1, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ bridgeRequest = self.randomClientRequest()
+ bridgeRequest.withIPv4()
+ bridgeRequest.filters.append(byIPv6)
+ bridgeRequest.generateFilters()
+
+ bridges = dist.getBridges(bridgeRequest, 1)
+ self.assertEqual(len(bridges), 3)
+
+ bridge = random.choice(bridges)
+ bridgeLine = bridge.getBridgeLine(bridgeRequest)
+ addrport, fingerprint = bridgeLine.split()
+ address, port = addrport.rsplit(':', 1)
+ address = address.strip('[]')
+ self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
+ self.assertIsNotNone(byIPv4(random.choice(bridges)))
+
+ def test_HTTPSDistributor_getBridges_ipv6_ipv4(self):
+ """Asking for bridge addresses which are simultaneously IPv6 and IPv4
+ (in that order) should return IPv6 bridges.
+ """
+ dist = distributor.HTTPSDistributor(1, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ bridgeRequest = self.randomClientRequest()
+ bridgeRequest.withIPv6()
+ bridgeRequest.generateFilters()
+ bridgeRequest.filters.append(byIPv4)
+
+ bridges = dist.getBridges(bridgeRequest, 1)
+ self.assertEqual(len(bridges), 3)
+
+ bridge = random.choice(bridges)
+ bridgeLine = bridge.getBridgeLine(bridgeRequest)
+ addrport, fingerprint = bridgeLine.split()
+ address, port = addrport.rsplit(':', 1)
+ address = address.strip('[]')
+ self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
+ self.assertIsNotNone(byIPv6(random.choice(bridges)))
+
+ def test_HTTPSDistributor_getBridges_ipv6(self):
+ """A request for IPv6 bridges should return IPv6 bridges."""
+ dist = distributor.HTTPSDistributor(3, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ for i in xrange(500):
+ bridgeRequest = self.randomClientRequest()
+ bridgeRequest.withIPv6()
+ bridgeRequest.generateFilters()
+
+ bridges = dist.getBridges(bridgeRequest, "faketimestamp")
+ self.assertTrue(type(bridges) is list)
+ self.assertGreater(len(bridges), 0)
+
+ bridge = random.choice(bridges)
+ bridgeLine = bridge.getBridgeLine(bridgeRequest)
+ addrport, fingerprint = bridgeLine.split()
+ address, port = addrport.rsplit(':', 1)
+ address = address.strip('[]')
+ self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
+ self.assertIsNotNone(byIPv6(random.choice(bridges)))
+
+ def test_HTTPSDistributor_getBridges_ipv4(self):
+ """A request for IPv4 bridges should return IPv4 bridges."""
+ dist = distributor.HTTPSDistributor(1, self.key)
+ [dist.insert(bridge) for bridge in self.bridges[:250]]
+
+ for i in xrange(500):
+ bridgeRequest = self.randomClientRequest()
+ bridgeRequest.generateFilters()
+
+ bridges = dist.getBridges(bridgeRequest, "faketimestamp")
+ self.assertTrue(type(bridges) is list)
+ self.assertGreater(len(bridges), 0)
+
+ bridge = random.choice(bridges)
+ bridgeLine = bridge.getBridgeLine(bridgeRequest)
+ addrport, fingerprint = bridgeLine.split()
+ address, port = addrport.rsplit(':', 1)
+ self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
+ self.assertIsNotNone(byIPv4(random.choice(bridges)))
diff --git a/test/test_https_request.py b/test/test_https_request.py
new file mode 100644
index 0000000..c39662f
--- /dev/null
+++ b/test/test_https_request.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+from twisted.trial import unittest
+
+from bridgedb.bridgerequest import IRequestBridges
+from bridgedb.https import request
+
+
+class MockRequest(object):
+ def __init__(self, args):
+ self.args = args
+
+
+class HTTPSBridgeRequestTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.https.request.HTTPSBridgeRequest`."""
+
+ def setUp(self):
+ """Setup test run."""
+ self.request = request.HTTPSBridgeRequest()
+
+ def test_HTTPSBridgeRequest_implements_IRequestBridges(self):
+ """HTTPSBridgeRequest should implement IRequestBridges interface."""
+ self.assertTrue(IRequestBridges.implementedBy(request.HTTPSBridgeRequest))
+
+ def test_HTTPSBridgeRequest_withIPversion(self):
+ """HTTPSBridgeRequest.withIPversion({ipv6=[â¦]}) should store that the
+ client wanted IPv6 bridges."""
+ parameters = {'ipv6': 'wooooooooo'}
+ self.request.withIPversion(parameters)
+
+ def test_HTTPSBridgeRequest_withoutBlockInCountry_IR(self):
+ """HTTPSBridgeRequest.withoutBlockInCountry() should add the country CC
+ to the ``notBlockedIn`` attribute.
+ """
+ httprequest = MockRequest({'unblocked': ['ir']})
+ self.request.withoutBlockInCountry(httprequest)
+ self.assertIn('ir', self.request.notBlockedIn)
+
+ def test_HTTPSBridgeRequest_withoutBlockInCountry_US(self):
+ """HTTPSBridgeRequest.withoutBlockInCountry() should add the country CC
+ to the ``notBlockedIn`` attribute (and not any other countries).
+ """
+ httprequest = MockRequest({'unblocked': ['us']})
+ self.request.withoutBlockInCountry(httprequest)
+ self.assertNotIn('ir', self.request.notBlockedIn)
+
+ def test_HTTPSBridgeRequest_withoutBlockInCountry_no_addClientCountryCode(self):
+ """HTTPSBridgeRequest.withoutBlockInCountry(), when
+ addClientCountryCode=False, shouldn't add the client's country code to the
+ ``notBlockedIn`` attribute.
+ """
+ httprequest = MockRequest({'unblocked': ['nl']})
+ self.request = request.HTTPSBridgeRequest(addClientCountryCode=False)
+ self.request.client = '5.5.5.5'
+ self.request.withoutBlockInCountry(httprequest)
+ self.assertItemsEqual(['nl'], self.request.notBlockedIn)
+
+ def test_HTTPSBridgeRequest_withoutBlockInCountry_bad_params(self):
+ """HTTPSBridgeRequest.withoutBlockInCountry() should stop processing if
+ the request had a bad "unblocked" parameter.
+ """
+ httprequest = MockRequest({'unblocked': [3,]})
+ self.request.withoutBlockInCountry(httprequest)
+ self.assertNotIn('IR', self.request.notBlockedIn)
+
+ def test_HTTPSBridgeRequest_withPluggableTransportType(self):
+ """HTTPSBridgeRequest.withPluggableTransportType() should add the
+ pluggable transport type to the ``transport`` attribute.
+ """
+ httprequest = MockRequest({'transport': ['huggable_transport']})
+ self.request.withPluggableTransportType(httprequest.args)
+ self.assertIn('huggable_transport', self.request.transports)
+
+ def test_HTTPSBridgeRequest_withPluggableTransportType_bad_param(self):
+ """HTTPSBridgeRequest.withPluggableTransportType() should stop
+ processing if the request had a bad "unblocked" parameter.
+ """
+ httprequest = MockRequest({'transport': [3,]})
+ self.request.withPluggableTransportType(httprequest.args)
+ self.assertNotIn('huggable_transport', self.request.transports)
diff --git a/test/test_https_server.py b/test/test_https_server.py
new file mode 100644
index 0000000..e782135
--- /dev/null
+++ b/test/test_https_server.py
@@ -0,0 +1,838 @@
+# -*- encoding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2014-2015, Isis Lovecruft
+# (c) 2014-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.https.server`."""
+
+from __future__ import print_function
+
+import logging
+import os
+import shutil
+
+import ipaddr
+
+from BeautifulSoup import BeautifulSoup
+
+from twisted.internet import reactor
+from twisted.internet import task
+from twisted.trial import unittest
+from twisted.web.resource import Resource
+from twisted.web.test import requesthelper
+
+from bridgedb.https import server
+from bridgedb.schedule import ScheduledInterval
+from bridgedb.test.https_helpers import _createConfig
+from bridgedb.test.https_helpers import DummyRequest
+from bridgedb.test.https_helpers import DummyHTTPSDistributor
+from bridgedb.test.util import DummyBridge
+from bridgedb.test.util import DummyMaliciousBridge
+
+
+# For additional logger output for debugging, comment out the following:
+logging.disable(50)
+# and then uncomment the following line:
+#server.logging.getLogger().setLevel(10)
+
+
+class GetClientIPTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.https.server.getClientIP`."""
+
+ def createRequestWithIPs(self):
+ """Set the IP address returned from ``request.getClientIP()`` to
+ '3.3.3.3', and the IP address reported in the 'X-Forwarded-For' header
+ to '2.2.2.2'.
+ """
+ request = DummyRequest([''])
+ request.headers.update({'x-forwarded-for': '2.2.2.2'})
+ # See :api:`twisted.test.requesthelper.DummyRequest.getClientIP`
+ request.client = requesthelper.IPv4Address('TCP', '3.3.3.3', 443)
+ request.method = b'GET'
+ return request
+
+ def test_getClientIP_XForwardedFor(self):
+ """getClientIP() should return the IP address from the
+ 'X-Forwarded-For' header when ``useForwardedHeader=True``.
+ """
+ request = self.createRequestWithIPs()
+ clientIP = server.getClientIP(request, useForwardedHeader=True)
+ self.assertEqual(clientIP, '2.2.2.2')
+
+ def test_getClientIP_XForwardedFor_bad_ip(self):
+ """getClientIP() should return None if the IP address from the
+ 'X-Forwarded-For' header is bad/invalid and
+ ``useForwardedHeader=True``.
+ """
+ request = self.createRequestWithIPs()
+ request.headers.update({'x-forwarded-for': 'pineapple'})
+ clientIP = server.getClientIP(request, useForwardedHeader=True)
+ self.assertEqual(clientIP, None)
+
+ def test_getClientIP_fromRequest(self):
+ """getClientIP() should return the IP address from the request instance
+ when ``useForwardedHeader=False``.
+ """
+ request = self.createRequestWithIPs()
+ clientIP = server.getClientIP(request)
+ self.assertEqual(clientIP, '3.3.3.3')
+
+
+class ReplaceErrorPageTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.https.server.replaceErrorPage`."""
+
+ def test_replaceErrorPage(self):
+ """``replaceErrorPage`` should return the expected html."""
+ exc = Exception("vegan gümmibären")
+ errorPage = server.replaceErrorPage(exc)
+ self.assertSubstring("Something went wrong", errorPage)
+ self.assertNotSubstring("vegan gümmibären", errorPage)
+
+
+class IndexResourceTests(unittest.TestCase):
+ """Test for :class:`bridgedb.https.server.IndexResource`."""
+
+ def setUp(self):
+ self.pagename = ''
+ self.indexResource = server.IndexResource()
+ self.root = Resource()
+ self.root.putChild(self.pagename, self.indexResource)
+
+ def test_IndexResource_render_GET(self):
+ """renderGet() should return the index page."""
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ page = self.indexResource.render_GET(request)
+ self.assertSubstring("add the bridges to Tor Browser", page)
+
+ def test_IndexResource_render_GET_lang_ta(self):
+ """renderGet() with ?lang=ta should return the index page in Tamil."""
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.addArg('lang', 'ta')
+ page = self.indexResource.render_GET(request)
+ self.assertSubstring("bridge-à®à®³à¯ Tor Browser-à®à®³à¯", page)
+
+
+class HowtoResourceTests(unittest.TestCase):
+ """Test for :class:`bridgedb.https.server.HowtoResource`."""
+
+ def setUp(self):
+ self.pagename = 'howto.html'
+ self.howtoResource = server.HowtoResource()
+ self.root = Resource()
+ self.root.putChild(self.pagename, self.howtoResource)
+
+ def test_HowtoResource_render_GET(self):
+ """renderGet() should return the howto page."""
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ page = self.howtoResource.render_GET(request)
+ self.assertSubstring("the wizard", page)
+
+ def test_HowtoResource_render_GET_lang_ru(self):
+ """renderGet() with ?lang=ru should return the howto page in Russian."""
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.addArg('lang', 'ru')
+ page = self.howtoResource.render_GET(request)
+ self.assertSubstring("ÑледÑйÑе инÑÑÑÑкÑиÑм ÑÑÑановÑика", page)
+
+
+class CaptchaProtectedResourceTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.https.server.CaptchaProtectedResource`."""
+
+ def setUp(self):
+ self.dist = None
+ self.sched = None
+ self.pagename = b'bridges.html'
+ self.root = Resource()
+ self.protectedResource = server.BridgesResource(self.dist, self.sched)
+ self.captchaResource = server.CaptchaProtectedResource(
+ useForwardedHeader=True, protectedResource=self.protectedResource)
+ self.root.putChild(self.pagename, self.captchaResource)
+
+ def test_render_GET_noCaptcha(self):
+ """render_GET() should return a page without a CAPTCHA, which has the
+ image alt text.
+ """
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ page = self.captchaResource.render_GET(request)
+ self.assertSubstring(
+ "Your browser is not displaying images properly", page)
+
+ def test_render_GET_missingTemplate(self):
+ """render_GET() with a missing template should raise an error and
+ return the result of replaceErrorPage().
+ """
+ oldLookup = server.lookup
+ try:
+ server.lookup = None
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ page = self.captchaResource.render_GET(request)
+ errorPage = server.replaceErrorPage(Exception('kablam'))
+ self.assertEqual(page, errorPage)
+ finally:
+ server.lookup = oldLookup
+
+ def createRequestWithIPs(self):
+ """Set the IP address returned from ``request.getClientIP()`` to
+ '3.3.3.3', and the IP address reported in the 'X-Forwarded-For' header
+ to '2.2.2.2'.
+ """
+ request = DummyRequest([self.pagename])
+ # Since we do not set ``request.getClientIP`` here like we do in some
+ # of the other unittests, an exception would be raised here if
+ # ``getBridgesForRequest()`` is unable to get the IP address from this
+ # 'X-Forwarded-For' header (because ``ip`` would get set to ``None``).
+ request.headers.update({'x-forwarded-for': '2.2.2.2'})
+ # See :api:`twisted.test.requesthelper.DummyRequest.getClientIP`
+ request.client = requesthelper.IPv4Address('TCP', '3.3.3.3', 443)
+ request.method = b'GET'
+ return request
+
+ def test_getClientIP_XForwardedFor(self):
+ """CaptchaProtectedResource.getClientIP() should return the IP address
+ from the 'X-Forwarded-For' header when ``useForwardedHeader=True``.
+ """
+ self.captchaResource.useForwardedHeader = True
+ request = self.createRequestWithIPs()
+ clientIP = self.captchaResource.getClientIP(request)
+ self.assertEqual(clientIP, '2.2.2.2')
+
+ def test_getClientIP_fromRequest(self):
+ """CaptchaProtectedResource.getClientIP() should return the IP address
+ from the request instance when ``useForwardedHeader=False``.
+ """
+ self.captchaResource.useForwardedHeader = False
+ request = self.createRequestWithIPs()
+ clientIP = self.captchaResource.getClientIP(request)
+ self.assertEqual(clientIP, '3.3.3.3')
+
+ def test_render_POST(self):
+ """render_POST() with a wrong 'captcha_response_field' should return
+ a redirect to the CaptchaProtectedResource page.
+ """
+ request = DummyRequest([self.pagename])
+ request.method = b'POST'
+ page = self.captchaResource.render_POST(request)
+ self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
+ 'refresh')
+
+
+class GimpCaptchaProtectedResourceTests(unittest.TestCase):
+ """Tests for :mod:`bridgedb.https.server.GimpCaptchaProtectedResource`."""
+
+ def setUp(self):
+ """Create a :class:`server.BridgesResource` and protect it with
+ a :class:`GimpCaptchaProtectedResource`.
+ """
+ # Create our cached CAPTCHA directory:
+ self.captchaDir = 'captchas'
+ if not os.path.isdir(self.captchaDir):
+ os.makedirs(self.captchaDir)
+
+ # Set up our resources to fake a minimal HTTP(S) server:
+ self.pagename = b'captcha.html'
+ self.root = Resource()
+ # (None, None) is the (distributor, scheduleInterval):
+ self.protectedResource = server.BridgesResource(None, None)
+ self.captchaResource = server.GimpCaptchaProtectedResource(
+ secretKey='42',
+ publicKey='23',
+ hmacKey='abcdefghijklmnopqrstuvwxyz012345',
+ captchaDir='captchas',
+ useForwardedHeader=True,
+ protectedResource=self.protectedResource)
+
+ self.root.putChild(self.pagename, self.captchaResource)
+
+ # Set up the basic parts of our faked request:
+ self.request = DummyRequest([self.pagename])
+
+ def tearDown(self):
+ """Delete the cached CAPTCHA directory if it still exists."""
+ if os.path.isdir(self.captchaDir):
+ shutil.rmtree(self.captchaDir)
+
+ def test_extractClientSolution(self):
+ """A (challenge, sollution) pair extracted from a request resulting
+ from a POST should have the same unmodified (challenge, sollution) as
+ the client originally POSTed.
+ """
+ expectedChallenge = '23232323232323232323'
+ expectedResponse = 'awefawefaefawefaewf'
+
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', expectedChallenge)
+ self.request.addArg('captcha_response_field', expectedResponse)
+
+ response = self.captchaResource.extractClientSolution(self.request)
+ (challenge, response) = response
+ self.assertEqual(challenge, expectedChallenge)
+ self.assertEqual(response, expectedResponse)
+
+ def test_checkSolution(self):
+ """checkSolution() should return False is the solution is invalid."""
+ expectedChallenge = '23232323232323232323'
+ expectedResponse = 'awefawefaefawefaewf'
+
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', expectedChallenge)
+ self.request.addArg('captcha_response_field', expectedResponse)
+
+ valid = self.captchaResource.checkSolution(self.request)
+ self.assertFalse(valid)
+
+ def test_getCaptchaImage(self):
+ """Retrieving a (captcha, challenge) pair with an empty captchaDir
+ should return None for both of the (captcha, challenge) strings.
+ """
+ self.request.method = b'GET'
+ response = self.captchaResource.getCaptchaImage(self.request)
+ (image, challenge) = response
+ # Because we created the directory, there weren't any CAPTCHAs to
+ # retrieve from it:
+ self.assertIs(image, None)
+ self.assertIs(challenge, None)
+
+ def test_getCaptchaImage_noCaptchaDir(self):
+ """Retrieving a (captcha, challenge) with an missing captchaDir should
+ raise a bridgedb.captcha.GimpCaptchaError.
+ """
+ shutil.rmtree(self.captchaDir)
+ self.request.method = b'GET'
+ self.assertRaises(server.captcha.GimpCaptchaError,
+ self.captchaResource.getCaptchaImage, self.request)
+
+ def test_render_GET_missingTemplate(self):
+ """render_GET() with a missing template should raise an error and
+ return the result of replaceErrorPage().
+ """
+ oldLookup = server.lookup
+ try:
+ server.lookup = None
+ self.request.method = b'GET'
+ page = self.captchaResource.render_GET(self.request)
+ errorPage = server.replaceErrorPage(Exception('kablam'))
+ self.assertEqual(page, errorPage)
+ finally:
+ server.lookup = oldLookup
+
+ def test_render_POST_blankFields(self):
+ """render_POST() with a blank 'captcha_response_field' should return
+ a redirect to the CaptchaProtectedResource page.
+ """
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', '')
+ self.request.addArg('captcha_response_field', '')
+
+ page = self.captchaResource.render_POST(self.request)
+ self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
+ 'refresh')
+
+ def test_render_POST_wrongSolution(self):
+ """render_POST() with a wrong 'captcha_response_field' should return
+ a redirect to the CaptchaProtectedResource page.
+ """
+ expectedChallenge = '23232323232323232323'
+ expectedResponse = 'awefawefaefawefaewf'
+
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', expectedChallenge)
+ self.request.addArg('captcha_response_field', expectedResponse)
+
+ page = self.captchaResource.render_POST(self.request)
+ self.assertEqual(BeautifulSoup(page).find('meta')['http-equiv'],
+ 'refresh')
+
+
+class ReCaptchaProtectedResourceTests(unittest.TestCase):
+ """Tests for :mod:`bridgedb.https.server.ReCaptchaProtectedResource`."""
+
+ def setUp(self):
+ """Create a :class:`server.BridgesResource` and protect it with
+ a :class:`ReCaptchaProtectedResource`.
+ """
+ self.timeout = 10.0 # Can't take longer than that, right?
+ # Set up our resources to fake a minimal HTTP(S) server:
+ self.pagename = b'captcha.html'
+ self.root = Resource()
+ # (None, None) is the (distributor, scheduleInterval):
+ self.protectedResource = server.BridgesResource(None, None)
+ self.captchaResource = server.ReCaptchaProtectedResource(
+ publicKey='23',
+ secretKey='42',
+ remoteIP='111.111.111.111',
+ useForwardedHeader=True,
+ protectedResource=self.protectedResource)
+
+ self.root.putChild(self.pagename, self.captchaResource)
+
+ # Set up the basic parts of our faked request:
+ self.request = DummyRequest([self.pagename])
+
+ def tearDown(self):
+ """Cleanup method for removing timed out connections on the reactor.
+
+ This seems to be the solution for the dirty reactor due to
+ ``DelayedCall``s which is mentioned at the beginning of this
+ file. There doesn't seem to be any documentation anywhere which
+ proposes this solution, although this seems to solve the problem.
+ """
+ for delay in reactor.getDelayedCalls():
+ try:
+ delay.cancel()
+ except (AlreadyCalled, AlreadyCancelled):
+ pass
+
+ def test_renderDeferred_invalid(self):
+ """:meth:`_renderDeferred` should redirect a ``Request`` (after the
+ CAPTCHA was NOT xsuccessfully solved) which results from a
+ ``Deferred``'s callback.
+ """
+ self.request.method = b'POST'
+
+ def testCB(request):
+ """Check the ``Request`` returned from ``_renderDeferred``."""
+ self.assertIsInstance(request, DummyRequest)
+ soup = BeautifulSoup(b''.join(request.written)).find('meta')['http-equiv']
+ self.assertEqual(soup, 'refresh')
+
+ d = task.deferLater(reactor, 0, lambda x: x, (False, self.request))
+ d.addCallback(self.captchaResource._renderDeferred)
+ d.addCallback(testCB)
+ return d
+
+ def test_renderDeferred_valid(self):
+ """:meth:`_renderDeferred` should correctly render a ``Request`` (after
+ the CAPTCHA has been successfully solved) which results from a
+ ``Deferred``'s callback.
+ """
+ self.request.method = b'POST'
+
+ def testCB(request):
+ """Check the ``Request`` returned from ``_renderDeferred``."""
+ self.assertIsInstance(request, DummyRequest)
+ html = b''.join(request.written)
+ self.assertSubstring('Uh oh, spaghettios!', html)
+
+ d = task.deferLater(reactor, 0, lambda x: x, (True, self.request))
+ d.addCallback(self.captchaResource._renderDeferred)
+ d.addCallback(testCB)
+ return d
+
+ def test_renderDeferred_nontuple(self):
+ """:meth:`_renderDeferred` should correctly render a ``Request`` (after
+ the CAPTCHA has been successfully solved) which results from a
+ ``Deferred``'s callback.
+ """
+ self.request.method = b'POST'
+
+ def testCB(request):
+ """Check the ``Request`` returned from ``_renderDeferred``."""
+ self.assertIs(request, None)
+
+ d = task.deferLater(reactor, 0, lambda x: x, (self.request))
+ d.addCallback(self.captchaResource._renderDeferred)
+ d.addCallback(testCB)
+ return d
+
+ def test_checkSolution_blankFields(self):
+ """:meth:`server.ReCaptchaProtectedResource.checkSolution` should
+ return a redirect if is the solution field is blank.
+ """
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', '')
+ self.request.addArg('captcha_response_field', '')
+
+ self.assertEqual((False, self.request),
+ self.successResultOf(
+ self.captchaResource.checkSolution(self.request)))
+
+ def test_getRemoteIP_useRandomIP(self):
+ """Check that removing our remoteip setting produces a random IP."""
+ self.captchaResource.remoteIP = None
+ ip = self.captchaResource.getRemoteIP()
+ realishIP = ipaddr.IPv4Address(ip).compressed
+ self.assertTrue(realishIP)
+ self.assertNotEquals(realishIP, '111.111.111.111')
+
+ def test_getRemoteIP_useConfiguredIP(self):
+ """Check that our remoteip setting is used if configured."""
+ ip = self.captchaResource.getRemoteIP()
+ realishIP = ipaddr.IPv4Address(ip).compressed
+ self.assertTrue(realishIP)
+ self.assertEquals(realishIP, '111.111.111.111')
+
+ def test_render_GET_missingTemplate(self):
+ """render_GET() with a missing template should raise an error and
+ return the result of replaceErrorPage().
+ """
+ oldLookup = server.lookup
+ try:
+ server.lookup = None
+ self.request.method = b'GET'
+ page = self.captchaResource.render_GET(self.request)
+ errorPage = server.replaceErrorPage(Exception('kablam'))
+ self.assertEqual(page, errorPage)
+ finally:
+ server.lookup = oldLookup
+
+ def test_render_POST_blankFields(self):
+ """render_POST() with a blank 'captcha_response_field' should return
+ a redirect to the CaptchaProtectedResource page.
+ """
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', '')
+ self.request.addArg('captcha_response_field', '')
+
+ page = self.captchaResource.render_POST(self.request)
+ self.assertEqual(page, server.NOT_DONE_YET)
+
+ def test_render_POST_wrongSolution(self):
+ """render_POST() with a wrong 'captcha_response_field' should return
+ a redirect to the CaptchaProtectedResource page.
+ """
+ expectedChallenge = '23232323232323232323'
+ expectedResponse = 'awefawefaefawefaewf'
+
+ self.request.method = b'POST'
+ self.request.addArg('captcha_challenge_field', expectedChallenge)
+ self.request.addArg('captcha_response_field', expectedResponse)
+
+ page = self.captchaResource.render_POST(self.request)
+ self.assertEqual(page, server.NOT_DONE_YET)
+
+
+class BridgesResourceTests(unittest.TestCase):
+ """Tests for :class:`https.server.BridgesResource`."""
+
+ def setUp(self):
+ """Set up our resources to fake a minimal HTTP(S) server."""
+ self.pagename = b'bridges.html'
+ self.root = Resource()
+
+ self.dist = DummyHTTPSDistributor()
+ self.sched = ScheduledInterval(1, 'hour')
+ self.nBridgesPerRequest = 2
+
+ def useBenignBridges(self):
+ self.dist._bridge_class = DummyBridge
+ self.bridgesResource = server.BridgesResource(
+ self.dist, self.sched, N=self.nBridgesPerRequest,
+ includeFingerprints=True)
+ self.root.putChild(self.pagename, self.bridgesResource)
+
+ def useMaliciousBridges(self):
+ self.dist._bridge_class = DummyMaliciousBridge
+ self.bridgesResource = server.BridgesResource(
+ self.dist, self.sched, N=self.nBridgesPerRequest,
+ includeFingerprints=True)
+ self.root.putChild(self.pagename, self.bridgesResource)
+
+ def parseBridgesFromHTMLPage(self, page):
+ """Utility to pull the bridge lines out of an HTML response page.
+
+ :param str page: A rendered HTML page, as a string.
+ :raises: Any error which might occur.
+ :rtype: list
+ :returns: A list of the bridge lines contained on the **page**.
+ """
+ # The bridge lines are contained in a <div class='bridges'> tag:
+ soup = BeautifulSoup(page)
+ well = soup.find('div', {'class': 'bridge-lines'})
+ content = well.renderContents().strip()
+ lines = content.splitlines()
+
+ bridges = []
+ for line in lines:
+ bridgelines = line.split('<br />')
+ for bridge in bridgelines:
+ if bridge: # It still could be an empty string at this point
+ bridges.append(bridge)
+
+ return bridges
+
+ def test_render_GET_malicious_newlines(self):
+ """Test rendering a request when the some of the bridges returned have
+ malicious (HTML, Javascript, etc., in their) PT arguments.
+ """
+ self.useMaliciousBridges()
+
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.getClientIP = lambda: '1.1.1.1'
+
+ page = self.bridgesResource.render(request)
+ self.assertTrue(
+ 'bad=Bridge 6.6.6.6:6666 0123456789abcdef0123456789abcdef01234567' in str(page),
+ "Newlines in bridge lines should be removed.")
+
+ def test_render_GET_malicious_returnchar(self):
+ """Test rendering a request when the some of the bridges returned have
+ malicious (HTML, Javascript, etc., in their) PT arguments.
+ """
+ self.useMaliciousBridges()
+
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.getClientIP = lambda: '1.1.1.1'
+
+ page = self.bridgesResource.render(request)
+ self.assertTrue(
+ 'eww=Bridge 1.2.3.4:1234' in str(page),
+ "Return characters in bridge lines should be removed.")
+
+ def test_render_GET_malicious_javascript(self):
+ """Test rendering a request when the some of the bridges returned have
+ malicious (HTML, Javascript, etc., in their) PT arguments.
+ """
+ self.useMaliciousBridges()
+
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.getClientIP = lambda: '1.1.1.1'
+
+ page = self.bridgesResource.render(request)
+ self.assertTrue(
+ "evil=<script>alert('fuuuu');</script>" in str(page),
+ ("The characters &, <, >, ', and \" in bridge lines should be "
+ "replaced with their corresponding HTML special characters."))
+
+ def test_renderAnswer_GET_textplain_malicious(self):
+ """If the request format specifies 'plain', we should return content
+ with mimetype 'text/plain' and ASCII control characters replaced.
+ """
+ self.useMaliciousBridges()
+
+ request = DummyRequest([self.pagename])
+ request.args.update({'format': ['plain']})
+ request.getClientIP = lambda: '4.4.4.4'
+ request.method = b'GET'
+
+ page = self.bridgesResource.render(request)
+ self.assertTrue("html" not in str(page))
+ self.assertTrue(
+ 'eww=Bridge 1.2.3.4:1234' in str(page),
+ "Return characters in bridge lines should be removed.")
+ self.assertTrue(
+ 'bad=Bridge 6.6.6.6:6666' in str(page),
+ "Newlines in bridge lines should be removed.")
+
+ def test_render_GET_vanilla(self):
+ """Test rendering a request for normal, vanilla bridges."""
+ self.useBenignBridges()
+
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ request.getClientIP = lambda: '1.1.1.1'
+
+ page = self.bridgesResource.render(request)
+
+ # The response should explain how to use the bridge lines:
+ self.assertTrue("To enter bridges into Tor Browser" in str(page))
+
+ for b in self.parseBridgesFromHTMLPage(page):
+ # Check that each bridge line had the expected number of fields:
+ fields = b.split(' ')
+ self.assertEqual(len(fields), 2)
+
+ # Check that the IP and port seem okay:
+ ip, port = fields[0].rsplit(':')
+ self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
+ self.assertIsInstance(int(port), int)
+ self.assertGreater(int(port), 0)
+ self.assertLessEqual(int(port), 65535)
+
+ def test_render_GET_XForwardedFor(self):
+ """The client's IP address should be obtainable from the
+ 'X-Forwarded-For' header in the request.
+ """
+ self.useBenignBridges()
+
+ self.bridgesResource.useForwardedHeader = True
+ request = DummyRequest([self.pagename])
+ request.method = b'GET'
+ # Since we do not set ``request.getClientIP`` here like we do in some
+ # of the other unittests, an exception would be raised here if
+ # ``getBridgesForRequest()`` is unable to get the IP address from this
+ # 'X-Forwarded-For' header (because ``ip`` would get set to ``None``).
+ request.headers.update({'x-forwarded-for': '2.2.2.2'})
+
+ page = self.bridgesResource.render(request)
+ self.bridgesResource.useForwardedHeader = False # Reset it
+
+ # The response should explain how to use the bridge lines:
+ self.assertTrue("To enter bridges into Tor Browser" in str(page))
+
+ def test_render_GET_RTLlang(self):
+ """Test rendering a request for plain bridges in Arabic."""
+ self.useBenignBridges()
+
+ request = DummyRequest([b"bridges?transport=obfs3"])
+ request.method = b'GET'
+ request.getClientIP = lambda: '3.3.3.3'
+ # For some strange reason, the 'Accept-Language' value *should not* be
+ # a list, unlike all the other headers and argsâ¦
+ request.headers.update({'accept-language': 'ar,en,en_US,'})
+
+ page = self.bridgesResource.render(request)
+ self.assertSubstring("direction: rtl", page)
+ self.assertSubstring(
+ # "I need an alternative way to get bridges!"
+ "Ø£Øتاج Ø¥ÙÙ ÙسÙÙØ© بدÙÙØ© ÙÙØصÙ٠عÙÙ bridges", page)
+
+ for bridgeLine in self.parseBridgesFromHTMLPage(page):
+ # Check that each bridge line had the expected number of fields:
+ bridgeLine = bridgeLine.split(' ')
+ self.assertEqual(len(bridgeLine), 2)
+
+ def test_render_GET_RTLlang_obfs3(self):
+ """Test rendering a request for obfs3 bridges in Farsi."""
+ self.useBenignBridges()
+
+ request = DummyRequest([b"bridges?transport=obfs3"])
+ request.method = b'GET'
+ request.getClientIP = lambda: '3.3.3.3'
+ request.headers.update({'accept-language': 'fa,en,en_US,'})
+ # We actually have to set the request args manually when using a
+ # DummyRequest:
+ request.args.update({'transport': ['obfs3']})
+
+ page = self.bridgesResource.render(request)
+ self.assertSubstring("direction: rtl", page)
+ self.assertSubstring(
+ # "How to use the above bridge lines" (since there should be
+ # bridges in this response, we don't tell them about alternative
+ # mechanisms for getting bridges)
+ "ÚÚ¯ÙÙÚ¯Û Ø§Ø² Ù¾ÙâÙØ§Û Ø®Ùد استÙاد٠کÙÛد", page)
+
+ for bridgeLine in self.parseBridgesFromHTMLPage(page):
+ # Check that each bridge line had the expected number of fields:
+ bridgeLine = bridgeLine.split(' ')
+ self.assertEqual(len(bridgeLine), 3)
+ self.assertEqual(bridgeLine[0], 'obfs3')
+
+ # Check that the IP and port seem okay:
+ ip, port = bridgeLine[1].rsplit(':')
+ self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
+ self.assertIsInstance(int(port), int)
+ self.assertGreater(int(port), 0)
+ self.assertLessEqual(int(port), 65535)
+
+ def test_renderAnswer_textplain(self):
+ """If the request format specifies 'plain', we should return content
+ with mimetype 'text/plain'.
+ """
+ self.useBenignBridges()
+
+ request = DummyRequest([self.pagename])
+ request.args.update({'format': ['plain']})
+ request.getClientIP = lambda: '4.4.4.4'
+ request.method = b'GET'
+
+ page = self.bridgesResource.render(request)
+ self.assertTrue("html" not in str(page))
+
+ # We just need to strip and split it because it looks like:
+ #
+ # 94.235.85.233:9492 0d9d0547c3471cddc473f7288a6abfb54562dc06
+ # 255.225.204.145:9511 1fb89d618b3a12afe3529fd072127ea08fb50466
+ #
+ # (Yes, there are two leading spaces at the beginning of each line)
+ #
+ bridgeLines = [line.strip() for line in page.strip().split('\n')]
+
+ for bridgeLine in bridgeLines:
+ bridgeLine = bridgeLine.split(' ')
+ self.assertEqual(len(bridgeLine), 2)
+
+ # Check that the IP and port seem okay:
+ ip, port = bridgeLine[0].rsplit(':')
+ self.assertIsInstance(ipaddr.IPv4Address(ip), ipaddr.IPv4Address)
+ self.assertIsInstance(int(port), int)
+ self.assertGreater(int(port), 0)
+ self.assertLessEqual(int(port), 65535)
+
+
+class OptionsResourceTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.https.server.OptionsResource`."""
+
+ def setUp(self):
+ """Create a :class:`server.OptionsResource`."""
+ # Set up our resources to fake a minimal HTTP(S) server:
+ self.pagename = b'options.html'
+ self.root = Resource()
+ self.optionsResource = server.OptionsResource()
+ self.root.putChild(self.pagename, self.optionsResource)
+
+ def test_render_GET_RTLlang(self):
+ """Test rendering a request for obfs3 bridges in Hebrew."""
+ request = DummyRequest(["bridges?transport=obfs3"])
+ request.method = b'GET'
+ request.getClientIP = lambda: '3.3.3.3'
+ request.headers.update({'accept-language': 'he'})
+ # We actually have to set the request args manually when using a
+ # DummyRequest:
+ request.args.update({'transport': ['obfs2']})
+
+ page = self.optionsResource.render(request)
+ self.assertSubstring("direction: rtl", page)
+ self.assertSubstring("××× ×שר××?", page)
+
+
+class HTTPSServerServiceTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.email.server.addWebServer`."""
+
+ def setUp(self):
+ """Create a config and an HTTPSDistributor."""
+ self.config = _createConfig()
+ self.distributor = DummyHTTPSDistributor()
+
+ def tearDown(self):
+ """Cleanup method after each ``test_*`` method runs; removes timed out
+ connections on the reactor and clears the :ivar:`transport`.
+
+ Basically, kill all connections with fire.
+ """
+ for delay in reactor.getDelayedCalls():
+ try:
+ delay.cancel()
+ except (AlreadyCalled, AlreadyCancelled):
+ pass
+
+ # FIXME: this is definitely not how we're supposed to do this, but it
+ # kills the DirtyReactorAggregateErrors.
+ reactor.disconnectAll()
+ reactor.runUntilCurrent()
+
+ def test_addWebServer_GIMP_CAPTCHA_ENABLED(self):
+ """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
+ server.addWebServer(self.config, self.distributor)
+
+ def test_addWebServer_RECAPTCHA_ENABLED(self):
+ """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
+ config = self.config
+ config.RECAPTCHA_ENABLED = True
+ server.addWebServer(config, self.distributor)
+
+ def test_addWebServer_no_captchas(self):
+ """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
+ config = self.config
+ config.GIMP_CAPTCHA_ENABLED = False
+ server.addWebServer(config, self.distributor)
+
+ def test_addWebServer_no_HTTPS_ROTATION_PERIOD(self):
+ """Call :func:`bridgedb.https.server.addWebServer` to test startup."""
+ config = self.config
+ config.HTTPS_ROTATION_PERIOD = None
+ server.addWebServer(config, self.distributor)
diff --git a/test/test_interfaces.py b/test/test_interfaces.py
new file mode 100644
index 0000000..8a78291
--- /dev/null
+++ b/test/test_interfaces.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# ____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+# ____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.interfaces`."""
+
+from twisted.trial import unittest
+
+from bridgedb import interfaces
+
+
+class DummyNamedOtherThing(interfaces.Named):
+ def __init__(self):
+ self.name = "hipster"
+
+
+class DummyNamedThing(interfaces.Named):
+ def __init__(self):
+ self.whatever = DummyNamedOtherThing()
+ self.name = "original"
+
+
+class NamedTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.interfaces.Named`."""
+
+ def test_Named_init(self):
+ """Initializing a Named() object should set its name to ''."""
+ named = interfaces.Named()
+ self.assertEqual(named.name, '')
+
+ def test_Named_name(self):
+ """For a Named object A without any other Named objects which have
+ object A as an attribute, should just have its name set to whatever
+ it was set to.
+ """
+ named = DummyNamedOtherThing()
+ self.assertEqual(named.name, "hipster")
+
+ def test_Named_with_named_object_for_attribute(self):
+ """For a Named object A which has another Named object B as an
+ attribute, object A should just have its name set to whatever
+ it was set to, and object B should have its name set to object A's
+ name plus whatever object B's name was set to.
+ """
+ named = DummyNamedThing()
+ self.assertEqual(named.name, "original")
+ self.assertEqual(named.whatever.name, "original hipster")
diff --git a/test/test_parse_addr.py b/test/test_parse_addr.py
new file mode 100644
index 0000000..4659efb
--- /dev/null
+++ b/test/test_parse_addr.py
@@ -0,0 +1,749 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.parse.addr` module.
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import ipaddr
+import random
+
+from twisted.python import log
+from twisted.trial import unittest
+
+from bridgedb.parse import addr
+
+
+IP4LinkLocal = "169.254.0.0"
+IP6LinkLocal = "fe80::1234"
+IP4Loopback = "127.0.0.0"
+IP4Localhost = "127.0.0.1"
+IP6Localhost = "::1"
+IP4LimitedBroadcast = "255.255.255.0"
+IP4Multicast_224 = "224.0.0.1"
+IP4Multicast_239 = "239.0.0.1"
+IP4Unspecified = "0.0.0.0"
+IP6Unspecified = "::"
+IP4DefaultRoute = "0.0.0.0"
+IP6DefaultRoute = "::"
+IP4ReservedRFC1918_10 = "10.0.0.0"
+IP4ReservedRFC1918_172_16 = "172.16.0.0"
+IP4ReservedRFC1918_192_168 = "192.168.0.0"
+IP4ReservedRFC1700 = "240.0.0.0"
+IP6UniqueLocal = "fc00::"
+IP6SiteLocal = "fec0::"
+
+
+class CanonicalizeEmailDomainTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.canonicalizeEmailDomain`."""
+
+ def test_nonDict(self):
+ """Using a non-dict domainmap as a parameter to
+ canonicalizeEmailDomain() should log an AttributeError and then raise
+ an UnsupportedDomain error.
+ """
+ domainmap = 'example.com'
+ domain = 'fubar.com'
+ self.assertRaises(addr.UnsupportedDomain,
+ addr.canonicalizeEmailDomain,
+ domain, domainmap)
+
+ def test_notPermitted(self):
+ """A domain not in the domainmap of allowed domains should raise an
+ UnsupportedDomain error.
+ """
+ domainmap = {'foo.example.com': 'example.com'}
+ domain = 'bar.example.com'
+ self.assertRaises(addr.UnsupportedDomain,
+ addr.canonicalizeEmailDomain,
+ domain, domainmap)
+
+ def test_permitted(self):
+ """A domain in the domainmap of allowed domains should return the
+ canonical domain.
+ """
+ domainmap = {'foo.example.com': 'example.com'}
+ domain = 'foo.example.com'
+ canonical = addr.canonicalizeEmailDomain(domain, domainmap)
+ self.assertEquals(canonical, 'example.com')
+
+
+class ExtractEmailAddressTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.extractEmailAddress`."""
+
+ def test_23(self):
+ """The email address int(23) should raise a BadEmail error."""
+ self.assertRaises(addr.BadEmail,
+ addr.extractEmailAddress,
+ int(23))
+
+ def test_lessThanChars(self):
+ """The email address 'Alice <alice at riseup.net>' should return
+ ('alice', 'riseup.net').
+ """
+ local, domain = addr.extractEmailAddress('Alice <alice at riseup.net>')
+ self.assertEqual(local, 'alice')
+ self.assertEqual(domain, 'riseup.net')
+
+ def test_extraLessThanChars(self):
+ """The email address 'Mallory <mal<lory at riseup.net>' should return
+ ('lory', 'riseup.net')
+ """
+ local, domain = addr.extractEmailAddress('Mallory <mal<lory at riseup.net>')
+ self.assertEqual(local, 'lory')
+ self.assertEqual(domain, 'riseup.net')
+
+ def test_extraLessAndGreaterThanChars(self):
+ """The email address 'Mallory <mal><>>lory at riseup.net>' should raise a
+ BadEmail error.
+ """
+ self.assertRaises(addr.BadEmail,
+ addr.extractEmailAddress,
+ 'Mallory <mal><>>lory at riseup.net>')
+
+ def test_extraAppendedEmailAddress(self):
+ """The email address 'Mallory <mallory at riseup.net><mallory at gmail.com>'
+ should use the last address.
+ """
+ local, domain = addr.extractEmailAddress(
+ 'Mallory <mallory at riseup.net><mallory at gmail.com>')
+ self.assertEqual(local, 'mallory')
+ self.assertEqual(domain, 'gmail.com')
+
+
+class NormalizeEmailTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.normalizeEmail`."""
+
+ def test_permitted(self):
+ """A valid email address from a permitted domain should return
+ unchanged.
+ """
+ domainrules = {}
+ domainmap = {'foo.example.com': 'example.com'}
+ emailaddr = 'alice at foo.example.com'
+ normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
+ self.assertEqual(emailaddr, normalized)
+
+ def test_notPermitted(self):
+ """A valid email address from a non-permitted domain should raise an
+ UnsupportedDomain error.
+ """
+ domainrules = {}
+ domainmap = {'bar.example.com': 'example.com'}
+ emailaddr = 'Alice <alice at foo.example.com>'
+ self.assertRaises(addr.UnsupportedDomain,
+ addr.normalizeEmail,
+ emailaddr, domainmap, domainrules)
+
+ def test_ignoreDots(self):
+ """A valid email address with a '.' should remove the '.' if
+ 'ignore_dots' is in domainrules.
+ """
+ domainrules = {'example.com': 'ignore_dots'}
+ domainmap = {'foo.example.com': 'example.com'}
+ emailaddr = 'alice.bridges at foo.example.com'
+ normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
+ self.assertEqual('alicebridges at foo.example.com', normalized)
+
+ def test_ignorePlus(self):
+ """A valid email address with a '+' and some extra stuff, from a
+ permitted domain, should remove the '+' stuff if 'ignore_plus' is
+ enabled.
+ """
+ domainrules = {}
+ domainmap = {'foo.example.com': 'example.com'}
+ emailaddr = 'alice+bridges at foo.example.com'
+ normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules)
+ self.assertEqual('alice at foo.example.com', normalized)
+
+ def test_dontIgnorePlus(self):
+ """A valid email address with a '+' and some extra stuff, from a
+ permitted domain, should return unchanged if 'ignore_plus' is disabled.
+ """
+ domainrules = {}
+ domainmap = {'foo.example.com': 'example.com'}
+ emailaddr = 'alice+bridges at foo.example.com'
+ normalized = addr.normalizeEmail(emailaddr, domainmap, domainrules,
+ ignorePlus=False)
+ self.assertEqual(emailaddr, normalized)
+
+
+class ParseAddrIsIPAddressTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.isIPAddress`.
+
+ .. note:: All of the ``test_isIPAddress_IP*`` methods in this class should
+ get ``False`` as the **result** returned from :func:`addr.isIPAddress`,
+ because all the ``IP*`` constants defined above are invalid addresses
+ according to :func:`addr.isValidIP`.
+ """
+
+ def test_isIPAddress_randomIP4(self):
+ """Test :func:`addr.isIPAddress` with a random IPv4 address.
+
+ This test asserts that the returned IP address is not None (because
+ the IP being tested is random, it *could* randomly be an invalid IP
+ address and thus :func:`~bridgdb.addr.isIPAddress` would return
+ ``False``).
+ """
+ randomAddress = ipaddr.IPv4Address(random.getrandbits(32))
+ result = addr.isIPAddress(randomAddress)
+ log.msg("Got addr.isIPAddress() result for random IPv4 address %r: %s"
+ % (randomAddress, result))
+ self.assertTrue(result is not None)
+
+ def test_isIPAddress_randomIP6(self):
+ """Test :func:`addr.isIPAddress` with a random IPv6 address.
+
+ This test asserts that the returned IP address is not None (because
+ the IP being tested is random, it *could* randomly be an invalid IP
+ address and thus :func:`~bridgdb.addr.isIPAddress` would return
+ ``False``).
+ """
+ randomAddress = ipaddr.IPv6Address(random.getrandbits(128))
+ result = addr.isIPAddress(randomAddress)
+ log.msg("Got addr.isIPAddress() result for random IPv6 address %r: %s"
+ % (randomAddress, result))
+ self.assertTrue(result is not None)
+
+ def runTestForAddr(self, testAddress):
+ """Test :func:`addr.isIPAddress` with the specified ``testAddress``.
+
+ :param str testAddress: A string which specifies either an IPv4 or
+ IPv6 address to test.
+ """
+ result = addr.isIPAddress(testAddress)
+ log.msg("addr.isIPAddress(%r) => %s" % (testAddress, result))
+ self.assertTrue(result is not None,
+ "Got a None for testAddress: %r" % testAddress)
+ self.assertFalse(isinstance(result, basestring),
+ "Expected %r result from isIPAddress(%r): %r %r"
+ % (bool, testAddress, result, type(result)))
+
+ def test_isIPAddress_IP4LinkLocal(self):
+ """Test :func:`addr.isIPAddress` with a link local IPv4 address."""
+ self.runTestForAddr(IP4LinkLocal)
+
+ def test_isIPAddress_IP6LinkLocal(self):
+ """Test :func:`addr.isIPAddress` with a link local IPv6 address."""
+ self.runTestForAddr(IP6LinkLocal)
+
+ def test_isIPAddress_IP4Loopback(self):
+ """Test :func:`addr.isIPAddress` with the loopback IPv4 address."""
+ self.runTestForAddr(IP4Loopback)
+
+ def test_isIPAddress_IP4Localhost(self):
+ """Test :func:`addr.isIPAddress` with a localhost IPv4 address."""
+ self.runTestForAddr(IP4Localhost)
+
+ def test_isIPAddress_IP6LinkLocal(self):
+ """Test :func:`addr.isIPAddress` with a localhost IPv6 address."""
+ self.runTestForAddr(IP6Localhost)
+
+ def test_isIPAddress_IP4LimitedBroadcast(self):
+ """Test :func:`addr.isIPAddress` with a limited broadcast IPv4
+ address.
+ """
+ self.runTestForAddr(IP4LimitedBroadcast)
+
+ def test_isIPAddress_IP4Multicast_224(self):
+ """Test :func:`addr.isIPAddress` with a multicast IPv4 address."""
+ self.runTestForAddr(IP4Multicast_224)
+
+ def test_isIPAddress_IP4Multicast_239(self):
+ """Test :func:`addr.isIPAddress` with a multicast IPv4 address."""
+ self.runTestForAddr(IP4Multicast_239)
+
+ def test_isIPAddress_IP4Unspecified(self):
+ """Test :func:`addr.isIPAddress` with an unspecified IPv4 address."""
+ self.runTestForAddr(IP4Unspecified)
+
+ def test_isIPAddress_IP6Unspecified(self):
+ """Test :func:`addr.isIPAddress` with an unspecified IPv6 address."""
+ self.runTestForAddr(IP6Unspecified)
+
+ def test_isIPAddress_IP4DefaultRoute(self):
+ """Test :func:`addr.isIPAddress` with a default route IPv4 address."""
+ self.runTestForAddr(IP4DefaultRoute)
+
+ def test_isIPAddress_IP6DefaultRoute(self):
+ """Test :func:`addr.isIPAddress` with a default route IPv6 address."""
+ self.runTestForAddr(IP6DefaultRoute)
+
+ def test_isIPAddress_IP4ReservedRFC1918_10(self):
+ """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
+ self.runTestForAddr(IP4ReservedRFC1918_10)
+
+ def test_isIPAddress_IP4ReservedRFC1918_172_16(self):
+ """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
+ self.runTestForAddr(IP4ReservedRFC1918_172_16)
+
+ def test_isIPAddress_IP4ReservedRFC1918_192_168(self):
+ """Test :func:`addr.isIPAddress` with a reserved IPv4 address."""
+ self.runTestForAddr(IP4ReservedRFC1918_192_168)
+
+ def test_isIPAddress_IP4ReservedRFC1700(self):
+ """Test :func:`addr.isIPAddress` with a :rfc:`1700` reserved IPv4
+ address.
+ """
+ self.runTestForAddr(IP4ReservedRFC1700)
+
+ def test_isIPAddress_IP6UniqueLocal(self):
+ """Test :func:`addr.isIPAddress` with an unique local IPv6 address."""
+ self.runTestForAddr(IP6UniqueLocal)
+
+ def test_isIPAddress_IP6SiteLocal(self):
+ """Test :func:`addr.isIPAddress` with a site local IPv6 address."""
+ self.runTestForAddr(IP6SiteLocal)
+
+ def test_isIPAddress_withNonIP(self):
+ """Test :func:`addr.isIPAddress` with non-IP input."""
+ self.runTestForAddr('not an ip address')
+
+ def test_filehandle(self):
+ """Test :func:`addr.isIPAddress` with a file handle for input.
+
+ Try to raise a non- :exc:`~exceptions.ValueError` exception in
+ :func:`addr.isIPAddress`.
+ """
+ fh = open('{0}-filehandle'.format(self.__class__.__name__), 'wb')
+ self.runTestForAddr(fh)
+
+ def test_returnUncompressedIP(self):
+ """Test returning a :class:`ipaddr.IPAddress`."""
+ testAddress = '86.59.30.40'
+ result = addr.isIPAddress(testAddress, compressed=False)
+ log.msg("addr.isIPAddress(%r, compressed=False) => %r"
+ % (testAddress, result))
+ self.assertTrue(
+ isinstance(result, ipaddr.IPv4Address),
+ "Expected %r result from isIPAddress(%r, compressed=False): %r %r"
+ % (ipaddr.IPv4Address, testAddress, result, type(result)))
+
+ def test_unicode(self):
+ """Test with unicode input."""
+ self.runTestForAddr("ââââââââââââââââââ")
+
+
+class ParseAddrIsIPv4Tests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.isIPv4`."""
+
+ def runTestForIPv4(self, testAddress):
+ """Test :func:`addr.isIPv4` with the specified IPv4 **testAddress**.
+
+ This test asserts that the returned value is ``True``.
+
+ :param str testAddress: A string which specifies the IPv4 address to
+ test, which should cause :func:`addr.isIPv4` to return ``True``.
+ """
+ result = addr.isIPv4(testAddress)
+ log.msg("addr.isIPv4(%r) => %s" % (testAddress, result))
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv4() should be boolean: %r" % type(result))
+ self.assertTrue(result,
+ "addr.isIPv4(%r) should be True!" % testAddress)
+
+ def runTestForIPv6(self, testAddress):
+ """Test :func:`addr.isIPv4` with the specified IPv6 **testAddress**.
+
+ This test asserts that the returned value is ``False``.
+
+ :param str testAddress: A string which specifies the IPv6 address to
+ test, which should cause :func:`addr.isIPv4` to return ``False``.
+ """
+ result = addr.isIPv4(testAddress)
+ log.msg("addr.isIPv4(%r) => %s" % (testAddress, result))
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv4() should be boolean: %r" % type(result))
+ self.assertFalse(result,
+ "addr.isIPv4(%r) should be False!" % testAddress)
+
+ def test_isIPv4_randomIP4(self):
+ """Test :func:`addr.isIPv4` with a random IPv4 address.
+
+ This test asserts that the returned value is a :obj:`bool`. Because
+ the IP being tested is random, it *could* randomly be an invalid IP
+ address and thus :func:`~bridgdb.addr.isIPv4` would return ``False``).
+ """
+ randomAddr = ipaddr.IPv4Address(random.getrandbits(32)).compressed
+ log.msg("Testing randomly generated IPv4 address: %s" % randomAddr)
+ result = addr.isIPv4(randomAddr)
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv4() should be boolean: %r" % type(result))
+
+ def test_isIPv4_randomIP6(self):
+ """Test :func:`addr.isIPv4` with a random IPv6 address."""
+ randomAddr = ipaddr.IPv6Address(random.getrandbits(128)).compressed
+ log.msg("Testing randomly generated IPv6 address: %s" % randomAddr)
+ self.runTestForIPv6(randomAddr)
+
+ def test_isIPv4_IP4LinkLocal(self):
+ """Test :func:`addr.isIPv4` with a link local IPv4 address."""
+ self.runTestForIPv4(IP4LinkLocal)
+
+ def test_isIPv4_IP6LinkLocal(self):
+ """Test :func:`addr.isIPv4` with a link local IPv6 address."""
+ self.runTestForIPv6(IP6LinkLocal)
+
+ def test_isIPv4_IP4Loopback(self):
+ """Test :func:`addr.isIPv4` with the loopback IPv4 address."""
+ self.runTestForIPv4(IP4Loopback)
+
+ def test_isIPv4_IP4Localhost(self):
+ """Test :func:`addr.isIPv4` with a localhost IPv4 address."""
+ self.runTestForIPv4(IP4Localhost)
+
+ def test_isIPv4_IP6Localhost(self):
+ """Test :func:`addr.isIPv4` with a localhost IPv6 address."""
+ self.runTestForIPv6(IP6Localhost)
+
+ def test_isIPv4_IP4LimitedBroadcast(self):
+ """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4LimitedBroadcast)
+
+ def test_isIPv4_IP4Multicast_224(self):
+ """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4Multicast_224)
+
+ def test_isIPv4_IP4Multicast_239(self):
+ """Test :func:`addr.isIPv4` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4Multicast_239)
+
+ def test_isIPv4_IP4Unspecified(self):
+ """Test :func:`addr.isIPv4` with an unspecified IPv4 address."""
+ self.runTestForIPv4(IP4Unspecified)
+
+ def test_isIPv4_IP6Unspecified(self):
+ """Test :func:`addr.isIPv4` with an unspecified IPv6 address."""
+ self.runTestForIPv6(IP6Unspecified)
+
+ def test_isIPv4_IP4DefaultRoute(self):
+ """Test :func:`addr.isIPv4` with a default route IPv4 address."""
+ self.runTestForIPv4(IP4DefaultRoute)
+
+ def test_isIPv4_IP6DefaultRoute(self):
+ """Test :func:`addr.isIPv4` with a default route IPv6 address."""
+ self.runTestForIPv6(IP6DefaultRoute)
+
+ def test_isIPv4_IP4ReservedRFC1918_10(self):
+ """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_10)
+
+ def test_isIPv4_IP4ReservedRFC1918_172_16(self):
+ """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_172_16)
+
+ def test_isIPv4_IP4ReservedRFC1918_192_168(self):
+ """Test :func:`addr.isIPv4` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_192_168)
+
+ def test_isIPv4_IP4ReservedRFC1700(self):
+ """Test :func:`addr.isIPv4` with a :rfc:`1700` reserved IPv4
+ address.
+ """
+ self.runTestForIPv4(IP4ReservedRFC1700)
+
+ def test_isIPv4_IP6UniqueLocal(self):
+ """Test :func:`addr.isIPv4` with an unique local IPv6 address."""
+ self.runTestForIPv6(IP6UniqueLocal)
+
+ def test_isIPv4_IP6SiteLocal(self):
+ """Test :func:`addr.isIPv4` with a site local IPv6 address."""
+ self.runTestForIPv6(IP6SiteLocal)
+
+ def test_isIPv4_withValidIPv4(self):
+ """Test :func:`addr.isIPv4` with a valid IPv4 address."""
+ self.runTestForIPv4('38.229.72.2')
+
+ def test_isIPv4_withValidIPv4_2(self):
+ """Test :func:`addr.isIPv4` with a valid IPv4 address."""
+ self.runTestForIPv4('15.15.15.15')
+
+ def test_isIPv4_withValidIPv4_3(self):
+ """Test :func:`addr.isIPv4` with a valid IPv4 address."""
+ self.runTestForIPv4('93.95.227.222')
+
+ def test_isIPv4_withValidIPv6(self):
+ """Test :func:`addr.isIPv4` with a valid IPv6 address."""
+ self.runTestForIPv6("2a00:1450:4001:808::1010")
+
+ def test_isIPv4_withNonIP(self):
+ """Test :func:`addr.isIPv4` with non-IP input."""
+ self.runTestForIPv6('not an ip address')
+
+
+class ParseAddrIsIPv6Tests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.addr.isIPv6`.
+
+ .. note:: All of the ``test_isIPv6_IP*`` methods in this class should get
+ ``False`` as their **result** value returned from :func:`addr.isIPv6`,
+ because all of the ``IP*`` constants defined above are invalid
+ according to :func:`addr.isValidIP`.
+ """
+
+ def runTestForIPv4(self, testAddress):
+ """Test :func:`addr.isIPv6` with the specified IPv4 **testAddress**.
+
+ This test asserts that the returned value is ``False``.
+
+ :param str testAddress: A string which specifies the IPv4 address to
+ test, which should cause :func:`addr.isIPv6` to return ``False``.
+ """
+ result = addr.isIPv6(testAddress)
+ log.msg("addr.isIPv6(%r) => %s" % (testAddress, result))
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv6() should be boolean: %r" % type(result))
+ self.assertFalse(result,
+ "addr.isIPv6(%r) should be False!" % testAddress)
+
+ def runTestForIPv6(self, testAddress):
+ """Test :func:`addr.isIPv6` with the specified IPv6 **testAddress**.
+
+ This test asserts that the returned value is ``True``.
+
+ Random addresses should *not* be tested with this function, because
+ :func:`~addr.isIPv6` uses :func:`~addr.isValidIP` internally, and will
+ return False if the IP is invalid.
+
+ :param str testAddress: A string which specifies the IPv6 address to
+ test, which should cause :func:`addr.isIPv6` to return ``True``.
+ """
+ result = addr.isIPv6(testAddress)
+ log.msg("addr.isIPv6(%r) => %s" % (testAddress, result))
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv6() should be boolean: %r" % type(result))
+ self.assertTrue(result,
+ "addr.isIPv6(%r) should be True!" % testAddress)
+
+ def test_isIPv6_randomIP4(self):
+ """Test :func:`addr.isIPv6` with a random IPv4 address."""
+ randomAddr = ipaddr.IPv4Address(random.getrandbits(32)).compressed
+ log.msg("Testing randomly generated IPv4 address: %s" % randomAddr)
+ self.runTestForIPv4(randomAddr)
+
+ def test_isIPv6_randomIP6(self):
+ """Test :func:`addr.isIPv6` with a random IPv6 address.
+
+ This test asserts that the returned IP address is a :obj:`bool`
+ (because the IP being tested is random, it *could* randomly be an
+ invalid IP address and thus :func:`~bridgdb.addr.isIPv6` would return
+ ``False``).
+ """
+ randomAddr = ipaddr.IPv6Address(random.getrandbits(128)).compressed
+ log.msg("Testing randomly generated IPv6 address: %s" % randomAddr)
+ result = addr.isIPv6(randomAddr)
+ self.assertTrue(isinstance(result, bool),
+ "addr.isIPv6() should be boolean: %r" % type(result))
+
+ def test_isIPv6_IP4LinkLocal(self):
+ """Test :func:`addr.isIPv6` with a link local IPv4 address.
+
+ :meth:`runTestForIPv4` is used because this address is invalid
+ according to :func:`addr.isValidIP`; therefore, the result from
+ :func:`addr.isIPv6` should be ``False``.
+ """
+ self.runTestForIPv4(IP4LinkLocal)
+
+ def test_isIPv6_IP6LinkLocal(self):
+ """Test :func:`addr.isIPv6` with a link local IPv6 address."""
+ self.runTestForIPv6(IP6LinkLocal)
+
+ def test_isIPv6_IP4Loopback(self):
+ """Test :func:`addr.isIPv6` with the loopback IPv4 address."""
+ self.runTestForIPv4(IP4Loopback)
+
+ def test_isIPv6_IP4Localhost(self):
+ """Test :func:`addr.isIPv6` with a localhost IPv4 address."""
+ self.runTestForIPv4(IP4Localhost)
+
+ def test_isIPv6_IP6Localhost(self):
+ """Test :func:`addr.isIPv6` with a localhost IPv6 address."""
+ self.runTestForIPv6(IP6Localhost)
+
+ def test_isIPv6_IP4LimitedBroadcast(self):
+ """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4LimitedBroadcast)
+
+ def test_isIPv6_IP4Multicast_224(self):
+ """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4Multicast_224)
+
+ def test_isIPv6_IP4Multicast_239(self):
+ """Test :func:`addr.isIPv6` with a multicast IPv4 address."""
+ self.runTestForIPv4(IP4Multicast_239)
+
+ def test_isIPv6_IP4Unspecified(self):
+ """Test :func:`addr.isIPv6` with an unspecified IPv4 address."""
+ self.runTestForIPv4(IP4Unspecified)
+
+ def test_isIPv6_IP6Unspecified(self):
+ """Test :func:`addr.isIPv6` with an unspecified IPv6 address."""
+ self.runTestForIPv6(IP6Unspecified)
+
+ def test_isIPv6_IP4DefaultRoute(self):
+ """Test :func:`addr.isIPv6` with a default route IPv4 address."""
+ self.runTestForIPv4(IP4DefaultRoute)
+
+ def test_isIPv6_IP6DefaultRoute(self):
+ """Test :func:`addr.isIPv6` with a default route IPv6 address."""
+ self.runTestForIPv6(IP6DefaultRoute)
+
+ def test_isIPv6_IP4ReservedRFC1918_10(self):
+ """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_10)
+
+ def test_isIPv6_IP4ReservedRFC1918_172_16(self):
+ """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_172_16)
+
+ def test_isIPv6_IP4ReservedRFC1918_192_168(self):
+ """Test :func:`addr.isIPv6` with a reserved IPv4 address."""
+ self.runTestForIPv4(IP4ReservedRFC1918_192_168)
+
+ def test_isIPv6_IP4ReservedRFC1700(self):
+ """Test :func:`addr.isIPv6` with a :rfc:`1700` reserved IPv4
+ address.
+ """
+ self.runTestForIPv4(IP4ReservedRFC1700)
+
+ def test_isIPv6_IP6UniqueLocal(self):
+ """Test :func:`addr.isIPv6` with an unique local IPv6 address."""
+ self.runTestForIPv6(IP6UniqueLocal)
+
+ def test_isIPv6_IP6SiteLocal(self):
+ """Test :func:`addr.isIPv6` with a site local IPv6 address."""
+ self.runTestForIPv6(IP6SiteLocal)
+
+ def test_isIPv6_withValidIPv4(self):
+ """Test :func:`addr.isIPv6` with a valid IPv4 address."""
+ self.runTestForIPv4('38.229.72.2')
+
+ def test_isIPv6_withValidIPv4_2(self):
+ """Test :func:`addr.isIPv6` with a valid IPv4 address."""
+ self.runTestForIPv4('15.15.15.15')
+
+ def test_isIPv6_withValidIPv4_3(self):
+ """Test :func:`addr.isIPv6` with a valid IPv4 address."""
+ self.runTestForIPv4('93.95.227.222')
+
+ def test_isIPv6_withValidIPv6(self):
+ """Test :func:`addr.isIPv6` with a valid IPv6 address."""
+ self.runTestForIPv6("2a00:1450:4001:808::1010")
+
+ def test_isIPv6_withNonIP(self):
+ """Test :func:`addr.isIPv6` with non-IP input."""
+ self.runTestForIPv4('not an ip address')
+
+
+class PortListTest(unittest.TestCase):
+ """Unittests for :class:`bridgedb.parse.addr.PortList`."""
+
+ def getRandomPort(self):
+ """Get a port in the range [1, 65535] inclusive.
+
+ :rtype: int
+ :returns: A random port number.
+ """
+ return random.randint(1, 65535)
+
+ def test_tooFewPorts(self):
+ """Create a :class:`addr.PortList` with no ports at all."""
+ portList = addr.PortList()
+ self.assertEqual(len(portList), 0)
+
+ def test_tooManyPorts(self):
+ """Create a :class:`addr.PortList` with more than the maximum
+ allowed ports, as given in ``PortList.PORTSPEC_LEN``.
+
+ We don't currently do anything to deal with a PortList having too many
+ ports.
+ """
+ tooMany = addr.PortList.PORTSPEC_LEN + 1
+ ports = [self.getRandomPort() for x in xrange(tooMany)]
+ log.msg("Testing addr.PortList(%s))"
+ % ', '.join([type('')(port) for port in ports]))
+ portList = addr.PortList(*ports)
+ self.assertEqual(len(portList), tooMany)
+
+ def test_invalidPortNumber(self):
+ """Test creating a :class:`addr.PortList` with an invalid port.
+
+ Should raise an InvalidPort error.
+ """
+ self.assertRaises(addr.InvalidPort, addr.PortList, 66666, 6666)
+
+ def test_contains(self):
+ """Test creating a :class:`addr.PortList` with valid ports.
+
+ Then check that ``__contains__`` works properly.
+ """
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertIn(443, portList)
+
+ def test_iter(self):
+ """Test creating a :class:`addr.PortList` with valid ports.
+
+ Then check that ``__iter__`` works properly.
+ """
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ iterator = iter(portList)
+ for x in xrange(len(ports)):
+ self.assertIn(iterator.next(), portList)
+
+ def test_str(self):
+ """Test creating a :class:`addr.PortList` with valid ports.
+
+ Then check that ``__str__`` works properly.
+ """
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertTrue(isinstance(str(portList), basestring))
+ for port in ports:
+ self.assertIn(str(port), str(portList))
+
+ def test_getitem_shouldContain(self):
+ """Test ``__getitem__`` with a port number in the PortList."""
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertEqual(portList.__getitem__(0), 9001)
+
+ def test_getitem_shouldNotContain(self):
+ """Test ``__getitem__`` with a port number not in the PortList."""
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertRaises(IndexError, portList.__getitem__, 555)
+
+ def test_getitem_string(self):
+ """Test ``__getitem__`` with a string."""
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertRaises(TypeError, portList.__getitem__, '443')
+
+ def test_getitem_long(self):
+ """Test ``__getitem__`` with a string."""
+ ports = (443, 9001, 9030)
+ portList = addr.PortList(*ports)
+ self.assertEqual(portList.__getitem__(long(0)), 9001)
+
+ def test_mixedArgs(self):
+ """Create a :class:`addr.PortList` with mixed type parameters."""
+ firstList = addr.PortList('1111,2222,3333')
+ portList = addr.PortList(443, "9001,9030, 9050", firstList)
+ self.assertTrue(portList)
+
+ def test_invalidStringArgs(self):
+ """Create a :class:`addr.PortList` with mixed type parameters."""
+ self.assertRaises(addr.InvalidPort,
+ addr.PortList, '1111, 666666, 3333')
diff --git a/test/test_parse_descriptors.py b/test/test_parse_descriptors.py
new file mode 100644
index 0000000..dd6d146
--- /dev/null
+++ b/test/test_parse_descriptors.py
@@ -0,0 +1,855 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for :class:`bridgedb.parse.descriptors` module."""
+
+from __future__ import print_function
+
+import datetime
+import glob
+import hashlib
+import io
+import os
+import textwrap
+
+from twisted.trial import unittest
+from twisted.trial.unittest import SkipTest
+
+HAS_STEM = False
+
+try:
+ from stem.descriptor.server_descriptor import RelayDescriptor
+ from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor
+ from stem.descriptor.router_status_entry import RouterStatusEntryV3
+ from bridgedb.parse import descriptors
+except (ImportError, NameError), error:
+ print("There was an error importing stem: %s" % error)
+else:
+ HAS_STEM = True
+
+from bridgedb.test.util import Benchmarker
+
+
+BRIDGE_NETWORKSTATUS_0 = '''\
+r MiserLandfalls 4IsyTSCtChPhFPAnq5rD8yymlqA /GMC4lz8RXT/62v6kZNdmzSmopk 2014-11-04 06:23:22 2.215.61.223 4056 0
+a [c5fd:4467:98a7:90be:c76a:b449:8e6f:f0a7]:4055
+s Fast Guard Running Stable Valid
+w Bandwidth=1678904
+p reject 1-65535
+'''
+
+BRIDGE_NETWORKSTATUS_1 = '''\
+r Unmentionable BgOrX0ViP5hNsK5ZvixAuPZ6EY0 NTg9NoE5ls9KjF96Dp/UdrabZ9Y 2014-11-04 12:23:37 80.44.173.87 51691 0
+a [da14:7d1e:ba8e:60d0:b078:3f88:382b:5c70]:51690
+s Fast Guard Running Stable Valid
+w Bandwidth=24361
+p reject 1-65535
+'''
+
+BRIDGE_SERVER_DESCRIPTOR = '''\
+ at purpose bridge
+router MiserLandfalls 2.215.61.223 4056 0 0
+or-address [c5fd:4467:98a7:90be:c76a:b449:8e6f:f0a7]:4055
+platform Tor 0.2.2.39 on Linux
+opt protocols Link 1 2 Circuit 1
+published 2014-11-04 06:23:22
+opt fingerprint E08B 324D 20AD 0A13 E114 F027 AB9A C3F3 2CA6 96A0
+uptime 24247659
+bandwidth 1977077890 2234957615 1719198165
+opt extra-info-digest 1CBBB3D6158F324476E6804B7EE25623899271CB
+onion-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAOm4NX2wi8JmgcAyvOyiAEfq9UkzaNHK+VnSZBiPIrb5GAKFibR7S+Bb
+7+x7tsT8VBNbe9QmwML2GVah3xXg68gJAksMNIgFdpud+zMhduuGd0jr7V55aLmH
+ePGJYCh78B9RqfvmeTridp3pljwcAheKKH/YKi3nv1fPY0BwahurAgMBAAE=
+-----END RSA PUBLIC KEY-----
+signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBANd/JkrTZRT24EkK3DDc/E+Nj1QBnKIm/xXMyW0gkotFOVdewIWjwQ5z
+Tn3YbDhrFN0aFYVdVwNbRhW83e+jZDkpIQuxlQOx6bT13vrzmg8ff1tH8I9EePl7
+MO4v0DLPIEcu7Zfz90oC1bl36oqNsD4h0v4yK/XjVwLutIGiy3gTAgMBAAE=
+-----END RSA PUBLIC KEY-----
+contact Somebody <somebody at example.com>
+ntor-onion-key NBsk2O6ks5qnxLhhhKPd59zi0IzfjnakoOJP+Cm8OAE
+reject *:*
+router-signature
+-----BEGIN SIGNATURE-----
+YYA5wJTHcjqXk/QBaDXHX/4Fb8W2OctF4X4VHyxH9Hsou4Ip7nzdfWzbBTcBiIrt
+ybaaMO15L9Ctkli/capN+nCw2jWgivgiPnAmJNmLGeN6skTKjLPAau+839hBuQxu
+P2aB/+XQfzFBA5TaWF83coDng4OGodhwHaOx10Kn7Bg=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_EXTRA_INFO_DESCRIPTOR = '''\
+extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
+published 2014-11-04 06:23:22
+write-history 2014-11-04 06:23:22 (900 s) 3188736,2226176,2866176
+read-history 2014-11-04 06:23:22 (900 s) 3891200,2483200,2698240
+dirreq-write-history 2014-11-04 06:23:22 (900 s) 1024,0,2048
+dirreq-read-history 2014-11-04 06:23:22 (900 s) 0,0,0
+geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
+geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
+dirreq-stats-end 2014-11-04 06:23:22 (86400 s)
+dirreq-v3-ips
+dirreq-v3-reqs
+dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
+transport obfs3 2.215.61.223:4057
+transport obfs2 2.215.61.223:4058
+transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
+bridge-stats-end 2014-11-04 06:23:22 (86400 s)
+bridge-ips ca=8
+bridge-ip-versions v4=8,v6=0
+bridge-ip-transports <OR>=8
+router-signature
+-----BEGIN SIGNATURE-----
+KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
+RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
+U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE = '''\
+extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
+published 2014-11-04 08:10:25
+write-history 2014-11-04 08:10:25 (900 s) 3188736,2226176,2866176,2226176
+read-history 2014-11-04 08:10:25 (900 s) 3891200,2483200,2698240,2483200
+dirreq-write-history 2014-11-04 08:10:25 (900 s) 1024,0,2048,3072
+dirreq-read-history 2014-11-04 08:10:25 (900 s) 0,0,0,0
+geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
+geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
+dirreq-stats-end 2014-11-04 08:10:25 (86400 s)
+dirreq-v3-ips
+dirreq-v3-reqs
+dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
+transport obfs3 2.215.61.223:4057
+transport obfs2 2.215.61.223:4058
+transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
+bridge-stats-end 2014-11-04 08:10:25 (86400 s)
+bridge-ips ca=8
+bridge-ip-versions v4=8,v6=0
+bridge-ip-transports <OR>=8
+router-signature
+-----BEGIN SIGNATURE-----
+KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
+RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
+U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE = '''\
+extra-info MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0
+published 2014-12-04 03:10:25
+write-history 2014-12-04 03:10:25 (900 s) 3188736,2226176,2866176,2226176
+read-history 2014-12-04 03:10:25 (900 s) 3891200,2483200,2698240,2483200
+dirreq-write-history 2014-12-04 03:10:25 (900 s) 1024,0,2048,3072
+dirreq-read-history 2014-12-04 03:10:25 (900 s) 0,0,0,0
+geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F
+geoip6-db-digest E983833985E4BCA34CEF611B2DF51942D188E638
+dirreq-stats-end 2014-12-04 03:10:25 (86400 s)
+dirreq-v3-ips
+dirreq-v3-reqs
+dirreq-v3-resp ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=12,timeout=0,running=0
+transport obfs3 2.215.61.223:4057
+transport obfs2 2.215.61.223:4058
+transport scramblesuit 2.215.61.223:4059 password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+transport obfs4 2.215.61.223:4060 iat-mode=0,node-id=19a448c01aa2e7d55979473b647e282459995b85,public-key=7a61b53701befdae0eeeffaecc73f14e20b537bb0f8b91ad7c2936dc63562b25
+bridge-stats-end 2014-12-04 03:10:25 (86400 s)
+bridge-ips ca=8
+bridge-ip-versions v4=8,v6=0
+bridge-ip-transports <OR>=8
+router-signature
+-----BEGIN SIGNATURE-----
+KOXNPCoe+Q+thFA/Lz7RTja2tWp4oC6SvyIooEZibHtEDgiXuU4sELWT4bSOk3np
+RVmu7QPMmNybx4LHowq3pOeNLtJzpWg8Pfo+N6tR+K4nqPwBRmpsuDhCD/tIXJlP
+U36EY4UoN5ABPowhNZFeyr5A3vKiDr6j0hCOqYOhxPY=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_SERVER_DESCRIPTOR_ED25519 = '''\
+ at purpose bridge
+router piratepartei 80.92.79.70 80 0 0
+identity-ed25519
+-----BEGIN ED25519 CERT-----
+AQQABhauAccW2uNOkPPWU7h9x9FFWtUJXCnw423dKqL/89pTHFRcAQAgBADfGmFI
+//1tBiZZxZ2aXNvvLbEdS/0XHYCWY6Oz3lHCU2xHCJzW03U7htLpq95lWStr2bMm
+D9N1MJp8Zufal71nFV5dgCm0DvMoeCN0d1F6zYnrGvyq+2E6p32x/DG33Qs=
+-----END ED25519 CERT-----
+master-key-ed25519 3xphSP/9bQYmWcWdmlzb7y2xHUv9Fx2AlmOjs95RwlM
+platform Tor 0.2.7.1-alpha-dev on Linux
+protocols Link 1 2 Circuit 1
+published 2015-06-09 21:59:40
+fingerprint 312D 6427 4C29 1560 0584 3EEC B19C 6865 FA3C C10C
+uptime 0
+bandwidth 14971520 104857600 64512
+extra-info-digest 30E10A35CCEA6AA1E04C15FD5F99022F4CACEBC6 pph/KzxlcGa20Sl6/nQl7noyKctzcWkRkTbBX7aIapQ
+onion-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBALRXlkmBc96bz/WFSJ0/NoNYuOivpRBkMDqE0617x63EE9zA+BQGVk81
+5mbF50IQRS12J3F7x+m7USGF7xcUw+id9pe1jzyyqOTo2BPf2Wemif+CvVc9uD0v
+BLO38iImiret0yZtxq3RQ2KaCg2z0y+RPDudR6z/d6V3ASFSlPgBAgMBAAE=
+-----END RSA PUBLIC KEY-----
+signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBALGE2wcWNpWczHlLOa3MbRMKYGDMNe3MsTDKqxftImHuUdMV758q5/4c
+2d0znZ1k5zma7TIKXM1xblVWaHmSZ65jMyy0jgZl7SNbxibP3xM8mfHAJOoWfnQu
+LSj8tKSir2BdA8rncajrDmtQe0C8mxA/RgUHuB6ZF42kAB9lm/33AgMBAAE=
+-----END RSA PUBLIC KEY-----
+onion-key-crosscert
+-----BEGIN CROSSCERT-----
+EPpvZluK8YLLXU00HVskixVqpJfkCeKWXkQPv5Vq87n7E/gtzrVM9A0DasSPHgor
+0Y1jP2K/6G0nuloeDZuNNqPxxz7LEKom5q66UO0Tk4Xdnmj1yp/hSsqi/8sUGe9R
+BmZmuz45UJGmADiYwwFnwec/bKkX3al4BwuQRHwcZd0=
+-----END CROSSCERT-----
+ntor-onion-key-crosscert 1
+-----BEGIN ED25519 CERT-----
+AQoABhSGAd8aYUj//W0GJlnFnZpc2+8tsR1L/RcdgJZjo7PeUcJTABR4eqhKqYNN
+Sgpojtm7C+QRvD3mTk06EEbFly9VrXOaSK4BVxTlsHadm4ti7vdqGHbTWN7DRRu6
+nnUKJPMOsAk=
+-----END ED25519 CERT-----
+hidden-service-dir
+contact 0x02225522 Frenn vun der Enn (FVDE) <info AT enn DOT lu>
+ntor-onion-key ycFwQVUCqJlPaLwJNvlrpgNLwkU780t4pKiILLWZw0o=
+reject *:*
+router-sig-ed25519 /uWcpQeWcwywFwy+O1WGfLQFuxkLMsy8u+rTTum4CQd8uN7bt3VCHRG82X9sc18rMv2VHUs7b+WZcfX39ADMDw
+router-signature
+-----BEGIN SIGNATURE-----
+FpF1a2jF1gkVbSUEuuDrw8ggeyQl4HLqHGXJM/J3SPQDky0OhvqPEV8E0CpONG38
+YNumnkSJ0vjI0YUuVyOZKpODHS/dlXnz5F/Yz8vwQfC7IsNRQgNgf5tbT3iAF8yh
+VC4FdHgFlAkXbiqkpWtD0ojJJjLlEeXbmGILjC1Ls2I=
+-----END SIGNATURE-----
+'''
+
+BRIDGE_EXTRA_INFO_DESCRIPTOR_ED25519 = '''\
+extra-info piratepartei 312D64274C29156005843EECB19C6865FA3CC10C
+identity-ed25519
+-----BEGIN ED25519 CERT-----
+AQQABhauAccW2uNOkPPWU7h9x9FFWtUJXCnw423dKqL/89pTHFRcAQAgBADfGmFI
+//1tBiZZxZ2aXNvvLbEdS/0XHYCWY6Oz3lHCU2xHCJzW03U7htLpq95lWStr2bMm
+D9N1MJp8Zufal71nFV5dgCm0DvMoeCN0d1F6zYnrGvyq+2E6p32x/DG33Qs=
+-----END ED25519 CERT-----
+published 2015-06-09 21:59:40
+write-history 2015-06-09 19:41:54 (14400 s) 1093632,3138560,1309696,1641472,1064960,1799168
+read-history 2015-06-09 19:41:54 (14400 s) 4406272,6537216,5197824,5701632,5342208,5817344
+dirreq-write-history 2015-06-09 19:17:22 (14400 s) 28672,1727488,575488,589824,43008,618496
+dirreq-read-history 2015-06-09 19:17:22 (14400 s) 0,0,0,0,0,0
+geoip-db-digest 0A1F9C09E08F6F2490E8880664D4E863D1680A12
+geoip6-db-digest A6E9B5DE6F887315749B29F9C9F698215BE5240A
+dirreq-stats-end 2015-06-09 12:33:11 (86400 s)
+dirreq-v3-ips ir=8,us=8
+dirreq-v3-reqs ir=8,us=8
+dirreq-v3-resp ok=8,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0
+dirreq-v3-direct-dl complete=0,timeout=0,running=0
+dirreq-v3-tunneled-dl complete=8,timeout=4,running=0
+transport scramblesuit 80.92.79.70:7333 password=S3DVRHWD5375I3AA5NMQBG4WED5MBIYD
+transport fte 80.92.79.70:7331
+transport websocket 80.92.79.70:9901
+transport obfs3 80.92.79.70:7332
+transport obfs4 80.92.79.70:7334 cert=/Q8QygIhLarhjvB+rKiFvSmXdjhO9AF6OXACR8JH+voMwKF0s5uMaG3H3uEBiZNQI79jPw,iat-mode=0
+bridge-stats-end 2015-06-09 12:33:17 (86400 s)
+bridge-ips us=24
+bridge-ip-versions v4=24,v6=0
+bridge-ip-transports obfs4=24
+router-sig-ed25519 O+yrUnkHXZ16Cf0+a3gfDl2ggygbxQUal4kRi5BD2v3NW8CrWjqGJLBjked8g5eJCThUXZuraHwkapeu8gtAAg
+router-signature
+-----BEGIN SIGNATURE-----
+HbZs8ckBwKbJ4vg0LJztGosNaDqSRD+pHiWgBAmx9ARbz7niJMY/ql+Qxh7NFifQ
+xa39dJvObxE65qeaZJvcznSyEkUDcHBFcHLWZev7XQjXf2no9vUL86JvwBKHHKC1
+GnoYumyiqlKn3MOiqVYN5KXhO5i6qN/W8SjMVvywxZI=
+-----END SIGNATURE-----
+extra-info Unnamed 9673B58C3A72BC279C4FADEA678DEDCF63E524D2
+published 2015-06-09 22:00:10
+router-signature
+-----BEGIN SIGNATURE-----
+S57LSzZy2ecjd9jqA5R7nzRUOWJBNVGA8TMLiuWMHXj4DY540ZgbObNtAIU/uzR5
+C3sfCVx39iQ39DKgi93zaeZ7s37KGKiUXwJkvDsY0+A2N/TNX5DyI0ZH8WAwCMNq
+EXVgdbhn8RrQiVT69evPXjdr6hmgllUofRT7LimvR60=
+-----END SIGNATURE-----
+'''
+
+
+class ParseDescriptorsTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.parse.descriptors` module."""
+
+ skip = True if not HAS_STEM else False
+
+ def setUp(self):
+ """Test if we have Stem installed. Skip these tests if it's missing."""
+ self.expectedIPBridge0 = '2.215.61.223'
+ self.expectedIPBridge1 = '80.44.173.87'
+
+ self.expectedFprBridge0 = 'E08B324D20AD0A13E114F027AB9AC3F32CA696A0'
+
+ if self.skip:
+ raise unittest.SkipTest("Couldn't import Stem.")
+
+ def writeTestDescriptorsToFile(self, filename, *descriptors):
+ """Write **descriptors** to **filename**.
+
+ :param str filename: A filename. It will be appended to the current
+ working directory automatically.
+ :param str descriptors: Some optional strings containing
+ descriptors. Each one will be written to **filename** as-is.
+ :rtype: str
+ :returns: The full path to the file which was written to.
+ """
+ descFilename = os.path.join(os.getcwd(), filename)
+ with open(descFilename, 'w') as fh:
+ for desc in descriptors:
+ fh.write(desc)
+ fh.flush()
+ return descFilename
+
+ def test_parse_descriptors_parseServerDescriptorsFile(self):
+ """Test for ``b.p.descriptors.parseServerDescriptorsFile``."""
+ descFile = io.BytesIO(BRIDGE_SERVER_DESCRIPTOR)
+ routers = descriptors.parseServerDescriptorsFile(descFile)
+ self.assertIsInstance(routers, list)
+ bridge = routers[0]
+ self.assertIsInstance(bridge, RelayDescriptor)
+ self.assertEqual(bridge.address, self.expectedIPBridge0)
+ self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
+
+ def test_parse_descriptors_parseNetworkStatusFile_return_type(self):
+ """``b.p.descriptors.parseNetworkStatusFile`` should return a dict."""
+ # Write the descriptor to a file for testing. This is necessary
+ # because the function opens the networkstatus file to read it.
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ BRIDGE_NETWORKSTATUS_0)
+ routers = descriptors.parseNetworkStatusFile(descFile)
+ self.assertIsInstance(routers, list)
+
+ def test_parse_descriptors_parseNetworkStatusFile_has_RouterStatusEntryV2(self):
+ """The items in the dict returned from
+ ``b.p.descriptors.parseNetworkStatusFile`` should be
+ ``RouterStatusEntryV2``s.
+ """
+ # Write the descriptor to a file for testing. This is necessary
+ # because the function opens the networkstatus file to read it.
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ BRIDGE_NETWORKSTATUS_0)
+ routers = descriptors.parseNetworkStatusFile(descFile)
+ bridge = routers[0]
+ self.assertIsInstance(bridge, RouterStatusEntryV3)
+
+ def test_parse_descriptors_parseNetworkStatusFile_one_file(self):
+ """Test ``b.p.descriptors.parseNetworkStatusFile`` with one bridge
+ networkstatus descriptor.
+ """
+ # Write the descriptor to a file for testing. This is necessary
+ # because the function opens the networkstatus file to read it.
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ BRIDGE_NETWORKSTATUS_0)
+ routers = descriptors.parseNetworkStatusFile(descFile)
+ bridge = routers[0]
+ self.assertEqual(bridge.address, self.expectedIPBridge0)
+ self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
+
+ def test_parse_descriptors_parseNetworkStatusFile_two_files(self):
+ """Test ``b.p.descriptors.parseNetworkStatusFile`` with two bridge
+ networkstatus descriptors.
+ """
+ expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
+
+ # Write the descriptor to a file for testing. This is necessary
+ # because the function opens the networkstatus file to read it.
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ BRIDGE_NETWORKSTATUS_0,
+ BRIDGE_NETWORKSTATUS_1)
+ routers = descriptors.parseNetworkStatusFile(descFile)
+ bridge = routers[0]
+
+ self.assertIn(bridge.address, expectedIPs)
+ self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
+
+ def test_parse_descriptors_parseNetworkStatusFile_bad_nickname(self):
+ """``b.p.descriptors.parseNetworkStatusFile`` with a bridge
+ networkstatus descriptor which has a nickname that is too long should
+ raise InvalidRouterNickname.
+ """
+ unparseable = BRIDGE_NETWORKSTATUS_0.replace(
+ 'MiserLandfalls',
+ 'MiserLandfallsWaterfallsSnowfallsAvalanche')
+ # Write the descriptor to a file for testing. This is necessary
+ # because the function opens the networkstatus file to read it.
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ unparseable)
+ self.assertRaises(descriptors.InvalidRouterNickname,
+ descriptors.parseNetworkStatusFile,
+ descFile)
+
+ def test_parse_descriptors_parseNetworkStatusFile_IPv6_ORAddress(self):
+ """A Bridge can't have its primary ORAddress be IPv6 without raising
+ a ValueError.
+ """
+ unparseable = BRIDGE_NETWORKSTATUS_0.replace(
+ '2.215.61.223', '[2837:fcd2:387b:e376:34c:1ec7:11ff:1686]')
+ descFile = self.writeTestDescriptorsToFile('networkstatus-bridges',
+ unparseable)
+ self.assertRaises(ValueError,
+ descriptors.parseNetworkStatusFile,
+ descFile)
+
+ def test_parse_descriptors_parseNetworkStatusFile_with_annotations(self):
+ """Test ``b.p.descriptors.parseNetworkStatusFile`` with some document
+ headers before the first 'r'-line.
+ """
+ expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
+ descFile = 'networkstatus-bridges'
+
+ with open(descFile, 'w') as fh:
+ fh.write('signature and stuff from the BridgeAuth would go here\n')
+ fh.write('some more annotations with parameters and stuff\n')
+ fh.write(BRIDGE_NETWORKSTATUS_0)
+ fh.write(BRIDGE_NETWORKSTATUS_1)
+ fh.flush()
+
+ routers = descriptors.parseNetworkStatusFile(descFile)
+ bridge = routers[0]
+ self.assertIn(bridge.address, expectedIPs)
+ self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
+
+ def test_parse_descriptors_parseNetworkStatusFile_with_annotations_no_skipping(self):
+ """Test ``b.p.descriptors.parseNetworkStatusFile`` with some
+ document headers before the first 'r'-line, but without skipping said
+ annotations.
+ """
+ expectedIPs = [self.expectedIPBridge0, self.expectedIPBridge1]
+ descFile = 'networkstatus-bridges'
+
+ with open(descFile, 'w') as fh:
+ fh.write('signature and stuff from the BridgeAuth would go here\n')
+ fh.write('some more annotations with parameters and stuff\n')
+ fh.write(BRIDGE_NETWORKSTATUS_0)
+ fh.write(BRIDGE_NETWORKSTATUS_1)
+ fh.flush()
+
+ self.assertRaises(ValueError,
+ descriptors.parseNetworkStatusFile,
+ descFile, skipAnnotations=False)
+
+ def test_parse_descriptors_parseExtraInfoFiles_return_type(self):
+ """The return type of ``b.p.descriptors.parseExtraInfoFiles``
+ should be a dictionary (after deduplication).
+ """
+ descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFile)
+ self.assertIsInstance(routers, dict)
+
+ def test_parse_descriptors_parseExtraInfoFiles_has_BridgeExtraInfoDescriptor(self):
+ """The return of ``b.p.descriptors.parseExtraInfoFiles`` should
+ contain ``BridgeExtraInfoDescriptor``s.
+ """
+ descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFile)
+ bridge = routers.values()[0]
+ self.assertIsInstance(bridge, RelayExtraInfoDescriptor)
+
+ def test_parse_descriptors_parseExtraInfoFiles_one_file(self):
+ """Test for ``b.p.descriptors.parseExtraInfoFiles`` with only one
+ bridge extrainfo file.
+ """
+ descFile = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFile)
+ bridge = routers.values()[0]
+
+ # The number of transports we parsed should be equal to the number of
+ # 'transport' lines in the descriptor:
+ self.assertEqual(len(bridge.transport),
+ BRIDGE_EXTRA_INFO_DESCRIPTOR.count('transport '))
+
+ self.assertEqual(bridge.fingerprint, self.expectedFprBridge0)
+
+ def test_parse_descriptors_deduplicate_identical_timestamps(self):
+ """Parsing two descriptors for the same bridge with identical
+ timestamps should log a ``b.p.descriptors.DescriptorWarning``
+ and retain only one copy of the descriptor.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
+
+ self.assertEqual(len(routers), 1)
+
+ def test_parse_descriptors_parseExtraInfoFiles_two_files(self):
+ """Test for ``b.p.descriptors.parseExtraInfoFiles`` with two
+ bridge extrainfo files, and check that only the newest extrainfo
+ descriptor is used.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
+ routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
+
+ # We shouldn't have duplicates:
+ self.assertEqual(len(routers), 1,
+ "We shouldn't have any duplicate descriptors.")
+
+ # We should only have the newest descriptor:
+ bridge = routers.values()[0]
+ self.assertEqual(
+ bridge.published,
+ datetime.datetime.strptime("2014-11-04 08:10:25", "%Y-%m-%d %H:%M:%S"),
+ "We should have the newest available descriptor for this router.")
+
+ def test_parse_descriptors_parseExtraInfoFiles_two_files_reverse(self):
+ """Test for ``b.p.descriptors.parseExtraInfoFiles`` with two bridge
+ extrainfo files. This time, they are processed in reverse to ensure
+ that we only keep the newer duplicates of descriptors, no matter what
+ order they appeared in the files.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFileOne, descFileTwo)
+
+ self.assertEqual(len(routers), 1,
+ "We shouldn't have any duplicate descriptors.")
+
+ bridge = routers.values()[0]
+ self.assertEqual(
+ bridge.published,
+ datetime.datetime.strptime("2014-11-04 08:10:25", "%Y-%m-%d %H:%M:%S"),
+ "We should have the newest available descriptor for this router.")
+
+ def test_parse_descriptors_parseExtraInfoFiles_three_files(self):
+ """Test for ``b.p.descriptors.parseExtraInfoFiles`` with three
+ bridge extrainfo files, and check that only the newest extrainfo
+ descriptor is used.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWER_DUPLICATE)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ descFileThree = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
+ routers = descriptors.parseExtraInfoFiles(descFileOne,
+ descFileTwo,
+ descFileThree)
+
+ # We shouldn't have duplicates:
+ self.assertEqual(len(routers), 1,
+ "We shouldn't have any duplicate descriptors.")
+
+ # We should only have the newest descriptor:
+ bridge = routers.values()[0]
+ self.assertEqual(
+ bridge.published,
+ datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
+ "We should have the newest available descriptor for this router.")
+
+ def createDuplicatesForBenchmark(self, b=1, n=1200):
+ """Create a bunch of duplicate extrainfos for benchmark tests.
+
+ :param int b: The number of fake "bridges" to create **n** duplicate
+ descriptors for.
+ :param int n: The number of duplicate descriptors for each bridge
+ **b**.
+ """
+ descFiles = []
+
+ # The timestamp and fingerprint from BRIDGE_EXTRA_INFO_DESCRIPTOR:
+ timestamp = "2014-11-04 06:23:22"
+ Y, M, rest = timestamp.split("-")
+ fpr = "E08B324D20AD0A13E114F027AB9AC3F32CA696A0"
+ newerFpr = "E08B324D20AD0A13E114F027AB9AC3F32CA696A0"
+
+ total = 0
+ needed = b * n
+ for x in range(b):
+ if total >= needed:
+ break
+ # Re-digest the fingerprint to create a "new" bridge
+ newerFpr = hashlib.sha1(newerFpr).hexdigest().upper()
+ # Generate n extrainfos with different timestamps:
+ count = 0
+ for year in range(1, ((n + 1)/ 12) + 2): # Start from the next year
+ if count >= n:
+ break
+ for month in range(1, 13):
+ if count < n:
+ newerTimestamp = "-".join([str(int(Y) + year), "%02d" % month, rest])
+ newerDuplicate = BRIDGE_EXTRA_INFO_DESCRIPTOR[:].replace(
+ fpr, newerFpr).replace(
+ timestamp, newerTimestamp)
+ descFiles.append(io.BytesIO(newerDuplicate))
+ count += 1
+ total += 1
+ else:
+ break
+
+ print("Deduplicating %5d total descriptors (%4d per bridge; %3d bridges):"
+ % (len(descFiles), n, b), end='\t')
+ return descFiles
+
+ def test_parse_descriptors_parseExtraInfoFiles_benchmark_100_bridges(self):
+ """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``."""
+ print()
+ for i in range(1, 6):
+ descFiles = self.createDuplicatesForBenchmark(b=100, n=i)
+ with Benchmarker():
+ routers = descriptors.parseExtraInfoFiles(*descFiles)
+
+ def test_parse_descriptors_parseExtraInfoFiles_benchmark_1000_bridges(self):
+ """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``."""
+ raise SkipTest(("This test can take several minutes to complete. "
+ "Run it on your own free time."))
+
+ print()
+ for i in range(1, 6):
+ descFiles = self.createDuplicatesForBenchmark(b=1000, n=i)
+ with Benchmarker():
+ routers = descriptors.parseExtraInfoFiles(*descFiles)
+
+ def test_parse_descriptors_parseExtraInfoFiles_benchmark_10000_bridges(self):
+ """Benchmark test for ``b.p.descriptors.parseExtraInfoFiles``.
+ The algorithm should grow linearly in the number of duplicates.
+ """
+ raise SkipTest(("This test can take several minutes to complete. "
+ "Run it on your own free time."))
+
+ print()
+ for i in range(1, 6):
+ descFiles = self.createDuplicatesForBenchmark(b=10000, n=i)
+ with Benchmarker():
+ routers = descriptors.parseExtraInfoFiles(*descFiles)
+
+ def test_parse_descriptors_parseExtraInfoFiles_no_validate(self):
+ """Test for ``b.p.descriptors.parseExtraInfoFiles`` with
+ descriptor validation disabled.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ routers = descriptors.parseExtraInfoFiles(descFileOne,
+ validate=False)
+ self.assertGreaterEqual(len(routers), 1)
+
+ def test_parse_descriptors_parseExtraInfoFiles_unparseable(self):
+ """Test parsing three extrainfo descriptors: one is a valid descriptor,
+ one is an older duplicate, and one is unparseable (it has a bad
+ geoip-db-digest line). There should be only one descriptor returned
+ after parsing.
+ """
+ # Give it a bad geoip-db-digest:
+ unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
+ "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
+ "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
+ "geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F",
+ "geoip-db-digest FOOOOOOOOOOOOOOOOOOBAAAAAAAAAAAAAAAAAARR")
+
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ # This must be a "real" file or _copyUnparseableDescriptorFile() will
+ # raise an AttributeError saying:
+ # '_io.BytesIO' object has no attribute 'rpartition'"
+ descFileThree = self.writeTestDescriptorsToFile(
+ "unparseable-descriptor", unparseable)
+ routers = descriptors.parseExtraInfoFiles(descFileOne,
+ descFileTwo,
+ descFileThree)
+ self.assertIsInstance(routers, dict)
+ self.assertEqual(len(routers), 1, (
+ "There were three extrainfo descriptors: one was a duplicate, "
+ "and one was unparseable, so that should only leave one "
+ "descriptor remaining."))
+
+ bridge = routers.values()[0]
+ self.assertEqual(
+ bridge.fingerprint,
+ "E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
+ ("It looks like the (supposedly) unparseable bridge was returned "
+ "instead of the valid one!"))
+ self.assertEqual(
+ bridge.published,
+ datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
+ "We should have the newest available descriptor for this router.")
+
+ def test_parse_descriptors_parseExtraInfoFiles_unparseable_and_parseable(self):
+ """Test parsing four extrainfo descriptors: two are valid descriptors,
+ one is an older duplicate of one of the valid descriptors, and one is
+ unparseable (it has a line we shouldn't recognise). There should be
+ only two descriptors returned after parsing.
+ """
+ # Mess up the bridge-ip-transports line:
+ unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
+ "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
+ "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
+ "bridge-ip-transports <OR>=8",
+ "bridge-ip-transports <OR>")
+
+ parseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
+ "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
+ "ImOkWithBeingParsed 2B5DA67FBA13A6449DE625673B7AE9E3AA7DF75F")
+
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
+ # This must be a "real" file or _copyUnparseableDescriptorFile() will
+ # raise an AttributeError saying:
+ # '_io.BytesIO' object has no attribute 'rpartition'"
+ descFileThree = self.writeTestDescriptorsToFile(
+ "unparseable-descriptor.new", unparseable)
+ descFileFour = io.BytesIO(parseable)
+ routers = descriptors.parseExtraInfoFiles(descFileOne,
+ descFileTwo,
+ descFileThree,
+ descFileFour)
+ self.assertIsInstance(routers, dict)
+ self.assertEqual(len(routers), 2, (
+ "There were four extrainfo descriptors: one was a duplicate, "
+ "and one was unparseable, so that should only leave two "
+ "descriptors remaining."))
+
+ self.assertNotIn("F373CC1D86D82267F1F1F5D39470F0E0A022122E", routers.keys(),
+ "The 'unparseable' descriptor was returned by the parser.")
+
+ self.assertIn("E08B324D20AD0A13E114F027AB9AC3F32CA696A0", routers.keys(),
+ ("A bridge extrainfo which had duplicates was completely missing "
+ "from the data which the parser returned."))
+ self.assertEqual(
+ routers["E08B324D20AD0A13E114F027AB9AC3F32CA696A0"].published,
+ datetime.datetime.strptime("2014-12-04 03:10:25", "%Y-%m-%d %H:%M:%S"),
+ "We should have the newest available descriptor for this router.")
+
+ self.assertIn("2B5DA67FBA13A6449DE625673B7AE9E3AA7DF75F", routers.keys(),
+ "The 'parseable' descriptor wasn't returned by the parser.")
+
+ def test_parse_descriptors_parseExtraInfoFiles_bad_signature_footer(self):
+ """Calling parseExtraInfoFiles() with a descriptor which has a
+ signature with a bad "-----END SIGNATURE-----" footer should return
+ zero parsed descriptors.
+ """
+ unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
+ '-----END SIGNATURE-----',
+ '-----END SIGNATURE FOR REALZ-----')
+ # This must be a "real" file or _copyUnparseableDescriptorFile() will
+ # raise an AttributeError saying:
+ # '_io.BytesIO' object has no attribute 'rpartition'"
+ descFileOne = self.writeTestDescriptorsToFile(
+ "bad-signature-footer", unparseable)
+ routers = descriptors.parseExtraInfoFiles(descFileOne)
+
+ self.assertEqual(len(routers), 0)
+
+ def test_parse_descriptors_parseExtraInfoFiles_missing_signature(self):
+ """Calling parseExtraInfoFiles() with a descriptor which is
+ missing the signature should return zero parsed descriptors.
+ """
+ # Remove the signature
+ BEGIN_SIG = '-----BEGIN SIGNATURE-----'
+ unparseable, _ = BRIDGE_EXTRA_INFO_DESCRIPTOR.split(BEGIN_SIG)
+ # This must be a "real" file or _copyUnparseableDescriptorFile() will
+ # raise an AttributeError saying:
+ # '_io.BytesIO' object has no attribute 'rpartition'"
+ descFileOne = self.writeTestDescriptorsToFile(
+ "missing-signature", unparseable)
+ routers = descriptors.parseExtraInfoFiles(descFileOne)
+
+ self.assertEqual(len(routers), 0)
+
+ def test_parse_descriptors_parseExtraInfoFiles_bad_signature_too_short(self):
+ """Calling _verifyExtraInfoSignature() with a descriptor which has a
+ bad signature should raise an InvalidExtraInfoSignature exception.
+ """
+ # Truncate the signature to 50 bytes
+ BEGIN_SIG = '-----BEGIN SIGNATURE-----'
+ doc, sig = BRIDGE_EXTRA_INFO_DESCRIPTOR.split(BEGIN_SIG)
+ unparseable = BEGIN_SIG.join([doc, sig[:50]])
+ # This must be a "real" file or _copyUnparseableDescriptorFile() will
+ # raise an AttributeError saying:
+ # '_io.BytesIO' object has no attribute 'rpartition'"
+ descFileOne = self.writeTestDescriptorsToFile(
+ "truncated-signature", unparseable)
+ routers = descriptors.parseExtraInfoFiles(descFileOne)
+
+ self.assertEqual(len(routers), 0)
+
+ def test_parse_descriptors_parseExtraInfoFiles_unparseable_BytesIO(self):
+ """Test parsing three extrainfo descriptors: one is a valid descriptor,
+ one is an older duplicate, and one is unparseable (it has a bad
+ geoip-db-digest line). The parsing should raise an unhandled
+ AttributeError because _copyUnparseableDescriptorFile() tries to
+ manipulate the io.BytesIO object's filename, and it doesn't have one.
+ """
+ # Give it a bad geoip-db-digest:
+ unparseable = BRIDGE_EXTRA_INFO_DESCRIPTOR.replace(
+ "MiserLandfalls E08B324D20AD0A13E114F027AB9AC3F32CA696A0",
+ "DontParseMe F373CC1D86D82267F1F1F5D39470F0E0A022122E").replace(
+ "geoip-db-digest 09A0E093100B279AD9CFF47A67B13A21C6E1483F",
+ "geoip-db-digest FOOOOOOOOOOOOOOOOOOBAAAAAAAAAAAAAAAAAARR")
+
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR)
+ descFileTwo = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_NEWEST_DUPLICATE)
+ descFileThree = io.BytesIO(unparseable)
+ self.assertRaises(AttributeError,
+ descriptors.parseExtraInfoFiles,
+ descFileOne, descFileTwo, descFileThree)
+
+ def test_parse_descriptors_parseExtraInfoFiles_empty_file(self):
+ """Test parsing an empty extrainfo descriptors file."""
+ routers = descriptors.parseExtraInfoFiles(io.BytesIO(''))
+ self.assertIsInstance(routers, dict)
+ self.assertEqual(len(routers), 0)
+
+ def test_parse_descriptors_parseExtraInfoFiles_ed25519(self):
+ """Test parsing an extrainfo descriptor with Ed25519 keys/certificates.
+ """
+ descFileOne = io.BytesIO(BRIDGE_EXTRA_INFO_DESCRIPTOR_ED25519)
+ routers = descriptors.parseExtraInfoFiles(descFileOne)
+ self.assertEqual(len(routers), 1)
+
+ def test_parse_descriptors_parseExtraInfoFiles_ed25519(self):
+ """Test parsing an extrainfo descriptor with Ed25519 keys/certificates.
+ """
+ descFileOne = io.BytesIO(BRIDGE_SERVER_DESCRIPTOR_ED25519)
+ routers = descriptors.parseServerDescriptorsFile(descFileOne)
+ self.assertIsInstance(routers, list)
+ self.assertEqual(len(routers), 1)
+
+ bridge = routers[0]
+ self.assertIsInstance(bridge, RelayDescriptor)
+ self.assertEqual(bridge.address, u'80.92.79.70')
+ self.assertEqual(bridge.fingerprint, u'312D64274C29156005843EECB19C6865FA3CC10C')
+
+ def test_parse_descriptors_copyUnparseableDescriptorFile_return_value(self):
+ """``b.p.descriptors._copyUnparseableDescriptorFile()`` should return
+ True when the new file is successfully created.
+ """
+ filename = "bridge-descriptors"
+ with open(filename, 'w') as fh:
+ fh.write(BRIDGE_SERVER_DESCRIPTOR)
+ fh.flush()
+
+ result = descriptors._copyUnparseableDescriptorFile(filename)
+ self.assertTrue(result) # should return True
+
+ def test_parse_descriptors_copyUnparseableDescriptorFile_new_filename(self):
+ """``b.p.descriptors._copyUnparseableDescriptorFile()`` should create a
+ copy of the bad file with a specific filename format.
+ """
+ filename = "bridge-descriptors"
+ with open(filename, 'w') as fh:
+ fh.write(BRIDGE_SERVER_DESCRIPTOR)
+ fh.flush()
+
+ descriptors._copyUnparseableDescriptorFile(filename)
+ matchingFiles = glob.glob("*_bridge-descriptors.unparseable")
+ self.assertEqual(len(matchingFiles), 1)
+
+ newFile = matchingFiles[-1]
+ self.assertTrue(os.path.isfile(newFile))
+
+ timestamp = datetime.datetime.strptime(newFile.split("_")[0],
+ "%Y-%m-%d-%H:%M:%S")
+ # The timestamp should be roughly today (unless we just passed
+ # midnight, then it might be +/- 1):
+ self.assertApproximates(timestamp.now().day, timestamp.day, 1)
+
+ # The timestamp should be roughly this hour (+/- 1):
+ self.assertApproximates(timestamp.now().hour, timestamp.hour, 1)
diff --git a/test/test_parse_headers.py b/test/test_parse_headers.py
new file mode 100644
index 0000000..278e721
--- /dev/null
+++ b/test/test_parse_headers.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2014-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.parse.headers` module."""
+
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from twisted.trial import unittest
+
+from bridgedb.parse import headers
+
+
+class ParseAcceptLanguageTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.headers.parseAcceptLanguage`."""
+
+ def test_noHeaders(self):
+ """No header should return an empty list."""
+ header = None
+ langs = headers.parseAcceptLanguage(header)
+ self.assertIsInstance(langs, list)
+ self.assertEqual(len(langs), 0)
+
+ def test_defaultTBBHeader(self):
+ """The header 'en-us,en;q=0.5' should return ['en_us', 'en']."""
+ header = 'en-us,en;q=0.5'
+ langs = headers.parseAcceptLanguage(header)
+ self.assertIsInstance(langs, list)
+ self.assertEqual(len(langs), 2)
+ self.assertEqual(langs[0], 'en_us')
+ self.assertEqual(langs[1], 'en')
+
+ def test_addNonLocalizedVariant(self):
+ """The header 'en-us,en-gb;q=0.5' should return
+ ['en_us', 'en', 'en_gb'].
+ """
+ header = 'en-us,en-gb;q=0.5'
+ langs = headers.parseAcceptLanguage(header)
+ self.assertIsInstance(langs, list)
+ self.assertEqual(len(langs), 3)
+ self.assertEqual(langs[0], 'en_us')
+ self.assertEqual(langs[1], 'en')
+ self.assertEqual(langs[2], 'en_gb')
diff --git a/test/test_parse_nickname.py b/test/test_parse_nickname.py
new file mode 100644
index 0000000..fea000a
--- /dev/null
+++ b/test/test_parse_nickname.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.parse.nickname` module."""
+
+from twisted.trial import unittest
+
+from bridgedb.parse.nickname import isValidRouterNickname
+
+
+class IsValidRouterNicknameTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.parse.nickname.isValidRouterNickname`."""
+
+ def test_parse_nickname_isValidRouterNickname_valid(self):
+ """isValidRouterNickname() should return True for a valid nickname."""
+ name = 'Unmentionable'
+ self.assertTrue(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_valid_1(self):
+ """isValidRouterNickname() should return True for a valid nickname."""
+ name = 'maketotaldestroy'
+ self.assertTrue(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_invalid_symbols(self):
+ """isValidRouterNickname() should return False for an invalid nickname
+ (with symbols in it).
+ """
+ name = 'what_the_bl#@p?!'
+ self.assertFalse(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_invalid_too_long(self):
+ """isValidRouterNickname() should return False for an invalid nickname
+ (too long).
+ """
+ name = 'ThisIsReallyMoreOfANovellaRatherThanAnOnionRouterNickname'
+ self.assertFalse(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_invalid_too_short(self):
+ """isValidRouterNickname() should return False for an invalid nickname
+ (empty string).
+ """
+ name = ''
+ self.assertFalse(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_invalid_None(self):
+ """isValidRouterNickname(None) should return False."""
+ name = None
+ self.assertFalse(isValidRouterNickname(name))
+
+ def test_parse_nickname_isValidRouterNickname_invalid_spaces(self):
+ """isValidRouterNickname() should return False for an invalid nickname
+ (contains spaces).
+ """
+ name = 'As you wish'
+ self.assertFalse(isValidRouterNickname(name))
diff --git a/test/test_parse_options.py b/test/test_parse_options.py
new file mode 100644
index 0000000..c505c36
--- /dev/null
+++ b/test/test_parse_options.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2014-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.parse.options`."""
+
+
+from __future__ import print_function
+
+import os
+import sys
+
+from twisted.python.usage import UsageError
+from twisted.trial import unittest
+
+from bridgedb.parse import options
+
+
+class ParseOptionsTests(unittest.TestCase):
+ """Unittests for :mod:`bridgedb.parse.options`."""
+
+ def setUp(self):
+ """Replace the current sys.argv's for the run of this test, and
+ redirect sys.stdout to os.devnull to prevent the options parser from
+ printing the --help a bunch of times.
+ """
+ # Make sure a config file is in the current directory, or else the
+ # argument parser will get angry and throw another SystemExit
+ # exception.
+ with open(os.path.join(os.getcwd(), 'bridgedb.conf'), 'a+') as fh:
+ fh.write('\n')
+
+ self.oldSysArgv = sys.argv
+ self.oldStdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
+
+ def tearDown(self):
+ """Put the original sys.argv's back."""
+ sys.stdout.close() # Actually closes the FD we opened for /dev/null
+ sys.argv = self.oldSysArgv
+ sys.stdout = self.oldStdout
+ self.oldSysArgv = None
+ self.oldStdout = None
+
+ def test_parse_options_parseOptions_with_invalid_options(self):
+ """:func:`options.parseOptions` should raise SystemExit because
+ the args 'somearg anotherarg' are invalid commands.
+ """
+ fakeSysArgv = ['somearg', 'anotherarg']
+ sys.argv = fakeSysArgv
+ self.assertRaises(SystemExit, options.parseOptions)
+
+ def test_parse_options_parseOptions_with_valid_options(self):
+ """:func:`options.parseOptions` should return a
+ :class:`options.MainOptions` when given valid commandline arguments.
+ """
+ fakeSysArgv = ['bridgedb', 'mock', '-n', '-1']
+ sys.argv = fakeSysArgv
+ opts = options.parseOptions()
+ self.assertIsInstance(opts, options.MainOptions)
+
+ def test_parse_options_parseOptions_verbosity_quiet_quiet(self):
+ """If we use `-q` twice on the commandline, ``opts['verbosity']``
+ should equal ``10``.
+ """
+ fakeSysArgv = ['bridgedb', '-q', '-q', 'mock', '-n', '-1']
+ sys.argv = fakeSysArgv
+ opts = options.parseOptions()
+ self.assertEqual(opts['verbosity'], 10)
+
+ def test_parse_options_parseOptions_verbosity_verbose(self):
+ """If we use `-v` once on the commandline, ``opts['verbosity']``
+ should equal ``50``.
+ """
+ fakeSysArgv = ['bridgedb', '-v', '-v', 'mock', '-n', '-1']
+ sys.argv = fakeSysArgv
+ opts = options.parseOptions()
+ self.assertEqual(opts['verbosity'], 50)
+
+ def test_parse_options_parseOptions_rundir(self):
+ """The automatic rundir should be our current directory."""
+ fakeSysArgv = ['bridgedb', 'mock', '-n', '-1']
+ sys.argv = fakeSysArgv
+ opts = options.parseOptions()
+ self.assertEqual(opts['rundir'], os.getcwd())
+
+ def test_parse_options_parseOptions_version(self):
+ """:func:`options.parseOptions` when given a `--version` argument on
+ the commandline, should raise SystemExit (after printing some stuff,
+ but we don't care what it prints).
+ """
+ fakeSysArgv = ['bridgedb', '--version']
+ sys.argv = fakeSysArgv
+ self.assertRaises(SystemExit, options.parseOptions)
diff --git a/test/test_parse_versions.py b/test/test_parse_versions.py
new file mode 100644
index 0000000..becdd20
--- /dev/null
+++ b/test/test_parse_versions.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2014-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.parse.versions`."""
+
+
+from __future__ import print_function
+
+from twisted.trial import unittest
+
+from bridgedb.parse import versions
+
+
+class ParseVersionTests(unittest.TestCase):
+ """Unitests for :class:`bridgedb.parse.versions.Version`."""
+
+ def test_Version_with_bad_delimiter(self):
+ """Test parsing a version number which uses '-' as a delimiter."""
+ self.assertRaises(versions.InvalidVersionStringFormat,
+ versions.Version, '2-6-0', package='tor')
+
+ def test_Version_really_long_version_string(self):
+ """Parsing a version number which is way too long should raise
+ an IndexError which is ignored.
+ """
+ v = versions.Version('2.6.0.0.beta', package='tor')
+ self.assertEqual(v.prerelease, 'beta')
+ self.assertEqual(v.major, 6)
+
+ def test_Version_string(self):
+ """Test converting a valid Version object into string form."""
+ v = versions.Version('0.2.5.4', package='tor')
+ self.assertEqual(v.base(), '0.2.5.4')
diff --git a/test/test_persistent.py b/test/test_persistent.py
new file mode 100644
index 0000000..95abf61
--- /dev/null
+++ b/test/test_persistent.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.persistent` module.
+
+These tests are meant to ensure that the :mod:`bridgedb.persistent` module is
+functioning as expected.
+"""
+
+from __future__ import print_function
+
+import os.path
+
+from copy import deepcopy
+from io import StringIO
+
+from bridgedb import persistent
+from bridgedb.parse.options import MainOptions
+from twisted.python import log
+from twisted.trial import unittest
+
+import sure
+from sure import this
+from sure import the
+from sure import expect
+
+
+TEST_CONFIG_FILE = StringIO(unicode("""\
+BRIDGE_FILES = ['bridge-descriptors', 'bridge-descriptors.new']
+LOGFILE = 'bridgedb.log'"""))
+
+
+class StateTest(unittest.TestCase):
+ """Tests for :class:`bridgedb.persistent.State`."""
+
+ timeout = 15
+
+ def setUp(self):
+ configuration = {}
+ TEST_CONFIG_FILE.seek(0)
+ compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
+ exec compiled in configuration
+ config = persistent.Conf(**configuration)
+
+ fakeArgs = ['-c', os.path.join(os.getcwdu(), '..', 'bridgedb.conf')]
+ options = MainOptions()
+ options.parseOptions(fakeArgs)
+
+ self.options = options
+ self.config = config
+ self.state = persistent.State(**config.__dict__)
+ self.state.options = options
+ self.state.config = config
+
+ def test_configCreation(self):
+ this(self.config).should.be.ok
+ this(self.config).should.be.a(persistent.Conf)
+
+ def test_optionsCreation(self):
+ this(self.options).should.be.ok
+ this(self.options).should.be.a(dict)
+
+ def test_stateCreation(self):
+ this(self.state).should.be.ok
+
+ this(self.state).should.have.property('config').being.ok
+ this(self.state).should.have.property('config').being.equal(self.config)
+
+ this(self.state.options).should.be.ok
+ this(self.state.options).should.equal(self.options)
+
+ def test_docstring_persistent(self):
+ persistent.should.have.property('__doc__').being.a(str)
+
+ def test_docstring_persistentState(self):
+ the(self.state).should.have.property('__doc__').being.a(str)
+
+ def test_state_init(self):
+ this(self.state).should.have.property('config')
+ this(self.state).should.have.property('proxyList')
+ this(self.state).should.have.property('statefile')
+
+ def test_persistent_getState(self):
+ persistent.should.have.property('_getState').being(callable)
+ this(persistent._getState()).should.be.a(persistent.State)
+
+ def test_getStateFor(self):
+ jellyState = self.state.getStateFor(self)
+ expect(jellyState).to.be.a(dict)
+ expect(jellyState.keys()).to.contain('LOGFILE')
+
+ def test_STATEFILE(self):
+ this(self.state).should.have.property('statefile')
+ the(self.state.statefile).should.be.a(str)
+
+ def test_existsSave(self):
+ this(self.state).should.have.property('save').being(callable)
+
+ def test_existsLoad(self):
+ persistent.should.have.property('load').being(callable)
+
+ def test_persistent_state(self):
+ the(persistent._state).should.be.a(persistent.State)
+
+ def test_before_useChangedSettings_state(self):
+ this(self.state).shouldnt.have.property('FOO')
+ this(self.state).shouldnt.have.property('BAR')
+ this(self.state).should.have.property('LOGFILE').being.a(str)
+ this(self.state).should.have.property(
+ 'BRIDGE_FILES').being.a(list)
+
+ def test_before_useChangedSettings_config(self):
+ this(self.config).shouldnt.have.property('FOO')
+ this(self.config).shouldnt.have.property('BAR')
+ this(self.config).should.have.property('LOGFILE').being.a(str)
+ this(self.config).should.have.property(
+ 'BRIDGE_FILES').being.a(list)
+
+ def test_before_useChangedSettings_stateConfig(self):
+ this(self.state.config).shouldnt.have.property('FOO')
+ this(self.state.config).shouldnt.have.property('BAR')
+ this(self.state.config).should.have.property('LOGFILE').being.a(str)
+ this(self.state.config).should.have.property(
+ 'BRIDGE_FILES').being.a(list)
+
+ def test_useChangedSettings(self):
+ # This deepcopying must be done to avoid changing the State object
+ # which is used for the rest of the tests.
+
+ thatConfig = deepcopy(self.config)
+ thatState = deepcopy(self.state)
+
+ setattr(thatConfig, 'FOO', 'fuuuuu')
+ setattr(thatConfig, 'BAR', 'all of the things')
+ setattr(thatConfig, 'LOGFILE', 42)
+
+ this(thatConfig).should.have.property('FOO').being.a(basestring)
+ this(thatConfig).should.have.property('BAR').being.a(basestring)
+ this(thatConfig).should.have.property('LOGFILE').being.an(int)
+ this(thatConfig).should.have.property('BRIDGE_FILES').being.a(list)
+
+ the(thatConfig.FOO).must.equal('fuuuuu')
+ the(thatConfig.BAR).must.equal('all of the things')
+ the(thatConfig.LOGFILE).must.equal(42)
+
+ the(thatState).should.have.property('useChangedSettings')
+ the(thatState.useChangedSettings).should.be(callable)
+ thatState.useChangedSettings(thatConfig)
+
+ the(thatState.FOO).should.equal('fuuuuu')
+ the(thatState).should.have.property('FOO').being.a(basestring)
+ the(thatState).should.have.property('BAR').being.a(basestring)
+ the(thatState).should.have.property('LOGFILE').being.an(int)
+ the(thatState.FOO).must.equal(thatConfig.FOO)
+ the(thatState.BAR).must.equal(thatConfig.BAR)
+ the(thatState.LOGFILE).must.equal(thatConfig.LOGFILE)
+
+ this(thatState.config).should.have.property('FOO')
+ this(thatState.config).should.have.property('BAR')
+ this(thatState.config).should.have.property('LOGFILE').being.an(int)
+ this(thatState.config).should.have.property(
+ 'BRIDGE_FILES').being.a(list)
diff --git a/test/test_persistentSaveAndLoad.py b/test/test_persistentSaveAndLoad.py
new file mode 100644
index 0000000..6d6584d
--- /dev/null
+++ b/test/test_persistentSaveAndLoad.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.persistent` module.
+
+These tests ensure that :meth:`bridgedb.persistent.State.save`,
+:func:`bridgedb.persistent.load`, and :meth:`bridgedb.persistent.State.load`
+are all functioning as expected.
+
+This module should not import :mod:`sure`.
+"""
+
+import os
+
+from copy import deepcopy
+from io import StringIO
+
+from twisted.trial import unittest
+
+from bridgedb import persistent
+
+
+TEST_CONFIG_FILE = StringIO(unicode("""\
+BRIDGE_FILES = ['bridge-descriptors', 'bridge-descriptors.new']
+LOGFILE = 'bridgedb.log'"""))
+
+
+class StateSaveAndLoadTests(unittest.TestCase):
+ """Test save() and load() of :mod:`~bridgedb.persistent`."""
+
+ timeout = 15
+
+ def setUp(self):
+ configuration = {}
+ TEST_CONFIG_FILE.seek(0)
+ compiled = compile(TEST_CONFIG_FILE.read(), '<string>', 'exec')
+ exec compiled in configuration
+ config = persistent.Conf(**configuration)
+
+ self.config = config
+ self.state = persistent.State(**config.__dict__)
+ self.state.config = config
+ self.state.statefile = os.path.abspath('bridgedb.state')
+
+ def loadedStateAssertions(self, loadedState):
+ # For some reason, twisted.trial.unittest.TestCase in Python2.6
+ # doesn't have an 'assertIsNotNone' attribute...
+ self.assertTrue(loadedState is not None)
+ self.assertIsInstance(loadedState, persistent.State)
+ self.assertNotIdentical(self.state, loadedState)
+ self.assertNotEqual(self.state, loadedState)
+ # For some reason, twisted.trial.unittest.TestCase in Python2.6
+ # doesn't have an 'assertItemsEqual' attribute...
+ self.assertEqual(self.state.__dict__.keys().sort(),
+ loadedState.__dict__.keys().sort())
+
+ def savedStateAssertions(self, savedStatefile=None):
+ self.assertTrue(os.path.isfile(str(self.state.statefile)))
+ if savedStatefile:
+ self.assertTrue(os.path.isfile(str(savedStatefile)))
+
+ def test_init_with_STATEFILE(self):
+ config = self.config
+ setattr(config, 'STATEFILE', '~/foo.state')
+ state = persistent.State(**config.__dict__)
+ self.loadedStateAssertions(state)
+ statefile = state.statefile
+ self.assertTrue(statefile.endswith('foo.state'))
+
+ def test_init_without_config(self):
+ state = persistent.State(None)
+ self.loadedStateAssertions(state)
+
+ def test_init_with_config(self):
+ state = persistent.State(self.config)
+ self.loadedStateAssertions(state)
+
+ def test_get_statefile(self):
+ statefile = self.state._get_statefile()
+ self.assertIsInstance(statefile, basestring)
+
+ def test_set_statefile(self):
+ self.state._set_statefile('bar.state')
+ statefile = self.state._get_statefile()
+ self.assertIsInstance(statefile, basestring)
+
+ def test_set_statefile_new_dir(self):
+ config = self.config
+ setattr(config, 'STATEFILE', 'statefiles/foo.state')
+ state = persistent.State(**config.__dict__)
+ self.loadedStateAssertions(state)
+ statefile = state.statefile
+ self.assertTrue(statefile.endswith('foo.state'))
+
+ def test_del_statefile(self):
+ self.state._set_statefile('baz.state')
+ self.state._del_statefile()
+ statefile = self.state._get_statefile()
+ self.assertIsNone(statefile)
+
+ def test_save(self):
+ self.state.save()
+ self.savedStateAssertions()
+
+ def test_stateSaveTempfile(self):
+ savefile = self.mktemp()
+ self.state.statefile = savefile
+ self.state.save(savefile)
+ savedStatefile = str(self.state.statefile)
+
+ def test_stateLoadTempfile(self):
+ savefile = self.mktemp()
+ self.state.statefile = savefile
+ self.assertTrue(self.state.statefile.endswith(savefile))
+ self.state.save(savefile)
+ self.savedStateAssertions(savefile)
+ loadedState = self.state.load(savefile)
+ self.loadedStateAssertions(loadedState)
+
+ def test_stateSaveAndLoad(self):
+ self.state.save()
+ loadedState = self.state.load()
+ self.loadedStateAssertions(loadedState)
+
+ def test_load(self):
+ self.state.save()
+ loadedState = persistent.load()
+ self.loadedStateAssertions(loadedState)
+
+ def test_load_with_state(self):
+ loadedState = persistent.load(self.state)
+ self.loadedStateAssertions(loadedState)
+
+ def test_load_with_None(self):
+ persistent._setState(None)
+ self.assertRaises(persistent.MissingState,
+ persistent.load, None)
+
+ def test_load_with_statefile(self):
+ self.assertRaises(persistent.MissingState,
+ self.state.load, 'quux.state')
+
+ def test_load_with_statefile_opened(self):
+ fh = open('quux.state', 'w+')
+ self.assertRaises(persistent.MissingState, self.state.load, fh)
+ fh.close()
+
+ def test_load_with_statefile_object(self):
+ self.assertRaises(persistent.MissingState, self.state.load, object)
+
+ def test_load_without_statefile(self):
+ persistent._setState(None)
+ self.state.statefile = None
+ self.assertRaises(persistent.MissingState,
+ persistent.load)
diff --git a/test/test_proxy.py b/test/test_proxy.py
new file mode 100644
index 0000000..909218d
--- /dev/null
+++ b/test/test_proxy.py
@@ -0,0 +1,590 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.proxy`."""
+
+import sure
+
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.trial import unittest
+
+from bridgedb import proxy
+
+
+EXIT_LIST_0 = """\
+11.11.11.11
+22.22.22.22
+123.45.67.89"""
+
+EXIT_LIST_1 = """\
+33.33.33.33
+44.44.44.44
+55.55.55.55
+66.66.66.66
+77.77.77.77"""
+
+EXIT_LIST_BAD = """\
+foo
+bar
+baz"""
+
+
+class MockExitListProtocol(proxy.ExitListProtocol):
+ """A mocked version of :class:`~bridgedb.proxy.ExitListProtocol`."""
+
+ def __init__(self):
+ proxy.ExitListProtocol.__init__(self)
+ self._data = EXIT_LIST_0
+ self.script = '/bin/echo'
+ print()
+
+ def _log(self, msg):
+ print("%s: %s" % (self.__class__.__name__, msg))
+
+ def childConnectionLost(self, childFD):
+ self._log("childConnectionLost() called with childFD=%s" % childFD)
+ proxy.ExitListProtocol.childConnectionLost(self, childFD)
+
+ def connectionMade(self):
+ self._log("connectionMade() called")
+ proxy.ExitListProtocol.connectionMade(self)
+
+ def errReceived(self, data):
+ self._log("errReceived() called with %s" % data)
+ proxy.ExitListProtocol.errReceived(self, data)
+
+ def outReceivedData(self, data):
+ self._log("outReceivedData() called with %s" % data)
+ proxy.ExitListProtocol.outReceivedData(self, data)
+
+ def outConnectionLost(self):
+ self._log("outConnectionLost() called")
+ proxy.ExitListProtocol.outConnectionLost(self)
+
+ def parseData(self):
+ data = self._data.split('\n')
+ for line in data:
+ line = line.strip()
+ if not line: continue
+ if line.startswith('<'): break
+ if line.startswith('#'): continue
+ ip = proxy.isIPAddress(line)
+ if ip:
+ self._log("adding IP %s to exitlist..." % ip)
+ self.exitlist.add(ip)
+
+ def processEnded(self, reason):
+ self._log("processEnded() called with %s" % reason)
+ proxy.ExitListProtocol.processEnded(self, reason)
+
+ def processExited(self, reason):
+ self._log("processExited() called with %s" % reason)
+ proxy.ExitListProtocol.processExited(self, reason)
+
+
+class ProxySetImplementationTest(unittest.TestCase):
+
+ def setUp(self):
+ # We have to put something in it, otherwise self.ps.should.be.ok won't
+ # think it's truthy:
+ self.ps = proxy.ProxySet(['1.1.1.1'])
+
+ def test_instantiation(self):
+ self.ps.should.be.ok
+ self.ps.should.have.property('__contains__').being.callable
+ self.ps.should.have.property('__hash__').being.callable
+ self.ps.should.have.property('__iter__').being.callable
+ self.ps.should.have.property('__len__').being.callable
+ self.ps.should.have.property('add').being.callable
+ self.ps.should.have.property('copy').being.callable
+ self.ps.should.have.property('contains').being.callable
+ self.ps.should.have.property('discard').being.callable
+ self.ps.should.have.property('remove').being.callable
+
+ self.ps.should.have.property('difference').being.callable
+ self.ps.should.have.property('issubset').being.callable
+ self.ps.should.have.property('issuperset').being.callable
+ self.ps.should.have.property('intersection').being.callable
+ self.ps.should.have.property('symmetric_difference').being.callable
+ self.ps.should.have.property('union').being.callable
+
+ def test_attributes(self):
+ self.ps.should.have.property('proxies').being.a(list)
+ self.ps.should.have.property('exitRelays').being.a(set)
+ self.ps.should.have.property('_proxies').being.a(set)
+ self.ps.should.have.property('_proxydict').being.a(dict)
+
+
+class LoadProxiesFromFileIntegrationTests(unittest.TestCase):
+ """Unittests for :class:`~bridgedb.proxy.loadProxiesFromFile()`."""
+
+ def setUp(self):
+ self.fn0 = '%s-0' % self.__class__.__name__
+ self.fn1 = '%s-1' % self.__class__.__name__
+ self.badfile = '%s-badfile' % self.__class__.__name__
+ self.writeFiles()
+
+ def writeFiles(self):
+ with open(self.fn0, 'w') as fh:
+ fh.write(EXIT_LIST_0)
+ fh.flush()
+ with open(self.fn1, 'w') as fh:
+ fh.write(EXIT_LIST_1)
+ fh.flush()
+ with open(self.badfile, 'w') as fh:
+ fh.write(EXIT_LIST_BAD)
+ fh.flush()
+
+ def emptyFile(self, filename):
+ """We have to do this is a separate method, otherwise Twisted doesn't
+ actually do it.
+ """
+ fh = open(filename, 'w')
+ fh.truncate()
+ fh.close()
+
+ def test_proxy_loadProxiesFromFile_1_file(self):
+ """Test loading proxies from one file."""
+ proxies = proxy.loadProxiesFromFile(self.fn0)
+ self.assertEqual(len(proxies), 3)
+
+ def test_proxy_loadProxiesFromFile_1_file_missing(self):
+ """Test loading proxies from one file that doesn't exist."""
+ proxies = proxy.loadProxiesFromFile('%s-missing' % self.__class__.__name__)
+ self.assertEqual(len(proxies), 0)
+
+ def test_proxy_loadProxiesFromFile_1_file_and_proxyset(self):
+ """Test loading proxies from one file."""
+ proxyList = proxy.ProxySet(['1.1.1.1'])
+ proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
+ self.assertEqual(len(proxies), 3)
+ self.assertEqual(len(proxyList), 4)
+
+ def test_proxy_loadProxiesFromFile_2_files_and_proxyset(self):
+ """Test loading proxies from two files."""
+ proxyList = proxy.ProxySet(['1.1.1.1'])
+ proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
+ proxies = proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
+ self.assertEqual(len(proxies), 5)
+ self.assertEqual(len(proxyList), 9)
+
+ def test_proxy_loadProxiesFromFile_removeStale(self):
+ """Test loading proxies from two files and removing the stale ones."""
+ proxyList = proxy.ProxySet(['1.1.1.1'])
+ self.assertEqual(len(proxyList), 1)
+ proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList)
+ self.assertEqual(len(proxies), 3)
+ self.assertEqual(len(proxyList), 4)
+ proxies = proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
+ self.assertEqual(len(proxies), 5)
+ self.assertEqual(len(proxyList), 9)
+
+ self.emptyFile(self.fn0)
+ proxies = proxy.loadProxiesFromFile(self.fn0, proxySet=proxyList,
+ removeStale=True)
+ self.assertEqual(len(proxies), 0)
+ self.assertEqual(len(proxyList), 6)
+
+ def test_proxy_loadProxiesFromFile_duplicates(self):
+ """Loading proxies from the same file twice shouldn't store
+ duplicates.
+ """
+ proxyList = proxy.ProxySet(['1.1.1.1'])
+ proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
+ self.assertEqual(len(proxyList), 6)
+ proxy.loadProxiesFromFile(self.fn1, proxySet=proxyList)
+ self.assertEqual(len(proxyList), 6)
+
+ def test_proxy_loadProxiesFromFile_bad_file(self):
+ """Loading proxies from a file with invalid IPs in it should do
+ nothing.
+ """
+ proxyList = proxy.ProxySet()
+ proxy.loadProxiesFromFile(self.badfile, proxySet=proxyList)
+ self.assertEqual(len(proxyList), 0)
+
+
+class DownloadTorExitsTests(unittest.TestCase):
+ """Tests for `~bridgedb.proxy.downloadTorExits()`."""
+
+ def setUp(self):
+ self.protocol = MockExitListProtocol
+ self.proxyList = proxy.ProxySet()
+
+ def tearDown(self):
+ """Cleanup method after each ``test_*`` method runs; removes all
+ selectable readers and writers from the reactor.
+ """
+ reactor.removeAll()
+
+ def test_proxy_downloadTorExits(self):
+ def do_test():
+ return proxy.downloadTorExits(self.proxyList,
+ 'OurIPWouldGoHere',
+ protocol=self.protocol)
+ d = do_test()
+
+
+class ProxySetUnittests(unittest.TestCase):
+ """Unittests for :class:`~bridgedb.proxy.ProxySet`."""
+
+ def setUp(self):
+ self.proxies = EXIT_LIST_1.split('\n')
+ self.moarProxies = EXIT_LIST_0.split('\n')
+
+ self.proxyList = proxy.ProxySet()
+ for p in self.proxies:
+ self.proxyList.add(p)
+
+ def test_ProxySet_init(self):
+ """When initialised (after setUp() has run), the ProxySet should
+ contain a number of proxies equal to the number we added in the setUp()
+ method.
+ """
+ self.assertEquals(len(self.proxyList), len(self.proxies))
+
+ def test_ProxySet_proxies_getter(self):
+ """ProxySet.proxies should list all proxies."""
+ self.assertItemsEqual(self.proxyList.proxies, set(self.proxies))
+
+ def test_ProxySet_proxies_setter(self):
+ """``ProxySet.proxies = ['foo']`` should raise an ``AttributeError``."""
+ self.assertRaises(AttributeError, self.proxyList.__setattr__, 'proxies', ['foo'])
+
+ def test_ProxySet_proxies_deleter(self):
+ """``del(ProxySet.proxies)`` should raise an AttributeError."""
+ self.assertRaises(AttributeError, self.proxyList.__delattr__, 'proxies')
+
+ def test_ProxySet_exitRelays_issubset_proxies(self):
+ """ProxySet.exitRelays should always be a subset of ProxySet.proxies."""
+ self.assertTrue(self.proxyList.exitRelays.issubset(self.proxyList.proxies))
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertTrue(self.proxyList.exitRelays.issubset(self.proxyList.proxies))
+
+ def test_ProxySet_exitRelays_getter(self):
+ """ProxySet.exitRelays should list all exit relays."""
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertItemsEqual(self.proxyList.exitRelays, set(self.moarProxies))
+
+ def test_ProxySet_exitRelays_setter(self):
+ """``ProxySet.exitRelays = ['foo']`` should raise an ``AttributeError``."""
+ self.assertRaises(AttributeError, self.proxyList.__setattr__, 'exitRelays', ['foo'])
+
+ def test_ProxySet_exitRelays_deleter(self):
+ """``del(ProxySet.exitRelays)`` should raise an AttributeError."""
+ self.assertRaises(AttributeError, self.proxyList.__delattr__, 'exitRelays')
+
+ def test_ProxySet_add_new(self):
+ """ProxySet.add() should add a new proxy."""
+ self.proxyList.add('110.110.110.110')
+ self.assertEquals(len(self.proxyList), len(self.proxies) + 1)
+ self.assertIn('110.110.110.110', self.proxyList)
+
+ def test_ProxySet_add_None(self):
+ """ProxySet.add() called with None should return False."""
+ self.assertFalse(self.proxyList.add(None))
+ self.assertEquals(len(self.proxyList), len(self.proxies))
+
+ def test_ProxySet_add_duplicate(self):
+ """ProxySet.add() shouldn't add the same proxy twice."""
+ self.proxyList.add(self.proxies[0])
+ self.assertEquals(len(self.proxyList), len(self.proxies))
+ self.assertIn(self.proxies[0], self.proxyList)
+
+ def test_ProxySet_addExitRelays(self):
+ """ProxySet.addExitRelays() should add the new proxies."""
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertIn(self.moarProxies[0], self.proxyList)
+
+ def test_ProxySet_radd_new(self):
+ """ProxySet.radd() should add a new proxy."""
+ self.proxyList.__radd__('110.110.110.110')
+ self.assertEquals(len(self.proxyList), len(self.proxies) + 1)
+ self.assertIn('110.110.110.110', self.proxyList)
+
+ def test_ProxySet_addExitRelays_tagged(self):
+ """ProxySet.addExitRelays() should add the new proxies, and they should
+ be tagged as being Tor exit relays.
+ """
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertTrue(self.proxyList.isExitRelay(self.moarProxies[0]))
+ self.assertEquals(self.proxyList.getTag(self.moarProxies[0]),
+ self.proxyList._exitTag)
+
+ def test_ProxySet_addExitRelays_length(self):
+ """ProxySet.addExitRelays() should add the new proxies and then the
+ total number should be equal to the previous number of proxies plus the
+ new exit relays added.
+ """
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
+
+ def test_ProxySet_addExitRelays_previous_proxies_kept(self):
+ """ProxySet.addExitRelays() should add the new proxies and keep ones that
+ were already in the ProxySet.
+ """
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertIn(self.proxies[0], self.proxyList)
+
+ def test_ProxySet_addExitRelays_previous_proxies_not_tagged(self):
+ """ProxySet.addExitRelays() should add the new proxies and tag them,
+ but any previous non-exit relays in the ProxySet shouldn't be tagged as
+ being Tor exit relays.
+ """
+ self.proxyList.addExitRelays(self.moarProxies)
+ self.assertFalse(self.proxyList.isExitRelay(self.proxies[0]))
+ self.assertNotEquals(self.proxyList.getTag(self.proxies[0]),
+ self.proxyList._exitTag)
+
+ def test_ProxySet_addProxies_tuple_individual_tags(self):
+ """ProxySet.addProxies() should add the new proxies and tag them with
+ whatever tags we want.
+ """
+ tags = ['foo', 'bar', 'baz']
+ extraProxies = zip(self.moarProxies, tags)
+ self.proxyList.addProxies(extraProxies)
+ self.assertEquals(len(self.proxyList), len(self.proxies) + len(extraProxies))
+ self.assertIn(extraProxies[0][0], self.proxyList)
+ self.assertEquals(self.proxyList._proxydict[extraProxies[0][0]], extraProxies[0][1])
+ self.assertEquals(self.proxyList._proxydict[extraProxies[1][0]], extraProxies[1][1])
+ self.assertEquals(self.proxyList._proxydict[extraProxies[2][0]], extraProxies[2][1])
+
+ def test_ProxySet_addProxies_tuple_too_many_items(self):
+ """``ProxySet.addProxies()`` where the tuples have >2 items should
+ raise a ValueError.
+ """
+ extraProxies = zip(self.moarProxies,
+ ['sometag' for _ in range(len(self.moarProxies))],
+ ['othertag' for _ in range(len(self.moarProxies))])
+ self.assertRaises(ValueError, self.proxyList.addProxies, extraProxies)
+
+ def test_ProxySet_addProxies_list(self):
+ """``ProxySet.addProxies(..., tag='sometag')`` should add the new
+ proxies and tag them all with the same tag.
+ """
+ self.proxyList.addProxies(self.moarProxies, tag='sometag')
+ self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
+ self.assertIn(self.moarProxies[0], self.proxyList)
+ for p in self.moarProxies:
+ self.assertEquals(self.proxyList.getTag(p), 'sometag')
+ for p in self.proxies:
+ self.assertNotEqual(self.proxyList.getTag(p), 'sometag')
+
+ def test_ProxySet_addProxies_set(self):
+ """``ProxySet.addProxies(..., tag=None)`` should add the new
+ proxies and tag them all with timestamps.
+ """
+ self.proxyList.addProxies(set(self.moarProxies))
+ self.assertEquals(len(self.proxyList), len(self.proxies) + len(self.moarProxies))
+ self.assertIn(self.moarProxies[0], self.proxyList)
+ for p in self.moarProxies:
+ self.assertIsInstance(self.proxyList.getTag(p), float)
+ for p in self.proxies:
+ self.assertNotEqual(self.proxyList.getTag(p), 'sometag')
+
+ def test_ProxySet_addProxies_bad_type(self):
+ """``ProxySet.addProxies()`` called with something which is neither an
+ iterable, a basestring, or an int should raise a ValueError.
+ """
+ self.assertRaises(ValueError, self.proxyList.addProxies, object)
+
+ def test_ProxySet_addProxies_list_of_bad_types(self):
+ """``ProxySet.addProxies()`` called with something which is neither an
+ iterable, a basestring, or an int should raise a ValueError.
+ """
+ self.assertRaises(ValueError, self.proxyList.addProxies, [object, object, object])
+
+ def test_ProxySet_getTag(self):
+ """ProxySet.getTag() should get the tag for a proxy in the set."""
+ self.proxyList.add('1.1.1.1', 'bestproxyevar')
+ self.assertEquals(self.proxyList.getTag('1.1.1.1'), 'bestproxyevar')
+
+ def test_ProxySet_getTag_nonexistent(self):
+ """ProxySet.getTag() should get None for a proxy not in the set."""
+ self.assertIsNone(self.proxyList.getTag('1.1.1.1'))
+
+ def test_ProxySet_clear(self):
+ """ProxySet.clear() should clear the set of proxies."""
+ self.proxyList.clear()
+ self.assertEquals(len(self.proxyList), 0)
+ self.assertEquals(len(self.proxyList.proxies), 0)
+ self.assertEquals(len(self.proxyList._proxies), 0)
+ self.assertEquals(len(self.proxyList._proxydict.items()), 0)
+
+ def test_ProxySet_contains_list(self):
+ """Calling ``list() is in ProxySet()`` should return False."""
+ self.assertFalse(self.proxyList.contains(list(self.proxies[0],)))
+
+ def test_ProxySet_contains_nonexistent(self):
+ """``ProxySet().contains()`` with a proxy not in the set should
+ return False.
+ """
+ self.assertFalse(self.proxyList.contains(self.moarProxies[0]))
+
+ def test_ProxySet_contains_nonexistent(self):
+ """``ProxySet().contains()`` with a proxy in the set should
+ return True.
+ """
+ self.assertTrue(self.proxyList.contains(self.proxies[0]))
+
+ def test_ProxySet_copy(self):
+ """ProxySet.copy() should create an exact copy."""
+ newProxyList = self.proxyList.copy()
+ self.assertEquals(newProxyList, self.proxyList)
+
+ def test_ProxySet_difference(self):
+ """ProxySet.difference() should list the items in ProxySetA which
+ aren't in ProxySetB.
+ """
+ proxySetA = self.proxyList
+ proxySetB = proxy.ProxySet(self.moarProxies)
+ self.assertItemsEqual(proxySetA.difference(proxySetB),
+ set(self.proxies))
+ self.assertItemsEqual(proxySetB.difference(proxySetA),
+ set(self.moarProxies))
+
+ def test_ProxySet_firstSeen_returns_timestamp(self):
+ """ProxySet.firstSeen() should return a timestamp for a proxy with a
+ timestamp tag.
+ """
+ self.proxyList.add(self.moarProxies[0])
+ self.assertIsNotNone(self.proxyList.firstSeen(self.moarProxies[0]))
+
+ def test_ProxySet_firstSeen_returns_float(self):
+ """ProxySet.firstSeen() should return a timestamp for a proxy with a
+ timestamp tag.
+ """
+ self.proxyList.add(self.moarProxies[1])
+ self.assertIsInstance(self.proxyList.firstSeen(self.moarProxies[1]), float)
+
+ def test_ProxySet_firstSeen_other_tags(self):
+ """ProxySet.firstSeen() should return None when a proxy doesn't have a
+ timestamp.
+ """
+ self.proxyList.add(self.moarProxies[2], 'sometag')
+ self.assertIsNone(self.proxyList.firstSeen(self.moarProxies[2]))
+
+ def test_ProxySet_issubset(self):
+ """ProxySet.issubset() on a superset should return True."""
+ self.assertTrue(self.proxyList.issubset(set(self.proxies + self.moarProxies[:0])))
+
+ def test_ProxySet_issuperset(self):
+ """ProxySet.issubset() on a subset should return True."""
+ self.assertTrue(self.proxyList.issuperset(set(self.proxies[:1])))
+
+ def test_ProxySet_intersection(self):
+ """ProxySet.intersection() should return the combination of two
+ disjoint sets.
+ """
+ raise unittest.SkipTest(
+ ("FIXME: bridgedb.proxy.ProxySet.intersection() is broken and "
+ "always returns an empty set()."))
+
+ a = self.proxies
+ a.extend(self.moarProxies)
+ a = set(a)
+ b = self.proxyList.intersection(set(self.moarProxies))
+ self.assertItemsEqual(a, b)
+
+ def test_ProxySet_remove(self):
+ """ProxySet.remove() should subtract proxies which were already added
+ to the set.
+ """
+ self.proxyList.remove(self.proxies[0])
+ self.assertEquals(len(self.proxyList), len(self.proxies) - 1)
+ self.assertNotIn(self.proxies[0], self.proxyList)
+
+ def test_ProxySet_remove_nonexistent(self):
+ """ProxySet.remove() shouldn't subtract proxies which aren't already in
+ the set.
+ """
+ self.proxyList.remove('110.110.110.110')
+ self.assertEquals(len(self.proxyList), len(self.proxies))
+ self.assertNotIn('110.110.110.110', self.proxyList)
+
+ def test_ProxySet_replaceProxyList(self):
+ """ProxySet.replaceProxyList should remove all the current proxies and
+ add all the new ones.
+ """
+ self.proxyList.replaceProxyList(self.moarProxies, 'seven proxies')
+ for p in self.moarProxies:
+ self.assertIn(p, self.proxyList)
+ self.assertEqual(self.proxyList.getTag(p), 'seven proxies')
+ for p in self.proxies:
+ self.assertNotIn(p, self.proxyList)
+
+ def test_ProxySet_replaceProxyList_bad_type(self):
+ """ProxySet.replaceProxyList should remove all the current proxies and
+ then since we're giving it a bad type it should do nothing else.
+ """
+ self.proxyList.replaceProxyList([object, object, object])
+ self.assertEqual(len(self.proxyList), 0)
+
+ def test_ProxySet_hash(self):
+ """Two equal ProxySets should return the same hash."""
+ proxyListA = proxy.ProxySet(self.proxies)
+ proxyListB = proxy.ProxySet(self.proxies)
+ self.assertEqual(proxyListA, proxyListB)
+ self.assertItemsEqual(proxyListA, proxyListB)
+ self.assertEqual(hash(proxyListA), hash(proxyListB))
+
+
+class ExitListProtocolTests(unittest.TestCase):
+ """Unittests for :class:`~bridgedb.proxy.ExitListProtocol`."""
+
+ def setUp(self):
+ self.proto = proxy.ExitListProtocol()
+
+ def test_ExitListProtocol_parseData_error_page(self):
+ """ """
+ self.proto.data = """\
+<!doctype html>
+<html lang="en">
+<body>
+ <div class="content">
+ <img src="/torcheck/img/tor-on.png" class="onion" />
+ <h4>Welcome to the Tor Bulk Exit List exporting tool.</h4>
+ </div>
+</body>
+</html>"""
+ self.proto.parseData()
+ self.assertEqual(len(self.proto.exitlist), 0)
+
+ def test_ExitListProtocol_parseData_page_with_3_ips_with_comments(self):
+ """ """
+ self.proto.data = """\
+# This is a list of all Tor exit nodes from the past 16 hours that can contact 1.1.1.1 on port 443 #
+# You can update this list by visiting https://check.torproject.org/cgi-bin/TorBulkExitList.py?ip=1.1.1.1&port=443 #
+# This file was generated on Fri Feb 6 02:04:27 UTC 2015 #
+101.99.64.150
+103.10.197.50
+103.240.91.7"""
+ self.proto.parseData()
+ self.assertEqual(len(self.proto.exitlist), 3)
+
+ def test_ExitListProtocol_parseData_page_with_3_ips(self):
+ """ """
+ self.proto.data = """
+101.99.64.150
+103.10.197.50
+103.240.91.7"""
+ self.proto.parseData()
+ self.assertEqual(len(self.proto.exitlist), 3)
+
+ def test_ExitListProtocol_parseData_page_with_bad_ip(self):
+ """ """
+ self.proto.data = """
+192.168.0.1
+127.0.0.1
+103.240.91.7"""
+ self.proto.parseData()
+ self.assertEqual(len(self.proto.exitlist), 1)
diff --git a/test/test_qrcodes.py b/test/test_qrcodes.py
new file mode 100644
index 0000000..4abe8cc
--- /dev/null
+++ b/test/test_qrcodes.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+# (c) 2014-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Tests for :mod:`bridgedb.qrcodes`."""
+
+
+from twisted.trial import unittest
+
+from bridgedb import qrcodes
+
+
+class GenerateQRTests(unittest.TestCase):
+ """Unittests for :func:`bridgedb.qrcodes.generateQR`."""
+
+ def setUp(self):
+ self.qrcodeModule = qrcodes.qrcode
+ bridgelines = [
+ "obfs4 63.125.48.205:26573 441a151632806a3cc42adfecc2e6e823299db7b2 iat-mode=1 public-key=8d27ba37c5d810106b55f3fd6cdb35842007e88754184bfc0e6035f9bcede633 node-id=42d2a6ad49f93ab4b987b1a9e738425aacb8d2af",
+ "obfs4 103.111.131.45:43288 cb1362f8eaf5d3c2b6fad5da300f66c4197f23e5 iat-mode=0 public-key=c75cb66ae28d8ebc6eded002c28a8ba0d06d3a78c6b5cbf9b2ade051f0775ac4 node-id=4cd66dfabbd964f8c6c4414b07cdb45dae692e19",
+ "obfs4 17.194.28.21:5530 86ef8ab343e76bcd3c57ee32febe4482c98141c7 iat-mode=0 public-key=36ebe205bcdfc499a25e6923f4450fa8d48196ceb4fa0ce077d9d8ec4a36926d node-id=6b6277afcb65d33525545904e95c2fa240632660",
+ ]
+ self.bridgelines = '\n'.join(bridgelines)
+
+ def tearDown(self):
+ """Replace the qrcode module to its original form."""
+ qrcodes.qrcode = self.qrcodeModule
+
+ def test_generateQR(self):
+ """Calling generateQR() should generate an image."""
+ self.assertTrue(qrcodes.generateQR(self.bridgelines))
+
+ def test_generateQR_bad_bridgelines(self):
+ """Calling generateQR() with a bad type for the bridgelines should
+ return None.
+ """
+ self.assertIsNone(qrcodes.generateQR(list()))
+
+ def test_generateQR_no_bridgelines(self):
+ """Calling generateQR() without bridgelines should return None."""
+ self.assertIsNone(qrcodes.generateQR(""))
+
+ def test_generateQR_no_qrcode_module(self):
+ """Calling generateQR() without the qrcode module installed should
+ return None.
+ """
+ qrcodes.qrcode = None
+ self.assertIsNone(qrcodes.generateQR(self.bridgelines))
+
+ def test_generateQR_bridgeSchema(self):
+ """Calling generateQR() with bridgeSchema=True should prepend
+ ``'bridge://`` to each of the QR encoded bridge lines.
+ """
+ # If we were to install the python-qrtools Debian package, we'd be
+ # able to decode the resulting QRCode to check that it contains the
+ # 'bridge://' prefix for each bridge line⦠but that would add another
+ # Debian dependency just to unittest 5 lines of code.
+ #
+ # Instead:
+ self.assertTrue(qrcodes.generateQR(self.bridgelines, bridgeSchema=True))
+
+ def test_generateQR_save_nonexistent_format(self):
+ """Calling generateQR() with imageFormat=u'FOOBAR' should return None.
+ """
+ self.assertIsNone(qrcodes.generateQR(self.bridgelines, imageFormat=u'FOOBAR'))
diff --git a/test/test_safelog.py b/test/test_safelog.py
new file mode 100644
index 0000000..247db06
--- /dev/null
+++ b/test/test_safelog.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+
+"""Unittests for :mod:`bridgedb.safelog`."""
+
+import re
+
+from twisted.test.proto_helpers import StringTransport
+from twisted.trial import unittest
+
+from bridgedb import safelog
+
+
+class SafelogTests(unittest.TestCase):
+ """Tests for functions and attributes in :mod:`bridgedb.safelog`."""
+
+ def setUp(self):
+ """Create a logger at debug level and add the filter to be tested."""
+ self.logfile = StringTransport()
+ self.handler = safelog.logging.StreamHandler(self.logfile)
+ self.logger = safelog.logging.getLogger(self.__class__.__name__)
+ self.logger.setLevel(10)
+ self.logger.addHandler(self.handler)
+ self.sensitiveData = 'Nicholas Bourbaki'
+
+ def tearDown(self):
+ """Rewind and truncate the logfile so that we have an empty one."""
+ self.logfile.clear()
+
+ def test_setSafeLogging_off(self):
+ """Calls to ``logSafely()`` should return the original data when
+ ``safe_logging`` is disabled.
+ """
+ safelog.setSafeLogging(False)
+ self.logger.warn("Got a connection from %s..."
+ % safelog.logSafely(self.sensitiveData))
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ #self.assertSubstring("Got a connection from", contents)
+ #self.assertSubstring(self.sensitiveData, contents)
+ #self.failIfSubstring("[scrubbed]", contents)
+
+ def test_setSafeLogging_on(self):
+ """Calls to ``logSafely()`` should return ``"[scrubbed]"`` for any
+ arbitrary data when ``safe_logging`` is enabled.
+ """
+ safelog.setSafeLogging(True)
+ self.logger.warn("Got a connection from %s..."
+ % safelog.logSafely(self.sensitiveData))
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ #self.assertSubstring("Got a connection from", contents)
+ #self.failIfSubstring(self.sensitiveData, contents)
+ #self.assertSubstring("[scrubbed]", contents)
+
+
+class BaseSafelogFilterTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.safelog.BaseSafelogFilter`."""
+
+ def setUp(self):
+ safelog.setSafeLogging(True)
+ self.logfile = StringTransport()
+ self.handler = safelog.logging.StreamHandler(self.logfile)
+ self.logger = safelog.logging.getLogger(self.__class__.__name__)
+ self.logger.setLevel(10)
+ self.logger.addHandler(self.handler)
+ self.filter = safelog.BaseSafelogFilter()
+ self.logger.addFilter(self.filter)
+
+ self.logMessage = "testing 1 2 3"
+ self.record = safelog.logging.LogRecord('name', 10, __file__, 1337,
+ self.logMessage, {}, None)
+
+ def test_doubleCheck(self):
+ """BaseSafelogFilter.doubleCheck() should always return True."""
+ checked = self.filter.doubleCheck(self.logMessage)
+ self.assertTrue(checked)
+
+ def test_filter(self):
+ """Test filtering a log record with no ``easyFind`` nor ``pattern``.
+
+ The ``LogRecord.message`` shouldn't change.
+ """
+ filtered = self.filter.filter(self.record)
+ self.assertEqual(filtered.getMessage(), self.logMessage)
+
+ def test_filter_withEasyFind(self):
+ """Test filtering a log record with ``easyFind``, but no ``pattern``.
+
+ The ``LogRecord.message`` shouldn't change.
+ """
+ self.filter.easyFind = "2"
+ filtered = self.filter.filter(self.record)
+ self.assertEqual(filtered.getMessage(), self.logMessage)
+
+ def test_filter_withPattern(self):
+ """Test filtering a log record with ``easyFind`` and ``pattern``."""
+ self.filter.easyFind = "2"
+ self.filter.pattern = re.compile("1 2 3")
+ filtered = self.filter.filter(self.record)
+ self.assertEqual(filtered.msg, "testing [scrubbed]")
+
+
+class SafelogEmailFilterTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.safelog.SafelogEmailFilter`."""
+
+ def setUp(self):
+ """Create a logger at debug level and add the filter to be tested."""
+ self.logfile = StringTransport()
+ self.handler = safelog.logging.StreamHandler(self.logfile)
+ self.filter = safelog.SafelogEmailFilter()
+ self.logger = safelog.logging.getLogger(self.__class__.__name__)
+ self.logger.setLevel(10)
+ self.logger.addHandler(self.handler)
+ self.logger.addFilter(self.filter)
+ self.s1 = "Here is an email address: "
+ self.s2 = "blackhole at torproject.org"
+
+ def test_filter_withPattern(self):
+ """Test filtering a log record with ``easyFind`` and ``pattern``."""
+ record = safelog.logging.LogRecord('name', 10, __file__, 1337,
+ "testing blackhole at torproject.org",
+ {}, None)
+ filtered = self.filter.filter(record)
+ self.assertEqual(filtered.msg, "testing [scrubbed]")
+
+ def test_debugLevel(self):
+ self.logger.debug("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ # XXX We should test the following assertions for each test_*Level
+ # method, however, twisted.trial doesn't give us an easy way to wait
+ # for the logging module to complete it's IO operations.
+ #self.assertSubstring(self.s1, contents)
+ #self.failIfSubstring(self.s2, contents)
+
+ def test_infoLevel(self):
+ self.logger.info("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_warnLevel(self):
+ self.logger.warn("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_errorLevel(self):
+ self.logger.error("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_exceptionLevel(self):
+ try:
+ raise Exception("%s %s" % (self.s1, self.s2))
+ except Exception as error:
+ self.logger.exception(error)
+
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ #self.assertSubstring(self.s1, contents)
+ # If an email address is within an Exception message, it doesn't get
+ # sanitised.
+ #self.assertSubstring(self.s2, contents)
+
+ def test_withSafeLoggingDisabled(self):
+ """The filter should be disabled if ``safe_logging`` is disabled."""
+ safelog.setSafeLogging(False)
+ self.logger.info("%s %s" % (self.s1, self.s2))
+ self.logfile.io.flush()
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ #self.assertSubstring(self.s1, contents)
+ #self.assertSubstring(self.s2, contents)
+
+
+class SafelogIPv4FilterTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.safelog.SafelogIPv4Filter`."""
+
+ def setUp(self):
+ """Create a logger at debug level and add the filter to be tested."""
+ self.logfile = StringTransport()
+ self.handler = safelog.logging.StreamHandler(self.logfile)
+ self.filter = safelog.SafelogIPv4Filter()
+ self.logger = safelog.logging.getLogger(str(self.__class__.__name__))
+ self.logger.addHandler(self.handler)
+ self.logger.addFilter(self.filter)
+ self.logger.setLevel(10)
+ self.s1 = "There's an IPv4 address at the end of this book: "
+ self.s2 = "1.2.3.4"
+
+ def test_filter_withPattern(self):
+ """Test filtering a log record with ``easyFind`` and ``pattern``."""
+ record = safelog.logging.LogRecord('name', 10, __file__, 1337,
+ "testing 1.2.3.4",
+ {}, None)
+ filtered = self.filter.filter(record)
+ self.assertIsInstance(filtered, safelog.logging.LogRecord)
+
+ def test_doubleCheck_IPv4(self):
+ checked = self.filter.doubleCheck("1.2.3.4")
+ self.assertIs(checked, True)
+
+ def test_doubleCheck_IPv6(self):
+ checked = self.filter.doubleCheck("2af1:a470:9b36::a1:3:82")
+ self.assertIsNot(checked, True)
+
+ def test_debugLevel(self):
+ self.logger.debug("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_infoLevel(self):
+ self.logger.info("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_warnLevel(self):
+ self.logger.warn("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_errorLevel(self):
+ self.logger.error("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_exceptionLevel(self):
+ try:
+ raise Exception("%s %s" % (self.s1, self.s2))
+ except Exception as error:
+ self.logger.exception(error)
+
+ self.logfile.io.flush()
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_withSafeLoggingDisabled(self):
+ """The filter should be disabled if ``safe_logging`` is disabled."""
+ safelog.setSafeLogging(False)
+ self.logger.info("%s %s" % (self.s1, self.s2))
+ self.logfile.io.flush()
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+
+class SafelogIPv6FilterTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.safelog.SafelogIPv6Filter`."""
+
+ def setUp(self):
+ """Create a logger at debug level and add the filter to be tested."""
+ self.logfile = StringTransport()
+ self.handler = safelog.logging.StreamHandler(self.logfile)
+ self.filter = safelog.SafelogIPv6Filter()
+ self.logger = safelog.logging.getLogger(str(self.__class__.__name__))
+ self.logger.addHandler(self.handler)
+ self.logger.addFilter(self.filter)
+ self.logger.setLevel(10)
+ self.s1 = "There's an IPv6 address at the end of this book: "
+ self.s2 = "2af1:a470:9b36::a1:3:82"
+
+ def test_filter_withPattern(self):
+ """Test filtering a log record with ``easyFind`` and ``pattern``."""
+ record = safelog.logging.LogRecord('name', 10, __file__, 1337,
+ "2af1:a470:9b36::a1:3:82",
+ {}, None)
+ filtered = self.filter.filter(record)
+ self.assertIsInstance(filtered, safelog.logging.LogRecord)
+
+ def test_doubleCheck_IPv4(self):
+ checked = self.filter.doubleCheck("1.2.3.4")
+ self.assertIsNot(checked, True)
+
+ def test_doubleCheck_IPv6(self):
+ checked = self.filter.doubleCheck("2af1:a470:9b36::a1:3:82")
+ self.assertIs(checked, True)
+
+ def test_debugLevel(self):
+ self.logger.debug("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_infoLevel(self):
+ self.logger.info("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_warnLevel(self):
+ self.logger.warn("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_errorLevel(self):
+ self.logger.error("%s %s" % (self.s1, self.s2))
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+
+ def test_exceptionLevel(self):
+ try:
+ raise Exception("%s %s" % (self.s1, self.s2))
+ except Exception as error:
+ self.logger.exception(error)
+
+ self.logfile.io.flush()
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
+ #self.assertSubstring(self.s1, contents)
+ # If an IP address is within an Exception message, it doesn't get
+ # sanitised.
+ #self.assertSubstring(self.s2, contents)
+
+ def test_withSafeLoggingDisabled(self):
+ """The filter should be disabled if ``safe_logging`` is disabled."""
+ safelog.setSafeLogging(False)
+ self.logger.info("%s %s" % (self.s1, self.s2))
+
+ self.logfile.io.flush()
+ self.logfile.io.seek(0)
+ contents = self.logfile.value()
+ self.assertIsNotNone(contents)
diff --git a/test/test_schedule.py b/test/test_schedule.py
new file mode 100644
index 0000000..6a38871
--- /dev/null
+++ b/test/test_schedule.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2014-2015, Isis Lovecruft
+# (c) 2014-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.schedule` module."""
+
+from __future__ import print_function
+
+from twisted.trial import unittest
+
+from bridgedb import schedule
+
+
+class UnscheduledTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.scheduled.Unscheduled`."""
+
+ def setUp(self):
+ self.sched = schedule.Unscheduled()
+
+ def test_Unscheduled_init(self):
+ """The instance should be an instance of its class."""
+ self.assertIsInstance(self.sched, schedule.Unscheduled)
+
+ def test_Unscheduled_providesISchedule(self):
+ """Unscheduled should implement the ISchedule interface."""
+ schedule.ISchedule.namesAndDescriptions()
+ self.assertTrue(schedule.ISchedule.providedBy(self.sched))
+
+ def test_Unscheduled_intervalStart_noargs(self):
+ time = self.sched.intervalStart()
+ self.assertIsInstance(time, int)
+ self.assertEquals(time, -62135596800)
+
+ def test_Unscheduled_getInterval_is_constant(self):
+ import time
+ now = time.time()
+
+ interval_default = self.sched.getInterval()
+ self.assertIsInstance(interval_default, str)
+
+ interval_zero = self.sched.getInterval(0)
+ self.assertIsInstance(interval_zero, str)
+
+ interval_now = self.sched.getInterval(now)
+ self.assertIsInstance(interval_now, str)
+
+ self.assertEquals(interval_default, interval_zero)
+ self.assertEquals(interval_default, interval_now)
+
+ def test_Unscheduled_nextIntervalStarts_noargs(self):
+ time = self.sched.nextIntervalStarts()
+ self.assertIsInstance(time, int)
+ self.assertEquals(time, 253402300799)
+
+
+class ScheduledIntervalTests(unittest.TestCase):
+ """Tests for :class:`bridgedb.scheduled.ScheduledInterval`."""
+
+ def setUp(self):
+ import time
+ self.now = time.time
+ self.sched = schedule.ScheduledInterval
+
+ def test_ScheduledInterval_providesISchedule(self):
+ """ScheduledInterval should implement the ISchedule interface."""
+ self.assertTrue(schedule.ISchedule.providedBy(self.sched(1, 'month')))
+
+ def _check_init(self, sched):
+ """The instance should be an instance of its class."""
+ self.assertIsInstance(sched, schedule.ScheduledInterval)
+
+ def test_ScheduledInterval_init_month(self):
+ self._check_init(self.sched(1, 'month'))
+
+ def test_ScheduledInterval_init_week(self):
+ self._check_init(self.sched(2, 'week'))
+
+ def test_ScheduledInterval_init_day(self):
+ self._check_init(self.sched(5, 'days'))
+
+ def test_ScheduledInterval_init_hour(self):
+ self._check_init(self.sched(12, 'hours'))
+
+ def test_ScheduledInterval_init_minute(self):
+ self._check_init(self.sched(10, 'minute'))
+
+ def test_ScheduledInterval_init_seconds(self):
+ self._check_init(self.sched(30, 'seconds'))
+
+ def test_ScheduledInterval_init_badIntervalPeriod(self):
+ self.assertRaises(schedule.UnknownInterval,
+ self.sched, 2, 'decades')
+
+ def test_ScheduledInterval_init_badIntervalCount(self):
+ self.assertRaises(schedule.UnknownInterval,
+ self.sched, 'd20', 'minutes')
+
+ def test_ScheduledInterval_init_negativeIntervalCount(self):
+ sched = self.sched(-100000, 'days')
+ self.assertEquals(sched.intervalCount, 1)
+ self.assertEquals(sched.intervalPeriod, 'day')
+
+ def test_ScheduledInterval_init_noargs(self):
+ """Check that the defaults parameters function as expected."""
+ sched = self.sched()
+ self.assertEquals(sched.intervalCount, 1)
+ self.assertEquals(sched.intervalPeriod, 'hour')
+
+ def _check_intervalStart(self, count=30, period='second', variance=30):
+ """Test the ScheduledInterval.intervalStart() method.
+
+ :param int count: The number of **period**s within an interval.
+ :param str period: The interval type for the period.
+ :param int variance: The amount of variance (in seconds) to tolerate
+ between the start of the interval containing now, and now.
+ """
+ now = int(self.now())
+ sched = self.sched(count, period)
+ time = sched.intervalStart(now)
+ self.assertIsInstance(time, int)
+ self.assertApproximates(now, time, variance)
+
+ def test_ScheduledInterval_intervalStart_month(self):
+ self._check_intervalStart(1, 'month', 31*24*60*60)
+
+ def test_ScheduledInterval_intervalStart_week(self):
+ self._check_intervalStart(2, 'week', 14*24*60*60)
+
+ def test_ScheduledInterval_intervalStart_day(self):
+ self._check_intervalStart(5, 'days', 5*24*60*60)
+
+ def test_ScheduledInterval_intervalStart_hour(self):
+ self._check_intervalStart(12, 'hours', 12*60*60)
+
+ def test_ScheduledInterval_intervalStart_minute(self):
+ self._check_intervalStart(10, 'minute', 10*60)
+
+ def test_ScheduledInterval_intervalStart_seconds(self):
+ self._check_intervalStart(30, 'seconds', 30)
+
+ def test_ScheduledInterval_intervalStart_time_time(self):
+ """Calling ScheduledInterval.intervalStart(time.time()) should only
+ return ints, not floats.
+ """
+ import time
+
+ timestamp = time.time()
+ sched = self.sched(5, 'minutes')
+
+ self.assertIsInstance(timestamp, float)
+ self.assertIsInstance(sched.intervalStart(timestamp), int)
+
+ def _check_getInterval(self, count=30, period='second', variance=30):
+ """Test the ScheduledInterval.getInterval() method.
+
+ :param int count: The number of **period**s within an interval.
+ :param str period: The interval type for the period.
+ :param int variance: The amount of variance (in seconds) to tolerate
+ between the start of the interval containing now, and now.
+ """
+ now = int(self.now())
+ sched = self.sched(count, period)
+ ts = sched.getInterval(now)
+ self.assertIsInstance(ts, str)
+ secs = [int(x) for x in ts.replace('-', ' ').replace(':', ' ').split()]
+ [secs.append(0) for _ in xrange(6-len(secs))]
+ secs = schedule.calendar.timegm(secs)
+ self.assertApproximates(now, secs, variance)
+
+ def test_ScheduledInterval_getInterval_month(self):
+ self._check_getInterval(2, 'month', 2*31*24*60*60)
+
+ def test_ScheduledInterval_getInterval_week(self):
+ self._check_getInterval(1, 'week', 7*24*60*60)
+
+ def test_ScheduledInterval_getInterval_day(self):
+ self._check_getInterval(4, 'days', 4*24*60*60)
+
+ def test_ScheduledInterval_getInterval_hour(self):
+ self._check_getInterval(23, 'hours', 23*60*60)
+
+ def test_ScheduledInterval_getInterval_minute(self):
+ self._check_getInterval(15, 'minutes', 15*60)
+
+ def test_ScheduledInterval_getInterval_seconds(self):
+ self._check_getInterval(10, 'seconds', 60)
+
+ def _check_nextIntervalStarts(self, count=30, period='second', variance=30):
+ """Test the ScheduledInterval.nextIntervalStarts() method.
+
+ :param int count: The number of **period**s within an interval.
+ :param str period: The interval type for the period.
+ :param int variance: The amount of variance (in seconds) to tolerate
+ between the start of the interval containing now, and now.
+ """
+ now = int(self.now())
+ sched = self.sched(count, period)
+ time = sched.nextIntervalStarts(now)
+ self.assertIsInstance(time, int)
+ # (now + variance - time) should be > variance
+ self.assertApproximates(now + variance, time, variance)
+
+ def test_ScheduledInterval_nextIntervalStarts_month(self):
+ self._check_nextIntervalStarts(2, 'month', 2*31*24*60*60)
+
+ def test_ScheduledInterval_nextIntervalStarts_week(self):
+ self._check_nextIntervalStarts(1, 'week', 7*24*60*60)
+
+ def test_ScheduledInterval_nextIntervalStarts_day(self):
+ self._check_nextIntervalStarts(4, 'days', 4*24*60*60)
+
+ def test_ScheduledInterval_nextIntervalStarts_hour(self):
+ self._check_nextIntervalStarts(23, 'hours', 23*60*60)
+
+ def test_ScheduledInterval_nextIntervalStarts_minute(self):
+ self._check_nextIntervalStarts(15, 'minutes', 15*60)
+
+ def test_ScheduledInterval_nextIntervalStarts_seconds(self):
+ self._check_nextIntervalStarts(10, 'seconds', 10)
diff --git a/test/test_smtp.py b/test/test_smtp.py
new file mode 100644
index 0000000..de443b3
--- /dev/null
+++ b/test/test_smtp.py
@@ -0,0 +1,206 @@
+"""integration tests for BridgeDB ."""
+
+from __future__ import print_function
+
+import smtplib
+import asyncore
+import threading
+import Queue
+import random
+import os
+
+from smtpd import SMTPServer
+
+from twisted.trial import unittest
+from twisted.trial.unittest import FailTest
+from twisted.trial.unittest import SkipTest
+
+from bridgedb.test.util import processExists
+from bridgedb.test.util import getBridgeDBPID
+
+# ------------- SMTP Client Config
+SMTP_DEBUG_LEVEL = 0 # set to 1 to see SMTP message exchange
+BRIDGEDB_SMTP_SERVER_ADDRESS = "localhost"
+BRIDGEDB_SMTP_SERVER_PORT = 6725
+# %d is parameterised with a random integer to make the sender unique
+FROM_ADDRESS_TEMPLATE = "test%d at 127.0.0.1"
+# Minimum value used to parameterise FROM_ADDRESS_TEMPLATE
+MIN_FROM_ADDRESS = 1
+# Max value used to parameterise FROM_ADDRESS_TEMPLATE. Needs to be pretty big
+# to reduce the chance of collisions
+MAX_FROM_ADDRESS = 10**8
+TO_ADDRESS = "bridges at torproject.org"
+MESSAGE_TEMPLATE = """From: %s
+To: %s
+Subject: testing
+
+get bridges"""
+
+# ------------- SMTP Server Setup
+# Setup an SMTP server which we use to check for responses
+# from bridgedb. This needs to be done before sending the actual mail
+LOCAL_SMTP_SERVER_ADDRESS = 'localhost'
+LOCAL_SMTP_SERVER_PORT = 2525 # Must be the same as bridgedb's EMAIL_SMTP_PORT
+
+
+class EmailServer(SMTPServer):
+ def process_message(self, peer, mailfrom, rcpttos, data):
+ ''' Overridden from SMTP server, called whenever a message is received'''
+ self.message_queue.put(data)
+
+ def thread_proc(self):
+ ''' This function runs in thread, and will continue looping
+ until the _stop Event object is set by the stop() function'''
+ while self._stop.is_set() == False:
+ asyncore.loop(timeout=0.0, count=1)
+ # Must close, or asyncore will hold on to the socket and subsequent
+ # tests will fail with 'Address not in use'.
+ self.close()
+
+ def start(self):
+ self.message_queue = Queue.Queue()
+ self._stop = threading.Event()
+ self._thread = threading.Thread(target=self.thread_proc)
+ # Ensures that if any tests do fail, then threads will exit when the
+ # parent exits.
+ self._thread.setDaemon(True)
+ self._thread.start()
+
+ @classmethod
+ def startServer(cls):
+ #print("Starting SMTP server on %s:%s"
+ # % (LOCAL_SMTP_SERVER_ADDRESS, LOCAL_SMTP_SERVER_PORT))
+ server = EmailServer((LOCAL_SMTP_SERVER_ADDRESS,
+ LOCAL_SMTP_SERVER_PORT),
+ None)
+ server.start()
+ return server
+
+ def stop(self):
+ # Signal thread_proc to stop:
+ self._stop.set()
+ # Wait for thread_proc to return (shouldn't take long)
+ self._thread.join()
+ assert self._thread.is_alive() == False, "Thread is alive and kicking"
+
+ def getAndCheckMessageContains(self, text, timeoutInSecs=2.0):
+ try:
+ message = self.message_queue.get(block=True, timeout=timeoutInSecs)
+ # Queue.Empty, according to its documentation, is only supposed to be
+ # raised when Queue.get(block=False) or Queue.get_nowait() are called.
+ # I've no idea why it's getting raised here, when we're blocking for
+ # it, but nonetheless it causes occasional, non-deterministic CI
+ # failures:
+ #
+ # https://travis-ci.org/isislovecruft/bridgedb/jobs/58996136#L3281
+ except Queue.Empty:
+ pass
+ else:
+ assert message.find(text) != -1, ("Message did not contain text '%s'."
+ "Full message is:\n %s"
+ % (text, message))
+
+ def checkNoMessageReceived(self, timeoutInSecs=2.0):
+ try:
+ self.message_queue.get(block=True, timeout=timeoutInSecs)
+ except Queue.Empty:
+ return True
+ assert False, "Found a message in the queue, but expected none"
+
+def sendMail(fromAddress):
+ #print("Connecting to %s:%d"
+ # % (BRIDGEDB_SMTP_SERVER_ADDRESS, BRIDGEDB_SMTP_SERVER_PORT))
+ client = smtplib.SMTP(BRIDGEDB_SMTP_SERVER_ADDRESS,
+ BRIDGEDB_SMTP_SERVER_PORT)
+ client.set_debuglevel(SMTP_DEBUG_LEVEL)
+
+ #print("Sending mail TO:%s, FROM:%s"
+ # % (TO_ADDRESS, fromAddress))
+ result = client.sendmail(fromAddress, TO_ADDRESS,
+ MESSAGE_TEMPLATE % (fromAddress, TO_ADDRESS))
+ assert result == {}, "Failed to send mail"
+ client.quit()
+
+
+class SMTPTests(unittest.TestCase):
+ def setUp(self):
+ '''Called at the start of each test, ensures that the SMTP server is
+ running.
+ '''
+ here = os.getcwd()
+ topdir = here.rstrip('_trial_temp')
+ self.rundir = os.path.join(topdir, 'run')
+ self.pidfile = os.path.join(self.rundir, 'bridgedb.pid')
+ self.pid = getBridgeDBPID(self.pidfile)
+ self.server = EmailServer.startServer()
+
+ def tearDown(self):
+ '''Called after each test, ensures that the SMTP server is cleaned up.
+ '''
+ self.server.stop()
+
+ def test_getBridges(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ # send the mail to bridgedb, choosing a random email address
+ sendMail(fromAddress=FROM_ADDRESS_TEMPLATE
+ % random.randint(MIN_FROM_ADDRESS, MAX_FROM_ADDRESS))
+
+ # then check that our local SMTP server received a response
+ # and that response contained some bridges
+ self.server.getAndCheckMessageContains("Here are your bridges")
+
+ def test_getBridges_rateLimitExceeded(self):
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ # send the mail to bridgedb, choosing a random email address
+ FROM_ADDRESS = FROM_ADDRESS_TEMPLATE % random.randint(
+ MIN_FROM_ADDRESS, MAX_FROM_ADDRESS)
+ sendMail(FROM_ADDRESS)
+
+ # then check that our local SMTP server received a response
+ # and that response contained some bridges
+ self.server.getAndCheckMessageContains("Here are your bridges")
+
+ # send another request from the same email address
+ sendMail(FROM_ADDRESS)
+
+ # this time, the email response should not contain any bridges
+ self.server.getAndCheckMessageContains(
+ "You have exceeded the rate limit. Please slow down!")
+
+ # then we send another request from the same email address
+ sendMail(FROM_ADDRESS)
+
+ # now there should be no response at all (wait 1 second to make sure)
+ self.server.checkNoMessageReceived(timeoutInSecs=1.0)
+
+ def test_getBridges_stressTest(self):
+ '''Sends a large number of emails in a short period of time, and checks
+ that a response is received for each message.
+ '''
+ if os.environ.get("CI"):
+ if not self.pid or not processExists(self.pid):
+ raise FailTest("Could not start BridgeDB process on CI server!")
+ if not self.pid or not processExists(self.pid):
+ raise SkipTest("Can't run test: no BridgeDB process running.")
+
+ NUM_MAILS = 100
+ for i in range(NUM_MAILS):
+ # Note: if by chance two emails with the same FROM_ADDRESS are
+ # generated, this test will fail Setting 'MAX_FROM_ADDRESS' to be
+ # a high value reduces the probability of this occuring, but does
+ # not rule it out
+ sendMail(fromAddress=FROM_ADDRESS_TEMPLATE
+ % random.randint(MIN_FROM_ADDRESS, MAX_FROM_ADDRESS))
+
+ for i in range(NUM_MAILS):
+ self.server.getAndCheckMessageContains("Here are your bridges")
diff --git a/test/test_translations.py b/test/test_translations.py
new file mode 100644
index 0000000..48229c6
--- /dev/null
+++ b/test/test_translations.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2014-2015, Isis Lovecruft
+# (c) 2014-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+
+from twisted.trial import unittest
+
+from bridgedb import translations
+from bridgedb.test.test_https_server import DummyRequest
+
+
+REALISH_HEADERS = {
+ b'Accept-Encoding': [b'gzip, deflate'],
+ b'User-Agent': [
+ b'Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0'],
+ b'Accept': [
+ b'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'],
+}
+
+# Add this to the above REALISH_HEADERS to use it:
+ACCEPT_LANGUAGE_HEADER = {
+ b'Accept-Language': [b'de-de,en-gb;q=0.8,en;q=0.5,en-us;q=0.3'],
+}
+
+
+class TranslationsMiscTests(unittest.TestCase):
+ """Tests for module-level code in ``bridgedb.translations`` module."""
+
+ def test_getLocaleFromHTTPRequest_withLangParam(self):
+ """This request uses a '?lang=ar' param, without an 'Accept-Language'
+ header.
+
+ The request result should be: ['ar', 'en', 'en-US'].
+ """
+ request = DummyRequest([b"bridges"])
+ request.headers.update(REALISH_HEADERS)
+ request.args.update({
+ b'transport': [b'obfs3',],
+ b'lang': [b'ar',],
+ })
+
+ parsed = translations.getLocaleFromHTTPRequest(request)
+ self.assertEqual(parsed[0], 'ar')
+ self.assertEqual(parsed[1], 'en')
+ self.assertEqual(parsed[2], 'en_US')
+ self.assertEqual(len(parsed), 3)
+
+ def test_getLocaleFromHTTPRequest_withLangParam_AcceptLanguage(self):
+ """This request uses a '?lang=ar' param, with an 'Accept-Language'
+ header which includes: ['de-de', 'en-gb', 'en', 'en-us'].
+
+ The request result should be: ['fa', 'de-de', 'en-gb', 'en', 'en-us'].
+ """
+ request = DummyRequest([b"options"])
+ request.headers.update(ACCEPT_LANGUAGE_HEADER)
+ request.args.update({b'lang': [b'fa']})
+
+ parsed = translations.getLocaleFromHTTPRequest(request)
+ self.assertEqual(parsed[0], 'fa')
+ self.assertEqual(parsed[1], 'en')
+ self.assertEqual(parsed[2], 'en_US')
+ #self.assertEqual(parsed[3], 'en-gb')
+ self.assertEqual(len(parsed), 3)
+
+ def test_getLocaleFromPlusAddr(self):
+ emailAddr = 'bridges at torproject.org'
+ replyLocale = translations.getLocaleFromPlusAddr(emailAddr)
+ self.assertEqual('en', replyLocale)
+
+ def test_getLocaleFromPlusAddr_ar(self):
+ emailAddr = 'bridges+ar at torproject.org'
+ replyLocale = translations.getLocaleFromPlusAddr(emailAddr)
+ self.assertEqual('ar', replyLocale)
diff --git a/test/test_txrecaptcha.py b/test/test_txrecaptcha.py
new file mode 100644
index 0000000..4704d49
--- /dev/null
+++ b/test/test_txrecaptcha.py
@@ -0,0 +1,270 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# :copyright: (c) 2013-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests for the bridgedb.txrecaptcha module."""
+
+import logging
+
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.internet.base import DelayedCall
+from twisted.internet.error import ConnectionDone
+from twisted.internet.error import ConnectionLost
+from twisted.internet.error import ConnectionRefusedError
+from twisted.test import proto_helpers
+from twisted.trial import unittest
+from twisted.python import failure
+from twisted.web.client import ResponseDone
+from twisted.web.http_headers import Headers
+from twisted.web.iweb import IBodyProducer
+
+from zope.interface.verify import verifyObject
+
+from bridgedb import txrecaptcha
+
+
+logging.disable(50)
+
+# Set ``DelayedCall.debug=True``, because the following traceback was occuring:
+#
+# Traceback (most recent call last):
+# Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
+# DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
+# <DelayedCall 0x1ba5b90 [29.991571188s] called=0 cancelled=0
+# Client.failIfNotConnected(TimeoutError('',))>
+# <DelayedCall 0x1baa3f8 [59.9993360043s] called=0 cancelled=0
+# ThreadedResolver._cleanup('www.google.com', <Deferred at 0x1baa320>)>
+DelayedCall.debug = True
+
+
+class MockResponse(object):
+ """Fake :api:`twisted.internet.interfaces.IResponse` for testing readBody
+ that just captures the protocol passed to deliverBody.
+
+ :ivar protocol: After :meth:`deliverBody` is called, the protocol it was
+ called with.
+ """
+ code = 200
+ phrase = "OK"
+
+ def __init__(self, headers=None):
+ """Create a mock response.
+
+ :type headers: :api:`twisted.web.http_headers.Headers`
+ :param headers: The headers for this response. If ``None``, an empty
+ ``Headers`` instance will be used.
+ """
+ if headers is None:
+ headers = Headers()
+ self.headers = headers
+
+ def deliverBody(self, protocol):
+ """Just record the given protocol without actually delivering anything
+ to it.
+ """
+ self.protocol = protocol
+
+
+class RecaptchaResponseProtocolTests(unittest.TestCase):
+ """Tests for bridgedb.txrecaptcha.RecaptchaResponseProtocol."""
+
+ def setUp(self):
+ """Setup the tests."""
+ self.finished = defer.Deferred()
+ self.proto = txrecaptcha.RecaptchaResponseProtocol(self.finished)
+
+ def _test(self, responseBody, connCloseError):
+ """Deliver the **responseBody** to
+ ``RecaptchaResponseProtocol.dataReceived``, and then lose the transport
+ connection with a **connCloseError**.
+
+ The resulting ``RecaptchaResponseProtocol.response`` should be equal
+ to the original **responseBody**.
+ """
+ self.proto.dataReceived(responseBody)
+ self.proto.connectionLost(failure.Failure(connCloseError()))
+ self.assertEqual(responseBody, self.proto.response)
+ response = self.successResultOf(self.finished)
+ return response
+
+ def test_trueResponse(self):
+ """A valid API response which states 'true' should result in
+ ``RecaptchaResponse.is_valid`` being ``True`` after receiving a
+ ``ConnectionDone``.
+ """
+ responseBody = "true\nsome-reason-or-another\n"
+ response = self._test(responseBody, ConnectionDone)
+ self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
+ self.assertTrue(response.is_valid)
+ self.assertEqual(response.error_code, "some-reason-or-another")
+
+ def test_falseResponse(self):
+ """A valid API response which states 'false' should result in
+ ``RecaptchaResponse.is_valid`` being ``false``.
+ """
+ responseBody = "false\nsome-reason-or-another\n"
+ response = self._test(responseBody, ResponseDone)
+ self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
+ self.assertIs(response.is_valid, False)
+ self.assertEqual(response.error_code, "some-reason-or-another")
+
+ def test_responseDone(self):
+ """A valid response body with a ``ResponseDone`` should result in
+ ``RecaptchaResponse.is_valid`` which is ``True``.
+ """
+ responseBody = "true\nsome-reason-or-another\n"
+ response = self._test(responseBody, ResponseDone)
+ self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
+ self.assertTrue(response.is_valid)
+ self.assertEqual(response.error_code, "some-reason-or-another")
+
+ def test_incompleteResponse(self):
+ """ConnectionLost with an incomplete response should produce a specific
+ RecaptchaResponse.error_code message.
+ """
+ responseBody = "true"
+ response = self._test(responseBody, ConnectionLost)
+ self.assertIs(response.is_valid, False)
+ self.assertEqual(response.error_code,
+ "Couldn't parse response from reCaptcha API server")
+
+
+class BodyProducerTests(unittest.TestCase):
+ """Test for :class:`bridgedb.txrecaptcha.BodyProducer`."""
+
+ def setUp(self):
+ """Setup the tests."""
+ self.content = 'Line 1\r\nLine 2\r\n'
+ self.producer = txrecaptcha._BodyProducer(self.content)
+
+ def test_interface(self):
+ """BodyProducer should correctly implement IBodyProducer interface."""
+ self.assertTrue(verifyObject(IBodyProducer, self.producer))
+
+ def test_length(self):
+ """BodyProducer.length should be equal to the total contect length."""
+ self.assertEqual(self.producer.length, len(self.content))
+
+ def test_body(self):
+ """BodyProducer.body should be the content."""
+ self.assertEqual(self.producer.body, self.content)
+
+ def test_startProducing(self):
+ """:func:`txrecaptcha.BodyProducer.startProducing` should deliver the
+ original content to an IConsumer implementation.
+ """
+ consumer = proto_helpers.StringTransport()
+ consumer.registerProducer(self.producer, False)
+ self.producer.startProducing(consumer)
+ self.assertEqual(consumer.value(), self.content)
+ consumer.clear()
+
+
+class SubmitTests(unittest.TestCase):
+ """Tests for :func:`bridgedb.txrecaptcha.submit`."""
+
+ def setUp(self):
+ """Setup the tests."""
+ self.challenge = (
+ "03AHJ_Vutbkv3jolF5JXfJTFf5wtbdkwIJF7WA77WYjLfOUEvKW7eHBiEDKQB__7"
+ "GHtUOmXC13GFYIt09HuS-ZN1j5EuDmC7bzHpHUAlpI5rbOvByypYt1vtskwnN24g"
+ "zwWkrtKj8yGBWRNFljFMvtqYqHeHwJitRktSfKmV4q9VVgLBwkwlbvGUICmGaDrx"
+ "dg5lYV3hpijIkmnwXygWIwoqQ0VeCgPQQ1Yw")
+ self.response = "cknwnlym+ullyHLy"
+ self.key = '6BdkT-18FFHAAA349auGabiqntjRJAiEM2cqPMaM8'
+ self.ip = "1.2.3.4"
+
+ def test_submit_emptyResponseField(self):
+ """An empty 'recaptcha_response_field' should return a deferred which
+ callbacks with a RecaptchaResponse whose error_code is
+ 'incorrect-captcha-sol'.
+ """
+ def checkResponse(response):
+ """Check that the response is a
+ :class:`txcaptcha.RecaptchaResponse`.
+ """
+ self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
+ self.assertIs(response.is_valid, False)
+ self.assertEqual(response.error_code, 'incorrect-captcha-sol')
+
+ d = txrecaptcha.submit(self.challenge, '', self.key, self.ip)
+ d.addCallback(checkResponse)
+ return d
+
+ def test_submit_returnsDeferred(self):
+ """:func:`txrecaptcha.submit` should return a deferred."""
+ response = txrecaptcha.submit(self.challenge, self.response, self.key,
+ self.ip)
+ self.assertIsInstance(response, defer.Deferred)
+
+ def test_submit_resultIsRecaptchaResponse(self):
+ """Regardless of success or failure, the deferred returned from
+ :func:`txrecaptcha.submit` should be a
+ :class:`txcaptcha.RecaptchaResponse`.
+ """
+ def checkResponse(response):
+ """Check that the response is a
+ :class:`txcaptcha.RecaptchaResponse`.
+ """
+ self.assertIsInstance(response, txrecaptcha.RecaptchaResponse)
+ self.assertIsInstance(response.is_valid, bool)
+ self.assertIsInstance(response.error_code, basestring)
+
+ d = txrecaptcha.submit(self.challenge, self.response, self.key,
+ self.ip)
+ d.addCallback(checkResponse)
+ return d
+
+ def tearDown(self):
+ """Cleanup method for removing timed out connections on the reactor.
+
+ This seems to be the solution for the dirty reactor due to
+ ``DelayedCall``s which is mentioned at the beginning of this
+ file. There doesn't seem to be any documentation anywhere which
+ proposes this solution, although this seems to solve the problem.
+ """
+ for delay in reactor.getDelayedCalls():
+ try:
+ delay.cancel()
+ except (AlreadyCalled, AlreadyCancelled):
+ pass
+
+
+class MiscTests(unittest.TestCase):
+ """Tests for miscellaneous functions in :mod:`~bridgedb.txrecaptcha`."""
+
+ def test_cbRequest(self):
+ """Send a :class:`MockResponse` and check that the resulting protocol
+ is a :class:`~bridgedb.txrecaptcha.RecaptchaResponseProtocol`.
+ """
+ response = MockResponse()
+ result = txrecaptcha._cbRequest(response)
+ self.assertIsInstance(result, defer.Deferred)
+ self.assertIsInstance(response.protocol,
+ txrecaptcha.RecaptchaResponseProtocol)
+
+ def test_ebRequest(self):
+ """Send a :api:`twisted.python.failure.Failure` and check that the
+ resulting protocol is a
+ :class:`~bridgedb.txrecaptcha.RecaptchaResponseProtocol`.
+ """
+ msg = "Einhorn"
+ fail = failure.Failure(ConnectionRefusedError(msg))
+ result = txrecaptcha._ebRequest(fail)
+ self.assertIsInstance(result, txrecaptcha.RecaptchaResponse)
+ self.assertRegexpMatches(result.error_code, msg)
+
+ def test_encodeIfNecessary(self):
+ """:func:`txrecapcha._encodeIfNecessary` should convert unicode objects
+ into strings.
+ """
+ origString = unicode('abc')
+ self.assertIsInstance(origString, unicode)
+ newString = txrecaptcha._encodeIfNecessary(origString)
+ self.assertIsInstance(newString, str)
diff --git a/test/test_util.py b/test/test_util.py
new file mode 100644
index 0000000..da4ddf4
--- /dev/null
+++ b/test/test_util.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2014-2015, Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: see LICENSE for licensing information
+
+"""Unittests for the :mod:`bridgedb.util` module."""
+
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from twisted.mail.smtp import Address
+from twisted.trial import unittest
+
+from bridgedb import util
+
+
+class MiscLoggingUtilTests(unittest.TestCase):
+ """Unittests for miscellaneous logging functions in :mod:`bridgedb.util`."""
+
+ def test_getLogHandlers(self):
+ """util._getLogHandlers() should return ['rotating', 'console'] if
+ both stderr and logfile logging are enabled.
+ """
+ logHandlers = util._getLogHandlers()
+ self.assertIsInstance(logHandlers, list)
+ self.assertEqual(len(logHandlers), 2)
+
+ def test_getLogHandlers_disableStderr(self):
+ """util._getLogHandlers() should return ['rotating'] if stderr logging
+ is disabled.
+ """
+ logHandlers = util._getLogHandlers(logToStderr=False)
+ self.assertIsInstance(logHandlers, list)
+ self.assertEqual(len(logHandlers), 1)
+ self.assertTrue('console' not in logHandlers)
+
+ def test_getLogHandlers_disable_logfile(self):
+ """util._getLogHandlers() should return ['console'] if stderr logging
+ is disabled.
+ """
+ logHandlers = util._getLogHandlers(logToFile=False)
+ self.assertIsInstance(logHandlers, list)
+ self.assertEqual(len(logHandlers), 1)
+ self.assertTrue('rotating' not in logHandlers)
+
+ def test_getRotatingFileHandler(self):
+ """_getRotatingFileHandler() should create a file with 0600
+ permissions (os.ST_WRITE | os.ST_APPEND).
+ """
+ filename = str(self.id()) + '.log'
+ logHandler = util._getRotatingFileHandler(filename)
+ self.assertTrue(os.path.isfile(filename))
+ self.assertEqual(os.stat_result(os.stat(filename)).st_mode, 33152)
+ self.assertIsInstance(logHandler(),
+ util.logging.handlers.RotatingFileHandler)
+
+ def test_configureLogging(self):
+ """Configure logging should be callable without borking anything."""
+ from bridgedb.persistent import Conf
+ util.configureLogging(Conf())
+ util.logging.info("BridgeDB's email address: bridges at torproject.org")
+
+
+class LevenshteinDistanceTests(unittest.TestCase):
+ """Unittests for `bridgedb.util.levenshteinDistance."""
+
+ def test_levenshteinDistance_blank_blank(self):
+ """The Levenshtein Distance between '' and '' should be 0."""
+ distance = util.levenshteinDistance('', '')
+ self.assertEqual(distance, 0)
+
+ def test_levenshteinDistance_cat_cat(self):
+ """The Levenshtein Distance between 'cat' and 'cat' should be 0."""
+ distance = util.levenshteinDistance('cat', 'cat')
+ self.assertEqual(distance, 0)
+
+ def test_levenshteinDistance_bat_cat(self):
+ """The Levenshtein Distance between 'bat' and 'cat' should be 1."""
+ distance = util.levenshteinDistance('bat', 'cat')
+ self.assertEqual(distance, 1)
+
+ def test_levenshteinDistance_bar_cat(self):
+ """The Levenshtein Distance between 'bar' and 'cat' should be 2."""
+ distance = util.levenshteinDistance('bar', 'cat')
+ self.assertEqual(distance, 2)
+
+ def test_levenshteinDistance_bridgedb_doge(self):
+ """The Levenshtein Distance between 'bridgedb' and 'doge' should be 6."""
+ distance = util.levenshteinDistance('bridgedb', 'doge')
+ self.assertEqual(distance, 6)
+
+ def test_levenshteinDistance_feidanchaoren0043_feidanchaoren0011(self):
+ """The Levenshtein Distance between the usernames in
+ 'feidanchaoren0043 at gmail.com' and 'feidanchaoren0011 at gmail.com' should
+ be less than an EMAIL_FUZZY_MATCH parameter.
+ """
+ email1 = Address('feidanchaoren0043 at gmail.com')
+ email2 = Address('feidanchaoren0011 at gmail.com')
+ # Fuzzy match if the Levenshtein Distance is less than or equal to:
+ fuzzyMatch = 4
+ distance = util.levenshteinDistance(email1.local, email2.local)
+ self.assertLessEqual(distance, fuzzyMatch)
+
+
+class JustifiedLogFormatterTests(unittest.TestCase):
+ """Unittests for :class:`bridgedb.util.JustifiedLogFormatter`."""
+
+ def setUp(self):
+ # name, level, path, lineno, message, args, exc_info
+ self.record = logging.LogRecord('name', logging.INFO, '/foo/bar/baz',
+ 12345, 'This is a message', None, None)
+
+ def test_util_JustifiedLogFormatter(self):
+ formatter = util.JustifiedLogFormatter()
+ self.assertIsInstance(formatter, logging.Formatter)
+
+ def test_util_JustifiedLogFormatter_logThreads(self):
+ formatter = util.JustifiedLogFormatter(logThreads=True)
+ self.assertIsInstance(formatter, logging.Formatter)
+
+ def test_util_JustifiedLogFormatter_formatCallingFuncName(self):
+ formatter = util.JustifiedLogFormatter()
+ record = formatter._formatCallingFuncName(self.record)
+ self.assertIsInstance(formatter, logging.Formatter)
+ self.assertIsInstance(record, logging.LogRecord)
+
+ def test_util_JustifiedLogFormatter_format(self):
+ formatter = util.JustifiedLogFormatter()
+ formatted = formatter.format(self.record)
+ self.assertIsInstance(formatter, logging.Formatter)
+ self.assertIsInstance(formatted, basestring)
+ self.assertNotEqual(formatted, '')
+ self.assertTrue('INFO' in formatted)
+ self.assertTrue('This is a message' in formatted)
diff --git a/test/util.py b/test/util.py
new file mode 100644
index 0000000..0f1e0f9
--- /dev/null
+++ b/test/util.py
@@ -0,0 +1,301 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013, Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-Clause BSD, see LICENSE for licensing information
+
+"""Unittests utilitys the `bridgedb.test` package."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import errno
+import ipaddr
+import os
+import random
+import time
+
+from functools import wraps
+
+from twisted.trial import unittest
+
+from bridgedb import util as bdbutil
+from bridgedb.bridges import IBridge
+from bridgedb.parse.addr import isIPAddress
+
+from zope.interface import implementer
+
+
+def fileCheckDecorator(func):
+ """Method decorator for a t.t.unittest.TestCase test_* method.
+
+ .. codeblock:: python
+
+ import shutil
+ from twisted.trial import unittest
+
+ pyunit = __import__('unittest')
+
+ class TestTests(unittest.TestCase):
+ @fileCheckDecorator
+ def doCopyFile(src, dst, description=None):
+ shutil.copy(src, dst)
+ def test_doCopyFile(self):
+ srcfile = self.mktemp()
+ dstfile = self.mktemp()
+ with open(srcfile, 'wb') as fh:
+ fh.write('testing TestCase method decorator utility')
+ fh.flush()
+ self.doCopyFile(srcfile, dstfile, 'asparagus')
+
+ testtest = TestTests()
+ testtest.runTest()
+
+ ..
+
+ :type func: callable
+ :param func: The ``test_*`` method, from a
+ :api:`twisted.trial.unittest.TestCase` instance, to wrap.
+ """
+ @wraps(func)
+ def wrapper(self, src, dst, description):
+ self.assertTrue(os.path.isfile(src),
+ "Couldn't find original %s file: %r"
+ % (str(description), src))
+ func(self, src, dst, description)
+ self.assertTrue(os.path.isfile(dst),
+ "Couldn't find new %s file: %r. Original: %r"
+ % (str(description), dst, src))
+ return wrapper
+
+def processExists(pid):
+ """Test if the process with **pid** exists.
+
+ :param int pid: An integer specifying the process ID.
+ :raises: OSError, if ``OSError.errno`` wasn't an expected errno (according
+ to the "ERRORS" section from ``man 2 kill``).
+ :rtype: bool
+ :returns: ``True`` if a process with **pid** exists, ``False`` otherwise.
+ """
+ try:
+ os.kill(pid, 0)
+ except OSError as err:
+ if err.errno == errno.ESRCH: # ESRCH: No such process
+ return False
+ if err.errno == errno.EPERM: # EPERM: Operation not permitted
+ # If we're not allowed to signal the process, then there exists a
+ # process that we don't have permissions to access.
+ return True
+ else:
+ raise
+ else:
+ return True
+
+def getBridgeDBPID(pidfile="bridgedb.pid"):
+ """Read the ``bridgedb.pid`` file in **rundir**, if it exists, to get the
+ PID.
+
+ :param str pidfile: The path to the BridgeDB pidfile.
+ :rtype: int
+ :returns: The process ID, if available, otherwise ``0``.
+ """
+ fh = None
+ try:
+ fh = open(pidfile)
+ except (IOError, OSError) as err:
+ print(err)
+ pid = 0
+ else:
+ pid = int(fh.read())
+
+ if fh:
+ fh.close()
+
+ return pid
+
+def bracketIPv6(ip):
+ """Put brackets around an IPv6 address, just as tor does."""
+ return "[%s]" % ip
+
+def randomPort():
+ return random.randint(1, 65535)
+
+def randomHighPort():
+ return random.randint(1024, 65535)
+
+def randomIPv4():
+ return ipaddr.IPv4Address(random.getrandbits(32))
+
+def randomIPv6():
+ return ipaddr.IPv6Address(random.getrandbits(128))
+
+def randomIP():
+ if random.choice(xrange(2)):
+ return randomIPv4()
+ return randomIPv6()
+
+def randomIPv4String():
+ return randomIPv4().compressed
+
+def randomIPv6String():
+ return bracketIPv6(randomIPv6().compressed)
+
+def randomIPString():
+ if random.choice(xrange(2)):
+ return randomIPv4String()
+ return randomIPv6String()
+
+def valid(func):
+ """Wrapper for the above ``randomIPv*`` functions to ensure they only
+ return addresses which BridgeDB considers "valid".
+
+ .. seealso:: :func:`bridgedb.parse.addr.isIPAddress`
+ """
+ @wraps(func)
+ def wrapper():
+ ip = None
+ while not isIPAddress(ip):
+ ip = func()
+ return ip
+ return wrapper
+
+randomValidIPv4 = valid(randomIPv4)
+randomValidIPv6 = valid(randomIPv6)
+randomValidIP = valid(randomIP)
+randomValidIPv4String = valid(randomIPv4String)
+randomValidIPv6String = valid(randomIPv6String)
+randomValidIPString = valid(randomIPString)
+
+_FAKE_BRIDGES = []
+
+def generateFakeBridges(n=500):
+ """Generate a set of **n** :class:`~bridgedb.bridges.Bridges` with random
+ data.
+ """
+ from bridgedb.bridges import Bridge
+ from bridgedb.bridges import PluggableTransport
+
+ global _FAKE_BRIDGES
+
+ if _FAKE_BRIDGES:
+ return _FAKE_BRIDGES
+
+ bridges = []
+
+ for i in range(n):
+ addr = randomValidIPv4String()
+ nick = 'bridge-%d' % i
+ port = randomHighPort()
+ # Real tor currently only supports one extra ORAddress, and it can
+ # only be IPv6.
+ addrs = [(randomValidIPv6(), randomHighPort(), 6)]
+ fpr = "".join(random.choice('abcdef0123456789') for _ in xrange(40))
+
+ # We only support the ones without PT args, because they're easier to fake.
+ supported = ["obfs2", "obfs3", "fte"]
+ transports = []
+ for j, method in zip(range(1, len(supported) + 1), supported):
+ pt = PluggableTransport(fpr, method, addr, port - j, {})
+ transports.append(pt)
+
+ bridge = Bridge(nick, addr, port, fpr)
+ bridge.flags.update("Running Stable")
+ bridge.transports = transports
+ bridge.orAddresses = addrs
+ bridges.append(bridge)
+
+ _FAKE_BRIDGES = bridges
+ return bridges
+
+
+#: Mixin class for use with :api:`~twisted.trial.unittest.TestCase`. A
+#: ``TestCaseMixin`` can be used to add additional methods, which should be
+#: common to multiple ``TestCase`` subclasses, without the ``TestCaseMixin``
+#: being run as a ``TestCase`` by ``twisted.trial``.
+TestCaseMixin = bdbutil.mixin
+TestCaseMixin.register(unittest.TestCase)
+
+
+class Benchmarker(object):
+ """Wrap a context with a timer to benchmark execution time.
+
+ .. hint:: Use like so::
+
+ with Benchmarker():
+ foo(bar, baz)
+
+ Once the ``with`` context exits, something like::
+
+ Benchmark: 180.269957ms (0s)
+
+ will be printed to stdout (if **verbose** is set to ``True``).
+ """
+
+ def __init__(self, verbose=True):
+ self.verbose = verbose
+
+ def __enter__(self):
+ self.start = time.time()
+ return self
+
+ def __exit__(self, *args):
+ self.end = time.time()
+ self.seconds = self.end - self.start
+ self.milliseconds = self.seconds * 1000
+ if self.verbose:
+ print("Benchmark: %12fms %12fs" % (self.milliseconds, self.seconds))
+
+
+ at implementer(IBridge)
+class DummyBridge(object):
+ """A mock :class:`bridgedb.bridges.Bridge` which only supports a mocked
+ ``getBridgeLine`` method."""
+
+ ptArgs = {}
+
+ def __init__(self):
+ """Create a mocked bridge suitable for testing distributors and web
+ resource rendering.
+ """
+ ipv4 = randomIPv4()
+ self.nickname = "bridge-{0}".format(ipv4)
+ self.address = ipaddr.IPv4Address(ipv4)
+ self.orPort = randomPort()
+ self.fingerprint = "".join(random.choice('abcdef0123456789')
+ for _ in xrange(40))
+ self.orAddresses = [(randomIPv6(), randomPort(), 6)]
+
+ def getBridgeLine(self, bridgeRequest, includeFingerprint=True):
+ """Get a "torrc" bridge config line to give to a client."""
+ if not bridgeRequest.isValid():
+ return
+ line = []
+ if bridgeRequest.transports:
+ line.append(bridgeRequest.transports[-1]) # Just the last PT
+ if bridgeRequest.ipVersion is 6:
+ line.append("[%s]:%s" % self.orAddresses[0][:2])
+ else:
+ line.append("%s:%s" % (self.address, self.orPort))
+ if includeFingerprint is True:
+ line.append(self.fingerprint)
+ if self.ptArgs:
+ line.append(','.join(['='.join(x) for x in self.ptArgs.items()]))
+ return " ".join([item for item in line])
+
+
+ at implementer(IBridge)
+class DummyMaliciousBridge(DummyBridge):
+ """A mock :class:`bridgedb.Bridges.Bridge` which only supports a mocked
+ ``getConfigLine`` method and which maliciously insert an additional fake
+ bridgeline and some javascript into its PT arguments.
+ """
+ ptArgs = {
+ "eww": "\rBridge 1.2.3.4:1234",
+ "bad": "\nBridge 6.6.6.6:6666 0123456789abcdef0123456789abcdef01234567",
+ "evil": "<script>alert('fuuuu');</script>",
+ }
More information about the tor-commits
mailing list