onionshare_cli 2.3__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 (45) hide show
  1. onionshare_cli/__init__.py +522 -0
  2. onionshare_cli/common.py +296 -0
  3. onionshare_cli/mode_settings.py +146 -0
  4. onionshare_cli/onion.py +845 -0
  5. onionshare_cli/onionshare.py +110 -0
  6. onionshare_cli/resources/static/css/style.css +393 -0
  7. onionshare_cli/resources/static/img/ajax.gif +0 -0
  8. onionshare_cli/resources/static/img/favicon.ico +0 -0
  9. onionshare_cli/resources/static/img/logo.png +0 -0
  10. onionshare_cli/resources/static/img/logo_large.png +0 -0
  11. onionshare_cli/resources/static/img/web_file.png +0 -0
  12. onionshare_cli/resources/static/img/web_folder.png +0 -0
  13. onionshare_cli/resources/static/js/chat.js +173 -0
  14. onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
  15. onionshare_cli/resources/static/js/receive.js +130 -0
  16. onionshare_cli/resources/static/js/send.js +75 -0
  17. onionshare_cli/resources/static/js/socket.io.min.js +3 -0
  18. onionshare_cli/resources/templates/401.html +21 -0
  19. onionshare_cli/resources/templates/403.html +21 -0
  20. onionshare_cli/resources/templates/404.html +21 -0
  21. onionshare_cli/resources/templates/405.html +21 -0
  22. onionshare_cli/resources/templates/chat.html +46 -0
  23. onionshare_cli/resources/templates/denied.html +14 -0
  24. onionshare_cli/resources/templates/listing.html +59 -0
  25. onionshare_cli/resources/templates/receive.html +46 -0
  26. onionshare_cli/resources/templates/send.html +69 -0
  27. onionshare_cli/resources/templates/thankyou.html +27 -0
  28. onionshare_cli/resources/torrc_template +8 -0
  29. onionshare_cli/resources/torrc_template-meek_lite_amazon +2 -0
  30. onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
  31. onionshare_cli/resources/torrc_template-obfs4 +27 -0
  32. onionshare_cli/resources/version.txt +1 -0
  33. onionshare_cli/resources/wordlist.txt +7776 -0
  34. onionshare_cli/settings.py +201 -0
  35. onionshare_cli/web/__init__.py +21 -0
  36. onionshare_cli/web/chat_mode.py +169 -0
  37. onionshare_cli/web/receive_mode.py +488 -0
  38. onionshare_cli/web/send_base_mode.py +338 -0
  39. onionshare_cli/web/share_mode.py +406 -0
  40. onionshare_cli/web/web.py +424 -0
  41. onionshare_cli/web/website_mode.py +123 -0
  42. onionshare_cli-2.3.dist-info/METADATA +34 -0
  43. onionshare_cli-2.3.dist-info/RECORD +45 -0
  44. onionshare_cli-2.3.dist-info/WHEEL +4 -0
  45. onionshare_cli-2.3.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,338 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OnionShare | https://onionshare.org/
