onionshare_cli 2.6.1__py3-none-any.whl

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 (48) hide show
  1. onionshare_cli/__init__.py +538 -0
  2. onionshare_cli/censorship.py +311 -0
  3. onionshare_cli/common.py +624 -0
  4. onionshare_cli/meek.py +199 -0
  5. onionshare_cli/mode_settings.py +150 -0
  6. onionshare_cli/onion.py +964 -0
  7. onionshare_cli/onionshare.py +91 -0
  8. onionshare_cli/resources/static/css/style.css +432 -0
  9. onionshare_cli/resources/static/img/ajax.gif +0 -0
  10. onionshare_cli/resources/static/img/favicon.ico +0 -0
  11. onionshare_cli/resources/static/img/logo.png +0 -0
  12. onionshare_cli/resources/static/img/logo_large.png +0 -0
  13. onionshare_cli/resources/static/img/web_file.png +0 -0
  14. onionshare_cli/resources/static/img/web_folder.png +0 -0
  15. onionshare_cli/resources/static/js/chat.js +188 -0
  16. onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
  17. onionshare_cli/resources/static/js/receive.js +141 -0
  18. onionshare_cli/resources/static/js/send.js +79 -0
  19. onionshare_cli/resources/static/js/socket.io.js +9 -0
  20. onionshare_cli/resources/static/js/socket.io.min.js +7 -0
  21. onionshare_cli/resources/templates/403.html +21 -0
  22. onionshare_cli/resources/templates/404.html +21 -0
  23. onionshare_cli/resources/templates/405.html +21 -0
  24. onionshare_cli/resources/templates/500.html +21 -0
  25. onionshare_cli/resources/templates/chat.html +47 -0
  26. onionshare_cli/resources/templates/denied.html +14 -0
  27. onionshare_cli/resources/templates/listing.html +60 -0
  28. onionshare_cli/resources/templates/receive.html +66 -0
  29. onionshare_cli/resources/templates/send.html +70 -0
  30. onionshare_cli/resources/templates/thankyou.html +27 -0
  31. onionshare_cli/resources/torrc_template +12 -0
  32. onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  33. onionshare_cli/resources/torrc_template-obfs4 +12 -0
  34. onionshare_cli/resources/torrc_template-snowflake +3 -0
  35. onionshare_cli/resources/version.txt +1 -0
  36. onionshare_cli/resources/wordlist.txt +7776 -0
  37. onionshare_cli/settings.py +209 -0
  38. onionshare_cli/web/__init__.py +21 -0
  39. onionshare_cli/web/chat_mode.py +190 -0
  40. onionshare_cli/web/receive_mode.py +630 -0
  41. onionshare_cli/web/send_base_mode.py +360 -0
  42. onionshare_cli/web/share_mode.py +599 -0
  43. onionshare_cli/web/web.py +409 -0
  44. onionshare_cli/web/website_mode.py +122 -0
  45. onionshare_cli-2.6.1.dist-info/METADATA +44 -0
  46. onionshare_cli-2.6.1.dist-info/RECORD +48 -0
  47. onionshare_cli-2.6.1.dist-info/WHEEL +4 -0
  48. onionshare_cli-2.6.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,624 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OnionShare | https://onionshare.org/
