Porting onionshare from python2 to python3 (#261). This commit only ports the CLI version, not the GUI. Has not been tested in Fedora, Windows, or OSX. Removed hack to make unicode filenames work because hack does not work in python3. Replaced constant_time_compare function with a new one that works in python3. Tweaked hidden service checking code because urllib is different in python3.

This commit is contained in:
Micah Lee 2016-02-12 14:34:19 -08:00
parent b2bda8294a
commit 170811f450
12 changed files with 71 additions and 73 deletions

View File

@ -11,10 +11,10 @@ cd onionshare
*For .deb-based distros (like Debian, Ubuntu, Linux Mint):* *For .deb-based distros (like Debian, Ubuntu, Linux Mint):*
Note that python-stem appears in Debian wheezy and newer, and it appears in Ubuntu 13.10 and newer. Older versions of Debian and Ubuntu aren't supported. Note that python3-stem appears in Debian wheezy and newer, and it appears in Ubuntu 13.10 and newer. Older versions of Debian and Ubuntu aren't supported.
```sh ```sh
sudo apt-get install -y build-essential fakeroot python-all python-stdeb python-flask python-stem python-qt4 dh-python sudo apt-get install -y build-essential fakeroot python3-all python3-stdeb python3-flask python3-stem python3-pyqt5 dh-python
./install/build_deb.sh ./install/build_deb.sh
sudo dpkg -i deb_dist/onionshare_*.deb sudo dpkg -i deb_dist/onionshare_*.deb
``` ```

View File

@ -9,7 +9,7 @@ VERSION=`cat version`
rm -r deb_dist >/dev/null 2>&1 rm -r deb_dist >/dev/null 2>&1
# build binary package # build binary package
python setup.py --command-packages=stdeb.command bdist_deb python3 setup.py --command-packages=stdeb.command bdist_deb
# return install instructions if onionshare builds properly # return install instructions if onionshare builds properly
echo "" echo ""

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
OnionShare | https://onionshare.org/ OnionShare | https://onionshare.org/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
OnionShare | https://onionshare.org/ OnionShare | https://onionshare.org/

View File

@ -17,4 +17,4 @@ 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 onionshare import * from .onionshare import *

View File

@ -17,15 +17,7 @@ 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 os, inspect, hashlib, base64, hmac, platform, zipfile, tempfile import sys, os, inspect, hashlib, base64, hmac, platform, zipfile, tempfile, math, time
from itertools import izip
import math
import time
# hack to make unicode filenames work (#141)
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
def get_platform(): def get_platform():
@ -82,20 +74,20 @@ def get_version():
def constant_time_compare(val1, val2): def constant_time_compare(val1, val2):
""" """
Compares two values in constant time. Returns True if the two strings are equal, False otherwise.
"""
_builtin_constant_time_compare = getattr(hmac, 'compare_digest', None)
if _builtin_constant_time_compare is not None:
return _builtin_constant_time_compare(val1, val2)
len_eq = len(val1) == len(val2) The time taken is independent of the number of characters that match.
if len_eq:
result = 0 For the sake of simplicity, this function executes in constant time only
left = val1 when the two strings have the same length. It short-circuits when they
else: have different lengths.
result = 1
left = val2 From: http://www.levigross.com/2014/02/07/constant-time-comparison-functions-in...-python-haskell-clojure-and-java/
for x, y in izip(bytearray(left), bytearray(val2)): """
if len(val1) != len(val2):
return False
result = 0
for x, y in zip(val1, val2):
result |= x ^ y result |= x ^ y
return result == 0 return result == 0
@ -106,7 +98,7 @@ def random_string(num_bytes, output_len=None):
""" """
b = os.urandom(num_bytes) b = os.urandom(num_bytes)
h = hashlib.sha256(b).digest()[:16] h = hashlib.sha256(b).digest()[:16]
s = base64.b32encode(h).lower().replace('=', '') s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
if not output_len: if not output_len:
return s return s
return s[:output_len] return s[:output_len]

View File

@ -19,10 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from stem.control import Controller from stem.control import Controller
import os, sys, tempfile, shutil, urllib2, httplib from stem import SocketError
import socks import os, sys, tempfile, shutil, urllib
import helpers, strings from . import socks
from . import helpers, strings
class NoTor(Exception): class NoTor(Exception):
""" """
@ -62,17 +63,19 @@ class HS(object):
self.service_id = None self.service_id = None
# connect to the tor controlport # connect to the tor controlport
found_tor = False
self.c = None self.c = None
ports = [9151, 9153, 9051] ports = [9151, 9153, 9051]
for port in ports: for port in ports:
try: try:
self.c = Controller.from_port(port=port) self.c = Controller.from_port(port=port)
self.c.authenticate() self.c.authenticate()
found_tor = True
break break
except: except SocketError:
pass pass
if not self.c: if not found_tor:
raise NoTor(strings._("cant_connect_ctrlport").format(ports)) raise NoTor(strings._("cant_connect_ctrlport").format(str(ports)))
# do the versions of stem and tor that I'm using support ephemeral hidden services? # do the versions of stem and tor that I'm using support ephemeral hidden services?
tor_version = self.c.get_version().version_str tor_version = self.c.get_version().version_str
@ -84,9 +87,9 @@ class HS(object):
Start a hidden service on port 80, pointing to the given port, and Start a hidden service on port 80, pointing to the given port, and
return the onion hostname. return the onion hostname.
""" """
print strings._("connecting_ctrlport").format(int(port)) print(strings._("connecting_ctrlport").format(int(port)))
if self.supports_ephemeral: if self.supports_ephemeral:
print strings._('using_ephemeral') print(strings._('using_ephemeral'))
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication = False) res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication = False)
self.service_id = res.content()[0][2].split('=')[1] self.service_id = res.content()[0][2].split('=')[1]
onion_host = res.content()[0][2].split('=')[1] + '.onion' onion_host = res.content()[0][2].split('=')[1] + '.onion'
@ -102,7 +105,7 @@ class HS(object):
path = '/tmp/onionshare' path = '/tmp/onionshare'
try: try:
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(path, 0700) os.makedirs(path, 0o700)
except: except:
raise HSDirError(strings._("error_hs_dir_cannot_create").format(path)) raise HSDirError(strings._("error_hs_dir_cannot_create").format(path))
if not os.access(path, os.W_OK): if not os.access(path, os.W_OK):
@ -139,7 +142,7 @@ class HS(object):
successfully connects.. successfully connects..
""" """
# legacy only, this function is no longer required with ephemeral hidden services # legacy only, this function is no longer required with ephemeral hidden services
print strings._('wait_for_hs') print(strings._('wait_for_hs'))
ready = False ready = False
while not ready: while not ready:
@ -149,7 +152,7 @@ class HS(object):
if self.transparent_torification: if self.transparent_torification:
# no need to set the socks5 proxy # no need to set the socks5 proxy
urllib2.urlopen('http://{0:s}'.format(onion_host)) urllib.request.urlopen('http://{0:s}'.format(onion_host))
else: else:
tor_exists = False tor_exists = False
ports = [9150, 9152, 9050] ports = [9150, 9152, 9050]
@ -164,17 +167,17 @@ class HS(object):
except socks.ProxyConnectionError: except socks.ProxyConnectionError:
pass pass
if not tor_exists: if not tor_exists:
raise NoTor(strings._("cant_connect_socksport").format(tor_socks_ports)) raise NoTor(strings._("cant_connect_socksport").format(str(ports)))
ready = True ready = True
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_yup'))) sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_yup')))
except socks.GeneralProxyError:
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush()
except socks.SOCKS5Error: except socks.SOCKS5Error:
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() sys.stdout.flush()
except urllib2.HTTPError: # torification error except urllib.error.HTTPError: # torification error
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush()
except httplib.BadStatusLine: # torification (with bridge) error
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() sys.stdout.flush()
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -19,7 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading
import strings, helpers, web, hs
from . import strings, helpers, web, hs
class OnionShare(object): class OnionShare(object):
""" """
@ -97,7 +98,7 @@ def main(cwd=None):
onionshare uses. onionshare uses.
""" """
strings.load_strings() strings.load_strings()
print strings._('version_string').format(helpers.get_version()) print(strings._('version_string').format(helpers.get_version()))
# onionshare CLI in OSX needs to change current working directory (#132) # onionshare CLI in OSX needs to change current working directory (#132)
if helpers.get_platform() == 'Darwin': if helpers.get_platform() == 'Darwin':
@ -142,15 +143,15 @@ def main(cwd=None):
sys.exit(e.args[0]) sys.exit(e.args[0])
# prepare files to share # prepare files to share
print strings._("preparing_files") print(strings._("preparing_files"))
web.set_file_info(filenames) web.set_file_info(filenames)
app.cleanup_filenames.append(web.zip_filename) app.cleanup_filenames.append(web.zip_filename)
# warn about sending large files over Tor # warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb if web.zip_filesize >= 157286400: # 150mb
print '' print('')
print strings._("large_filesize") print(strings._("large_filesize"))
print '' print('')
# start onionshare service in new thread # start onionshare service in new thread
t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.transparent_torification)) t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.transparent_torification))
@ -164,10 +165,10 @@ def main(cwd=None):
if not ready: if not ready:
sys.exit() sys.exit()
print strings._("give_this_url") print(strings._("give_this_url"))
print 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
print '' print('')
print strings._("ctrlc_to_stop") print(strings._("ctrlc_to_stop"))
# wait for app to close # wait for app to close
while t.is_alive(): while t.is_alive():

