onionshare_cli 2.6.1__tar.gz → 2.6.3__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.
Files changed (49) hide show
  1. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/PKG-INFO +9 -10
  2. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/__init__.py +12 -1
  3. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/censorship.py +17 -15
  4. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/common.py +9 -8
  5. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/meek.py +2 -2
  6. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/mode_settings.py +16 -3
  7. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/onion.py +4 -2
  8. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/receive.html +1 -1
  9. onionshare_cli-2.6.3/onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  10. onionshare_cli-2.6.3/onionshare_cli/resources/torrc_template-snowflake +3 -0
  11. onionshare_cli-2.6.3/onionshare_cli/resources/version.txt +1 -0
  12. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/settings.py +4 -3
  13. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/chat_mode.py +70 -23
  14. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/receive_mode.py +5 -2
  15. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/send_base_mode.py +28 -12
  16. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/share_mode.py +12 -4
  17. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/web.py +10 -4
  18. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/pyproject.toml +8 -8
  19. onionshare_cli-2.6.1/onionshare_cli/resources/torrc_template-meek_lite_azure +0 -2
  20. onionshare_cli-2.6.1/onionshare_cli/resources/torrc_template-snowflake +0 -3
  21. onionshare_cli-2.6.1/onionshare_cli/resources/version.txt +0 -1
  22. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/onionshare.py +0 -0
  23. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/css/style.css +0 -0
  24. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/ajax.gif +0 -0
  25. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/favicon.ico +0 -0
  26. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/logo.png +0 -0
  27. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/logo_large.png +0 -0
  28. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/web_file.png +0 -0
  29. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/img/web_folder.png +0 -0
  30. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/chat.js +0 -0
  31. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/jquery-3.5.1.min.js +0 -0
  32. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/receive.js +0 -0
  33. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/send.js +0 -0
  34. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/socket.io.js +0 -0
  35. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/static/js/socket.io.min.js +0 -0
  36. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/403.html +0 -0
  37. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/404.html +0 -0
  38. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/405.html +0 -0
  39. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/500.html +0 -0
  40. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/chat.html +0 -0
  41. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/denied.html +0 -0
  42. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/listing.html +0 -0
  43. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/send.html +0 -0
  44. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/templates/thankyou.html +0 -0
  45. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/torrc_template +0 -0
  46. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/torrc_template-obfs4 +0 -0
  47. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/resources/wordlist.txt +0 -0
  48. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/__init__.py +0 -0
  49. {onionshare_cli-2.6.1 → onionshare_cli-2.6.3}/onionshare_cli/web/website_mode.py +0 -0
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: onionshare_cli
3
- Version: 2.6.1
3
+ Version: 2.6.3
4
4
  Summary: OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service.
5
5
  License: GPLv3+
6
6
  Author: Micah Lee
7
7
  Author-email: micah@micahflee.com
8
- Requires-Python: >=3.8,<3.12
8
+ Requires-Python: >=3.10,<3.13
9
9
  Classifier: Environment :: Web Environment
10
10
  Classifier: Framework :: Flask
11
11
  Classifier: Intended Audience :: End Users/Desktop
@@ -13,10 +13,9 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
13
13
  Classifier: License :: Other/Proprietary License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.8
17
- Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Topic :: Communications :: File Sharing
21
20
  Classifier: Topic :: Security :: Cryptography
22
21
  Requires-Dist: cffi (>=1.15.1,<2.0.0)
@@ -29,16 +28,16 @@ Requires-Dist: flask-compress (>=1.13,<2.0)
29
28
  Requires-Dist: flask-socketio (==5.3.4)
30
29
  Requires-Dist: gevent (>=23.9.1,<24.0.0)
31
30
  Requires-Dist: gevent-websocket
32
- Requires-Dist: packaging (>=23.1,<24.0)
31
+ Requires-Dist: packaging (>=24)
33
32
  Requires-Dist: psutil
34
33
  Requires-Dist: pynacl
35
34
  Requires-Dist: pysocks
36
35
  Requires-Dist: qrcode (>=7.4.2,<8.0.0)
