1 #include "filezilla.h"
2
3 #include "asyncrequestqueue.h"
4 #include "defaultfileexistsdlg.h"
5 #include "fileexistsdlg.h"
6 #include "loginmanager.h"
7 #include "Mainfrm.h"
8 #include "Options.h"
9 #include "queue.h"
10 #include "verifycertdialog.h"
11 #include "verifyhostkeydialog.h"
12
13 #include <libfilezilla/translate.hpp>
14
15 wxDECLARE_EVENT(fzEVT_PROCESSASYNCREQUESTQUEUE, wxCommandEvent);
16 wxDEFINE_EVENT(fzEVT_PROCESSASYNCREQUESTQUEUE, wxCommandEvent);
17
BEGIN_EVENT_TABLE(CAsyncRequestQueue,wxEvtHandler)18 BEGIN_EVENT_TABLE(CAsyncRequestQueue, wxEvtHandler)
19 EVT_COMMAND(wxID_ANY, fzEVT_PROCESSASYNCREQUESTQUEUE, CAsyncRequestQueue::OnProcessQueue)
20 EVT_TIMER(wxID_ANY, CAsyncRequestQueue::OnTimer)
21 END_EVENT_TABLE()
22
23 CAsyncRequestQueue::CAsyncRequestQueue(wxTopLevelWindow *parent, cert_store & certStore)
24 : parent_(parent)
25 , certStore_(certStore)
26 {
27 CContextManager::Get()->RegisterHandler(this, STATECHANGE_REMOVECONTEXT, false);
28 m_timer.SetOwner(this);
29 }
30
~CAsyncRequestQueue()31 CAsyncRequestQueue::~CAsyncRequestQueue()
32 {
33 CContextManager::Get()->UnregisterHandler(this, STATECHANGE_REMOVECONTEXT);
34 }
35
ProcessDefaults(CFileZillaEngine * pEngine,std::unique_ptr<CAsyncRequestNotification> & pNotification)36 bool CAsyncRequestQueue::ProcessDefaults(CFileZillaEngine *pEngine, std::unique_ptr<CAsyncRequestNotification> & pNotification)
37 {
38 // Process notifications, see if we have defaults not requirering user interaction.
39 switch (pNotification->GetRequestID())
40 {
41 case reqId_fileexists:
42 {
43 CFileExistsNotification *pFileExistsNotification = static_cast<CFileExistsNotification *>(pNotification.get());
44
45 // Get the action, go up the hierarchy till one is found
46 CFileExistsNotification::OverwriteAction action = pFileExistsNotification->overwriteAction;
47 if (action == CFileExistsNotification::unknown) {
48 action = CDefaultFileExistsDlg::GetDefault(pFileExistsNotification->download);
49 }
50 if (action == CFileExistsNotification::unknown) {
51 int option = COptions::Get()->get_int(pFileExistsNotification->download ? OPTION_FILEEXISTS_DOWNLOAD : OPTION_FILEEXISTS_UPLOAD);
52 if (option < CFileExistsNotification::unknown || option >= CFileExistsNotification::ACTION_COUNT) {
53 action = CFileExistsNotification::unknown;
54 }
55 else {
56 action = static_cast<CFileExistsNotification::OverwriteAction>(option);
57 }
58 }
59
60 // Ask and rename options require user interaction
61 if (action == CFileExistsNotification::unknown || action == CFileExistsNotification::ask || action == CFileExistsNotification::rename) {
62 break;
63 }
64
65 if (action == CFileExistsNotification::resume && pFileExistsNotification->ascii) {
66 // Check if resuming ascii files is allowed
67 if (!COptions::Get()->get_int(OPTION_ASCIIRESUME)) {
68 // Overwrite instead
69 action = CFileExistsNotification::overwrite;
70 }
71 }
72
73 pFileExistsNotification->overwriteAction = action;
74
75 pEngine->SetAsyncRequestReply(std::move(pNotification));
76
77 return true;
78 }
79 case reqId_hostkey:
80 case reqId_hostkeyChanged:
81 {
82 auto & hostKeyNotification = static_cast<CHostKeyNotification&>(*pNotification.get());
83
84 if (!CVerifyHostkeyDialog::IsTrusted(hostKeyNotification)) {
85 break;
86 }
87
88 hostKeyNotification.m_trust = true;
89 hostKeyNotification.m_alwaysTrust = false;
90
91 pEngine->SetAsyncRequestReply(std::move(pNotification));
92
93 return true;
94 }
95 case reqId_certificate:
96 {
97 auto & certNotification = static_cast<CCertificateNotification&>(*pNotification.get());
98
99 if (!certStore_.IsTrusted(certNotification.info_)) {
100 break;
101 }
102
103 certNotification.trusted_ = true;
104 pEngine->SetAsyncRequestReply(std::move(pNotification));
105
106 return true;
107 }
108 break;
109 case reqId_insecure_connection:
110 {
111 auto & insecureNotification = static_cast<CInsecureConnectionNotification&>(*pNotification.get());
112 if (!certStore_.IsInsecure(fz::to_utf8(insecureNotification.server_.GetHost()), insecureNotification.server_.GetPort())) {
113 break;
114 }
115
116 insecureNotification.allow_ = true;
117 pEngine->SetAsyncRequestReply(std::move(pNotification));
118
119 return true;
120 }
121 case reqId_tls_no_resumption:
122 {
123 auto & notification = static_cast<FtpTlsNoResumptionNotification&>(*pNotification.get());
124
125 auto v = certStore_.GetSessionResumptionSupport(fz::to_utf8(notification.server_.GetHost()), notification.server_.GetPort());
126 if (!v || *v) {
127 break;
128 }
129
130 notification.allow_ = true;
131 pEngine->SetAsyncRequestReply(std::move(pNotification));
132 return true;
133 }
134 default:
135 break;
136 }
137
138 return false;
139 }
140
AddRequest(CFileZillaEngine * pEngine,std::unique_ptr<CAsyncRequestNotification> && pNotification)141 bool CAsyncRequestQueue::AddRequest(CFileZillaEngine *pEngine, std::unique_ptr<CAsyncRequestNotification> && pNotification)
142 {
143 ClearPending(pEngine);
144
145 if (ProcessDefaults(pEngine, pNotification)) {
146 return false;
147 }
148
149 m_requestList.emplace_back(pEngine, std::move(pNotification));
150
151 if (m_requestList.size() == 1) {
152 QueueEvent(new wxCommandEvent(fzEVT_PROCESSASYNCREQUESTQUEUE));
153 }
154
155 return true;
156 }
157
ProcessNextRequest()158 bool CAsyncRequestQueue::ProcessNextRequest()
159 {
160 if (m_requestList.empty()) {
161 return true;
162 }
163
164 t_queueEntry &entry = m_requestList.front();
165
166 if (!entry.pEngine || !entry.pEngine->IsPendingAsyncRequestReply(entry.pNotification)) {
167 m_requestList.pop_front();
168 return true;
169 }
170
171 if (entry.pNotification->GetRequestID() == reqId_fileexists) {
172 if (!ProcessFileExistsNotification(entry)) {
173 return false;
174 }
175 }
176 else if (entry.pNotification->GetRequestID() == reqId_interactiveLogin) {
177 auto & notification = static_cast<CInteractiveLoginNotification&>(*entry.pNotification.get());
178
179 if (notification.IsRepeated()) {
180 CLoginManager::Get().CachedPasswordFailed(notification.server, notification.GetChallenge());
181 }
182 bool canRemember = notification.GetType() == CInteractiveLoginNotification::keyfile;
183
184 Site site(notification.server, notification.handle_, notification.credentials);
185 if (CLoginManager::Get().GetPassword(site, true, notification.GetChallenge(), canRemember)) {
186 notification.credentials = site.credentials;
187 notification.passwordSet = true;
188 }
189 else {
190 // Retry with prompt
191
192 if (!CheckWindowState()) {
193 return false;
194 }
195
196 if (CLoginManager::Get().GetPassword(site, false, notification.GetChallenge(), canRemember)) {
197 notification.credentials = site.credentials;
198 notification.passwordSet = true;
199 }
200 }
201
202 SendReply(entry);
203 }
204 else if (entry.pNotification->GetRequestID() == reqId_hostkey || entry.pNotification->GetRequestID() == reqId_hostkeyChanged) {
205 if (!CheckWindowState()) {
206 return false;
207 }
208
209 auto & notification = static_cast<CHostKeyNotification&>(*entry.pNotification.get());
210
211 if (CVerifyHostkeyDialog::IsTrusted(notification)) {
212 notification.m_trust = true;
213 notification.m_alwaysTrust = false;
214 }
215 else {
216 CVerifyHostkeyDialog::ShowVerificationDialog(parent_, notification);
217 }
218
219 SendReply(entry);
220 }
221 else if (entry.pNotification->GetRequestID() == reqId_certificate) {
222 if (!CheckWindowState()) {
223 return false;
224 }
225
226 auto & notification = static_cast<CCertificateNotification&>(*entry.pNotification.get());
227 CVerifyCertDialog::ShowVerificationDialog(certStore_, notification);
228
229 SendReply(entry);
230 }
231 else if (entry.pNotification->GetRequestID() == reqId_insecure_connection) {
232 if (!CheckWindowState()) {
233 return false;
234 }
235
236 auto & notification = static_cast<CInsecureConnectionNotification&>(*entry.pNotification.get());
237
238 ConfirmInsecureConection(parent_, certStore_, notification);
239
240 SendReply(entry);
241 }
242 else if (entry.pNotification->GetRequestID() == reqId_tls_no_resumption) {
243 if (!CheckWindowState()) {
244 return false;
245 }
246
247 auto & notification = static_cast<FtpTlsNoResumptionNotification&>(*entry.pNotification.get());
248
249 auto v = certStore_.GetSessionResumptionSupport(fz::to_utf8(notification.server_.GetHost()), notification.server_.GetPort());
250 if (v && !*v) {
251 notification.allow_ = true;
252 }
253 else {
254 ConfirmFtpTlsNoResumptionNotification(parent_, certStore_, notification);
255 }
256
257 SendReply(entry);
258 }
259 else {
260 SendReply(entry);
261 }
262
263 m_requestList.pop_front();
264
265 return true;
266 }
267
ProcessFileExistsNotification(t_queueEntry & entry)268 bool CAsyncRequestQueue::ProcessFileExistsNotification(t_queueEntry &entry)
269 {
270 auto & notification = static_cast<CFileExistsNotification&>(*entry.pNotification.get());
271
272 // Get the action, go up the hierarchy till one is found
273 CFileExistsNotification::OverwriteAction action = notification.overwriteAction;
274 if (action == CFileExistsNotification::unknown) {
275 action = CDefaultFileExistsDlg::GetDefault(notification.download);
276 }
277 if (action == CFileExistsNotification::unknown) {
278 int option = COptions::Get()->get_int(notification.download ? OPTION_FILEEXISTS_DOWNLOAD : OPTION_FILEEXISTS_UPLOAD);
279 if (option <= CFileExistsNotification::unknown || option >= CFileExistsNotification::ACTION_COUNT) {
280 action = CFileExistsNotification::ask;
281 }
282 else {
283 action = static_cast<CFileExistsNotification::OverwriteAction>(option);
284 }
285 }
286
287 if (action == CFileExistsNotification::ask) {
288 if (!CheckWindowState()) {
289 return false;
290 }
291
292 CFileExistsDlg dlg(¬ification);
293 dlg.Create(parent_);
294 int res = dlg.ShowModal();
295
296 if (res == wxID_OK) {
297 action = dlg.GetAction();
298
299 bool directionOnly, queueOnly;
300 if (dlg.Always(directionOnly, queueOnly)) {
301 if (!queueOnly) {
302 if (notification.download || !directionOnly) {
303 CDefaultFileExistsDlg::SetDefault(true, action);
304 }
305
306 if (!notification.download || !directionOnly) {
307 CDefaultFileExistsDlg::SetDefault(false, action);
308 }
309 }
310 else {
311 // For the notifications already in the request queue, we have to set the queue action directly
312 for (auto iter = ++m_requestList.begin(); iter != m_requestList.end(); ++iter) {
313 if (!iter->pNotification || iter->pNotification->GetRequestID() != reqId_fileexists) {
314 continue;
315 }
316 auto & p = static_cast<CFileExistsNotification&>(*iter->pNotification.get());
317
318 if (!directionOnly || notification.download == p.download) {
319 p.overwriteAction = CFileExistsNotification::OverwriteAction(action);
320 }
321 }
322
323 TransferDirection direction;
324 if (directionOnly) {
325 if (notification.download) {
326 direction = TransferDirection::download;
327 }
328 else {
329 direction = TransferDirection::upload;
330 }
331 }
332 else {
333 direction = TransferDirection::both;
334 }
335
336 if (m_pQueueView) {
337 m_pQueueView->SetDefaultFileExistsAction(action, direction);
338 }
339 }
340 }
341 }
342 else {
343 action = CFileExistsNotification::skip;
344 }
345 }
346
347 if (action == CFileExistsNotification::unknown || action == CFileExistsNotification::ask) {
348 action = CFileExistsNotification::skip;
349 }
350
351 if (action == CFileExistsNotification::resume && notification.ascii) {
352 // Check if resuming ascii files is allowed
353 if (!COptions::Get()->get_int(OPTION_ASCIIRESUME)) {
354 // Overwrite instead
355 action = CFileExistsNotification::overwrite;
356 }
357 }
358
359 switch (action)
360 {
361 case CFileExistsNotification::rename:
362 {
363 if (!CheckWindowState()) {
364 return false;
365 }
366
367 wxString msg;
368 std::wstring defaultName;
369 if (notification.download) {
370 msg.Printf(_("The file %s already exists.\nPlease enter a new name:"), notification.localFile);
371 CLocalPath fn(notification.localFile, &defaultName);
372 if (fn.empty() || defaultName.empty()) {
373 defaultName = fztranslate("new name");
374 }
375 }
376 else {
377 wxString fullName = notification.remotePath.FormatFilename(notification.remoteFile);
378 msg.Printf(_("The file %s already exists.\nPlease enter a new name:"), fullName);
379 defaultName = notification.remoteFile;
380 }
381 wxTextEntryDialog dlg(parent_, msg, _("Rename file"), defaultName);
382
383 // Repeat until user cancels or enters a new name
384 for (;;) {
385 int res = dlg.ShowModal();
386 if (res == wxID_OK) {
387 if (dlg.GetValue().empty()) {
388 continue; // Disallow empty names
389 }
390 if (dlg.GetValue() == defaultName) {
391 wxMessageDialog dlg2(parent_, _("You did not enter a new name for the file. Overwrite the file instead?"), _("Filename unchanged"),
392 wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION | wxCANCEL);
393 int res2 = dlg2.ShowModal();
394
395 if (res2 == wxID_CANCEL) {
396 notification.overwriteAction = CFileExistsNotification::skip;
397 }
398 else if (res2 == wxID_NO) {
399 continue;
400 }
401 else {
402 notification.overwriteAction = CFileExistsNotification::skip;
403 }
404 }
405 else {
406 notification.overwriteAction = CFileExistsNotification::rename;
407 notification.newName = dlg.GetValue().ToStdWstring();
408
409 if (m_pQueueView) {
410 m_pQueueView->RenameFileInTransfer(entry.pEngine, dlg.GetValue().ToStdWstring(), notification.download, notification.new_writer_factory_);
411 }
412 SendReply(entry);
413
414 return true;
415 }
416 }
417 else {
418 notification.overwriteAction = CFileExistsNotification::skip;
419 }
420 break;
421 }
422 }
423 break;
424 default:
425 notification.overwriteAction = action;
426 break;
427 }
428
429 SendReply(entry);
430 return true;
431 }
432
ClearPending(CFileZillaEngine const * const pEngine)433 void CAsyncRequestQueue::ClearPending(CFileZillaEngine const* const pEngine)
434 {
435 if (!pEngine) {
436 return;
437 }
438
439 for (auto iter = m_requestList.begin(); iter != m_requestList.end();) {
440 if (iter->pEngine == pEngine) {
441 if (m_inside_request && iter == m_requestList.begin()) {
442 // Can't remove this entry as it displays a dialog at this moment, holding a reference.
443 iter->pEngine = nullptr;
444 ++iter;
445 }
446 else {
447 // Even though there _should_ be at most a single request per engine, in rare circumstances there may be additional requests
448 iter = m_requestList.erase(iter);
449 }
450 }
451 else {
452 ++iter;
453 }
454 }
455 }
456
RecheckDefaults()457 void CAsyncRequestQueue::RecheckDefaults()
458 {
459 std::list<t_queueEntry>::iterator it = m_requestList.begin();
460 if (m_inside_request) {
461 ++it;
462 }
463 while (it != m_requestList.end()) {
464 if (ProcessDefaults(it->pEngine, it->pNotification)) {
465 it = m_requestList.erase(it);
466 }
467 else {
468 ++it;
469 }
470 }
471 }
472
SetQueue(CQueueView * pQueue)473 void CAsyncRequestQueue::SetQueue(CQueueView *pQueue)
474 {
475 m_pQueueView = pQueue;
476 }
477
OnProcessQueue(wxCommandEvent &)478 void CAsyncRequestQueue::OnProcessQueue(wxCommandEvent &)
479 {
480 if (m_inside_request) {
481 return;
482 }
483
484 m_inside_request = true;
485 bool success = ProcessNextRequest();
486 m_inside_request = false;
487
488 if (success) {
489 RecheckDefaults();
490
491 if (!m_requestList.empty()) {
492 QueueEvent(new wxCommandEvent(fzEVT_PROCESSASYNCREQUESTQUEUE));
493 }
494 }
495 }
496
TriggerProcessing()497 void CAsyncRequestQueue::TriggerProcessing()
498 {
499 if (m_inside_request || m_requestList.empty()) {
500 return;
501 }
502
503 QueueEvent(new wxCommandEvent(fzEVT_PROCESSASYNCREQUESTQUEUE));
504 }
505
CheckWindowState()506 bool CAsyncRequestQueue::CheckWindowState()
507 {
508 m_timer.Stop();
509 if (!wxDialogEx::CanShowPopupDialog(parent_)) {
510 m_timer.Start(100, true);
511 return false;
512 }
513
514 #ifndef __WXMAC__
515 if (parent_->IsIconized()) {
516 #ifndef __WXGTK__
517 parent_->Show();
518 parent_->Iconize(true);
519 parent_->RequestUserAttention();
520 #endif
521 return false;
522 }
523
524 wxWindow* pFocus = parent_->FindFocus();
525 while (pFocus && pFocus != parent_) {
526 pFocus = pFocus->GetParent();
527 }
528 if (!pFocus) {
529 parent_->RequestUserAttention();
530 }
531 #endif
532
533 return true;
534 }
535
OnTimer(wxTimerEvent &)536 void CAsyncRequestQueue::OnTimer(wxTimerEvent&)
537 {
538 TriggerProcessing();
539 }
540
SendReply(t_queueEntry & entry)541 bool CAsyncRequestQueue::SendReply(t_queueEntry& entry)
542 {
543 if (!entry.pEngine) {
544 return false;
545 }
546
547 return entry.pEngine->SetAsyncRequestReply(std::move(entry.pNotification));
548 }
549
OnStateChange(CState * pState,t_statechange_notifications,std::wstring const &,const void *)550 void CAsyncRequestQueue::OnStateChange(CState* pState, t_statechange_notifications, std::wstring const&, const void*)
551 {
552 if (pState) {
553 ClearPending(pState->engine_.get());
554 }
555 }
556