4
+
5
+ Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ """
20
+ import base64
21
+ import hashlib
22
+ import os
23
+ import platform
24
+ import random
25
+ import requests
26
+ import socket
27
+ import sys
28
+ import threading
29
+ import time
30
+ import shutil
31
+ import re
32
+ from pkg_resources import resource_filename
33
+
34
+ import colorama
35
+ from colorama import Fore, Back, Style
36
+
37
+ from .settings import Settings
38
+
39
+
40
+ class CannotFindTor(Exception):
41
+ """
42
+ OnionShare can't find a tor binary
43
+ """
44
+
45
+
46
+ class Common:
47
+ """
48
+ The Common object is shared amongst all parts of OnionShare.
49
+ """
50
+
51
+ def __init__(self, verbose=False):
52
+ self.verbose = verbose
53
+
54
+ colorama.init(autoreset=True)
55
+
56
+ # The platform OnionShare is running on
57
+ self.platform = platform.system()
58
+ if self.platform.endswith("BSD") or self.platform == "DragonFly":
59
+ self.platform = "BSD"
60
+
61
+ # The current version of OnionShare
62
+ with open(self.get_resource_path("version.txt")) as f:
63
+ self.version = f.read().strip()
64
+
65
+ def display_banner(self):
66
+ """
67
+ Raw ASCII art example:
68
+ ╭───────────────────────────────────────────╮
69
+ │ * ▄▄█████▄▄ * │
70
+ │ ▄████▀▀▀████▄ * │
71
+ │ ▀▀█▀ ▀██▄ │
72
+ │ * ▄█▄ ▀██▄ │
73
+ │ ▄█████▄ ███ -+- │
74
+ │ ███ ▀█████▀ │
75
+ │ ▀██▄ ▀█▀ │
76
+ │ * ▀██▄ ▄█▄▄ * │
77
+ │ * ▀████▄▄▄████▀ │
78
+ │ ▀▀█████▀▀ │
79
+ │ -+- * │
80
+ │ ▄▀▄ ▄▀▀ █ │
81
+ │ █ █ ▀ ▀▄ █ │
82
+ │ █ █ █▀▄ █ ▄▀▄ █▀▄ ▀▄ █▀▄ ▄▀▄ █▄▀ ▄█▄ │
83
+ │ ▀▄▀ █ █ █ ▀▄▀ █ █ ▄▄▀ █ █ ▀▄█ █ ▀▄▄ │
84
+ │ │
85
+ │ v2.3.1 │
86
+ │ │
87
+ │ https://onionshare.org/ │
88
+ ╰───────────────────────────────────────────╯
89
+ """
90
+
91
+ try:
92
+ print(
93
+ Back.MAGENTA
94
+ + Fore.WHITE
95
+ + "╭───────────────────────────────────────────╮"
96
+ )
97
+ print(
98
+ Back.MAGENTA
99
+ + Fore.WHITE
100
+ + "│"
101
+ + Fore.LIGHTMAGENTA_EX
102
+ + " * "
103
+ + Fore.WHITE
104
+ + "▄▄█████▄▄"
105
+ + Fore.LIGHTMAGENTA_EX
106
+ + " * "
107
+ + Fore.WHITE
108
+ + "│"
109
+ )
110
+ print(
111
+ Back.MAGENTA
112
+ + Fore.WHITE
113
+ + "│"
114
+ + Fore.WHITE
115
+ + " ▄████▀▀▀████▄"
116
+ + Fore.LIGHTMAGENTA_EX
117
+ + " * "
118
+ + Fore.WHITE
119
+ + "│"
120
+ )
121
+ print(
122
+ Back.MAGENTA
123
+ + Fore.WHITE
124
+ + "│"
125
+ + Fore.WHITE
126
+ + " ▀▀█▀ ▀██▄ "
127
+ + Fore.WHITE
128
+ + "│"
129
+ )
130
+ print(
131
+ Back.MAGENTA
132
+ + Fore.WHITE
133
+ + "│"
134
+ + Fore.LIGHTMAGENTA_EX
135
+ + " * "
136
+ + Fore.WHITE
137
+ + "▄█▄ ▀██▄ "
138
+ + Fore.WHITE
139
+ + "│"
140
+ )
141
+ print(
142
+ Back.MAGENTA
143
+ + Fore.WHITE
144
+ + "│"
145
+ + Fore.WHITE
146
+ + " ▄█████▄ ███"
147
+ + Fore.LIGHTMAGENTA_EX
148
+ + " -+- "
149
+ + Fore.WHITE
150
+ + "│"
151
+ )
152
+ print(
153
+ Back.MAGENTA
154
+ + Fore.WHITE
155
+ + "│"
156
+ + Fore.WHITE
157
+ + " ███ ▀█████▀ "
158
+ + Fore.WHITE
159
+ + "│"
160
+ )
161
+ print(
162
+ Back.MAGENTA
163
+ + Fore.WHITE
164
+ + "│"
165
+ + Fore.WHITE
166
+ + " ▀██▄ ▀█▀ "
167
+ + Fore.WHITE
168
+ + "│"
169
+ )
170
+ print(
171
+ Back.MAGENTA
172
+ + Fore.WHITE
173
+ + "│"
174
+ + Fore.LIGHTMAGENTA_EX
175
+ + " * "
176
+ + Fore.WHITE
177
+ + "▀██▄ ▄█▄▄"
178
+ + Fore.LIGHTMAGENTA_EX
179
+ + " * "
180
+ + Fore.WHITE
181
+ + "│"
182
+ )
183
+ print(
184
+ Back.MAGENTA
185
+ + Fore.WHITE
186
+ + "│"
187
+ + Fore.LIGHTMAGENTA_EX
188
+ + " * "
189
+ + Fore.WHITE
190
+ + "▀████▄▄▄████▀ "
191
+ + Fore.WHITE
192
+ + "│"
193
+ )
194
+ print(
195
+ Back.MAGENTA
196
+ + Fore.WHITE
197
+ + "│"
198
+ + Fore.WHITE
199
+ + " ▀▀█████▀▀ "
200
+ + Fore.WHITE
201
+ + "│"
202
+ )
203
+ print(
204
+ Back.MAGENTA
205
+ + Fore.WHITE
206
+ + "│"
207
+ + Fore.LIGHTMAGENTA_EX
208
+ + " -+- * "
209
+ + Fore.WHITE
210
+ + "│"
211
+ )
212
+ print(
213
+ Back.MAGENTA
214
+ + Fore.WHITE
215
+ + "│"
216
+ + Fore.WHITE
217
+ + " ▄▀▄ ▄▀▀ █ "
218
+ + Fore.WHITE
219
+ + "│"
220
+ )
221
+ print(
222
+ Back.MAGENTA
223
+ + Fore.WHITE
224
+ + "│"
225
+ + Fore.WHITE
226
+ + " █ █ ▀ ▀▄ █ "
227
+ + Fore.WHITE
228
+ + "│"
229
+ )
230
+ print(
231
+ Back.MAGENTA
232
+ + Fore.WHITE
233
+ + "│"
234
+ + Fore.WHITE
235
+ + " █ █ █▀▄ █ ▄▀▄ █▀▄ ▀▄ █▀▄ ▄▀▄ █▄▀ ▄█▄ "
236
+ + Fore.WHITE
237
+ + "│"
238
+ )
239
+ print(
240
+ Back.MAGENTA
241
+ + Fore.WHITE
242
+ + "│"
243
+ + Fore.WHITE
244
+ + " ▀▄▀ █ █ █ ▀▄▀ █ █ ▄▄▀ █ █ ▀▄█ █ ▀▄▄ "
245
+ + Fore.WHITE
246
+ + "│"
247
+ )
248
+ print(
249
+ Back.MAGENTA
250
+ + Fore.WHITE
251
+ + "│ │"
252
+ )
253
+ left_spaces = (43 - len(self.version) - 1) // 2
254
+ right_spaces = left_spaces
255
+ if left_spaces + len(self.version) + 1 + right_spaces < 43:
256
+ right_spaces += 1
257
+ print(
258
+ Back.MAGENTA
259
+ + Fore.WHITE
260
+ + "│"
261
+ + Fore.WHITE
262
+ + f"{' '*left_spaces}v{self.version}{' '*right_spaces}"
263
+ + Fore.WHITE
264
+ + "│"
265
+ )
266
+ print(
267
+ Back.MAGENTA
268
+ + Fore.WHITE
269
+ + "│ │"
270
+ )
271
+ print(
272
+ Back.MAGENTA
273
+ + Fore.WHITE
274
+ + "│"
275
+ + Fore.WHITE
276
+ + " https://onionshare.org/ "
277
+ + Fore.WHITE
278
+ + "│"
279
+ )
280
+ print(
281
+ Back.MAGENTA
282
+ + Fore.WHITE
283
+ + "╰───────────────────────────────────────────╯"
284
+ )
285
+ print()
286
+ except:
287
+ # If anything fails, print a boring banner
288
+ print(f"OnionShare v{self.version}")
289
+ print("https://onionshare.org/")
290
+ print()
291
+
292
+ def load_settings(self, config=None):
293
+ """
294
+ Loading settings, optionally from a custom config json file.
295
+ """
296
+ self.settings = Settings(self, config)
297
+ self.settings.load()
298
+
299
+ def log(self, module, func, msg=None):
300
+ """
301
+ If verbose mode is on, log error messages to stdout
302
+ """
303
+ if self.verbose:
304
+ timestamp = time.strftime("%b %d %Y %X")
305
+ final_msg = f"{Fore.LIGHTBLACK_EX + Style.DIM}[{timestamp}]{Style.RESET_ALL} {Fore.WHITE + Style.DIM}{module}.{func}{Style.RESET_ALL}"
306
+ if msg:
307
+ final_msg = (
308
+ f"{final_msg}{Fore.WHITE + Style.DIM}: {msg}{Style.RESET_ALL}"
309
+ )
310
+ print(final_msg)
311
+
312
+ def get_resource_path(self, filename):
313
+ """
314
+ Returns the absolute path of a resource
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
319
+
320
+ def get_tor_paths(self):
321
+ if self.platform == "Linux":
322
+ tor_path = shutil.which("tor")
323
+ if not tor_path:
324
+ raise CannotFindTor()
325
+ obfs4proxy_file_path = shutil.which("obfs4proxy")
326
+ snowflake_file_path = shutil.which("snowflake-client")
327
+ meek_client_file_path = shutil.which("meek-client")
328
+ prefix = os.path.dirname(os.path.dirname(tor_path))
329
+ tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
330
+ tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
331
+ elif self.platform == "Windows":
332
+ # In Windows, the Tor binaries are in the onionshare package, not the onionshare_cli package
333
+ base_path = self.get_resource_path("tor")
334
+ base_path = base_path.replace("onionshare_cli", "onionshare")
335
+ tor_path = os.path.join(base_path, "tor.exe")
336
+
337
+ # If tor.exe isn't there, mayber we're running from the source tree
338
+ if not os.path.exists(tor_path):
339
+ self.log(
340
+ "Common", "get_tor_paths", f"Cannot find tor.exe at {tor_path}"
341
+ )
342
+ base_path = os.path.join(os.getcwd(), "onionshare", "resources", "tor")
343
+
344
+ tor_path = os.path.join(base_path, "tor.exe")
345
+ if not os.path.exists(tor_path):
346
+ self.log(
347
+ "Common", "get_tor_paths", f"Cannot find tor.exe at {tor_path}"
348
+ )
349
+ raise CannotFindTor()
350
+
351
+ obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy.exe")
352
+ snowflake_file_path = os.path.join(base_path, "snowflake-client.exe")
353
+ meek_client_file_path = os.path.join(base_path, "meek-client.exe")
354
+ tor_geo_ip_file_path = os.path.join(base_path, "geoip")
355
+ tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
356
+
357
+ elif self.platform == "Darwin":
358
+ # Let's see if we have tor binaries in the onionshare GUI package
359
+ base_path = self.get_resource_path("tor")
360
+ base_path = base_path.replace("onionshare_cli", "onionshare")
361
+ tor_path = os.path.join(base_path, "tor")
362
+ if os.path.exists(tor_path):
363
+ obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
364
+ snowflake_file_path = os.path.join(base_path, "snowflake-client")
365
+ meek_client_file_path = os.path.join(base_path, "meek-client")
366
+ tor_geo_ip_file_path = os.path.join(base_path, "geoip")
367
+ tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
368
+ else:
369
+ # Fallback to looking in the path
370
+ tor_path = shutil.which("tor")
371
+ if not os.path.exists(tor_path):
372
+ raise CannotFindTor()
373
+
374
+ obfs4proxy_file_path = shutil.which("obfs4proxy")
375
+ snowflake_file_path = shutil.which("snowflake-client")
376
+ meek_client_file_path = shutil.which("meek-client")
377
+ prefix = os.path.dirname(os.path.dirname(tor_path))
378
+ tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
379
+ tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
380
+
381
+ elif self.platform == "BSD":
382
+ tor_path = "/usr/local/bin/tor"
383
+ tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
384
+ tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
385
+ obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
386
+ snowflake_file_path = "/usr/local/bin/snowflake-client"
387
+ meek_client_file_path = "/usr/local/bin/meek-client"
388
+
389
+ return (
390
+ tor_path,
391
+ tor_geo_ip_file_path,
392
+ tor_geo_ipv6_file_path,
393
+ obfs4proxy_file_path,
394
+ snowflake_file_path,
395
+ meek_client_file_path,
396
+ )
397
+
398
+ def build_data_dir(self):
399
+ """
400
+ Returns the path of the OnionShare data directory.
401
+ """
402
+ if self.platform == "Windows":
403
+ try:
404
+ appdata = os.environ["APPDATA"]
405
+ onionshare_data_dir = f"{appdata}\\OnionShare"
406
+ except Exception:
407
+ # If for some reason we don't have the 'APPDATA' environment variable
408
+ # (like running tests in Linux while pretending to be in Windows)
409
+ try:
410
+ xdg_config_home = os.environ["XDG_CONFIG_HOME"]
411
+ onionshare_data_dir = f"{xdg_config_home}/onionshare"
412
+ except Exception:
413
+ onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
414
+ elif self.platform == "Darwin":
415
+ onionshare_data_dir = os.path.expanduser(
416
+ "~/Library/Application Support/OnionShare"
417
+ )
418
+ else:
419
+ try:
420
+ xdg_config_home = os.environ["XDG_CONFIG_HOME"]
421
+ onionshare_data_dir = f"{xdg_config_home}/onionshare"
422
+ except Exception:
423
+ onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
424
+
425
+ # Modify the data dir if running tests
426
+ if getattr(sys, "onionshare_test_mode", False):
427
+ onionshare_data_dir += "-testdata"
428
+
429
+ os.makedirs(onionshare_data_dir, 0o700, True)
430
+ return onionshare_data_dir
431
+
432
+ def build_tmp_dir(self):
433
+ """
434
+ Returns path to a folder that can hold temporary files
435
+ """
436
+ tmp_dir = os.path.join(self.build_data_dir(), "tmp")
437
+ os.makedirs(tmp_dir, 0o700, True)
438
+ return tmp_dir
439
+
440
+ def build_persistent_dir(self):
441
+ """
442
+ Returns the path to the folder that holds persistent files
443
+ """
444
+ persistent_dir = os.path.join(self.build_data_dir(), "persistent")
445
+ os.makedirs(persistent_dir, 0o700, True)
446
+ return persistent_dir
447
+
448
+ def build_tor_dir(self):
449
+ """
450
+ Returns path to the tor data directory
451
+ """
452
+ tor_dir = os.path.join(self.build_data_dir(), "tor_data")
453
+ os.makedirs(tor_dir, 0o700, True)
454
+ return tor_dir
455
+
456
+ def build_password(self, word_count=2):
457
+ """
458
+ Returns a random string made of words from the wordlist, such as "deter-trig".
459
+ """
460
+ with open(self.get_resource_path("wordlist.txt")) as f:
461
+ wordlist = f.read().split()
462
+
463
+ r = random.SystemRandom()
464
+ return "-".join(r.choice(wordlist) for _ in range(word_count))
465
+
466
+ def build_username(self, word_count=2):
467
+ """
468
+ Returns a random string made of words from the wordlist, such as "deter-trig".
469
+ """
470
+ with open(self.get_resource_path("wordlist.txt")) as f:
471
+ wordlist = f.read().split()
472
+
473
+ r = random.SystemRandom()
474
+ return "-".join(r.choice(wordlist) for _ in range(word_count))
475
+
476
+ def check_bridges_valid(self, bridges):
477
+ """
478
+ Does a regex check against a supplied list of bridges, to make sure they
479
+ are valid strings depending on the bridge type.
480
+ """
481
+ valid_bridges = []
482
+ self.log("Common", "check_bridges_valid", "Checking bridge syntax")
483
+ for bridge in bridges:
484
+ if bridge != "":
485
+ # Check the syntax of the custom bridge to make sure it looks legitimate
486
+ 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
+ )
489
+ 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
+ )
492
+ 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
+ )
495
+ snowflake_pattern = re.compile(
496
+ "(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
497
+ )
498
+ if (
499
+ ipv4_pattern.match(bridge)
500
+ or ipv6_pattern.match(bridge)
501
+ or meek_lite_pattern.match(bridge)
502
+ or snowflake_pattern.match(bridge)
503
+ ):
504
+ valid_bridges.append(bridge)
505
+ if valid_bridges:
506
+ return valid_bridges
507
+ else:
508
+ return False
509
+
510
+ def is_flatpak(self):
511
+ """
512
+ Returns True if OnionShare is running in a Flatpak sandbox
513
+ """
514
+ return os.environ.get("FLATPAK_ID") == "org.onionshare.OnionShare"
515
+
516
+ def is_snapcraft(self):
517
+ """
518
+ Returns True if OnionShare is running in a Snapcraft sandbox
519
+ """
520
+ return os.environ.get("SNAP_INSTANCE_NAME") == "onionshare"
521
+
522
+ @staticmethod
523
+ def random_string(num_bytes, output_len=None):
524
+ """
525
+ Returns a random string with a specified number of bytes.
526
+ """
527
+ b = os.urandom(num_bytes)
528
+ h = hashlib.sha256(b).digest()[:16]
529
+ s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8")
530
+ if not output_len:
531
+ return s
532
+ return s[:output_len]
533
+
534
+ @staticmethod
535
+ def human_readable_filesize(b):
536
+ """
537
+ Returns filesize in a human readable format.
538
+ """
539
+ thresh = 1024.0
540
+ if b < thresh:
541
+ return "{:.1f} B".format(b)
542
+ units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
543
+ u = 0
544
+ b /= thresh
545
+ while b >= thresh:
546
+ b /= thresh
547
+ u += 1
548
+ return "{:.1f} {}".format(b, units[u])
549
+
550
+ @staticmethod
551
+ def format_seconds(seconds):
552
+ """Return a human-readable string of the format 1d2h3m4s"""
553
+ days, seconds = divmod(seconds, 86400)
554
+ hours, seconds = divmod(seconds, 3600)
555
+ minutes, seconds = divmod(seconds, 60)
556
+
557
+ human_readable = []
558
+ if days:
559
+ human_readable.append("{:.0f}d".format(days))
560
+ if hours:
561
+ human_readable.append("{:.0f}h".format(hours))
562
+ if minutes:
563
+ human_readable.append("{:.0f}m".format(minutes))
564
+ if seconds or not human_readable:
565
+ human_readable.append("{:.0f}s".format(seconds))
566
+ return "".join(human_readable)
567
+
568
+ @staticmethod
569
+ def estimated_time_remaining(bytes_downloaded, total_bytes, started):
570
+ now = time.time()
571
+ time_elapsed = now - started # in seconds
572
+ download_rate = bytes_downloaded / time_elapsed
573
+ remaining_bytes = total_bytes - bytes_downloaded
574
+ eta = remaining_bytes / download_rate
575
+ return Common.format_seconds(eta)
576
+
577
+ @staticmethod
578
+ def get_available_port(min_port, max_port):
579
+ """
580
+ Find a random available port within the given range.
581
+ """
582
+ with socket.socket() as tmpsock:
583
+ while True:
584
+ try:
585
+ tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
586
+ break
587
+ except OSError:
588
+ pass
589
+ _, port = tmpsock.getsockname()
590
+ return port
591
+
592
+ @staticmethod
593
+ def dir_size(start_path):
594
+ """
595
+ Calculates the total size, in bytes, of all of the files in a directory.
596
+ """
597
+ total_size = 0
598
+ for dirpath, dirnames, filenames in os.walk(start_path):
599
+ for f in filenames:
600
+ fp = os.path.join(dirpath, f)
601
+ if not os.path.islink(fp):
602
+ total_size += os.path.getsize(fp)
603
+ return total_size
604
+
605
+
606
+ class AutoStopTimer(threading.Thread):
607
+ """
608
+ Background thread sleeps t hours and returns.
609
+ """
610
+
611
+ def __init__(self, common, time):
612
+ threading.Thread.__init__(self)
613
+
614
+ self.common = common
615
+
616
+ self.setDaemon(True)
617
+ self.time = time
618
+
619
+ def run(self):
620
+ self.common.log(
621
+ "AutoStopTimer", f"Server will shut down after {self.time} seconds"
622
+ )
623
+ time.sleep(self.time)
624
+ return 1