Merge branch 'gui' into ecmendenhall-write-tests

This commit is contained in:
Micah Lee 2014-05-31 19:57:27 -04:00
commit 1485734462
12 changed files with 402 additions and 23 deletions

View File

@ -2,3 +2,4 @@ include LICENSE
include README.md include README.md
include onionshare/*.html include onionshare/*.html
include onionshare/strings.json include onionshare/strings.json
include onionshare_gui/html/*

10
bin/onionshare-gui Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
import sys, os
try:
import onionshare_gui
except ImportError:
sys.path.append(os.path.abspath(os.path.dirname(__file__)+'/..'))
import onionshare_gui
onionshare_gui.main()

View File

@ -18,6 +18,10 @@ from stem.control import Controller
from stem import SocketError from stem import SocketError
from flask import Flask, Markup, Response, request, make_response, send_from_directory, render_template_string from flask import Flask, Markup, Response, request, make_response, send_from_directory, render_template_string
class NoTor(Exception):
pass
app = Flask(__name__) app = Flask(__name__)
strings = {} strings = {}
@ -25,12 +29,20 @@ strings = {}
# generate an unguessable string # generate an unguessable string
slug = os.urandom(16).encode('hex') slug = os.urandom(16).encode('hex')
# file information # information about the file
filename = filehash = filesize = '' filename = filesize = filehash = None
def set_file_info(new_filename, new_filehash, new_filesize):
global filename, filehash, filesize
filename = new_filename
filehash = new_filehash
filesize = new_filesize
@app.route("/{0}".format(slug)) @app.route("/{0}".format(slug))
def index(): def index():
global filename, filesize, filehash, slug, strings global filename, filesize, filehash, slug, strings
print 'filename: {0}'.format(filename)
print 'filehash: {0}'.format(filehash)
print 'filesize: {0}'.format(filesize)
return render_template_string(open('{0}/index.html'.format(os.path.dirname(__file__))).read(), return render_template_string(open('{0}/index.html'.format(os.path.dirname(__file__))).read(),
slug=slug, filename=os.path.basename(filename), filehash=filehash, filesize=filesize, strings=strings) slug=slug, filename=os.path.basename(filename), filehash=filehash, filesize=filesize, strings=strings)
@ -79,22 +91,10 @@ def load_strings(default="en"):
lang = lc[:2] lang = lc[:2]
if lang in translated: if lang in translated:
strings = translated[lang] strings = translated[lang]
return strings
def main(): def file_crunching(filename):
global filename, filehash, filesize
load_strings()
# validate filename
if len(sys.argv) != 2:
sys.exit('Usage: {0} [filename]'.format(sys.argv[0]));
filename = sys.argv[1]
if not os.path.isfile(filename):
sys.exit(strings["not_a_file"].format(filename))
else:
filename = os.path.abspath(filename)
# calculate filehash, file size # calculate filehash, file size
print strings["calculating_sha1"]
BLOCKSIZE = 65536 BLOCKSIZE = 65536
hasher = hashlib.sha1() hasher = hashlib.sha1()
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
@ -104,15 +104,18 @@ def main():
buf = f.read(BLOCKSIZE) buf = f.read(BLOCKSIZE)
filehash = hasher.hexdigest() filehash = hasher.hexdigest()
filesize = os.path.getsize(filename) filesize = os.path.getsize(filename)
return filehash, filesize
def choose_port():
# let the OS choose a port # let the OS choose a port
tmpsock = socket.socket() tmpsock = socket.socket()
tmpsock.bind(("127.0.0.1", 0)) tmpsock.bind(("127.0.0.1", 0))
port = tmpsock.getsockname()[1] port = tmpsock.getsockname()[1]
tmpsock.close() tmpsock.close()
return port
def start_hidden_service(port):
# connect to the tor controlport # connect to the tor controlport
print strings["connecting_ctrlport"].format(port)
controlports = [9051, 9151] controlports = [9051, 9151]
controller = False controller = False
for controlport in controlports: for controlport in controlports:
@ -121,7 +124,7 @@ def main():
except SocketError: except SocketError:
pass pass
if not controller: if not controller:
sys.exit(strings["cant_connect_ctrlport"].format(controlports)) raise NoTor(strings["cant_connect_ctrlport"].format(controlports))
controller.authenticate() controller.authenticate()
# set up hidden service # set up hidden service
@ -130,11 +133,33 @@ def main():
('HiddenServicePort', '80 127.0.0.1:{0}'.format(port)) ('HiddenServicePort', '80 127.0.0.1:{0}'.format(port))
]) ])
onion_host = get_hidden_service_hostname(port) onion_host = get_hidden_service_hostname(port)
return onion_host
# punch a hole in the firewall def main():
load_strings()
# try starting hidden service
port = choose_port()
print strings["connecting_ctrlport"].format(port)
try:
onion_host = start_hidden_service(port)
except NoTor as e:
sys.exit(e.args[0])
# select file to share
if len(sys.argv) != 2:
sys.exit('Usage: {0} [filename]'.format(sys.argv[0]));
filename = sys.argv[1]
if not os.path.isfile(filename):
sys.exit(strings["not_a_file"].format(filename))
else:
filename = os.path.abspath(filename)
# startup
print strings["calculating_sha1"]
filehash, filesize = file_crunching(filename)
set_file_info(filename, filehash, filesize)
tails_open_port(port) tails_open_port(port)
# instructions
print '\n' + strings["give_this_url"] print '\n' + strings["give_this_url"]
print 'http://{0}/{1}'.format(onion_host, slug) print 'http://{0}/{1}'.format(onion_host, slug)
print '' print ''

View File

@ -0,0 +1 @@
from onionshare_gui import *

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="style.css" />
</head>
<body>
<h1><span id="basename"></span></h1>
<div id="output"></div>
<button class="button" id="copy-button">Copy URL</button>
<script src="jquery-1.11.1.min.js"></script>
<script src="onionshare.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,24 @@
function send(msg) {
document.title = "null";
document.title = msg;
}
function init(basename, strings) {
$('#basename').html(basename).show();
}
function url_is_set() {
$('#copy-button')
.click(function(){
send('copy_url');
})
.show();
}
function update(msg) {
var $line = $('<p></p>').append(msg);
$('#output').append($line);
// scroll to bottom
$('#output').scrollTop($('#output').height());
}

View File

@ -0,0 +1,86 @@
body {
background-color: #ffffff;
color: #000000;
font-family: arial;
width: 550px;
height: 300px;
margin: 0 auto;
padding: 0;
}
h1 {
background-color: #222222;
color: #ffffff;
text-align: center;
font-size: 20px;
margin: 0;
padding: 10px;
height: 20px;
}
p {
padding: 0;
margin: 0 0 .3em 0;
}
#output {
padding: 10px;
word-wrap: break-word;
height: 260px;
overflow: auto;
font-family: monospace;
}
.button {
-moz-box-shadow:inset 0px 1px 0px 0px #f29c93;
-webkit-box-shadow:inset 0px 1px 0px 0px #f29c93;
box-shadow:inset 0px 1px 0px 0px #f29c93;
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #fe1a00), color-stop(1, #ce0100) );
background:-moz-linear-gradient( center top, #fe1a00 5%, #ce0100 100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fe1a00', endColorstr='#ce0100');
background-color:#fe1a00;
-webkit-border-top-left-radius:0px;
-moz-border-radius-topleft:0px;
border-top-left-radius:0px;
-webkit-border-top-right-radius:0px;
-moz-border-radius-topright:0px;
border-top-right-radius:0px;
-webkit-border-bottom-right-radius:0px;
-moz-border-radius-bottomright:0px;
border-bottom-right-radius:0px;
-webkit-border-bottom-left-radius:0px;
-moz-border-radius-bottomleft:0px;
border-bottom-left-radius:0px;
text-indent:0;
border:1px solid #d83526;
display:inline-block;
color:#ffffff;
font-family:Arial;
font-size:13px;
font-weight:bold;
font-style:normal;
text-decoration:none;
text-align:center;
text-shadow:1px 1px 0px #b23e35;
}
.button:hover {
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ce0100), color-stop(1, #fe1a00) );
background:-moz-linear-gradient( center top, #ce0100 5%, #fe1a00 100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ce0100', endColorstr='#fe1a00');
background-color:#ce0100;
}
#copy-button {
display: none;
position: absolute;
right: 20px;
bottom: 10px;
}
.loader {
width: 21px;
height: 20px;
background-image: url('loader.gif');
background-position: top left;
background-repeat: no-repeat;
}

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python
import onionshare, webgui
import os, sys, time, json, gtk, gobject, thread
url = None
class Global(object):
quit = False
@classmethod
def set_quit(cls, *args, **kwargs):
cls.quit = True
def alert(msg, type=gtk.MESSAGE_INFO):
dialog = gtk.MessageDialog(
parent=None,
flags=gtk.DIALOG_MODAL,
type=type,
buttons=gtk.BUTTONS_OK,
message_format=msg)
response = dialog.run()
dialog.destroy()
def select_file(strings):
# get filename, either from argument or file chooser dialog
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
canceled = False
chooser = gtk.FileChooserDialog(
title="Choose a file to share",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
response = chooser.run()
if response == gtk.RESPONSE_OK:
filename = chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL:
canceled = True
chooser.destroy()
if canceled:
return False, False
# validate filename
if not os.path.isfile(filename):
alert(strings["not_a_file"].format(filename), gtk.MESSAGE_ERROR)
return False, False
filename = os.path.abspath(filename)
basename = os.path.basename(filename)
return filename, basename
def main():
global url
strings = onionshare.load_strings()
# try starting hidden service
port = onionshare.choose_port()
try:
onion_host = onionshare.start_hidden_service(port)
except onionshare.NoTor as e:
alert(e.args[0], gtk.MESSAGE_ERROR)
return
onionshare.tails_open_port(port)
# select file to share
filename, basename = select_file(strings)
if not filename:
return
# open the window, launching webkit browser
webgui.start_gtk_thread()
browser, web_recv, web_send = webgui.sync_gtk_msg(webgui.launch_window)(
title="OnionShare | {0}".format(basename),
quit_function=Global.set_quit,
echo=False)
# clipboard
clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
def set_clipboard():
global url
clipboard.set_text(url)
web_send("update('{0}')".format('Copied secret URL to clipboard.'))
# the async nature of things requires startup to be split into multiple functions
def startup_async():
global url
filehash, filesize = onionshare.file_crunching(filename)
onionshare.set_file_info(filename, filehash, filesize)
url = 'http://{0}/{1}'.format(onion_host, onionshare.slug)
web_send("update('{0}')".format(strings['give_this_url'].replace('\'', '\\\'')))
web_send("update('<strong>{0}</strong>')".format(url))
web_send("url_is_set()")
# clipboard needs a bit of time before copying url
gobject.timeout_add(500, set_clipboard)
def startup_sync():
web_send("init('{0}', {1});".format(basename, json.dumps(strings)))
web_send("update('{0}')".format(strings['calculating_sha1']))
# run other startup in the background
thread_crunch = thread.start_new_thread(startup_async, ())
# start the web server
thread_web = thread.start_new_thread(onionshare.app.run, (), {"port": port})
gobject.timeout_add(100, startup_sync)
# main loop
last_second = time.time()
uptime_seconds = 1
clicks = 0
while not Global.quit:
current_time = time.time()
again = False
msg = web_recv()
if msg:
again = True
# check msg for messages from the browser
if msg == 'copy_url':
set_clipboard()
if not again:
time.sleep(0.1)
# shutdown
onionshare.tails_close_port(port)
if __name__ == '__main__':
main()

81
onionshare_gui/webgui.py Normal file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
import time, Queue, thread, gtk, gobject, os, webkit
def async_gtk_msg(fun):
def worker((function, args, kwargs)):
apply(function, args, kwargs)
def fun2(*args, **kwargs):
gobject.idle_add(worker, (fun, args, kwargs))
return fun2
def sync_gtk_msg(fun):
class NoResult: pass
def worker((R, function, args, kwargs)):
R.result = apply(function, args, kwargs)
def fun2(*args, **kwargs):
class R: result = NoResult
gobject.idle_add(worker, (R, fun, args, kwargs))
while R.result is NoResult: time.sleep(0.01)
return R.result
return fun2
def launch_window(title='OnionShare', quit_function=None, echo=True):
window = gtk.Window()
window.set_title(title)
browser = webkit.WebView()
box = gtk.VBox(homogeneous=False, spacing=0)
window.add(box)
if quit_function is not None:
window.connect('destroy', quit_function)
box.pack_start(browser, expand=True, fill=True, padding=0)
window.set_default_size(600, 600)
window.set_resizable(False)
window.show_all()
message_queue = Queue.Queue()
def callback_wrapper(widget, frame, title):
if title != 'null':
message_queue.put(title)
browser.connect('title-changed', callback_wrapper)
browser.open('file://'+os.path.abspath(os.path.dirname(__file__))+'/html/index.html')
def web_recv():
if message_queue.empty():
return None
else:
msg = message_queue.get()
if echo: print '>>>', msg
return msg
def web_send(msg):
if echo: print '<<<', msg
async_gtk_msg(browser.execute_script)(msg)
return browser, web_recv, web_send
def start_gtk_thread():
# Start GTK in its own thread:
gtk.gdk.threads_init()
thread.start_new_thread(gtk.main, ())
def kill_gtk_thread():
async_gtk_msg(gtk.main_quit)()
def main():
if not select_file():
return
launch_browser()

View File

@ -27,6 +27,6 @@ setup(
], ],
license="GPL v3", license="GPL v3",
keywords='onion, share, onionshare, tor, anonymous, web server', keywords='onion, share, onionshare, tor, anonymous, web server',
packages=['onionshare'], packages=['onionshare', 'onionshare_gui'],
scripts=['bin/onionshare'] scripts=['bin/onionshare', 'bin/onionshare-gui']
) )