summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml5
-rw-r--r--cli/onionshare_cli/__init__.py21
-rw-r--r--cli/onionshare_cli/web/chat_mode.py7
-rw-r--r--cli/onionshare_cli/web/receive_mode.py18
-rw-r--r--cli/onionshare_cli/web/send_base_mode.py4
-rw-r--r--cli/onionshare_cli/web/share_mode.py13
-rw-r--r--cli/onionshare_cli/web/web.py64
-rw-r--r--cli/tests/test_cli_common.py2
-rw-r--r--cli/tests/test_cli_settings.py2
-rw-r--r--cli/tests/test_cli_web.py4
10 files changed, 62 insertions, 78 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3be131b3..175595f3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,8 +1,3 @@
-# To run the tests, CircleCI needs these environment variables:
-# QT_EMAIL - email address for a Qt account
-# QT_PASSWORD - password for a Qt account
-# (Unfortunately you can't install Qt without logging in.)
-
version: 2
workflows:
version: 2
diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py
index a359f770..4bc00929 100644
--- a/cli/onionshare_cli/__init__.py
+++ b/cli/onionshare_cli/__init__.py
@@ -201,15 +201,6 @@ def main(cwd=None):
disable_csp = bool(args.disable_csp)
verbose = bool(args.verbose)
- if receive:
- mode = "receive"
- elif website:
- mode = "website"
- elif chat:
- mode = "chat"
- else:
- mode = "share"
-
# Verbose mode?
common.verbose = verbose
@@ -223,16 +214,26 @@ def main(cwd=None):
if persistent_filename:
mode_settings = ModeSettings(common, persistent_filename)
mode_settings.set("persistent", "enabled", True)
- mode_settings.set("persistent", "mode", mode)
else:
mode_settings = ModeSettings(common)
+ if receive:
+ mode = "receive"
+ elif website:
+ mode = "website"
+ elif chat:
+ mode = "chat"
+ else:
+ mode = "share"
+
if mode_settings.just_created:
# This means the mode settings were just created, not loaded from disk
mode_settings.set("general", "title", title)
mode_settings.set("general", "public", public)
mode_settings.set("general", "autostart_timer", autostart_timer)
mode_settings.set("general", "autostop_timer", autostop_timer)
+ if persistent_filename:
+ mode_settings.set("persistent", "mode", mode)
if mode == "share":
mode_settings.set("share", "autostop_sharing", autostop_sharing)
if mode == "receive":
diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py
index f6dc2d1a..e92ce385 100644
--- a/cli/onionshare_cli/web/chat_mode.py
+++ b/cli/onionshare_cli/web/chat_mode.py
@@ -68,15 +68,12 @@ class ChatModeWeb:
)
self.web.add_request(self.web.REQUEST_LOAD, request.path)
- r = make_response(
- render_template(
+ return render_template(
"chat.html",
static_url_path=self.web.static_url_path,
username=session.get("name"),
title=self.web.settings.get("general", "title"),
- )
)
- return self.web.add_security_headers(r)
@self.web.app.route("/update-session-username", methods=["POST"], provide_automatic_options=False)
def update_session_username():
@@ -112,7 +109,7 @@ class ChatModeWeb:
success=False,
)
)
- return self.web.add_security_headers(r)
+ return r
@self.web.socketio.on("joined", namespace="/chat")
def joined(message):
diff --git a/cli/onionshare_cli/web/receive_mode.py b/cli/onionshare_cli/web/receive_mode.py
index 76abb0a8..6b106d37 100644
--- a/cli/onionshare_cli/web/receive_mode.py
+++ b/cli/onionshare_cli/web/receive_mode.py
@@ -86,16 +86,13 @@ class ReceiveModeWeb:
)
self.web.add_request(self.web.REQUEST_LOAD, request.path)
- r = make_response(
- render_template(
- "receive.html",
- static_url_path=self.web.static_url_path,
- disable_text=self.web.settings.get("receive", "disable_text"),
- disable_files=self.web.settings.get("receive", "disable_files"),
- title=self.web.settings.get("general", "title"),
- )
+ return render_template(
+ "receive.html",
+ static_url_path=self.web.static_url_path,
+ disable_text=self.web.settings.get("receive", "disable_text"),
+ disable_files=self.web.settings.get("receive", "disable_files"),
+ title=self.web.settings.get("general", "title")
)
- return self.web.add_security_headers(r)
@self.web.app.route("/upload", methods=["POST"], provide_automatic_options=False)
def upload(ajax=False):
@@ -222,12 +219,11 @@ class ReceiveModeWeb:
)
else:
# It was the last upload and the timer ran out
- r = make_response(
+ return make_response(
render_template("thankyou.html"),
static_url_path=self.web.static_url_path,
title=self.web.settings.get("general", "title"),
)
- return self.web.add_security_headers(r)
@self.web.app.route("/upload-ajax", methods=["POST"], provide_automatic_options=False)
def upload_ajax_public():
diff --git a/cli/onionshare_cli/web/send_base_mode.py b/cli/onionshare_cli/web/send_base_mode.py
index e448d2dd..27de598a 100644
--- a/cli/onionshare_cli/web/send_base_mode.py
+++ b/cli/onionshare_cli/web/send_base_mode.py
@@ -149,10 +149,9 @@ class SendBaseModeWeb:
# If filesystem_path is None, this is the root directory listing
files, dirs = self.build_directory_listing(path, filenames, filesystem_path)
- r = self.directory_listing_template(
+ return self.directory_listing_template(
path, files, dirs, breadcrumbs, breadcrumbs_leaf
)
- return self.web.add_security_headers(r)
def build_directory_listing(self, path, filenames, filesystem_path):
files = []
@@ -286,7 +285,6 @@ class SendBaseModeWeb:
"filename*": "UTF-8''%s" % url_quote(basename),
}
r.headers.set("Content-Disposition", "inline", **filename_dict)
- r = self.web.add_security_headers(r)
(content_type, _) = mimetypes.guess_type(basename, strict=False)
if content_type is not None:
r.headers.set("Content-Type", content_type)
diff --git a/cli/onionshare_cli/web/share_mode.py b/cli/onionshare_cli/web/share_mode.py
index 51ddd674..8ac4055e 100644
--- a/cli/onionshare_cli/web/share_mode.py
+++ b/cli/onionshare_cli/web/share_mode.py
@@ -25,7 +25,7 @@ import sys
import tempfile
import zipfile
import mimetypes
-from datetime import datetime
+from datetime import datetime, timezone
from flask import Response, request, render_template, make_response, abort
from unidecode import unidecode
from werkzeug.http import parse_date, http_date
@@ -127,7 +127,7 @@ class ShareModeWeb(SendBaseModeWeb):
self.download_etag = None
self.gzip_etag = None
- self.last_modified = datetime.utcnow()
+ self.last_modified = datetime.now(tz=timezone.utc)
def define_routes(self):
"""
@@ -149,8 +149,7 @@ class ShareModeWeb(SendBaseModeWeb):
and self.download_in_progress
)
if deny_download:
- r = make_response(render_template("denied.html"))
- return self.web.add_security_headers(r)
+ return render_template("denied.html")
# If download is allowed to continue, serve download page
if self.should_use_gzip():
@@ -172,8 +171,7 @@ class ShareModeWeb(SendBaseModeWeb):
and self.download_in_progress
)
if deny_download:
- r = make_response(render_template("denied.html"))
- return self.web.add_security_headers(r)
+ return render_template("denied.html")
# Prepare some variables to use inside generate() function below
# which is outside of the request context
@@ -232,7 +230,6 @@ class ShareModeWeb(SendBaseModeWeb):
"filename*": "UTF-8''%s" % url_quote(basename),
}
r.headers.set("Content-Disposition", "attachment", **filename_dict)
- r = self.web.add_security_headers(r)
# guess content type
(content_type, _) = mimetypes.guess_type(basename, strict=False)
if content_type is not None:
@@ -288,6 +285,8 @@ class ShareModeWeb(SendBaseModeWeb):
if_unmod = request.headers.get("If-Unmodified-Since")
if if_unmod:
if_date = parse_date(if_unmod)
+ if if_date and not if_date.tzinfo:
+ if_date = if_date.replace(tzinfo=timezone.utc) # Compatible with Flask < 2.0.0
if if_date and if_date > last_modified:
abort(412)
elif range_header is None:
diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py
index 0f2dfe7e..e12fccc7 100644
--- a/cli/onionshare_cli/web/web.py
+++ b/cli/onionshare_cli/web/web.py
@@ -191,6 +191,21 @@ class Web:
Common web app routes between all modes.
"""
+ @self.app.after_request
+ def add_security_headers(r):
+ """
+ Add security headers to a response
+ """
+ for header, value in self.security_headers:
+ r.headers.set(header, value)
+ # Set a CSP header unless in website mode and the user has disabled it
+ if not self.settings.get("website", "disable_csp") or self.mode != "website":
+ r.headers.set(
+ "Content-Security-Policy",
+ "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; img-src 'self' data:;",
+ )
+ return r
+
@self.app.errorhandler(404)
def not_found(e):
mode = self.get_mode()
@@ -232,10 +247,7 @@ class Web:
def error403(self):
self.add_request(Web.REQUEST_OTHER, request.path)
- r = make_response(
- render_template("403.html", static_url_path=self.static_url_path), 403
- )
- return self.add_security_headers(r)
+ return render_template("403.html", static_url_path=self.static_url_path), 403
def error404(self, history_id):
mode = self.get_mode()
@@ -247,10 +259,7 @@ class Web:
)
self.add_request(Web.REQUEST_OTHER, request.path)
- r = make_response(
- render_template("404.html", static_url_path=self.static_url_path), 404
- )
- return self.add_security_headers(r)
+ return render_template("404.html", static_url_path=self.static_url_path), 404
def error405(self, history_id):
mode = self.get_mode()
@@ -262,10 +271,7 @@ class Web:
)
self.add_request(Web.REQUEST_OTHER, request.path)
- r = make_response(
- render_template("405.html", static_url_path=self.static_url_path), 405
- )
- return self.add_security_headers(r)
+ return render_template("405.html", static_url_path=self.static_url_path), 405
def error500(self, history_id):
mode = self.get_mode()
@@ -277,24 +283,7 @@ class Web:
)
self.add_request(Web.REQUEST_OTHER, request.path)
- r = make_response(
- render_template("500.html", static_url_path=self.static_url_path), 500
- )
- return self.add_security_headers(r)
-
- def add_security_headers(self, r):
- """
- Add security headers to a request
- """
- for header, value in self.security_headers:
- r.headers.set(header, value)
- # Set a CSP header unless in website mode and the user has disabled it
- if not self.settings.get("website", "disable_csp") or self.mode != "website":
- r.headers.set(
- "Content-Security-Policy",
- "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; img-src 'self' data:;",
- )
- return r
+ return render_template("500.html", static_url_path=self.static_url_path), 500
def _safe_select_jinja_autoescape(self, filename):
if filename is None:
@@ -372,9 +361,18 @@ class Web:
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
# (We're putting the shutdown_password in the path as well to make routing simpler)
if self.running:
- requests.get(
- f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown"
- )
+ try:
+ requests.get(
+ f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown"
+ )
+ except requests.exceptions.ConnectionError as e:
+ # The way flask-socketio stops a connection when running using
+ # eventlet is by raising SystemExit to abort all the processes.
+ # Hence the connections are closed and no response is returned
+ # to the above request. So I am just catching the ConnectionError
+ # to check if it was chat mode, in which case it's okay
+ if self.mode != "chat":
+ raise e
def cleanup(self):
"""
diff --git a/cli/tests/test_cli_common.py b/cli/tests/test_cli_common.py
index 3288e52b..9f113a84 100644
--- a/cli/tests/test_cli_common.py
+++ b/cli/tests/test_cli_common.py
@@ -169,7 +169,7 @@ class TestGetTorPaths:
obfs4proxy_file_path,
)
- @pytest.mark.skipif(sys.platform != "Linux", reason="requires Linux")
+ @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
def test_get_tor_paths_linux(self, platform_linux, common_obj):
(
tor_path,
diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py
index 4c012901..ed8d5bb9 100644
--- a/cli/tests/test_cli_settings.py
+++ b/cli/tests/test_cli_settings.py
@@ -123,7 +123,7 @@ class TestSettings:
"~/Library/Application Support/OnionShare-testdata/onionshare.json"
)
- @pytest.mark.skipif(sys.platform != "Linux", reason="requires Linux")
+ @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
def test_filename_linux(self, monkeypatch, platform_linux):
obj = settings.Settings(common.Common())
assert obj.filename == os.path.expanduser(
diff --git a/cli/tests/test_cli_web.py b/cli/tests/test_cli_web.py
index f2b1af62..71bfeeeb 100644
--- a/cli/tests/test_cli_web.py
+++ b/cli/tests/test_cli_web.py
@@ -569,7 +569,7 @@ class TestRangeRequests:
assert resp.status_code == 206
- @pytest.mark.skipif(sys.platform != "Linux", reason="requires Linux")
+ @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
@check_unsupported("curl", ["--version"])
def test_curl(self, temp_dir, tmpdir, common_obj):
web = web_obj(temp_dir, common_obj, "share", 3)
@@ -591,7 +591,7 @@ class TestRangeRequests:
]
)
- @pytest.mark.skipif(sys.platform != "Linux", reason="requires Linux")
+ @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
@check_unsupported("wget", ["--version"])
def test_wget(self, temp_dir, tmpdir, common_obj):
web = web_obj(temp_dir, common_obj, "share", 3)