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,488 @@
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 tempfile
23
+ import json
24
+ from datetime import datetime
25
+ from flask import Request, request, render_template, make_response, flash, redirect
26
+ from werkzeug.utils import secure_filename
27
+
28
+
29
+ class ReceiveModeWeb:
30
+ """
31
+ All of the web logic for receive mode
32
+ """
33
+
34
+ def __init__(self, common, web):
35
+ self.common = common
36
+ self.common.log("ReceiveModeWeb", "__init__")
37
+
38
+ self.web = web
39
+
40
+ self.can_upload = True
41
+ self.uploads_in_progress = []
42
+
43
+ # This tracks the history id
44
+ self.cur_history_id = 0
45
+
46
+ self.define_routes()
47
+
48
+ def define_routes(self):
49
+ """
50
+ The web app routes for receiving files
51
+ """
52
+
53
+ @self.web.app.route("/")
54
+ def index():
55
+ history_id = self.cur_history_id
56
+ self.cur_history_id += 1
57
+ self.web.add_request(
58
+ self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
59
+ request.path,
60
+ {"id": history_id, "status_code": 200},
61
+ )
62
+
63
+ self.web.add_request(self.web.REQUEST_LOAD, request.path)
64
+ r = make_response(
65
+ render_template(
66
+ "receive.html", static_url_path=self.web.static_url_path
67
+ )
68
+ )
69
+ return self.web.add_security_headers(r)
70
+
71
+ @self.web.app.route("/upload", methods=["POST"])
72
+ def upload(ajax=False):
73
+ """
74
+ Handle the upload files POST request, though at this point, the files have
75
+ already been uploaded and saved to their correct locations.
76
+ """
77
+ files = request.files.getlist("file[]")
78
+ filenames = []
79
+ for f in files:
80
+ if f.filename != "":
81
+ filename = secure_filename(f.filename)
82
+ filenames.append(filename)
83
+ local_path = os.path.join(request.receive_mode_dir, filename)
84
+ basename = os.path.basename(local_path)
85
+
86
+ # Tell the GUI the receive mode directory for this file
87
+ self.web.add_request(
88
+ self.web.REQUEST_UPLOAD_SET_DIR,
89
+ request.path,
90
+ {
91
+ "id": request.history_id,
92
+ "filename": basename,
93
+ "dir": request.receive_mode_dir,
94
+ },
95
+ )
96
+
97
+ self.common.log(
98
+ "ReceiveModeWeb",
99
+ "define_routes",
100
+ f"/upload, uploaded {f.filename}, saving to {local_path}",
101
+ )
102
+ print(f"\nReceived: {local_path}")
103
+
104
+ if request.upload_error:
105
+ self.common.log(
106
+ "ReceiveModeWeb",
107
+ "define_routes",
108
+ "/upload, there was an upload error",
109
+ )
110
+
111
+ self.web.add_request(
112
+ self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
113
+ request.path,
114
+ {"receive_mode_dir": request.receive_mode_dir},
115
+ )
116
+ print(
117
+ f"Could not create OnionShare data folder: {request.receive_mode_dir}"
118
+ )
119
+
120
+ msg = "Error uploading, please inform the OnionShare user"
121
+ if ajax:
122
+ return json.dumps({"error_flashes": [msg]})
123
+ else:
124
+ flash(msg, "error")
125
+ return redirect("/")
126
+
127
+ if ajax:
128
+ info_flashes = []
129
+
130
+ if len(filenames) == 0:
131
+ msg = "No files uploaded"
132
+ if ajax:
133
+ info_flashes.append(msg)
134
+ else:
135
+ flash(msg, "info")
136
+ else:
137
+ msg = "Sent "
138
+ for filename in filenames:
139
+ msg += f"{filename}, "
140
+ msg = msg.rstrip(", ")
141
+ if ajax:
142
+ info_flashes.append(msg)
143
+ else:
144
+ flash(msg, "info")
145
+
146
+ if self.can_upload:
147
+ if ajax:
148
+ return json.dumps({"info_flashes": info_flashes})
149
+ else:
150
+ return redirect("/")
151
+ else:
152
+ if ajax:
153
+ return json.dumps(
154
+ {
155
+ "new_body": render_template(
156
+ "thankyou.html",
157
+ static_url_path=self.web.static_url_path,
158
+ )
159
+ }
160
+ )
161
+ else:
162
+ # It was the last upload and the timer ran out
163
+ r = make_response(
164
+ render_template("thankyou.html"),
165
+ static_url_path=self.web.static_url_path,
166
+ )
167
+ return self.web.add_security_headers(r)
168
+
169
+ @self.web.app.route("/upload-ajax", methods=["POST"])
170
+ def upload_ajax_public():
171
+ if not self.can_upload:
172
+ return self.web.error403()
173
+ return upload(ajax=True)
174
+
175
+
176
+ class ReceiveModeWSGIMiddleware(object):
177
+ """
178
+ Custom WSGI middleware in order to attach the Web object to environ, so
179
+ ReceiveModeRequest can access it.
180
+ """
181
+
182
+ def __init__(self, app, web):
183
+ self.app = app
184
+ self.web = web
185
+
186
+ def __call__(self, environ, start_response):
187
+ environ["web"] = self.web
188
+ environ["stop_q"] = self.web.stop_q
189
+ return self.app(environ, start_response)
190
+
191
+
192
+ class ReceiveModeFile(object):
193
+ """
194
+ A custom file object that tells ReceiveModeRequest every time data gets
195
+ written to it, in order to track the progress of uploads. It starts out with
196
+ a .part file extension, and when it's complete it removes that extension.
197
+ """
198
+
199
+ def __init__(self, request, filename, write_func, close_func):
200
+ self.onionshare_request = request
201
+ self.onionshare_filename = filename
202
+ self.onionshare_write_func = write_func
203
+ self.onionshare_close_func = close_func
204
+
205
+ self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename)
206
+ self.filename_in_progress = f"{self.filename}.part"
207
+
208
+ # Open the file
209
+ self.upload_error = False
210
+ try:
211
+ self.f = open(self.filename_in_progress, "wb+")
212
+ except:
213
+ # This will only happen if someone is messing with the data dir while
214
+ # OnionShare is running, but if it does make sure to throw an error
215
+ self.upload_error = True
216
+ self.f = tempfile.TemporaryFile("wb+")
217
+
218
+ # Make all the file-like methods and attributes actually access the
219
+ # TemporaryFile, except for write
220
+ attrs = [
221
+ "closed",
222
+ "detach",
223
+ "fileno",
224
+ "flush",
225
+ "isatty",
226
+ "mode",
227
+ "name",
228
+ "peek",
229
+ "raw",
230
+ "read",
231
+ "read1",
232
+ "readable",
233
+ "readinto",
234
+ "readinto1",
235
+ "readline",
236
+ "readlines",
237
+ "seek",
238
+ "seekable",
239
+ "tell",
240
+ "truncate",
241
+ "writable",
242
+ "writelines",
243
+ ]
244
+ for attr in attrs:
245
+ setattr(self, attr, getattr(self.f, attr))
246
+
247
+ def write(self, b):
248
+ """
249
+ Custom write method that calls out to onionshare_write_func
250
+ """
251
+ if self.upload_error or (not self.onionshare_request.stop_q.empty()):
252
+ self.close()
253
+ self.onionshare_request.close()
254
+ return
255
+
256
+ try:
257
+ bytes_written = self.f.write(b)
258
+ self.onionshare_write_func(self.onionshare_filename, bytes_written)
259
+
260
+ except:
261
+ self.upload_error = True
262
+
263
+ def close(self):
264
+ """
265
+ Custom close method that calls out to onionshare_close_func
266
+ """
267
+ try:
268
+ self.f.close()
269
+
270
+ if not self.upload_error:
271
+ # Rename the in progress file to the final filename
272
+ os.rename(self.filename_in_progress, self.filename)
273
+
274
+ except:
275
+ self.upload_error = True
276
+
277
+ self.onionshare_close_func(self.onionshare_filename, self.upload_error)
278
+
279
+
280
+ class ReceiveModeRequest(Request):
281
+ """
282
+ A custom flask Request object that keeps track of how much data has been
283
+ uploaded for each file, for receive mode.
284
+ """
285
+
286
+ def __init__(self, environ, populate_request=True, shallow=False):
287
+ super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
288
+ self.web = environ["web"]
289
+ self.stop_q = environ["stop_q"]
290
+
291
+ self.web.common.log("ReceiveModeRequest", "__init__")
292
+
293
+ # Prevent running the close() method more than once
294
+ self.closed = False
295
+
296
+ # Is this a valid upload request?
297
+ self.upload_request = False
298
+ if self.method == "POST":
299
+ if self.path == "/upload" or self.path == "/upload-ajax":
300
+ self.upload_request = True
301
+
302
+ if self.upload_request:
303
+ # No errors yet
304
+ self.upload_error = False
305
+
306
+ # Figure out what files should be saved
307
+ now = datetime.now()
308
+ date_dir = now.strftime("%Y-%m-%d")
309
+ time_dir = now.strftime("%H.%M.%S")
310
+ self.receive_mode_dir = os.path.join(
311
+ self.web.settings.get("receive", "data_dir"), date_dir, time_dir
312
+ )
313
+
314
+ # Create that directory, which shouldn't exist yet
315
+ try:
316
+ os.makedirs(self.receive_mode_dir, 0o700, exist_ok=False)
317
+ except OSError:
318
+ # If this directory already exists, maybe someone else is uploading files at
319
+ # the same second, so use a different name in that case
320
+ if os.path.exists(self.receive_mode_dir):
321
+ # Keep going until we find a directory name that's available
322
+ i = 1
323
+ while True:
324
+ new_receive_mode_dir = f"{self.receive_mode_dir}-{i}"
325
+ try:
326
+ os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False)
327
+ self.receive_mode_dir = new_receive_mode_dir
328
+ break
329
+ except OSError:
330
+ pass
331
+ i += 1
332
+ # Failsafe
333
+ if i == 100:
334
+ self.web.common.log(
335
+ "ReceiveModeRequest",
336
+ "__init__",
337
+ "Error finding available receive mode directory",
338
+ )
339
+ self.upload_error = True
340
+ break
341
+ except PermissionError:
342
+ self.web.add_request(
343
+ self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
344
+ request.path,
345
+ {"receive_mode_dir": self.receive_mode_dir},
346
+ )
347
+ print(
348
+ f"Could not create OnionShare data folder: {self.receive_mode_dir}"
349
+ )
350
+ self.web.common.log(
351
+ "ReceiveModeRequest",
352
+ "__init__",
353
+ "Permission denied creating receive mode directory",
354
+ )
355
+ self.upload_error = True
356
+
357
+ # If there's an error so far, finish early
358
+ if self.upload_error:
359
+ return
360
+
361
+ # A dictionary that maps filenames to the bytes uploaded so far
362
+ self.progress = {}
363
+
364
+ # Prevent new uploads if we've said so (timer expired)
365
+ if self.web.receive_mode.can_upload:
366
+
367
+ # Create an history_id, attach it to the request
368
+ self.history_id = self.web.receive_mode.cur_history_id
369
+ self.web.receive_mode.cur_history_id += 1
370
+
371
+ # Figure out the content length
372
+ try:
373
+ self.content_length = int(self.headers["Content-Length"])
374
+ except:
375
+ self.content_length = 0
376
+
377
+ date_str = datetime.now().strftime("%b %d, %I:%M%p")
378
+ size_str = self.web.common.human_readable_filesize(self.content_length)
379
+ print(f"{date_str}: Upload of total size {size_str} is starting")
380
+
381
+ # Don't tell the GUI that a request has started until we start receiving files
382
+ self.told_gui_about_request = False
383
+
384
+ self.previous_file = None
385
+
386
+ def _get_file_stream(
387
+ self, total_content_length, content_type, filename=None, content_length=None
388
+ ):
389
+ """
390
+ This gets called for each file that gets uploaded, and returns an file-like
391
+ writable stream.
392
+ """
393
+ if self.upload_request:
394
+ if not self.told_gui_about_request:
395
+ # Tell the GUI about the request
396
+ self.web.add_request(
397
+ self.web.REQUEST_STARTED,
398
+ self.path,
399
+ {"id": self.history_id, "content_length": self.content_length},
400
+ )
401
+ self.web.receive_mode.uploads_in_progress.append(self.history_id)
402
+
403
+ self.told_gui_about_request = True
404
+
405
+ self.filename = secure_filename(filename)
406
+
407
+ self.progress[self.filename] = {"uploaded_bytes": 0, "complete": False}
408
+
409
+ f = ReceiveModeFile(
410
+ self, self.filename, self.file_write_func, self.file_close_func
411
+ )
412
+ if f.upload_error:
413
+ self.web.common.log(
414
+ "ReceiveModeRequest", "_get_file_stream", "Error creating file"
415
+ )
416
+ self.upload_error = True
417
+ return f
418
+
419
+ def close(self):
420
+ """
421
+ Closing the request.
422
+ """
423
+ super(ReceiveModeRequest, self).close()
424
+
425
+ # Prevent calling this method more than once per request
426
+ if self.closed:
427
+ return
428
+ self.closed = True
429
+
430
+ self.web.common.log("ReceiveModeRequest", "close")
431
+
432
+ try:
433
+ if self.told_gui_about_request:
434
+ history_id = self.history_id
435
+
436
+ if (
437
+ not self.web.stop_q.empty()
438
+ or not self.progress[self.filename]["complete"]
439
+ ):
440
+ # Inform the GUI that the upload has canceled
441
+ self.web.add_request(
442
+ self.web.REQUEST_UPLOAD_CANCELED, self.path, {"id": history_id}
443
+ )
444
+ else:
445
+ # Inform the GUI that the upload has finished
446
+ self.web.add_request(
447
+ self.web.REQUEST_UPLOAD_FINISHED, self.path, {"id": history_id}
448
+ )
449
+ self.web.receive_mode.uploads_in_progress.remove(history_id)
450
+
451
+ except AttributeError:
452
+ pass
453
+
454
+ def file_write_func(self, filename, length):
455
+ """
456
+ This function gets called when a specific file is written to.
457
+ """
458
+ if self.closed:
459
+ return
460
+
461
+ if self.upload_request:
462
+ self.progress[filename]["uploaded_bytes"] += length
463
+
464
+ if self.previous_file != filename:
465
+ self.previous_file = filename
466
+
467
+ size_str = self.web.common.human_readable_filesize(
468
+ self.progress[filename]["uploaded_bytes"]
469
+ )
470
+ print(f"\r=> {size_str} {filename} ", end="")
471
+
472
+ # Update the GUI on the upload progress
473
+ if self.told_gui_about_request:
474
+ self.web.add_request(
475
+ self.web.REQUEST_PROGRESS,
476
+ self.path,
477
+ {"id": self.history_id, "progress": self.progress},
478
+ )
479
+
480
+ def file_close_func(self, filename, upload_error=False):
481
+ """
482
+ This function gets called when a specific file is closed.
483
+ """
484
+ self.progress[filename]["complete"] = True
485
+
486
+ # If the file tells us there was an upload error, let the request know as well
487
+ if upload_error:
488
+ self.upload_error = True