mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
commit
d0bdb9143f
@ -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:
|
||||
|
@ -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,19 +57,21 @@ 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]
|
||||
s = base64.b32encode(h).lower().replace('=','')
|
||||
s = base64.b32encode(h).lower().replace('=', '')
|
||||
if not output_len:
|
||||
return s
|
||||
return s[:output_len]
|
||||
|
||||
|
||||
def human_readable_filesize(b):
|
||||
thresh = 1024.0
|
||||
if b < thresh:
|
||||
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
|
||||
b /= thresh
|
||||
while 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()
|
||||
|
||||
|
@ -25,18 +25,25 @@ 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])]
|
||||
for vals in zip(dic.get('HiddenServiceDir',[]),dic.get('HiddenServicePort',[]))
|
||||
[('HiddenServiceDir', vals[0]), ('HiddenServicePort', vals[1])]
|
||||
for vals in zip(dic.get('HiddenServiceDir', []), dic.get('HiddenServicePort', []))
|
||||
] 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)
|
||||
@ -64,7 +70,7 @@ class OnionShare(object):
|
||||
hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
|
||||
'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)
|
||||
del hsdic['HiddenServiceDir'][dropme]
|
||||
del hsdic['HiddenServicePort'][dropme]
|
||||
@ -96,7 +102,7 @@ class OnionShare(object):
|
||||
else:
|
||||
args = ['/usr/bin/sudo', '--', '/usr/bin/onionshare']
|
||||
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:
|
||||
self.onion_host = stdout
|
||||
@ -132,14 +138,14 @@ class OnionShare(object):
|
||||
hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
|
||||
'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
|
||||
dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
|
||||
del hsdic['HiddenServiceDir'][dropme]
|
||||
del hsdic['HiddenServicePort'][dropme]
|
||||
hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir',[])+[self.hidserv_dir]
|
||||
hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort',[])+[
|
||||
'80 127.0.0.1:{0}'.format(self.port) ]
|
||||
hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir]
|
||||
hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[
|
||||
'80 127.0.0.1:{0}'.format(self.port)]
|
||||
|
||||
self.controller.set_options(hsdic2list(hsdic))
|
||||
|
||||
@ -181,16 +187,17 @@ class OnionShare(object):
|
||||
ready = True
|
||||
|
||||
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.flush()
|
||||
except urllib2.HTTPError: # Tails error
|
||||
except urllib2.HTTPError: # Tails error
|
||||
sys.stdout.write('{0}\n'.format(strings._('wait_for_hs_nope')))
|
||||
sys.stdout.flush()
|
||||
except KeyboardInterrupt:
|
||||
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'])
|
||||
|
||||
def handler(signum=None, frame=None):
|
||||
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()
|
||||
@ -273,7 +284,7 @@ def main():
|
||||
app.cleanup_filenames.append(web.zip_filename)
|
||||
|
||||
# warn about sending large files over Tor
|
||||
if web.zip_filesize >= 157286400: # 150mb
|
||||
if web.zip_filesize >= 157286400: # 150mb
|
||||
print ''
|
||||
print strings._("large_filesize")
|
||||
print ''
|
||||
@ -283,7 +294,7 @@ def main():
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
try: # Trap Ctrl-C
|
||||
try: # Trap Ctrl-C
|
||||
# wait for hs
|
||||
ready = app.wait_for_hs()
|
||||
if not ready:
|
||||
|
@ -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,32 +80,54 @@ 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",
|
||||
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"
|
||||
}
|
||||
class GeneralProxyError(ProxyError):
|
||||
pass
|
||||
|
||||
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
|
||||
}
|
||||
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",
|
||||
}
|
||||
|
||||
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):
|
||||
"""
|
||||
@ -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,7 +166,8 @@ def wrap_module(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_password=None, timeout=None):
|
||||
"""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)
|
||||
return sock
|
||||
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
|
||||
@ -181,10 +208,11 @@ class socksocket(socket.socket):
|
||||
self.proxy_sockname = None
|
||||
self.proxy_peername = None
|
||||
|
||||
self.proxy_negotiators = { SOCKS4: self._negotiate_SOCKS4,
|
||||
SOCKS5: self._negotiate_SOCKS5,
|
||||
HTTP: self._negotiate_HTTP
|
||||
}
|
||||
self.proxy_negotiators = {
|
||||
SOCKS4: self._negotiate_SOCKS4,
|
||||
SOCKS5: self._negotiate_SOCKS5,
|
||||
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))
|
||||
|
@ -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')
|
||||
|
@ -28,11 +28,13 @@ app = Flask(__name__)
|
||||
file_info = []
|
||||
zip_filename = None
|
||||
zip_filesize = None
|
||||
|
||||
|
||||
def set_file_info(filenames):
|
||||
global file_info, zip_filename, zip_filesize
|
||||
|
||||
# build file info list
|
||||
file_info = {'files':[], 'dirs':[]}
|
||||
file_info = {'files': [], 'dirs': []}
|
||||
for filename in filenames:
|
||||
info = {
|
||||
'filename': filename,
|
||||
@ -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,24 +69,31 @@ REQUEST_OTHER = 3
|
||||
REQUEST_CANCELED = 4
|
||||
q = Queue.Queue()
|
||||
|
||||
|
||||
def add_request(type, path, data=None):
|
||||
global q
|
||||
q.put({
|
||||
'type': type,
|
||||
'path': path,
|
||||
'data': data
|
||||
'type': type,
|
||||
'path': path,
|
||||
'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
|
||||
@ -128,13 +140,13 @@ def download(slug_candidate):
|
||||
path = request.path
|
||||
|
||||
# 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)
|
||||
basename = os.path.basename(zip_filename)
|
||||
|
||||
def generate():
|
||||
chunk_size = 102400 # 100kb
|
||||
chunk_size = 102400 # 100kb
|
||||
|
||||
fp = open(zip_filename, 'rb')
|
||||
done = False
|
||||
@ -150,16 +162,17 @@ 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 })
|
||||
add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
|
||||
except:
|
||||
# looks like the download was canceled
|
||||
done = True
|
||||
canceled = True
|
||||
|
||||
# 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()
|
||||
sys.stdout.write("\n")
|
||||
@ -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))
|
||||
|
@ -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')
|
||||
|
@ -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__()
|
||||
|
@ -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()
|
||||
@ -129,7 +130,7 @@ class FileList(QtGui.QListWidget):
|
||||
thresh = 1024.0
|
||||
if b < thresh:
|
||||
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
|
||||
b /= thresh
|
||||
while b >= thresh:
|
||||
@ -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()
|
||||
|
@ -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()
|
||||
@ -110,7 +112,7 @@ class OnionShareGui(QtGui.QWidget):
|
||||
self.status_bar.showMessage(strings._('gui_starting_server3', True))
|
||||
|
||||
# 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.show()
|
||||
|
||||
@ -151,7 +153,7 @@ class OnionShareGui(QtGui.QWidget):
|
||||
self.start_server_finished.emit()
|
||||
|
||||
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.start()
|
||||
|
||||
@ -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()
|
||||
|
||||
|
@ -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__()
|
||||
|
@ -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()
|
||||
|
17
setup.py
17
setup.py
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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:"
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -18,22 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
|
||||
class MockSubprocess():
|
||||
def __init__(self):
|
||||
self.last_call = None
|
||||
def __init__(self):
|
||||
self.last_call = None
|
||||
|
||||
def call(self, args):
|
||||
self.last_call = args
|
||||
def call(self, 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):
|
||||
tempdir = tempfile.mkdtemp()
|
||||
path = tempdir + "/test-file.txt"
|
||||
with open(path, "w") as f:
|
||||
f.write(text)
|
||||
f.close()
|
||||
f.write(text)
|
||||
f.close()
|
||||
return path
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user