View File

@ -18,7 +18,8 @@ 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, sys, os import json, locale, sys, os
import helpers
from . import helpers
strings = {} strings = {}
@ -62,9 +63,6 @@ def translated(k, gui=False):
""" """
Returns a translated string. Returns a translated string.
""" """
if gui: return strings[k]
return strings[k].encode("utf-8").decode('utf-8', 'replace')
else:
return strings[k].encode("utf-8")
_ = translated _ = translated

View File

@ -17,10 +17,12 @@ 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 Queue, mimetypes, platform, os, sys, urllib2 import queue, mimetypes, platform, os, sys
from urllib.request import urlopen
from flask import Flask, Response, request, render_template_string, abort from flask import Flask, Response, request, render_template_string, abort
from functools import wraps from functools import wraps
import strings, helpers
from . import strings, helpers
app = Flask(__name__) app = Flask(__name__)
@ -72,7 +74,7 @@ REQUEST_DOWNLOAD = 1
REQUEST_PROGRESS = 2 REQUEST_PROGRESS = 2
REQUEST_OTHER = 3 REQUEST_OTHER = 3
REQUEST_CANCELED = 4 REQUEST_CANCELED = 4
q = Queue.Queue() q = queue.Queue()
def add_request(request_type, path, data=None): def add_request(request_type, path, data=None):
@ -154,7 +156,7 @@ def index(slug_candidate):
open(helpers.get_html_path('index.html')).read(), open(helpers.get_html_path('index.html')).read(),
slug=slug, slug=slug,
file_info=file_info, file_info=file_info,
filename=os.path.basename(zip_filename).decode("utf-8"), filename=os.path.basename(zip_filename),
filesize=zip_filesize, filesize=zip_filesize,
filesize_human=helpers.human_readable_filesize(zip_filesize) filesize_human=helpers.human_readable_filesize(zip_filesize)
) )
@ -192,7 +194,7 @@ def download(slug_candidate):
canceled = False canceled = False
while not done: while not done:
chunk = fp.read(chunk_size) chunk = fp.read(chunk_size)
if chunk == '': if chunk == b'':
done = True done = True
else: else:
try: try:
@ -224,7 +226,7 @@ def download(slug_candidate):
# download is finished, close the server # download is finished, close the server
if not stay_open and not canceled: if not stay_open and not canceled:
print strings._("closing_automatically") print(strings._("closing_automatically"))
if shutdown_func is None: if shutdown_func is None:
raise RuntimeError('Not running with the Werkzeug Server') raise RuntimeError('Not running with the Werkzeug Server')
shutdown_func() shutdown_func()
@ -292,6 +294,6 @@ def stop(port):
s.connect(('127.0.0.1', port)) s.connect(('127.0.0.1', port))
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug)) s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))
else: else:
urllib2.urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read() urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read()
except: except:
pass pass

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
OnionShare | https://onionshare.org/ OnionShare | https://onionshare.org/
@ -83,9 +83,11 @@ if system == 'Linux':
url='https://github.com/micahflee/onionshare', url='https://github.com/micahflee/onionshare',
license="GPL v3", license="GPL v3",
keywords='onion, share, onionshare, tor, anonymous, web server', keywords='onion, share, onionshare, tor, anonymous, web server',
packages=['onionshare', 'onionshare_gui'], #packages=['onionshare', 'onionshare_gui'],
packages=['onionshare'],
include_package_data=True, include_package_data=True,
scripts=['install/linux_scripts/onionshare', 'install/linux_scripts/onionshare-gui'], #scripts=['install/linux_scripts/onionshare', 'install/linux_scripts/onionshare-gui'],
scripts=['install/linux_scripts/onionshare'],
data_files=[ data_files=[
(os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']), (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']),
(os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']), (os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']),

View File

@ -1,5 +1,5 @@
[DEFAULT] [DEFAULT]
Package: onionshare Package3: onionshare
Depends: python-flask, python-stem, python-qt4 Depends3: python3-flask, python3-stem, python3-pyqt5
Build-Depends: dh-python Build-Depends3: python3-stdeb, python3-nose
Suite: trusty Suite: trusty