Merge master branch and fix conflicts

This commit is contained in:
Miguel Jacq 2017-12-03 17:06:33 +11:00
commit 2eb7bca242
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
15 changed files with 128 additions and 39 deletions

1
.gitignore vendored
View File

@ -27,6 +27,7 @@ pip-log.txt
.coverage .coverage
.tox .tox
nosetests.xml nosetests.xml
.cache
# Translations # Translations
*.mo *.mo

View File

@ -1,11 +1,12 @@
include LICENSE include LICENSE
include README.md include README.md
include BUILD.md include BUILD.md
include resources/* include share/*
include resources/images/* include share/images/*
include resources/locale/* include share/locale/*
include resources/html/* include share/html/*
include install/onionshare.desktop include install/onionshare.desktop
include install/onionshare.appdata.xml include install/onionshare.appdata.xml
include install/onionshare80.xpm include install/onionshare80.xpm
include install/scripts/onionshare-nautilus.py include install/scripts/onionshare-nautilus.py
include test/*.py

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python
import os import os
import sys
import json
import locale
import subprocess import subprocess
import urllib import urllib
import gi import gi
@ -12,7 +13,55 @@ from gi.repository import GObject
# Put me in /usr/share/nautilus-python/extensions/ # Put me in /usr/share/nautilus-python/extensions/
class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self): def __init__(self):
pass # Get the localized string for "Share via OnionShare" label
self.label = None
default_label = 'Share via OnionShare'
try:
# Re-implement localization in python2
default_locale = 'en'
locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale')
if os.path.exists(locale_dir):
# Load all translations
strings = {}
translations = {}
for filename in os.listdir(locale_dir):
abs_filename = os.path.join(locale_dir, filename)
lang, ext = os.path.splitext(filename)
if ext == '.json':
with open(abs_filename) as f:
translations[lang] = json.load(f)
strings = translations[default_locale]
lc, enc = locale.getdefaultlocale()
if lc:
lang = lc[:2]
if lang in translations:
# if a string doesn't exist, fallback to English
for key in translations[default_locale]:
if key in translations[lang]:
strings[key] = translations[lang][key]
self.label = strings['share_via_onionshare']
except:
self.label = default_label
if not self.label:
self.label = default_label
"""
# This more elegant solution will only work if nautilus is using python3, and onionshare is installed system-wide.
# But nautilus is using python2, so this is commented out.
try:
import onionshare
onionshare.strings.load_strings(onionshare.common)
self.label = onionshare.strings._('share_via_onionshare')
except:
import sys
print('python version: {}').format(sys.version)
self.label = 'Share via OnionShare'
"""
def url2path(self,url): def url2path(self,url):
file_uri = url.get_activation_uri() file_uri = url.get_activation_uri()
@ -31,7 +80,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
def get_file_items(self, window, files): def get_file_items(self, window, files):
menuitem = Nautilus.MenuItem(name='OnionShare::Nautilus', menuitem = Nautilus.MenuItem(name='OnionShare::Nautilus',
label='Share via OnionShare', label=self.label,
tip='', tip='',
icon='') icon='')
menu = Nautilus.Menu() menu = Nautilus.Menu()

View File

@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64 import base64
import hashlib import hashlib
import inspect import inspect
import math
import os import os
import platform import platform
import random import random
@ -70,9 +69,12 @@ def get_resource_path(filename):
if getattr(sys, 'onionshare_dev_mode', False): if getattr(sys, 'onionshare_dev_mode', False):
# Look for resources directory relative to python file # Look for resources directory relative to python file
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share')
if not os.path.exists(prefix):
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
elif p == 'Linux' and sys.argv and sys.argv[0].startswith(sys.prefix): elif p == 'Linux':
# OnionShare is installed systemwide in Linux # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
prefix = os.path.join(sys.prefix, 'share/onionshare') prefix = os.path.join(sys.prefix, 'share/onionshare')
elif getattr(sys, 'frozen', False): elif getattr(sys, 'frozen', False):
@ -144,14 +146,14 @@ def human_readable_filesize(b):
""" """
thresh = 1024.0 thresh = 1024.0
if b < thresh: if b < thresh:
return '{0:.1f} B'.format(b) return '{:.1f} 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:
b /= thresh b /= thresh
u += 1 u += 1
return '{0:.1f} {1:s}'.format(round(b, 1), units[u]) return '{:.1f} {}'.format(b, units[u])
def format_seconds(seconds): def format_seconds(seconds):

View File

@ -433,6 +433,13 @@ class Onion(object):
self.stealth = False self.stealth = False
self.service_id = None self.service_id = None
try:
# Delete the temporary tor data directory
self.tor_data_directory.cleanup()
except AttributeError:
# Skip if cleanup was somehow run before connect
pass
def get_tor_socks_port(self): def get_tor_socks_port(self):
""" """
Returns a (address, port) tuple for the Tor SOCKS port Returns a (address, port) tuple for the Tor SOCKS port

View File

@ -18,10 +18,13 @@ 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/>.
""" """
import platform, os, json import json
import os
import platform
from . import strings, common from . import strings, common
class Settings(object): class Settings(object):
""" """
This class stores all of the settings for OnionShare, specifically for how This class stores all of the settings for OnionShare, specifically for how
@ -95,7 +98,7 @@ class Settings(object):
try: try:
common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
with open(self.filename, 'r') as f: with open(self.filename, 'r') as f:
self._settings = json.loads(f.read()) self._settings = json.load(f)
self.fill_in_defaults() self.fill_in_defaults()
except: except:
pass pass

View File

@ -17,17 +17,19 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License 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/>.
""" """
import json, locale, os import json
import locale
import os
strings = {} strings = {}
def load_strings(common, default="en"): def load_strings(common, default="en"):
""" """
Loads translated strings and fallback to English Loads translated strings and fallback to English
if the translation does not exist. if the translation does not exist.
""" """
global strings global strings
p = common.get_platform()
# find locale dir # find locale dir
locale_dir = common.get_resource_path('locale') locale_dir = common.get_resource_path('locale')
@ -37,10 +39,9 @@ def load_strings(common, default="en"):
for filename in os.listdir(locale_dir): for filename in os.listdir(locale_dir):
abs_filename = os.path.join(locale_dir, filename) abs_filename = os.path.join(locale_dir, filename)
lang, ext = os.path.splitext(filename) lang, ext = os.path.splitext(filename)
if abs_filename.endswith('.json'): if ext == '.json':
with open(abs_filename, encoding='utf-8') as f: with open(abs_filename, encoding='utf-8') as f:
lang_json = f.read() translations[lang] = json.load(f)
translations[lang] = json.loads(lang_json)
strings = translations[default] strings = translations[default]
lc, enc = locale.getdefaultlocale() lc, enc = locale.getdefaultlocale()

View File

@ -17,12 +17,22 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License 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 distutils.version import StrictVersion as Version
import queue, mimetypes, platform, os, sys, socket, logging, hmac import hmac
import logging
import mimetypes
import os
import queue
import socket
import sys
import tempfile
from distutils.version import LooseVersion as Version
from urllib.request import urlopen from urllib.request import urlopen
from flask import Flask, Response, request, render_template_string, abort, make_response from flask import (
from flask import __version__ as flask_version Flask, Response, request, render_template_string, abort, make_response,
__version__ as flask_version
)
from . import strings, common from . import strings, common
@ -56,6 +66,7 @@ security_headers = [
('Server', 'OnionShare') ('Server', 'OnionShare')
] ]
def set_file_info(filenames, processed_size_callback=None): def set_file_info(filenames, processed_size_callback=None):
""" """
Using the list of filenames being shared, fill in details that the web Using the list of filenames being shared, fill in details that the web
@ -115,6 +126,8 @@ def add_request(request_type, path, data=None):
slug = None slug = None
def generate_slug(): def generate_slug():
global slug global slug
slug = common.build_slug() slug = common.build_slug()
@ -123,12 +136,16 @@ download_count = 0
error404_count = 0 error404_count = 0
stay_open = False stay_open = False
def set_stay_open(new_stay_open): def set_stay_open(new_stay_open):
""" """
Set stay_open variable. Set stay_open variable.
""" """
global stay_open global stay_open
stay_open = new_stay_open stay_open = new_stay_open
def get_stay_open(): def get_stay_open():
""" """
Get stay_open variable. Get stay_open variable.
@ -138,6 +155,8 @@ def get_stay_open():
# Are we running in GUI mode? # Are we running in GUI mode?
gui_mode = False gui_mode = False
def set_gui_mode(): def set_gui_mode():
""" """
Tell the web service that we're running in GUI mode Tell the web service that we're running in GUI mode
@ -145,21 +164,19 @@ def set_gui_mode():
global gui_mode global gui_mode
gui_mode = True gui_mode = True
def debug_mode(): def debug_mode():
""" """
Turn on debugging mode, which will log flask errors to a debug file. Turn on debugging mode, which will log flask errors to a debug file.
""" """
if platform.system() == 'Windows': temp_dir = tempfile.gettempdir()
temp_dir = os.environ['Temp'].replace('\\', '/') log_handler = logging.FileHandler(
else: os.path.join(temp_dir, 'onionshare_server.log'))
temp_dir = '/tmp/'
log_handler = logging.FileHandler('{0:s}/onionshare_server.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 check_slug_candidate(slug_candidate, slug_compare=None): def check_slug_candidate(slug_candidate, slug_compare=None):
global slug
if not slug_compare: if not slug_compare:
slug_compare = slug slug_compare = slug
if not hmac.compare_digest(slug_compare, slug_candidate): if not hmac.compare_digest(slug_compare, slug_candidate):
@ -170,6 +187,7 @@ def check_slug_candidate(slug_candidate, slug_compare = None):
# one download at a time. # one download at a time.
download_in_progress = False download_in_progress = False
@app.route("/<slug_candidate>") @app.route("/<slug_candidate>")
def index(slug_candidate): def index(slug_candidate):
""" """
@ -202,11 +220,13 @@ def index(slug_candidate):
r.headers.set(header, value) r.headers.set(header, value)
return r return r
# If the client closes the OnionShare window while a download is in progress, # If the client closes the OnionShare window while a download is in progress,
# it should immediately stop serving the file. The client_cancel global is # it should immediately stop serving the file. The client_cancel global is
# used to tell the download function that the client is canceling the download. # used to tell the download function that the client is canceling the download.
client_cancel = False client_cancel = False
@app.route("/<slug_candidate>/download") @app.route("/<slug_candidate>/download")
def download(slug_candidate): def download(slug_candidate):
""" """
@ -331,11 +351,12 @@ def page_not_found(e):
force_shutdown() force_shutdown()
print(strings._('error_rate_limit')) print(strings._('error_rate_limit'))
r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read())) r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read()), 404)
for header, value in security_headers: for header, value in security_headers:
r.headers.set(header, value) r.headers.set(header, value)
return r return r
# 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 = common.random_string(16) shutdown_slug = common.random_string(16)

View File

@ -434,6 +434,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
if not web.get_stay_open(): if not web.get_stay_open():
self.server_status.stop_server() self.server_status.stop_server()
self.server_status.shutdown_timeout_reset() self.server_status.shutdown_timeout_reset()
else:
if self.server_status.status == self.server_status.STATUS_STOPPED:
self.downloads.cancel_download(event["data"]["id"])
elif event["type"] == web.REQUEST_CANCELED: elif event["type"] == web.REQUEST_CANCELED:
download_in_progress = False download_in_progress = False

View File

@ -125,7 +125,7 @@ class TorConnectionThread(QtCore.QThread):
# Connect to the Onion # Connect to the Onion
try: try:
self.onion.connect(self.settings, self._tor_status_update) self.onion.connect(self.settings, False, self._tor_status_update)
if self.onion.connected_to_tor: if self.onion.connected_to_tor:
self.connected_to_tor.emit() self.connected_to_tor.emit()
else: else:

View File

@ -118,5 +118,6 @@
"gui_tor_connection_ask_quit": "Quit", "gui_tor_connection_ask_quit": "Quit",
"gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.", "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.",
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing." "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
"share_via_onionshare": "Share via OnionShare"
} }