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 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):
pass
def random_string(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():
p = platform.system()
@ -37,6 +43,19 @@ def set_stay_open(new_stay_open):
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
if get_platform() == 'Darwin':
onionshare_dir = os.path.dirname(__file__)
@ -73,9 +92,13 @@ def human_readable_filesize(b):
u += 1
return '{0} {1}'.format(round(b, 1), units[u])
@app.route("/{0}".format(slug))
def index():
@app.route("/<slug_candidate>")
def index(slug_candidate):
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)
return render_template_string(
open('{0}/index.html'.format(onionshare_dir)).read(),
@ -87,11 +110,14 @@ def index():
strings=strings
)
@app.route("/{0}/download".format(slug))
def download():
@app.route("/<slug_candidate>/download")
def download(slug_candidate):
global filename, filesize, q, download_count
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
download_id = download_count
download_count += 1
@ -159,7 +185,7 @@ def tails_open_port(port):
def tails_close_port(port):
if get_platform() == 'Tails':
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"):
global strings
@ -260,14 +286,19 @@ def main():
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('--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')
args = parser.parse_args()
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
stay_open = args.stay_open
stay_open = bool(args.stay_open)
if not (filename and os.path.isfile(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",
"not_a_file": "{0} is geen bestand.",
"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": {
"punching_a_hole": "Abrindo um buraco no firewall.",
"closing_hole": "Fechando buraco no firewall.",

View File

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

View File

@ -9,3 +9,52 @@ function human_readable_filesize(bytes, si) {
} while(bytes >= thresh);
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 {
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
onionshare = None
@ -22,15 +23,40 @@ def debug_mode():
else:
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)
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("/")
@csp
def index():
return render_template('index.html')
@app.route("/init_info")
@csp
def init_info():
global onionshare, filename, stay_open
basename = os.path.basename(filename)
@ -42,6 +68,7 @@ def init_info():
})
@app.route("/start_onionshare")
@csp
def start_onionshare():
global onionshare, onionshare_port, filename, onion_host, url
@ -62,6 +89,7 @@ def start_onionshare():
})
@app.route("/copy_url")
@csp
def copy_url():
if platform.system() == 'Windows':
# Qt's QClipboard isn't working in Windows
@ -82,16 +110,19 @@ def copy_url():
return ''
@app.route("/stay_open_true")
@csp
def stay_open_true():
global onionshare
onionshare.set_stay_open(True)
@app.route("/stay_open_false")
@csp
def stay_open_false():
global onionshare
onionshare.set_stay_open(False)
@app.route("/heartbeat")
@csp
def check_for_requests():
global onionshare
events = []
@ -107,6 +138,7 @@ def check_for_requests():
return json.dumps(events)
@app.route("/close")
@csp
def close():
global qtapp
time.sleep(1)

View File

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

View File

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

View File

@ -1 +1 @@
0.3
0.4