onionshare_cli 2.6.1__tar.gz → 2.6.4__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 (53) hide show
  1. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/PKG-INFO +20 -18
  2. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/__init__.py +27 -10
  3. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/censorship.py +17 -15
  4. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/common.py +10 -9
  5. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/meek.py +2 -2
  6. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/mode_settings.py +17 -3
  7. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/onion.py +4 -2
  8. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/css/style.css +17 -6
  9. onionshare_cli-2.6.4/onionshare_cli/resources/static/js/chat.js +251 -0
  10. onionshare_cli-2.6.4/onionshare_cli/resources/static/js/jquery-3.7.1.slim.min.js +2 -0
  11. onionshare_cli-2.6.4/onionshare_cli/resources/static/js/receive.js +176 -0
  12. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/js/send.js +32 -18
  13. onionshare_cli-2.6.4/onionshare_cli/resources/static/js/socket.io.min.js +7 -0
  14. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/chat.html +1 -1
  15. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/receive.html +2 -2
  16. onionshare_cli-2.6.4/onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  17. onionshare_cli-2.6.4/onionshare_cli/resources/torrc_template-snowflake +3 -0
  18. onionshare_cli-2.6.4/onionshare_cli/resources/version.txt +1 -0
  19. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/settings.py +6 -5
  20. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/chat_mode.py +70 -23
  21. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/receive_mode.py +144 -89
  22. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/send_base_mode.py +92 -17
  23. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/share_mode.py +32 -13
  24. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/web.py +11 -14
  25. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/pyproject.toml +17 -16
  26. onionshare_cli-2.6.1/onionshare_cli/resources/static/js/chat.js +0 -188
  27. onionshare_cli-2.6.1/onionshare_cli/resources/static/js/jquery-3.5.1.min.js +0 -2
  28. onionshare_cli-2.6.1/onionshare_cli/resources/static/js/receive.js +0 -141
  29. onionshare_cli-2.6.1/onionshare_cli/resources/static/js/socket.io.js +0 -9
  30. onionshare_cli-2.6.1/onionshare_cli/resources/static/js/socket.io.min.js +0 -7
  31. onionshare_cli-2.6.1/onionshare_cli/resources/torrc_template-meek_lite_azure +0 -2
  32. onionshare_cli-2.6.1/onionshare_cli/resources/torrc_template-snowflake +0 -3
  33. onionshare_cli-2.6.1/onionshare_cli/resources/version.txt +0 -1
  34. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/onionshare.py +0 -0
  35. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/ajax.gif +0 -0
  36. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/favicon.ico +0 -0
  37. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/logo.png +0 -0
  38. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/logo_large.png +0 -0
  39. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/web_file.png +0 -0
  40. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/static/img/web_folder.png +0 -0
  41. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/403.html +0 -0
  42. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/404.html +0 -0
  43. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/405.html +0 -0
  44. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/500.html +0 -0
  45. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/denied.html +0 -0
  46. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/listing.html +0 -0
  47. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/send.html +0 -0
  48. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/templates/thankyou.html +0 -0
  49. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/torrc_template +0 -0
  50. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/torrc_template-obfs4 +0 -0
  51. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/resources/wordlist.txt +0 -0
  52. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/__init__.py +0 -0
  53. {onionshare_cli-2.6.1 → onionshare_cli-2.6.4}/onionshare_cli/web/website_mode.py +0 -0
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: onionshare_cli
3
- Version: 2.6.1
3
+ Version: 2.6.4
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,<4.0
9
9
  Classifier: Environment :: Web Environment
10
10
  Classifier: Framework :: Flask
11
11
  Classifier: Intended Audience :: End Users/Desktop
@@ -13,32 +13,34 @@ 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
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Topic :: Communications :: File Sharing
21
22
  Classifier: Topic :: Security :: Cryptography
22
- Requires-Dist: cffi (>=1.15.1,<2.0.0)
23
+ Requires-Dist: cffi (>=2,<3)
23
24
  Requires-Dist: click
24
25
  Requires-Dist: colorama
25
26
  Requires-Dist: cython (>=3.0.2,<4.0.0)
26
27
  Requires-Dist: eventlet
