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(&notification);
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