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.
- onionshare_cli/__init__.py +538 -0
- onionshare_cli/censorship.py +311 -0
- onionshare_cli/common.py +624 -0
- onionshare_cli/meek.py +199 -0
- onionshare_cli/mode_settings.py +150 -0
- onionshare_cli/onion.py +964 -0
- onionshare_cli/onionshare.py +91 -0
- onionshare_cli/resources/static/css/style.css +432 -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 +188 -0
- onionshare_cli/resources/static/js/jquery-3.5.1.min.js +2 -0
- onionshare_cli/resources/static/js/receive.js +141 -0
- onionshare_cli/resources/static/js/send.js +79 -0
- onionshare_cli/resources/static/js/socket.io.js +9 -0
- onionshare_cli/resources/static/js/socket.io.min.js +7 -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/500.html +21 -0
- onionshare_cli/resources/templates/chat.html +47 -0
- onionshare_cli/resources/templates/denied.html +14 -0
- onionshare_cli/resources/templates/listing.html +60 -0
- onionshare_cli/resources/templates/receive.html +66 -0
- onionshare_cli/resources/templates/send.html +70 -0
- onionshare_cli/resources/templates/thankyou.html +27 -0
- onionshare_cli/resources/torrc_template +12 -0
- onionshare_cli/resources/torrc_template-meek_lite_azure +2 -0
- onionshare_cli/resources/torrc_template-obfs4 +12 -0
- onionshare_cli/resources/torrc_template-snowflake +3 -0
- onionshare_cli/resources/version.txt +1 -0
- onionshare_cli/resources/wordlist.txt +7776 -0
- onionshare_cli/settings.py +209 -0
- onionshare_cli/web/__init__.py +21 -0
- onionshare_cli/web/chat_mode.py +190 -0
- onionshare_cli/web/receive_mode.py +630 -0
- onionshare_cli/web/send_base_mode.py +360 -0
- onionshare_cli/web/share_mode.py +599 -0
- onionshare_cli/web/web.py +409 -0
- onionshare_cli/web/website_mode.py +122 -0
- onionshare_cli-2.6.1.dist-info/METADATA +44 -0
- onionshare_cli-2.6.1.dist-info/RECORD +48 -0
- onionshare_cli-2.6.1.dist-info/WHEEL +4 -0
- onionshare_cli-2.6.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,630 @@
|
|
|
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 tempfile
|
|
23
|
+
import json
|
|
24
|
+
import requests
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from flask import Request, request, render_template, make_response, flash, redirect
|
|
27
|
+
from werkzeug.utils import secure_filename
|
|
28
|
+
|
|
29
|
+
# Receive mode uses a special flask requests object, ReceiveModeRequest, in
|
|
30
|
+
# order to keep track of upload progress. Here's what happens when someone
|
|
31
|
+
# uploads files:
|
|
32
|
+
# - new ReceiveModeRequest object is created
|
|
33
|
+
# - ReceiveModeRequest.__init__
|
|
34
|
+
# - creates a directory based on the timestamp
|
|
35
|
+
# - creates empty self.progress = dict, which will map uploaded files to their upload progress
|
|
36
|
+
# - ReceiveModeRequest._get_file_stream
|
|
37
|
+
# - called for each file that gets upload
|
|
38
|
+
# - the first time, send REQUEST_STARTED to GUI, and append to self.web.receive_mode.uploads_in_progress
|
|
39
|
+
# - updates self.progress[self.filename] for the current file
|
|
40
|
+
# - uses custom ReceiveModeFile to save file to disk
|
|
41
|
+
# - ReceiveModeRequest.file_write_func called on each write
|
|
42
|
+
# - Display progress in CLI, and send REQUEST_PROGRESS to GUI
|
|
43
|
+
# - ReceiveModeRequest.file_close_func called when each file closes
|
|
44
|
+
# - self.progress[filename]["complete"] = True
|
|
45
|
+
# - ReceiveModeRequest.close
|
|
46
|
+
# - send either REQUEST_UPLOAD_CANCELED or REQUEST_UPLOAD_FINISHED to GUI
|
|
47
|
+
# - remove from self.web.receive_mode.uploads_in_progress
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReceiveModeWeb:
|
|
51
|
+
"""
|
|
52
|
+
All of the web logic for receive mode
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, common, web):
|
|
56
|
+
self.common = common
|
|
57
|
+
self.common.log("ReceiveModeWeb", "__init__")
|
|
58
|
+
|
|
59
|
+
self.web = web
|
|
60
|
+
|
|
61
|
+
self.can_upload = True
|
|
62
|
+
self.uploads_in_progress = []
|
|
63
|
+
|
|
64
|
+
# This tracks the history id
|
|
65
|
+
self.cur_history_id = 0
|
|
66
|
+
|
|
67
|
+
# Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED
|
|
68
|
+
# and maybe other events when requests come in to this mode
|
|
69
|
+
self.supports_file_requests = True
|
|
70
|
+
|
|
71
|
+
self.define_routes()
|
|
72
|
+
|
|
73
|
+
def define_routes(self):
|
|
74
|
+
"""
|
|
75
|
+
The web app routes for receiving files
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
@self.web.app.route("/", methods=["GET"], provide_automatic_options=False)
|
|
79
|
+
def index():
|
|
80
|
+
history_id = self.cur_history_id
|
|
81
|
+
self.cur_history_id += 1
|
|
82
|
+
self.web.add_request(
|
|
83
|
+
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
|
|
84
|
+
request.path,
|
|
85
|
+
{"id": history_id, "status_code": 200},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
|
89
|
+
return render_template(
|
|
90
|
+
"receive.html",
|
|
91
|
+
static_url_path=self.web.static_url_path,
|
|
92
|
+
disable_text=self.web.settings.get("receive", "disable_text"),
|
|
93
|
+
disable_files=self.web.settings.get("receive", "disable_files"),
|
|
94
|
+
title=self.web.settings.get("general", "title")
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@self.web.app.route("/upload", methods=["POST"], provide_automatic_options=False)
|
|
98
|
+
def upload(ajax=False):
|
|
99
|
+
"""
|
|
100
|
+
Handle the upload files POST request, though at this point, the files have
|
|
101
|
+
already been uploaded and saved to their correct locations.
|
|
102
|
+
"""
|
|
103
|
+
message_received = request.includes_message
|
|
104
|
+
|
|
105
|
+
files_received = 0
|
|
106
|
+
if not self.web.settings.get("receive", "disable_files"):
|
|
107
|
+
files = request.files.getlist("file[]")
|
|
108
|
+
|
|
109
|
+
filenames = []
|
|
110
|
+
for f in files:
|
|
111
|
+
if f.filename != "":
|
|
112
|
+
filename = secure_filename(f.filename)
|
|
113
|
+
filenames.append(filename)
|
|
114
|
+
local_path = os.path.join(request.receive_mode_dir, filename)
|
|
115
|
+
basename = os.path.basename(local_path)
|
|
116
|
+
|
|
117
|
+
# Tell the GUI the receive mode directory for this file
|
|
118
|
+
self.web.add_request(
|
|
119
|
+
self.web.REQUEST_UPLOAD_SET_DIR,
|
|
120
|
+
request.path,
|
|
121
|
+
{
|
|
122
|
+
"id": request.history_id,
|
|
123
|
+
"filename": basename,
|
|
124
|
+
"dir": request.receive_mode_dir,
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.common.log(
|
|
129
|
+
"ReceiveModeWeb",
|
|
130
|
+
"define_routes",
|
|
131
|
+
f"/upload, uploaded {f.filename}, saving to {local_path}",
|
|
132
|
+
)
|
|
133
|
+
print(f"Received: {local_path}")
|
|
134
|
+
|
|
135
|
+
files_received = len(filenames)
|
|
136
|
+
|
|
137
|
+
# Send webhook if configured
|
|
138
|
+
if (
|
|
139
|
+
self.web.settings.get("receive", "webhook_url") is not None
|
|
140
|
+
and not request.upload_error
|
|
141
|
+
and (message_received or files_received)
|
|
142
|
+
):
|
|
143
|
+
msg = ""
|
|
144
|
+
if files_received > 0:
|
|
145
|
+
if files_received == 1:
|
|
146
|
+
msg += "1 file"
|
|
147
|
+
else:
|
|
148
|
+
msg += f"{files_received} files"
|
|
149
|
+
if message_received:
|
|
150
|
+
if msg == "":
|
|
151
|
+
msg = "A text message"
|
|
152
|
+
else:
|
|
153
|
+
msg += " and a text message"
|
|
154
|
+
self.send_webhook_notification(f"{msg} submitted to OnionShare")
|
|
155
|
+
|
|
156
|
+
if request.upload_error:
|
|
157
|
+
self.common.log(
|
|
158
|
+
"ReceiveModeWeb",
|
|
159
|
+
"define_routes",
|
|
160
|
+
"/upload, there was an upload error",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.web.add_request(
|
|
164
|
+
self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
|
|
165
|
+
request.path,
|
|
166
|
+
{"receive_mode_dir": request.receive_mode_dir},
|
|
167
|
+
)
|
|
168
|
+
print(
|
|
169
|
+
f"Could not create OnionShare data folder: {request.receive_mode_dir}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
msg = "Error uploading, please inform the OnionShare user"
|
|
173
|
+
if ajax:
|
|
174
|
+
return json.dumps({"error_flashes": [msg]})
|
|
175
|
+
else:
|
|
176
|
+
flash(msg, "error")
|
|
177
|
+
return redirect("/")
|
|
178
|
+
|
|
179
|
+
if ajax:
|
|
180
|
+
info_flashes = []
|
|
181
|
+
|
|
182
|
+
if files_received > 0:
|
|
183
|
+
files_msg = ""
|
|
184
|
+
for filename in filenames:
|
|
185
|
+
files_msg += f"{filename}, "
|
|
186
|
+
files_msg = files_msg.rstrip(", ")
|
|
187
|
+
|
|
188
|
+
if message_received:
|
|
189
|
+
if files_received > 0:
|
|
190
|
+
msg = f"Message submitted, uploaded {files_msg}"
|
|
191
|
+
else:
|
|
192
|
+
msg = "Message submitted"
|
|
193
|
+
else:
|
|
194
|
+
if files_received > 0:
|
|
195
|
+
msg = f"Uploaded {files_msg}"
|
|
196
|
+
else:
|
|
197
|
+
msg = "Nothing submitted"
|
|
198
|
+
|
|
199
|
+
if ajax:
|
|
200
|
+
info_flashes.append(msg)
|
|
201
|
+
else:
|
|
202
|
+
flash(msg, "info")
|
|
203
|
+
|
|
204
|
+
if self.can_upload:
|
|
205
|
+
if ajax:
|
|
206
|
+
return json.dumps({"info_flashes": info_flashes})
|
|
207
|
+
else:
|
|
208
|
+
return redirect("/")
|
|
209
|
+
else:
|
|
210
|
+
if ajax:
|
|
211
|
+
return json.dumps(
|
|
212
|
+
{
|
|
213
|
+
"new_body": render_template(
|
|
214
|
+
"thankyou.html",
|
|
215
|
+
static_url_path=self.web.static_url_path,
|
|
216
|
+
title=self.web.settings.get("general", "title"),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
# It was the last upload and the timer ran out
|
|
222
|
+
return make_response(
|
|
223
|
+
render_template("thankyou.html"),
|
|
224
|
+
static_url_path=self.web.static_url_path,
|
|
225
|
+
title=self.web.settings.get("general", "title"),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@self.web.app.route("/upload-ajax", methods=["POST"], provide_automatic_options=False)
|
|
229
|
+
def upload_ajax_public():
|
|
230
|
+
if not self.can_upload:
|
|
231
|
+
return self.web.error403()
|
|
232
|
+
return upload(ajax=True)
|
|
233
|
+
|
|
234
|
+
def send_webhook_notification(self, data):
|
|
235
|
+
self.common.log("ReceiveModeWeb", "send_webhook_notification", data)
|
|
236
|
+
try:
|
|
237
|
+
requests.post(
|
|
238
|
+
self.web.settings.get("receive", "webhook_url"),
|
|
239
|
+
data=data,
|
|
240
|
+
timeout=5,
|
|
241
|
+
proxies=self.web.proxies,
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
print(f"Webhook notification failed: {e}")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ReceiveModeWSGIMiddleware(object):
|
|
248
|
+
"""
|
|
249
|
+
Custom WSGI middleware in order to attach the Web object to environ, so
|
|
250
|
+
ReceiveModeRequest can access it.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def __init__(self, app, web):
|
|
254
|
+
self.app = app
|
|
255
|
+
self.web = web
|
|
256
|
+
|
|
257
|
+
def __call__(self, environ, start_response):
|
|
258
|
+
environ["web"] = self.web
|
|
259
|
+
environ["stop_q"] = self.web.stop_q
|
|
260
|
+
return self.app(environ, start_response)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ReceiveModeFile(object):
|
|
264
|
+
"""
|
|
265
|
+
A custom file object that tells ReceiveModeRequest every time data gets
|
|
266
|
+
written to it, in order to track the progress of uploads. It starts out with
|
|
267
|
+
a .part file extension, and when it's complete it removes that extension.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(self, request, filename, write_func, close_func):
|
|
271
|
+
self.onionshare_request = request
|
|
272
|
+
self.onionshare_filename = filename
|
|
273
|
+
self.onionshare_write_func = write_func
|
|
274
|
+
self.onionshare_close_func = close_func
|
|
275
|
+
|
|
276
|
+
self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename)
|
|
277
|
+
self.filename_in_progress = f"{self.filename}.part"
|
|
278
|
+
|
|
279
|
+
# Open the file
|
|
280
|
+
self.upload_error = False
|
|
281
|
+
try:
|
|
282
|
+
self.f = open(self.filename_in_progress, "wb+")
|
|
283
|
+
except Exception:
|
|
284
|
+
# This will only happen if someone is messing with the data dir while
|
|
285
|
+
# OnionShare is running, but if it does make sure to throw an error
|
|
286
|
+
self.upload_error = True
|
|
287
|
+
self.f = tempfile.TemporaryFile("wb+")
|
|
288
|
+
|
|
289
|
+
# Make all the file-like methods and attributes actually access the
|
|
290
|
+
# TemporaryFile, except for write
|
|
291
|
+
attrs = [
|
|
292
|
+
"closed",
|
|
293
|
+
"detach",
|
|
294
|
+
"fileno",
|
|
295
|
+
"flush",
|
|
296
|
+
"isatty",
|
|
297
|
+
"mode",
|
|
298
|
+
"name",
|
|
299
|
+
"peek",
|
|
300
|
+
"raw",
|
|
301
|
+
"read",
|
|
302
|
+
"read1",
|
|
303
|
+
"readable",
|
|
304
|
+
"readinto",
|
|
305
|
+
"readinto1",
|
|
306
|
+
"readline",
|
|
307
|
+
"readlines",
|
|
308
|
+
"seek",
|
|
309
|
+
"seekable",
|
|
310
|
+
"tell",
|
|
311
|
+
"truncate",
|
|
312
|
+
"writable",
|
|
313
|
+
"writelines",
|
|
314
|
+
]
|
|
315
|
+
for attr in attrs:
|
|
316
|
+
setattr(self, attr, getattr(self.f, attr))
|
|
317
|
+
|
|
318
|
+
def write(self, b):
|
|
319
|
+
"""
|
|
320
|
+
Custom write method that calls out to onionshare_write_func
|
|
321
|
+
"""
|
|
322
|
+
if self.upload_error or (not self.onionshare_request.stop_q.empty()):
|
|
323
|
+
self.close()
|
|
324
|
+
self.onionshare_request.close()
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
bytes_written = self.f.write(b)
|
|
329
|
+
self.onionshare_write_func(self.onionshare_filename, bytes_written)
|
|
330
|
+
|
|
331
|
+
except Exception:
|
|
332
|
+
self.upload_error = True
|
|
333
|
+
|
|
334
|
+
def close(self):
|
|
335
|
+
"""
|
|
336
|
+
Custom close method that calls out to onionshare_close_func
|
|
337
|
+
"""
|
|
338
|
+
try:
|
|
339
|
+
self.f.close()
|
|
340
|
+
|
|
341
|
+
if not self.upload_error:
|
|
342
|
+
# Rename the in progress file to the final filename
|
|
343
|
+
os.rename(self.filename_in_progress, self.filename)
|
|
344
|
+
|
|
345
|
+
except Exception:
|
|
346
|
+
self.upload_error = True
|
|
347
|
+
|
|
348
|
+
self.onionshare_close_func(self.onionshare_filename, self.upload_error)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class ReceiveModeRequest(Request):
|
|
352
|
+
"""
|
|
353
|
+
A custom flask Request object that keeps track of how much data has been
|
|
354
|
+
uploaded for each file, for receive mode.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
def __init__(self, environ, populate_request=True, shallow=False):
|
|
358
|
+
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
|
359
|
+
self.web = environ["web"]
|
|
360
|
+
self.stop_q = environ["stop_q"]
|
|
361
|
+
self.filename = None
|
|
362
|
+
|
|
363
|
+
# Prevent running the close() method more than once
|
|
364
|
+
self.closed = False
|
|
365
|
+
|
|
366
|
+
# Is this a valid upload request?
|
|
367
|
+
self.upload_request = False
|
|
368
|
+
if self.method == "POST":
|
|
369
|
+
if self.path == "/upload" or self.path == "/upload-ajax":
|
|
370
|
+
self.upload_request = True
|
|
371
|
+
|
|
372
|
+
if self.upload_request:
|
|
373
|
+
self.web.common.log("ReceiveModeRequest", "__init__")
|
|
374
|
+
|
|
375
|
+
# No errors yet
|
|
376
|
+
self.upload_error = False
|
|
377
|
+
|
|
378
|
+
# Figure out what files should be saved
|
|
379
|
+
now = datetime.now()
|
|
380
|
+
date_dir = now.strftime("%Y-%m-%d")
|
|
381
|
+
time_dir = now.strftime("%H%M%S%f")
|
|
382
|
+
self.receive_mode_dir = os.path.join(
|
|
383
|
+
self.web.settings.get("receive", "data_dir"), date_dir, time_dir
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Create that directory, which shouldn't exist yet
|
|
387
|
+
try:
|
|
388
|
+
os.makedirs(self.receive_mode_dir, 0o700, exist_ok=False)
|
|
389
|
+
except OSError:
|
|
390
|
+
# If this directory already exists, maybe someone else is uploading files at
|
|
391
|
+
# the same second, so use a different name in that case
|
|
392
|
+
if os.path.exists(self.receive_mode_dir):
|
|
393
|
+
# Keep going until we find a directory name that's available
|
|
394
|
+
i = 1
|
|
395
|
+
while True:
|
|
396
|
+
new_receive_mode_dir = f"{self.receive_mode_dir}-{i}"
|
|
397
|
+
try:
|
|
398
|
+
os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False)
|
|
399
|
+
self.receive_mode_dir = new_receive_mode_dir
|
|
400
|
+
break
|
|
401
|
+
except OSError:
|
|
402
|
+
pass
|
|
403
|
+
i += 1
|
|
404
|
+
# Failsafe
|
|
405
|
+
if i == 100:
|
|
406
|
+
self.web.common.log(
|
|
407
|
+
"ReceiveModeRequest",
|
|
408
|
+
"__init__",
|
|
409
|
+
"Error finding available receive mode directory",
|
|
410
|
+
)
|
|
411
|
+
self.upload_error = True
|
|
412
|
+
break
|
|
413
|
+
except PermissionError:
|
|
414
|
+
self.web.add_request(
|
|
415
|
+
self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
|
|
416
|
+
request.path,
|
|
417
|
+
{"receive_mode_dir": self.receive_mode_dir},
|
|
418
|
+
)
|
|
419
|
+
print(
|
|
420
|
+
f"Could not create OnionShare data folder: {self.receive_mode_dir}"
|
|
421
|
+
)
|
|
422
|
+
self.web.common.log(
|
|
423
|
+
"ReceiveModeRequest",
|
|
424
|
+
"__init__",
|
|
425
|
+
"Permission denied creating receive mode directory",
|
|
426
|
+
)
|
|
427
|
+
self.upload_error = True
|
|
428
|
+
|
|
429
|
+
# Figure out the message filename, in case there is a message
|
|
430
|
+
self.message_filename = f"{self.receive_mode_dir}-message.txt"
|
|
431
|
+
|
|
432
|
+
# If there's an error so far, finish early
|
|
433
|
+
if self.upload_error:
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# A dictionary that maps filenames to the bytes uploaded so far
|
|
437
|
+
self.progress = {}
|
|
438
|
+
|
|
439
|
+
# Prevent new uploads if we've said so (timer expired)
|
|
440
|
+
if self.web.receive_mode.can_upload:
|
|
441
|
+
|
|
442
|
+
# Create an history_id, attach it to the request
|
|
443
|
+
self.history_id = self.web.receive_mode.cur_history_id
|
|
444
|
+
self.web.receive_mode.cur_history_id += 1
|
|
445
|
+
|
|
446
|
+
# Figure out the content length
|
|
447
|
+
try:
|
|
448
|
+
self.content_length = int(self.headers["Content-Length"])
|
|
449
|
+
except Exception:
|
|
450
|
+
self.content_length = 0
|
|
451
|
+
|
|
452
|
+
date_str = datetime.now().strftime("%b %d, %I:%M%p")
|
|
453
|
+
size_str = self.web.common.human_readable_filesize(self.content_length)
|
|
454
|
+
print(f"{date_str}: Upload of total size {size_str} is starting")
|
|
455
|
+
|
|
456
|
+
# Don't tell the GUI that a request has started until we start receiving files
|
|
457
|
+
self.told_gui_about_request = False
|
|
458
|
+
|
|
459
|
+
self.previous_file = None
|
|
460
|
+
|
|
461
|
+
# Is there a text message?
|
|
462
|
+
self.includes_message = False
|
|
463
|
+
if not self.web.settings.get("receive", "disable_text"):
|
|
464
|
+
text_message = self.form.get("text")
|
|
465
|
+
if text_message:
|
|
466
|
+
if text_message.strip() != "":
|
|
467
|
+
self.includes_message = True
|
|
468
|
+
|
|
469
|
+
with open(self.message_filename, "w") as f:
|
|
470
|
+
f.write(text_message)
|
|
471
|
+
|
|
472
|
+
self.web.common.log(
|
|
473
|
+
"ReceiveModeRequest",
|
|
474
|
+
"__init__",
|
|
475
|
+
f"saved message to {self.message_filename}",
|
|
476
|
+
)
|
|
477
|
+
print(f"Received: {self.message_filename}")
|
|
478
|
+
|
|
479
|
+
# Tell the GUI about the message
|
|
480
|
+
self.tell_gui_request_started()
|
|
481
|
+
self.web.common.log(
|
|
482
|
+
"ReceiveModeRequest",
|
|
483
|
+
"__init__",
|
|
484
|
+
"sending REQUEST_UPLOAD_INCLUDES_MESSAGE to GUI",
|
|
485
|
+
)
|
|
486
|
+
self.web.add_request(
|
|
487
|
+
self.web.REQUEST_UPLOAD_INCLUDES_MESSAGE,
|
|
488
|
+
self.path,
|
|
489
|
+
{
|
|
490
|
+
"id": self.history_id,
|
|
491
|
+
"filename": self.message_filename,
|
|
492
|
+
},
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
def tell_gui_request_started(self):
|
|
496
|
+
# Tell the GUI about the request
|
|
497
|
+
if not self.told_gui_about_request:
|
|
498
|
+
self.web.common.log(
|
|
499
|
+
"ReceiveModeRequest",
|
|
500
|
+
"tell_gui_request_started",
|
|
501
|
+
"sending REQUEST_STARTED to GUI",
|
|
502
|
+
)
|
|
503
|
+
self.web.add_request(
|
|
504
|
+
self.web.REQUEST_STARTED,
|
|
505
|
+
self.path,
|
|
506
|
+
{
|
|
507
|
+
"id": self.history_id,
|
|
508
|
+
"content_length": self.content_length,
|
|
509
|
+
},
|
|
510
|
+
)
|
|
511
|
+
self.web.receive_mode.uploads_in_progress.append(self.history_id)
|
|
512
|
+
|
|
513
|
+
self.told_gui_about_request = True
|
|
514
|
+
|
|
515
|
+
def _get_file_stream(
|
|
516
|
+
self, total_content_length, content_type, filename=None, content_length=None
|
|
517
|
+
):
|
|
518
|
+
"""
|
|
519
|
+
This gets called for each file that gets uploaded, and returns an file-like
|
|
520
|
+
writable stream.
|
|
521
|
+
"""
|
|
522
|
+
if self.upload_request:
|
|
523
|
+
self.tell_gui_request_started()
|
|
524
|
+
|
|
525
|
+
self.filename = secure_filename(filename)
|
|
526
|
+
|
|
527
|
+
self.progress[self.filename] = {"uploaded_bytes": 0, "complete": False}
|
|
528
|
+
|
|
529
|
+
f = ReceiveModeFile(
|
|
530
|
+
self, self.filename, self.file_write_func, self.file_close_func
|
|
531
|
+
)
|
|
532
|
+
if f.upload_error:
|
|
533
|
+
self.web.common.log(
|
|
534
|
+
"ReceiveModeRequest", "_get_file_stream", "Error creating file"
|
|
535
|
+
)
|
|
536
|
+
self.upload_error = True
|
|
537
|
+
return f
|
|
538
|
+
|
|
539
|
+
def close(self):
|
|
540
|
+
"""
|
|
541
|
+
Closing the request.
|
|
542
|
+
"""
|
|
543
|
+
super(ReceiveModeRequest, self).close()
|
|
544
|
+
|
|
545
|
+
# Prevent calling this method more than once per request
|
|
546
|
+
if self.closed:
|
|
547
|
+
return
|
|
548
|
+
self.closed = True
|
|
549
|
+
|
|
550
|
+
if self.upload_request:
|
|
551
|
+
self.web.common.log("ReceiveModeRequest", "close")
|
|
552
|
+
|
|
553
|
+
if self.told_gui_about_request:
|
|
554
|
+
history_id = self.history_id
|
|
555
|
+
|
|
556
|
+
if not self.web.stop_q.empty() or (
|
|
557
|
+
self.filename in self.progress
|
|
558
|
+
and not self.progress[self.filename]["complete"]
|
|
559
|
+
):
|
|
560
|
+
# Inform the GUI that the upload has canceled
|
|
561
|
+
self.web.common.log(
|
|
562
|
+
"ReceiveModeRequest",
|
|
563
|
+
"close",
|
|
564
|
+
"sending REQUEST_UPLOAD_CANCELED to GUI",
|
|
565
|
+
)
|
|
566
|
+
self.web.add_request(
|
|
567
|
+
self.web.REQUEST_UPLOAD_CANCELED,
|
|
568
|
+
self.path,
|
|
569
|
+
{"id": history_id},
|
|
570
|
+
)
|
|
571
|
+
else:
|
|
572
|
+
# Inform the GUI that the upload has finished
|
|
573
|
+
self.web.common.log(
|
|
574
|
+
"ReceiveModeRequest",
|
|
575
|
+
"close",
|
|
576
|
+
"sending REQUEST_UPLOAD_FINISHED to GUI",
|
|
577
|
+
)
|
|
578
|
+
self.web.add_request(
|
|
579
|
+
self.web.REQUEST_UPLOAD_FINISHED,
|
|
580
|
+
self.path,
|
|
581
|
+
{"id": history_id},
|
|
582
|
+
)
|
|
583
|
+
self.web.receive_mode.uploads_in_progress.remove(history_id)
|
|
584
|
+
|
|
585
|
+
# If no files were written to self.receive_mode_dir, delete it
|
|
586
|
+
try:
|
|
587
|
+
if len(os.listdir(self.receive_mode_dir)) == 0:
|
|
588
|
+
os.rmdir(self.receive_mode_dir)
|
|
589
|
+
except Exception:
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
def file_write_func(self, filename, length):
|
|
593
|
+
"""
|
|
594
|
+
This function gets called when a specific file is written to.
|
|
595
|
+
"""
|
|
596
|
+
if self.closed:
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
if self.upload_request:
|
|
600
|
+
self.progress[filename]["uploaded_bytes"] += length
|
|
601
|
+
|
|
602
|
+
if self.previous_file != filename:
|
|
603
|
+
self.previous_file = filename
|
|
604
|
+
|
|
605
|
+
size_str = self.web.common.human_readable_filesize(
|
|
606
|
+
self.progress[filename]["uploaded_bytes"]
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if self.web.common.verbose:
|
|
610
|
+
print(f"=> {size_str} {filename}")
|
|
611
|
+
else:
|
|
612
|
+
print(f"\r=> {size_str} {filename} ", end="")
|
|
613
|
+
|
|
614
|
+
# Update the GUI on the upload progress
|
|
615
|
+
if self.told_gui_about_request:
|
|
616
|
+
self.web.add_request(
|
|
617
|
+
self.web.REQUEST_PROGRESS,
|
|
618
|
+
self.path,
|
|
619
|
+
{"id": self.history_id, "progress": self.progress},
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def file_close_func(self, filename, upload_error=False):
|
|
623
|
+
"""
|
|
624
|
+
This function gets called when a specific file is closed.
|
|
625
|
+
"""
|
|
626
|
+
self.progress[filename]["complete"] = True
|
|
627
|
+
|
|
628
|
+
# If the file tells us there was an upload error, let the request know as well
|
|
629
|
+
if upload_error:
|
|
630
|
+
self.upload_error = True
|