onionshare_cli 2.3.3__tar.gz → 2.5__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 (51) hide show
  1. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/PKG-INFO +5 -4
  2. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/__init__.py +56 -91
  3. onionshare_cli-2.5/onionshare_cli/censorship.py +200 -0
  4. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/common.py +257 -199
  5. onionshare_cli-2.5/onionshare_cli/meek.py +189 -0
  6. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/mode_settings.py +5 -7
  7. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/onion.py +251 -112
  8. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/onionshare.py +6 -5
  9. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/css/style.css +6 -6
  10. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/js/chat.js +20 -14
  11. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/js/send.js +14 -10
  12. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/403.html +1 -1
  13. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/404.html +1 -1
  14. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/405.html +1 -1
  15. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/500.html +1 -1
  16. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/send.html +9 -9
  17. onionshare_cli-2.5/onionshare_cli/resources/torrc_template +12 -0
  18. onionshare_cli-2.5/onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  19. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/torrc_template-obfs4 +9 -9
  20. onionshare_cli-2.5/onionshare_cli/resources/torrc_template-snowflake +2 -0
  21. onionshare_cli-2.5/onionshare_cli/resources/version.txt +1 -0
  22. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/settings.py +23 -21
  23. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/__init__.py +1 -1
  24. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/chat_mode.py +62 -52
  25. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/receive_mode.py +9 -13
  26. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/send_base_mode.py +13 -11
  27. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/share_mode.py +39 -29
  28. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/web.py +74 -135
  29. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/web/website_mode.py +1 -1
  30. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/pyproject.toml +4 -4
  31. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/setup.py +5 -5
  32. onionshare_cli-2.3.3/onionshare_cli/resources/templates/401.html +0 -21
  33. onionshare_cli-2.3.3/onionshare_cli/resources/torrc_template +0 -8
  34. onionshare_cli-2.3.3/onionshare_cli/resources/torrc_template-meek_lite_amazon +0 -2
  35. onionshare_cli-2.3.3/onionshare_cli/resources/torrc_template-meek_lite_azure +0 -2
  36. onionshare_cli-2.3.3/onionshare_cli/resources/version.txt +0 -1
  37. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/ajax.gif +0 -0
  38. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/favicon.ico +0 -0
  39. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/logo.png +0 -0
  40. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/logo_large.png +0 -0
  41. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/web_file.png +0 -0
  42. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/img/web_folder.png +0 -0
  43. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/js/jquery-3.5.1.min.js +0 -0
  44. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/js/receive.js +0 -0
  45. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/static/js/socket.io.min.js +0 -0
  46. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/chat.html +0 -0
  47. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/denied.html +0 -0
  48. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/listing.html +0 -0
  49. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/receive.html +0 -0
  50. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/templates/thankyou.html +0 -0
  51. {onionshare_cli-2.3.3 → onionshare_cli-2.5}/onionshare_cli/resources/wordlist.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: onionshare-cli
3
- Version: 2.3.3
3
+ Version: 2.5
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
@@ -13,23 +13,24 @@ 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.10
16
17
  Classifier: Programming Language :: Python :: 3.6
17
18
  Classifier: Programming Language :: Python :: 3.7
18
19
  Classifier: Programming Language :: Python :: 3.8
19
20
  Classifier: Programming Language :: Python :: 3.9
20
21
  Classifier: Topic :: Communications :: File Sharing
21
22
  Classifier: Topic :: Security :: Cryptography
23
+ Requires-Dist: cepa (==1.8.3)
22
24
  Requires-Dist: click
23
25
  Requires-Dist: colorama
24
26
  Requires-Dist: eventlet
25
27
  Requires-Dist: flask (==1.1.4)
26
- Requires-Dist: flask-httpauth
27
28
  Requires-Dist: flask-socketio (==5.0.1)
29
+ Requires-Dist: gevent-websocket
28
30
  Requires-Dist: psutil
29
- Requires-Dist: pycryptodome
31
+ Requires-Dist: pynacl
30
32
  Requires-Dist: pysocks
31
33
  Requires-Dist: requests[socks]
32
34
  Requires-Dist: setuptools
33
- Requires-Dist: stem
34
35
  Requires-Dist: unidecode
35
36
  Requires-Dist: urllib3
