onionshare_cli 2.7__tar.gz → 2.7.1__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 (46) hide show
  1. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/PKG-INFO +1 -1
  2. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/__init__.py +6 -2
  3. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/censorship.py +4 -2
  4. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/common.py +28 -0
  5. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/meek.py +12 -2
  6. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/onion.py +9 -3
  7. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/settings.py +2 -2
  8. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/share_mode.py +13 -32
  9. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/pyproject.toml +1 -1
  10. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/mode_settings.py +0 -0
  11. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/onionshare.py +0 -0
  12. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/css/style.css +0 -0
  13. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/ajax.gif +0 -0
  14. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/favicon.ico +0 -0
  15. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/logo.png +0 -0
  16. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/logo_large.png +0 -0
  17. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/web_file.png +0 -0
  18. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/img/web_folder.png +0 -0
  19. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/js/chat.js +0 -0
  20. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/js/jquery-3.7.1.slim.min.js +0 -0
  21. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/js/receive.js +0 -0
  22. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/js/send.js +0 -0
  23. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/static/js/socket.io.min.js +0 -0
  24. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/403.html +0 -0
  25. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/404.html +0 -0
  26. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/405.html +0 -0
  27. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/413.html +0 -0
  28. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/500.html +0 -0
  29. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/chat.html +0 -0
  30. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/denied.html +0 -0
  31. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/listing.html +0 -0
  32. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/receive.html +0 -0
  33. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/send.html +0 -0
  34. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/templates/thankyou.html +0 -0
  35. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/torrc_template +0 -0
  36. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/torrc_template-meek +0 -0
  37. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/torrc_template-obfs4 +0 -0
  38. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/torrc_template-snowflake +0 -0
  39. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/torrc_template-webtunnel +0 -0
  40. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/resources/wordlist.txt +0 -0
  41. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/__init__.py +0 -0
  42. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/chat_mode.py +0 -0
  43. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/receive_mode.py +0 -0
  44. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/send_base_mode.py +0 -0
  45. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/web.py +0 -0
  46. {onionshare_cli-2.7 → onionshare_cli-2.7.1}/onionshare_cli/web/website_mode.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: onionshare_cli
3
- Version: 2.7
3
+ Version: 2.7.1
4
4
  Summary: Securely and anonymously share files, host websites, and chat with friends using the Tor network
5
5
  License: GPL-3.0-or-later
6
6
  Keywords: onion,share,onionshare,tor,anonymous,web server
@@ -470,8 +470,12 @@ def main(cwd=None):
470
470
  sys.exit(1)
471
471
 
472
472
  if mode == "share":
473
- # Prepare files to share
474
- print("Compressing files.")
473
+ # Prepare files to share. A single file is served as-is; only multiple
474
+ # files or folders are compressed into a zip file.
475
+ if len(filenames) == 1 and os.path.isfile(filenames[0]):
476
+ print("Preparing file.")
477
+ else:
478
+ print("Compressing files.")
475
479
  try:
476
480
  web.share_mode.set_file_info(filenames)
477
481
  except OSError as e:
@@ -53,8 +53,10 @@ class CensorshipCircumvention(object):
53
53
  self.api_proxies = self.meek.meek_proxies
54
54
  if onion:
55
55
  self.onion = onion
56
- if not self.onion.is_authenticated:
57
- return False
56
+ if not self.onion.is_authenticated():
57
+ # Tor isn't connected, so leave api_proxies empty. The request_*
58
+ # methods refuse to run without a proxy, so nothing is sent.
59
+ return
58
60
  else:
