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,406 @@
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 zipfile
25
+ import mimetypes
26
+ from flask import Response, request, render_template, make_response
27
+ from unidecode import unidecode
28
+ from werkzeug.urls import url_quote
29
+
30
+ from .send_base_mode import SendBaseModeWeb
31
+
32
+
33
+ class ShareModeWeb(SendBaseModeWeb):
34
+ """
35
+ All of the web logic for share mode
36
+ """
37
+
38
+ def init(self):
39
+ self.common.log("ShareModeWeb", "init")
40
+
41
+ # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked
42
+ self.download_individual_files = not self.web.settings.get(
43
+ "share", "autostop_sharing"
44
+ )
45
+
46
+ def define_routes(self):
47
+ """
48
+ The web app routes for sharing files
49
+ """
50
+
51
+ @self.web.app.route("/", defaults={"path": ""})
52
+ @self.web.app.route("/<path:path>")
53
+ def index(path):
54
+ """
55
+ Render the template for the onionshare landing page.
56
+ """
57
+ self.web.add_request(self.web.REQUEST_LOAD, request.path)
58
+
59
+ # Deny new downloads if "Stop sharing after files have been sent" is checked and there is
60
+ # currently a download
61
+ deny_download = (
62
+ self.web.settings.get("share", "autostop_sharing")
63
+ and self.download_in_progress
64
+ )
65
+ if deny_download:
66
+ r = make_response(render_template("denied.html"))
67
+ return self.web.add_security_headers(r)
68
+
69
+ # If download is allowed to continue, serve download page
70
+ if self.should_use_gzip():
71
+ self.filesize = self.gzip_filesize
72
+ else:
73
+ self.filesize = self.download_filesize
74
+
75
+ return self.render_logic(path)
76
+
77
+ @self.web.app.route("/download")
78
+ def download():
79
+ """
80
+ Download the zip file.
81
+ """
82
+ # Deny new downloads if "Stop After First Download" is checked and there is
83
+ # currently a download
84
+ deny_download = (
85
+ self.web.settings.get("share", "autostop_sharing")
86
+ and self.download_in_progress
87
+ )
88
+ if deny_download:
89
+ r = make_response(render_template("denied.html"))
90
+ return self.web.add_security_headers(r)
91
+
92
+ # Prepare some variables to use inside generate() function below
93
+ # which is outside of the request context
94
+ shutdown_func = request.environ.get("werkzeug.server.shutdown")
95
+ path = request.path
96
+
97
+ # If this is a zipped file, then serve as-is. If it's not zipped, then,
98
+ # if the http client supports gzip compression, gzip the file first
99
+ # and serve that
100
+ use_gzip = self.should_use_gzip()
101
+ if use_gzip:
102
+ file_to_download = self.gzip_filename
103
+ self.filesize = self.gzip_filesize
104
+ else:
105
+ file_to_download = self.download_filename
106
+ self.filesize = self.download_filesize
107
+
108
+ # Tell GUI the download started
109
+ history_id = self.cur_history_id
110
+ self.cur_history_id += 1
111
+ self.web.add_request(
112
+ self.web.REQUEST_STARTED, path, {"id": history_id, "use_gzip": use_gzip}
113
+ )
114
+
115
+ basename = os.path.basename(self.download_filename)
116
+
117
+ def generate():
118
+ # Starting a new download
119
+ if self.web.settings.get("share", "autostop_sharing"):
120
+ self.download_in_progress = True
121
+
122
+ chunk_size = 102400 # 100kb
123
+
124
+ fp = open(file_to_download, "rb")
125
+ self.web.done = False
126
+ canceled = False
127
+ while not self.web.done:
128
+ # The user has canceled the download, so stop serving the file
129
+ if not self.web.stop_q.empty():
130
+ self.web.add_request(
131
+ self.web.REQUEST_CANCELED, path, {"id": history_id}
132
+ )
133
+ break
134
+
135
+ chunk = fp.read(chunk_size)
136
+ if chunk == b"":
137
+ self.web.done = True
138
+ else:
139
+ try:
140
+ yield chunk
141
+
142
+ # tell GUI the progress
143
+ downloaded_bytes = fp.tell()
144
+ percent = (1.0 * downloaded_bytes / self.filesize) * 100
145
+
146
+ # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
147
+ if (
148
+ not self.web.is_gui
149
+ or self.common.platform == "Linux"
150
+ or self.common.platform == "BSD"
151
+ ):
152
+ sys.stdout.write(
153
+ "\r{0:s}, {1:.2f}% ".format(
154
+ self.common.human_readable_filesize(
155
+ downloaded_bytes
156
+ ),
157
+ percent,
158
+ )
159
+ )
160
+ sys.stdout.flush()
161
+
162
+ self.web.add_request(
163
+ self.web.REQUEST_PROGRESS,
164
+ path,
165
+ {"id": history_id, "bytes": downloaded_bytes},
166
+ )
167
+ self.web.done = False
168
+ except:
169
+ # looks like the download was canceled
170
+ self.web.done = True
171
+ canceled = True
172
+
173
+ # tell the GUI the download has canceled
174
+ self.web.add_request(
175
+ self.web.REQUEST_CANCELED, path, {"id": history_id}
176
+ )
177
+
178
+ fp.close()
179
+
180
+ if self.common.platform != "Darwin":
181
+ sys.stdout.write("\n")
182
+
183
+ # Download is finished
184
+ if self.web.settings.get("share", "autostop_sharing"):
185
+ self.download_in_progress = False
186
+
187
+ # Close the server, if necessary
188
+ if self.web.settings.get("share", "autostop_sharing") and not canceled:
189
+ print("Stopped because transfer is complete")
190
+ self.web.running = False
191
+ try:
192
+ if shutdown_func is None:
193
+ raise RuntimeError("Not running with the Werkzeug Server")
194
+ shutdown_func()
195
+ except:
196
+ pass
197
+
198
+ r = Response(generate())
199
+ if use_gzip:
200
+ r.headers.set("Content-Encoding", "gzip")
201
+ r.headers.set("Content-Length", self.filesize)
202
+ filename_dict = {
203
+ "filename": unidecode(basename),
204
+ "filename*": "UTF-8''%s" % url_quote(basename),
205
+ }
206
+ r.headers.set("Content-Disposition", "attachment", **filename_dict)
207
+ r = self.web.add_security_headers(r)
208
+ # guess content type
209
+ (content_type, _) = mimetypes.guess_type(basename, strict=False)
210
+ if content_type is not None:
211
+ r.headers.set("Content-Type", content_type)
212
+ return r
213
+
214
+ def directory_listing_template(
215
+ self, path, files, dirs, breadcrumbs, breadcrumbs_leaf
216
+ ):
217
+ return make_response(
218
+ render_template(
219
+ "send.html",
220
+ file_info=self.file_info,
221
+ files=files,
222
+ dirs=dirs,
223
+ breadcrumbs=breadcrumbs,
224
+ breadcrumbs_leaf=breadcrumbs_leaf,
225
+ filename=os.path.basename(self.download_filename),
226
+ filesize=self.filesize,
227
+ filesize_human=self.common.human_readable_filesize(
228
+ self.download_filesize
229
+ ),
230
+ is_zipped=self.is_zipped,
231
+ static_url_path=self.web.static_url_path,
232
+ download_individual_files=self.download_individual_files,
233
+ )
234
+ )
235
+
236
+ def set_file_info_custom(self, filenames, processed_size_callback):
237
+ self.common.log("ShareModeWeb", "set_file_info_custom")
238
+ self.web.cancel_compression = False
239
+ self.build_zipfile_list(filenames, processed_size_callback)
240
+
241
+ def render_logic(self, path=""):
242
+ if path in self.files:
243
+ filesystem_path = self.files[path]
244
+
245
+ # If it's a directory
246
+ if os.path.isdir(filesystem_path):
247
+ # Render directory listing
248
+ filenames = []
249
+ for filename in os.listdir(filesystem_path):
250
+ if os.path.isdir(os.path.join(filesystem_path, filename)):
251
+ filenames.append(filename + "/")
252
+ else:
253
+ filenames.append(filename)
254
+ filenames.sort()
255
+ return self.directory_listing(filenames, path, filesystem_path)
256
+
257
+ # If it's a file
258
+ elif os.path.isfile(filesystem_path):
259
+ if self.download_individual_files:
260
+ return self.stream_individual_file(filesystem_path)
261
+ else:
262
+ history_id = self.cur_history_id
263
+ self.cur_history_id += 1
264
+ return self.web.error404(history_id)
265
+
266
+ # If it's not a directory or file, throw a 404
267
+ else:
268
+ history_id = self.cur_history_id
269
+ self.cur_history_id += 1
270
+ return self.web.error404(history_id)
271
+ else:
272
+ # Special case loading /
273
+
274
+ if path == "":
275
+ # Root directory listing
276
+ filenames = list(self.root_files)
277
+ filenames.sort()
278
+ return self.directory_listing(filenames, path)
279
+
280
+ else:
281
+ # If the path isn't found, throw a 404
282
+ history_id = self.cur_history_id
283
+ self.cur_history_id += 1
284
+ return self.web.error404(history_id)
285
+
286
+ def build_zipfile_list(self, filenames, processed_size_callback=None):
287
+ self.common.log("ShareModeWeb", "build_zipfile_list")
288
+ for filename in filenames:
289
+ info = {
290
+ "filename": filename,
291
+ "basename": os.path.basename(filename.rstrip("/")),
292
+ }
293
+ if os.path.isfile(filename):
294
+ info["size"] = os.path.getsize(filename)
295
+ info["size_human"] = self.common.human_readable_filesize(info["size"])
296
+ self.file_info["files"].append(info)
297
+ if os.path.isdir(filename):
298
+ info["size"] = self.common.dir_size(filename)
299
+ info["size_human"] = self.common.human_readable_filesize(info["size"])
300
+ self.file_info["dirs"].append(info)
301
+ self.file_info["files"].sort(key=lambda k: k["basename"])
302
+ self.file_info["dirs"].sort(key=lambda k: k["basename"])
303
+
304
+ # Check if there's only 1 file and no folders
305
+ if len(self.file_info["files"]) == 1 and len(self.file_info["dirs"]) == 0:
306
+ self.download_filename = self.file_info["files"][0]["filename"]
307
+ self.download_filesize = self.file_info["files"][0]["size"]
308
+
309
+ # Compress the file with gzip now, so we don't have to do it on each request
310
+ self.gzip_filename = tempfile.mkstemp("wb+")[1]
311
+ self._gzip_compress(
312
+ self.download_filename, self.gzip_filename, 6, processed_size_callback
313
+ )
314
+ self.gzip_filesize = os.path.getsize(self.gzip_filename)
315
+
316
+ # Make sure the gzip file gets cleaned up when onionshare stops
317
+ self.cleanup_filenames.append(self.gzip_filename)
318
+
319
+ self.is_zipped = False
320
+
321
+ else:
322
+ # Zip up the files and folders
323
+ self.zip_writer = ZipWriter(
324
+ self.common, processed_size_callback=processed_size_callback
325
+ )
326
+ self.download_filename = self.zip_writer.zip_filename
327
+ for info in self.file_info["files"]:
328
+ self.zip_writer.add_file(info["filename"])
329
+ # Canceling early?
330
+ if self.web.cancel_compression:
331
+ self.zip_writer.close()
332
+ return False
333
+
334
+ for info in self.file_info["dirs"]:
335
+ if not self.zip_writer.add_dir(info["filename"]):
336
+ return False
337
+
338
+ self.zip_writer.close()
339
+ self.download_filesize = os.path.getsize(self.download_filename)
340
+
341
+ # Make sure the zip file gets cleaned up when onionshare stops
342
+ self.cleanup_filenames.append(self.zip_writer.zip_filename)
343
+
344
+ self.is_zipped = True
345
+
346
+ return True
347
+
348
+
349
+ class ZipWriter(object):
350
+ """
351
+ ZipWriter accepts files and directories and compresses them into a zip file
352
+ with. If a zip_filename is not passed in, it will use the default onionshare
353
+ filename.
354
+ """
355
+
356
+ def __init__(self, common, zip_filename=None, processed_size_callback=None):
357
+ self.common = common
358
+ self.cancel_compression = False
359
+
360
+ if zip_filename:
361
+ self.zip_filename = zip_filename
362
+ else:
363
+ self.zip_filename = (
364
+ f"{tempfile.mkdtemp()}/onionshare_{self.common.random_string(4, 6)}.zip"
365
+ )
366
+
367
+ self.z = zipfile.ZipFile(self.zip_filename, "w", allowZip64=True)
368
+ self.processed_size_callback = processed_size_callback
369
+ if self.processed_size_callback is None:
370
+ self.processed_size_callback = lambda _: None
371
+ self._size = 0
372
+ self.processed_size_callback(self._size)
373
+
374
+ def add_file(self, filename):
375
+ """
376
+ Add a file to the zip archive.
377
+ """
378
+ self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
379
+ self._size += os.path.getsize(filename)
380
+ self.processed_size_callback(self._size)
381
+
382
+ def add_dir(self, filename):
383
+ """
384
+ Add a directory, and all of its children, to the zip archive.
385
+ """
386
+ dir_to_strip = os.path.dirname(filename.rstrip("/")) + "/"
387
+ for dirpath, dirnames, filenames in os.walk(filename):
388
+ for f in filenames:
389
+ # Canceling early?
390
+ if self.cancel_compression:
391
+ return False
392
+
393
+ full_filename = os.path.join(dirpath, f)
394
+ if not os.path.islink(full_filename):
395
+ arc_filename = full_filename[len(dir_to_strip) :]
396
+ self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
397
+ self._size += os.path.getsize(full_filename)
398
+ self.processed_size_callback(self._size)
399
+
400
+ return True
401
+
402
+ def close(self):
403
+ """
404
+ Close the zip archive.
405
+ """
406
+ self.z.close()