4
+
5
+ Copyright (C) 2014-2020 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, render_template, make_response
27
+ from unidecode import unidecode
28
+ from werkzeug.urls import url_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.gzip_filename = None
46
+ self.gzip_filesize = None
47
+ self.zip_writer = None
48
+
49
+ # If autostop_sharing, only allow one download at a time
50
+ self.download_in_progress = False
51
+
52
+ # This tracks the history id
53
+ self.cur_history_id = 0
54
+
55
+ self.define_routes()
56
+ self.init()
57
+
58
+ def set_file_info(self, filenames, processed_size_callback=None):
59
+ """
60
+ Build a data structure that describes the list of files
61
+ """
62
+ # If there's just one folder, replace filenames with a list of files inside that folder
63
+ if len(filenames) == 1 and os.path.isdir(filenames[0]):
64
+ filenames = [
65
+ os.path.join(filenames[0], x) for x in os.listdir(filenames[0])
66
+ ]
67
+
68
+ # Re-initialize
69
+ self.files = {} # Dictionary mapping file paths to filenames on disk
70
+ self.root_files = (
71
+ {}
72
+ ) # This is only the root files and dirs, as opposed to all of them
73
+ self.cleanup_filenames = []
74
+ self.cur_history_id = 0
75
+ self.file_info = {"files": [], "dirs": []}
76
+ self.gzip_individual_files = {}
77
+ self.init()
78
+
79
+ # Build the file list
80
+ for filename in filenames:
81
+ basename = os.path.basename(filename.rstrip("/"))
82
+
83
+ # If it's a filename, add it
84
+ if os.path.isfile(filename):
85
+ self.files[basename] = filename
86
+ self.root_files[basename] = filename
87
+
88
+ # If it's a directory, add it recursively
89
+ elif os.path.isdir(filename):
90
+ self.root_files[basename] = filename
91
+
92
+ for root, _, nested_filenames in os.walk(filename):
93
+ # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
94
+ # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
95
+ # The normalized_root should be "some_folder/foobar"
96
+ normalized_root = os.path.join(
97
+ basename, root[len(filename) :].lstrip("/")
98
+ ).rstrip("/")
99
+
100
+ # Add the dir itself
101
+ self.files[normalized_root] = root
102
+
103
+ # Add the files in this dir
104
+ for nested_filename in nested_filenames:
105
+ self.files[
106
+ os.path.join(normalized_root, nested_filename)
107
+ ] = os.path.join(root, nested_filename)
108
+
109
+ self.set_file_info_custom(filenames, processed_size_callback)
110
+
111
+ def directory_listing(self, filenames, path="", filesystem_path=None):
112
+ # Tell the GUI about the directory listing
113
+ history_id = self.cur_history_id
114
+ self.cur_history_id += 1
115
+ self.web.add_request(
116
+ self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
117
+ f"/{path}",
118
+ {"id": history_id, "method": request.method, "status_code": 200},
119
+ )
120
+
121
+ breadcrumbs = [("☗", "/")]
122
+ parts = path.split("/")
123
+ if parts[-1] == "":
124
+ parts = parts[:-1]
125
+ for i in range(len(parts)):
126
+ breadcrumbs.append((parts[i], f"/{'/'.join(parts[0 : i + 1])}"))
127
+ breadcrumbs_leaf = breadcrumbs.pop()[0]
128
+
129
+ # If filesystem_path is None, this is the root directory listing
130
+ files, dirs = self.build_directory_listing(path, filenames, filesystem_path)
131
+ r = self.directory_listing_template(
132
+ path, files, dirs, breadcrumbs, breadcrumbs_leaf
133
+ )
134
+ return self.web.add_security_headers(r)
135
+
136
+ def build_directory_listing(self, path, filenames, filesystem_path):
137
+ files = []
138
+ dirs = []
139
+
140
+ for filename in filenames:
141
+ if filesystem_path:
142
+ this_filesystem_path = os.path.join(filesystem_path, filename)
143
+ else:
144
+ this_filesystem_path = self.files[filename]
145
+
146
+ is_dir = os.path.isdir(this_filesystem_path)
147
+
148
+ if is_dir:
149
+ dirs.append(
150
+ {"link": os.path.join(f"/{path}", filename), "basename": filename}
151
+ )
152
+ else:
153
+ size = os.path.getsize(this_filesystem_path)
154
+ size_human = self.common.human_readable_filesize(size)
155
+ files.append(
156
+ {
157
+ "link": os.path.join(f"/{path}", filename),
158
+ "basename": filename,
159
+ "size_human": size_human,
160
+ }
161
+ )
162
+
163
+ return files, dirs
164
+
165
+ def stream_individual_file(self, filesystem_path):
166
+ """
167
+ Return a flask response that's streaming the download of an individual file, and gzip
168
+ compressing it if the browser supports it.
169
+ """
170
+ use_gzip = self.should_use_gzip()
171
+
172
+ # gzip compress the individual file, if it hasn't already been compressed
173
+ if use_gzip:
174
+ if filesystem_path not in self.gzip_individual_files:
175
+ gzip_filename = tempfile.mkstemp("wb+")[1]
176
+ self._gzip_compress(filesystem_path, gzip_filename, 6, None)
177
+ self.gzip_individual_files[filesystem_path] = gzip_filename
178
+
179
+ # Make sure the gzip file gets cleaned up when onionshare stops
180
+ self.cleanup_filenames.append(gzip_filename)
181
+
182
+ file_to_download = self.gzip_individual_files[filesystem_path]
183
+ filesize = os.path.getsize(self.gzip_individual_files[filesystem_path])
184
+ else:
185
+ file_to_download = filesystem_path
186
+ filesize = os.path.getsize(filesystem_path)
187
+
188
+ path = request.path
189
+
190
+ # Tell GUI the individual file started
191
+ history_id = self.cur_history_id
192
+ self.cur_history_id += 1
193
+
194
+ # Only GET requests are allowed, any other method should fail
195
+ if request.method != "GET":
196
+ return self.web.error405(history_id)
197
+
198
+ self.web.add_request(
199
+ self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
200
+ path,
201
+ {"id": history_id, "filesize": filesize},
202
+ )
203
+
204
+ def generate():
205
+ chunk_size = 102400 # 100kb
206
+
207
+ fp = open(file_to_download, "rb")
208
+ done = False
209
+ while not done:
210
+ chunk = fp.read(chunk_size)
211
+ if chunk == b"":
212
+ done = True
213
+ else:
214
+ try:
215
+ yield chunk
216
+
217
+ # Tell GUI the progress
218
+ downloaded_bytes = fp.tell()
219
+ percent = (1.0 * downloaded_bytes / filesize) * 100
220
+ if (
221
+ not self.web.is_gui
222
+ or self.common.platform == "Linux"
223
+ or self.common.platform == "BSD"
224
+ ):
225
+ sys.stdout.write(
226
+ "\r{0:s}, {1:.2f}% ".format(
227
+ self.common.human_readable_filesize(
228
+ downloaded_bytes
229
+ ),
230
+ percent,
231
+ )
232
+ )
233
+ sys.stdout.flush()
234
+
235
+ self.web.add_request(
236
+ self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS,
237
+ path,
238
+ {
239
+ "id": history_id,
240
+ "bytes": downloaded_bytes,
241
+ "filesize": filesize,
242
+ },
243
+ )
244
+ done = False
245
+ except:
246
+ # Looks like the download was canceled
247
+ done = True
248
+
249
+ # Tell the GUI the individual file was canceled
250
+ self.web.add_request(
251
+ self.web.REQUEST_INDIVIDUAL_FILE_CANCELED,
252
+ path,
253
+ {"id": history_id},
254
+ )
255
+
256
+ fp.close()
257
+
258
+ if self.common.platform != "Darwin":
259
+ sys.stdout.write("\n")
260
+
261
+ basename = os.path.basename(filesystem_path)
262
+
263
+ r = Response(generate())
264
+ if use_gzip:
265
+ r.headers.set("Content-Encoding", "gzip")
266
+ r.headers.set("Content-Length", filesize)
267
+ filename_dict = {
268
+ 'filename': unidecode(basename),
269
+ 'filename*': "UTF-8''%s" % url_quote(basename)
270
+ }
271
+ r.headers.set("Content-Disposition", "inline", **filename_dict)
272
+ r = self.web.add_security_headers(r)
273
+ (content_type, _) = mimetypes.guess_type(basename, strict=False)
274
+ if content_type is not None:
275
+ r.headers.set("Content-Type", content_type)
276
+ return r
277
+
278
+ def should_use_gzip(self):
279
+ """
280
+ Should we use gzip for this browser?
281
+ """
282
+ return (not self.is_zipped) and (
283
+ "gzip" in request.headers.get("Accept-Encoding", "").lower()
284
+ )
285
+
286
+ def _gzip_compress(
287
+ self, input_filename, output_filename, level, processed_size_callback=None
288
+ ):
289
+ """
290
+ Compress a file with gzip, without loading the whole thing into memory
291
+ Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror
292
+ """
293
+ bytes_processed = 0
294
+ blocksize = 1 << 16 # 64kB
295
+ with open(input_filename, "rb") as input_file:
296
+ output_file = gzip.open(output_filename, "wb", level)
297
+ while True:
298
+ if processed_size_callback is not None:
299
+ processed_size_callback(bytes_processed)
300
+
301
+ block = input_file.read(blocksize)
302
+ if len(block) == 0:
303
+ break
304
+ output_file.write(block)
305
+ bytes_processed += blocksize
306
+
307
+ output_file.close()
308
+
309
+ def init(self):
310
+ """
311
+ Inherited class will implement this
312
+ """
313
+ pass
314
+
315
+ def define_routes(self):
316
+ """
317
+ Inherited class will implement this
318
+ """
319
+ pass
320
+
321
+ def directory_listing_template(self):
322
+ """
323
+ Inherited class will implement this. It should call render_template and return
324
+ the response.
325
+ """
326
+ pass
327
+
328
+ def set_file_info_custom(self, filenames, processed_size_callback):
329
+ """
330
+ Inherited class will implement this.
331
+ """
332
+ pass
333
+
334
+ def render_logic(self, path=""):
335
+ """
336
+ Inherited class will implement this.
337
+ """
338
+ pass