37
36
  Requires-Dist: requests[socks]
38
- Requires-Dist: setuptools
37
+ Requires-Dist: setuptools (>=70.0.0)
39
38
  Requires-Dist: stem (==1.8.1)
40
39
  Requires-Dist: unidecode
41
- Requires-Dist: urllib3
42
- Requires-Dist: waitress (>=2.1.2,<3.0.0)
43
- Requires-Dist: werkzeug (>=2.3.4)
40
+ Requires-Dist: urllib3 (>=2.2.2,<3.0.0)
41
+ Requires-Dist: waitress (>=3.0.1,<4.0.0)
42
+ Requires-Dist: werkzeug (==3.0.6)
44
43
  Requires-Dist: wheel (>=0.41.2,<0.42.0)
@@ -119,6 +119,13 @@ def main(cwd=None):
119
119
  default=False,
120
120
  help="Share files: Continue sharing after files have been sent (default is to stop sharing)",
121
121
  )
122
+ parser.add_argument(
123
+ "--log-filenames",
124
+ action="store_true",
125
+ dest="log_filenames",
126
+ default=False,
127
+ help="Log file download activity to stdout"
128
+ )
122
129
  parser.add_argument(
123
130
  "--qr",
124
131
  action="store_true",
@@ -204,6 +211,7 @@ def main(cwd=None):
204
211
  disable_files = args.disable_files
205
212
  disable_csp = bool(args.disable_csp)
206
213
  custom_csp = args.custom_csp
214
+ log_filenames = bool(args.log_filenames)
207
215
  verbose = bool(args.verbose)
208
216
 
209
217
  # Verbose mode?
@@ -242,6 +250,7 @@ def main(cwd=None):
242
250
  mode_settings.set("persistent", "mode", mode)
243
251
  if mode == "share":
244
252
  mode_settings.set("share", "autostop_sharing", autostop_sharing)
253
+ mode_settings.set("share", "log_filenames", log_filenames)
245
254
  if mode == "receive":
246
255
  if data_dir:
247
256
  mode_settings.set("receive", "data_dir", data_dir)
@@ -259,6 +268,7 @@ def main(cwd=None):
259
268
  if custom_csp:
260
269
  mode_settings.set("website", "custom_csp", custom_csp)
261
270
  mode_settings.set("website", "disable_csp", False)
271
+ mode_settings.set("website", "log_filenames", log_filenames)
262
272
  else:
263
273
  # See what the persistent mode was
264
274
  mode = mode_settings.get("persistent", "mode")
@@ -505,7 +515,7 @@ def main(cwd=None):
505
515
  if not app.autostop_timer_thread.is_alive():
506
516
  if mode == "share":
507
517
  # If there were no attempts to download the share, or all downloads are done, we can stop
508
- if web.share_mode.cur_history_id == 0 or web.done:
518
+ if not web.share_mode.download_in_progress or web.share_mode.cur_history_id == 0 or web.done:
509
519
  print("Stopped because auto-stop timer ran out")
510
520
  web.stop(app.port)
511
521
  break
@@ -531,6 +541,7 @@ def main(cwd=None):
531
541
  finally:
532
542
  # Shutdown
533
543
  web.cleanup()
544
+ t.join()
534
545
  onion.cleanup()
535
546
 
536
547
 
@@ -238,21 +238,23 @@ class CensorshipCircumvention(object):
238
238
  bridges_ok = False
239
239
  self.settings = settings
240
240
 
241
- # @TODO there might be several bridge types recommended.
242
- # Should we attempt to iterate over each type if one of them fails to connect?
243
- # But if so, how to stop it starting 3 separate Tor connection threads?
244
- # for bridges in request_bridges["settings"]:
245
- bridges = bridge_settings["settings"][0]["bridges"]
246
- bridge_strings = bridges["bridge_strings"]
247
-
248
- self.settings.set("bridges_type", "custom")
249
-
250
- # Sanity check the bridges provided from the Tor API before saving
251
- bridges_checked = self.common.check_bridges_valid(bridge_strings)
252
-
253
- if bridges_checked:
254
- self.settings.set("bridges_custom", "\n".join(bridges_checked))
255
- bridges_ok = True
241
+ # We iterate over each group of bridges returned in settings.
242
+ # The first set of valid bridges are the ones we use.
243
+ if bridge_settings.get("settings", False):
244
+ for returned_bridge_settings in bridge_settings["settings"]:
245
+ if returned_bridge_settings.get("bridges", False):
246
+ bridges = returned_bridge_settings["bridges"]
247
+ bridge_strings = bridges["bridge_strings"]
248
+
249
+ self.settings.set("bridges_type", "custom")
250
+
251
+ # Sanity check the bridges provided from the Tor API before saving
252
+ bridges_checked = self.common.check_bridges_valid(bridge_strings)
253
+
254
+ if bridges_checked:
255
+ self.settings.set("bridges_custom", "\n".join(bridges_checked))
256
+ bridges_ok = True
257
+ break
256
258
 
257
259
  # If we got any good bridges, save them to settings and return.
258
260
  if bridges_ok:
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
19
  """
20
20
  import base64
21
21
  import hashlib
22
+ import importlib.resources as importlib_resources
22
23
  import os
23
24
  import platform
24
25
  import random
@@ -29,7 +30,6 @@ import threading
29
30
  import time
30
31
  import shutil
31
32
  import re
32
- from pkg_resources import resource_filename
33
33
 
34
34
  import colorama
35
35
  from colorama import Fore, Back, Style
@@ -313,9 +313,10 @@ class Common:
313
313
  """
314
314
  Returns the absolute path of a resource
315
315
  """
316
- path = resource_filename("onionshare_cli", os.path.join("resources", filename))
317
- self.log("Common", "get_resource_path", f"filename={filename}, path={path}")
318
- return path
316
+ ref = importlib_resources.files("onionshare_cli.resources") / filename
317
+ with importlib_resources.as_file(ref) as path:
318
+ self.log("Common", "get_resource_path", f"filename={filename}, path={str(path)}")
319
+ return str(path)
319
320
 
320
321
  def get_tor_paths(self):
321
322
  if self.platform == "Linux":
@@ -484,16 +485,16 @@ class Common:
484
485
  if bridge != "":
485
486
  # Check the syntax of the custom bridge to make sure it looks legitimate
486
487
  ipv4_pattern = re.compile(
487
- "(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]+)(.+)$"
488
+ r"(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]+)(.+)$"
488
489
  )
489
490
  ipv6_pattern = re.compile(
490
- "(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]+(.+)$"
491
+ r"(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]+(.+)$"
491
492
  )
492
493
  meek_lite_pattern = re.compile(
493
- "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
494
+ r"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
494
495
  )
495
496
  snowflake_pattern = re.compile(
496
- "(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
497
+ r"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
497
498
  )
498
499
  if (
499
500
  ipv4_pattern.match(bridge)
@@ -51,8 +51,8 @@ class Meek(object):
51
51
  ) = get_tor_paths()
52
52
 
53
53
  self.meek_proxies = {}
54
- self.meek_url = "https://moat.torproject.org.global.prod.fastly.net/"
55
- self.meek_front = "cdn.sstatic.net"
54
+ self.meek_url = "https://1723079976.rsc.cdn77.org/"
55
+ self.meek_front = "www.phpmyadmin.net"
56
56
  self.meek_env = {
57
57
  "TOR_PT_MANAGED_TRANSPORT_VER": "1",
58
58
  "TOR_PT_CLIENT_TRANSPORTS": "meek",
@@ -40,7 +40,11 @@ class ModeSettings:
40
40
  "client_auth_priv_key": None,
41
41
  "client_auth_pub_key": None,
42
42
  },
43
- "persistent": {"mode": None, "enabled": False},
43
+ "persistent": {
44
+ "mode": None,
45
+ "enabled": False,
46
+ "autostart_on_launch": False
47
+ },
44
48
  "general": {
45
49
  "title": None,
46
50
  "public": False,
@@ -48,14 +52,23 @@ class ModeSettings:
48
52
  "autostop_timer": False,
49
53
  "service_id": None,
50
54
  },
51
- "share": {"autostop_sharing": True, "filenames": []},
55
+ "share": {
56
+ "autostop_sharing": True,
57
+ "filenames": [],
58
+ "log_filenames": False,
59
+ },
52
60
  "receive": {
53
61
  "data_dir": self.build_default_receive_data_dir(),
54
62
  "webhook_url": None,
55
63
  "disable_text": False,
56
64
  "disable_files": False,
57
65
  },
58
- "website": {"disable_csp": False, "custom_csp": None, "filenames": []},
66
+ "website": {
67
+ "disable_csp": False,
68
+ "custom_csp": None,
69
+ "log_filenames": False,
70
+ "filenames": []
71
+ },
59
72
  "chat": {},
60
73
  }
61
74
  self._settings = {}
@@ -27,13 +27,14 @@ import base64
27
27
  import nacl.public
28
28
  import os
29
29
  import psutil
30
+ import re
30
31
  import shlex
31
32
  import subprocess
32
33
  import tempfile
33
34
  import time
34
35
  import traceback
35
36
 
36
- from distutils.version import LooseVersion as Version
37
+ from packaging.version import Version
37
38
 
38
39
 
39
40
  class TorErrorAutomatic(Exception):
@@ -658,7 +659,8 @@ class Onion(object):
658
659
  # Does this version of Tor support next-gen ('v3') onions?
659
660
  # Note, this is the version of Tor where this bug was fixed:
660
661
  # https://trac.torproject.org/projects/tor/ticket/28619
661
- self.supports_v3_onions = self.tor_version >= Version("0.3.5.7")
662
+ cleaned_tor_version = re.sub(r"\s*\(.*\)", "", self.tor_version)
663
+ self.supports_v3_onions = Version(cleaned_tor_version) >= Version("0.3.5.7")
662
664
 
663
665
  # Now that we are connected to Tor, if we are using built-in bridges,
664
666
  # update them with the latest copy available from the Tor API
@@ -53,7 +53,7 @@
53
53
  <p><input type="file" id="file-select" name="file[]" multiple /></p>
54
54
  {% endif %}
55
55
  {% if not disable_text %}
56
- <p><textarea id="text" name="text" placeholder="Write a message"></textarea></p>
56
+ <p><textarea id="text" name="text" placeholder="Write a message (max length 524288 characters)" maxlength="524288"></textarea></p>
57
57
  {% endif %}
58
58
  <p><button type="submit" id="send-button" class="button">Submit</button></p>
59
59
  </form>
@@ -0,0 +1,2 @@
1
+ # Enable built-in meek-azure bridge
2
+ Bridge meek_lite 192.0.2.20:80 url=https://1314488750.rsc.cdn77.org front=www.phpmyadmin.net utls=HelloRandomizedALPN
@@ -0,0 +1,3 @@
1
+ # Enable built-in snowflake bridge
2
+ Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.antisip.com:3478,stun:stun.epygi.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.mixvoip.com:3478,stun:stun.nextcloud.com:3478,stun:stun.bethesda.net:3478,stun:stun.nextcloud.com:443 utls-imitate=hellorandomizedalpn
3
+ Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.antisip.com:3478,stun:stun.epygi.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.mixvoip.com:3478,stun:stun.nextcloud.com:3478,stun:stun.bethesda.net:3478,stun:stun.nextcloud.com:443 utls-imitate=hellorandomizedalpn
@@ -66,7 +66,7 @@ class Settings(object):
66
66
  "zh_Hans": "中文 (简体)", # Simplified Chinese
67
67
  "hr": "Hrvatski", # Croatian
68
68
  "cs": "čeština", # Czech
69
- # "da": "Dansk", # Danish
69
+ "da": "Dansk", # Danish
70
70
  # "nl": "Nederlands", # Dutch
71
71
  "en": "English", # English
72
72
  "fi": "Suomi", # Finnish
@@ -76,7 +76,7 @@ class Settings(object):
76
76
  "el": "Ελληνικά", # Greek
77
77
  "is": "Íslenska", # Icelandic
78
78
  # "id": "Bahasa Indonesia", # Indonesian
79
- # "ga": "Gaeilge", # Irish
79
+ "ga": "Gaeilge", # Irish
80
80
  "it": "Italiano", # Italian
81
81
  "ja": "日本語", # Japanese
82
82
  "km": "ខ្មែរ", # Khmer(Central)
@@ -91,10 +91,11 @@ class Settings(object):
91
91
  "ru": "Русский", # Russian
92
92
  "sn": "chiShona", # Shona
93
93
  # "sr_Latn": "Srpska (latinica)", # Serbian (latin)
94
- # "sk": "Slovenčina", # Slovak
94
+ "sk": "Slovenčina", # Slovak
95
95
  "es": "Español", # Spanish
96
96
  "sw": "Kiswahili", # Swahili
97
97
  "sv": "Svenska", # Swedish
98
+ "ta": "Tamil", # Tamil
98
99
  # "te": "తెలుగు", # Telugu
99
100
  "tr": "Türkçe", # Turkish
100
101
  "uk": "Українська", # Ukrainian
@@ -17,6 +17,7 @@ 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
+ import unicodedata
20
21
 
21
22
  from flask import request, render_template, make_response, jsonify, session
22
23
  from flask_socketio import emit, ConnectionRefusedError
@@ -47,15 +48,45 @@ class ChatModeWeb:
47
48
 
48
49
  self.define_routes()
49
50
 
50
- def validate_username(self, username):
51
- username = username.strip()
52
- return (
53
- username
54
- and username.isascii()
55
- and username not in self.connected_users
56
- and len(username) < 128
51
+ def remove_unallowed_characters(self, text):
52
+ """
53
+ Sanitize username to remove unwanted characters.
54
+ Allowed characters right now are:
55
+ - all ASCII numbers
56
+ - all ASCII letters
57
+ - dash, underscore and single space
58
+ """
59
+
60
+ def allowed_character(ch):
61
+ allowed_unicode_categories = [
62
+ 'L', # All letters
63
+ 'N', # All numbers
64
+ ]
65
+ allowed_special_characters = [
66
+ '-', # dash
67
+ '_', # underscore
68
+ ' ', # single space
69
+ ]
70
+ return (
71
+ unicodedata.category(ch)[0] in allowed_unicode_categories and ord(ch) < 128
72
+ ) or ch in allowed_special_characters
73
+
74
+ return "".join(
75
+ ch for ch in text if allowed_character(ch)
57
76
  )
58
77
 
78
+ def validate_username(self, username):
79
+ try:
80
+ username = self.remove_unallowed_characters(username.strip())
81
+ return (
82
+ username
83
+ and username not in self.connected_users
84
+ and len(username) < 128
85
+ )
86
+ except Exception as e:
87
+ self.common.log("ChatModeWeb", "validate_username", e)
88
+ return False
89
+
59
90
  def define_routes(self):
60
91
  """
61
92
  The web app routes for chatting
@@ -77,13 +108,17 @@ class ChatModeWeb:
77
108
 
78
109
  self.web.add_request(self.web.REQUEST_LOAD, request.path)
79
110
  return render_template(
80
- "chat.html",
81
- static_url_path=self.web.static_url_path,
82
- username=session.get("name"),
83
- title=self.web.settings.get("general", "title"),
111
+ "chat.html",
112
+ static_url_path=self.web.static_url_path,
113
+ username=session.get("name"),
114
+ title=self.web.settings.get("general", "title"),
84
115
  )
85
116
 
86
- @self.web.app.route("/update-session-username", methods=["POST"], provide_automatic_options=False)
117
+ @self.web.app.route(
118
+ "/update-session-username",
119
+ methods=["POST"],
120
+ provide_automatic_options=False,
121
+ )
87
122
  def update_session_username():
88
123
  history_id = self.cur_history_id
89
124
  data = request.get_json()
@@ -122,6 +157,8 @@ class ChatModeWeb:
122
157
  A status message is broadcast to all people in the room."""
123
158
  if self.validate_username(session.get("name")):
124
159
  self.connected_users.append(session.get("name"))
160
+ # Store the session id for the user
161
+ session["socketio_session_id"] = request.sid
125
162
  emit(
126
163
  "status",
127
164
  {
@@ -133,7 +170,7 @@ class ChatModeWeb:
133
170
  broadcast=True,
134
171
  )
135
172
  else:
136
- raise ConnectionRefusedError('You are active from another session!')
173
+ raise ConnectionRefusedError('Invalid session')
137
174
 
138
175
  @self.web.socketio.on("text", namespace="/chat")
139
176
  def text(message):
@@ -153,9 +190,9 @@ class ChatModeWeb:
153
190
  new_name = message.get("username", "").strip()
154
191
  if self.validate_username(new_name):
155
192
  session["name"] = new_name
156
- self.connected_users[
157
- self.connected_users.index(current_name)
158
- ] = session.get("name")
193
+ self.connected_users[self.connected_users.index(current_name)] = (
194
+ session.get("name")
195
+ )
159
196
  emit(
160
197
  "status",
161
198
  {
@@ -178,13 +215,23 @@ class ChatModeWeb:
178
215
  def disconnect():
179
216
  """Sent by clients when they disconnect.
180
217
  A status message is broadcast to all people in the server."""
218
+ user_already_disconnected = False
181
219
  if session.get("name") in self.connected_users:
182
220
  self.connected_users.remove(session.get("name"))
183
- emit(
184
- "status",
185
- {
186
- "msg": "{} has left the room.".format(session.get("name")),
187
- "connected_users": self.connected_users,
188
- },
189
- broadcast=True,
221
+ else:
222
+ user_already_disconnected = True
223
+
224
+ # Forcefully disconnect the user
225
+ self.web.socketio.server.disconnect(
226
+ sid=session.get("socketio_session_id"), namespace="/chat"
190
227
  )
228
+
229
+ if not user_already_disconnected:
230
+ emit(
231
+ "status",
232
+ {
233
+ "msg": "{} has left the room.".format(session.get("name")),
234
+ "connected_users": self.connected_users,
235
+ },
236
+ broadcast=True,
237
+ )
@@ -194,7 +194,10 @@ class ReceiveModeWeb:
194
194
  if files_received > 0:
195
195
  msg = f"Uploaded {files_msg}"
196
196
  else:
197
- msg = "Nothing submitted"
197
+ if not self.web.settings.get("receive", "disable_text"):
198
+ msg = "Nothing submitted or message was too long (> 524288 characters)"
199
+ else:
200
+ msg = "Nothing submitted"
198
201
 
199
202
  if ajax:
200
203
  info_flashes.append(msg)
@@ -462,7 +465,7 @@ class ReceiveModeRequest(Request):
462
465
  self.includes_message = False
463
466
  if not self.web.settings.get("receive", "disable_text"):
464
467
  text_message = self.form.get("text")
465
- if text_message:
468
+ if text_message and len(text_message) <= 524288:
466
469
  if text_message.strip() != "":
467
470
  self.includes_message = True
468
471
 
@@ -25,7 +25,7 @@ import mimetypes
25
25
  import gzip
26
26
  from flask import Response, request
27
27
  from unidecode import unidecode
28
- from urllib.parse import quote
28
+ from urllib.parse import quote, unquote
29
29
 
30
30
 
31
31
  class SendBaseModeWeb:
@@ -132,6 +132,9 @@ class SendBaseModeWeb:
132
132
  self.set_file_info_custom(filenames, processed_size_callback)
133
133
 
134
134
  def directory_listing(self, filenames, path="", filesystem_path=None, add_trailing_slash=False):
135
+ """
136
+ Display the front page of a share or index.html-less website, listing the files/directories.
137
+ """
135
138
  # Tell the GUI about the directory listing
136
139
  history_id = self.cur_history_id
137
140
  self.cur_history_id += 1
@@ -151,6 +154,11 @@ class SendBaseModeWeb:
151
154
 
152
155
  # If filesystem_path is None, this is the root directory listing
153
156
  files, dirs = self.build_directory_listing(path, filenames, filesystem_path, add_trailing_slash)
157
+
158
+ # Mark the request as done so we know we can close the share if in auto-stop mode.
159
+ self.web.done = True
160
+
161
+ # Render and return the response.
154
162
  return self.directory_listing_template(
155
163
  path, files, dirs, breadcrumbs, breadcrumbs_leaf
156
164
  )
@@ -170,18 +178,18 @@ class SendBaseModeWeb:
170
178
  if is_dir:
171
179
  if add_trailing_slash:
172
180
  dirs.append(
173
- {"link": os.path.join(f"/{path}", filename, ""), "basename": filename}
181
+ {"link": os.path.join(f"/{path}", quote(filename), ""), "basename": filename}
174
182
  )
175
183
  else:
176
184
  dirs.append(
177
- {"link": os.path.join(f"/{path}", filename), "basename": filename}
185
+ {"link": os.path.join(f"/{path}", quote(filename)), "basename": filename}
178
186
  )
179
187
  else:
180
188
  size = os.path.getsize(this_filesystem_path)
181
189
  size_human = self.common.human_readable_filesize(size)
182
190
  files.append(
183
191
  {
184
- "link": os.path.join(f"/{path}", filename),
192
+ "link": os.path.join(f"/{path}", quote(filename)),
185
193
  "basename": filename,
186
194
  "size_human": size_human,
187
195
  }
@@ -228,11 +236,11 @@ class SendBaseModeWeb:
228
236
  chunk_size = 102400 # 100kb
229
237
 
230
238
  fp = open(file_to_download, "rb")
231
- done = False
232
- while not done:
239
+ self.web.done = False
240
+ while not self.web.done:
233
241
  chunk = fp.read(chunk_size)
234
242
  if chunk == b"":
235
- done = True
243
+ self.web.done = True
236
244
  else:
237
245
  try:
238
246
  yield chunk
@@ -245,8 +253,17 @@ class SendBaseModeWeb:
245
253
  or self.common.platform == "Linux"
246
254
  or self.common.platform == "BSD"
247
255
  ):
256
+ if self.web.settings.get(self.web.mode, "log_filenames"):
257
+ # Decode and sanitize the path to remove newlines
258
+ decoded_path = unquote(path)
259
+ decoded_path = decoded_path.replace("\r", "").replace("\n", "")
260
+ filename_str = f"{decoded_path} - "
261
+ else:
262
+ filename_str = ""
263
+
248
264
  sys.stdout.write(
249
- "\r{0:s}, {1:.2f}% ".format(
265
+ "\r{0}{1:s}, {2:.2f}% ".format(
266
+ filename_str,
250
267
  self.common.human_readable_filesize(
251
268
  downloaded_bytes
252
269
  ),
@@ -264,10 +281,10 @@ class SendBaseModeWeb:
264
281
  "filesize": filesize,
265
282
  },
266
283
  )
267
- done = False
284
+ self.web.done = False
268
285
  except Exception:
269
286
  # Looks like the download was canceled
270
- done = True
287
+ self.web.done = True
271
288
 
272
289
  # Tell the GUI the individual file was canceled
273
290
  self.web.add_request(
@@ -278,8 +295,7 @@ class SendBaseModeWeb:
278
295
 
279
296
  fp.close()
280
297
 
281
- if self.common.platform != "Darwin":
282
- sys.stdout.write("\n")
298
+ sys.stdout.write("\n")
283
299
 
284
300
  basename = os.path.basename(filesystem_path)
285
301
 
@@ -29,7 +29,7 @@ from datetime import datetime, timezone
29
29
  from flask import Response, request, render_template, make_response, abort
30
30
  from unidecode import unidecode
31
31
  from werkzeug.http import parse_date, http_date
32
- from urllib.parse import quote
32
+ from urllib.parse import quote, unquote
33
33
 
34
34
  from .send_base_mode import SendBaseModeWeb
35
35
 
@@ -346,8 +346,17 @@ class ShareModeWeb(SendBaseModeWeb):
346
346
  or self.common.platform == "Linux"
347
347
  or self.common.platform == "BSD"
348
348
  ):
349
+ if self.web.settings.get("share", "log_filenames"):
350
+ # Decode and sanitize the path to remove newlines
351
+ decoded_path = unquote(path)
352
+ decoded_path = decoded_path.replace("\r", "").replace("\n", "")
353
+ filename_str = f"{decoded_path} - "
354
+ else:
355
+ filename_str = ""
356
+
349
357
  sys.stdout.write(
350
- "\r{0:s}, {1:.2f}% ".format(
358
+ "\r{0}{1:s}, {2:.2f}% ".format(
359
+ filename_str,
351
360
  self.common.human_readable_filesize(downloaded_bytes),
352
361
  percent,
353
362
  )
@@ -376,8 +385,7 @@ class ShareModeWeb(SendBaseModeWeb):
376
385
 
377
386
  fp.close()
378
387
 
379
- if self.common.platform != "Darwin":
380
- sys.stdout.write("\n")
388
+ sys.stdout.write("\n")
381
389
 
382
390
  # Download is finished
383
391
  if self.web.settings.get("share", "autostop_sharing"):
@@ -176,11 +176,17 @@ class Web:
176
176
  self.website_mode = WebsiteModeWeb(self.common, self)
177
177
  elif self.mode == "chat":
178
178
  if self.common.verbose:
179
- self.socketio = SocketIO(
180
- async_mode="gevent", logger=True, engineio_logger=True
181
- )
179
+ try:
180
+ self.socketio = SocketIO(
181
+ async_mode="gevent", logger=True, engineio_logger=True
182
+ )
183
+ except ValueError:
184
+ self.socketio = SocketIO(logger=True, engineio_logger=True)
182
185
  else:
183
- self.socketio = SocketIO(async_mode="gevent")
186
+ try:
187
+ self.socketio = SocketIO(async_mode="gevent")
188
+ except ValueError:
189
+ self.socketio = SocketIO()
184
190
  self.socketio.init_app(self.app)
185
191
  self.chat_mode = ChatModeWeb(self.common, self)
186
192
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "onionshare_cli"
3
- version = "2.6.1"
3
+ version = "2.6.3"
4
4
  description = "OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service."
5
5
  authors = ["Micah Lee <micah@micahflee.com>"]
6
6
  license = "GPLv3+"
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
 
18
18
  [tool.poetry.dependencies]
19
- python = ">=3.8,<3.12"
19
+ python = ">=3.10,<3.13"
20
20
  click = "*"
21
21
  flask = "2.3.2"
22
22
  flask-compress = "^1.13"
@@ -25,23 +25,23 @@ psutil = "*"
25
25
  pysocks = "*"
26
26
  requests = {extras = ["socks"], version = "*"}
27
27
  unidecode = "*"
28
- urllib3 = "*"
28
+ urllib3 = "^2.2.2"
29
29
  eventlet = "*"
30
- setuptools = "*"
30
+ setuptools = ">=70.0.0"
31
31
  pynacl = "*"
32
32
  colorama = "*"
33
33
  gevent-websocket = "*"
34
34
  stem = "1.8.1"
35
- waitress = "^2.1.2"
36
- werkzeug = ">=2.3.4"
37
- packaging = "^23.1"
35
+ waitress = "^3.0.1"
36
+ werkzeug = "3.0.6"
37
+ packaging = ">=24"
38
38
  gevent = "^23.9.1"
39
39
  wheel = "^0.41.2"
40
40
  cffi = "^1.15.1"
41
41
  cython = "^3.0.2"
42
42
  qrcode = "^7.4.2"
43
43
 
44
- [tool.poetry.dev-dependencies]
44
+ [tool.poetry.group.dev.dependencies]
45
45
  pytest = ">=7.2.0"
46
46
 
47
47
  [tool.poetry.scripts]
@@ -1,2 +0,0 @@
1
- # Enable built-in meek-azure bridge
2
- Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
@@ -1,3 +0,0 @@
1
- # Enable built-in snowflake bridge
2
- Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn
3
- Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn