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,360 @@
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
+
21
+ import os
22
+ import sys
23
+ import tempfile
24
+ import mimetypes
25
+ import gzip
26
+ from flask import Response, request
27
+ from unidecode import unidecode
28
+ from urllib.parse import quote
29
+
30
+
31
+ class SendBaseModeWeb:
32
+ """
33
+ All of the web logic shared between share and website mode (modes where the user sends files)
34
+ """
35
+
36
+ def __init__(self, common, web):
37
+ super(SendBaseModeWeb, self).__init__()
38
+ self.common = common
39
+ self.web = web
40
+
41
+ # Information about the file to be shared
42
+ self.is_zipped = False
43
+ self.download_filename = None
44
+ self.download_filesize = None
45
+ self.zip_writer = None
46
+
47
+ # Create a temporary dir to store gzip files in
48
+ self.gzip_tmp_dir = tempfile.TemporaryDirectory(dir=self.common.build_tmp_dir())
49
+ self.gzip_counter = 0
50
+
51
+ # If autostop_sharing, only allow one download at a time
52
+ self.download_in_progress = False
53
+
54
+ # This tracks the history id
55
+ self.cur_history_id = 0
56
+
57
+ # Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED
58
+ # and maybe other events when requests come in to this mode
59
+ self.supports_file_requests = True
60
+
61
+ self.define_routes()
62
+ self.init()
63
+
64
+ def fix_windows_paths(self, path):
65
+ """
66
+ If on Windows, replace backslashes with slashes
67
+ """
68
+ if self.common.platform == "Windows":
69
+ return path.replace("\\", "/")
70
+
71
+ return path
72
+
73
+ def set_file_info(self, filenames, processed_size_callback=None):
74
+ """
75
+ Build a data structure that describes the list of files
76
+ """
77
+ # If there's just one folder, replace filenames with a list of files inside that folder
78
+ if len(filenames) == 1 and os.path.isdir(filenames[0]):
79
+ filenames = [
80
+ os.path.join(filenames[0], x) for x in os.listdir(filenames[0])
81
+ ]
82
+
83
+ # Re-initialize
84
+ self.files = {} # Dictionary mapping file paths to filenames on disk
85
+ self.root_files = (
86
+ {}
87
+ ) # This is only the root files and dirs, as opposed to all of them
88
+ self.cur_history_id = 0
89
+ self.file_info = {"files": [], "dirs": []}
90
+ self.gzip_individual_files = {}
91
+ self.init()
92
+
93
+ # Windows paths use backslashes, but website paths use forward slashes. We have to
94
+ # make sure we're stripping the correct type of slash
95
+ if self.common.platform == "Windows":
96
+ slash = "\\"
97
+ else:
98
+ slash = "/"
99
+
100
+ # Build the file list
101
+ for filename in filenames:
102
+ basename = os.path.basename(filename.rstrip(slash))
103
+
104
+ # If it's a filename, add it
105
+ if os.path.isfile(filename):
106
+ self.files[self.fix_windows_paths(basename)] = filename
107
+ self.root_files[self.fix_windows_paths(basename)] = filename
108
+
109
+ # If it's a directory, add it recursively
110
+ elif os.path.isdir(filename):
111
+ self.root_files[self.fix_windows_paths(basename)] = filename
112
+
113
+ for root, _, nested_filenames in os.walk(filename):
114
+ # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
115
+ # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
116
+ # The normalized_root should be "some_folder/foobar"
117
+ normalized_root = os.path.join(
118
+ basename, root[len(filename) :].lstrip(slash)
119
+ ).rstrip(slash)
120
+
121
+ # Add the dir itself
122
+ self.files[self.fix_windows_paths(normalized_root)] = root
123
+
124
+ # Add the files in this dir
125
+ for nested_filename in nested_filenames:
126
+ self.files[
127
+ self.fix_windows_paths(
128
+ os.path.join(normalized_root, nested_filename)
129
+ )
130
+ ] = os.path.join(root, nested_filename)
131
+
132
+ self.set_file_info_custom(filenames, processed_size_callback)
133
+
134
+ def directory_listing(self, filenames, path="", filesystem_path=None, add_trailing_slash=False):
135
+ # Tell the GUI about the directory listing
136
+ history_id = self.cur_history_id
137
+ self.cur_history_id += 1
138
+ self.web.add_request(
139
+ self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
140
+ f"/{path}",
141
+ {"id": history_id, "method": request.method, "status_code": 200},
142
+ )
143
+
144
+ breadcrumbs = [("☗", "/")]
145
+ parts = path.split("/")
146
+ if parts[-1] == "":
147
+ parts = parts[:-1]
148
+ for i in range(len(parts)):
149
+ breadcrumbs.append((parts[i], f"/{'/'.join(parts[0 : i + 1])}"))
150
+ breadcrumbs_leaf = breadcrumbs.pop()[0]
151
+
152
+ # If filesystem_path is None, this is the root directory listing
153
+ files, dirs = self.build_directory_listing(path, filenames, filesystem_path, add_trailing_slash)
154
+ return self.directory_listing_template(
155
+ path, files, dirs, breadcrumbs, breadcrumbs_leaf
156
+ )
157
+
158
+ def build_directory_listing(self, path, filenames, filesystem_path, add_trailing_slash=False):
159
+ files = []
160
+ dirs = []
161
+
162
+ for filename in filenames:
163
+ if filesystem_path:
164
+ this_filesystem_path = os.path.join(filesystem_path, filename)
165
+ else:
166
+ this_filesystem_path = self.files[filename]
167
+
168
+ is_dir = os.path.isdir(this_filesystem_path)
169
+
170
+ if is_dir:
171
+ if add_trailing_slash:
172
+ dirs.append(
173
+ {"link": os.path.join(f"/{path}", filename, ""), "basename": filename}
174
+ )
175
+ else:
176
+ dirs.append(
177
+ {"link": os.path.join(f"/{path}", filename), "basename": filename}
178
+ )
179
+ else:
180
+ size = os.path.getsize(this_filesystem_path)
181
+ size_human = self.common.human_readable_filesize(size)
182
+ files.append(
183
+ {
184
+ "link": os.path.join(f"/{path}", filename),
185
+ "basename": filename,
186
+ "size_human": size_human,
187
+ }
188
+ )
189
+
190
+ return files, dirs
191
+
192
+ def stream_individual_file(self, filesystem_path):
193
+ """
194
+ Return a flask response that's streaming the download of an individual file, and gzip
195
+ compressing it if the browser supports it.
196
+ """
197
+ use_gzip = self.should_use_gzip()
198
+
199
+ # gzip compress the individual file, if it hasn't already been compressed
200
+ if use_gzip:
201
+ if filesystem_path not in self.gzip_individual_files:
202
+ gzip_filename = os.path.join(
203
+ self.gzip_tmp_dir.name, str(self.gzip_counter)
204
+ )
205
+ self.gzip_counter += 1
206
+ self._gzip_compress(filesystem_path, gzip_filename, 6, None)
207
+ self.gzip_individual_files[filesystem_path] = gzip_filename
208
+
209
+ file_to_download = self.gzip_individual_files[filesystem_path]
210
+ filesize = os.path.getsize(self.gzip_individual_files[filesystem_path])
211
+ else:
212
+ file_to_download = filesystem_path
213
+ filesize = os.path.getsize(filesystem_path)
214
+
215
+ path = request.path
216
+
217
+ # Tell GUI the individual file started
218
+ history_id = self.cur_history_id
219
+ self.cur_history_id += 1
220
+
221
+ self.web.add_request(
222
+ self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
223
+ path,
224
+ {"id": history_id, "filesize": filesize},
225
+ )
226
+
227
+ def generate():
228
+ chunk_size = 102400 # 100kb
229
+
230
+ fp = open(file_to_download, "rb")
231
+ done = False
232
+ while not done:
233
+ chunk = fp.read(chunk_size)
234
+ if chunk == b"":
235
+ done = True
236
+ else:
237
+ try:
238
+ yield chunk
239
+
240
+ # Tell GUI the progress
241
+ downloaded_bytes = fp.tell()
242
+ percent = (1.0 * downloaded_bytes / filesize) * 100
243
+ if (
244
+ not self.web.is_gui
245
+ or self.common.platform == "Linux"
246
+ or self.common.platform == "BSD"
247
+ ):
248
+ sys.stdout.write(
249
+ "\r{0:s}, {1:.2f}% ".format(
250
+ self.common.human_readable_filesize(
251
+ downloaded_bytes
252
+ ),
253
+ percent,
254
+ )
255
+ )
256
+ sys.stdout.flush()
257
+
258
+ self.web.add_request(
259
+ self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS,
260
+ path,
261
+ {
262
+ "id": history_id,
263
+ "bytes": downloaded_bytes,
264
+ "filesize": filesize,
265
+ },
266
+ )
267
+ done = False
268
+ except Exception:
269
+ # Looks like the download was canceled
270
+ done = True
271
+
272
+ # Tell the GUI the individual file was canceled
273
+ self.web.add_request(
274
+ self.web.REQUEST_INDIVIDUAL_FILE_CANCELED,
275
+ path,
276
+ {"id": history_id},
277
+ )
278
+
279
+ fp.close()
280
+
281
+ if self.common.platform != "Darwin":
282
+ sys.stdout.write("\n")
283
+
284
+ basename = os.path.basename(filesystem_path)
285
+
286
+ r = Response(generate())
287
+ if use_gzip:
288
+ r.headers.set("Content-Encoding", "gzip")
289
+ r.headers.set("Content-Length", filesize)
290
+ filename_dict = {
291
+ "filename": unidecode(basename),
292
+ "filename*": "UTF-8''%s" % quote(basename),
293
+ }
294
+ r.headers.set("Content-Disposition", "inline", **filename_dict)
295
+ (content_type, _) = mimetypes.guess_type(basename, strict=False)
296
+ if content_type is not None:
297
+ r.headers.set("Content-Type", content_type)
298
+ return r
299
+
300
+ def should_use_gzip(self):
301
+ """
302
+ Should we use gzip for this browser?
303
+ """
304
+ return (not self.is_zipped) and (
305
+ "gzip" in request.headers.get("Accept-Encoding", "").lower()
306
+ )
307
+
308
+ def _gzip_compress(
309
+ self, input_filename, output_filename, level, processed_size_callback=None
310
+ ):
311
+ """
312
+ Compress a file with gzip, without loading the whole thing into memory
313
+ Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror
314
+ """
315
+ bytes_processed = 0
316
+ blocksize = 1 << 16 # 64kB
317
+ with open(input_filename, "rb") as input_file:
318
+ output_file = gzip.open(output_filename, "wb", level)
319
+ while True:
320
+ if processed_size_callback is not None:
321
+ processed_size_callback(bytes_processed)
322
+
323
+ block = input_file.read(blocksize)
324
+ if len(block) == 0:
325
+ break
326
+ output_file.write(block)
327
+ bytes_processed += blocksize
328
+
329
+ output_file.close()
330
+
331
+ def init(self):
332
+ """
333
+ Inherited class will implement this
334
+ """
335
+ pass
336
+
337
+ def define_routes(self):
338
+ """
339
+ Inherited class will implement this
340
+ """
341
+ pass
342
+
343
+ def directory_listing_template(self):
344
+ """
345
+ Inherited class will implement this. It should call render_template and return
346
+ the response.
347
+ """
348
+ pass
349
+
350
+ def set_file_info_custom(self, filenames, processed_size_callback):
351
+ """
352
+ Inherited class will implement this.
353
+ """
354
+ pass
355
+
356
+ def render_logic(self, path=""):
357
+ """
358
+ Inherited class will implement this.
359
+ """
360
+ pass