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.
- onionshare_cli/__init__.py +522 -0
- onionshare_cli/common.py +296 -0
- onionshare_cli/mode_settings.py +146 -0
- onionshare_cli/onion.py +845 -0
- onionshare_cli/onionshare.py +110 -0
- onionshare_cli/resources/static/css/style.css +393 -0
- onionshare_cli/resources/static/img/ajax.gif +0 -0
- onionshare_cli/resources/static/img/favicon.ico +0 -0
- onionshare_cli/resources/static/img/logo.png +0 -0
- onionshare_cli/resources/static/img/logo_large.png +0 -0
- onionshare_cli/resources/static/img/web_file.png +0 -0
- onionshare_cli/resources/static/img/web_folder.png +0 -0
- onionshare_cli/resources/static/js/chat.js +173 -0
- onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
- onionshare_cli/resources/static/js/receive.js +130 -0
- onionshare_cli/resources/static/js/send.js +75 -0
- onionshare_cli/resources/static/js/socket.io.min.js +3 -0
- onionshare_cli/resources/templates/401.html +21 -0
- onionshare_cli/resources/templates/403.html +21 -0
- onionshare_cli/resources/templates/404.html +21 -0
- onionshare_cli/resources/templates/405.html +21 -0
- onionshare_cli/resources/templates/chat.html +46 -0
- onionshare_cli/resources/templates/denied.html +14 -0
- onionshare_cli/resources/templates/listing.html +59 -0
- onionshare_cli/resources/templates/receive.html +46 -0
- onionshare_cli/resources/templates/send.html +69 -0
- onionshare_cli/resources/templates/thankyou.html +27 -0
- onionshare_cli/resources/torrc_template +8 -0
- onionshare_cli/resources/torrc_template-meek_lite_amazon +2 -0
- onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
- onionshare_cli/resources/torrc_template-obfs4 +27 -0
- onionshare_cli/resources/version.txt +1 -0
- onionshare_cli/resources/wordlist.txt +7776 -0
- onionshare_cli/settings.py +201 -0
- onionshare_cli/web/__init__.py +21 -0
- onionshare_cli/web/chat_mode.py +169 -0
- onionshare_cli/web/receive_mode.py +488 -0
- onionshare_cli/web/send_base_mode.py +338 -0
- onionshare_cli/web/share_mode.py +406 -0
- onionshare_cli/web/web.py +424 -0
- onionshare_cli/web/website_mode.py +123 -0
- onionshare_cli-2.3.dist-info/METADATA +34 -0
- onionshare_cli-2.3.dist-info/RECORD +45 -0
- onionshare_cli-2.3.dist-info/WHEEL +4 -0
- 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()
|