@@ -2,7 +2,7 @@
2
2
  """
3
3
  OnionShare | https://onionshare.org/
4
4
 
5
- Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
5
+ Copyright (C) 2014-2022 Micah Lee, et al. <micah@micahflee.com>
6
6
 
7
7
  This program is free software: you can redistribute it and/or modify
8
8
  it under the terms of the GNU General Public License as published by
@@ -28,24 +28,11 @@ from datetime import timedelta
28
28
 
29
29
  from .common import Common, CannotFindTor
30
30
  from .web import Web
31
- from .onion import (
32
- TorErrorProtocolError,
33
- TorTooOldEphemeral,
34
- TorTooOldStealth,
35
- Onion,
36
- )
31
+ from .onion import TorErrorProtocolError, TorTooOldEphemeral, TorTooOldStealth, Onion
37
32
  from .onionshare import OnionShare
38
33
  from .mode_settings import ModeSettings
39
34
 
40
35
 
41
- def build_url(mode_settings, app, web):
42
- # Build the URL
43
- if mode_settings.get("general", "public"):
44
- return f"http://{app.onion_host}"
45
- else:
46
- return f"http://onionshare:{web.password}@{app.onion_host}"
47
-
48
-
49
36
  def main(cwd=None):
50
37
  """
51
38
  The main() function implements all of the logic that the command-line version of
@@ -102,18 +89,13 @@ def main(cwd=None):
102
89
  help="Filename of persistent session",
103
90
  )
104
91
  # General args
105
- parser.add_argument(
106
- "--title",
107
- metavar="TITLE",
108
- default=None,
109
- help="Set a title",
110
- )
92
+ parser.add_argument("--title", metavar="TITLE", default=None, help="Set a title")
111
93
  parser.add_argument(
112
94
  "--public",
113
95
  action="store_true",
114
96
  dest="public",
115
97
  default=False,
116
- help="Don't use a password",
98
+ help="Don't use a private key",
117
99
  )
118
100
  parser.add_argument(
119
101
  "--auto-start-timer",
@@ -129,20 +111,6 @@ def main(cwd=None):
129
111
  default=0,
130
112
  help="Stop onion service at schedule time (N seconds from now)",
131
113
  )
132
- parser.add_argument(
133
- "--legacy",
134
- action="store_true",
135
- dest="legacy",
136
- default=False,
137
- help="Use legacy address (v2 onion service, not recommended)",
138
- )
139
- parser.add_argument(
140
- "--client-auth",
141
- action="store_true",
142
- dest="client_auth",
143
- default=False,
144
- help="Use client authorization (requires --legacy)",
145
- )
146
114
  # Share args
147
115
  parser.add_argument(
148
116
  "--no-autostop-sharing",
@@ -182,7 +150,13 @@ def main(cwd=None):
182
150
  action="store_true",
183
151
  dest="disable_csp",
184
152
  default=False,
185
- help="Publish website: Disable Content Security Policy header (allows your website to use third-party resources)",
153
+ help="Publish website: Disable the default Content Security Policy header (allows your website to use third-party resources)",
154
+ )
155
+ parser.add_argument(
156
+ "--custom_csp",
157
+ metavar="custom_csp",
158
+ default=None,
159
+ help="Publish website: Set a custom Content Security Policy header",
186
160
  )
187
161
  # Other
188
162
  parser.add_argument(
@@ -215,35 +189,18 @@ def main(cwd=None):
215
189
  public = bool(args.public)
216
190
  autostart_timer = int(args.autostart_timer)
217
191
  autostop_timer = int(args.autostop_timer)
218
- legacy = bool(args.legacy)
219
- client_auth = bool(args.client_auth)
220
192
  autostop_sharing = not bool(args.no_autostop_sharing)
221
193
  data_dir = args.data_dir
222
194
  webhook_url = args.webhook_url
223
195
  disable_text = args.disable_text
224
196
  disable_files = args.disable_files
225
197
  disable_csp = bool(args.disable_csp)
198
+ custom_csp = args.custom_csp
226
199
  verbose = bool(args.verbose)
227
200
 
228
- if receive:
229
- mode = "receive"
230
- elif website:
231
- mode = "website"
232
- elif chat:
233
- mode = "chat"
234
- else:
235
- mode = "share"
236
-
237
201
  # Verbose mode?
238
202
  common.verbose = verbose
239
203
 
240
- # client_auth can only be set if legacy is also set
241
- if client_auth and not legacy:
242
- print(
243
- "Client authentication (--client-auth) is only supported with with legacy onion services (--legacy)"
244
- )
245
- sys.exit()
246
-
247
204
  # Re-load settings, if a custom config was passed in
248
205
  if config_filename:
249
206
  common.load_settings(config_filename)
@@ -254,18 +211,26 @@ def main(cwd=None):
254
211
  if persistent_filename:
255
212
  mode_settings = ModeSettings(common, persistent_filename)
256
213
  mode_settings.set("persistent", "enabled", True)
257
- mode_settings.set("persistent", "mode", mode)
258
214
  else:
259
215
  mode_settings = ModeSettings(common)
260
216
 
217
+ if receive:
218
+ mode = "receive"
219
+ elif website:
220
+ mode = "website"
221
+ elif chat:
222
+ mode = "chat"
223
+ else:
224
+ mode = "share"
225
+
261
226
  if mode_settings.just_created:
262
227
  # This means the mode settings were just created, not loaded from disk
263
228
  mode_settings.set("general", "title", title)
264
229
  mode_settings.set("general", "public", public)
265
230
  mode_settings.set("general", "autostart_timer", autostart_timer)
266
231
  mode_settings.set("general", "autostop_timer", autostop_timer)
267
- mode_settings.set("general", "legacy", legacy)
268
- mode_settings.set("general", "client_auth", client_auth)
232
+ if persistent_filename:
233
+ mode_settings.set("persistent", "mode", mode)
269
234
  if mode == "share":
270
235
  mode_settings.set("share", "autostop_sharing", autostop_sharing)
271
236
  if mode == "receive":
@@ -276,7 +241,15 @@ def main(cwd=None):
276
241
  mode_settings.set("receive", "disable_text", disable_text)
277
242
  mode_settings.set("receive", "disable_files", disable_files)
278
243
  if mode == "website":
279
- mode_settings.set("website", "disable_csp", disable_csp)
244
+ if disable_csp and custom_csp:
245
+ print("You cannot disable the CSP and set a custom one. Either set --disable-csp or --custom-csp but not both.")
246
+ sys.exit()
247
+ if disable_csp:
248
+ mode_settings.set("website", "disable_csp", True)
249
+ mode_settings.set("website", "custom_csp", None)
250
+ if custom_csp:
251
+ mode_settings.set("website", "custom_csp", custom_csp)
252
+ mode_settings.set("website", "disable_csp", False)
280
253
  else:
281
254
  # See what the persistent mode was
282
255
  mode = mode_settings.get("persistent", "mode")
@@ -352,11 +325,6 @@ def main(cwd=None):
352
325
  try:
353
326
  common.settings.load()
354
327
 
355
- if mode_settings.get("general", "public"):
356
- web.password = None
357
- else:
358
- web.generate_password(mode_settings.get("onion", "password"))
359
-
360
328
  # Receive mode needs to know the tor proxy details for webhooks
361
329
  if mode == "receive":
362
330
  if local_only:
@@ -381,7 +349,7 @@ def main(cwd=None):
381
349
  sys.exit()
382
350
 
383
351
  app.start_onion_service(mode, mode_settings, False)
384
- url = build_url(mode_settings, app, web)
352
+ url = f"http://{app.onion_host}"
385
353
  schedule = datetime.now() + timedelta(seconds=autostart_timer)
386
354
  if mode == "receive":
387
355
  print(
@@ -394,21 +362,21 @@ def main(cwd=None):
394
362
  "what you are doing."
395
363
  )
396
364
  print("")
397
- if mode_settings.get("general", "client_auth"):
365
+ if not mode_settings.get("general", "public"):
398
366
  print(
399
- f"Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
367
+ f"Give this address and private key to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
400
368
  )
401
- print(app.auth_string)
369
+ print(f"Private key: {app.auth_string}")
402
370
  else:
403
371
  print(
404
372
  f"Give this address to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
405
373
  )
406
374
  else:
407
- if mode_settings.get("general", "client_auth"):
375
+ if not mode_settings.get("general", "public"):
408
376
  print(
409
- f"Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
377
+ f"Give this address and private key to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
410
378
  )
411
- print(app.auth_string)
379
+ print(f"Private key: {app.auth_string}")
412
380
  else:
413
381
  print(
414
382
  f"Give this address to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
@@ -426,7 +394,6 @@ def main(cwd=None):
426
394
  sys.exit()
427
395
  except (TorTooOldEphemeral, TorTooOldStealth, TorErrorProtocolError) as e:
428
396
  print("")
429
- print(e.args[0])
430
397
  sys.exit()
431
398
 
432
399
  if mode == "website":
@@ -447,7 +414,7 @@ def main(cwd=None):
447
414
  sys.exit(1)
448
415
 
449
416
  # Warn about sending large files over Tor
450
- if web.share_mode.download_filesize >= 157286400: # 150mb
417
+ if web.share_mode.download_filesize >= 157_286_400: # 150mb
451
418
  print("")
452
419
  print("Warning: Sending a large share could take hours")
453
420
  print("")
@@ -458,21 +425,14 @@ def main(cwd=None):
458
425
  t.start()
459
426
 
460
427
  try: # Trap Ctrl-C
461
- # Wait for web.generate_password() to finish running
462
428
  time.sleep(0.2)
463
429
 
464
430
  # start auto-stop timer thread
465
431
  if app.autostop_timer > 0:
466
432
  app.autostop_timer_thread.start()
467
433
 
468
- # Save the web password if we are using a persistent private key
469
- if mode_settings.get("persistent", "enabled"):
470
- if not mode_settings.get("onion", "password"):
471
- mode_settings.set("onion", "password", web.password)
472
- # mode_settings.save()
473
-
474
434
  # Build the URL
475
- url = build_url(mode_settings, app, web)
435
+ url = f"http://{app.onion_host}"
476
436
 
477
437
  print("")
478
438
  if autostart_timer > 0:
@@ -490,21 +450,21 @@ def main(cwd=None):
490
450
  )
491
451
  print("")
492
452
 
493
- if mode_settings.get("general", "client_auth"):
494
- print("Give this address and HidServAuth to the sender:")
453
+ if mode_settings.get("general", "public"):
454
+ print("Give this address to the sender:")
495
455
  print(url)
496
- print(app.auth_string)
497
456
  else:
498
- print("Give this address to the sender:")
457
+ print("Give this address and private key to the sender:")
499
458
  print(url)
459
+ print(f"Private key: {app.auth_string}")
500
460
  else:
501
- if mode_settings.get("general", "client_auth"):
502
- print("Give this address and HidServAuth line to the recipient:")
461
+ if mode_settings.get("general", "public"):
462
+ print("Give this address to the recipient:")
503
463
  print(url)
504
- print(app.auth_string)
505
464
  else:
506
- print("Give this address to the recipient:")
465
+ print("Give this address and private key to the recipient:")
507
466
  print(url)
467
+ print(f"Private key: {app.auth_string}")
508
468
  print("")
509
469
  print("Press Ctrl+C to stop the server")
510
470
 
@@ -513,13 +473,13 @@ def main(cwd=None):
513
473
  if app.autostop_timer > 0:
514
474
  # if the auto-stop timer was set and has run out, stop the server
515
475
  if not app.autostop_timer_thread.is_alive():
516
- if mode == "share" or (mode == "website"):
476
+ if mode == "share":
517
477
  # If there were no attempts to download the share, or all downloads are done, we can stop
518
478
  if web.share_mode.cur_history_id == 0 or web.done:
519
479
  print("Stopped because auto-stop timer ran out")
520
480
  web.stop(app.port)
521
481
  break
522
- if mode == "receive":
482
+ elif mode == "receive":
523
483
  if (
524
484
  web.receive_mode.cur_history_id == 0
525
485
  or not web.receive_mode.uploads_in_progress
@@ -528,6 +488,11 @@ def main(cwd=None):
528
488
  web.stop(app.port)
529
489
  break
530
490
  web.receive_mode.can_upload = False
491
+ else:
492
+ # website or chat mode
493
+ print("Stopped because auto-stop timer ran out")
494
+ web.stop(app.port)
495
+ break
531
496
  # Allow KeyboardInterrupt exception to be handled with threads
532
497
  # https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception
533
498
  time.sleep(0.2)
@@ -0,0 +1,200 @@
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 requests
21
+
22
+ from .meek import MeekNotRunning
23
+
24
+
25
+ class CensorshipCircumvention(object):
26
+ """
27
+ Connect to the Tor Moat APIs to retrieve censorship
28
+ circumvention recommendations or the latest bridges.
29
+
30
+ We support reaching this API over Tor, or Meek
31
+ (domain fronting) if Tor is not connected.
32
+ """
33
+
34
+ def __init__(self, common, meek=None, onion=None):
35
+ """
36
+ Set up the CensorshipCircumvention object to hold
37
+ common and meek objects.
38
+ """
39
+ self.common = common
40
+ self.common.log("CensorshipCircumvention", "__init__")
41
+ self.api_proxies = {}
42
+ if meek:
43
+ self.meek = meek
44
+ if not self.meek.meek_proxies:
45
+ raise MeekNotRunning()
46
+ else:
47
+ self.common.log(
48
+ "CensorshipCircumvention",
49
+ "__init__",
50
+ "Using Meek with CensorShipCircumvention API",
51
+ )
52
+ self.api_proxies = self.meek.meek_proxies
53
+ if onion:
54
+ self.onion = onion
55
+ if not self.onion.is_authenticated:
56
+ return False
57
+ else:
58
+ self.common.log(
59
+ "CensorshipCircumvention",
60
+ "__init__",
61
+ "Using Tor with CensorShipCircumvention API",
62
+ )
63
+ (socks_address, socks_port) = self.onion.get_tor_socks_port()
64
+ self.api_proxies = {
65
+ "http": f"socks5h://{socks_address}:{socks_port}",
66
+ "https": f"socks5h://{socks_address}:{socks_port}",
67
+ }
68
+
69
+ def request_map(self, country=False):
70
+ """
71
+ Retrieves the Circumvention map from Tor Project and store it
72
+ locally for further look-ups if required.
73
+
74
+ Optionally pass a country code in order to get recommended settings
75
+ just for that country.
76
+
77
+ Note that this API endpoint doesn't return actual bridges,
78
+ it just returns the recommended bridge type countries.
79
+ """
80
+ if not self.api_proxies:
81
+ return False
82
+ endpoint = "https://bridges.torproject.org/moat/circumvention/map"
83
+ data = {}
84
+ if country:
85
+ data = {"country": country}
86
+
87
+ r = requests.post(
88
+ endpoint,
89
+ json=data,
90
+ headers={"Content-Type": "application/vnd.api+json"},
91
+ proxies=self.api_proxies,
92
+ )
93
+ if r.status_code != 200:
94
+ self.common.log(
95
+ "CensorshipCircumvention",
96
+ "censorship_obtain_map",
97
+ f"status_code={r.status_code}",
98
+ )
99
+ return False
100
+
101
+ result = r.json()
102
+
103
+ if "errors" in result:
104
+ self.common.log(
105
+ "CensorshipCircumvention",
106
+ "censorship_obtain_map",
107
+ f"errors={result['errors']}",
108
+ )
109
+ return False
110
+
111
+ return result
112
+
113
+ def request_settings(self, country=False, transports=False):
114
+ """
115
+ Retrieves the Circumvention Settings from Tor Project, which
116
+ will return recommended settings based on the country code of
117
+ the requesting IP.
118
+
119
+ Optionally, a country code can be specified in order to override
120
+ the IP detection.
121
+
122
+ Optionally, a list of transports can be specified in order to
123
+ return recommended settings for just that transport type.
124
+ """
125
+ if not self.api_proxies:
126
+ return False
127
+ endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
128
+ data = {}
129
+ if country:
130
+ data = {"country": country}
131
+ if transports:
132
+ data.append({"transports": transports})
133
+ r = requests.post(
134
+ endpoint,
135
+ json=data,
136
+ headers={"Content-Type": "application/vnd.api+json"},
137
+ proxies=self.api_proxies,
138
+ )
139
+ if r.status_code != 200:
140
+ self.common.log(
141
+ "CensorshipCircumvention",
142
+ "censorship_obtain_settings",
143
+ f"status_code={r.status_code}",
144
+ )
145
+ return False
146
+
147
+ result = r.json()
148
+
149
+ if "errors" in result:
150
+ self.common.log(
151
+ "CensorshipCircumvention",
152
+ "censorship_obtain_settings",
153
+ f"errors={result['errors']}",
154
+ )
155
+ return False
156
+
157
+ # There are no settings - perhaps this country doesn't require censorship circumvention?
158
+ # This is not really an error, so we can just check if False and assume direct Tor
159
+ # connection will work.
160
+ if not "settings" in result:
161
+ self.common.log(
162
+ "CensorshipCircumvention",
163
+ "censorship_obtain_settings",
164
+ "No settings found for this country",
165
+ )
166
+ return False
167
+
168
+ return result
169
+
170
+ def request_builtin_bridges(self):
171
+ """
172
+ Retrieves the list of built-in bridges from the Tor Project.
173
+ """
174
+ if not self.api_proxies:
175
+ return False
176
+ endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
177
+ r = requests.post(
178
+ endpoint,
179
+ headers={"Content-Type": "application/vnd.api+json"},
180
+ proxies=self.api_proxies,
181
+ )
182
+ if r.status_code != 200:
183
+ self.common.log(
184
+ "CensorshipCircumvention",
185
+ "censorship_obtain_builtin_bridges",
186
+ f"status_code={r.status_code}",
187
+ )
188
+ return False
189
+
190
+ result = r.json()
191
+
192
+ if "errors" in result:
193
+ self.common.log(
194
+ "CensorshipCircumvention",
195
+ "censorship_obtain_builtin_bridges",
196
+ f"errors={result['errors']}",
197
+ )
198
+ return False
199
+
200
+ return result