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

View File

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

View File

@ -25,18 +25,25 @@ from stem import SocketError
import strings, helpers, web import strings, helpers, web
class NoTor(Exception): pass
class TailsError(Exception): pass class NoTor(Exception):
pass
class TailsError(Exception):
pass
def hsdic2list(dic): 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 [ return [
pair for pairs in [ pair for pairs in [
[('HiddenServiceDir',vals[0]),('HiddenServicePort',vals[1])] [('HiddenServiceDir', vals[0]), ('HiddenServicePort', vals[1])]
for vals in zip(dic.get('HiddenServiceDir',[]),dic.get('HiddenServicePort',[])) for vals in zip(dic.get('HiddenServiceDir', []), dic.get('HiddenServicePort', []))
] for pair in pairs ] for pair in pairs
] ]
class OnionShare(object): class OnionShare(object):
def __init__(self, debug=False, local_only=False, stay_open=False): def __init__(self, debug=False, local_only=False, stay_open=False):
self.port = None self.port = None
@ -56,7 +63,6 @@ class OnionShare(object):
# files and dirs to delete on shutdown # files and dirs to delete on shutdown
self.cleanup_filenames = [] self.cleanup_filenames = []
def cleanup(self): def cleanup(self):
if self.controller: if self.controller:
# Get fresh hidden services (maybe changed since last time) # Get fresh hidden services (maybe changed since last time)
@ -64,7 +70,7 @@ class OnionShare(object):
hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
'HiddenServiceDir': [], 'HiddenServicePort': [] 'HiddenServiceDir': [], 'HiddenServicePort': []
} }
if self.hidserv_dir and self.hidserv_dir in hsdic.get('HiddenServiceDir',[]): if self.hidserv_dir and self.hidserv_dir in hsdic.get('HiddenServiceDir', []):
dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
del hsdic['HiddenServiceDir'][dropme] del hsdic['HiddenServiceDir'][dropme]
del hsdic['HiddenServicePort'][dropme] del hsdic['HiddenServicePort'][dropme]
@ -96,7 +102,7 @@ class OnionShare(object):
else: else:
args = ['/usr/bin/sudo', '--', '/usr/bin/onionshare'] args = ['/usr/bin/sudo', '--', '/usr/bin/onionshare']
p = subprocess.Popen(args+[str(self.port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE) p = subprocess.Popen(args+[str(self.port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
stdout = p.stdout.read(22) # .onion URLs are 22 chars long stdout = p.stdout.read(22) # .onion URLs are 22 chars long
if stdout: if stdout:
self.onion_host = stdout self.onion_host = stdout
@ -132,14 +138,14 @@ class OnionShare(object):
hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
'HiddenServiceDir': [], 'HiddenServicePort': [] 'HiddenServiceDir': [], 'HiddenServicePort': []
} }
if self.hidserv_dir in hsdic.get('HiddenServiceDir',[]): if self.hidserv_dir in hsdic.get('HiddenServiceDir', []):
# Maybe a stale service with the wrong local port # Maybe a stale service with the wrong local port
dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
del hsdic['HiddenServiceDir'][dropme] del hsdic['HiddenServiceDir'][dropme]
del hsdic['HiddenServicePort'][dropme] del hsdic['HiddenServicePort'][dropme]
hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir',[])+[self.hidserv_dir] hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir]
hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort',[])+[ hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[
'80 127.0.0.1:{0}'.format(self.port) ] '80 127.0.0.1:{0}'.format(self.port)]
self.controller.set_options(hsdic2list(hsdic)) self.controller.set_options(hsdic2list(hsdic))
@ -181,16 +187,17 @@ class OnionShare(object):
ready = True ready = True
sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_yup'))) sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_yup')))
except socks.SOCKS5Error: # non-Tails error except socks.SOCKS5Error: # non-Tails error
sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() sys.stdout.flush()
except urllib2.HTTPError: # Tails error except urllib2.HTTPError: # Tails error
sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() sys.stdout.flush()
except KeyboardInterrupt: except KeyboardInterrupt:
return False return False
return True return True
def tails_root(): def tails_root():
# if running in Tails and as root, do only the things that require root # if running in Tails and as root, do only the things that require root
if helpers.get_platform() == 'Tails' and helpers.is_root(): if helpers.get_platform() == 'Tails' and helpers.is_root():
@ -205,7 +212,8 @@ def tails_root():
sys.exit(-1) sys.exit(-1)
# open hole in firewall # 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 # start hidden service
app = OnionShare() app = OnionShare()
@ -217,8 +225,10 @@ def tails_root():
# close hole in firewall on shutdown # close hole in firewall on shutdown
import signal import signal
def handler(signum = None, frame = None):
subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo', '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT']) def handler(signum=None, frame=None):
subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo',
'-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
sys.exit() sys.exit()
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]:
signal.signal(sig, handler) signal.signal(sig, handler)
@ -227,6 +237,7 @@ def tails_root():
while True: while True:
time.sleep(1) time.sleep(1)
def main(): def main():
strings.load_strings() strings.load_strings()
tails_root() tails_root()
@ -273,7 +284,7 @@ def main():
app.cleanup_filenames.append(web.zip_filename) app.cleanup_filenames.append(web.zip_filename)
# warn about sending large files over Tor # warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb if web.zip_filesize >= 157286400: # 150mb
print '' print ''
print strings._("large_filesize") print strings._("large_filesize")
print '' print ''
@ -283,7 +294,7 @@ def main():
t.daemon = True t.daemon = True
t.start() t.start()
try: # Trap Ctrl-C try: # Trap Ctrl-C
# wait for hs # wait for hs
ready = app.wait_for_hs() ready = app.wait_for_hs()
if not ready: if not ready:

