mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Merge branch 'develop' of github.com:micahflee/onionshare into develop
This commit is contained in:
commit
7ab240fd7f
@ -42,7 +42,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: run tests
|
name: run tests
|
||||||
command: |
|
command: |
|
||||||
xvfb-run pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/
|
xvfb-run -s "-screen 0 1280x1024x24" pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv --no-qt-log tests/
|
||||||
|
|
||||||
test-3.6:
|
test-3.6:
|
||||||
<<: *test-template
|
<<: *test-template
|
||||||
|
@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
|
|||||||
rm -r build dist >/dev/null 2>&1
|
rm -r build dist >/dev/null 2>&1
|
||||||
|
|
||||||
# build binary package
|
# build binary package
|
||||||
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
|
python3 setup.py bdist_rpm --requires="python3-flask, python3-flask-httpauth, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
|
||||||
|
|
||||||
# install it
|
# install it
|
||||||
echo ""
|
echo ""
|
||||||
|
@ -59,6 +59,7 @@ def main():
|
|||||||
files_in(dir, 'onionshare_gui/mode') + \
|
files_in(dir, 'onionshare_gui/mode') + \
|
||||||
files_in(dir, 'onionshare_gui/mode/share_mode') + \
|
files_in(dir, 'onionshare_gui/mode/share_mode') + \
|
||||||
files_in(dir, 'onionshare_gui/mode/receive_mode') + \
|
files_in(dir, 'onionshare_gui/mode/receive_mode') + \
|
||||||
|
files_in(dir, 'onionshare_gui/mode/website_mode') + \
|
||||||
files_in(dir, 'install/scripts') + \
|
files_in(dir, 'install/scripts') + \
|
||||||
files_in(dir, 'tests')
|
files_in(dir, 'tests')
|
||||||
pysrc = [p for p in src if p.endswith('.py')]
|
pysrc = [p for p in src if p.endswith('.py')]
|
||||||
|
@ -3,6 +3,7 @@ certifi==2019.3.9
|
|||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
Click==7.0
|
Click==7.0
|
||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
|
Flask-HTTPAuth==3.2.4
|
||||||
future==0.17.1
|
future==0.17.1
|
||||||
idna==2.8
|
idna==2.8
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
|
@ -27,6 +27,15 @@ from .web import Web
|
|||||||
from .onion import *
|
from .onion import *
|
||||||
from .onionshare import OnionShare
|
from .onionshare import OnionShare
|
||||||
|
|
||||||
|
|
||||||
|
def build_url(common, app, web):
|
||||||
|
# Build the URL
|
||||||
|
if common.settings.get('public_mode'):
|
||||||
|
return 'http://{0:s}'.format(app.onion_host)
|
||||||
|
else:
|
||||||
|
return 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host)
|
||||||
|
|
||||||
|
|
||||||
def main(cwd=None):
|
def main(cwd=None):
|
||||||
"""
|
"""
|
||||||
The main() function implements all of the logic that the command-line version of
|
The main() function implements all of the logic that the command-line version of
|
||||||
@ -51,6 +60,7 @@ def main(cwd=None):
|
|||||||
parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
|
parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
|
||||||
parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
|
parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
|
||||||
parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
|
parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
|
||||||
|
parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website")
|
||||||
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
|
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
|
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
|
||||||
parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
|
parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
|
||||||
@ -68,10 +78,13 @@ def main(cwd=None):
|
|||||||
connect_timeout = int(args.connect_timeout)
|
connect_timeout = int(args.connect_timeout)
|
||||||
stealth = bool(args.stealth)
|
stealth = bool(args.stealth)
|
||||||
receive = bool(args.receive)
|
receive = bool(args.receive)
|
||||||
|
website = bool(args.website)
|
||||||
config = args.config
|
config = args.config
|
||||||
|
|
||||||
if receive:
|
if receive:
|
||||||
mode = 'receive'
|
mode = 'receive'
|
||||||
|
elif website:
|
||||||
|
mode = 'website'
|
||||||
else:
|
else:
|
||||||
mode = 'share'
|
mode = 'share'
|
||||||
|
|
||||||
@ -117,12 +130,13 @@ def main(cwd=None):
|
|||||||
try:
|
try:
|
||||||
common.settings.load()
|
common.settings.load()
|
||||||
if not common.settings.get('public_mode'):
|
if not common.settings.get('public_mode'):
|
||||||
web.generate_slug(common.settings.get('slug'))
|
web.generate_password(common.settings.get('password'))
|
||||||
else:
|
else:
|
||||||
web.slug = None
|
web.password = None
|
||||||
app = OnionShare(common, onion, local_only, autostop_timer)
|
app = OnionShare(common, onion, local_only, autostop_timer)
|
||||||
app.set_stealth(stealth)
|
app.set_stealth(stealth)
|
||||||
app.choose_port()
|
app.choose_port()
|
||||||
|
|
||||||
# Delay the startup if a startup timer was set
|
# Delay the startup if a startup timer was set
|
||||||
if autostart_timer > 0:
|
if autostart_timer > 0:
|
||||||
# Can't set a schedule that is later than the auto-stop timer
|
# Can't set a schedule that is later than the auto-stop timer
|
||||||
@ -131,10 +145,7 @@ def main(cwd=None):
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
app.start_onion_service(False, True)
|
app.start_onion_service(False, True)
|
||||||
if common.settings.get('public_mode'):
|
url = build_url(common, app, web)
|
||||||
url = 'http://{0:s}'.format(app.onion_host)
|
|
||||||
else:
|
|
||||||
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
|
||||||
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
||||||
if mode == 'receive':
|
if mode == 'receive':
|
||||||
print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir')))
|
print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir')))
|
||||||
@ -168,6 +179,14 @@ def main(cwd=None):
|
|||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
if mode == 'website':
|
||||||
|
# Prepare files to share
|
||||||
|
try:
|
||||||
|
web.website_mode.set_file_info(filenames)
|
||||||
|
except OSError as e:
|
||||||
|
print(e.strerror)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if mode == 'share':
|
if mode == 'share':
|
||||||
# Prepare files to share
|
# Prepare files to share
|
||||||
print("Compressing files.")
|
print("Compressing files.")
|
||||||
@ -185,29 +204,26 @@ def main(cwd=None):
|
|||||||
print('')
|
print('')
|
||||||
|
|
||||||
# Start OnionShare http service in new thread
|
# Start OnionShare http service in new thread
|
||||||
t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.slug))
|
t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.password))
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
try: # Trap Ctrl-C
|
try: # Trap Ctrl-C
|
||||||
# Wait for web.generate_slug() to finish running
|
# Wait for web.generate_password() to finish running
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# start auto-stop timer thread
|
# start auto-stop timer thread
|
||||||
if app.autostop_timer > 0:
|
if app.autostop_timer > 0:
|
||||||
app.autostop_timer_thread.start()
|
app.autostop_timer_thread.start()
|
||||||
|
|
||||||
# Save the web slug if we are using a persistent private key
|
# Save the web password if we are using a persistent private key
|
||||||
if common.settings.get('save_private_key'):
|
if common.settings.get('save_private_key'):
|
||||||
if not common.settings.get('slug'):
|
if not common.settings.get('password'):
|
||||||
common.settings.set('slug', web.slug)
|
common.settings.set('password', web.password)
|
||||||
common.settings.save()
|
common.settings.save()
|
||||||
|
|
||||||
# Build the URL
|
# Build the URL
|
||||||
if common.settings.get('public_mode'):
|
url = build_url(common, app, web)
|
||||||
url = 'http://{0:s}'.format(app.onion_host)
|
|
||||||
else:
|
|
||||||
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
if autostart_timer > 0:
|
if autostart_timer > 0:
|
||||||
@ -242,7 +258,7 @@ def main(cwd=None):
|
|||||||
if app.autostop_timer > 0:
|
if app.autostop_timer > 0:
|
||||||
# if the auto-stop timer was set and has run out, stop the server
|
# if the auto-stop timer was set and has run out, stop the server
|
||||||
if not app.autostop_timer_thread.is_alive():
|
if not app.autostop_timer_thread.is_alive():
|
||||||
if mode == 'share':
|
if mode == 'share' or (mode == 'website'):
|
||||||
# If there were no attempts to download the share, or all downloads are done, we can stop
|
# If there were no attempts to download the share, or all downloads are done, we can stop
|
||||||
if web.share_mode.download_count == 0 or web.done:
|
if web.share_mode.download_count == 0 or web.done:
|
||||||
print("Stopped because auto-stop timer ran out")
|
print("Stopped because auto-stop timer ran out")
|
||||||
|
@ -143,7 +143,7 @@ class Common(object):
|
|||||||
os.makedirs(onionshare_data_dir, 0o700, True)
|
os.makedirs(onionshare_data_dir, 0o700, True)
|
||||||
return onionshare_data_dir
|
return onionshare_data_dir
|
||||||
|
|
||||||
def build_slug(self):
|
def build_password(self):
|
||||||
"""
|
"""
|
||||||
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
||||||
"""
|
"""
|
||||||
|
@ -111,7 +111,7 @@ class Settings(object):
|
|||||||
'save_private_key': False,
|
'save_private_key': False,
|
||||||
'private_key': '',
|
'private_key': '',
|
||||||
'public_mode': False,
|
'public_mode': False,
|
||||||
'slug': '',
|
'password': '',
|
||||||
'hidservauth_string': '',
|
'hidservauth_string': '',
|
||||||
'data_dir': self.build_default_data_dir(),
|
'data_dir': self.build_default_data_dir(),
|
||||||
'locale': None # this gets defined in fill_in_defaults()
|
'locale': None # this gets defined in fill_in_defaults()
|
||||||
|
@ -18,6 +18,9 @@ class ReceiveModeWeb(object):
|
|||||||
|
|
||||||
self.web = web
|
self.web = web
|
||||||
|
|
||||||
|
# Reset assets path
|
||||||
|
self.web.app.static_folder=self.common.get_resource_path('static')
|
||||||
|
|
||||||
self.can_upload = True
|
self.can_upload = True
|
||||||
self.upload_count = 0
|
self.upload_count = 0
|
||||||
self.uploads_in_progress = []
|
self.uploads_in_progress = []
|
||||||
@ -28,36 +31,15 @@ class ReceiveModeWeb(object):
|
|||||||
"""
|
"""
|
||||||
The web app routes for receiving files
|
The web app routes for receiving files
|
||||||
"""
|
"""
|
||||||
def index_logic():
|
@self.web.app.route("/")
|
||||||
|
def index():
|
||||||
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||||
|
r = make_response(render_template('receive.html',
|
||||||
if self.common.settings.get('public_mode'):
|
static_url_path=self.web.static_url_path))
|
||||||
upload_action = '/upload'
|
|
||||||
else:
|
|
||||||
upload_action = '/{}/upload'.format(self.web.slug)
|
|
||||||
|
|
||||||
r = make_response(render_template(
|
|
||||||
'receive.html',
|
|
||||||
upload_action=upload_action))
|
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
@self.web.app.route("/<slug_candidate>")
|
@self.web.app.route("/upload", methods=['POST'])
|
||||||
def index(slug_candidate):
|
def upload(ajax=False):
|
||||||
if not self.can_upload:
|
|
||||||
return self.web.error403()
|
|
||||||
self.web.check_slug_candidate(slug_candidate)
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
@self.web.app.route("/")
|
|
||||||
def index_public():
|
|
||||||
if not self.can_upload:
|
|
||||||
return self.web.error403()
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.web.error404()
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
|
|
||||||
def upload_logic(slug_candidate='', ajax=False):
|
|
||||||
"""
|
"""
|
||||||
Handle the upload files POST request, though at this point, the files have
|
Handle the upload files POST request, though at this point, the files have
|
||||||
already been uploaded and saved to their correct locations.
|
already been uploaded and saved to their correct locations.
|
||||||
@ -94,11 +76,7 @@ class ReceiveModeWeb(object):
|
|||||||
return json.dumps({"error_flashes": [msg]})
|
return json.dumps({"error_flashes": [msg]})
|
||||||
else:
|
else:
|
||||||
flash(msg, 'error')
|
flash(msg, 'error')
|
||||||
|
return redirect('/')
|
||||||
if self.common.settings.get('public_mode'):
|
|
||||||
return redirect('/')
|
|
||||||
else:
|
|
||||||
return redirect('/{}'.format(slug_candidate))
|
|
||||||
|
|
||||||
# Note that flash strings are in English, and not translated, on purpose,
|
# Note that flash strings are in English, and not translated, on purpose,
|
||||||
# to avoid leaking the locale of the OnionShare user
|
# to avoid leaking the locale of the OnionShare user
|
||||||
@ -125,48 +103,22 @@ class ReceiveModeWeb(object):
|
|||||||
if ajax:
|
if ajax:
|
||||||
return json.dumps({"info_flashes": info_flashes})
|
return json.dumps({"info_flashes": info_flashes})
|
||||||
else:
|
else:
|
||||||
if self.common.settings.get('public_mode'):
|
return redirect('/')
|
||||||
path = '/'
|
|
||||||
else:
|
|
||||||
path = '/{}'.format(slug_candidate)
|
|
||||||
return redirect('{}'.format(path))
|
|
||||||
else:
|
else:
|
||||||
if ajax:
|
if ajax:
|
||||||
return json.dumps({"new_body": render_template('thankyou.html')})
|
return json.dumps({
|
||||||
|
"new_body": render_template('thankyou.html', static_url_path=self.web.static_url_path)
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
# It was the last upload and the timer ran out
|
# It was the last upload and the timer ran out
|
||||||
r = make_response(render_template('thankyou.html'))
|
r = make_response(render_template('thankyou.html'), static_url_path=self.web.static_url_path)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
@self.web.app.route("/<slug_candidate>/upload", methods=['POST'])
|
|
||||||
def upload(slug_candidate):
|
|
||||||
if not self.can_upload:
|
|
||||||
return self.web.error403()
|
|
||||||
self.web.check_slug_candidate(slug_candidate)
|
|
||||||
return upload_logic(slug_candidate)
|
|
||||||
|
|
||||||
@self.web.app.route("/upload", methods=['POST'])
|
|
||||||
def upload_public():
|
|
||||||
if not self.can_upload:
|
|
||||||
return self.web.error403()
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.web.error404()
|
|
||||||
return upload_logic()
|
|
||||||
|
|
||||||
@self.web.app.route("/<slug_candidate>/upload-ajax", methods=['POST'])
|
|
||||||
def upload_ajax(slug_candidate):
|
|
||||||
if not self.can_upload:
|
|
||||||
return self.web.error403()
|
|
||||||
self.web.check_slug_candidate(slug_candidate)
|
|
||||||
return upload_logic(slug_candidate, ajax=True)
|
|
||||||
|
|
||||||
@self.web.app.route("/upload-ajax", methods=['POST'])
|
@self.web.app.route("/upload-ajax", methods=['POST'])
|
||||||
def upload_ajax_public():
|
def upload_ajax_public():
|
||||||
if not self.can_upload:
|
if not self.can_upload:
|
||||||
return self.web.error403()
|
return self.web.error403()
|
||||||
if not self.common.settings.get('public_mode'):
|
return upload(ajax=True)
|
||||||
return self.web.error404()
|
|
||||||
return upload_logic(ajax=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeWSGIMiddleware(object):
|
class ReceiveModeWSGIMiddleware(object):
|
||||||
@ -269,12 +221,8 @@ class ReceiveModeRequest(Request):
|
|||||||
# Is this a valid upload request?
|
# Is this a valid upload request?
|
||||||
self.upload_request = False
|
self.upload_request = False
|
||||||
if self.method == 'POST':
|
if self.method == 'POST':
|
||||||
if self.web.common.settings.get('public_mode'):
|
if self.path == '/upload' or self.path == '/upload-ajax':
|
||||||
if self.path == '/upload' or self.path == '/upload-ajax':
|
self.upload_request = True
|
||||||
self.upload_request = True
|
|
||||||
else:
|
|
||||||
if self.path == '/{}/upload'.format(self.web.slug) or self.path == '/{}/upload-ajax'.format(self.web.slug):
|
|
||||||
self.upload_request = True
|
|
||||||
|
|
||||||
if self.upload_request:
|
if self.upload_request:
|
||||||
# No errors yet
|
# No errors yet
|
||||||
|
@ -34,24 +34,18 @@ class ShareModeWeb(object):
|
|||||||
# one download at a time.
|
# one download at a time.
|
||||||
self.download_in_progress = False
|
self.download_in_progress = False
|
||||||
|
|
||||||
|
# Reset assets path
|
||||||
|
self.web.app.static_folder=self.common.get_resource_path('static')
|
||||||
|
|
||||||
|
|
||||||
self.define_routes()
|
self.define_routes()
|
||||||
|
|
||||||
def define_routes(self):
|
def define_routes(self):
|
||||||
"""
|
"""
|
||||||
The web app routes for sharing files
|
The web app routes for sharing files
|
||||||
"""
|
"""
|
||||||
@self.web.app.route("/<slug_candidate>")
|
|
||||||
def index(slug_candidate):
|
|
||||||
self.web.check_slug_candidate(slug_candidate)
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
@self.web.app.route("/")
|
@self.web.app.route("/")
|
||||||
def index_public():
|
def index():
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.web.error404()
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
def index_logic(slug_candidate=''):
|
|
||||||
"""
|
"""
|
||||||
Render the template for the onionshare landing page.
|
Render the template for the onionshare landing page.
|
||||||
"""
|
"""
|
||||||
@ -61,7 +55,8 @@ class ShareModeWeb(object):
|
|||||||
# currently a download
|
# currently a download
|
||||||
deny_download = not self.web.stay_open and self.download_in_progress
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
if deny_download:
|
if deny_download:
|
||||||
r = make_response(render_template('denied.html'))
|
r = make_response(render_template('denied.html'),
|
||||||
|
static_url_path=self.web.static_url_path)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
# If download is allowed to continue, serve download page
|
# If download is allowed to continue, serve download page
|
||||||
@ -70,38 +65,18 @@ class ShareModeWeb(object):
|
|||||||
else:
|
else:
|
||||||
self.filesize = self.download_filesize
|
self.filesize = self.download_filesize
|
||||||
|
|
||||||
if self.web.slug:
|
r = make_response(render_template(
|
||||||
r = make_response(render_template(
|
'send.html',
|
||||||
'send.html',
|
file_info=self.file_info,
|
||||||
slug=self.web.slug,
|
filename=os.path.basename(self.download_filename),
|
||||||
file_info=self.file_info,
|
filesize=self.filesize,
|
||||||
filename=os.path.basename(self.download_filename),
|
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
||||||
filesize=self.filesize,
|
is_zipped=self.is_zipped,
|
||||||
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
static_url_path=self.web.static_url_path))
|
||||||
is_zipped=self.is_zipped))
|
|
||||||
else:
|
|
||||||
# If download is allowed to continue, serve download page
|
|
||||||
r = make_response(render_template(
|
|
||||||
'send.html',
|
|
||||||
file_info=self.file_info,
|
|
||||||
filename=os.path.basename(self.download_filename),
|
|
||||||
filesize=self.filesize,
|
|
||||||
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
|
||||||
is_zipped=self.is_zipped))
|
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
@self.web.app.route("/<slug_candidate>/download")
|
|
||||||
def download(slug_candidate):
|
|
||||||
self.web.check_slug_candidate(slug_candidate)
|
|
||||||
return download_logic()
|
|
||||||
|
|
||||||
@self.web.app.route("/download")
|
@self.web.app.route("/download")
|
||||||
def download_public():
|
def download():
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.web.error404()
|
|
||||||
return download_logic()
|
|
||||||
|
|
||||||
def download_logic(slug_candidate=''):
|
|
||||||
"""
|
"""
|
||||||
Download the zip file.
|
Download the zip file.
|
||||||
"""
|
"""
|
||||||
@ -109,7 +84,8 @@ class ShareModeWeb(object):
|
|||||||
# currently a download
|
# currently a download
|
||||||
deny_download = not self.web.stay_open and self.download_in_progress
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
if deny_download:
|
if deny_download:
|
||||||
r = make_response(render_template('denied.html'))
|
r = make_response(render_template('denied.html',
|
||||||
|
static_url_path=self.web.static_url_path))
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
# Each download has a unique id
|
# Each download has a unique id
|
||||||
|
@ -5,17 +5,19 @@ import queue
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import requests
|
||||||
from distutils.version import LooseVersion as Version
|
from distutils.version import LooseVersion as Version
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version
|
from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version
|
||||||
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
|
||||||
from .. import strings
|
from .. import strings
|
||||||
|
|
||||||
from .share_mode import ShareModeWeb
|
from .share_mode import ShareModeWeb
|
||||||
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
||||||
|
from .website_mode import WebsiteModeWeb
|
||||||
|
|
||||||
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||||
# are not applicable to OnionShare
|
# are not applicable to OnionShare
|
||||||
@ -43,6 +45,7 @@ class Web(object):
|
|||||||
REQUEST_UPLOAD_FINISHED = 8
|
REQUEST_UPLOAD_FINISHED = 8
|
||||||
REQUEST_UPLOAD_CANCELED = 9
|
REQUEST_UPLOAD_CANCELED = 9
|
||||||
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10
|
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10
|
||||||
|
REQUEST_INVALID_PASSWORD = 11
|
||||||
|
|
||||||
def __init__(self, common, is_gui, mode='share'):
|
def __init__(self, common, is_gui, mode='share'):
|
||||||
self.common = common
|
self.common = common
|
||||||
@ -53,6 +56,9 @@ class Web(object):
|
|||||||
static_folder=self.common.get_resource_path('static'),
|
static_folder=self.common.get_resource_path('static'),
|
||||||
template_folder=self.common.get_resource_path('templates'))
|
template_folder=self.common.get_resource_path('templates'))
|
||||||
self.app.secret_key = self.common.random_string(8)
|
self.app.secret_key = self.common.random_string(8)
|
||||||
|
self.generate_static_url_path()
|
||||||
|
self.auth = HTTPBasicAuth()
|
||||||
|
self.auth.error_handler(self.error401)
|
||||||
|
|
||||||
# Verbose mode?
|
# Verbose mode?
|
||||||
if self.common.verbose:
|
if self.common.verbose:
|
||||||
@ -92,13 +98,14 @@ class Web(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
self.q = queue.Queue()
|
self.q = queue.Queue()
|
||||||
self.slug = None
|
self.password = None
|
||||||
self.error404_count = 0
|
|
||||||
|
self.reset_invalid_passwords()
|
||||||
|
|
||||||
self.done = False
|
self.done = False
|
||||||
|
|
||||||
# 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
|
||||||
self.shutdown_slug = self.common.random_string(16)
|
self.shutdown_password = self.common.random_string(16)
|
||||||
|
|
||||||
# Keep track if the server is running
|
# Keep track if the server is running
|
||||||
self.running = False
|
self.running = False
|
||||||
@ -111,57 +118,79 @@ class Web(object):
|
|||||||
self.receive_mode = None
|
self.receive_mode = None
|
||||||
if self.mode == 'receive':
|
if self.mode == 'receive':
|
||||||
self.receive_mode = ReceiveModeWeb(self.common, self)
|
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||||
|
elif self.mode == 'website':
|
||||||
|
self.website_mode = WebsiteModeWeb(self.common, self)
|
||||||
elif self.mode == 'share':
|
elif self.mode == 'share':
|
||||||
self.share_mode = ShareModeWeb(self.common, self)
|
self.share_mode = ShareModeWeb(self.common, self)
|
||||||
|
|
||||||
|
|
||||||
def define_common_routes(self):
|
def define_common_routes(self):
|
||||||
"""
|
"""
|
||||||
Common web app routes between sending and receiving
|
Common web app routes between all modes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@self.auth.get_password
|
||||||
|
def get_pw(username):
|
||||||
|
if username == 'onionshare':
|
||||||
|
return self.password
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@self.app.before_request
|
||||||
|
def conditional_auth_check():
|
||||||
|
# Allow static files without basic authentication
|
||||||
|
if(request.path.startswith(self.static_url_path + '/')):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If public mode is disabled, require authentication
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
@self.auth.login_required
|
||||||
|
def _check_login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return _check_login()
|
||||||
|
|
||||||
@self.app.errorhandler(404)
|
@self.app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def not_found(e):
|
||||||
"""
|
|
||||||
404 error page.
|
|
||||||
"""
|
|
||||||
return self.error404()
|
return self.error404()
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>/shutdown")
|
@self.app.route("/<password_candidate>/shutdown")
|
||||||
def shutdown(slug_candidate):
|
def shutdown(password_candidate):
|
||||||
"""
|
"""
|
||||||
Stop the flask web server, from the context of an http request.
|
Stop the flask web server, from the context of an http request.
|
||||||
"""
|
"""
|
||||||
self.check_shutdown_slug_candidate(slug_candidate)
|
if password_candidate == self.shutdown_password:
|
||||||
self.force_shutdown()
|
self.force_shutdown()
|
||||||
return ""
|
return ""
|
||||||
|
abort(404)
|
||||||
|
|
||||||
@self.app.route("/noscript-xss-instructions")
|
def error401(self):
|
||||||
def noscript_xss_instructions():
|
auth = request.authorization
|
||||||
"""
|
if auth:
|
||||||
Display instructions for disabling Tor Browser's NoScript XSS setting
|
if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_passwords:
|
||||||
"""
|
print('Invalid password guess: {}'.format(auth['password']))
|
||||||
r = make_response(render_template('receive_noscript_xss.html'))
|
self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth['password'])
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
self.invalid_passwords.append(auth['password'])
|
||||||
|
self.invalid_passwords_count += 1
|
||||||
|
|
||||||
|
if self.invalid_passwords_count == 20:
|
||||||
|
self.add_request(Web.REQUEST_RATE_LIMIT)
|
||||||
|
self.force_shutdown()
|
||||||
|
print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.")
|
||||||
|
|
||||||
|
r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401)
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def error404(self):
|
def error404(self):
|
||||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||||
if request.path != '/favicon.ico':
|
r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
|
||||||
self.error404_count += 1
|
|
||||||
|
|
||||||
# In receive mode, with public mode enabled, skip rate limiting 404s
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
if self.error404_count == 20:
|
|
||||||
self.add_request(Web.REQUEST_RATE_LIMIT, request.path)
|
|
||||||
self.force_shutdown()
|
|
||||||
print("Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.")
|
|
||||||
|
|
||||||
r = make_response(render_template('404.html'), 404)
|
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def error403(self):
|
def error403(self):
|
||||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||||
|
|
||||||
r = make_response(render_template('403.html'), 403)
|
r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def add_security_headers(self, r):
|
def add_security_headers(self, r):
|
||||||
@ -177,7 +206,7 @@ class Web(object):
|
|||||||
return True
|
return True
|
||||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||||
|
|
||||||
def add_request(self, request_type, path, data=None):
|
def add_request(self, request_type, path=None, data=None):
|
||||||
"""
|
"""
|
||||||
Add a request to the queue, to communicate with the GUI.
|
Add a request to the queue, to communicate with the GUI.
|
||||||
"""
|
"""
|
||||||
@ -187,14 +216,26 @@ class Web(object):
|
|||||||
'data': data
|
'data': data
|
||||||
})
|
})
|
||||||
|
|
||||||
def generate_slug(self, persistent_slug=None):
|
def generate_password(self, persistent_password=None):
|
||||||
self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug))
|
self.common.log('Web', 'generate_password', 'persistent_password={}'.format(persistent_password))
|
||||||
if persistent_slug != None and persistent_slug != '':
|
if persistent_password != None and persistent_password != '':
|
||||||
self.slug = persistent_slug
|
self.password = persistent_password
|
||||||
self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug))
|
self.common.log('Web', 'generate_password', 'persistent_password sent, so password is: "{}"'.format(self.password))
|
||||||
else:
|
else:
|
||||||
self.slug = self.common.build_slug()
|
self.password = self.common.build_password()
|
||||||
self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug))
|
self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password))
|
||||||
|
|
||||||
|
def generate_static_url_path(self):
|
||||||
|
# The static URL path has a 128-bit random number in it to avoid having name
|
||||||
|
# collisions with files that might be getting shared
|
||||||
|
self.static_url_path = '/static_{}'.format(self.common.random_string(16))
|
||||||
|
self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path))
|
||||||
|
|
||||||
|
# Update the flask route to handle the new static URL path
|
||||||
|
self.app.static_url_path = self.static_url_path
|
||||||
|
self.app.add_url_rule(
|
||||||
|
self.static_url_path + '/<path:filename>',
|
||||||
|
endpoint='static', view_func=self.app.send_static_file)
|
||||||
|
|
||||||
def verbose_mode(self):
|
def verbose_mode(self):
|
||||||
"""
|
"""
|
||||||
@ -205,17 +246,9 @@ class Web(object):
|
|||||||
log_handler.setLevel(logging.WARNING)
|
log_handler.setLevel(logging.WARNING)
|
||||||
self.app.logger.addHandler(log_handler)
|
self.app.logger.addHandler(log_handler)
|
||||||
|
|
||||||
def check_slug_candidate(self, slug_candidate):
|
def reset_invalid_passwords(self):
|
||||||
self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
self.invalid_passwords_count = 0
|
||||||
if self.common.settings.get('public_mode'):
|
self.invalid_passwords = []
|
||||||
abort(404)
|
|
||||||
if not hmac.compare_digest(self.slug, slug_candidate):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
def check_shutdown_slug_candidate(self, slug_candidate):
|
|
||||||
self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
|
||||||
if not hmac.compare_digest(self.shutdown_slug, slug_candidate):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
def force_shutdown(self):
|
def force_shutdown(self):
|
||||||
"""
|
"""
|
||||||
@ -231,11 +264,11 @@ class Web(object):
|
|||||||
pass
|
pass
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def start(self, port, stay_open=False, public_mode=False, slug=None):
|
def start(self, port, stay_open=False, public_mode=False, password=None):
|
||||||
"""
|
"""
|
||||||
Start the flask web server.
|
Start the flask web server.
|
||||||
"""
|
"""
|
||||||
self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, slug={}'.format(port, stay_open, public_mode, slug))
|
self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, password={}'.format(port, stay_open, public_mode, password))
|
||||||
|
|
||||||
self.stay_open = stay_open
|
self.stay_open = stay_open
|
||||||
|
|
||||||
@ -264,17 +297,11 @@ class Web(object):
|
|||||||
# Let the mode know that the user stopped the server
|
# Let the mode know that the user stopped the server
|
||||||
self.stop_q.put(True)
|
self.stop_q.put(True)
|
||||||
|
|
||||||
# Reset any slug that was in use
|
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
|
||||||
self.slug = None
|
# (We're putting the shutdown_password in the path as well to make routing simpler)
|
||||||
|
|
||||||
# To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
|
||||||
if self.running:
|
if self.running:
|
||||||
try:
|
requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_password),
|
||||||
s = socket.socket()
|
auth=requests.auth.HTTPBasicAuth('onionshare', self.password))
|
||||||
s.connect(('127.0.0.1', port))
|
|
||||||
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug))
|
# Reset any password that was in use
|
||||||
except:
|
self.password = None
|
||||||
try:
|
|
||||||
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
181
onionshare/web/website_mode.py
Normal file
181
onionshare/web/website_mode.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import mimetypes
|
||||||
|
from flask import Response, request, render_template, make_response, send_from_directory
|
||||||
|
|
||||||
|
from .. import strings
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteModeWeb(object):
|
||||||
|
"""
|
||||||
|
All of the web logic for share mode
|
||||||
|
"""
|
||||||
|
def __init__(self, common, web):
|
||||||
|
self.common = common
|
||||||
|
self.common.log('WebsiteModeWeb', '__init__')
|
||||||
|
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
# Dictionary mapping file paths to filenames on disk
|
||||||
|
self.files = {}
|
||||||
|
self.visit_count = 0
|
||||||
|
|
||||||
|
# Reset assets path
|
||||||
|
self.web.app.static_folder=self.common.get_resource_path('static')
|
||||||
|
|
||||||
|
self.define_routes()
|
||||||
|
|
||||||
|
def define_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for sharing a website
|
||||||
|
"""
|
||||||
|
|
||||||
|
@self.web.app.route('/', defaults={'path': ''})
|
||||||
|
@self.web.app.route('/<path:path>')
|
||||||
|
def path_public(path):
|
||||||
|
return path_logic(path)
|
||||||
|
|
||||||
|
def path_logic(path=''):
|
||||||
|
"""
|
||||||
|
Render the onionshare website.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Each download has a unique id
|
||||||
|
visit_id = self.visit_count
|
||||||
|
self.visit_count += 1
|
||||||
|
|
||||||
|
# Tell GUI the page has been visited
|
||||||
|
self.web.add_request(self.web.REQUEST_STARTED, path, {
|
||||||
|
'id': visit_id,
|
||||||
|
'action': 'visit'
|
||||||
|
})
|
||||||
|
|
||||||
|
if path in self.files:
|
||||||
|
filesystem_path = self.files[path]
|
||||||
|
|
||||||
|
# If it's a directory
|
||||||
|
if os.path.isdir(filesystem_path):
|
||||||
|
# Is there an index.html?
|
||||||
|
index_path = os.path.join(path, 'index.html')
|
||||||
|
if index_path in self.files:
|
||||||
|
# Render it
|
||||||
|
dirname = os.path.dirname(self.files[index_path])
|
||||||
|
basename = os.path.basename(self.files[index_path])
|
||||||
|
return send_from_directory(dirname, basename)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Otherwise, render directory listing
|
||||||
|
filenames = []
|
||||||
|
for filename in os.listdir(filesystem_path):
|
||||||
|
if os.path.isdir(os.path.join(filesystem_path, filename)):
|
||||||
|
filenames.append(filename + '/')
|
||||||
|
else:
|
||||||
|
filenames.append(filename)
|
||||||
|
filenames.sort()
|
||||||
|
return self.directory_listing(path, filenames, filesystem_path)
|
||||||
|
|
||||||
|
# If it's a file
|
||||||
|
elif os.path.isfile(filesystem_path):
|
||||||
|
dirname = os.path.dirname(filesystem_path)
|
||||||
|
basename = os.path.basename(filesystem_path)
|
||||||
|
return send_from_directory(dirname, basename)
|
||||||
|
|
||||||
|
# If it's not a directory or file, throw a 404
|
||||||
|
else:
|
||||||
|
return self.web.error404()
|
||||||
|
else:
|
||||||
|
# Special case loading /
|
||||||
|
if path == '':
|
||||||
|
index_path = 'index.html'
|
||||||
|
if index_path in self.files:
|
||||||
|
# Render it
|
||||||
|
dirname = os.path.dirname(self.files[index_path])
|
||||||
|
basename = os.path.basename(self.files[index_path])
|
||||||
|
return send_from_directory(dirname, basename)
|
||||||
|
else:
|
||||||
|
# Root directory listing
|
||||||
|
filenames = list(self.root_files)
|
||||||
|
filenames.sort()
|
||||||
|
return self.directory_listing(path, filenames)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If the path isn't found, throw a 404
|
||||||
|
return self.web.error404()
|
||||||
|
|
||||||
|
def directory_listing(self, path, filenames, filesystem_path=None):
|
||||||
|
# If filesystem_path is None, this is the root directory listing
|
||||||
|
files = []
|
||||||
|
dirs = []
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
if filesystem_path:
|
||||||
|
this_filesystem_path = os.path.join(filesystem_path, filename)
|
||||||
|
else:
|
||||||
|
this_filesystem_path = self.files[filename]
|
||||||
|
|
||||||
|
is_dir = os.path.isdir(this_filesystem_path)
|
||||||
|
|
||||||
|
if is_dir:
|
||||||
|
dirs.append({
|
||||||
|
'basename': filename
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
size = os.path.getsize(this_filesystem_path)
|
||||||
|
size_human = self.common.human_readable_filesize(size)
|
||||||
|
files.append({
|
||||||
|
'basename': filename,
|
||||||
|
'size_human': size_human
|
||||||
|
})
|
||||||
|
|
||||||
|
r = make_response(render_template('listing.html',
|
||||||
|
path=path,
|
||||||
|
files=files,
|
||||||
|
dirs=dirs,
|
||||||
|
static_url_path=self.web.static_url_path))
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
def set_file_info(self, filenames):
|
||||||
|
"""
|
||||||
|
Build a data structure that describes the list of files that make up
|
||||||
|
the static website.
|
||||||
|
"""
|
||||||
|
self.common.log("WebsiteModeWeb", "set_file_info")
|
||||||
|
|
||||||
|
# This is a dictionary that maps HTTP routes to filenames on disk
|
||||||
|
self.files = {}
|
||||||
|
|
||||||
|
# This is only the root files and dirs, as opposed to all of them
|
||||||
|
self.root_files = {}
|
||||||
|
|
||||||
|
# If there's just one folder, replace filenames with a list of files inside that folder
|
||||||
|
if len(filenames) == 1 and os.path.isdir(filenames[0]):
|
||||||
|
filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])]
|
||||||
|
|
||||||
|
# Loop through the files
|
||||||
|
for filename in filenames:
|
||||||
|
basename = os.path.basename(filename.rstrip('/'))
|
||||||
|
|
||||||
|
# If it's a filename, add it
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
self.files[basename] = filename
|
||||||
|
self.root_files[basename] = filename
|
||||||
|
|
||||||
|
# If it's a directory, add it recursively
|
||||||
|
elif os.path.isdir(filename):
|
||||||
|
self.root_files[basename + '/'] = filename
|
||||||
|
|
||||||
|
for root, _, nested_filenames in os.walk(filename):
|
||||||
|
# Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
|
||||||
|
# and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
|
||||||
|
# The normalized_root should be "some_folder/foobar"
|
||||||
|
normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/')
|
||||||
|
|
||||||
|
# Add the dir itself
|
||||||
|
self.files[normalized_root + '/'] = root
|
||||||
|
|
||||||
|
# Add the files in this dir
|
||||||
|
for nested_filename in nested_filenames:
|
||||||
|
self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename)
|
||||||
|
|
||||||
|
return True
|
@ -24,7 +24,7 @@ from onionshare.common import AutoStopTimer
|
|||||||
|
|
||||||
from ..server_status import ServerStatus
|
from ..server_status import ServerStatus
|
||||||
from ..threads import OnionThread
|
from ..threads import OnionThread
|
||||||
from ..threads import AutoStartTimer
|
from ..threads import AutoStartTimer
|
||||||
from ..widgets import Alert
|
from ..widgets import Alert
|
||||||
|
|
||||||
class Mode(QtWidgets.QWidget):
|
class Mode(QtWidgets.QWidget):
|
||||||
@ -181,7 +181,7 @@ class Mode(QtWidgets.QWidget):
|
|||||||
self.app.port = None
|
self.app.port = None
|
||||||
|
|
||||||
# Start the onion thread. If this share was scheduled for a future date,
|
# Start the onion thread. If this share was scheduled for a future date,
|
||||||
# the OnionThread will start and exit 'early' to obtain the port, slug
|
# the OnionThread will start and exit 'early' to obtain the port, password
|
||||||
# and onion address, but it will not start the WebThread yet.
|
# and onion address, but it will not start the WebThread yet.
|
||||||
if self.server_status.autostart_timer_datetime:
|
if self.server_status.autostart_timer_datetime:
|
||||||
self.start_onion_thread(obtain_onion_early=True)
|
self.start_onion_thread(obtain_onion_early=True)
|
||||||
|
@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
|||||||
|
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
|
|
||||||
from ...widgets import Alert, AddFileDialog
|
from ..widgets import Alert, AddFileDialog
|
||||||
|
|
||||||
class DropHereLabel(QtWidgets.QLabel):
|
class DropHereLabel(QtWidgets.QLabel):
|
||||||
"""
|
"""
|
@ -341,6 +341,35 @@ class ReceiveHistoryItem(HistoryItem):
|
|||||||
self.label.setText(self.get_canceled_label_text(self.started))
|
self.label.setText(self.get_canceled_label_text(self.started))
|
||||||
|
|
||||||
|
|
||||||
|
class VisitHistoryItem(HistoryItem):
|
||||||
|
"""
|
||||||
|
Download history item, for share mode
|
||||||
|
"""
|
||||||
|
def __init__(self, common, id, total_bytes):
|
||||||
|
super(VisitHistoryItem, self).__init__()
|
||||||
|
self.status = HistoryItem.STATUS_STARTED
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.id = id
|
||||||
|
self.visited = time.time()
|
||||||
|
self.visited_dt = datetime.fromtimestamp(self.visited)
|
||||||
|
|
||||||
|
# Label
|
||||||
|
self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.visited_dt.strftime("%b %d, %I:%M%p")))
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(self.label)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.label.setText(self.get_finished_label_text(self.started_dt))
|
||||||
|
self.status = HistoryItem.STATUS_FINISHED
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.progress_bar.setFormat(strings._('gui_canceled'))
|
||||||
|
self.status = HistoryItem.STATUS_CANCELED
|
||||||
|
|
||||||
class HistoryItemList(QtWidgets.QScrollArea):
|
class HistoryItemList(QtWidgets.QScrollArea):
|
||||||
"""
|
"""
|
||||||
List of items
|
List of items
|
||||||
@ -404,19 +433,19 @@ class HistoryItemList(QtWidgets.QScrollArea):
|
|||||||
Reset all items, emptying the list. Override this method.
|
Reset all items, emptying the list. Override this method.
|
||||||
"""
|
"""
|
||||||
for key, item in self.items.copy().items():
|
for key, item in self.items.copy().items():
|
||||||
if item.status != HistoryItem.STATUS_STARTED:
|
self.items_layout.removeWidget(item)
|
||||||
self.items_layout.removeWidget(item)
|
item.close()
|
||||||
item.close()
|
del self.items[key]
|
||||||
del self.items[key]
|
|
||||||
|
|
||||||
class History(QtWidgets.QWidget):
|
class History(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
A history of what's happened so far in this mode. This contains an internal
|
A history of what's happened so far in this mode. This contains an internal
|
||||||
object full of a scrollable list of items.
|
object full of a scrollable list of items.
|
||||||
"""
|
"""
|
||||||
def __init__(self, common, empty_image, empty_text, header_text):
|
def __init__(self, common, empty_image, empty_text, header_text, mode=''):
|
||||||
super(History, self).__init__()
|
super(History, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
self.setMinimumWidth(350)
|
self.setMinimumWidth(350)
|
||||||
|
|
||||||
@ -535,12 +564,14 @@ class History(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
Update the 'in progress' widget.
|
Update the 'in progress' widget.
|
||||||
"""
|
"""
|
||||||
if self.in_progress_count == 0:
|
if self.mode != 'website':
|
||||||
image = self.common.get_resource_path('images/share_in_progress_none.png')
|
if self.in_progress_count == 0:
|
||||||
else:
|
image = self.common.get_resource_path('images/share_in_progress_none.png')
|
||||||
image = self.common.get_resource_path('images/share_in_progress.png')
|
else:
|
||||||
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
|
image = self.common.get_resource_path('images/share_in_progress.png')
|
||||||
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
|
|
||||||
|
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
|
||||||
|
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
|
||||||
|
|
||||||
|
|
||||||
class ToggleHistory(QtWidgets.QPushButton):
|
class ToggleHistory(QtWidgets.QPushButton):
|
||||||
|
@ -113,7 +113,7 @@ class ReceiveMode(Mode):
|
|||||||
"""
|
"""
|
||||||
# Reset web counters
|
# Reset web counters
|
||||||
self.web.receive_mode.upload_count = 0
|
self.web.receive_mode.upload_count = 0
|
||||||
self.web.error404_count = 0
|
self.web.reset_invalid_passwords()
|
||||||
|
|
||||||
# Hide and reset the uploads if we have previously shared
|
# Hide and reset the uploads if we have previously shared
|
||||||
self.reset_info_counters()
|
self.reset_info_counters()
|
||||||
|
@ -25,7 +25,7 @@ from onionshare.onion import *
|
|||||||
from onionshare.common import Common
|
from onionshare.common import Common
|
||||||
from onionshare.web import Web
|
from onionshare.web import Web
|
||||||
|
|
||||||
from .file_selection import FileSelection
|
from ..file_selection import FileSelection
|
||||||
from .threads import CompressThread
|
from .threads import CompressThread
|
||||||
from .. import Mode
|
from .. import Mode
|
||||||
from ..history import History, ToggleHistory, ShareHistoryItem
|
from ..history import History, ToggleHistory, ShareHistoryItem
|
||||||
@ -147,7 +147,7 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
# Reset web counters
|
# Reset web counters
|
||||||
self.web.share_mode.download_count = 0
|
self.web.share_mode.download_count = 0
|
||||||
self.web.error404_count = 0
|
self.web.reset_invalid_passwords()
|
||||||
|
|
||||||
# Hide and reset the downloads if we have previously shared
|
# Hide and reset the downloads if we have previously shared
|
||||||
self.reset_info_counters()
|
self.reset_info_counters()
|
||||||
|
274
onionshare_gui/mode/website_mode/__init__.py
Normal file
274
onionshare_gui/mode/website_mode/__init__.py
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
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
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from onionshare import strings
|
||||||
|
from onionshare.onion import *
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
|
||||||
|
from ..file_selection import FileSelection
|
||||||
|
from .. import Mode
|
||||||
|
from ..history import History, ToggleHistory, VisitHistoryItem
|
||||||
|
from ...widgets import Alert
|
||||||
|
|
||||||
|
class WebsiteMode(Mode):
|
||||||
|
"""
|
||||||
|
Parts of the main window UI for sharing files.
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
"""
|
||||||
|
Custom initialization for ReceiveMode.
|
||||||
|
"""
|
||||||
|
# Create the Web object
|
||||||
|
self.web = Web(self.common, True, 'website')
|
||||||
|
|
||||||
|
# File selection
|
||||||
|
self.file_selection = FileSelection(self.common, self)
|
||||||
|
if self.filenames:
|
||||||
|
for filename in self.filenames:
|
||||||
|
self.file_selection.file_list.add_file(filename)
|
||||||
|
|
||||||
|
# Server status
|
||||||
|
self.server_status.set_mode('website', self.file_selection)
|
||||||
|
self.server_status.server_started.connect(self.file_selection.server_started)
|
||||||
|
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
|
||||||
|
self.server_status.server_stopped.connect(self.update_primary_action)
|
||||||
|
self.server_status.server_canceled.connect(self.file_selection.server_stopped)
|
||||||
|
self.server_status.server_canceled.connect(self.update_primary_action)
|
||||||
|
self.file_selection.file_list.files_updated.connect(self.server_status.update)
|
||||||
|
self.file_selection.file_list.files_updated.connect(self.update_primary_action)
|
||||||
|
# Tell server_status about web, then update
|
||||||
|
self.server_status.web = self.web
|
||||||
|
self.server_status.update()
|
||||||
|
|
||||||
|
# Filesize warning
|
||||||
|
self.filesize_warning = QtWidgets.QLabel()
|
||||||
|
self.filesize_warning.setWordWrap(True)
|
||||||
|
self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning'])
|
||||||
|
self.filesize_warning.hide()
|
||||||
|
|
||||||
|
# Download history
|
||||||
|
self.history = History(
|
||||||
|
self.common,
|
||||||
|
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))),
|
||||||
|
strings._('gui_website_mode_no_files'),
|
||||||
|
strings._('gui_all_modes_history'),
|
||||||
|
'website'
|
||||||
|
)
|
||||||
|
self.history.hide()
|
||||||
|
|
||||||
|
# Info label
|
||||||
|
self.info_label = QtWidgets.QLabel()
|
||||||
|
self.info_label.hide()
|
||||||
|
|
||||||
|
# Toggle history
|
||||||
|
self.toggle_history = ToggleHistory(
|
||||||
|
self.common, self, self.history,
|
||||||
|
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')),
|
||||||
|
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Top bar
|
||||||
|
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||||
|
top_bar_layout.addWidget(self.info_label)
|
||||||
|
top_bar_layout.addStretch()
|
||||||
|
top_bar_layout.addWidget(self.toggle_history)
|
||||||
|
|
||||||
|
# Primary action layout
|
||||||
|
self.primary_action_layout.addWidget(self.filesize_warning)
|
||||||
|
self.primary_action.hide()
|
||||||
|
self.update_primary_action()
|
||||||
|
|
||||||
|
# Main layout
|
||||||
|
self.main_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.main_layout.addLayout(top_bar_layout)
|
||||||
|
self.main_layout.addLayout(self.file_selection)
|
||||||
|
self.main_layout.addWidget(self.primary_action)
|
||||||
|
self.main_layout.addWidget(self.min_width_widget)
|
||||||
|
|
||||||
|
# Wrapper layout
|
||||||
|
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||||
|
self.wrapper_layout.addLayout(self.main_layout)
|
||||||
|
self.wrapper_layout.addWidget(self.history, stretch=1)
|
||||||
|
self.setLayout(self.wrapper_layout)
|
||||||
|
|
||||||
|
# Always start with focus on file selection
|
||||||
|
self.file_selection.setFocus()
|
||||||
|
|
||||||
|
def get_stop_server_autostop_timer_text(self):
|
||||||
|
"""
|
||||||
|
Return the string to put on the stop server button, if there's an auto-stop timer
|
||||||
|
"""
|
||||||
|
return strings._('gui_share_stop_server_autostop_timer')
|
||||||
|
|
||||||
|
def autostop_timer_finished_should_stop_server(self):
|
||||||
|
"""
|
||||||
|
The auto-stop timer expired, should we stop the server? Returns a bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.server_status.stop_server()
|
||||||
|
self.server_status_label.setText(strings._('close_on_autostop_timer'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def start_server_custom(self):
|
||||||
|
"""
|
||||||
|
Starting the server.
|
||||||
|
"""
|
||||||
|
# Reset web counters
|
||||||
|
self.web.website_mode.visit_count = 0
|
||||||
|
self.web.reset_invalid_passwords()
|
||||||
|
|
||||||
|
# Hide and reset the downloads if we have previously shared
|
||||||
|
self.reset_info_counters()
|
||||||
|
|
||||||
|
def start_server_step2_custom(self):
|
||||||
|
"""
|
||||||
|
Step 2 in starting the server. Zipping up files.
|
||||||
|
"""
|
||||||
|
self.filenames = []
|
||||||
|
for index in range(self.file_selection.file_list.count()):
|
||||||
|
self.filenames.append(self.file_selection.file_list.item(index).filename)
|
||||||
|
|
||||||
|
# Continue
|
||||||
|
self.starting_server_step3.emit()
|
||||||
|
self.start_server_finished.emit()
|
||||||
|
|
||||||
|
|
||||||
|
def start_server_step3_custom(self):
|
||||||
|
"""
|
||||||
|
Step 3 in starting the server. Display large filesize
|
||||||
|
warning, if applicable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.web.website_mode.set_file_info(self.filenames):
|
||||||
|
self.success.emit()
|
||||||
|
else:
|
||||||
|
# Cancelled
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_server_error_custom(self):
|
||||||
|
"""
|
||||||
|
Start server error.
|
||||||
|
"""
|
||||||
|
if self._zip_progress_bar is not None:
|
||||||
|
self.status_bar.removeWidget(self._zip_progress_bar)
|
||||||
|
self._zip_progress_bar = None
|
||||||
|
|
||||||
|
def stop_server_custom(self):
|
||||||
|
"""
|
||||||
|
Stop server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.filesize_warning.hide()
|
||||||
|
self.history.completed_count = 0
|
||||||
|
self.file_selection.file_list.adjustSize()
|
||||||
|
|
||||||
|
def cancel_server_custom(self):
|
||||||
|
"""
|
||||||
|
Log that the server has been cancelled
|
||||||
|
"""
|
||||||
|
self.common.log('WebsiteMode', 'cancel_server')
|
||||||
|
|
||||||
|
|
||||||
|
def handle_tor_broke_custom(self):
|
||||||
|
"""
|
||||||
|
Connection to Tor broke.
|
||||||
|
"""
|
||||||
|
self.primary_action.hide()
|
||||||
|
|
||||||
|
def handle_request_load(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_LOAD event.
|
||||||
|
"""
|
||||||
|
self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message'))
|
||||||
|
|
||||||
|
def handle_request_started(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_STARTED event.
|
||||||
|
"""
|
||||||
|
if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ):
|
||||||
|
item = VisitHistoryItem(self.common, event["data"]["id"], 0)
|
||||||
|
|
||||||
|
self.history.add(event["data"]["id"], item)
|
||||||
|
self.toggle_history.update_indicator(True)
|
||||||
|
self.history.completed_count += 1
|
||||||
|
self.history.update_completed()
|
||||||
|
|
||||||
|
self.system_tray.showMessage(strings._('systray_website_started_title'), strings._('systray_website_started_message'))
|
||||||
|
|
||||||
|
|
||||||
|
def on_reload_settings(self):
|
||||||
|
"""
|
||||||
|
If there were some files listed for sharing, we should be ok to re-enable
|
||||||
|
the 'Start Sharing' button now.
|
||||||
|
"""
|
||||||
|
if self.server_status.file_selection.get_num_files() > 0:
|
||||||
|
self.primary_action.show()
|
||||||
|
self.info_label.show()
|
||||||
|
|
||||||
|
def update_primary_action(self):
|
||||||
|
self.common.log('WebsiteMode', 'update_primary_action')
|
||||||
|
|
||||||
|
# Show or hide primary action layout
|
||||||
|
file_count = self.file_selection.file_list.count()
|
||||||
|
if file_count > 0:
|
||||||
|
self.primary_action.show()
|
||||||
|
self.info_label.show()
|
||||||
|
|
||||||
|
# Update the file count in the info label
|
||||||
|
total_size_bytes = 0
|
||||||
|
for index in range(self.file_selection.file_list.count()):
|
||||||
|
item = self.file_selection.file_list.item(index)
|
||||||
|
total_size_bytes += item.size_bytes
|
||||||
|
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
||||||
|
|
||||||
|
if file_count > 1:
|
||||||
|
self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
|
||||||
|
else:
|
||||||
|
self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.primary_action.hide()
|
||||||
|
self.info_label.hide()
|
||||||
|
|
||||||
|
def reset_info_counters(self):
|
||||||
|
"""
|
||||||
|
Set the info counters back to zero.
|
||||||
|
"""
|
||||||
|
self.history.reset()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _compute_total_size(filenames):
|
||||||
|
total_size = 0
|
||||||
|
for filename in filenames:
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
total_size += os.path.getsize(filename)
|
||||||
|
if os.path.isdir(filename):
|
||||||
|
total_size += Common.dir_size(filename)
|
||||||
|
return total_size
|
@ -25,6 +25,7 @@ from onionshare.web import Web
|
|||||||
|
|
||||||
from .mode.share_mode import ShareMode
|
from .mode.share_mode import ShareMode
|
||||||
from .mode.receive_mode import ReceiveMode
|
from .mode.receive_mode import ReceiveMode
|
||||||
|
from .mode.website_mode import WebsiteMode
|
||||||
|
|
||||||
from .tor_connection_dialog import TorConnectionDialog
|
from .tor_connection_dialog import TorConnectionDialog
|
||||||
from .settings_dialog import SettingsDialog
|
from .settings_dialog import SettingsDialog
|
||||||
@ -39,6 +40,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
"""
|
"""
|
||||||
MODE_SHARE = 'share'
|
MODE_SHARE = 'share'
|
||||||
MODE_RECEIVE = 'receive'
|
MODE_RECEIVE = 'receive'
|
||||||
|
MODE_WEBSITE = 'website'
|
||||||
|
|
||||||
def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False):
|
def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False):
|
||||||
super(OnionShareGui, self).__init__()
|
super(OnionShareGui, self).__init__()
|
||||||
@ -92,6 +94,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
|
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
|
||||||
self.receive_mode_button.setFixedHeight(50)
|
self.receive_mode_button.setFixedHeight(50)
|
||||||
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
|
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
|
||||||
|
self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button'));
|
||||||
|
self.website_mode_button.setFixedHeight(50)
|
||||||
|
self.website_mode_button.clicked.connect(self.website_mode_clicked)
|
||||||
self.settings_button = QtWidgets.QPushButton()
|
self.settings_button = QtWidgets.QPushButton()
|
||||||
self.settings_button.setDefault(False)
|
self.settings_button.setDefault(False)
|
||||||
self.settings_button.setFixedWidth(40)
|
self.settings_button.setFixedWidth(40)
|
||||||
@ -103,6 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
mode_switcher_layout.setSpacing(0)
|
mode_switcher_layout.setSpacing(0)
|
||||||
mode_switcher_layout.addWidget(self.share_mode_button)
|
mode_switcher_layout.addWidget(self.share_mode_button)
|
||||||
mode_switcher_layout.addWidget(self.receive_mode_button)
|
mode_switcher_layout.addWidget(self.receive_mode_button)
|
||||||
|
mode_switcher_layout.addWidget(self.website_mode_button)
|
||||||
mode_switcher_layout.addWidget(self.settings_button)
|
mode_switcher_layout.addWidget(self.settings_button)
|
||||||
|
|
||||||
# Server status indicator on the status bar
|
# Server status indicator on the status bar
|
||||||
@ -154,6 +160,20 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
||||||
self.receive_mode.set_server_active.connect(self.set_server_active)
|
self.receive_mode.set_server_active.connect(self.set_server_active)
|
||||||
|
|
||||||
|
# Website mode
|
||||||
|
self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames)
|
||||||
|
self.website_mode.init()
|
||||||
|
self.website_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
||||||
|
self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
||||||
|
self.website_mode.start_server_finished.connect(self.update_server_status_indicator)
|
||||||
|
self.website_mode.stop_server_finished.connect(self.update_server_status_indicator)
|
||||||
|
self.website_mode.stop_server_finished.connect(self.stop_server_finished)
|
||||||
|
self.website_mode.start_server_finished.connect(self.clear_message)
|
||||||
|
self.website_mode.server_status.button_clicked.connect(self.clear_message)
|
||||||
|
self.website_mode.server_status.url_copied.connect(self.copy_url)
|
||||||
|
self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
||||||
|
self.website_mode.set_server_active.connect(self.set_server_active)
|
||||||
|
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
self.update_server_status_indicator()
|
self.update_server_status_indicator()
|
||||||
|
|
||||||
@ -162,6 +182,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
contents_layout.setContentsMargins(10, 0, 10, 0)
|
contents_layout.setContentsMargins(10, 0, 10, 0)
|
||||||
contents_layout.addWidget(self.receive_mode)
|
contents_layout.addWidget(self.receive_mode)
|
||||||
contents_layout.addWidget(self.share_mode)
|
contents_layout.addWidget(self.share_mode)
|
||||||
|
contents_layout.addWidget(self.website_mode)
|
||||||
|
|
||||||
layout = QtWidgets.QVBoxLayout()
|
layout = QtWidgets.QVBoxLayout()
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
@ -199,15 +220,27 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
|
||||||
self.receive_mode.hide()
|
self.receive_mode.hide()
|
||||||
self.share_mode.show()
|
self.share_mode.show()
|
||||||
|
self.website_mode.hide()
|
||||||
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
|
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
||||||
|
|
||||||
|
self.receive_mode.hide()
|
||||||
|
self.share_mode.hide()
|
||||||
|
self.website_mode.show()
|
||||||
else:
|
else:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
||||||
|
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
|
||||||
self.share_mode.hide()
|
self.share_mode.hide()
|
||||||
self.receive_mode.show()
|
self.receive_mode.show()
|
||||||
|
self.website_mode.hide()
|
||||||
|
|
||||||
self.update_server_status_indicator()
|
self.update_server_status_indicator()
|
||||||
|
|
||||||
@ -223,6 +256,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.mode = self.MODE_RECEIVE
|
self.mode = self.MODE_RECEIVE
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
|
|
||||||
|
def website_mode_clicked(self):
|
||||||
|
if self.mode != self.MODE_WEBSITE:
|
||||||
|
self.common.log('OnionShareGui', 'website_mode_clicked')
|
||||||
|
self.mode = self.MODE_WEBSITE
|
||||||
|
self.update_mode_switcher()
|
||||||
|
|
||||||
def update_server_status_indicator(self):
|
def update_server_status_indicator(self):
|
||||||
# Set the status image
|
# Set the status image
|
||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
@ -239,6 +278,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
|
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
|
||||||
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
|
# Website mode
|
||||||
|
if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||||
|
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
|
||||||
|
self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
|
||||||
|
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||||
|
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
|
||||||
|
self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
|
||||||
|
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
|
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
||||||
|
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
|
||||||
else:
|
else:
|
||||||
# Receive mode
|
# Receive mode
|
||||||
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||||
@ -317,19 +367,23 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.timer.start(500)
|
self.timer.start(500)
|
||||||
self.share_mode.on_reload_settings()
|
self.share_mode.on_reload_settings()
|
||||||
self.receive_mode.on_reload_settings()
|
self.receive_mode.on_reload_settings()
|
||||||
|
self.website_mode.on_reload_settings()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
|
|
||||||
# If we switched off the auto-stop timer setting, ensure the widget is hidden.
|
# If we switched off the auto-stop timer setting, ensure the widget is hidden.
|
||||||
if not self.common.settings.get('autostop_timer'):
|
if not self.common.settings.get('autostop_timer'):
|
||||||
self.share_mode.server_status.autostop_timer_container.hide()
|
self.share_mode.server_status.autostop_timer_container.hide()
|
||||||
self.receive_mode.server_status.autostop_timer_container.hide()
|
self.receive_mode.server_status.autostop_timer_container.hide()
|
||||||
|
self.website_mode.server_status.autostop_timer_container.hide()
|
||||||
# If we switched off the auto-start timer setting, ensure the widget is hidden.
|
# If we switched off the auto-start timer setting, ensure the widget is hidden.
|
||||||
if not self.common.settings.get('autostart_timer'):
|
if not self.common.settings.get('autostart_timer'):
|
||||||
self.share_mode.server_status.autostart_timer_datetime = None
|
self.share_mode.server_status.autostart_timer_datetime = None
|
||||||
self.receive_mode.server_status.autostart_timer_datetime = None
|
self.receive_mode.server_status.autostart_timer_datetime = None
|
||||||
|
self.website_mode.server_status.autostart_timer_datetime = None
|
||||||
self.share_mode.server_status.autostart_timer_container.hide()
|
self.share_mode.server_status.autostart_timer_container.hide()
|
||||||
self.receive_mode.server_status.autostart_timer_container.hide()
|
self.receive_mode.server_status.autostart_timer_container.hide()
|
||||||
|
self.website_mode.server_status.autostart_timer_container.hide()
|
||||||
|
|
||||||
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
|
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
|
||||||
d.settings_saved.connect(reload_settings)
|
d.settings_saved.connect(reload_settings)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
@ -337,6 +391,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
# When settings close, refresh the server status UI
|
# When settings close, refresh the server status UI
|
||||||
self.share_mode.server_status.update()
|
self.share_mode.server_status.update()
|
||||||
self.receive_mode.server_status.update()
|
self.receive_mode.server_status.update()
|
||||||
|
self.website_mode.server_status.update()
|
||||||
|
|
||||||
def check_for_updates(self):
|
def check_for_updates(self):
|
||||||
"""
|
"""
|
||||||
@ -367,10 +422,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.share_mode.handle_tor_broke()
|
self.share_mode.handle_tor_broke()
|
||||||
self.receive_mode.handle_tor_broke()
|
self.receive_mode.handle_tor_broke()
|
||||||
|
self.website_mode.handle_tor_broke()
|
||||||
|
|
||||||
# Process events from the web object
|
# Process events from the web object
|
||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
mode = self.share_mode
|
mode = self.share_mode
|
||||||
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
|
mode = self.website_mode
|
||||||
else:
|
else:
|
||||||
mode = self.receive_mode
|
mode = self.receive_mode
|
||||||
|
|
||||||
@ -416,8 +474,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))
|
Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))
|
||||||
|
|
||||||
if event["type"] == Web.REQUEST_OTHER:
|
if event["type"] == Web.REQUEST_OTHER:
|
||||||
if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug):
|
if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_password):
|
||||||
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"]))
|
self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"]))
|
||||||
|
|
||||||
|
if event["type"] == Web.REQUEST_INVALID_PASSWORD:
|
||||||
|
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('invalid_password_guess'), event["data"]))
|
||||||
|
|
||||||
mode.timer_callback()
|
mode.timer_callback()
|
||||||
|
|
||||||
@ -450,13 +511,20 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
self.share_mode_button.show()
|
self.share_mode_button.show()
|
||||||
self.receive_mode_button.hide()
|
self.receive_mode_button.hide()
|
||||||
|
self.website_mode_button.hide()
|
||||||
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
|
self.share_mode_button.hide()
|
||||||
|
self.receive_mode_button.hide()
|
||||||
|
self.website_mode_button.show()
|
||||||
else:
|
else:
|
||||||
self.share_mode_button.hide()
|
self.share_mode_button.hide()
|
||||||
self.receive_mode_button.show()
|
self.receive_mode_button.show()
|
||||||
|
self.website_mode_button.hide()
|
||||||
else:
|
else:
|
||||||
self.settings_button.show()
|
self.settings_button.show()
|
||||||
self.share_mode_button.show()
|
self.share_mode_button.show()
|
||||||
self.receive_mode_button.show()
|
self.receive_mode_button.show()
|
||||||
|
self.website_mode_button.show()
|
||||||
|
|
||||||
# Disable settings menu action when server is active
|
# Disable settings menu action when server is active
|
||||||
self.settings_action.setEnabled(not active)
|
self.settings_action.setEnabled(not active)
|
||||||
@ -466,6 +534,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
try:
|
try:
|
||||||
if self.mode == OnionShareGui.MODE_SHARE:
|
if self.mode == OnionShareGui.MODE_SHARE:
|
||||||
server_status = self.share_mode.server_status
|
server_status = self.share_mode.server_status
|
||||||
|
if self.mode == OnionShareGui.MODE_WEBSITE:
|
||||||
|
server_status = self.website_mode.server_status
|
||||||
else:
|
else:
|
||||||
server_status = self.receive_mode.server_status
|
server_status = self.receive_mode.server_status
|
||||||
if server_status.status != server_status.STATUS_STOPPED:
|
if server_status.status != server_status.STATUS_STOPPED:
|
||||||
|
@ -39,6 +39,7 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
|
|
||||||
MODE_SHARE = 'share'
|
MODE_SHARE = 'share'
|
||||||
MODE_RECEIVE = 'receive'
|
MODE_RECEIVE = 'receive'
|
||||||
|
MODE_WEBSITE = 'website'
|
||||||
|
|
||||||
STATUS_STOPPED = 0
|
STATUS_STOPPED = 0
|
||||||
STATUS_WORKING = 1
|
STATUS_WORKING = 1
|
||||||
@ -159,7 +160,7 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
self.mode = share_mode
|
self.mode = share_mode
|
||||||
|
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE):
|
||||||
self.file_selection = file_selection
|
self.file_selection = file_selection
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
@ -207,6 +208,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
|
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
|
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
|
||||||
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
|
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
|
||||||
else:
|
else:
|
||||||
self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
|
self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
|
||||||
|
|
||||||
@ -240,8 +243,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.show_url()
|
self.show_url()
|
||||||
|
|
||||||
if self.common.settings.get('save_private_key'):
|
if self.common.settings.get('save_private_key'):
|
||||||
if not self.common.settings.get('slug'):
|
if not self.common.settings.get('password'):
|
||||||
self.common.settings.set('slug', self.web.slug)
|
self.common.settings.set('password', self.web.password)
|
||||||
self.common.settings.save()
|
self.common.settings.save()
|
||||||
|
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get('autostart_timer'):
|
||||||
@ -258,6 +261,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
# Button
|
# Button
|
||||||
if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0:
|
if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0:
|
||||||
self.server_button.hide()
|
self.server_button.hide()
|
||||||
|
elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0:
|
||||||
|
self.server_button.hide()
|
||||||
else:
|
else:
|
||||||
self.server_button.show()
|
self.server_button.show()
|
||||||
|
|
||||||
@ -266,6 +271,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.server_button.setText(strings._('gui_share_start_server'))
|
self.server_button.setText(strings._('gui_share_start_server'))
|
||||||
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
|
self.server_button.setText(strings._('gui_share_start_server'))
|
||||||
else:
|
else:
|
||||||
self.server_button.setText(strings._('gui_receive_start_server'))
|
self.server_button.setText(strings._('gui_receive_start_server'))
|
||||||
self.server_button.setToolTip('')
|
self.server_button.setToolTip('')
|
||||||
@ -278,6 +285,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.server_button.setText(strings._('gui_share_stop_server'))
|
self.server_button.setText(strings._('gui_share_stop_server'))
|
||||||
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
|
self.server_button.setText(strings._('gui_share_stop_server'))
|
||||||
else:
|
else:
|
||||||
self.server_button.setText(strings._('gui_receive_stop_server'))
|
self.server_button.setText(strings._('gui_receive_stop_server'))
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get('autostart_timer'):
|
||||||
@ -412,5 +421,5 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
if self.common.settings.get('public_mode'):
|
if self.common.settings.get('public_mode'):
|
||||||
url = 'http://{0:s}'.format(self.app.onion_host)
|
url = 'http://{0:s}'.format(self.app.onion_host)
|
||||||
else:
|
else:
|
||||||
url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
|
url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.password, self.app.onion_host)
|
||||||
return url
|
return url
|
||||||
|
@ -54,7 +54,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
# General settings
|
# General settings
|
||||||
|
|
||||||
# Use a slug or not ('public mode')
|
# Use a password or not ('public mode')
|
||||||
self.public_mode_checkbox = QtWidgets.QCheckBox()
|
self.public_mode_checkbox = QtWidgets.QCheckBox()
|
||||||
self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||||
self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox"))
|
self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox"))
|
||||||
@ -968,12 +968,12 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
if self.save_private_key_checkbox.isChecked():
|
if self.save_private_key_checkbox.isChecked():
|
||||||
settings.set('save_private_key', True)
|
settings.set('save_private_key', True)
|
||||||
settings.set('private_key', self.old_settings.get('private_key'))
|
settings.set('private_key', self.old_settings.get('private_key'))
|
||||||
settings.set('slug', self.old_settings.get('slug'))
|
settings.set('password', self.old_settings.get('password'))
|
||||||
settings.set('hidservauth_string', self.old_settings.get('hidservauth_string'))
|
settings.set('hidservauth_string', self.old_settings.get('hidservauth_string'))
|
||||||
else:
|
else:
|
||||||
settings.set('save_private_key', False)
|
settings.set('save_private_key', False)
|
||||||
settings.set('private_key', '')
|
settings.set('private_key', '')
|
||||||
settings.set('slug', '')
|
settings.set('password', '')
|
||||||
# Also unset the HidServAuth if we are removing our reusable private key
|
# Also unset the HidServAuth if we are removing our reusable private key
|
||||||
settings.set('hidservauth_string', '')
|
settings.set('hidservauth_string', '')
|
||||||
|
|
||||||
|
@ -42,13 +42,16 @@ class OnionThread(QtCore.QThread):
|
|||||||
def run(self):
|
def run(self):
|
||||||
self.mode.common.log('OnionThread', 'run')
|
self.mode.common.log('OnionThread', 'run')
|
||||||
|
|
||||||
# Choose port and slug early, because we need them to exist in advance for scheduled shares
|
# Make a new static URL path for each new share
|
||||||
|
self.mode.web.generate_static_url_path()
|
||||||
|
|
||||||
|
# Choose port and password early, because we need them to exist in advance for scheduled shares
|
||||||
self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
|
self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
|
||||||
if not self.mode.app.port:
|
if not self.mode.app.port:
|
||||||
self.mode.app.choose_port()
|
self.mode.app.choose_port()
|
||||||
if not self.mode.common.settings.get('public_mode'):
|
if not self.mode.common.settings.get('public_mode'):
|
||||||
if not self.mode.web.slug:
|
if not self.mode.web.password:
|
||||||
self.mode.web.generate_slug(self.mode.common.settings.get('slug'))
|
self.mode.web.generate_password(self.mode.common.settings.get('password'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.mode.obtain_onion_early:
|
if self.mode.obtain_onion_early:
|
||||||
@ -86,7 +89,7 @@ class WebThread(QtCore.QThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.mode.common.log('WebThread', 'run')
|
self.mode.common.log('WebThread', 'run')
|
||||||
self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.slug)
|
self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.password)
|
||||||
self.success.emit()
|
self.success.emit()
|
||||||
|
|
||||||
|
|
||||||
|
3
setup.py
3
setup.py
@ -88,7 +88,8 @@ setup(
|
|||||||
'onionshare_gui',
|
'onionshare_gui',
|
||||||
'onionshare_gui.mode',
|
'onionshare_gui.mode',
|
||||||
'onionshare_gui.mode.share_mode',
|
'onionshare_gui.mode.share_mode',
|
||||||
'onionshare_gui.mode.receive_mode'
|
'onionshare_gui.mode.receive_mode',
|
||||||
|
'onionshare_gui.mode.website_mode'
|
||||||
],
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
|
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"not_a_readable_file": "{0:s} is not a readable file.",
|
"not_a_readable_file": "{0:s} is not a readable file.",
|
||||||
"no_available_port": "Could not find an available port to start the onion service",
|
"no_available_port": "Could not find an available port to start the onion service",
|
||||||
"other_page_loaded": "Address loaded",
|
"other_page_loaded": "Address loaded",
|
||||||
|
"invalid_password_guess": "Invalid password guess",
|
||||||
"close_on_autostop_timer": "Stopped because auto-stop timer ran out",
|
"close_on_autostop_timer": "Stopped because auto-stop timer ran out",
|
||||||
"closing_automatically": "Stopped because transfer is complete",
|
"closing_automatically": "Stopped because transfer is complete",
|
||||||
"large_filesize": "Warning: Sending a large share could take hours",
|
"large_filesize": "Warning: Sending a large share could take hours",
|
||||||
@ -34,7 +35,7 @@
|
|||||||
"gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?",
|
"gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?",
|
||||||
"gui_quit_warning_quit": "Quit",
|
"gui_quit_warning_quit": "Quit",
|
||||||
"gui_quit_warning_dont_quit": "Cancel",
|
"gui_quit_warning_dont_quit": "Cancel",
|
||||||
"error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.",
|
"error_rate_limit": "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.",
|
||||||
"zip_progress_bar_format": "Compressing: %p%",
|
"zip_progress_bar_format": "Compressing: %p%",
|
||||||
"error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.",
|
"error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.",
|
||||||
"error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.",
|
"error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.",
|
||||||
@ -114,6 +115,7 @@
|
|||||||
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
|
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
|
||||||
"gui_save_private_key_checkbox": "Use a persistent address",
|
"gui_save_private_key_checkbox": "Use a persistent address",
|
||||||
"gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
"gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
|
"gui_website_url_description": "<b>Anyone</b> with this OnionShare address can <b>visit</b> your website using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
"gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
|
"gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
"gui_url_label_persistent": "This share will not auto-stop.<br><br>Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
|
"gui_url_label_persistent": "This share will not auto-stop.<br><br>Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
|
||||||
"gui_url_label_stay_open": "This share will not auto-stop.",
|
"gui_url_label_stay_open": "This share will not auto-stop.",
|
||||||
@ -135,6 +137,7 @@
|
|||||||
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
|
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
|
||||||
"gui_mode_share_button": "Share Files",
|
"gui_mode_share_button": "Share Files",
|
||||||
"gui_mode_receive_button": "Receive Files",
|
"gui_mode_receive_button": "Receive Files",
|
||||||
|
"gui_mode_website_button": "Publish Website",
|
||||||
"gui_settings_receiving_label": "Receiving settings",
|
"gui_settings_receiving_label": "Receiving settings",
|
||||||
"gui_settings_data_dir_label": "Save files to",
|
"gui_settings_data_dir_label": "Save files to",
|
||||||
"gui_settings_data_dir_browse_button": "Browse",
|
"gui_settings_data_dir_browse_button": "Browse",
|
||||||
@ -145,6 +148,8 @@
|
|||||||
"systray_menu_exit": "Quit",
|
"systray_menu_exit": "Quit",
|
||||||
"systray_page_loaded_title": "Page Loaded",
|
"systray_page_loaded_title": "Page Loaded",
|
||||||
"systray_page_loaded_message": "OnionShare address loaded",
|
"systray_page_loaded_message": "OnionShare address loaded",
|
||||||
|
"systray_site_loaded_title": "Site Loaded",
|
||||||
|
"systray_site_loaded_message": "OnionShare site loaded",
|
||||||
"systray_share_started_title": "Sharing Started",
|
"systray_share_started_title": "Sharing Started",
|
||||||
"systray_share_started_message": "Starting to send files to someone",
|
"systray_share_started_message": "Starting to send files to someone",
|
||||||
"systray_share_completed_title": "Sharing Complete",
|
"systray_share_completed_title": "Sharing Complete",
|
||||||
@ -153,6 +158,8 @@
|
|||||||
"systray_share_canceled_message": "Someone canceled receiving your files",
|
"systray_share_canceled_message": "Someone canceled receiving your files",
|
||||||
"systray_receive_started_title": "Receiving Started",
|
"systray_receive_started_title": "Receiving Started",
|
||||||
"systray_receive_started_message": "Someone is sending files to you",
|
"systray_receive_started_message": "Someone is sending files to you",
|
||||||
|
"systray_website_started_title": "Starting sharing website",
|
||||||
|
"systray_website_started_message": "Someone is visiting your website",
|
||||||
"gui_all_modes_history": "History",
|
"gui_all_modes_history": "History",
|
||||||
"gui_all_modes_clear_history": "Clear All",
|
"gui_all_modes_clear_history": "Clear All",
|
||||||
"gui_all_modes_transfer_started": "Started {}",
|
"gui_all_modes_transfer_started": "Started {}",
|
||||||
@ -165,8 +172,10 @@
|
|||||||
"gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
"gui_share_mode_no_files": "No Files Sent Yet",
|
"gui_share_mode_no_files": "No Files Sent Yet",
|
||||||
"gui_share_mode_autostop_timer_waiting": "Waiting to finish sending",
|
"gui_share_mode_autostop_timer_waiting": "Waiting to finish sending",
|
||||||
|
"gui_website_mode_no_files": "No Website Shared Yet",
|
||||||
"gui_receive_mode_no_files": "No Files Received Yet",
|
"gui_receive_mode_no_files": "No Files Received Yet",
|
||||||
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
|
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
|
||||||
|
"gui_visit_started": "Someone has visited your website {}",
|
||||||
"receive_mode_upload_starting": "Upload of total size {} is starting",
|
"receive_mode_upload_starting": "Upload of total size {} is starting",
|
||||||
"days_first_letter": "d",
|
"days_first_letter": "d",
|
||||||
"hours_first_letter": "h",
|
"hours_first_letter": "h",
|
||||||
|
@ -222,20 +222,3 @@ li.info {
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#noscript {
|
|
||||||
text-align: center;
|
|
||||||
color: #d709df;
|
|
||||||
padding: 1em;
|
|
||||||
line-height: 150%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#noscript a, div#noscript a:visited {
|
|
||||||
color: #d709df;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disable-noscript-xss-wrapper {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 804 B |
@ -1,2 +0,0 @@
|
|||||||
// Hide the noscript div, because our javascript is executing
|
|
||||||
document.getElementById('noscript').style.display = 'none';
|
|
@ -121,7 +121,7 @@ $(function(){
|
|||||||
$('#uploads').append($upload_div);
|
$('#uploads').append($upload_div);
|
||||||
|
|
||||||
// Send the request
|
// Send the request
|
||||||
ajax.open('POST', window.location.pathname.replace(/\/$/, '') + '/upload-ajax', true);
|
ajax.open('POST', '/upload-ajax', true);
|
||||||
ajax.send(formData);
|
ajax.send(formData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
19
share/templates/401.html
Normal file
19
share/templates/401.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>OnionShare: 401 Unauthorized Access</title>
|
||||||
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="info-wrapper">
|
||||||
|
<div class="info">
|
||||||
|
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||||
|
<p class="info-header">401 Unauthorized Access</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare: 403 Forbidden</title>
|
<title>OnionShare: 403 Forbidden</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="info-wrapper">
|
<div class="info-wrapper">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
|
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||||
<p class="info-header">You are not allowed to perform that action at this time.</p>
|
<p class="info-header">You are not allowed to perform that action at this time.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare: 404 Not Found</title>
|
<title>OnionShare: 404 Not Found</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="info-wrapper">
|
<div class="info-wrapper">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
|
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||||
<p class="info-header">404 Not Found</p>
|
<p class="info-header">404 Not Found</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare</title>
|
<title>OnionShare</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
47
share/templates/listing.html
Normal file
47
share/templates/listing.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare</title>
|
||||||
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
<link href="{{ static_url_path }}/css/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="clearfix">
|
||||||
|
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
|
||||||
|
<h1>OnionShare</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<table class="file-list" id="file-list">
|
||||||
|
<tr>
|
||||||
|
<th id="filename-header">Filename</th>
|
||||||
|
<th id="size-header">Size</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% for info in dirs %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_folder.png" />
|
||||||
|
<a href="{{ info.basename }}">
|
||||||
|
{{ info.basename }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>—</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for info in files %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_file.png" />
|
||||||
|
<a href="{{ info.basename }}">
|
||||||
|
{{ info.basename }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ info.size_human }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -2,31 +2,18 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare</title>
|
<title>OnionShare</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header class="clearfix">
|
<header class="clearfix">
|
||||||
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
|
||||||
<h1>OnionShare</h1>
|
<h1>OnionShare</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="upload-wrapper">
|
<div class="upload-wrapper">
|
||||||
<!--
|
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||||
We are not using a <noscript> tag because it only works when the security slider is set to
|
|
||||||
Safest, not Safer: https://trac.torproject.org/projects/tor/ticket/29506
|
|
||||||
-->
|
|
||||||
<div id="noscript">
|
|
||||||
<p>
|
|
||||||
<img src="/static/img/warning.png" title="Warning" /><strong>Warning:</strong> Due to a bug in Tor Browser and Firefox, uploads
|
|
||||||
sometimes never finish. To upload reliably, either set your Tor Browser
|
|
||||||
<a rel="noreferrer" target="_blank" href="https://tb-manual.torproject.org/en-US/security-slider/">security slider</a>
|
|
||||||
to Standard or
|
|
||||||
<a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
|
|
||||||
|
|
||||||
<p class="upload-header">Send Files</p>
|
<p class="upload-header">Send Files</p>
|
||||||
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
|
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
|
||||||
@ -45,14 +32,13 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}">
|
<form id="send" method="post" enctype="multipart/form-data" action="/upload">
|
||||||
<p><input type="file" id="file-select" name="file[]" multiple /></p>
|
<p><input type="file" id="file-select" name="file[]" multiple /></p>
|
||||||
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
|
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/js/receive-noscript.js"></script>
|
<script src="{{ static_url_path }}/js/jquery-3.4.0.min.js"></script>
|
||||||
<script src="/static/js/jquery-3.4.0.min.js"></script>
|
<script async src="{{ static_url_path }}/js/receive.js"></script>
|
||||||
<script async src="/static/js/receive.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>OnionShare</title>
|
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
|
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header class="clearfix">
|
|
||||||
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
|
||||||
<h1>OnionShare</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="disable-noscript-xss-wrapper">
|
|
||||||
<h3>Disable your Tor Browser's NoScript XSS setting</h3>
|
|
||||||
|
|
||||||
<p>If your security slider is set to Safest, JavaScript is disabled so XSS vulnerabilities won't affect you,
|
|
||||||
which makes it safe to disable NoScript's XSS protections.</p>
|
|
||||||
|
|
||||||
<p>Here is how to disable this setting:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Click the menu icon in the top-right of Tor Browser and open "Add-ons"</li>
|
|
||||||
<li>Next to the NoScript add-on, click the "Preferences" button</li>
|
|
||||||
<li>Switch to the "Advanced" tab</li>
|
|
||||||
<li>Uncheck "Sanitize cross-site suspicious requests"</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>If you'd like to learn technical details about this issue, check
|
|
||||||
<a rel="noreferrer" href="https://github.com/micahflee/onionshare/issues/899">this issue</a>
|
|
||||||
on GitHub.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare</title>
|
<title>OnionShare</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
<meta name="onionshare-filename" content="{{ filename }}">
|
<meta name="onionshare-filename" content="{{ filename }}">
|
||||||
<meta name="onionshare-filesize" content="{{ filesize }}">
|
<meta name="onionshare-filesize" content="{{ filesize }}">
|
||||||
</head>
|
</head>
|
||||||
@ -15,14 +15,10 @@
|
|||||||
<div class="right">
|
<div class="right">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Total size: <strong>{{ filesize_human }}</strong> {% if is_zipped %} (compressed){% endif %}</li>
|
<li>Total size: <strong>{{ filesize_human }}</strong> {% if is_zipped %} (compressed){% endif %}</li>
|
||||||
{% if slug %}
|
|
||||||
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a class="button" href='/download'>Download Files</a></li>
|
<li><a class="button" href='/download'>Download Files</a></li>
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
|
||||||
<h1>OnionShare</h1>
|
<h1>OnionShare</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -35,7 +31,7 @@
|
|||||||
{% for info in file_info.dirs %}
|
{% for info in file_info.dirs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img width="30" height="30" title="" alt="" src="/static/img/web_folder.png" />
|
<img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_folder.png" />
|
||||||
{{ info.basename }}
|
{{ info.basename }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ info.size_human }}</td>
|
<td>{{ info.size_human }}</td>
|
||||||
@ -45,7 +41,7 @@
|
|||||||
{% for info in file_info.files %}
|
{% for info in file_info.files %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
|
<img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_file.png" />
|
||||||
{{ info.basename }}
|
{{ info.basename }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ info.size_human }}</td>
|
<td>{{ info.size_human }}</td>
|
||||||
@ -53,7 +49,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
<script async src="/static/js/send.js" charset="utf-8"></script>
|
<script async src="{{ static_url_path }}/js/send.js" charset="utf-8"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare is closed</title>
|
<title>OnionShare is closed</title>
|
||||||
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
<link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header class="clearfix">
|
<header class="clearfix">
|
||||||
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
|
||||||
<h1>OnionShare</h1>
|
<h1>OnionShare</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="info-wrapper">
|
<div class="info-wrapper">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
|
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||||
<p class="info-header">Thank you for using OnionShare</p>
|
<p class="info-header">Thank you for using OnionShare</p>
|
||||||
<p class="info-description">You may now close this window.</p>
|
<p class="info-description">You may now close this window.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
Package3: onionshare
|
Package3: onionshare
|
||||||
Depends3: python3, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
|
Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
|
||||||
Build-Depends: python3, python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-requests, python-nautilus, tor, obfs4proxy
|
Build-Depends: python3, python3-pytest, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-requests, python-nautilus, tor, obfs4proxy
|
||||||
Suite: cosmic
|
Suite: cosmic
|
||||||
X-Python3-Version: >= 3.5.3
|
X-Python3-Version: >= 3.5.3
|
||||||
|
@ -2,8 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import base64
|
||||||
import socks
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtTest
|
from PyQt5 import QtCore, QtTest
|
||||||
|
|
||||||
@ -126,20 +125,20 @@ class GuiBaseTest(object):
|
|||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
# Upload a file
|
# Upload a file
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
||||||
if not public_mode:
|
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug)
|
if public_mode:
|
||||||
|
r = requests.post(url, files=files)
|
||||||
else:
|
else:
|
||||||
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
||||||
response = requests.post(path, files=files)
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
if type(mode) == ShareMode:
|
if type(mode) == ShareMode:
|
||||||
# Download files
|
# Download files
|
||||||
|
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug)
|
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
||||||
r = requests.get(url)
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
# Indicator should be visible, have a value of "1"
|
# Indicator should be visible, have a value of "1"
|
||||||
@ -185,17 +184,19 @@ class GuiBaseTest(object):
|
|||||||
|
|
||||||
def web_server_is_running(self):
|
def web_server_is_running(self):
|
||||||
'''Test that the web server has started'''
|
'''Test that the web server has started'''
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
try:
|
||||||
|
r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port))
|
||||||
self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
|
self.assertTrue(True)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
|
||||||
def have_a_slug(self, mode, public_mode):
|
def have_a_password(self, mode, public_mode):
|
||||||
'''Test that we have a valid slug'''
|
'''Test that we have a valid password'''
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)')
|
self.assertRegex(mode.server_status.web.password, r'(\w+)-(\w+)')
|
||||||
else:
|
else:
|
||||||
self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)')
|
self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)')
|
||||||
|
|
||||||
|
|
||||||
def url_description_shown(self, mode):
|
def url_description_shown(self, mode):
|
||||||
@ -212,7 +213,7 @@ class GuiBaseTest(object):
|
|||||||
if public_mode:
|
if public_mode:
|
||||||
self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port))
|
self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug))
|
self.assertEqual(clipboard.text(), 'http://onionshare:{}@127.0.0.1:{}'.format(mode.server_status.web.password, self.gui.app.port))
|
||||||
|
|
||||||
|
|
||||||
def server_status_indicator_says_started(self, mode):
|
def server_status_indicator_says_started(self, mode):
|
||||||
@ -225,31 +226,14 @@ class GuiBaseTest(object):
|
|||||||
|
|
||||||
def web_page(self, mode, string, public_mode):
|
def web_page(self, mode, string, public_mode):
|
||||||
'''Test that the web page contains a string'''
|
'''Test that the web page contains a string'''
|
||||||
s = socks.socksocket()
|
|
||||||
s.settimeout(60)
|
|
||||||
s.connect(('127.0.0.1', self.gui.app.port))
|
|
||||||
|
|
||||||
if not public_mode:
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
path = '/{}'.format(mode.server_status.web.slug)
|
if public_mode:
|
||||||
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
path = '/'
|
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
||||||
|
|
||||||
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
self.assertTrue(string in r.text)
|
||||||
http_request += 'Host: 127.0.0.1\r\n'
|
|
||||||
http_request += '\r\n'
|
|
||||||
s.sendall(http_request.encode('utf-8'))
|
|
||||||
|
|
||||||
with open('/tmp/webpage', 'wb') as file_to_write:
|
|
||||||
while True:
|
|
||||||
data = s.recv(1024)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
file_to_write.write(data)
|
|
||||||
file_to_write.close()
|
|
||||||
|
|
||||||
f = open('/tmp/webpage')
|
|
||||||
self.assertTrue(string in f.read())
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
def history_widgets_present(self, mode):
|
def history_widgets_present(self, mode):
|
||||||
@ -273,10 +257,12 @@ class GuiBaseTest(object):
|
|||||||
def web_server_is_stopped(self):
|
def web_server_is_stopped(self):
|
||||||
'''Test that the web server also stopped'''
|
'''Test that the web server also stopped'''
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
|
|
||||||
# We should be closed by now. Fail if not!
|
try:
|
||||||
self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
|
r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port))
|
||||||
|
self.assertTrue(False)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
|
||||||
def server_status_indicator_says_closed(self, mode, stay_open):
|
def server_status_indicator_says_closed(self, mode, stay_open):
|
||||||
|
@ -7,18 +7,26 @@ from .GuiBaseTest import GuiBaseTest
|
|||||||
class GuiReceiveTest(GuiBaseTest):
|
class GuiReceiveTest(GuiBaseTest):
|
||||||
def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False):
|
def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False):
|
||||||
'''Test that we can upload the file'''
|
'''Test that we can upload the file'''
|
||||||
files = {'file[]': open(file_to_upload, 'rb')}
|
|
||||||
if not public_mode:
|
# Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused
|
||||||
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
|
|
||||||
else:
|
|
||||||
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
|
||||||
response = requests.post(path, files=files)
|
|
||||||
if identical_files_at_once:
|
|
||||||
# Send a duplicate upload to test for collisions
|
|
||||||
response = requests.post(path, files=files)
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
# Make sure the file is within the last 10 seconds worth of filenames
|
files = {'file[]': open(file_to_upload, 'rb')}
|
||||||
|
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
|
if public_mode:
|
||||||
|
r = requests.post(url, files=files)
|
||||||
|
if identical_files_at_once:
|
||||||
|
# Send a duplicate upload to test for collisions
|
||||||
|
r = requests.post(url, files=files)
|
||||||
|
else:
|
||||||
|
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
||||||
|
if identical_files_at_once:
|
||||||
|
# Send a duplicate upload to test for collisions
|
||||||
|
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
||||||
|
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
|
# Make sure the file is within the last 10 seconds worth of fileames
|
||||||
exists = False
|
exists = False
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
@ -39,31 +47,28 @@ class GuiReceiveTest(GuiBaseTest):
|
|||||||
def upload_file_should_fail(self, public_mode):
|
def upload_file_should_fail(self, public_mode):
|
||||||
'''Test that we can't upload the file when permissions are wrong, and expected content is shown'''
|
'''Test that we can't upload the file when permissions are wrong, and expected content is shown'''
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
||||||
if not public_mode:
|
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
|
if public_mode:
|
||||||
|
r = requests.post(url, files=files)
|
||||||
else:
|
else:
|
||||||
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
||||||
response = requests.post(path, files=files)
|
|
||||||
|
|
||||||
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
||||||
self.assertTrue('Error uploading, please inform the OnionShare user' in response.text)
|
self.assertTrue('Error uploading, please inform the OnionShare user' in r.text)
|
||||||
|
|
||||||
def upload_dir_permissions(self, mode=0o755):
|
def upload_dir_permissions(self, mode=0o755):
|
||||||
'''Manipulate the permissions on the upload dir in between tests'''
|
'''Manipulate the permissions on the upload dir in between tests'''
|
||||||
os.chmod('/tmp/OnionShare', mode)
|
os.chmod('/tmp/OnionShare', mode)
|
||||||
|
|
||||||
def try_public_paths_in_non_public_mode(self):
|
def try_without_auth_in_non_public_mode(self):
|
||||||
response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port))
|
r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(r.status_code, 401)
|
||||||
response = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port))
|
r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
def uploading_zero_files_shouldnt_change_ui(self, mode, public_mode):
|
def uploading_zero_files_shouldnt_change_ui(self, mode, public_mode):
|
||||||
'''If you submit the receive mode form without selecting any files, the UI shouldn't get updated'''
|
'''If you submit the receive mode form without selecting any files, the UI shouldn't get updated'''
|
||||||
if not public_mode:
|
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
|
|
||||||
else:
|
|
||||||
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
|
||||||
|
|
||||||
# What were the counts before submitting the form?
|
# What were the counts before submitting the form?
|
||||||
before_in_progress_count = mode.history.in_progress_count
|
before_in_progress_count = mode.history.in_progress_count
|
||||||
@ -71,9 +76,15 @@ class GuiReceiveTest(GuiBaseTest):
|
|||||||
before_number_of_history_items = len(mode.history.item_list.items)
|
before_number_of_history_items = len(mode.history.item_list.items)
|
||||||
|
|
||||||
# Click submit without including any files a few times
|
# Click submit without including any files a few times
|
||||||
response = requests.post(path, files={})
|
if public_mode:
|
||||||
response = requests.post(path, files={})
|
r = requests.post(url, files={})
|
||||||
response = requests.post(path, files={})
|
r = requests.post(url, files={})
|
||||||
|
r = requests.post(url, files={})
|
||||||
|
else:
|
||||||
|
auth = requests.auth.HTTPBasicAuth('onionshare', mode.web.password)
|
||||||
|
r = requests.post(url, files={}, auth=auth)
|
||||||
|
r = requests.post(url, files={}, auth=auth)
|
||||||
|
r = requests.post(url, files={}, auth=auth)
|
||||||
|
|
||||||
# The counts shouldn't change
|
# The counts shouldn't change
|
||||||
self.assertEqual(mode.history.in_progress_count, before_in_progress_count)
|
self.assertEqual(mode.history.in_progress_count, before_in_progress_count)
|
||||||
@ -93,17 +104,17 @@ class GuiReceiveTest(GuiBaseTest):
|
|||||||
self.settings_button_is_hidden()
|
self.settings_button_is_hidden()
|
||||||
self.server_is_started(self.gui.receive_mode)
|
self.server_is_started(self.gui.receive_mode)
|
||||||
self.web_server_is_running()
|
self.web_server_is_running()
|
||||||
self.have_a_slug(self.gui.receive_mode, public_mode)
|
self.have_a_password(self.gui.receive_mode, public_mode)
|
||||||
self.url_description_shown(self.gui.receive_mode)
|
self.url_description_shown(self.gui.receive_mode)
|
||||||
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.receive_mode)
|
self.server_status_indicator_says_started(self.gui.receive_mode)
|
||||||
self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
|
self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
|
||||||
|
|
||||||
def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown):
|
def run_all_receive_mode_tests(self, public_mode):
|
||||||
'''Upload files in receive mode and stop the share'''
|
'''Upload files in receive mode and stop the share'''
|
||||||
self.run_all_receive_mode_setup_tests(public_mode)
|
self.run_all_receive_mode_setup_tests(public_mode)
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
self.try_public_paths_in_non_public_mode()
|
self.try_without_auth_in_non_public_mode()
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
||||||
self.history_widgets_present(self.gui.receive_mode)
|
self.history_widgets_present(self.gui.receive_mode)
|
||||||
self.counter_incremented(self.gui.receive_mode, 1)
|
self.counter_incremented(self.gui.receive_mode, 1)
|
||||||
@ -125,7 +136,7 @@ class GuiReceiveTest(GuiBaseTest):
|
|||||||
self.server_is_started(self.gui.receive_mode)
|
self.server_is_started(self.gui.receive_mode)
|
||||||
self.history_indicator(self.gui.receive_mode, public_mode)
|
self.history_indicator(self.gui.receive_mode, public_mode)
|
||||||
|
|
||||||
def run_all_receive_mode_unwritable_dir_tests(self, public_mode, receive_allow_receiver_shutdown):
|
def run_all_receive_mode_unwritable_dir_tests(self, public_mode):
|
||||||
'''Attempt to upload (unwritable) files in receive mode and stop the share'''
|
'''Attempt to upload (unwritable) files in receive mode and stop the share'''
|
||||||
self.run_all_receive_mode_setup_tests(public_mode)
|
self.run_all_receive_mode_setup_tests(public_mode)
|
||||||
self.upload_dir_permissions(0o400)
|
self.upload_dir_permissions(0o400)
|
||||||
|
@ -2,14 +2,15 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
import socks
|
import socks
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import tempfile
|
||||||
from PyQt5 import QtCore, QtTest
|
from PyQt5 import QtCore, QtTest
|
||||||
from .GuiBaseTest import GuiBaseTest
|
from .GuiBaseTest import GuiBaseTest
|
||||||
|
|
||||||
class GuiShareTest(GuiBaseTest):
|
class GuiShareTest(GuiBaseTest):
|
||||||
# Persistence tests
|
# Persistence tests
|
||||||
def have_same_slug(self, slug):
|
def have_same_password(self, password):
|
||||||
'''Test that we have the same slug'''
|
'''Test that we have the same password'''
|
||||||
self.assertEqual(self.gui.share_mode.server_status.web.slug, slug)
|
self.assertEqual(self.gui.share_mode.server_status.web.password, password)
|
||||||
|
|
||||||
# Share-specific tests
|
# Share-specific tests
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
'''Test that the number of items in the list is as expected'''
|
'''Test that the number of items in the list is as expected'''
|
||||||
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num)
|
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num)
|
||||||
|
|
||||||
|
|
||||||
def deleting_all_files_hides_delete_button(self):
|
def deleting_all_files_hides_delete_button(self):
|
||||||
'''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button'''
|
'''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button'''
|
||||||
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
|
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
|
||||||
@ -35,14 +36,14 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
# No more files, the delete button should be hidden
|
# No more files, the delete button should be hidden
|
||||||
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def add_a_file_and_delete_using_its_delete_widget(self):
|
def add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
'''Test that we can also delete a file by clicking on its [X] widget'''
|
'''Test that we can also delete a file by clicking on its [X] widget'''
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton)
|
||||||
self.file_selection_widget_has_files(0)
|
self.file_selection_widget_has_files(0)
|
||||||
|
|
||||||
|
|
||||||
def file_selection_widget_readd_files(self):
|
def file_selection_widget_readd_files(self):
|
||||||
'''Re-add some files to the list so we can share'''
|
'''Re-add some files to the list so we can share'''
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
||||||
@ -56,49 +57,37 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
with open('/tmp/large_file', 'wb') as fout:
|
with open('/tmp/large_file', 'wb') as fout:
|
||||||
fout.write(os.urandom(size))
|
fout.write(os.urandom(size))
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file')
|
||||||
|
|
||||||
|
|
||||||
def add_delete_buttons_hidden(self):
|
def add_delete_buttons_hidden(self):
|
||||||
'''Test that the add and delete buttons are hidden when the server starts'''
|
'''Test that the add and delete buttons are hidden when the server starts'''
|
||||||
self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
|
||||||
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def download_share(self, public_mode):
|
def download_share(self, public_mode):
|
||||||
'''Test that we can download the share'''
|
'''Test that we can download the share'''
|
||||||
s = socks.socksocket()
|
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
||||||
s.settimeout(60)
|
|
||||||
s.connect(('127.0.0.1', self.gui.app.port))
|
|
||||||
|
|
||||||
if public_mode:
|
if public_mode:
|
||||||
path = '/download'
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
path = '{}/download'.format(self.gui.share_mode.web.slug)
|
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password))
|
||||||
|
|
||||||
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
tmp_file = tempfile.NamedTemporaryFile()
|
||||||
http_request += 'Host: 127.0.0.1\r\n'
|
with open(tmp_file.name, 'wb') as f:
|
||||||
http_request += '\r\n'
|
f.write(r.content)
|
||||||
s.sendall(http_request.encode('utf-8'))
|
|
||||||
|
|
||||||
with open('/tmp/download.zip', 'wb') as file_to_write:
|
zip = zipfile.ZipFile(tmp_file.name)
|
||||||
while True:
|
|
||||||
data = s.recv(1024)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
file_to_write.write(data)
|
|
||||||
file_to_write.close()
|
|
||||||
|
|
||||||
zip = zipfile.ZipFile('/tmp/download.zip')
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
|
self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
|
||||||
|
|
||||||
def hit_404(self, public_mode):
|
def hit_401(self, public_mode):
|
||||||
'''Test that the server stops after too many 404s, or doesn't when in public_mode'''
|
'''Test that the server stops after too many 401s, or doesn't when in public_mode'''
|
||||||
bogus_path = '/gimme'
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
url = "http://127.0.0.1:{}/{}".format(self.gui.app.port, bogus_path)
|
|
||||||
|
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
r = requests.get(url)
|
password_guess = self.gui.common.build_password()
|
||||||
|
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password_guess))
|
||||||
|
|
||||||
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
|
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
@ -130,7 +119,7 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
self.add_a_file_and_delete_using_its_delete_widget()
|
self.add_a_file_and_delete_using_its_delete_widget()
|
||||||
self.file_selection_widget_readd_files()
|
self.file_selection_widget_readd_files()
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_started_tests(self, public_mode, startup_time=2000):
|
def run_all_share_mode_started_tests(self, public_mode, startup_time=2000):
|
||||||
"""Tests in share mode after starting a share"""
|
"""Tests in share mode after starting a share"""
|
||||||
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
||||||
@ -139,12 +128,12 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
self.settings_button_is_hidden()
|
self.settings_button_is_hidden()
|
||||||
self.server_is_started(self.gui.share_mode, startup_time)
|
self.server_is_started(self.gui.share_mode, startup_time)
|
||||||
self.web_server_is_running()
|
self.web_server_is_running()
|
||||||
self.have_a_slug(self.gui.share_mode, public_mode)
|
self.have_a_password(self.gui.share_mode, public_mode)
|
||||||
self.url_description_shown(self.gui.share_mode)
|
self.url_description_shown(self.gui.share_mode)
|
||||||
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.share_mode)
|
self.server_status_indicator_says_started(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
||||||
"""Tests in share mode after downloading a share"""
|
"""Tests in share mode after downloading a share"""
|
||||||
self.web_page(self.gui.share_mode, 'Total size', public_mode)
|
self.web_page(self.gui.share_mode, 'Total size', public_mode)
|
||||||
@ -158,7 +147,7 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
self.server_is_started(self.gui.share_mode)
|
self.server_is_started(self.gui.share_mode)
|
||||||
self.history_indicator(self.gui.share_mode, public_mode)
|
self.history_indicator(self.gui.share_mode, public_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_tests(self, public_mode, stay_open):
|
def run_all_share_mode_tests(self, public_mode, stay_open):
|
||||||
"""End-to-end share tests"""
|
"""End-to-end share tests"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
@ -178,12 +167,12 @@ class GuiShareTest(GuiBaseTest):
|
|||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
||||||
"""Same as end-to-end share tests but also test the slug is the same on multiple shared"""
|
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
self.run_all_share_mode_started_tests(public_mode)
|
self.run_all_share_mode_started_tests(public_mode)
|
||||||
slug = self.gui.share_mode.server_status.web.slug
|
password = self.gui.share_mode.server_status.web.password
|
||||||
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
||||||
self.have_same_slug(slug)
|
self.have_same_password(password)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_timer_tests(self, public_mode):
|
def run_all_share_mode_timer_tests(self, public_mode):
|
||||||
|
@ -76,7 +76,7 @@ class TorGuiBaseTest(GuiBaseTest):
|
|||||||
# Upload a file
|
# Upload a file
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug)
|
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.password)
|
||||||
else:
|
else:
|
||||||
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
||||||
response = session.post(path, files=files)
|
response = session.post(path, files=files)
|
||||||
@ -87,7 +87,7 @@ class TorGuiBaseTest(GuiBaseTest):
|
|||||||
if public_mode:
|
if public_mode:
|
||||||
path = "http://{}/download".format(self.gui.app.onion_host)
|
path = "http://{}/download".format(self.gui.app.onion_host)
|
||||||
else:
|
else:
|
||||||
path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug)
|
path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.password)
|
||||||
response = session.get(path)
|
response = session.get(path)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ class TorGuiBaseTest(GuiBaseTest):
|
|||||||
s.settimeout(60)
|
s.settimeout(60)
|
||||||
s.connect((self.gui.app.onion_host, 80))
|
s.connect((self.gui.app.onion_host, 80))
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = '/{}'.format(mode.server_status.web.slug)
|
path = '/{}'.format(mode.server_status.web.password)
|
||||||
else:
|
else:
|
||||||
path = '/'
|
path = '/'
|
||||||
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
||||||
@ -138,7 +138,7 @@ class TorGuiBaseTest(GuiBaseTest):
|
|||||||
if public_mode:
|
if public_mode:
|
||||||
self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host))
|
self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.slug))
|
self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.password))
|
||||||
|
|
||||||
|
|
||||||
# Stealth tests
|
# Stealth tests
|
||||||
|
@ -13,7 +13,7 @@ class TorGuiReceiveTest(TorGuiBaseTest):
|
|||||||
session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
|
session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
|
||||||
files = {'file[]': open(file_to_upload, 'rb')}
|
files = {'file[]': open(file_to_upload, 'rb')}
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug)
|
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.password)
|
||||||
else:
|
else:
|
||||||
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
||||||
response = session.post(path, files=files)
|
response = session.post(path, files=files)
|
||||||
@ -35,7 +35,7 @@ class TorGuiReceiveTest(TorGuiBaseTest):
|
|||||||
self.server_is_started(self.gui.receive_mode, startup_time=45000)
|
self.server_is_started(self.gui.receive_mode, startup_time=45000)
|
||||||
self.web_server_is_running()
|
self.web_server_is_running()
|
||||||
self.have_an_onion_service()
|
self.have_an_onion_service()
|
||||||
self.have_a_slug(self.gui.receive_mode, public_mode)
|
self.have_a_password(self.gui.receive_mode, public_mode)
|
||||||
self.url_description_shown(self.gui.receive_mode)
|
self.url_description_shown(self.gui.receive_mode)
|
||||||
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.receive_mode)
|
self.server_status_indicator_says_started(self.gui.receive_mode)
|
||||||
@ -56,4 +56,3 @@ class TorGuiReceiveTest(TorGuiBaseTest):
|
|||||||
self.server_working_on_start_button_pressed(self.gui.receive_mode)
|
self.server_working_on_start_button_pressed(self.gui.receive_mode)
|
||||||
self.server_is_started(self.gui.receive_mode, startup_time=45000)
|
self.server_is_started(self.gui.receive_mode, startup_time=45000)
|
||||||
self.history_indicator(self.gui.receive_mode, public_mode)
|
self.history_indicator(self.gui.receive_mode, public_mode)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
|||||||
if public_mode:
|
if public_mode:
|
||||||
path = "http://{}/download".format(self.gui.app.onion_host)
|
path = "http://{}/download".format(self.gui.app.onion_host)
|
||||||
else:
|
else:
|
||||||
path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug)
|
path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.password)
|
||||||
response = session.get(path, stream=True)
|
response = session.get(path, stream=True)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
|||||||
self.server_is_started(self.gui.share_mode, startup_time=45000)
|
self.server_is_started(self.gui.share_mode, startup_time=45000)
|
||||||
self.web_server_is_running()
|
self.web_server_is_running()
|
||||||
self.have_an_onion_service()
|
self.have_an_onion_service()
|
||||||
self.have_a_slug(self.gui.share_mode, public_mode)
|
self.have_a_password(self.gui.share_mode, public_mode)
|
||||||
self.url_description_shown(self.gui.share_mode)
|
self.url_description_shown(self.gui.share_mode)
|
||||||
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.share_mode)
|
self.server_status_indicator_says_started(self.gui.share_mode)
|
||||||
@ -74,16 +74,16 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
|||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
||||||
"""Same as end-to-end share tests but also test the slug is the same on multiple shared"""
|
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
self.run_all_share_mode_started_tests(public_mode)
|
self.run_all_share_mode_started_tests(public_mode)
|
||||||
slug = self.gui.share_mode.server_status.web.slug
|
password = self.gui.share_mode.server_status.web.password
|
||||||
onion = self.gui.app.onion_host
|
onion = self.gui.app.onion_host
|
||||||
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
||||||
self.have_same_onion(onion)
|
self.have_same_onion(onion)
|
||||||
self.have_same_slug(slug)
|
self.have_same_password(password)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_timer_tests(self, public_mode):
|
def run_all_share_mode_timer_tests(self, public_mode):
|
||||||
"""Auto-stop timer tests in share mode"""
|
"""Auto-stop timer tests in share mode"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
@ -92,4 +92,3 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
|||||||
self.autostop_timer_widget_hidden(self.gui.share_mode)
|
self.autostop_timer_widget_hidden(self.gui.share_mode)
|
||||||
self.server_timed_out(self.gui.share_mode, 125000)
|
self.server_timed_out(self.gui.share_mode, 125000)
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {
|
||||||
@ -22,7 +22,7 @@ class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
|||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(True, True)
|
self.run_all_share_mode_tests(True, True)
|
||||||
self.hit_404(True)
|
self.hit_401(True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
class Local404RateLimitTest(unittest.TestCase, GuiShareTest):
|
class Local401RateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {
|
||||||
@ -21,7 +21,7 @@ class Local404RateLimitTest(unittest.TestCase, GuiShareTest):
|
|||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, True)
|
self.run_all_share_mode_tests(False, True)
|
||||||
self.hit_404(False)
|
self.hit_401(False)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
@ -20,7 +20,7 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
|||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_unwritable_dir_tests(False, True)
|
self.run_all_receive_mode_unwritable_dir_tests(False)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -21,7 +21,7 @@ class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
|||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_unwritable_dir_tests(True, True)
|
self.run_all_receive_mode_unwritable_dir_tests(True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -21,7 +21,7 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest):
|
|||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(True, True)
|
self.run_all_receive_mode_tests(True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -20,7 +20,7 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
|
|||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(False, True)
|
self.run_all_receive_mode_tests(False)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -4,12 +4,12 @@ import unittest
|
|||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {
|
||||||
"public_mode": False,
|
"public_mode": False,
|
||||||
"slug": "",
|
"password": "",
|
||||||
"save_private_key": True,
|
"save_private_key": True,
|
||||||
"close_after_first_download": False,
|
"close_after_first_download": False,
|
||||||
}
|
}
|
@ -4,13 +4,13 @@ import unittest
|
|||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {
|
||||||
"use_legacy_v2_onions": True,
|
"use_legacy_v2_onions": True,
|
||||||
"public_mode": False,
|
"public_mode": False,
|
||||||
"slug": "",
|
"password": "",
|
||||||
"save_private_key": True,
|
"save_private_key": True,
|
||||||
"close_after_first_download": False,
|
"close_after_first_download": False,
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ LOG_MSG_REGEX = re.compile(r"""
|
|||||||
^\[Jun\ 06\ 2013\ 11:05:00\]
|
^\[Jun\ 06\ 2013\ 11:05:00\]
|
||||||
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
||||||
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
|
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
|
||||||
SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
|
PASSWORD_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
|
||||||
|
|
||||||
|
|
||||||
# TODO: Improve the Common tests to test it all as a single class
|
# TODO: Improve the Common tests to test it all as a single class
|
||||||
|
|
||||||
|
|
||||||
class TestBuildSlug:
|
class TestBuildPassword:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize('test_input,expected', (
|
||||||
# VALID, two lowercase words, separated by a hyphen
|
# VALID, two lowercase words, separated by a hyphen
|
||||||
('syrup-enzyme', True),
|
('syrup-enzyme', True),
|
||||||
@ -60,8 +60,8 @@ class TestBuildSlug:
|
|||||||
('too-many-hyphens-', False),
|
('too-many-hyphens-', False),
|
||||||
('symbols-!@#$%', False)
|
('symbols-!@#$%', False)
|
||||||
))
|
))
|
||||||
def test_build_slug_regex(self, test_input, expected):
|
def test_build_password_regex(self, test_input, expected):
|
||||||
""" Test that `SLUG_REGEX` accounts for the following patterns
|
""" Test that `PASSWORD_REGEX` accounts for the following patterns
|
||||||
|
|
||||||
There are a few hyphenated words in `wordlist.txt`:
|
There are a few hyphenated words in `wordlist.txt`:
|
||||||
* drop-down
|
* drop-down
|
||||||
@ -69,17 +69,17 @@ class TestBuildSlug:
|
|||||||
* t-shirt
|
* t-shirt
|
||||||
* yo-yo
|
* yo-yo
|
||||||
|
|
||||||
These words cause a few extra potential slug patterns:
|
These words cause a few extra potential password patterns:
|
||||||
* word-word
|
* word-word
|
||||||
* hyphenated-word-word
|
* hyphenated-word-word
|
||||||
* word-hyphenated-word
|
* word-hyphenated-word
|
||||||
* hyphenated-word-hyphenated-word
|
* hyphenated-word-hyphenated-word
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert bool(SLUG_REGEX.match(test_input)) == expected
|
assert bool(PASSWORD_REGEX.match(test_input)) == expected
|
||||||
|
|
||||||
def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode):
|
def test_build_password_unique(self, common_obj, sys_onionshare_dev_mode):
|
||||||
assert common_obj.build_slug() != common_obj.build_slug()
|
assert common_obj.build_password() != common_obj.build_password()
|
||||||
|
|
||||||
|
|
||||||
class TestDirSize:
|
class TestDirSize:
|
||||||
|
@ -63,7 +63,7 @@ class TestSettings:
|
|||||||
'use_legacy_v2_onions': False,
|
'use_legacy_v2_onions': False,
|
||||||
'save_private_key': False,
|
'save_private_key': False,
|
||||||
'private_key': '',
|
'private_key': '',
|
||||||
'slug': '',
|
'password': '',
|
||||||
'hidservauth_string': '',
|
'hidservauth_string': '',
|
||||||
'data_dir': os.path.expanduser('~/OnionShare'),
|
'data_dir': os.path.expanduser('~/OnionShare'),
|
||||||
'public_mode': False
|
'public_mode': False
|
||||||
|
@ -27,8 +27,10 @@ import socket
|
|||||||
import sys
|
import sys
|
||||||
import zipfile
|
import zipfile
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import base64
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from werkzeug.datastructures import Headers
|
||||||
|
|
||||||
from onionshare.common import Common
|
from onionshare.common import Common
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
@ -44,7 +46,7 @@ def web_obj(common_obj, mode, num_files=0):
|
|||||||
common_obj.settings = Settings(common_obj)
|
common_obj.settings = Settings(common_obj)
|
||||||
strings.load_strings(common_obj)
|
strings.load_strings(common_obj)
|
||||||
web = Web(common_obj, False, mode)
|
web = Web(common_obj, False, mode)
|
||||||
web.generate_slug()
|
web.generate_password()
|
||||||
web.stay_open = True
|
web.stay_open = True
|
||||||
web.running = True
|
web.running = True
|
||||||
|
|
||||||
@ -71,22 +73,23 @@ class TestWeb:
|
|||||||
web = web_obj(common_obj, 'share', 3)
|
web = web_obj(common_obj, 'share', 3)
|
||||||
assert web.mode is 'share'
|
assert web.mode is 'share'
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load 404 pages
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get('/')
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 404
|
assert res.status_code == 401
|
||||||
|
|
||||||
res = c.get('/invalidslug'.format(web.slug))
|
# Load / with invalid auth
|
||||||
|
res = c.get('/', headers=self._make_auth_headers('invalid'))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 404
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load download page
|
# Load / with valid auth
|
||||||
res = c.get('/{}'.format(web.slug))
|
res = c.get('/', headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
res = c.get('/{}/download'.format(web.slug))
|
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == 'application/zip'
|
||||||
@ -99,7 +102,7 @@ class TestWeb:
|
|||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Download the first time
|
# Download the first time
|
||||||
res = c.get('/{}/download'.format(web.slug))
|
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == 'application/zip'
|
||||||
@ -114,7 +117,7 @@ class TestWeb:
|
|||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Download the first time
|
# Download the first time
|
||||||
res = c.get('/{}/download'.format(web.slug))
|
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == 'application/zip'
|
||||||
@ -125,17 +128,18 @@ class TestWeb:
|
|||||||
assert web.mode is 'receive'
|
assert web.mode is 'receive'
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load 404 pages
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get('/')
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 404
|
assert res.status_code == 401
|
||||||
|
|
||||||
res = c.get('/invalidslug'.format(web.slug))
|
# Load / with invalid auth
|
||||||
|
res = c.get('/', headers=self._make_auth_headers('invalid'))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 404
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load upload page
|
# Load / with valid auth
|
||||||
res = c.get('/{}'.format(web.slug))
|
res = c.get('/', headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
@ -144,31 +148,37 @@ class TestWeb:
|
|||||||
common_obj.settings.set('public_mode', True)
|
common_obj.settings.set('public_mode', True)
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Upload page should be accessible from /
|
# Loading / should work without auth
|
||||||
res = c.get('/')
|
res = c.get('/')
|
||||||
data1 = res.get_data()
|
data1 = res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
# /[slug] should be a 404
|
|
||||||
res = c.get('/{}'.format(web.slug))
|
|
||||||
data2 = res.get_data()
|
|
||||||
assert res.status_code == 404
|
|
||||||
|
|
||||||
def test_public_mode_off(self, common_obj):
|
def test_public_mode_off(self, common_obj):
|
||||||
web = web_obj(common_obj, 'receive')
|
web = web_obj(common_obj, 'receive')
|
||||||
common_obj.settings.set('public_mode', False)
|
common_obj.settings.set('public_mode', False)
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# / should be a 404
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get('/')
|
||||||
data1 = res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 404
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Upload page should be accessible from /[slug]
|
# But static resources should work without auth
|
||||||
res = c.get('/{}'.format(web.slug))
|
res = c.get('{}/css/style.css'.format(web.static_url_path))
|
||||||
data2 = res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
|
# Load / with valid auth
|
||||||
|
res = c.get('/', headers=self._make_auth_headers(web.password))
|
||||||
|
res.get_data()
|
||||||
|
assert res.status_code == 200
|
||||||
|
|
||||||
|
def _make_auth_headers(self, password):
|
||||||
|
auth = base64.b64encode(b'onionshare:'+password.encode()).decode()
|
||||||
|
h = Headers()
|
||||||
|
h.add('Authorization', 'Basic ' + auth)
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
class TestZipWriterDefault:
|
class TestZipWriterDefault:
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize('test_input', (
|
||||||
|
Loading…
Reference in New Issue
Block a user