27
- Requires-Dist: flask (==2.3.2)
28
- Requires-Dist: flask-compress (>=1.13,<2.0)
29
- Requires-Dist: flask-socketio (==5.3.4)
30
- Requires-Dist: gevent (>=23.9.1,<24.0.0)
28
+ Requires-Dist: flask (==3.1.3)
29
+ Requires-Dist: flask-compress (>=1.24,<2.0)
30
+ Requires-Dist: flask-socketio (>=5.6,<6.0)
31
+ Requires-Dist: gevent (>=26.5.0,<27.0.0)
31
32
  Requires-Dist: gevent-websocket
32
- Requires-Dist: packaging (>=23.1,<24.0)
33
+ Requires-Dist: packaging (>=24)
34
+ Requires-Dist: pkgconfig (>=1.6.0,<2.0.0)
33
35
  Requires-Dist: psutil
34
- Requires-Dist: pynacl
36
+ Requires-Dist: pynacl (>=1.6.2,<2.0.0)
35
37
  Requires-Dist: pysocks
36
38
  Requires-Dist: qrcode (>=7.4.2,<8.0.0)
37
- Requires-Dist: requests[socks]
38
- Requires-Dist: setuptools
39
+ Requires-Dist: requests[socks] (>=2.32.3)
40
+ Requires-Dist: setuptools (>=80,<81)
39
41
  Requires-Dist: stem (==1.8.1)
40
42
  Requires-Dist: unidecode
41
- Requires-Dist: urllib3
42
- Requires-Dist: waitress (>=2.1.2,<3.0.0)
43
- Requires-Dist: werkzeug (>=2.3.4)
44
- Requires-Dist: wheel (>=0.41.2,<0.42.0)
43
+ Requires-Dist: urllib3 (>=2.7,<3.0)
44
+ Requires-Dist: waitress (>=3.0.1,<4.0.0)
45
+ Requires-Dist: werkzeug (==3.1.6)
46
+ Requires-Dist: wheel (>=0.46.2,<0.47.0)
@@ -33,6 +33,7 @@ from .onionshare import OnionShare
33
33
  from .mode_settings import ModeSettings
34
34
  from qrcode import QRCode
35
35
 
36
+
36
37
  def main(cwd=None):
