mirror of
https://github.com/onionshare/onionshare.git
synced 2025-06-24 14:30:29 -04:00
Merge branch 'mig5-censorship_automatically_attempt_and_reconnect' into 1422_autodetect_location
This commit is contained in:
commit
feaecc9e4d
164 changed files with 5100 additions and 1852 deletions
|
@ -59,28 +59,13 @@ pip install --user onionshare-cli
|
|||
|
||||
#### Set path
|
||||
|
||||
When you install programs with pip and use the --user flag, it installs them into ~/.local/bin, which isn't in your path by default. To add ~/.local/bin to your path automatically for the next time you reopen the terminal or source your shell configuration file, do the following:
|
||||
When you install programs with pip and use the `--user` flag, it installs them into *~/.local/bin*, which isn't in your path by default. To add *~/.local/bin* to your path automatically for the next time you reopen the terminal or source your shell configuration file, do the following:
|
||||
|
||||
First, discover what shell you are using:
|
||||
Apply the path to your shell file:
|
||||
|
||||
```sh
|
||||
echo $SHELL
|
||||
```
|
||||
|
||||
Then apply the path to your shell file:
|
||||
|
||||
bash:
|
||||
|
||||
```sh
|
||||
echo "PATH=\$PATH:~/.local/bin" >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
zsh:
|
||||
|
||||
```sh
|
||||
echo "PATH=\$PATH:~/.local/bin" >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
printf "PATH=\$PATH:~/.local/bin\n" >> ~/.${SHELL##*/}rc
|
||||
. ~/.${SHELL##*/}rc
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
|
|
@ -150,7 +150,13 @@ def main(cwd=None):
|
|||
action="store_true",
|
||||
dest="disable_csp",
|
||||
default=False,
|
||||
help="Publish website: Disable Content Security Policy header (allows your website to use third-party resources)",
|
||||
help="Publish website: Disable the default Content Security Policy header (allows your website to use third-party resources)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--custom_csp",
|
||||
metavar="custom_csp",
|
||||
default=None,
|
||||
help="Publish website: Set a custom Content Security Policy header",
|
||||
)
|
||||
# Other
|
||||
parser.add_argument(
|
||||
|
@ -189,6 +195,7 @@ def main(cwd=None):
|
|||
disable_text = args.disable_text
|
||||
disable_files = args.disable_files
|
||||
disable_csp = bool(args.disable_csp)
|
||||
custom_csp = args.custom_csp
|
||||
verbose = bool(args.verbose)
|
||||
|
||||
# Verbose mode?
|
||||
|
@ -234,7 +241,15 @@ def main(cwd=None):
|
|||
mode_settings.set("receive", "disable_text", disable_text)
|
||||
mode_settings.set("receive", "disable_files", disable_files)
|
||||
if mode == "website":
|
||||
mode_settings.set("website", "disable_csp", disable_csp)
|
||||
if disable_csp and custom_csp:
|
||||
print("You cannot disable the CSP and set a custom one. Either set --disable-csp or --custom-csp but not both.")
|
||||
sys.exit()
|
||||
if disable_csp:
|
||||
mode_settings.set("website", "disable_csp", True)
|
||||
mode_settings.set("website", "custom_csp", None)
|
||||
if custom_csp:
|
||||
mode_settings.set("website", "custom_csp", custom_csp)
|
||||
mode_settings.set("website", "disable_csp", False)
|
||||
else:
|
||||
# See what the persistent mode was
|
||||
mode = mode_settings.get("persistent", "mode")
|
||||
|
|
|
@ -25,21 +25,46 @@ from .meek import MeekNotRunning
|
|||
class CensorshipCircumvention(object):
|
||||
"""
|
||||
Connect to the Tor Moat APIs to retrieve censorship
|
||||
circumvention recommendations, over the Meek client.
|
||||
circumvention recommendations or the latest bridges.
|
||||
|
||||
We support reaching this API over Tor, or Meek
|
||||
(domain fronting) if Tor is not connected.
|
||||
"""
|
||||
|
||||
def __init__(self, common, meek, domain_fronting=True):
|
||||
def __init__(self, common, meek=None, onion=None):
|
||||
"""
|
||||
Set up the CensorshipCircumvention object to hold
|
||||
common and meek objects.
|
||||
"""
|
||||
self.common = common
|
||||
self.meek = meek
|
||||
self.common.log("CensorshipCircumvention", "__init__")
|
||||
|
||||
# Bail out if we requested domain fronting but we can't use meek
|
||||
if domain_fronting and not self.meek.meek_proxies:
|
||||
raise MeekNotRunning()
|
||||
self.api_proxies = {}
|
||||
if meek:
|
||||
self.meek = meek
|
||||
if not self.meek.meek_proxies:
|
||||
raise MeekNotRunning()
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Meek with CensorShipCircumvention API",
|
||||
)
|
||||
self.api_proxies = self.meek.meek_proxies
|
||||
if onion:
|
||||
self.onion = onion
|
||||
if not self.onion.is_authenticated:
|
||||
return False
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Tor with CensorShipCircumvention API",
|
||||
)
|
||||
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
||||
self.api_proxies = {
|
||||
"http": f"socks5h://{socks_address}:{socks_port}",
|
||||
"https": f"socks5h://{socks_address}:{socks_port}",
|
||||
}
|
||||
|
||||
def request_map(self, country=False):
|
||||
"""
|
||||
|
@ -52,6 +77,8 @@ class CensorshipCircumvention(object):
|
|||
Note that this API endpoint doesn't return actual bridges,
|
||||
it just returns the recommended bridge type countries.
|
||||
"""
|
||||
if not self.api_proxies:
|
||||
return False
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/map"
|
||||
data = {}
|
||||
if country:
|
||||
|
@ -61,7 +88,7 @@ class CensorshipCircumvention(object):
|
|||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek.meek_proxies,
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
|
@ -95,6 +122,8 @@ class CensorshipCircumvention(object):
|
|||
Optionally, a list of transports can be specified in order to
|
||||
return recommended settings for just that transport type.
|
||||
"""
|
||||
if not self.api_proxies:
|
||||
return False
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
|
||||
data = {}
|
||||
if country:
|
||||
|
@ -105,7 +134,7 @@ class CensorshipCircumvention(object):
|
|||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek.meek_proxies,
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
|
@ -142,11 +171,13 @@ class CensorshipCircumvention(object):
|
|||
"""
|
||||
Retrieves the list of built-in bridges from the Tor Project.
|
||||
"""
|
||||
if not self.api_proxies:
|
||||
return False
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek.meek_proxies,
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
|
@ -167,3 +198,65 @@ class CensorshipCircumvention(object):
|
|||
return False
|
||||
|
||||
return result
|
||||
|
||||
def save_settings(self, settings, bridge_settings):
|
||||
"""
|
||||
Checks the bridges and saves them in settings.
|
||||
"""
|
||||
bridges_ok = False
|
||||
self.settings = settings
|
||||
|
||||
# @TODO there might be several bridge types recommended.
|
||||
# Should we attempt to iterate over each type if one of them fails to connect?
|
||||
# But if so, how to stop it starting 3 separate Tor connection threads?
|
||||
# for bridges in request_bridges["settings"]:
|
||||
bridges = bridge_settings["settings"][0]["bridges"]
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
f"Obtained bridges: {bridges}",
|
||||
)
|
||||
bridge_strings = bridges["bridge_strings"]
|
||||
bridge_type = bridges["type"]
|
||||
bridge_source = bridges["source"]
|
||||
|
||||
# If the recommended bridge source is to use the built-in
|
||||
# bridges, set that in our settings, as if the user had
|
||||
# selected the built-in bridges for a specific PT themselves.
|
||||
#
|
||||
if bridge_source == "builtin":
|
||||
self.settings.set("bridges_type", "built-in")
|
||||
if bridge_type == "obfs4":
|
||||
self.settings.set("bridges_builtin_pt", "obfs4")
|
||||
if bridge_type == "snowflake":
|
||||
self.settings.set("bridges_builtin_pt", "snowflake")
|
||||
if bridge_type == "meek":
|
||||
self.settings.set("bridges_builtin_pt", "meek-azure")
|
||||
bridges_ok = True
|
||||
else:
|
||||
# Any other type of bridge we can treat as custom.
|
||||
self.settings.set("bridges_type", "custom")
|
||||
|
||||
# Sanity check the bridges provided from the Tor API before saving
|
||||
bridges_checked = self.common.check_bridges_valid(bridge_strings)
|
||||
|
||||
if bridges_checked:
|
||||
self.settings.set("bridges_custom", "\n".join(bridges_checked))
|
||||
bridges_ok = True
|
||||
|
||||
# If we got any good bridges, save them to settings and return.
|
||||
if bridges_ok:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Saving settings with automatically-obtained bridges",
|
||||
)
|
||||
self.settings.save()
|
||||
return True
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Could not use any of the obtained bridges.",
|
||||
)
|
||||
return False
|
||||
|
|
|
@ -28,6 +28,7 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
import shutil
|
||||
import re
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
import colorama
|
||||
|
@ -440,6 +441,40 @@ class Common:
|
|||
r = random.SystemRandom()
|
||||
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||
|
||||
def check_bridges_valid(self, bridges):
|
||||
"""
|
||||
Does a regex check against a supplied list of bridges, to make sure they
|
||||
are valid strings depending on the bridge type.
|
||||
"""
|
||||
valid_bridges = []
|
||||
self.log("Common", "check_bridges_valid", "Checking bridge syntax")
|
||||
for bridge in bridges:
|
||||
if bridge != "":
|
||||
# Check the syntax of the custom bridge to make sure it looks legitimate
|
||||
ipv4_pattern = re.compile(
|
||||
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
|
||||
)
|
||||
ipv6_pattern = re.compile(
|
||||
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
|
||||
)
|
||||
meek_lite_pattern = re.compile(
|
||||
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
|
||||
)
|
||||
snowflake_pattern = re.compile(
|
||||
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
||||
)
|
||||
if (
|
||||
ipv4_pattern.match(bridge)
|
||||
or ipv6_pattern.match(bridge)
|
||||
or meek_lite_pattern.match(bridge)
|
||||
or snowflake_pattern.match(bridge)
|
||||
):
|
||||
valid_bridges.append(bridge)
|
||||
if valid_bridges:
|
||||
return valid_bridges
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_flatpak(self):
|
||||
"""
|
||||
Returns True if OnionShare is running in a Flatpak sandbox
|
||||
|
@ -452,6 +487,7 @@ class Common:
|
|||
"""
|
||||
return os.environ.get("SNAP_INSTANCE_NAME") == "onionshare"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def random_string(num_bytes, output_len=None):
|
||||
"""
|
||||
|
|
|
@ -55,7 +55,11 @@ class ModeSettings:
|
|||
"disable_text": False,
|
||||
"disable_files": False,
|
||||
},
|
||||
"website": {"disable_csp": False, "filenames": []},
|
||||
"website": {
|
||||
"disable_csp": False,
|
||||
"custom_csp": None,
|
||||
"filenames": []
|
||||
},
|
||||
"chat": {"room": "default"},
|
||||
}
|
||||
self._settings = {}
|
||||
|
|
|
@ -18,17 +18,19 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from .censorship import CensorshipCircumvention
|
||||
from .meek import Meek
|
||||
from stem.control import Controller
|
||||
from stem import ProtocolError, SocketClosed
|
||||
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
||||
import base64
|
||||
import nacl.public
|
||||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import time
|
||||
import shlex
|
||||
import psutil
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion as Version
|
||||
|
@ -258,9 +260,7 @@ class Onion(object):
|
|||
and cmdline[2] == self.tor_torrc
|
||||
):
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
"found a stale tor process, killing it",
|
||||
"Onion", "connect", "found a stale tor process, killing it"
|
||||
)
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
@ -317,49 +317,75 @@ class Onion(object):
|
|||
)
|
||||
|
||||
with open(self.tor_torrc, "w") as f:
|
||||
self.common.log("Onion", "connect", "Writing torrc template file")
|
||||
f.write(torrc_template)
|
||||
|
||||
# Bridge support
|
||||
if self.settings.get("bridges_enabled"):
|
||||
f.write("\nUseBridges 1\n")
|
||||
if self.settings.get("bridges_type") == "built-in":
|
||||
if self.settings.get("bridges_builtin_pt") == "obfs4":
|
||||
with open(
|
||||
self.common.get_resource_path("torrc_template-obfs4")
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
elif self.settings.get("bridges_builtin_pt") == "meek-azure":
|
||||
with open(
|
||||
self.common.get_resource_path(
|
||||
"torrc_template-meek_lite_azure"
|
||||
use_torrc_bridge_templates = False
|
||||
builtin_bridge_type = self.settings.get("bridges_builtin_pt")
|
||||
# Use built-inbridges stored in settings, if they are there already.
|
||||
# They are probably newer than that of our hardcoded copies.
|
||||
if self.settings.get("bridges_builtin"):
|
||||
try:
|
||||
for line in self.settings.get("bridges_builtin")[
|
||||
builtin_bridge_type
|
||||
]:
|
||||
if line.strip() != "":
|
||||
f.write(f"Bridge {line}\n")
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
"Wrote in the built-in bridges from OnionShare settings",
|
||||
)
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
elif self.settings.get("bridges_builtin_pt") == "snowflake":
|
||||
with open(
|
||||
self.common.get_resource_path(
|
||||
"torrc_template-snowflake"
|
||||
)
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
|
||||
except KeyError:
|
||||
# Somehow we had built-in bridges in our settings, but
|
||||
# not for this bridge type. Fall back to using the hard-
|
||||
# coded templates.
|
||||
use_torrc_bridge_templates = True
|
||||
else:
|
||||
use_torrc_bridge_templates = True
|
||||
if use_torrc_bridge_templates:
|
||||
if builtin_bridge_type == "obfs4":
|
||||
with open(
|
||||
self.common.get_resource_path(
|
||||
"torrc_template-obfs4"
|
||||
)
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
elif builtin_bridge_type == "meek-azure":
|
||||
with open(
|
||||
self.common.get_resource_path(
|
||||
"torrc_template-meek_lite_azure"
|
||||
)
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
elif builtin_bridge_type == "snowflake":
|
||||
with open(
|
||||
self.common.get_resource_path(
|
||||
"torrc_template-snowflake"
|
||||
)
|
||||
) as o:
|
||||
f.write(o.read())
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
"Wrote in the built-in bridges from torrc templates",
|
||||
)
|
||||
elif self.settings.get("bridges_type") == "moat":
|
||||
for line in self.settings.get("bridges_moat").split("\n"):
|
||||
if line.strip() != "":
|
||||
f.write(f"Bridge {line}\n")
|
||||
f.write("\nUseBridges 1\n")
|
||||
|
||||
elif self.settings.get("bridges_type") == "custom":
|
||||
for line in self.settings.get("bridges_custom").split("\n"):
|
||||
if line.strip() != "":
|
||||
f.write(f"Bridge {line}\n")
|
||||
f.write("\nUseBridges 1\n")
|
||||
|
||||
# Execute a tor subprocess
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
f"starting {self.tor_path} subprocess",
|
||||
)
|
||||
self.common.log("Onion", "connect", f"starting {self.tor_path} subprocess")
|
||||
start_ts = time.time()
|
||||
if self.common.platform == "Windows":
|
||||
# In Windows, hide console window when opening tor.exe subprocess
|
||||
|
@ -385,19 +411,11 @@ class Onion(object):
|
|||
)
|
||||
|
||||
# Wait for the tor controller to start
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
f"tor pid: {self.tor_proc.pid}",
|
||||
)
|
||||
self.common.log("Onion", "connect", f"tor pid: {self.tor_proc.pid}")
|
||||
time.sleep(2)
|
||||
|
||||
# Connect to the controller
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"connect",
|
||||
"authenticating to tor controller",
|
||||
)
|
||||
self.common.log("Onion", "connect", "authenticating to tor controller")
|
||||
try:
|
||||
if (
|
||||
self.common.platform == "Windows"
|
||||
|
@ -638,6 +656,14 @@ class Onion(object):
|
|||
# https://trac.torproject.org/projects/tor/ticket/28619
|
||||
self.supports_v3_onions = self.tor_version >= Version("0.3.5.7")
|
||||
|
||||
# Now that we are connected to Tor, if we are using built-in bridges,
|
||||
# update them with the latest copy available from the Tor API
|
||||
if (
|
||||
self.settings.get("bridges_enabled")
|
||||
and self.settings.get("bridges_type") == "built-in"
|
||||
):
|
||||
self.update_builtin_bridges()
|
||||
|
||||
def is_authenticated(self):
|
||||
"""
|
||||
Returns True if the Tor connection is still working, or False otherwise.
|
||||
|
@ -881,3 +907,68 @@ class Onion(object):
|
|||
return ("127.0.0.1", 9150)
|
||||
else:
|
||||
return (self.settings.get("socks_address"), self.settings.get("socks_port"))
|
||||
|
||||
def update_builtin_bridges(self):
|
||||
"""
|
||||
Use the CensorshipCircumvention API to fetch the latest built-in bridges
|
||||
and update them in settings.
|
||||
"""
|
||||
builtin_bridges = False
|
||||
meek = None
|
||||
# Try obtaining bridges over Tor, if we're connected to it.
|
||||
if self.is_authenticated:
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"update_builtin_bridges",
|
||||
"Updating the built-in bridges. Trying over Tor first",
|
||||
)
|
||||
self.censorship_circumvention = CensorshipCircumvention(
|
||||
self.common, None, self
|
||||
)
|
||||
builtin_bridges = self.censorship_circumvention.request_builtin_bridges()
|
||||
|
||||
if not builtin_bridges:
|
||||
# Tor was not running or it failed to hit the Tor API.
|
||||
# Fall back to using Meek (domain-fronting).
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"update_builtin_bridges",
|
||||
"Updating the built-in bridges. Trying via Meek (no Tor)",
|
||||
)
|
||||
meek = Meek(self.common)
|
||||
meek.start()
|
||||
self.censorship_circumvention = CensorshipCircumvention(
|
||||
self.common, meek, None
|
||||
)
|
||||
builtin_bridges = self.censorship_circumvention.request_builtin_bridges()
|
||||
meek.cleanup()
|
||||
|
||||
if builtin_bridges:
|
||||
# If we got to this point, we have bridges
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"update_builtin_bridges",
|
||||
f"Obtained bridges: {builtin_bridges}",
|
||||
)
|
||||
if builtin_bridges["meek"]:
|
||||
# Meek bridge needs to be defined as "meek_lite", not "meek",
|
||||
# for it to work with obfs4proxy.
|
||||
# We also refer to this bridge type as 'meek-azure' in our settings.
|
||||
# So first, rename the key in the dict
|
||||
builtin_bridges["meek-azure"] = builtin_bridges.pop("meek")
|
||||
new_meek_bridges = []
|
||||
# Now replace the values. They also need the url/front params appended
|
||||
for item in builtin_bridges["meek-azure"]:
|
||||
newline = item.replace("meek", "meek_lite")
|
||||
new_meek_bridges.append(
|
||||
f"{newline} url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com"
|
||||
)
|
||||
builtin_bridges["meek-azure"] = new_meek_bridges
|
||||
# Save the new settings
|
||||
self.settings.set("bridges_builtin", builtin_bridges)
|
||||
self.settings.save()
|
||||
else:
|
||||
self.common.log(
|
||||
"Onion", "update_builtin_bridges", "Error getting built-in bridges"
|
||||
)
|
||||
return False
|
||||
|
|
|
@ -40,9 +40,6 @@ class OnionShare(object):
|
|||
self.onion_host = None
|
||||
self.port = None
|
||||
|
||||
# files and dirs to delete on shutdown
|
||||
self.cleanup_filenames = []
|
||||
|
||||
# do not use tor -- for development
|
||||
self.local_only = local_only
|
||||
|
||||
|
@ -75,7 +72,9 @@ class OnionShare(object):
|
|||
if self.local_only:
|
||||
self.onion_host = f"127.0.0.1:{self.port}"
|
||||
if not mode_settings.get("general", "public"):
|
||||
self.auth_string = "E2GOT5LTUTP3OAMRCRXO4GSH6VKJEUOXZQUC336SRKAHTTT5OVSA"
|
||||
self.auth_string = (
|
||||
"E2GOT5LTUTP3OAMRCRXO4GSH6VKJEUOXZQUC336SRKAHTTT5OVSA"
|
||||
)
|
||||
return
|
||||
|
||||
self.onion_host = self.onion.start_onion_service(
|
||||
|
|
|
@ -320,15 +320,15 @@ div#uploads .upload .upload-status {
|
|||
}
|
||||
|
||||
div#uploads .upload input.cancel {
|
||||
color: #d0011b;
|
||||
color: #d0011b;
|
||||
border: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0px;
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
float:right;
|
||||
}
|
||||
|
@ -398,4 +398,4 @@ a {
|
|||
|
||||
a:visited {
|
||||
color: #601ca0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ $(function () {
|
|||
// Store current username received from app context
|
||||
var current_username = $('#username').val();
|
||||
|
||||
// On browser connect, emit a socket event to be added to
|
||||
// On browser connect, emit a socket event to be added to
|
||||
// room and assigned random username
|
||||
socket.on('connect', function () {
|
||||
socket.emit('joined', {});
|
||||
|
@ -148,7 +148,7 @@ var getScrollDiffBefore = function () {
|
|||
|
||||
var scrollBottomMaybe = function (scrollDiff) {
|
||||
// Scrolls to bottom if the user is scrolled at bottom
|
||||
// if the user has scrolled upp, it wont scroll at bottom.
|
||||
// if the user has scrolled up, it won't scroll at bottom.
|
||||
// Note: when a user themselves send a message, it will still
|
||||
// scroll to the bottom even if they had scrolled up before.
|
||||
if (scrollDiff > 0) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<title>OnionShare: 403 Forbidden</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<title>OnionShare: 404 Not Found</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<title>OnionShare: 405 Method Not Allowed</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<title>OnionShare: An error occurred</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# Enable built-in meek-azure bridge
|
||||
Bridge meek_lite 0.0.2.0:3 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
UseBridges 1
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# Enable built-in obfs4-bridge
|
||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 [2a0c:4d80:42:702::1]:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
UseBridges 1
|
||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
|
||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
Bridge obfs4 [2a0c:4d80:42:702::1]:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
# Enable built-in snowflake bridge
|
||||
Bridge snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72
|
||||
UseBridges 1
|
||||
Bridge snowflake 0.0.3.0:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72
|
||||
|
|
|
@ -111,9 +111,11 @@ class Settings(object):
|
|||
"bridges_builtin_pt": "obfs4", # "obfs4", "meek-azure", or "snowflake"
|
||||
"bridges_moat": "",
|
||||
"bridges_custom": "",
|
||||
"bridges_builtin": {},
|
||||
"persistent_tabs": [],
|
||||
"locale": None, # this gets defined in fill_in_defaults()
|
||||
"theme": 0,
|
||||
"censorship_circumvention": False,
|
||||
}
|
||||
self._settings = {}
|
||||
self.fill_in_defaults()
|
||||
|
|
|
@ -42,10 +42,11 @@ class SendBaseModeWeb:
|
|||
self.is_zipped = False
|
||||
self.download_filename = None
|
||||
self.download_filesize = None
|
||||
self.gzip_filename = None
|
||||
self.gzip_filesize = None
|
||||
self.zip_writer = None
|
||||
|
||||
# Store the tempfile objects here, so when they're garbage collected the files are deleted
|
||||
self.gzip_files = []
|
||||
|
||||
# If autostop_sharing, only allow one download at a time
|
||||
self.download_in_progress = False
|
||||
|
||||
|
@ -192,12 +193,15 @@ class SendBaseModeWeb:
|
|||
# gzip compress the individual file, if it hasn't already been compressed
|
||||
if use_gzip:
|
||||
if filesystem_path not in self.gzip_individual_files:
|
||||
gzip_filename = tempfile.mkstemp("wb+")[1]
|
||||
self._gzip_compress(filesystem_path, gzip_filename, 6, None)
|
||||
self.gzip_individual_files[filesystem_path] = gzip_filename
|
||||
self.gzip_files.append(
|
||||
tempfile.NamedTemporaryFile("wb+", dir=self.common.build_tmp_dir())
|
||||
)
|
||||
gzip_file = self.gzip_files[-1]
|
||||
self._gzip_compress(filesystem_path, gzip_file.name, 6, None)
|
||||
self.gzip_individual_files[filesystem_path] = gzip_file.name
|
||||
|
||||
# Make sure the gzip file gets cleaned up when onionshare stops
|
||||
self.web.cleanup_filenames.append(gzip_filename)
|
||||
# Cleanup this temp file
|
||||
self.web.cleanup_tempfiles.append(gzip_file)
|
||||
|
||||
file_to_download = self.gzip_individual_files[filesystem_path]
|
||||
filesize = os.path.getsize(self.gzip_individual_files[filesystem_path])
|
||||
|
|
|
@ -134,8 +134,12 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
The web app routes for sharing files
|
||||
"""
|
||||
|
||||
@self.web.app.route("/", defaults={"path": ""}, methods=["GET"], provide_automatic_options=False)
|
||||
@self.web.app.route("/<path:path>", methods=["GET"], provide_automatic_options=False)
|
||||
@self.web.app.route(
|
||||
"/", defaults={"path": ""}, methods=["GET"], provide_automatic_options=False
|
||||
)
|
||||
@self.web.app.route(
|
||||
"/<path:path>", methods=["GET"], provide_automatic_options=False
|
||||
)
|
||||
def index(path):
|
||||
"""
|
||||
Render the template for the onionshare landing page.
|
||||
|
@ -159,7 +163,9 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
|
||||
return self.render_logic(path)
|
||||
|
||||
@self.web.app.route("/download", methods=["GET"], provide_automatic_options=False)
|
||||
@self.web.app.route(
|
||||
"/download", methods=["GET"], provide_automatic_options=False
|
||||
)
|
||||
def download():
|
||||
"""
|
||||
Download the zip file.
|
||||
|
@ -183,7 +189,7 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
# and serve that
|
||||
use_gzip = self.should_use_gzip()
|
||||
if use_gzip:
|
||||
file_to_download = self.gzip_filename
|
||||
file_to_download = self.gzip_file.name
|
||||
self.filesize = self.gzip_filesize
|
||||
etag = self.gzip_etag
|
||||
else:
|
||||
|
@ -286,7 +292,9 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
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_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:
|
||||
|
@ -459,7 +467,7 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
return self.web.error404(history_id)
|
||||
|
||||
def build_zipfile_list(self, filenames, processed_size_callback=None):
|
||||
self.common.log("ShareModeWeb", "build_zipfile_list")
|
||||
self.common.log("ShareModeWeb", "build_zipfile_list", f"filenames={filenames}")
|
||||
for filename in filenames:
|
||||
info = {
|
||||
"filename": filename,
|
||||
|
@ -484,23 +492,25 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
self.download_etag = make_etag(f)
|
||||
|
||||
# Compress the file with gzip now, so we don't have to do it on each request
|
||||
self.gzip_filename = tempfile.mkstemp("wb+")[1]
|
||||
self._gzip_compress(
|
||||
self.download_filename, self.gzip_filename, 6, processed_size_callback
|
||||
self.gzip_file = tempfile.NamedTemporaryFile(
|
||||
"wb+", dir=self.common.build_tmp_dir()
|
||||
)
|
||||
self.gzip_filesize = os.path.getsize(self.gzip_filename)
|
||||
with open(self.gzip_filename, "rb") as f:
|
||||
self._gzip_compress(
|
||||
self.download_filename, self.gzip_file.name, 6, processed_size_callback
|
||||
)
|
||||
self.gzip_filesize = os.path.getsize(self.gzip_file.name)
|
||||
with open(self.gzip_file.name, "rb") as f:
|
||||
self.gzip_etag = make_etag(f)
|
||||
|
||||
# Make sure the gzip file gets cleaned up when onionshare stops
|
||||
self.web.cleanup_filenames.append(self.gzip_filename)
|
||||
|
||||
self.is_zipped = False
|
||||
|
||||
# Cleanup this tempfile
|
||||
self.web.cleanup_tempfiles.append(self.gzip_file)
|
||||
|
||||
else:
|
||||
# Zip up the files and folders
|
||||
self.zip_writer = ZipWriter(
|
||||
self.common, processed_size_callback=processed_size_callback
|
||||
self.common, self.web, processed_size_callback=processed_size_callback
|
||||
)
|
||||
self.download_filename = self.zip_writer.zip_filename
|
||||
for info in self.file_info["files"]:
|
||||
|
@ -519,10 +529,6 @@ class ShareModeWeb(SendBaseModeWeb):
|
|||
with open(self.download_filename, "rb") as f:
|
||||
self.download_etag = make_etag(f)
|
||||
|
||||
# Make sure the zip file gets cleaned up when onionshare stops
|
||||
self.web.cleanup_filenames.append(self.zip_writer.zip_filename)
|
||||
self.web.cleanup_filenames.append(self.zip_writer.zip_temp_dir)
|
||||
|
||||
self.is_zipped = True
|
||||
|
||||
return True
|
||||
|
@ -535,17 +541,24 @@ class ZipWriter(object):
|
|||
filename.
|
||||
"""
|
||||
|
||||
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
||||
def __init__(
|
||||
self, common, web=None, zip_filename=None, processed_size_callback=None
|
||||
):
|
||||
self.common = common
|
||||
self.web = web
|
||||
self.cancel_compression = False
|
||||
|
||||
if zip_filename:
|
||||
self.zip_filename = zip_filename
|
||||
else:
|
||||
self.zip_temp_dir = tempfile.mkdtemp()
|
||||
self.zip_filename = (
|
||||
f"{self.zip_temp_dir}/onionshare_{self.common.random_string(4, 6)}.zip"
|
||||
self.zip_temp_dir = tempfile.TemporaryDirectory(
|
||||
dir=self.common.build_tmp_dir()
|
||||
)
|
||||
self.zip_filename = f"{self.zip_temp_dir.name}/onionshare_{self.common.random_string(4, 6)}.zip"
|
||||
|
||||
# Cleanup this temp dir
|
||||
if self.web:
|
||||
self.web.cleanup_tempdirs.append(self.zip_temp_dir)
|
||||
|
||||
self.z = zipfile.ZipFile(self.zip_filename, "w", allowZip64=True)
|
||||
self.processed_size_callback = processed_size_callback
|
||||
|
|
|
@ -155,7 +155,8 @@ class Web:
|
|||
self.socketio.init_app(self.app)
|
||||
self.chat_mode = ChatModeWeb(self.common, self)
|
||||
|
||||
self.cleanup_filenames = []
|
||||
self.cleanup_tempfiles = []
|
||||
self.cleanup_tempdirs = []
|
||||
|
||||
def get_mode(self):
|
||||
if self.mode == "share":
|
||||
|
@ -198,12 +199,20 @@ class Web:
|
|||
"""
|
||||
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:;",
|
||||
)
|
||||
default_csp = "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; img-src 'self' data:;"
|
||||
if self.mode != "website" or (
|
||||
not self.settings.get("website", "disable_csp")
|
||||
and not self.settings.get("website", "custom_csp")
|
||||
):
|
||||
r.headers.set("Content-Security-Policy", default_csp)
|
||||
else:
|
||||
if self.settings.get("website", "custom_csp"):
|
||||
r.headers.set(
|
||||
"Content-Security-Policy",
|
||||
self.settings.get("website", "custom_csp"),
|
||||
)
|
||||
return r
|
||||
|
||||
@self.app.errorhandler(404)
|
||||
|
@ -380,14 +389,13 @@ class Web:
|
|||
"""
|
||||
self.common.log("Web", "cleanup")
|
||||
|
||||
# Cleanup files
|
||||
try:
|
||||
for filename in self.cleanup_filenames:
|
||||
if os.path.isfile(filename):
|
||||
os.remove(filename)
|
||||
elif os.path.isdir(filename):
|
||||
shutil.rmtree(filename)
|
||||
except Exception:
|
||||
# Don't crash if file is still in use
|
||||
pass
|
||||
self.cleanup_filenames = []
|
||||
# Close all of the tempfile.NamedTemporaryFile
|
||||
for file in self.cleanup_tempfiles:
|
||||
file.close()
|
||||
|
||||
# Clean up the tempfile.NamedTemporaryDirectory objects
|
||||
for dir in self.cleanup_tempdirs:
|
||||
dir.cleanup()
|
||||
|
||||
self.cleanup_tempfiles = []
|
||||
self.cleanup_tempdirs = []
|
||||
|
|
|
@ -37,7 +37,7 @@ def temp_dir():
|
|||
"""Creates a persistent temporary directory for the CLI tests to use"""
|
||||
global test_temp_dir
|
||||
if not test_temp_dir:
|
||||
test_temp_dir = tempfile.mkdtemp()
|
||||
test_temp_dir = tempfile.TemporaryDirectory()
|
||||
return test_temp_dir
|
||||
|
||||
|
||||
|
@ -47,10 +47,9 @@ def temp_dir_1024(temp_dir):
|
|||
particular size (1024 bytes).
|
||||
"""
|
||||
|
||||
new_temp_dir = tempfile.mkdtemp(dir=temp_dir)
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=new_temp_dir)
|
||||
with open(tmp_file, "wb") as f:
|
||||
f.write(b"*" * 1024)
|
||||
new_temp_dir = tempfile.TemporaryDirectory(dir=temp_dir.name)
|
||||
tmp_file = tempfile.NamedTemporaryFile(dir=new_temp_dir.name)
|
||||
tmp_file.write(b"*" * 1024)
|
||||
return new_temp_dir
|
||||
|
||||
|
||||
|
@ -61,9 +60,8 @@ def temp_dir_1024_delete(temp_dir):
|
|||
the file inside) will be deleted after fixture usage.
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=temp_dir) as new_temp_dir:
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=new_temp_dir)
|
||||
with open(tmp_file, "wb") as f:
|
||||
with tempfile.TemporaryDirectory(dir=temp_dir.name) as new_temp_dir:
|
||||
with open(os.path.join(new_temp_dir, "file"), "wb") as f:
|
||||
f.write(b"*" * 1024)
|
||||
yield new_temp_dir
|
||||
|
||||
|
@ -72,9 +70,10 @@ def temp_dir_1024_delete(temp_dir):
|
|||
def temp_file_1024(temp_dir):
|
||||
"""Create a temporary file of a particular size (1024 bytes)."""
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, dir=temp_dir) as tmp_file:
|
||||
tmp_file.write(b"*" * 1024)
|
||||
return tmp_file.name
|
||||
filename = os.path.join(temp_dir.name, "file")
|
||||
with open(filename, "wb") as f:
|
||||
f.write(b"*" * 1024)
|
||||
return filename
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -84,11 +83,11 @@ def temp_file_1024_delete(temp_dir):
|
|||
The temporary file will be deleted after fixture usage.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(dir=temp_dir, delete=False) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as tmp_file:
|
||||
tmp_file.write(b"*" * 1024)
|
||||
tmp_file.flush()
|
||||
tmp_file.close()
|
||||
yield tmp_file.name
|
||||
yield tmp_file
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
|
|
@ -34,8 +34,10 @@ class TestSettings:
|
|||
"bridges_builtin_pt": "obfs4",
|
||||
"bridges_moat": "",
|
||||
"bridges_custom": "",
|
||||
"bridges_builtin": {},
|
||||
"persistent_tabs": [],
|
||||
"theme": 0,
|
||||
"censorship_circumvention": False,
|
||||
}
|
||||
for key in settings_obj._settings:
|
||||
# Skip locale, it will not always default to the same thing
|
||||
|
@ -54,7 +56,7 @@ class TestSettings:
|
|||
"socks_port": 9999,
|
||||
"use_stealth": True,
|
||||
}
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=temp_dir)
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=temp_dir.name)
|
||||
with open(tmp_file, "w") as f:
|
||||
json.dump(custom_settings, f)
|
||||
settings_obj.filename = tmp_file_path
|
||||
|
@ -69,7 +71,7 @@ class TestSettings:
|
|||
|
||||
def test_save(self, monkeypatch, temp_dir, settings_obj):
|
||||
settings_filename = "default_settings.json"
|
||||
new_temp_dir = tempfile.mkdtemp(dir=temp_dir)
|
||||
new_temp_dir = tempfile.mkdtemp(dir=temp_dir.name)
|
||||
settings_path = os.path.join(new_temp_dir, settings_filename)
|
||||
settings_obj.filename = settings_path
|
||||
settings_obj.save()
|
||||
|
|
|
@ -50,7 +50,8 @@ def web_obj(temp_dir, common_obj, mode, num_files=0):
|
|||
web = Web(common_obj, False, mode_settings, mode)
|
||||
web.running = True
|
||||
|
||||
web.cleanup_filenames == []
|
||||
web.cleanup_tempfiles == []
|
||||
web.cleanup_tempdirs == []
|
||||
web.app.testing = True
|
||||
|
||||
# Share mode
|
||||
|
@ -58,7 +59,9 @@ def web_obj(temp_dir, common_obj, mode, num_files=0):
|
|||
# Add files
|
||||
files = []
|
||||
for _ in range(num_files):
|
||||
with tempfile.NamedTemporaryFile(delete=False, dir=temp_dir) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, dir=temp_dir.name
|
||||
) as tmp_file:
|
||||
tmp_file.write(b"*" * 1024)
|
||||
files.append(tmp_file.name)
|
||||
web.share_mode.set_file_info(files)
|
||||
|
@ -131,7 +134,9 @@ class TestWeb:
|
|||
|
||||
with web.app.test_client() as c:
|
||||
# Load / with valid auth
|
||||
res = c.get("/",)
|
||||
res = c.get(
|
||||
"/",
|
||||
)
|
||||
res.get_data()
|
||||
assert res.status_code == 200
|
||||
|
||||
|
@ -169,7 +174,7 @@ class TestWeb:
|
|||
def test_receive_mode_message_no_files(self, temp_dir, common_obj):
|
||||
web = web_obj(temp_dir, common_obj, "receive")
|
||||
|
||||
data_dir = os.path.join(temp_dir, "OnionShare")
|
||||
data_dir = os.path.join(temp_dir.name, "OnionShare")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
web.settings.set("receive", "data_dir", data_dir)
|
||||
|
@ -200,7 +205,7 @@ class TestWeb:
|
|||
def test_receive_mode_message_and_files(self, temp_dir, common_obj):
|
||||
web = web_obj(temp_dir, common_obj, "receive")
|
||||
|
||||
data_dir = os.path.join(temp_dir, "OnionShare")
|
||||
data_dir = os.path.join(temp_dir.name, "OnionShare")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
web.settings.set("receive", "data_dir", data_dir)
|
||||
|
@ -235,7 +240,7 @@ class TestWeb:
|
|||
def test_receive_mode_files_no_message(self, temp_dir, common_obj):
|
||||
web = web_obj(temp_dir, common_obj, "receive")
|
||||
|
||||
data_dir = os.path.join(temp_dir, "OnionShare")
|
||||
data_dir = os.path.join(temp_dir.name, "OnionShare")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
web.settings.set("receive", "data_dir", data_dir)
|
||||
|
@ -267,7 +272,7 @@ class TestWeb:
|
|||
def test_receive_mode_no_message_no_files(self, temp_dir, common_obj):
|
||||
web = web_obj(temp_dir, common_obj, "receive")
|
||||
|
||||
data_dir = os.path.join(temp_dir, "OnionShare")
|
||||
data_dir = os.path.join(temp_dir.name, "OnionShare")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
web.settings.set("receive", "data_dir", data_dir)
|
||||
|
@ -300,15 +305,21 @@ class TestWeb:
|
|||
res.get_data()
|
||||
assert res.status_code == 200
|
||||
|
||||
def test_cleanup(self, common_obj, temp_dir_1024, temp_file_1024):
|
||||
def test_cleanup(self, common_obj, temp_dir_1024):
|
||||
web = web_obj(temp_dir_1024, common_obj, "share", 3)
|
||||
|
||||
web.cleanup_filenames = [temp_dir_1024, temp_file_1024]
|
||||
temp_file = tempfile.NamedTemporaryFile()
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
|
||||
web.cleanup_tempfiles = [temp_file]
|
||||
web.cleanup_tempdirs = [temp_dir]
|
||||
web.cleanup()
|
||||
|
||||
assert os.path.exists(temp_file_1024) is False
|
||||
assert os.path.exists(temp_dir_1024) is False
|
||||
assert web.cleanup_filenames == []
|
||||
assert os.path.exists(temp_file.name) is False
|
||||
assert os.path.exists(temp_dir.name) is False
|
||||
|
||||
assert web.cleanup_tempfiles == []
|
||||
assert web.cleanup_tempdirs == []
|
||||
|
||||
|
||||
class TestZipWriterDefault:
|
||||
|
@ -339,8 +350,10 @@ class TestZipWriterDefault:
|
|||
assert default_zw.processed_size_callback(None) is None
|
||||
|
||||
def test_add_file(self, default_zw, temp_file_1024_delete):
|
||||
default_zw.add_file(temp_file_1024_delete)
|
||||
zipfile_info = default_zw.z.getinfo(os.path.basename(temp_file_1024_delete))
|
||||
default_zw.add_file(temp_file_1024_delete.name)
|
||||
zipfile_info = default_zw.z.getinfo(
|
||||
os.path.basename(temp_file_1024_delete.name)
|
||||
)
|
||||
|
||||
assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
|
||||
assert zipfile_info.file_size == 1024
|
||||
|
@ -568,7 +581,6 @@ class TestRangeRequests:
|
|||
resp = client.get(url, headers=headers)
|
||||
assert resp.status_code == 206
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
|
||||
@check_unsupported("curl", ["--version"])
|
||||
def test_curl(self, temp_dir, tmpdir, common_obj):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue