Merge remote-tracking branch 'upstream/master'

This commit is contained in:
twhite 2014-07-21 11:00:04 -04:00
commit c02263230a
11 changed files with 152 additions and 20 deletions

3
git-hooks/README.md Normal file
View File

@ -0,0 +1,3 @@
To use these hooks, cp any of them to onionshare's `.git/hooks`.
* `pre-push` runs the test suite, and will push if the tests pass.

6
git-hooks/pre-push Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
# Pre-push hook. If you want to test with a different version of firefox, put
# the path in the CFX_FIREFOX environment variable.
nosetests test

View File

@ -6,14 +6,20 @@ from functools import wraps
from stem.control import Controller 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, abort
# Flask depends on itsdangerous, which needs constant time string comparison
# for the HMAC values in secure cookies. Since we know itsdangerous is
# available, we just use its function.
from itsdangerous import constant_time_compare
class NoTor(Exception): class NoTor(Exception):
pass pass
def random_string(num_bytes): def random_string(num_bytes):
b = os.urandom(num_bytes) b = os.urandom(num_bytes)
return base64.b32encode(b).lower().replace('=','') h = hashlib.sha256(b).digest()[:16]
return base64.b32encode(h).lower().replace('=','')
def get_platform(): def get_platform():
p = platform.system() p = platform.system()
@ -37,6 +43,19 @@ def set_stay_open(new_stay_open):
app = Flask(__name__) app = Flask(__name__)
def debug_mode():
import logging
global app
if platform.system() == 'Windows':
temp_dir = os.environ['Temp'].replace('\\', '/')
else:
temp_dir = '/tmp/'
log_handler = logging.FileHandler('{0}/onionshare_server.log'.format(temp_dir))
log_handler.setLevel(logging.WARNING)
app.logger.addHandler(log_handler)
# get path of onioshare directory # get path of onioshare directory
if get_platform() == 'Darwin': if get_platform() == 'Darwin':
onionshare_dir = os.path.dirname(__file__) onionshare_dir = os.path.dirname(__file__)
@ -73,9 +92,13 @@ 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])
@app.route("/{0}".format(slug)) @app.route("/<slug_candidate>")
def index(): def index(slug_candidate):
global filename, filesize, filehash, slug, strings, REQUEST_LOAD, onionshare_dir global filename, filesize, filehash, slug, strings, REQUEST_LOAD, onionshare_dir
if not constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
abort(404)
add_request(REQUEST_LOAD, request.path) add_request(REQUEST_LOAD, request.path)
return render_template_string( return render_template_string(
open('{0}/index.html'.format(onionshare_dir)).read(), open('{0}/index.html'.format(onionshare_dir)).read(),
@ -87,11 +110,14 @@ def index():
strings=strings strings=strings
) )
@app.route("/{0}/download".format(slug)) @app.route("/<slug_candidate>/download")
def download(): def download(slug_candidate):
global filename, filesize, q, download_count global filename, filesize, q, download_count
global REQUEST_DOWNLOAD, REQUEST_PROGRESS global REQUEST_DOWNLOAD, REQUEST_PROGRESS
if not constant_time_compare(slug.encode('ascii'), slug_candidate.encode('ascii')):
abort(404)
# each download has a unique id # each download has a unique id
download_id = download_count download_id = download_count
download_count += 1 download_count += 1
@ -159,7 +185,7 @@ def tails_open_port(port):
def tails_close_port(port): def tails_close_port(port):
if get_platform() == 'Tails': if get_platform() == 'Tails':
print translated("closing_hole") print translated("closing_hole")
subprocess.call(['/sbin/iptables', '-I', 'OUTPUT', '-o', 'lo', '-p', 'tcp', '--dport', str(port), '-j', 'REJECT']) subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo', '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT'])
def load_strings(default="en"): def load_strings(default="en"):
global strings global strings
@ -260,14 +286,19 @@ def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help='Do not attempt to use tor: for development only') parser.add_argument('--local-only', action='store_true', dest='local_only', help='Do not attempt to use tor: for development only')
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help='Keep hidden service running after download has finished') parser.add_argument('--stay-open', action='store_true', dest='stay_open', help='Keep hidden service running after download has finished')
parser.add_argument('--debug', action='store_true', dest='debug', help='Log errors to disk')
parser.add_argument('filename', nargs=1, help='File to share') parser.add_argument('filename', nargs=1, help='File to share')
args = parser.parse_args() args = parser.parse_args()
filename = os.path.abspath(args.filename[0]) filename = os.path.abspath(args.filename[0])
local_only = args.local_only local_only = bool(args.local_only)
debug = bool(args.debug)
if debug:
debug_mode()
global stay_open global stay_open
stay_open = args.stay_open stay_open = bool(args.stay_open)
if not (filename and os.path.isfile(filename)): if not (filename and os.path.isfile(filename)):
sys.exit(translated("not_a_file").format(filename)) sys.exit(translated("not_a_file").format(filename))

View File

@ -100,7 +100,17 @@
"ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen",
"not_a_file": "{0} is geen bestand.", "not_a_file": "{0} is geen bestand.",
"filesize": "Bestandsgrootte", "filesize": "Bestandsgrootte",
"sha1_checksum": "SHA1 controlecijfer" "sha1_checksum": "SHA1 controlecijfer",
"copied_url": "URL gekopieerd naar klembord",
"download_page_loaded": "Download pagina geladen",
"download_started": "Download gestart",
"download_finished": "Download voltooid",
"other_page_loaded": "Andere pagina is geladen",
"tails_requires_root": "Je moet OnionShare als root draaien in Tails",
"close_on_finish": "Sluit automatisch",
"close_countdown": "Sluit in {0} seconden...",
"choose_file": "Kies betsand om te delen",
"copy_url": "Kopieer URL"
}, "pt": { }, "pt": {
"punching_a_hole": "Abrindo um buraco no firewall.", "punching_a_hole": "Abrindo um buraco no firewall.",
"closing_hole": "Fechando buraco no firewall.", "closing_hole": "Fechando buraco no firewall.",

View File

@ -96,7 +96,7 @@ def main():
args = parser.parse_args() args = parser.parse_args()
filename = args.filename filename = args.filename
local_only = args.local_only local_only = bool(args.local_only)
stay_open = bool(args.stay_open) stay_open = bool(args.stay_open)
debug = bool(args.debug) debug = bool(args.debug)
@ -134,6 +134,7 @@ def main():
else: else:
webapp.onion_host = local_host webapp.onion_host = local_host
if debug: if debug:
onionshare.debug_mode()
webapp.debug_mode() webapp.debug_mode()
# run the web app in a new thread # run the web app in a new thread

View File

@ -9,3 +9,52 @@ function human_readable_filesize(bytes, si) {
} while(bytes >= thresh); } while(bytes >= thresh);
return bytes.toFixed(1)+' '+units[u]; return bytes.toFixed(1)+' '+units[u];
}; };
function htmlspecialchars(string, quote_style, charset, double_encode) {
var optTemp = 0,
i = 0,
noquotes = false;
if (typeof quote_style === 'undefined' || quote_style === null) {
quote_style = 2;
}
string = string.toString();
if (double_encode !== false) {
// Put this first to avoid double-encoding
string = string.replace(/&/g, '&amp;');
}
string = string.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
var OPTS = {
'ENT_NOQUOTES': 0,
'ENT_HTML_QUOTE_SINGLE': 1,
'ENT_HTML_QUOTE_DOUBLE': 2,
'ENT_COMPAT': 2,
'ENT_QUOTES': 3,
'ENT_IGNORE': 4
};
if (quote_style === 0) {
noquotes = true;
}
if (typeof quote_style !== 'number') {
// Allow for a single string or an array of string flags
quote_style = [].concat(quote_style);
for (i = 0; i < quote_style.length; i++) {
// Resolve string input to bitwise e.g. 'ENT_IGNORE' becomes 4
if (OPTS[quote_style[i]] === 0) {
noquotes = true;
} else if (OPTS[quote_style[i]]) {
optTemp = optTemp | OPTS[quote_style[i]];
}
}
quote_style = optTemp;
}
if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) {
string = string.replace(/'/g, '&#039;');
}
if (!noquotes) {
string = string.replace(/"/g, '&quot;');
}
return string;
}

View File

@ -65,7 +65,7 @@ $(function(){
} }
} else { } else {
if(r.path != '/favicon.ico') if(r.path != '/favicon.ico')
update($('<span>').addClass('weblog-error').html(onionshare.strings['other_page_loaded']+': '+r.path)); update($('<span>').addClass('weblog-error').html(onionshare.strings['other_page_loaded']+': '+htmlspecialchars(r.path)));
} }
} }
} }

View File

@ -1,4 +1,5 @@
from flask import Flask, render_template from flask import Flask, render_template, make_response
from functools import wraps
import threading, json, os, time, platform, sys import threading, json, os, time, platform, sys
onionshare = None onionshare = None
@ -22,15 +23,40 @@ def debug_mode():
else: else:
temp_dir = '/tmp/' temp_dir = '/tmp/'
log_handler = logging.FileHandler('{0}/onionshare.web.log'.format(temp_dir)) log_handler = logging.FileHandler('{0}/onionshare_gui.log'.format(temp_dir))
log_handler.setLevel(logging.WARNING) log_handler.setLevel(logging.WARNING)
app.logger.addHandler(log_handler) app.logger.addHandler(log_handler)
def add_response_headers(headers={}):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
resp = make_response(f(*args, **kwargs))
h = resp.headers
for header, value in headers.items():
h[header] = value
return resp
return decorated_function
return decorator
def csp(f):
@wraps(f)
# disable inline js, external js
@add_response_headers({'Content-Security-Policy': "default-src 'self'; connect-src 'self'"})
# ugh, webkit embedded in Qt4 is stupid old
# TODO: remove webkit, build GUI with normal Qt widgets
@add_response_headers({'X-WebKit-CSP': "default-src 'self'; connect-src 'self'"})
def decorated_function(*args, **kwargs):
return f(*args, **kwargs)
return decorated_function
@app.route("/") @app.route("/")
@csp
def index(): def index():
return render_template('index.html') return render_template('index.html')
@app.route("/init_info") @app.route("/init_info")
@csp
def init_info(): def init_info():
global onionshare, filename, stay_open global onionshare, filename, stay_open
basename = os.path.basename(filename) basename = os.path.basename(filename)
@ -42,6 +68,7 @@ def init_info():
}) })
@app.route("/start_onionshare") @app.route("/start_onionshare")
@csp
def start_onionshare(): def start_onionshare():
global onionshare, onionshare_port, filename, onion_host, url global onionshare, onionshare_port, filename, onion_host, url
@ -62,6 +89,7 @@ def start_onionshare():
}) })
@app.route("/copy_url") @app.route("/copy_url")
@csp
def copy_url(): def copy_url():
if platform.system() == 'Windows': if platform.system() == 'Windows':
# Qt's QClipboard isn't working in Windows # Qt's QClipboard isn't working in Windows
@ -82,16 +110,19 @@ def copy_url():
return '' return ''
@app.route("/stay_open_true") @app.route("/stay_open_true")
@csp
def stay_open_true(): def stay_open_true():
global onionshare global onionshare
onionshare.set_stay_open(True) onionshare.set_stay_open(True)
@app.route("/stay_open_false") @app.route("/stay_open_false")
@csp
def stay_open_false(): def stay_open_false():
global onionshare global onionshare
onionshare.set_stay_open(False) onionshare.set_stay_open(False)
@app.route("/heartbeat") @app.route("/heartbeat")
@csp
def check_for_requests(): def check_for_requests():
global onionshare global onionshare
events = [] events = []
@ -107,6 +138,7 @@ def check_for_requests():
return json.dumps(events) return json.dumps(events)
@app.route("/close") @app.route("/close")
@csp
def close(): def close():
global qtapp global qtapp
time.sleep(1) time.sleep(1)

View File

@ -1,6 +1,6 @@
# import stuff for pyinstaller to find # import stuff for pyinstaller to find
import os, sys, subprocess, time, hashlib, platform, json, locale, socket, argparse, Queue, inspect, base64, random, functools import os, sys, subprocess, time, hashlib, platform, json, locale, socket, argparse, Queue, inspect, base64, random, functools, logging
import PyQt4.QtCore, PyQt4.QtGui, PyQt4.QtWebKit from PyQt4 import QtCore, QtGui, QtWebKit
import stem, stem.control, flask import stem, stem.control, flask
import onionshare, onionshare_gui import onionshare, onionshare_gui

View File

@ -3,10 +3,10 @@
!define ABOUTURL "https://github.com/micahflee/onionshare" !define ABOUTURL "https://github.com/micahflee/onionshare"
# change these with each release # change these with each release
!define INSTALLSIZE 46094 !define INSTALLSIZE 46124
!define VERSIONMAJOR 0 !define VERSIONMAJOR 0
!define VERSIONMINOR 3 !define VERSIONMINOR 4
!define VERSIONSTRING "0.3dev" !define VERSIONSTRING "0.4"
RequestExecutionLevel admin RequestExecutionLevel admin

View File

@ -1 +1 @@
0.3 0.4