View File

@ -65,6 +65,7 @@ PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"}
_orgsocket = _orig_socket = socket.socket _orgsocket = _orig_socket = socket.socket
class ProxyError(IOError): class ProxyError(IOError):
""" """
socket_err contains original socket.error exception. socket_err contains original socket.error exception.
@ -79,32 +80,54 @@ class ProxyError(IOError):
def __str__(self): def __str__(self):
return self.msg 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):
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client", pass
0x5D: "Request rejected because the client program and identd report different user-ids"
}
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"
}
DEFAULT_PORTS = { SOCKS4: 1080, class ProxyConnectionError(ProxyError):
SOCKS5: 1080, pass
HTTP: 8080
}
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",
}
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",
}
DEFAULT_PORTS = {
SOCKS4: 1080,
SOCKS5: 1080,
HTTP: 8080,
}
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None): def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
""" """
@ -119,6 +142,7 @@ def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username
setdefaultproxy = set_default_proxy setdefaultproxy = set_default_proxy
def get_default_proxy(): def get_default_proxy():
""" """
Returns the default proxy, set by set_default_proxy. Returns the default proxy, set by set_default_proxy.
@ -127,6 +151,7 @@ def get_default_proxy():
getdefaultproxy = get_default_proxy getdefaultproxy = get_default_proxy
def wrap_module(module): def wrap_module(module):
""" """
Attempts to replace a module's socket library with a SOCKS socket. Must set Attempts to replace a module's socket library with a SOCKS socket. Must set
@ -141,7 +166,8 @@ def wrap_module(module):
wrapmodule = wrap_module wrapmodule = wrap_module
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
proxy_port=None, proxy_username=None, proxy_port=None, proxy_username=None,
proxy_password=None, timeout=None): proxy_password=None, timeout=None):
"""create_connection(dest_pair, **proxy_args) -> socket object """create_connection(dest_pair, **proxy_args) -> socket object
@ -161,6 +187,7 @@ def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
sock.connect(dest_pair) sock.connect(dest_pair)
return sock return sock
class socksocket(socket.socket): class socksocket(socket.socket):
"""socksocket([family[, type[, proto]]]) -> socket object """socksocket([family[, type[, proto]]]) -> socket object
@ -181,10 +208,11 @@ class socksocket(socket.socket):
self.proxy_sockname = None self.proxy_sockname = None
self.proxy_peername = None self.proxy_peername = None
self.proxy_negotiators = { SOCKS4: self._negotiate_SOCKS4, self.proxy_negotiators = {
SOCKS5: self._negotiate_SOCKS5, SOCKS4: self._negotiate_SOCKS4,
HTTP: self._negotiate_HTTP SOCKS5: self._negotiate_SOCKS5,
} HTTP: self._negotiate_HTTP,
}
def _recvall(self, count): def _recvall(self, count):
""" """
@ -446,7 +474,6 @@ class socksocket(socket.socket):
self.proxy_sockname = (b"0.0.0.0", 0) self.proxy_sockname = (b"0.0.0.0", 0)
self.proxy_peername = addr, dest_port self.proxy_peername = addr, dest_port
def connect(self, dest_pair): def connect(self, dest_pair):
""" """
Connects to the specified destination through a proxy. Connects to the specified destination through a proxy.
@ -465,7 +492,6 @@ class socksocket(socket.socket):
or not isinstance(dest_port, int)): or not isinstance(dest_port, int)):
raise GeneralProxyError("Invalid destination-connection (host, port) pair") raise GeneralProxyError("Invalid destination-connection (host, port) pair")
if proxy_type is None: if proxy_type is None:
# Treat like regular socket object # Treat like regular socket object
_orig_socket.connect(self, (dest_addr, dest_port)) _orig_socket.connect(self, (dest_addr, dest_port))

