Merge pull request #163 from ThomasWaldmann/master

pep8 fixes
This commit is contained in:
Micah Lee 2014-11-24 21:10:37 -08:00
commit d0bdb9143f
19 changed files with 194 additions and 97 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
"""
Check translation lacked or disused.
Example:
@ -24,7 +24,7 @@ es disused gui_starting_server
2. load translation key from locale/*.json.
3. compare these.
'''
"""
import fileinput, argparse, re, os, codecs, json, sys
@ -75,13 +75,11 @@ def main():
key = m.group(1)
translate_keys.add(key)
if args.show_all_keys:
for k in sorted(translate_keys):
print k
sys.exit()
locale_files = [f for f in files_in(dir, 'locale') if f.endswith('.json')]
for locale_file in locale_files:
with codecs.open(locale_file, 'r', encoding='utf-8') as f:

View File

@ -32,6 +32,7 @@ def get_platform():
p = 'Tails'
return p
def get_onionshare_dir():
if get_platform() == 'Darwin':
onionshare_dir = os.path.dirname(__file__)
@ -39,6 +40,7 @@ def get_onionshare_dir():
onionshare_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
return onionshare_dir
def constant_time_compare(val1, val2):
_builtin_constant_time_compare = getattr(hmac, 'compare_digest', None)
if _builtin_constant_time_compare is not None:
@ -55,6 +57,7 @@ def constant_time_compare(val1, val2):
result |= x ^ y
return result == 0
def random_string(num_bytes, output_len=None):
b = os.urandom(num_bytes)
h = hashlib.sha256(b).digest()[:16]
@ -63,6 +66,7 @@ def random_string(num_bytes, output_len=None):
return s
return s[:output_len]
def human_readable_filesize(b):
thresh = 1024.0
if b < thresh:
@ -75,9 +79,11 @@ def human_readable_filesize(b):
u += 1
return '{0} {1}'.format(round(b, 1), units[u])
def is_root():
return os.geteuid() == 0
def dir_size(start_path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
@ -87,6 +93,7 @@ def dir_size(start_path):
total_size += os.path.getsize(fp)
return total_size
def get_tmp_dir():
if get_platform() == "Windows":
if 'Temp' in os.environ:
@ -97,6 +104,7 @@ def get_tmp_dir():
temp = '/tmp'
return temp
class ZipWriter(object):
def __init__(self, zip_filename=None):
if zip_filename:
@ -120,4 +128,3 @@ class ZipWriter(object):
def close(self):
self.z.close()

View File

@ -25,11 +25,17 @@ from stem import SocketError
import strings, helpers, web
class NoTor(Exception): pass
class TailsError(Exception): pass
class NoTor(Exception):
pass
class TailsError(Exception):
pass
def hsdic2list(dic):
"Convert what we get from get_conf_map to what we need for set_options"
"""Convert what we get from get_conf_map to what we need for set_options"""
return [
pair for pairs in [
[('HiddenServiceDir', vals[0]), ('HiddenServicePort', vals[1])]
@ -37,6 +43,7 @@ def hsdic2list(dic):
] for pair in pairs
]
class OnionShare(object):
def __init__(self, debug=False, local_only=False, stay_open=False):
self.port = None
@ -56,7 +63,6 @@ class OnionShare(object):
# files and dirs to delete on shutdown
self.cleanup_filenames = []
def cleanup(self):
if self.controller:
# Get fresh hidden services (maybe changed since last time)
@ -191,6 +197,7 @@ class OnionShare(object):
return False
return True
def tails_root():
# if running in Tails and as root, do only the things that require root
if helpers.get_platform() == 'Tails' and helpers.is_root():
@ -205,7 +212,8 @@ def tails_root():
sys.exit(-1)
# open hole in firewall
subprocess.call(['/sbin/iptables', '-I', 'OUTPUT', '-o', 'lo', '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
subprocess.call(['/sbin/iptables', '-I', 'OUTPUT', '-o', 'lo',
'-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
# start hidden service
app = OnionShare()
@ -217,8 +225,10 @@ def tails_root():
# close hole in firewall on shutdown
import signal
def handler(signum=None, frame=None):
subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo', '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo',
'-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
sys.exit()
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]:
signal.signal(sig, handler)
@ -227,6 +237,7 @@ def tails_root():
while True:
time.sleep(1)
def main():
strings.load_strings()
tails_root()

View File

@ -65,6 +65,7 @@ PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"}
_orgsocket = _orig_socket = socket.socket
class ProxyError(IOError):
"""
socket_err contains original socket.error exception.
@ -79,33 +80,55 @@ class ProxyError(IOError):
def __str__(self):
return self.msg
class GeneralProxyError(ProxyError): pass
class ProxyConnectionError(ProxyError): pass
class SOCKS5AuthError(ProxyError): pass
class SOCKS5Error(ProxyError): pass
class SOCKS4Error(ProxyError): pass
class HTTPError(ProxyError): pass
SOCKS4_ERRORS = { 0x5B: "Request rejected or failed",
class GeneralProxyError(ProxyError):
pass
class ProxyConnectionError(ProxyError):
pass
class SOCKS5AuthError(ProxyError):
pass
class SOCKS5Error(ProxyError):
pass
class SOCKS4Error(ProxyError):
pass
class HTTPError(ProxyError):
pass
SOCKS4_ERRORS = {
0x5B: "Request rejected or failed",
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
0x5D: "Request rejected because the client program and identd report different user-ids"
0x5D: "Request rejected because the client program and identd report different user-ids",
}
SOCKS5_ERRORS = { 0x01: "General SOCKS server failure",
SOCKS5_ERRORS = {
0x01: "General SOCKS server failure",
0x02: "Connection not allowed by ruleset",
0x03: "Network unreachable",
0x04: "Host unreachable",
0x05: "Connection refused",
0x06: "TTL expired",
0x07: "Command not supported, or protocol error",
0x08: "Address type not supported"
0x08: "Address type not supported",
}
DEFAULT_PORTS = { SOCKS4: 1080,
DEFAULT_PORTS = {
SOCKS4: 1080,
SOCKS5: 1080,
HTTP: 8080
HTTP: 8080,
}
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
"""
set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
@ -119,6 +142,7 @@ def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username
setdefaultproxy = set_default_proxy
def get_default_proxy():
"""
Returns the default proxy, set by set_default_proxy.
@ -127,6 +151,7 @@ def get_default_proxy():
getdefaultproxy = get_default_proxy
def wrap_module(module):
"""
Attempts to replace a module's socket library with a SOCKS socket. Must set
@ -141,6 +166,7 @@ def wrap_module(module):
wrapmodule = wrap_module
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
proxy_port=None, proxy_username=None,
proxy_password=None, timeout=None):
@ -161,6 +187,7 @@ def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
sock.connect(dest_pair)
return sock
class socksocket(socket.socket):
"""socksocket([family[, type[, proto]]]) -> socket object
@ -181,9 +208,10 @@ class socksocket(socket.socket):
self.proxy_sockname = None
self.proxy_peername = None
self.proxy_negotiators = { SOCKS4: self._negotiate_SOCKS4,
self.proxy_negotiators = {
SOCKS4: self._negotiate_SOCKS4,
SOCKS5: self._negotiate_SOCKS5,
HTTP: self._negotiate_HTTP
HTTP: self._negotiate_HTTP,
}
def _recvall(self, count):
@ -446,7 +474,6 @@ class socksocket(socket.socket):
self.proxy_sockname = (b"0.0.0.0", 0)
self.proxy_peername = addr, dest_port
def connect(self, dest_pair):
"""
Connects to the specified destination through a proxy.
@ -465,7 +492,6 @@ class socksocket(socket.socket):
or not isinstance(dest_port, int)):
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
if proxy_type is None:
# Treat like regular socket object
_orig_socket.connect(self, (dest_addr, dest_port))

View File

@ -22,6 +22,7 @@ import helpers
strings = {}
def load_strings(default="en"):
global strings
@ -49,6 +50,7 @@ def load_strings(default="en"):
if key in translated[lang]:
strings[key] = translated[lang][key]
def translated(k, gui=False):
if gui:
return strings[k].encode("utf-8").decode('utf-8', 'replace')

View File

@ -28,6 +28,8 @@ app = Flask(__name__)
file_info = []
zip_filename = None
zip_filesize = None
def set_file_info(filenames):
global file_info, zip_filename, zip_filesize
@ -59,6 +61,7 @@ def set_file_info(filenames):
zip_filename = z.zip_filename
zip_filesize = os.path.getsize(zip_filename)
REQUEST_LOAD = 0
REQUEST_DOWNLOAD = 1
REQUEST_PROGRESS = 2
@ -66,6 +69,7 @@ REQUEST_OTHER = 3
REQUEST_CANCELED = 4
q = Queue.Queue()
def add_request(type, path, data=None):
global q
q.put({
@ -74,16 +78,22 @@ def add_request(type, path, data=None):
'data': data
})
slug = helpers.random_string(16)
download_count = 0
stay_open = False
def set_stay_open(new_stay_open):
global stay_open
stay_open = new_stay_open
def get_stay_open():
return stay_open
def debug_mode():
import logging
@ -96,6 +106,7 @@ def debug_mode():
log_handler.setLevel(logging.WARNING)
app.logger.addHandler(log_handler)
@app.route("/<slug_candidate>")
def index(slug_candidate):
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
@ -112,6 +123,7 @@ def index(slug_candidate):
strings=strings.strings
)
@app.route("/<slug_candidate>/download")
def download(slug_candidate):
global download_count
@ -150,7 +162,8 @@ def download(slug_candidate):
# tell GUI the progress
downloaded_bytes = fp.tell()
percent = round((1.0 * downloaded_bytes / zip_filesize) * 100, 2)
sys.stdout.write("\r{0}, {1}% ".format(helpers.human_readable_filesize(downloaded_bytes), percent))
sys.stdout.write(
"\r{0}, {1}% ".format(helpers.human_readable_filesize(downloaded_bytes), percent))
sys.stdout.flush()
add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
except:
@ -181,6 +194,7 @@ def download(slug_candidate):
r.headers.add('Content-Type', content_type)
return r
@app.errorhandler(404)
def page_not_found(e):
add_request(REQUEST_OTHER, request.path)
@ -188,6 +202,8 @@ def page_not_found(e):
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
shutdown_slug = helpers.random_string(16)
@app.route("/<shutdown_slug_candidate>/shutdown")
def shutdown(shutdown_slug_candidate):
if not helpers.constant_time_compare(shutdown_slug.encode('ascii'), shutdown_slug_candidate.encode('ascii')):
@ -201,16 +217,19 @@ def shutdown(shutdown_slug_candidate):
return ""
def start(port, stay_open=False):
set_stay_open(stay_open)
app.run(port=port, threaded=True)
def stop(port):
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
if helpers.get_platform() == 'Tails':
# in Tails everything is proxies over Tor, so we need to get lower level
# to connect not over the proxy
import socket
s = socket.socket()
s.connect(('127.0.0.1', port))
s.sendall('GET /{0}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os, sys, inspect, platform
def get_onionshare_gui_dir():
if platform.system() == 'Darwin':
onionshare_gui_dir = os.path.dirname(__file__)
@ -28,6 +29,7 @@ def get_onionshare_gui_dir():
onionshare_gui_dir = get_onionshare_gui_dir()
def get_image_path(filename):
if platform.system() == 'Linux':
prefix = os.path.join(sys.prefix, 'share/onionshare/images')

View File

@ -22,6 +22,7 @@ from PyQt4 import QtCore, QtGui
import common
from onionshare import strings, helpers
class Downloads(QtGui.QVBoxLayout):
def __init__(self):
super(Downloads, self).__init__()

View File

@ -23,6 +23,7 @@ from PyQt4 import QtCore, QtGui
import common
from onionshare import strings, helpers
class FileList(QtGui.QListWidget):
files_dropped = QtCore.pyqtSignal()
files_updated = QtCore.pyqtSignal()
@ -137,6 +138,7 @@ class FileList(QtGui.QListWidget):
u += 1
return '{0} {1}'.format(round(b, 1), units[u])
class FileSelection(QtGui.QVBoxLayout):
def __init__(self):
super(FileSelection, self).__init__()
@ -186,14 +188,16 @@ class FileSelection(QtGui.QVBoxLayout):
self.file_list.update()
def add_files(self):
filenames = QtGui.QFileDialog.getOpenFileNames(caption=strings._('gui_choose_files', True), options=QtGui.QFileDialog.ReadOnly)
filenames = QtGui.QFileDialog.getOpenFileNames(
caption=strings._('gui_choose_files', True), options=QtGui.QFileDialog.ReadOnly)
if filenames:
for filename in filenames:
self.file_list.add_file(str(filename))
self.update()
def add_dir(self):
filename = QtGui.QFileDialog.getExistingDirectory(caption=strings._('gui_choose_folder', True), options=QtGui.QFileDialog.ReadOnly)
filename = QtGui.QFileDialog.getExistingDirectory(
caption=strings._('gui_choose_folder', True), options=QtGui.QFileDialog.ReadOnly)
if filename:
self.file_list.add_file(str(filename))
self.update()

View File

@ -35,6 +35,7 @@ from server_status import ServerStatus
from downloads import Downloads
from options import Options
class Application(QtGui.QApplication):
def __init__(self):
platform = helpers.get_platform()
@ -42,6 +43,7 @@ class Application(QtGui.QApplication):
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtGui.QApplication.__init__(self, sys.argv)
class OnionShareGui(QtGui.QWidget):
start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal()
@ -206,6 +208,7 @@ class OnionShareGui(QtGui.QWidget):
def clear_message(self):
self.status_bar.clearMessage()
def alert(msg, icon=QtGui.QMessageBox.NoIcon):
dialog = QtGui.QMessageBox()
dialog.setWindowTitle("OnionShare")
@ -214,6 +217,7 @@ def alert(msg, icon=QtGui.QMessageBox.NoIcon):
dialog.setIcon(icon)
dialog.exec_()
def main():
strings.load_strings()

View File

@ -22,6 +22,7 @@ from PyQt4 import QtCore, QtGui
import common
from onionshare import strings, helpers
class Options(QtGui.QHBoxLayout):
def __init__(self, web):
super(Options, self).__init__()

View File

@ -23,6 +23,7 @@ from PyQt4 import QtCore, QtGui
import common
from onionshare import strings, helpers
class ServerStatus(QtGui.QVBoxLayout):
server_started = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal()

View File

@ -27,6 +27,7 @@ try:
except ImportError:
from distutils.core import setup
def file_list(path):
files = []
for filename in os.listdir(path):
@ -37,11 +38,23 @@ def file_list(path):
version = open('version').read().strip()
description = (
"""OnionShare lets you securely and anonymously share a file of any size with someone. """
"""It works by starting a web server, making it accessible as a Tor hidden service, """
"""and generating an unguessable URL to access and download the file.""")
long_description = description + " " + (
"""It doesn't require setting up a server on the internet somewhere or using a third """
"""party filesharing service. You host the file on your own computer and use a Tor """
"""hidden service to make it temporarily accessible over the internet. The other user """
"""just needs to use Tor Browser to download the file from you."""
)
setup(
name='onionshare',
version=version,
description='OnionShare lets you securely and anonymously share a file of any size with someone. It works by starting a web server, making it accessible as a Tor hidden service, and generating an unguessable URL to access and download the file.',
long_description="""OnionShare lets you securely and anonymously share a file of any size with someone. It works by starting a web server, making it accessible as a Tor hidden service, and generating an unguessable URL to access and download the file. It doesn't require setting up a server on the internet somewhere or using a third party filesharing service. You host the file on your own computer and use a Tor hidden service to make it temporarily accessible over the internet. The other user just needs to use Tor Browser to download the file from you.""",
description=description,
long_description=long_description,
author='Micah Lee',
author_email='micah@micahflee.com',
url='https://github.com/micahflee/onionshare',

View File

@ -18,7 +18,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import division
import os, sys, subprocess, time, hashlib, platform, json, locale, socket, argparse, Queue, inspect, base64, random, functools, logging, ctypes, hmac, shutil
import os, sys, subprocess, time, hashlib, platform, json, locale, socket
import argparse, Queue, inspect, base64, random, functools, logging, ctypes
import hmac, shutil
from itertools import izip
import stem, stem.control, flask
from PyQt4 import QtCore, QtGui

View File

@ -21,13 +21,15 @@ from nose import with_setup
import test_helpers
def test_get_platform_on_tails():
"get_platform() returns 'Tails' when hostname is 'amnesia'"
helpers.platform.uname = lambda: ('Linux', 'amnesia', '3.14-1-amd64', '#1 SMP Debian 3.14.4-1 (2014-05-13)', 'x86_64', '')
"""get_platform() returns 'Tails' when hostname is 'amnesia'"""
helpers.platform.uname = lambda: ('Linux', 'amnesia', '3.14-1-amd64',
'#1 SMP Debian 3.14.4-1 (2014-05-13)', 'x86_64', '')
assert helpers.get_platform() == 'Tails'
def test_get_platform_returns_platform_system():
"get_platform() returns platform.system() when ONIONSHARE_PLATFORM is not defined"
"""get_platform() returns platform.system() when ONIONSHARE_PLATFORM is not defined"""
helpers.platform.system = lambda: 'Sega Saturn'
assert helpers.get_platform() == 'Sega Saturn'

View File

@ -21,19 +21,21 @@ import locale
from onionshare import strings
from nose import with_setup
def test_starts_with_empty_strings():
"creates an empty strings dict by default"
"""creates an empty strings dict by default"""
assert strings.strings == {}
def test_load_strings_defaults_to_english():
"load_strings() loads English by default"
"""load_strings() loads English by default"""
locale.getdefaultlocale = lambda: ('en_US', 'UTF-8')
strings.load_strings()
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
def test_load_strings_loads_other_languages():
"load_strings() loads other languages in different locales"
"""load_strings() loads other languages in different locales"""
locale.getdefaultlocale = lambda: ('fr_FR', 'UTF-8')
strings.load_strings("fr")
assert strings._('wait_for_hs') == "En attente du HS:"

View File

@ -20,14 +20,16 @@ import socket
from onionshare import OnionShare
from nose import with_setup
def test_choose_port_returns_a_port_number():
"choose_port() returns a port number"
"""choose_port() returns a port number"""
app = OnionShare()
app.choose_port()
assert 1024 <= app.port <= 65535
def test_choose_port_returns_an_open_port():
"choose_port() returns an open port"
"""choose_port() returns an open port"""
app = OnionShare()
# choose a new port
app.choose_port()

View File

@ -19,17 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from onionshare import web
from nose import with_setup
def test_generate_slug_length():
"generates a 26-character slug"
"""generates a 26-character slug"""
assert len(web.slug) == 26
def test_generate_slug_characters():
"generates a base32-encoded slug"
"""generates a base32-encoded slug"""
def is_b32(string):
b32_alphabet = "01234556789abcdefghijklmnopqrstuvwxyz"
return all(char in b32_alphabet for char in string)
assert is_b32(web.slug)

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import tempfile
class MockSubprocess():
def __init__(self):
self.last_call = None
@ -28,6 +29,7 @@ class MockSubprocess():
def last_call_args(self):
return self.last_call
def write_tempfile(text):
tempdir = tempfile.mkdtemp()
path = tempdir + "/test-file.txt"
@ -35,5 +37,3 @@ def write_tempfile(text):
f.write(text)
f.close()
return path