37
38
  """
38
39
  The main() function implements all of the logic that the command-line version of
@@ -119,6 +120,13 @@ def main(cwd=None):
119
120
  default=False,
120
121
  help="Share files: Continue sharing after files have been sent (default is to stop sharing)",
121
122
  )
123
+ parser.add_argument(
124
+ "--log-filenames",
125
+ action="store_true",
126
+ dest="log_filenames",
127
+ default=False,
128
+ help="Log file download activity to stdout",
129
+ )
122
130
  parser.add_argument(
123
131
  "--qr",
124
132
  action="store_true",
@@ -204,6 +212,7 @@ def main(cwd=None):
204
212
  disable_files = args.disable_files
205
213
  disable_csp = bool(args.disable_csp)
206
214
  custom_csp = args.custom_csp
215
+ log_filenames = bool(args.log_filenames)
207
216
  verbose = bool(args.verbose)
208
217
 
209
218
  # Verbose mode?
@@ -242,6 +251,7 @@ def main(cwd=None):
242
251
  mode_settings.set("persistent", "mode", mode)
243
252
  if mode == "share":
244
253
  mode_settings.set("share", "autostop_sharing", autostop_sharing)
254
+ mode_settings.set("share", "log_filenames", log_filenames)
245
255
  if mode == "receive":
246
256
  if data_dir:
247
257
  mode_settings.set("receive", "data_dir", data_dir)
@@ -251,7 +261,9 @@ def main(cwd=None):
251
261
  mode_settings.set("receive", "disable_files", disable_files)
252
262
  if mode == "website":
253
263
  if disable_csp and custom_csp:
254
- print("You cannot disable the CSP and set a custom one. Either set --disable-csp or --custom-csp but not both.")
264
+ print(
265
+ "You cannot disable the CSP and set a custom one. Either set --disable-csp or --custom-csp but not both."
266
+ )
255
267
  sys.exit()
256
268
  if disable_csp:
257
269
  mode_settings.set("website", "disable_csp", True)
@@ -259,6 +271,7 @@ def main(cwd=None):
259
271
  if custom_csp:
260
272
  mode_settings.set("website", "custom_csp", custom_csp)
261
273
  mode_settings.set("website", "disable_csp", False)
274
+ mode_settings.set("website", "log_filenames", log_filenames)
262
275
  else:
263
276
  # See what the persistent mode was
264
277
  mode = mode_settings.get("persistent", "mode")
@@ -267,19 +280,18 @@ def main(cwd=None):
267
280
  if mode == "share" or mode == "website":
268
281
  # Unless you passed in a persistent filename, in which case get the filenames from
269
282
  # the mode settings
270
- if (
271
- persistent_filename
272
- and not mode_settings.just_created
273
- and len(filenames) != 0
274
- ):
283
+ if persistent_filename and not mode_settings.just_created:
284
+ # The assumption is that if we didn't just create the mode settings,
285
+ # this persistent json file already has validated the filenames
286
+ # defined therein.
275
287
  filenames = mode_settings.get(mode, "filenames")
276
-
277
288
  else:
278
- # Make sure filenames given if not using receiver mode
289
+ # Make sure filenames given. If it is somehow empty yet we had a persistent
290
+ # json file, something has gone wrong with that file, just remove it.
279
291
  if len(filenames) == 0:
280
292
  if persistent_filename:
281
293
  mode_settings.delete()
282
-
294
+ print("No file(s) were provided")
283
295
  parser.print_help()
284
296
  sys.exit()
285
297
 
@@ -505,7 +517,11 @@ def main(cwd=None):
505
517
  if not app.autostop_timer_thread.is_alive():
506
518
  if mode == "share":
507
519
  # 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:
520
+ if (
521
+ not web.share_mode.download_in_progress
522
+ or web.share_mode.cur_history_id == 0
523
+ or web.done
524
+ ):
509
525
  print("Stopped because auto-stop timer ran out")
510
526
  web.stop(app.port)
511
527
  break
@@ -531,6 +547,7 @@ def main(cwd=None):
531
547
  finally:
532
548
  # Shutdown
533
549
  web.cleanup()
550
+ t.join()
534
551
  onion.cleanup()
535
552
 
536
553
 
@@ -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)
@@ -613,7 +614,7 @@ class AutoStopTimer(threading.Thread):
613
614
 
614
615
  self.common = common
615
616
 
616
- self.setDaemon(True)
617
+ self.daemon = True
617
618
  self.time = time
618
619
 
619
620
  def run(self):
@@ -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,22 +40,36 @@ 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,
47
51
  "autostart_timer": False,
48
52
  "autostop_timer": False,
49
53
  "service_id": None,
54
+ "qr": False
55
+ },
56
+ "share": {
57
+ "autostop_sharing": True,
58
+ "filenames": [],
59
+ "log_filenames": False,
50
60
  },
51
- "share": {"autostop_sharing": True, "filenames": []},
52
61
  "receive": {
53
62
  "data_dir": self.build_default_receive_data_dir(),
54
63
  "webhook_url": None,
55
64
  "disable_text": False,
56
65
  "disable_files": False,
57
66
  },
58
- "website": {"disable_csp": False, "custom_csp": None, "filenames": []},
67
+ "website": {
68
+ "disable_csp": False,
69
+ "custom_csp": None,
70
+ "log_filenames": False,
71
+ "filenames": []
72
+ },
59
73
  "chat": {},
60
74
  }
61
75
  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
@@ -1,10 +1,10 @@
1
1
  .clearfix:after {
2
- content: ".";
3
- display: block;
4
- clear: both;
5
- visibility: hidden;
6
- line-height: 0;
7
- height: 0;
2
+ content: ".";
3
+ display: block;
4
+ clear: both;
5
+ visibility: hidden;
6
+ line-height: 0;
7
+ height: 0;
8
8
  }
9
9
 
10
10
  body {
@@ -369,6 +369,17 @@ div#uploads .upload progress {
369
369
  height: 20px;
370
370
  }
371
371
 
372
+ div#uploads .upload .upload-warning {
373
+ color: #c97000;
374
+ font-size: 0.85em;
375
+ font-weight: bold;
376
+ margin-top: 4px;
377
+ }
378
+
379
+ div#uploads .upload .upload-warning.hidden {
380
+ display: none;
381
+ }
382
+
372
383
  ul.flashes {
373
384
  list-style: none;
374
385
  margin: 0;
@@ -0,0 +1,251 @@
1
+ $(function () {
2
+ const chatContainerEls = document.getElementsByClassName("chat-container");
3
+ for (const chatContainerEl of chatContainerEls) {
4
+ chatContainerEl.classList.remove("no-js");
5
+ }
6
+
7
+ const socket = io(`${location.origin}/chat`, { transports: ["websocket"] });
8
+
9
+ // Store current username received from app context
10
+ /** @type {HTMLInputElement} */
11
+ const usernameEl = document.getElementById("username");
12
+ var current_username = usernameEl.value.trim();
13
+
14
+ // Triggered on any status change by any user, such as some
15
+ // user joined, or changed username, or left, etc.
16
+ socket.on("status", function (data) {
17
+ addMessageToPanel(data, current_username, "status");
18
+ console.log(data, current_username);
19
+ });
20
+
21
+ // Triggered when message is received from a user. Even when sent
22
+ // by self, it get triggered after the server sends back the emit.
23
+ socket.on("chat_message", function (data) {
24
+ addMessageToPanel(data, current_username, "chat");
25
+ console.log(data, current_username);
26
+ });
27
+
28
+ // Triggered when disconnected either by server stop or timeout
29
+ socket.on("disconnect", function (_reason, _details) {
30
+ addMessageToPanel(
31
+ { msg: "The chat server is disconnected." },
32
+ current_username,
33
+ "status",
34
+ );
35
+ });
36
+ socket.on("connect_error", function (error) {
37
+ console.error(error);
38
+ });
39
+
40
+ // Trigger new message on enter or click of send message button.
41
+ document
42
+ .getElementById("new-message")
43
+ .addEventListener("keypress", function (e) {
44
+ if (e.key === "Enter") {
45
+ emitMessage(socket);
46
+ }
47
+ });
48
+
49
+ // Keep buttons disabled unless changed or not empty
50
+ usernameEl.addEventListener("keyup", function (event) {
51
+ const username = event.currentTarget.value;
52
+ if (username !== "" && username !== current_username) {
53
+ if (event.key === "Enter") {
54
+ this.blur();
55
+ current_username = updateUsername(socket) || current_username;
56
+ }
57
+ }
58
+ });
59
+
60
+ // Show warning of losing data
61
+ window.addEventListener("beforeunload", function (e) {
62
+ e.preventDefault();
63
+ });
64
+ });
65
+
66
+ const addMessageToPanel = function (data, current_username, messageType) {
67
+ const scrollDiff = getScrollDiffBefore();
68
+ if (messageType === "status") {
69
+ addStatusMessage(data.msg);
70
+ if (data.connected_users) {
71
+ addUserList(data.connected_users, current_username);
72
+ }
73
+ } else if (messageType === "chat") {
74
+ addChatMessage(data.username, data.msg);
75
+ }
76
+ scrollBottomMaybe(scrollDiff);
77
+ };
78
+
79
+ const emitMessage = function (socket) {
80
+ const newMessageEl = document.getElementById("new-message");
81
+ const newMessage = newMessageEl.value;
82
+ newMessageEl.value = "";
83
+
84
+ const chatEl = document.getElementById("chat");
85
+ // $(chatEl).scrollTop(chatEl.scrollHeight);
86
+ chatEl.scrollTo({ top: chatEl.scrollHeight });
87
+
88
+ socket.emit("text", { msg: newMessage });
89
+ };
90
+
91
+ const updateUsername = function (socket) {
92
+ const username = document.getElementById("username").value;
93
+
94
+ if (
95
+ !checkUsernameExists(username) &&
96
+ !checkUsernameTooLong(username) &&
97
+ !checkUsernameAscii(username)
98
+ ) {
99
+ fetch(`${location.origin}/update-session-username`, {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ body: JSON.stringify({ username: username }),
105
+ })
106
+ .then(function (response) {
107
+ console.log(response);
108
+ return response.json();
109
+ })
110
+ .then(function (response) {
111
+ console.log(response);
112
+ if (response.success && response.username == username) {
113
+ socket.emit("update_username", { username: username });
114
+ } else {
115
+ throw new Error("Failed to update username.");
116
+ }
117
+ })
118
+ .catch(function () {
119
+ addStatusMessage("Failed to update username.");
120
+ });
121
+
122
+ return username;
123
+ }
124
+ return false;
125
+ };
126
+
127
+ /************************************/
128
+ /********* Util Functions ***********/
129
+ /************************************/
130
+
131
+ /**
132
+ * @param username {string}
133
+ * @returns {boolean}
134
+ */
135
+ const checkUsernameAscii = function (username) {
136
+ // ASCII characters have code points in the range U+0000-U+007F.
137
+ const usernameErrorEl = document.getElementById("username-error");
138
+ usernameErrorEl.textContent = "";
139
+
140
+ if (!/^[\u0000-\u007f]*$/.test(username)) {
141
+ usernameErrorEl.textContent = "Non-ASCII usernames are not supported.";
142
+ return true;
143
+ }
144
+
145
+ return false;
146
+ };
147
+
148
+ /**
149
+ * @param username {string}
150
+ * @returns {boolean}
151
+ */
152
+ const checkUsernameExists = function (username) {
153
+ const usernameErrorEl = document.getElementById("username-error");
154
+ usernameErrorEl.textContent = "";
155
+
156
+ const userEls = document.querySelectorAll("#user-list li");
157
+ const userMatches = Array.from(userEls).some(function (el) {
158
+ return el.textContent === username;
159
+ });
160
+
161
+ if (userMatches) {
162
+ usernameErrorEl.textContent = "User with that username exists!";
163
+ }
164
+
165
+ return userMatches;
166
+ };
167
+
168
+ /**
169
+ * @param username {string}
170
+ * @returns {boolean}
171
+ */
172
+ const checkUsernameTooLong = function (username) {
173
+ const usernameErrorEl = document.getElementById("username-error");
174
+ usernameErrorEl.textContent = "";
175
+
176
+ if (username.length > 128) {
177
+ usernameErrorEl.textContent = "Please choose a shorter username.";
178
+ return true;
179
+ }
180
+
181
+ return false;
182
+ };
183
+
184
+ /**
185
+ * @returns {number}
186
+ */
187
+ const getScrollDiffBefore = function () {
188
+ const chatEl = document.getElementById("chat");
189
+ return chatEl.scrollTop - (chatEl.scrollHeight - chatEl.offsetHeight);
190
+ };
191
+
192
+ /**
193
+ * @param scrollDiff {number}
194
+ */
195
+ const scrollBottomMaybe = function (scrollDiff) {
196
+ // Scrolls to bottom if the user is scrolled at bottom
197
+ // if the user has scrolled up, it won't scroll at bottom.
198
+ // Note: when a user themselves send a message, it will still
199
+ // scroll to the bottom even if they had scrolled up before.
200
+ const chatEl = document.getElementById("chat");
201
+ if (scrollDiff > 0) {
202
+ // $(chatEl).scrollTop(chatEl.scrollHeight);
203
+ chatEl.scrollTo({ top: chatEl.scrollHeight });
204
+ }
205
+ };
206
+
207
+ /**
208
+ * @param message {string}
209
+ */
210
+ const addStatusMessage = function (message) {
211
+ const messageEl = document.createElement("p");
212
+ messageEl.classList.add("status");
213
+ messageEl.textContent = message;
214
+
215
+ document.getElementById("chat").appendChild(messageEl);
216
+ };
217
+
218
+ /**
219
+ * @param username {string}
220
+ * @param message {string}
221
+ */
222
+ const addChatMessage = function (username, message) {
223
+ const messageEl = document.createElement("p");
224
+ messageEl.innerHTML = `<span class="username"></span><span class="message"></span>`;
225
+
226
+ messageEl.getElementsByClassName("username")[0].textContent = username;
227
+ messageEl.getElementsByClassName("message")[0].textContent = message;
228
+
229
+ document.getElementById("chat").appendChild(messageEl);
230
+ };
231
+
232
+ /**
233
+ * @param connected_users {string[]}
234
+ * @param current_username {string}
235
+ */
236
+ const addUserList = function (connected_users, current_username) {
237
+ connected_users.sort();
238
+
239
+ const liList = connected_users
240
+ .filter(function (username) {
241
+ return username !== current_username;
242
+ })
243
+ .map(function (username) {
244
+ const li = document.createElement("p");
245
+ li.textContent = username;
246
+ return li;
247
+ });
248
+
249
+ const userListEl = document.getElementById("user-list");
250
+ userListEl.replaceChildren(...liList);
251
+ };