onionshare_cli 2.6.5__tar.gz → 2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/PKG-INFO +21 -21
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/__init__.py +37 -1
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/censorship.py +1 -1
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/common.py +110 -12
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/meek.py +7 -1
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/mode_settings.py +13 -2
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/onion.py +77 -7
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/onionshare.py +70 -1
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/js/receive.js +7 -1
- onionshare_cli-2.7/onionshare_cli/resources/templates/413.html +21 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/receive.html +10 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/torrc_template +1 -0
- onionshare_cli-2.7/onionshare_cli/resources/torrc_template-meek +2 -0
- onionshare_cli-2.7/onionshare_cli/resources/torrc_template-obfs4 +8 -0
- onionshare_cli-2.7/onionshare_cli/resources/torrc_template-snowflake +3 -0
- onionshare_cli-2.7/onionshare_cli/resources/torrc_template-webtunnel +6 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/settings.py +31 -31
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/receive_mode.py +373 -99
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/share_mode.py +3 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/web.py +59 -9
- onionshare_cli-2.7/pyproject.toml +80 -0
- onionshare_cli-2.6.5/onionshare_cli/resources/torrc_template-meek_lite_azure +0 -2
- onionshare_cli-2.6.5/onionshare_cli/resources/torrc_template-obfs4 +0 -12
- onionshare_cli-2.6.5/onionshare_cli/resources/torrc_template-snowflake +0 -3
- onionshare_cli-2.6.5/onionshare_cli/resources/version.txt +0 -1
- onionshare_cli-2.6.5/pyproject.toml +0 -53
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/css/style.css +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/ajax.gif +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/favicon.ico +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/logo.png +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/logo_large.png +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/web_file.png +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/img/web_folder.png +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/js/chat.js +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/js/jquery-3.7.1.slim.min.js +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/js/send.js +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/static/js/socket.io.min.js +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/403.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/404.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/405.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/500.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/chat.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/denied.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/listing.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/send.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/templates/thankyou.html +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/resources/wordlist.txt +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/__init__.py +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/chat_mode.py +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/send_base_mode.py +0 -0
- {onionshare_cli-2.6.5 → onionshare_cli-2.7}/onionshare_cli/web/website_mode.py +0 -0
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: onionshare_cli
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary:
|
|
5
|
-
License:
|
|
3
|
+
Version: 2.7
|
|
4
|
+
Summary: Securely and anonymously share files, host websites, and chat with friends using the Tor network
|
|
5
|
+
License: GPL-3.0-or-later
|
|
6
|
+
Keywords: onion,share,onionshare,tor,anonymous,web server
|
|
6
7
|
Author: Micah Lee
|
|
7
8
|
Author-email: micah@micahflee.com
|
|
9
|
+
Maintainer: Micah Lee
|
|
10
|
+
Maintainer-email: micah@micahflee.com
|
|
8
11
|
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: Environment :: Web Environment
|
|
10
|
-
Classifier: Framework :: Flask
|
|
11
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
13
|
-
Classifier: License :: Other/Proprietary License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
12
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier:
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Framework :: Flask
|
|
21
14
|
Classifier: Topic :: Communications :: File Sharing
|
|
22
15
|
Classifier: Topic :: Security :: Cryptography
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
17
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Environment :: Web Environment
|
|
23
20
|
Requires-Dist: cffi (>=2,<3)
|
|
24
21
|
Requires-Dist: click
|
|
25
22
|
Requires-Dist: colorama
|
|
26
|
-
Requires-Dist: cython (>=3.0.2,<4.0
|
|
23
|
+
Requires-Dist: cython (>=3.0.2,<4.0)
|
|
27
24
|
Requires-Dist: eventlet
|
|
28
25
|
Requires-Dist: flask (==3.1.3)
|
|
29
26
|
Requires-Dist: flask-compress (>=1.24,<2.0)
|
|
@@ -31,16 +28,19 @@ Requires-Dist: flask-socketio (>=5.6,<6.0)
|
|
|
31
28
|
Requires-Dist: gevent (>=26.5.0,<27.0.0)
|
|
32
29
|
Requires-Dist: gevent-websocket
|
|
33
30
|
Requires-Dist: packaging (>=24)
|
|
34
|
-
Requires-Dist: pkgconfig (>=1.6.0,<2.0
|
|
31
|
+
Requires-Dist: pkgconfig (>=1.6.0,<2.0)
|
|
35
32
|
Requires-Dist: psutil
|
|
36
|
-
Requires-Dist: pynacl (>=1.6.2,<2.0
|
|
33
|
+
Requires-Dist: pynacl (>=1.6.2,<2.0)
|
|
37
34
|
Requires-Dist: pysocks
|
|
38
35
|
Requires-Dist: qrcode (>=7.4.2,<8.0.0)
|
|
39
36
|
Requires-Dist: requests[socks] (>=2.32.3)
|
|
40
|
-
Requires-Dist: setuptools (>=
|
|
37
|
+
Requires-Dist: setuptools (>=83)
|
|
41
38
|
Requires-Dist: stem (==1.8.1)
|
|
42
39
|
Requires-Dist: unidecode
|
|
43
40
|
Requires-Dist: urllib3 (>=2.7,<3.0)
|
|
44
41
|
Requires-Dist: waitress (>=3.0.1,<4.0.0)
|
|
45
|
-
Requires-Dist: werkzeug (==3.1.
|
|
46
|
-
Requires-Dist: wheel (>=0.46.2,<
|
|
42
|
+
Requires-Dist: werkzeug (==3.1.9)
|
|
43
|
+
Requires-Dist: wheel (>=0.46.2,<1.0)
|
|
44
|
+
Project-URL: Documentation, https://docs.onionshare.org/
|
|
45
|
+
Project-URL: Homepage, https://onionshare.org
|
|
46
|
+
Project-URL: Repository, https://github.com/onionshare/onionshare
|
|
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import os
|
|
22
|
+
import signal
|
|
22
23
|
import sys
|
|
23
24
|
import time
|
|
24
25
|
import argparse
|
|
@@ -42,6 +43,12 @@ def main(cwd=None):
|
|
|
42
43
|
common = Common()
|
|
43
44
|
common.display_banner()
|
|
44
45
|
|
|
46
|
+
# Allow SIGTERM (e.g. `kill`, systemd stop) to clean up like Ctrl-C does
|
|
47
|
+
def sigterm_handler(signum, frame):
|
|
48
|
+
raise KeyboardInterrupt
|
|
49
|
+
|
|
50
|
+
signal.signal(signal.SIGTERM, sigterm_handler)
|
|
51
|
+
|
|
45
52
|
# OnionShare CLI in OSX needs to change current working directory (#132)
|
|
46
53
|
if common.platform == "Darwin":
|
|
47
54
|
if cwd:
|
|
@@ -159,6 +166,18 @@ def main(cwd=None):
|
|
|
159
166
|
dest="disable_files",
|
|
160
167
|
help="Receive files: Disable receiving files",
|
|
161
168
|
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"--max-upload-size",
|
|
171
|
+
metavar="size",
|
|
172
|
+
default="0",
|
|
173
|
+
help="Receive files: Maximum size of an upload, e.g. 100mb (0 means no limit)",
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"--max-upload-size-total",
|
|
177
|
+
action="store_true",
|
|
178
|
+
dest="max_upload_size_total",
|
|
179
|
+
help="Receive files: Apply the maximum upload size to the entire service lifespan",
|
|
180
|
+
)
|
|
162
181
|
# Website args
|
|
163
182
|
parser.add_argument(
|
|
164
183
|
"--disable_csp",
|
|
@@ -210,11 +229,24 @@ def main(cwd=None):
|
|
|
210
229
|
webhook_url = args.webhook_url
|
|
211
230
|
disable_text = args.disable_text
|
|
212
231
|
disable_files = args.disable_files
|
|
232
|
+
max_upload_size_raw = args.max_upload_size
|
|
233
|
+
max_upload_size_total = args.max_upload_size_total
|
|
213
234
|
disable_csp = bool(args.disable_csp)
|
|
214
235
|
custom_csp = args.custom_csp
|
|
215
236
|
log_filenames = bool(args.log_filenames)
|
|
216
237
|
verbose = bool(args.verbose)
|
|
217
238
|
|
|
239
|
+
# Parse the maximum upload size, which is given in human friendly units
|
|
240
|
+
try:
|
|
241
|
+
max_upload_size = common.parse_human_readable_filesize(max_upload_size_raw)
|
|
242
|
+
except ValueError:
|
|
243
|
+
print(f"Invalid maximum upload size: {max_upload_size_raw}")
|
|
244
|
+
sys.exit()
|
|
245
|
+
|
|
246
|
+
if max_upload_size_total and max_upload_size <= 0:
|
|
247
|
+
print("You must set --max-upload-size to use --max-upload-size-total")
|
|
248
|
+
sys.exit()
|
|
249
|
+
|
|
218
250
|
# Verbose mode?
|
|
219
251
|
common.verbose = verbose
|
|
220
252
|
|
|
@@ -259,6 +291,8 @@ def main(cwd=None):
|
|
|
259
291
|
mode_settings.set("receive", "webhook_url", webhook_url)
|
|
260
292
|
mode_settings.set("receive", "disable_text", disable_text)
|
|
261
293
|
mode_settings.set("receive", "disable_files", disable_files)
|
|
294
|
+
mode_settings.set("receive", "max_upload_size", max_upload_size)
|
|
295
|
+
mode_settings.set("receive", "max_upload_size_total", max_upload_size_total)
|
|
262
296
|
if mode == "website":
|
|
263
297
|
if disable_csp and custom_csp:
|
|
264
298
|
print(
|
|
@@ -451,7 +485,9 @@ def main(cwd=None):
|
|
|
451
485
|
print("")
|
|
452
486
|
|
|
453
487
|
# Start OnionShare http service in new thread
|
|
454
|
-
t = threading.Thread(
|
|
488
|
+
t = threading.Thread(
|
|
489
|
+
target=web.start, args=(app.port, app.unix_socket, app.unix_socket_dir)
|
|
490
|
+
)
|
|
455
491
|
t.daemon = True
|
|
456
492
|
t.start()
|
|
457
493
|
|
|
@@ -17,8 +17,10 @@ GNU General Public License for more details.
|
|
|
17
17
|
You should have received a copy of the GNU General Public License
|
|
18
18
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
"""
|
|
20
|
+
|
|
20
21
|
import base64
|
|
21
22
|
import hashlib
|
|
23
|
+
import importlib.metadata as importlib_metadata
|
|
22
24
|
import importlib.resources as importlib_resources
|
|
23
25
|
import os
|
|
24
26
|
import platform
|
|
@@ -30,6 +32,7 @@ import threading
|
|
|
30
32
|
import time
|
|
31
33
|
import shutil
|
|
32
34
|
import re
|
|
35
|
+
import tempfile
|
|
33
36
|
|
|
34
37
|
import colorama
|
|
35
38
|
from colorama import Fore, Back, Style
|
|
@@ -59,8 +62,7 @@ class Common:
|
|
|
59
62
|
self.platform = "BSD"
|
|
60
63
|
|
|
61
64
|
# The current version of OnionShare
|
|
62
|
-
|
|
63
|
-
self.version = f.read().strip()
|
|
65
|
+
self.version = importlib_metadata.version(__package__)
|
|
64
66
|
|
|
65
67
|
def display_banner(self):
|
|
66
68
|
"""
|
|
@@ -326,6 +328,7 @@ class Common:
|
|
|
326
328
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
|
327
329
|
snowflake_file_path = shutil.which("snowflake-client")
|
|
328
330
|
meek_client_file_path = shutil.which("meek-client")
|
|
331
|
+
webtunnel_file_path = shutil.which("webtunnel-client")
|
|
329
332
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
|
330
333
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
|
331
334
|
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
|
@@ -352,6 +355,7 @@ class Common:
|
|
|
352
355
|
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy.exe")
|
|
353
356
|
snowflake_file_path = os.path.join(base_path, "snowflake-client.exe")
|
|
354
357
|
meek_client_file_path = os.path.join(base_path, "meek-client.exe")
|
|
358
|
+
webtunnel_file_path = os.path.join(base_path, "webtunnel-client.exe")
|
|
355
359
|
tor_geo_ip_file_path = os.path.join(base_path, "geoip")
|
|
356
360
|
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
|
357
361
|
|
|
@@ -364,6 +368,7 @@ class Common:
|
|
|
364
368
|
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
|
|
365
369
|
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
|
366
370
|
meek_client_file_path = os.path.join(base_path, "meek-client")
|
|
371
|
+
webtunnel_file_path = os.path.join(base_path, "webtunnel-client")
|
|
367
372
|
tor_geo_ip_file_path = os.path.join(base_path, "geoip")
|
|
368
373
|
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
|
369
374
|
else:
|
|
@@ -375,6 +380,7 @@ class Common:
|
|
|
375
380
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
|
376
381
|
snowflake_file_path = shutil.which("snowflake-client")
|
|
377
382
|
meek_client_file_path = shutil.which("meek-client")
|
|
383
|
+
webtunnel_file_path = shutil.which("webtunnel-client")
|
|
378
384
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
|
379
385
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
|
380
386
|
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
|
@@ -386,6 +392,7 @@ class Common:
|
|
|
386
392
|
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
|
387
393
|
snowflake_file_path = "/usr/local/bin/snowflake-client"
|
|
388
394
|
meek_client_file_path = "/usr/local/bin/meek-client"
|
|
395
|
+
webtunnel_file_path = "/usr/local/bin/webtunnel-client"
|
|
389
396
|
|
|
390
397
|
return (
|
|
391
398
|
tor_path,
|
|
@@ -394,6 +401,7 @@ class Common:
|
|
|
394
401
|
obfs4proxy_file_path,
|
|
395
402
|
snowflake_file_path,
|
|
396
403
|
meek_client_file_path,
|
|
404
|
+
webtunnel_file_path,
|
|
397
405
|
)
|
|
398
406
|
|
|
399
407
|
def build_data_dir(self):
|
|
@@ -430,12 +438,46 @@ class Common:
|
|
|
430
438
|
os.makedirs(onionshare_data_dir, 0o700, True)
|
|
431
439
|
return onionshare_data_dir
|
|
432
440
|
|
|
441
|
+
def build_cache_dir(self):
|
|
442
|
+
"""
|
|
443
|
+
Returns the path of the OnionShare cache directory, which holds
|
|
444
|
+
non-essential data that can safely be deleted. On Linux and BSD this
|
|
445
|
+
follows the XDG Base Directory Specification (~/.cache/onionshare).
|
|
446
|
+
"""
|
|
447
|
+
xdg_cache_home = os.environ.get("XDG_CACHE_HOME", "")
|
|
448
|
+
if not os.path.isabs(xdg_cache_home):
|
|
449
|
+
xdg_cache_home = os.path.expanduser("~/.cache")
|
|
450
|
+
|
|
451
|
+
onionshare_cache_dir = os.path.join(xdg_cache_home, "onionshare")
|
|
452
|
+
|
|
453
|
+
# Modify the cache dir if running tests
|
|
454
|
+
if getattr(sys, "onionshare_test_mode", False):
|
|
455
|
+
onionshare_cache_dir += "-testdata"
|
|
456
|
+
|
|
457
|
+
os.makedirs(onionshare_cache_dir, 0o700, True)
|
|
458
|
+
return onionshare_cache_dir
|
|
459
|
+
|
|
433
460
|
def build_tmp_dir(self):
|
|
434
461
|
"""
|
|
435
462
|
Returns path to a folder that can hold temporary files
|
|
436
463
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
464
|
+
if self.platform == "Darwin":
|
|
465
|
+
# macOS Application Support contains a space, which makes Unix
|
|
466
|
+
# socket targets unsuitable for Tor's ADD_ONION Port= argument.
|
|
467
|
+
# Keep all temporary files in the per-user system temp area.
|
|
468
|
+
dirname = "onionshare"
|
|
469
|
+
if getattr(sys, "onionshare_test_mode", False):
|
|
470
|
+
dirname += "-testdata"
|
|
471
|
+
tmp_dir = os.path.join(tempfile.gettempdir(), dirname)
|
|
472
|
+
else:
|
|
473
|
+
base_dir = (
|
|
474
|
+
self.build_data_dir()
|
|
475
|
+
if self.platform == "Windows"
|
|
476
|
+
else self.build_cache_dir()
|
|
477
|
+
)
|
|
478
|
+
tmp_dir = os.path.join(base_dir, "tmp")
|
|
479
|
+
|
|
480
|
+
os.makedirs(tmp_dir, mode=0o700, exist_ok=True)
|
|
439
481
|
return tmp_dir
|
|
440
482
|
|
|
441
483
|
def build_persistent_dir(self):
|
|
@@ -448,9 +490,16 @@ class Common:
|
|
|
448
490
|
|
|
449
491
|
def build_tor_dir(self):
|
|
450
492
|
"""
|
|
451
|
-
Returns path to the tor data directory
|
|
493
|
+
Returns path to the tor data directory. The tor data directory holds
|
|
494
|
+
non-essential data that can safely be deleted, so on Linux and BSD
|
|
495
|
+
it lives under the XDG cache directory (~/.cache/onionshare).
|
|
452
496
|
"""
|
|
453
|
-
|
|
497
|
+
if self.platform in ("Windows", "Darwin"):
|
|
498
|
+
base_dir = self.build_data_dir()
|
|
499
|
+
else:
|
|
500
|
+
base_dir = self.build_cache_dir()
|
|
501
|
+
|
|
502
|
+
tor_dir = os.path.join(base_dir, "tor_data")
|
|
454
503
|
os.makedirs(tor_dir, 0o700, True)
|
|
455
504
|
return tor_dir
|
|
456
505
|
|
|
@@ -496,11 +545,16 @@ class Common:
|
|
|
496
545
|
snowflake_pattern = re.compile(
|
|
497
546
|
r"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
|
498
547
|
)
|
|
548
|
+
# WebTunnel bridge lines: "webtunnel <IP>:<port> <fingerprint> url=https://..."
|
|
549
|
+
webtunnel_pattern = re.compile(
|
|
550
|
+
r"(webtunnel)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Fa-f]+)(\s)+url=(.+)"
|
|
551
|
+
)
|
|
499
552
|
if (
|
|
500
553
|
ipv4_pattern.match(bridge)
|
|
501
554
|
or ipv6_pattern.match(bridge)
|
|
502
555
|
or meek_lite_pattern.match(bridge)
|
|
503
556
|
or snowflake_pattern.match(bridge)
|
|
557
|
+
or webtunnel_pattern.match(bridge)
|
|
504
558
|
):
|
|
505
559
|
valid_bridges.append(bridge)
|
|
506
560
|
if valid_bridges:
|
|
@@ -533,20 +587,64 @@ class Common:
|
|
|
533
587
|
return s[:output_len]
|
|
534
588
|
|
|
535
589
|
@staticmethod
|
|
536
|
-
def
|
|
590
|
+
def split_filesize(b):
|
|
537
591
|
"""
|
|
538
|
-
|
|
592
|
+
Splits a filesize into a human readable value and the index of its
|
|
593
|
+
unit, where 0 is bytes, 1 is KiB, 2 is MiB, and so on.
|
|
539
594
|
"""
|
|
540
595
|
thresh = 1024.0
|
|
541
596
|
if b < thresh:
|
|
542
|
-
return
|
|
543
|
-
|
|
544
|
-
u = 0
|
|
597
|
+
return b, 0
|
|
598
|
+
u = 1
|
|
545
599
|
b /= thresh
|
|
546
600
|
while b >= thresh:
|
|
547
601
|
b /= thresh
|
|
548
602
|
u += 1
|
|
549
|
-
return
|
|
603
|
+
return b, u
|
|
604
|
+
|
|
605
|
+
@staticmethod
|
|
606
|
+
def human_readable_filesize(b):
|
|
607
|
+
"""
|
|
608
|
+
Returns filesize in a human readable format.
|
|
609
|
+
"""
|
|
610
|
+
units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
|
611
|
+
value, u = Common.split_filesize(b)
|
|
612
|
+
return "{:.1f} {}".format(value, units[u])
|
|
613
|
+
|
|
614
|
+
@staticmethod
|
|
615
|
+
def parse_human_readable_filesize(size_str):
|
|
616
|
+
"""
|
|
617
|
+
Parse a human readable filesize such as "100mb", "1 GiB" or "512" into
|
|
618
|
+
an integer number of bytes. Uses base 1024, matching the KiB/MiB/GiB
|
|
619
|
+
units produced by human_readable_filesize(). A bare number is treated
|
|
620
|
+
as bytes. Raises ValueError if the string cannot be parsed.
|
|
621
|
+
"""
|
|
622
|
+
match = re.match(r"^\s*([0-9]+(?:\.[0-9]+)?)\s*([a-zA-Z]*)\s*$", str(size_str))
|
|
623
|
+
if not match:
|
|
624
|
+
raise ValueError(f"Invalid filesize: {size_str}")
|
|
625
|
+
|
|
626
|
+
value = float(match.group(1))
|
|
627
|
+
unit = match.group(2).lower()
|
|
628
|
+
multipliers = {
|
|
629
|
+
"": 1,
|
|
630
|
+
"b": 1,
|
|
631
|
+
"k": 1024,
|
|
632
|
+
"kb": 1024,
|
|
633
|
+
"kib": 1024,
|
|
634
|
+
"m": 1024**2,
|
|
635
|
+
"mb": 1024**2,
|
|
636
|
+
"mib": 1024**2,
|
|
637
|
+
"g": 1024**3,
|
|
638
|
+
"gb": 1024**3,
|
|
639
|
+
"gib": 1024**3,
|
|
640
|
+
"t": 1024**4,
|
|
641
|
+
"tb": 1024**4,
|
|
642
|
+
"tib": 1024**4,
|
|
643
|
+
}
|
|
644
|
+
if unit not in multipliers:
|
|
645
|
+
raise ValueError(f"Unknown filesize unit: {unit}")
|
|
646
|
+
|
|
647
|
+
return int(value * multipliers[unit])
|
|
550
648
|
|
|
551
649
|
@staticmethod
|
|
552
650
|
def format_seconds(seconds):
|
|
@@ -48,9 +48,15 @@ class Meek(object):
|
|
|
48
48
|
self.obfs4proxy_file_path,
|
|
49
49
|
self.snowflake_file_path,
|
|
50
50
|
self.meek_client_file_path,
|
|
51
|
+
self.webtunnel_file_path,
|
|
51
52
|
) = get_tor_paths()
|
|
52
53
|
|
|
53
54
|
self.meek_proxies = {}
|
|
55
|
+
# The moat reflector, reached via domain fronting, that relays
|
|
56
|
+
# HTTPS requests to bridges.torproject.org. This is distinct from
|
|
57
|
+
# the CDN77 meek used as a built-in bridge, which only relays
|
|
58
|
+
# traffic to a Tor bridge and cannot serve the Moat API.
|
|
59
|
+
# https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/f6c72cec392f215a77a2aaed626bfcc53e030327
|
|
54
60
|
self.meek_url = "https://1723079976.rsc.cdn77.org/"
|
|
55
61
|
self.meek_front = "www.phpmyadmin.net"
|
|
56
62
|
self.meek_env = {
|
|
@@ -118,7 +124,7 @@ class Meek(object):
|
|
|
118
124
|
for line in iter(self.meek_proc.stdout.readline, b""):
|
|
119
125
|
if "CMETHOD meek socks5" in line:
|
|
120
126
|
self.meek_host = line.split(" ")[3].split(":")[0]
|
|
121
|
-
self.meek_port = line.split(" ")[3].split(":")[1]
|
|
127
|
+
self.meek_port = line.split(" ")[3].split(":")[1].strip()
|
|
122
128
|
self.common.log(
|
|
123
129
|
"Meek",
|
|
124
130
|
"start",
|
|
@@ -43,7 +43,7 @@ class ModeSettings:
|
|
|
43
43
|
"persistent": {
|
|
44
44
|
"mode": None,
|
|
45
45
|
"enabled": False,
|
|
46
|
-
"autostart_on_launch": False
|
|
46
|
+
"autostart_on_launch": False,
|
|
47
47
|
},
|
|
48
48
|
"general": {
|
|
49
49
|
"title": None,
|
|
@@ -63,13 +63,24 @@ class ModeSettings:
|
|
|
63
63
|
"webhook_url": None,
|
|
64
64
|
"disable_text": False,
|
|
65
65
|
"disable_files": False,
|
|
66
|
+
# Maximum size of a single upload request, in bytes. 0 means no
|
|
67
|
+
# limit.
|
|
68
|
+
"max_upload_size": 0,
|
|
69
|
+
# If True, apply max_upload_size to the service's entire
|
|
70
|
+
# lifespan: once total_upload_size reaches max_upload_size, no
|
|
71
|
+
# further uploads are accepted.
|
|
72
|
+
"max_upload_size_total": False,
|
|
73
|
+
# Running total of bytes received by the service, persisted so
|
|
74
|
+
# the lifespan limit survives restarts of a persistent service.
|
|
75
|
+
"total_upload_size": 0,
|
|
66
76
|
},
|
|
67
77
|
"website": {
|
|
68
78
|
"disable_csp": False,
|
|
69
79
|
"custom_csp": None,
|
|
70
80
|
"log_filenames": False,
|
|
71
|
-
"filenames": []
|
|
81
|
+
"filenames": [],
|
|
72
82
|
},
|
|
83
|
+
"download": {"data_dir": self.build_default_receive_data_dir(), "poll": 0},
|
|
73
84
|
"chat": {},
|
|
74
85
|
}
|
|
75
86
|
self._settings = {}
|
|
@@ -159,6 +159,7 @@ class Onion(object):
|
|
|
159
159
|
self.obfs4proxy_file_path,
|
|
160
160
|
self.snowflake_file_path,
|
|
161
161
|
self.meek_client_file_path,
|
|
162
|
+
self.webtunnel_file_path,
|
|
162
163
|
) = get_tor_paths()
|
|
163
164
|
|
|
164
165
|
# The tor process
|
|
@@ -190,6 +191,23 @@ class Onion(object):
|
|
|
190
191
|
s = key_b32.decode("utf-8")
|
|
191
192
|
return s
|
|
192
193
|
|
|
194
|
+
def private_key_to_b64(self, encoded_key):
|
|
195
|
+
"""
|
|
196
|
+
Converts a base32 encoded string back to a base64 encoded string.
|
|
197
|
+
|
|
198
|
+
This is used for adding a Private Key to an onion when operating in
|
|
199
|
+
client mode (such as when downloading from another OnionShare)
|
|
200
|
+
"""
|
|
201
|
+
# Decode the base32 string into bytes
|
|
202
|
+
encoded_key_b32 = encoded_key.encode("utf-8")
|
|
203
|
+
decoded_bytes = base64.b32decode(encoded_key_b32 + b"====")
|
|
204
|
+
|
|
205
|
+
# Convert the decoded bytes to base64
|
|
206
|
+
base64_key = base64.b64encode(decoded_bytes)
|
|
207
|
+
|
|
208
|
+
# Convert to string and return
|
|
209
|
+
return base64_key.decode("utf-8")
|
|
210
|
+
|
|
193
211
|
def connect(
|
|
194
212
|
self,
|
|
195
213
|
custom_settings=None,
|
|
@@ -316,6 +334,9 @@ class Onion(object):
|
|
|
316
334
|
torrc_template = torrc_template.replace(
|
|
317
335
|
"{{snowflake_path}}", str(self.snowflake_file_path)
|
|
318
336
|
)
|
|
337
|
+
torrc_template = torrc_template.replace(
|
|
338
|
+
"{{webtunnel_path}}", str(self.webtunnel_file_path)
|
|
339
|
+
)
|
|
319
340
|
|
|
320
341
|
with open(self.tor_torrc, "w") as f:
|
|
321
342
|
self.common.log("Onion", "connect", "Writing torrc template file")
|
|
@@ -356,10 +377,10 @@ class Onion(object):
|
|
|
356
377
|
)
|
|
357
378
|
) as o:
|
|
358
379
|
f.write(o.read())
|
|
359
|
-
elif builtin_bridge_type == "meek
|
|
380
|
+
elif builtin_bridge_type == "meek":
|
|
360
381
|
with open(
|
|
361
382
|
self.common.get_resource_path(
|
|
362
|
-
"torrc_template-
|
|
383
|
+
"torrc_template-meek"
|
|
363
384
|
)
|
|
364
385
|
) as o:
|
|
365
386
|
f.write(o.read())
|
|
@@ -370,6 +391,18 @@ class Onion(object):
|
|
|
370
391
|
)
|
|
371
392
|
) as o:
|
|
372
393
|
f.write(o.read())
|
|
394
|
+
elif builtin_bridge_type == "webtunnel":
|
|
395
|
+
# There are no built-in WebTunnel bridges:
|
|
396
|
+
# BridgeDB only distributes WebTunnel bridges
|
|
397
|
+
# via the Moat API. Requesting them here would
|
|
398
|
+
# result in a torrc with UseBridges 1 and no
|
|
399
|
+
# Bridge lines, which tor refuses to start.
|
|
400
|
+
raise TorErrorInvalidSetting(
|
|
401
|
+
"There are no built-in WebTunnel bridges. "
|
|
402
|
+
"Request WebTunnel bridges from BridgeDB "
|
|
403
|
+
"using the 'Request a bridge' option, or "
|
|
404
|
+
"enter them as custom bridge lines."
|
|
405
|
+
)
|
|
373
406
|
self.common.log(
|
|
374
407
|
"Onion",
|
|
375
408
|
"connect",
|
|
@@ -417,7 +450,11 @@ class Onion(object):
|
|
|
417
450
|
|
|
418
451
|
return_code = self.tor_proc.poll()
|
|
419
452
|
if return_code != None:
|
|
420
|
-
self.common.log(
|
|
453
|
+
self.common.log(
|
|
454
|
+
"Onion",
|
|
455
|
+
"connect",
|
|
456
|
+
f"tor process has terminated early: {return_code}",
|
|
457
|
+
)
|
|
421
458
|
|
|
422
459
|
# Connect to the controller
|
|
423
460
|
self.common.log("Onion", "connect", "authenticating to tor controller")
|
|
@@ -679,12 +716,45 @@ class Onion(object):
|
|
|
679
716
|
else:
|
|
680
717
|
return False
|
|
681
718
|
|
|
682
|
-
def
|
|
719
|
+
def add_onion_client_auth(self, service_id, private_key, key_type="x25519"):
|
|
720
|
+
"""
|
|
721
|
+
Implemented from scratch here because it never made it into Stem maint branch
|
|
722
|
+
"""
|
|
723
|
+
if self.supports_v3_onions and self.supports_ephemeral:
|
|
724
|
+
private_key64 = self.private_key_to_b64(private_key)
|
|
725
|
+
request = f"ONION_CLIENT_AUTH_ADD {service_id} {key_type}:{private_key64}"
|
|
726
|
+
response = self.c.msg(request)
|
|
727
|
+
self.common.log("Onion", "add_onion_client_auth", response)
|
|
728
|
+
return response
|
|
729
|
+
else:
|
|
730
|
+
raise TorTooOldEphemeral()
|
|
731
|
+
|
|
732
|
+
def remove_onion_client_auth(self, service_id):
|
|
733
|
+
"""
|
|
734
|
+
Implemented from scratch here because it never made it into Stem maint branch
|
|
735
|
+
"""
|
|
736
|
+
if self.supports_v3_onions and self.supports_ephemeral:
|
|
737
|
+
request = f"ONION_CLIENT_AUTH_REMOVE {service_id}"
|
|
738
|
+
response = self.c.msg(request)
|
|
739
|
+
self.common.log("Onion", "remove_onion_client_auth", response)
|
|
740
|
+
return response
|
|
741
|
+
else:
|
|
742
|
+
raise TorTooOldEphemeral()
|
|
743
|
+
|
|
744
|
+
def start_onion_service(
|
|
745
|
+
self, mode, mode_settings, port, await_publication, unix_socket=None
|
|
746
|
+
):
|
|
683
747
|
"""
|
|
684
748
|
Start a onion service on port 80, pointing to the given port, and
|
|
685
749
|
return the onion hostname.
|
|
686
750
|
"""
|
|
687
|
-
|
|
751
|
+
if unix_socket:
|
|
752
|
+
target = f"unix:{unix_socket}"
|
|
753
|
+
else:
|
|
754
|
+
target = port
|
|
755
|
+
self.common.log(
|
|
756
|
+
"Onion", "start_onion_service", f"port={port}, target={target}"
|
|
757
|
+
)
|
|
688
758
|
|
|
689
759
|
if not self.supports_ephemeral:
|
|
690
760
|
print(
|
|
@@ -736,7 +806,7 @@ class Onion(object):
|
|
|
736
806
|
try:
|
|
737
807
|
if not self.supports_stealth:
|
|
738
808
|
res = self.c.create_ephemeral_hidden_service(
|
|
739
|
-
{80:
|
|
809
|
+
{80: target},
|
|
740
810
|
await_publication=await_publication,
|
|
741
811
|
basic_auth=None,
|
|
742
812
|
key_type=key_type,
|
|
@@ -744,7 +814,7 @@ class Onion(object):
|
|
|
744
814
|
)
|
|
745
815
|
else:
|
|
746
816
|
res = self.c.create_ephemeral_hidden_service(
|
|
747
|
-
{80:
|
|
817
|
+
{80: target},
|
|
748
818
|
await_publication=await_publication,
|
|
749
819
|
basic_auth=None,
|
|
750
820
|
key_type=key_type,
|