59
61
  self.common.log(
60
62
  "CensorshipCircumvention",
@@ -673,6 +673,34 @@ class Common:
673
673
  eta = remaining_bytes / download_rate
674
674
  return Common.format_seconds(eta)
675
675
 
676
+ def drain_subprocess_output(self, stream, module):
677
+ """
678
+ Continuously read a subprocess's output pipe on a daemon thread, logging
679
+ each line, until the subprocess closes it.
680
+
681
+ A subprocess that writes to a pipe nobody reads will block as soon as the
682
+ pipe's buffer fills up. For tor this freezes the whole process, including
683
+ its control port, so every stem call against it hangs. The buffer is only
684
+ about 4 KiB on Windows (64 KiB on Linux), small enough that tor's log
685
+ output while trying several obfs4 bridges is enough to fill it.
686
+ """
687
+
688
+ def drain():
689
+ try:
690
+ for line in stream:
691
+ if isinstance(line, bytes):
692
+ line = line.decode("utf-8", errors="replace")
693
+ line = line.rstrip()
694
+ if line:
695
+ self.log(module, "output", line)
696
+ except (OSError, ValueError):
697
+ # The pipe was closed underneath us, e.g. during cleanup
698
+ pass
699
+
700
+ t = threading.Thread(target=drain, daemon=True)
701
+ t.start()
702
+ return t
703
+
676
704
  @staticmethod
677
705
  def get_available_port(min_port, max_port):
678
706
  """
@@ -120,8 +120,16 @@ class Meek(object):
120
120
  universal_newlines=True,
121
121
  )
122
122
 
123
- # Obtain the host and port that meek is running on
124
- for line in iter(self.meek_proc.stdout.readline, b""):
123
+ # meek-client logs to stderr. Keep reading it so it can never fill the
124
+ # pipe and block meek-client (the buffer is only ~4 KiB on Windows).
125
+ self.common.drain_subprocess_output(self.meek_proc.stderr, "Meek")
126
+
127
+ # Obtain the host and port that meek is running on. Iterating the pipe
128
+ # ends at EOF, so if meek-client exits without reporting a port we fall
129
+ # through to MeekNotRunning below. (The pipe is in text mode, so the
130
+ # previous iter(readline, b"") sentinel never matched "" at EOF, and
131
+ # this loop spun forever.)
132
+ for line in self.meek_proc.stdout:
125
133
  if "CMETHOD meek socks5" in line:
126
134
  self.meek_host = line.split(" ")[3].split(":")[0]
127
135
  self.meek_port = line.split(" ")[3].split(":")[1].strip()
@@ -138,6 +146,8 @@ class Meek(object):
138
146
  break
139
147
 
140
148
  if self.meek_port:
149
+ # Keep draining stdout too, now that we've read the port from it
150
+ self.common.drain_subprocess_output(self.meek_proc.stdout, "Meek")
141
151
  self.meek_proxies = {
142
152
  "http": f"socks5h://{self.meek_host}:{self.meek_port}",
143
153
  "https": f"socks5h://{self.meek_host}:{self.meek_port}",
@@ -428,7 +428,7 @@ class Onion(object):
428
428
  self.tor_proc = subprocess.Popen(
429
429
  [self.tor_path, "-f", self.tor_torrc],
430
430
  stdout=subprocess.PIPE,
431
- stderr=subprocess.PIPE,
431
+ stderr=subprocess.STDOUT,
432
432
  startupinfo=startupinfo,
433
433
  )
434
434
  else:
@@ -440,10 +440,16 @@ class Onion(object):
440
440
  self.tor_proc = subprocess.Popen(
441
441
  [self.tor_path, "-f", self.tor_torrc],
442
442
  stdout=subprocess.PIPE,
443
- stderr=subprocess.PIPE,
443
+ stderr=subprocess.STDOUT,
444
444
  env=env,
445
445
  )
446
446
 
447
+ # tor logs to stdout. If nothing reads it, tor blocks once the pipe's
448
+ # buffer is full, and then stops answering on its control port too.
449
+ # On Windows that buffer is only ~4 KiB, which the log output from
450
+ # trying the built-in obfs4 bridges fills within seconds.
451
+ self.common.drain_subprocess_output(self.tor_proc.stdout, "Tor")
452
+
447
453
  # Wait for the tor controller to start
448
454
  self.common.log("Onion", "connect", f"tor pid: {self.tor_proc.pid}")
449
455
  time.sleep(2)
@@ -992,7 +998,7 @@ class Onion(object):
992
998
  builtin_bridges = False
993
999
  meek = None
994
1000
  # Try obtaining bridges over Tor, if we're connected to it.
995
- if self.is_authenticated:
1001
+ if self.is_authenticated():
996
1002
  self.common.log(
997
1003
  "Onion",
998
1004
  "update_builtin_bridges",
@@ -57,7 +57,7 @@ class Settings(object):
57
57
  self.available_locales = {
58
58
  #"af": "Afrikaans", # Afrikaans
59
59
  #"sq": "Shqip", # Albanian
60
- #"ar": "العربية", # Arabic
60
+ "ar": "العربية", # Arabic
61
61
  #"be": "Беларуская", # Belarusian
62
62
  #"bn": "বাংলা", # Bengali
63
63
  #"bg": "Български", #Bulgarian
@@ -94,7 +94,7 @@ class Settings(object):
94
94
  #"sk": "Slovenčina", # Slovak
95
95
  #"es": "Español", # Spanish
96
96
  #"sw": "Kiswahili", # Swahili
97
- #"sv": "Svenska", # Swedish
97
+ "sv": "Svenska", # Swedish
98
98
  #"ta": "Tamil", # Tamil
99
99
  # "te": "తెలుగు", # Telugu
100
100
  "tr": "Türkçe", # Turkish
@@ -126,13 +126,15 @@ class ShareModeWeb(SendBaseModeWeb):
126
126
  )
127
127
 
128
128
  self.download_etag = None
129
- self.gzip_etag = None
130
129
  self.last_modified = datetime.now(tz=timezone.utc)
131
130
 
132
131
  def define_routes(self):
133
132
  """
134
133
  The web app routes for sharing files
135
134
  """
135
+ # Range offsets must refer to the original file, including when a browser
136
+ # resumes a download. Do not let Flask-Compress transform streamed files.
137
+ self.web.app.config["COMPRESS_STREAMS"] = False
136
138
 
137
139
  @self.web.app.route(
138
140
  "/", defaults={"path": ""}, methods=["GET"], provide_automatic_options=False
@@ -156,10 +158,7 @@ class ShareModeWeb(SendBaseModeWeb):
156
158
  return render_template("denied.html")
157
159
 
158
160
  # If download is allowed to continue, serve download page
159
- if self.should_use_gzip():
160
- self.filesize = self.gzip_filesize
161
- else:
162
- self.filesize = self.download_filesize
161
+ self.filesize = self.download_filesize
163
162
 
164
163
  return self.render_logic(path)
165
164
 
@@ -183,18 +182,11 @@ class ShareModeWeb(SendBaseModeWeb):
183
182
  # which is outside of the request context
184
183
  request_path = request.path
185
184
 
186
- # If this is a zipped file, then serve as-is. If it's not zipped, then,
187
- # if the http client supports gzip compression, gzip the file first
188
- # and serve that
189
- use_gzip = self.should_use_gzip()
190
- if use_gzip:
191
- file_to_download = self.gzip_filename
192
- self.filesize = self.gzip_filesize
193
- etag = self.gzip_etag
194
- else:
195
- file_to_download = self.download_filename
196
- self.filesize = self.download_filesize
197
- etag = self.download_etag
185
+ # Serve the same bytes on initial and resumed downloads. Browsers
186
+ # resume using offsets in the decoded file, not a gzip representation.
187
+ file_to_download = self.download_filename
188
+ self.filesize = self.download_filesize
189
+ etag = self.download_etag
198
190
 
199
191
  # for range requests
200
192
  range_, status_code = self.get_range_and_status_code(
@@ -207,7 +199,7 @@ class ShareModeWeb(SendBaseModeWeb):
207
199
  self.web.add_request(
208
200
  self.web.REQUEST_STARTED,
209
201
  request_path,
210
- {"id": history_id, "use_gzip": use_gzip},
202
+ {"id": history_id},
211
203
  )
212
204
 
213
205
  basename = os.path.basename(self.download_filename)
@@ -225,9 +217,6 @@ class ShareModeWeb(SendBaseModeWeb):
225
217
  )
226
218
  )
227
219
 
228
- if use_gzip:
229
- r.headers.set("Content-Encoding", "gzip")
230
-
231
220
  r.headers.set("Content-Length", range_[1] - range_[0] + 1)
232
221
  filename_dict = {
233
222
  "filename": unidecode(basename),
@@ -498,23 +487,15 @@ class ShareModeWeb(SendBaseModeWeb):
498
487
  with open(self.download_filename, "rb") as f:
499
488
  self.download_etag = make_etag(f)
500
489
 
501
- # Compress the file with gzip now, so we don't have to do it on each request
490
+ # Individual-file views store gzipped copies of shared files here.
491
+ # Delete the directory when the share stops.
502
492
  self.gzip_tmp_dir = tempfile.TemporaryDirectory(
503
493
  dir=self.common.build_tmp_dir()
504
494
  )
505
- self.gzip_filename = os.path.join(self.gzip_tmp_dir.name, "file.gz")
506
- self._gzip_compress(
507
- self.download_filename, self.gzip_filename, 6, processed_size_callback
508
- )
509
- self.gzip_filesize = os.path.getsize(self.gzip_filename)
510
- with open(self.gzip_filename, "rb") as f:
511
- self.gzip_etag = make_etag(f)
495
+ self.web.cleanup_tempdirs.append(self.gzip_tmp_dir)
512
496
 
513
497
  self.is_zipped = False
514
498
 
515
- # Cleanup this tempfile
516
- self.web.cleanup_tempdirs.append(self.gzip_tmp_dir)
517
-
518
499
  else:
519
500
  # Zip up the files and folders
520
501
  self.zip_writer = ZipWriter(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "onionshare_cli"
3
- version = "2.7"
3
+ version = "2.7.1"
4
4
  description = "Securely and anonymously share files, host websites, and chat with friends using the Tor network"
5
5
  requires-python = ">=3.10,<4.0"
6
6
  authors = [