View File

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

View File

@ -28,11 +28,13 @@ app = Flask(__name__)
file_info = [] file_info = []
zip_filename = None zip_filename = None
zip_filesize = None zip_filesize = None
def set_file_info(filenames): def set_file_info(filenames):
global file_info, zip_filename, zip_filesize global file_info, zip_filename, zip_filesize
# build file info list # build file info list
file_info = {'files':[], 'dirs':[]} file_info = {'files': [], 'dirs': []}
for filename in filenames: for filename in filenames:
info = { info = {
'filename': filename, 'filename': filename,
@ -59,6 +61,7 @@ def set_file_info(filenames):
zip_filename = z.zip_filename zip_filename = z.zip_filename
zip_filesize = os.path.getsize(zip_filename) zip_filesize = os.path.getsize(zip_filename)
REQUEST_LOAD = 0 REQUEST_LOAD = 0
REQUEST_DOWNLOAD = 1 REQUEST_DOWNLOAD = 1
REQUEST_PROGRESS = 2 REQUEST_PROGRESS = 2
@ -66,24 +69,31 @@ REQUEST_OTHER = 3
REQUEST_CANCELED = 4 REQUEST_CANCELED = 4
q = Queue.Queue() q = Queue.Queue()
def add_request(type, path, data=None): def add_request(type, path, data=None):
global q global q
q.put({ q.put({
'type': type, 'type': type,
'path': path, 'path': path,
'data': data 'data': data
}) })
slug = helpers.random_string(16) slug = helpers.random_string(16)
download_count = 0 download_count = 0
stay_open = False stay_open = False
def set_stay_open(new_stay_open): def set_stay_open(new_stay_open):
global stay_open global stay_open
stay_open = new_stay_open stay_open = new_stay_open
def get_stay_open(): def get_stay_open():
return stay_open return stay_open
def debug_mode(): def debug_mode():
import logging import logging
@ -96,6 +106,7 @@ def debug_mode():
log_handler.setLevel(logging.WARNING) log_handler.setLevel(logging.WARNING)
app.logger.addHandler(log_handler) app.logger.addHandler(log_handler)
@app.route("/<slug_candidate>") @app.route("/<slug_candidate>")
def index(slug_candidate): def index(slug_candidate):
if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')): if not helpers.constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
@ -112,6 +123,7 @@ def index(slug_candidate):
strings=strings.strings strings=strings.strings
) )
@app.route("/<slug_candidate>/download") @app.route("/<slug_candidate>/download")
def download(slug_candidate): def download(slug_candidate):
global download_count global download_count
@ -128,13 +140,13 @@ def download(slug_candidate):
path = request.path path = request.path
# tell GUI the download started # tell GUI the download started
add_request(REQUEST_DOWNLOAD, path, { 'id':download_id }) add_request(REQUEST_DOWNLOAD, path, {'id': download_id})
dirname = os.path.dirname(zip_filename) dirname = os.path.dirname(zip_filename)
basename = os.path.basename(zip_filename) basename = os.path.basename(zip_filename)
def generate(): def generate():
chunk_size = 102400 # 100kb chunk_size = 102400 # 100kb
fp = open(zip_filename, 'rb') fp = open(zip_filename, 'rb')
done = False done = False
@ -150,16 +162,17 @@ def download(slug_candidate):
# tell GUI the progress # tell GUI the progress
downloaded_bytes = fp.tell() downloaded_bytes = fp.tell()
percent = round((1.0 * downloaded_bytes / zip_filesize) * 100, 2) 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() sys.stdout.flush()
add_request(REQUEST_PROGRESS, path, { 'id':download_id, 'bytes':downloaded_bytes }) add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
except: except:
# looks like the download was canceled # looks like the download was canceled
done = True done = True
canceled = True canceled = True
# tell the GUI the download has canceled # tell the GUI the download has canceled
add_request(REQUEST_CANCELED, path, { 'id':download_id }) add_request(REQUEST_CANCELED, path, {'id': download_id})
fp.close() fp.close()
sys.stdout.write("\n") sys.stdout.write("\n")
@ -181,6 +194,7 @@ def download(slug_candidate):
r.headers.add('Content-Type', content_type) r.headers.add('Content-Type', content_type)
return r return r
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
add_request(REQUEST_OTHER, request.path) 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 # 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) shutdown_slug = helpers.random_string(16)
@app.route("/<shutdown_slug_candidate>/shutdown") @app.route("/<shutdown_slug_candidate>/shutdown")
def shutdown(shutdown_slug_candidate): def shutdown(shutdown_slug_candidate):
if not helpers.constant_time_compare(shutdown_slug.encode('ascii'), shutdown_slug_candidate.encode('ascii')): 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 "" return ""
def start(port, stay_open=False): def start(port, stay_open=False):
set_stay_open(stay_open) set_stay_open(stay_open)
app.run(port=port, threaded=True) app.run(port=port, threaded=True)
def stop(port): def stop(port):
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown # to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
if helpers.get_platform() == 'Tails': if helpers.get_platform() == 'Tails':
# in Tails everything is proxies over Tor, so we need to get lower level # in Tails everything is proxies over Tor, so we need to get lower level
# to connect not over the proxy # to connect not over the proxy
import socket import socket
s = socket.socket() s = socket.socket()
s.connect(('127.0.0.1', port)) s.connect(('127.0.0.1', port))
s.sendall('GET /{0}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug)) 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 import os, sys, inspect, platform
def get_onionshare_gui_dir(): def get_onionshare_gui_dir():
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
onionshare_gui_dir = os.path.dirname(__file__) onionshare_gui_dir = os.path.dirname(__file__)
@ -28,6 +29,7 @@ def get_onionshare_gui_dir():
onionshare_gui_dir = get_onionshare_gui_dir() onionshare_gui_dir = get_onionshare_gui_dir()
def get_image_path(filename): def get_image_path(filename):
if platform.system() == 'Linux': if platform.system() == 'Linux':
prefix = os.path.join(sys.prefix, 'share/onionshare/images') prefix = os.path.join(sys.prefix, 'share/onionshare/images')

View File

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

View File

@ -23,6 +23,7 @@ from PyQt4 import QtCore, QtGui
import common import common
from onionshare import strings, helpers from onionshare import strings, helpers
class FileList(QtGui.QListWidget): class FileList(QtGui.QListWidget):
files_dropped = QtCore.pyqtSignal() files_dropped = QtCore.pyqtSignal()
files_updated = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal()
@ -129,7 +130,7 @@ class FileList(QtGui.QListWidget):
thresh = 1024.0 thresh = 1024.0
if b < thresh: if b < thresh:
return '{0} B'.format(b) return '{0} B'.format(b)
units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'] units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
u = 0 u = 0
b /= thresh b /= thresh
while b >= thresh: while b >= thresh:
@ -137,6 +138,7 @@ class FileList(QtGui.QListWidget):
u += 1 u += 1
return '{0} {1}'.format(round(b, 1), units[u]) return '{0} {1}'.format(round(b, 1), units[u])
class FileSelection(QtGui.QVBoxLayout): class FileSelection(QtGui.QVBoxLayout):
def __init__(self): def __init__(self):
super(FileSelection, self).__init__() super(FileSelection, self).__init__()
@ -186,14 +188,16 @@ class FileSelection(QtGui.QVBoxLayout):
self.file_list.update() self.file_list.update()
def add_files(self): 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: if filenames:
for filename in filenames: for filename in filenames:
self.file_list.add_file(str(filename)) self.file_list.add_file(str(filename))
self.update() self.update()
def add_dir(self): 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: if filename:
self.file_list.add_file(str(filename)) self.file_list.add_file(str(filename))
self.update() self.update()

View File

@ -35,6 +35,7 @@ from server_status import ServerStatus
from downloads import Downloads from downloads import Downloads
from options import Options from options import Options
class Application(QtGui.QApplication): class Application(QtGui.QApplication):
def __init__(self): def __init__(self):
platform = helpers.get_platform() platform = helpers.get_platform()
@ -42,6 +43,7 @@ class Application(QtGui.QApplication):
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtGui.QApplication.__init__(self, sys.argv) QtGui.QApplication.__init__(self, sys.argv)
class OnionShareGui(QtGui.QWidget): class OnionShareGui(QtGui.QWidget):
start_server_finished = QtCore.pyqtSignal() start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal()
@ -110,7 +112,7 @@ class OnionShareGui(QtGui.QWidget):
self.status_bar.showMessage(strings._('gui_starting_server3', True)) self.status_bar.showMessage(strings._('gui_starting_server3', True))
# warn about sending large files over Tor # warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb if web.zip_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show() self.filesize_warning.show()
@ -151,7 +153,7 @@ class OnionShareGui(QtGui.QWidget):
self.start_server_finished.emit() self.start_server_finished.emit()
self.status_bar.showMessage(strings._('gui_starting_server2', True)) self.status_bar.showMessage(strings._('gui_starting_server2', True))
t = threading.Thread(target=finish_starting_server, kwargs={'self':self}) t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True t.daemon = True
t.start() t.start()
@ -206,6 +208,7 @@ class OnionShareGui(QtGui.QWidget):
def clear_message(self): def clear_message(self):
self.status_bar.clearMessage() self.status_bar.clearMessage()
def alert(msg, icon=QtGui.QMessageBox.NoIcon): def alert(msg, icon=QtGui.QMessageBox.NoIcon):
dialog = QtGui.QMessageBox() dialog = QtGui.QMessageBox()
dialog.setWindowTitle("OnionShare") dialog.setWindowTitle("OnionShare")
@ -214,6 +217,7 @@ def alert(msg, icon=QtGui.QMessageBox.NoIcon):
dialog.setIcon(icon) dialog.setIcon(icon)
dialog.exec_() dialog.exec_()
def main(): def main():
strings.load_strings() strings.load_strings()

View File

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

View File

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

View File

@ -27,6 +27,7 @@ try:
except ImportError: except ImportError:
from distutils.core import setup from distutils.core import setup
def file_list(path): def file_list(path):
files = [] files = []
for filename in os.listdir(path): for filename in os.listdir(path):
@ -37,11 +38,23 @@ def file_list(path):
version = open('version').read().strip() 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( setup(
name='onionshare', name='onionshare',
version=version, 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.', description=description,
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.""", long_description=long_description,
author='Micah Lee', author='Micah Lee',
author_email='micah@micahflee.com', author_email='micah@micahflee.com',
url='https://github.com/micahflee/onionshare', 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from __future__ import division 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 from itertools import izip
import stem, stem.control, flask import stem, stem.control, flask
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui

View File

@ -21,13 +21,15 @@ from nose import with_setup
import test_helpers import test_helpers
def test_get_platform_on_tails(): def test_get_platform_on_tails():
"get_platform() returns 'Tails' when hostname is 'amnesia'" """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', '') 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' assert helpers.get_platform() == 'Tails'
def test_get_platform_returns_platform_system(): 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' helpers.platform.system = lambda: 'Sega Saturn'
assert helpers.get_platform() == 'Sega Saturn' assert helpers.get_platform() == 'Sega Saturn'

View File

@ -21,19 +21,21 @@ import locale
from onionshare import strings from onionshare import strings
from nose import with_setup from nose import with_setup
def test_starts_with_empty_strings(): def test_starts_with_empty_strings():
"creates an empty strings dict by default" """creates an empty strings dict by default"""
assert strings.strings == {} assert strings.strings == {}
def test_load_strings_defaults_to_english(): 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') locale.getdefaultlocale = lambda: ('en_US', 'UTF-8')
strings.load_strings() strings.load_strings()
assert strings._('wait_for_hs') == "Waiting for HS to be ready:" assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
def test_load_strings_loads_other_languages(): 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') locale.getdefaultlocale = lambda: ('fr_FR', 'UTF-8')
strings.load_strings("fr") strings.load_strings("fr")
assert strings._('wait_for_hs') == "En attente du HS:" assert strings._('wait_for_hs') == "En attente du HS:"

View File

@ -20,14 +20,16 @@ import socket
from onionshare import OnionShare from onionshare import OnionShare
from nose import with_setup from nose import with_setup
def test_choose_port_returns_a_port_number(): def test_choose_port_returns_a_port_number():
"choose_port() returns a port number" """choose_port() returns a port number"""
app = OnionShare() app = OnionShare()
app.choose_port() app.choose_port()
assert 1024 <= app.port <= 65535 assert 1024 <= app.port <= 65535
def test_choose_port_returns_an_open_port(): def test_choose_port_returns_an_open_port():
"choose_port() returns an open port" """choose_port() returns an open port"""
app = OnionShare() app = OnionShare()
# choose a new port # choose a new port
app.choose_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 onionshare import web
from nose import with_setup from nose import with_setup
def test_generate_slug_length(): def test_generate_slug_length():
"generates a 26-character slug" """generates a 26-character slug"""
assert len(web.slug) == 26 assert len(web.slug) == 26
def test_generate_slug_characters(): def test_generate_slug_characters():
"generates a base32-encoded slug" """generates a base32-encoded slug"""
def is_b32(string): def is_b32(string):
b32_alphabet = "01234556789abcdefghijklmnopqrstuvwxyz" b32_alphabet = "01234556789abcdefghijklmnopqrstuvwxyz"
return all(char in b32_alphabet for char in string) return all(char in b32_alphabet for char in string)
assert is_b32(web.slug) assert is_b32(web.slug)

View File

@ -18,22 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
import tempfile import tempfile
class MockSubprocess(): class MockSubprocess():
def __init__(self): def __init__(self):
self.last_call = None self.last_call = None
def call(self, args): def call(self, args):
self.last_call = args self.last_call = args
def last_call_args(self):
return self.last_call
def last_call_args(self):
return self.last_call
def write_tempfile(text): def write_tempfile(text):
tempdir = tempfile.mkdtemp() tempdir = tempfile.mkdtemp()
path = tempdir + "/test-file.txt" path = tempdir + "/test-file.txt"
with open(path, "w") as f: with open(path, "w") as f:
f.write(text) f.write(text)
f.close() f.close()
return path return path