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):*
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
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
sudo dpkg -i deb_dist/onionshare_*.deb
```

View File

@ -9,7 +9,7 @@ VERSION=`cat version`
rm -r deb_dist >/dev/null 2>&1
# 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
echo ""

View File

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

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
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
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os, inspect, hashlib, base64, hmac, platform, zipfile, tempfile
from itertools import izip
import math
import time
# hack to make unicode filenames work (#141)
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
import sys, os, inspect, hashlib, base64, hmac, platform, zipfile, tempfile, math, time
def get_platform():
@ -82,20 +74,20 @@ def get_version():
def constant_time_compare(val1, val2):
"""
Compares two values in constant time.
"""
_builtin_constant_time_compare = getattr(hmac, 'compare_digest', None)
if _builtin_constant_time_compare is not None:
return _builtin_constant_time_compare(val1, val2)
Returns True if the two strings are equal, False otherwise.
len_eq = len(val1) == len(val2)
if len_eq:
result = 0
left = val1
else:
result = 1
left = val2
for x, y in izip(bytearray(left), bytearray(val2)):
The time taken is independent of the number of characters that match.
For the sake of simplicity, this function executes in constant time only
when the two strings have the same length. It short-circuits when they
have different lengths.
From: http://www.levigross.com/2014/02/07/constant-time-comparison-functions-in...-python-haskell-clojure-and-java/
"""
if len(val1) != len(val2):
return False
result = 0
for x, y in zip(val1, val2):
result |= x ^ y
return result == 0
@ -106,7 +98,7 @@ def random_string(num_bytes, output_len=None):
"""
b = os.urandom(num_bytes)
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:
return s
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
import os, sys, tempfile, shutil, urllib2, httplib
import socks
from stem import SocketError
import os, sys, tempfile, shutil, urllib
import helpers, strings
from . import socks
from . import helpers, strings
class NoTor(Exception):
"""
@ -62,17 +63,19 @@ class HS(object):
self.service_id = None
# connect to the tor controlport
found_tor = False
self.c = None
ports = [9151, 9153, 9051]
for port in ports:
try:
self.c = Controller.from_port(port=port)
self.c.authenticate()
found_tor = True
break
except:
except SocketError:
pass
if not self.c:
raise NoTor(strings._("cant_connect_ctrlport").format(ports))
if not found_tor:
raise NoTor(strings._("cant_connect_ctrlport").format(str(ports)))
# do the versions of stem and tor that I'm using support ephemeral hidden services?
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
return the onion hostname.
"""
print strings._("connecting_ctrlport").format(int(port))
print(strings._("connecting_ctrlport").format(int(port)))
if self.supports_ephemeral:
print strings._('using_ephemeral')
print(strings._('using_ephemeral'))
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication = False)
self.service_id = res.content()[0][2].split('=')[1]
onion_host = res.content()[0][2].split('=')[1] + '.onion'
@ -102,7 +105,7 @@ class HS(object):
path = '/tmp/onionshare'
try:
if not os.path.exists(path):
os.makedirs(path, 0700)
os.makedirs(path, 0o700)
except:
raise HSDirError(strings._("error_hs_dir_cannot_create").format(path))
if not os.access(path, os.W_OK):
@ -139,7 +142,7 @@ class HS(object):
successfully connects..
"""
# legacy only, this function is no longer required with ephemeral hidden services
print strings._('wait_for_hs')
print(strings._('wait_for_hs'))
ready = False
while not ready:
@ -149,7 +152,7 @@ class HS(object):
if self.transparent_torification:
# 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:
tor_exists = False
ports = [9150, 9152, 9050]
@ -164,17 +167,17 @@ class HS(object):
except socks.ProxyConnectionError:
pass
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
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:
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush()
except urllib2.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
except urllib.error.HTTPError: # torification error
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush()
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 strings, helpers, web, hs
from . import strings, helpers, web, hs
class OnionShare(object):
"""
@ -97,7 +98,7 @@ def main(cwd=None):
onionshare uses.
"""
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)
if helpers.get_platform() == 'Darwin':
@ -142,15 +143,15 @@ def main(cwd=None):
sys.exit(e.args[0])
# prepare files to share
print strings._("preparing_files")
print(strings._("preparing_files"))
web.set_file_info(filenames)
app.cleanup_filenames.append(web.zip_filename)
# warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb
print ''
print strings._("large_filesize")
print ''
print('')
print(strings._("large_filesize"))
print('')
# start onionshare service in new thread
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:
sys.exit()
print strings._("give_this_url")
print 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
print ''
print strings._("ctrlc_to_stop")
print(strings._("give_this_url"))
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
print('')
print(strings._("ctrlc_to_stop"))
# wait for app to close
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/>.
"""
import json, locale, sys, os
import helpers
from . import helpers
strings = {}
@ -62,9 +63,6 @@ def translated(k, gui=False):
"""
Returns a translated string.
"""
if gui:
return strings[k].encode("utf-8").decode('utf-8', 'replace')
else:
return strings[k].encode("utf-8")
return strings[k]
_ = 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
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 functools import wraps
import strings, helpers
from . import strings, helpers
app = Flask(__name__)
@ -72,7 +74,7 @@ REQUEST_DOWNLOAD = 1
REQUEST_PROGRESS = 2
REQUEST_OTHER = 3
REQUEST_CANCELED = 4
q = Queue.Queue()
q = queue.Queue()
def add_request(request_type, path, data=None):
@ -154,7 +156,7 @@ def index(slug_candidate):
open(helpers.get_html_path('index.html')).read(),
slug=slug,
file_info=file_info,
filename=os.path.basename(zip_filename).decode("utf-8"),
filename=os.path.basename(zip_filename),
filesize=zip_filesize,
filesize_human=helpers.human_readable_filesize(zip_filesize)
)
@ -192,7 +194,7 @@ def download(slug_candidate):
canceled = False
while not done:
chunk = fp.read(chunk_size)
if chunk == '':
if chunk == b'':
done = True
else:
try:
@ -224,7 +226,7 @@ def download(slug_candidate):
# download is finished, close the server
if not stay_open and not canceled:
print strings._("closing_automatically")
print(strings._("closing_automatically"))
if shutdown_func is None:
raise RuntimeError('Not running with the Werkzeug Server')
shutdown_func()
@ -292,6 +294,6 @@ def stop(port):
s.connect(('127.0.0.1', port))
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))
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:
pass

View File

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

View File

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