1 #include "filezilla.h"
2 #include "sitemanager_controls.h"
3 
4 #include "dialogex.h"
5 #include "fzputtygen_interface.h"
6 #include "Options.h"
7 #if USE_MAC_SANDBOX
8 #include "osx_sandbox_userdirs.h"
9 #endif
10 #include "sitemanager.h"
11 #include "textctrlex.h"
12 #include "xrc_helper.h"
13 #include "wxext/spinctrlex.h"
14 #if ENABLE_STORJ
15 #include "storj_key_interface.h"
16 #endif
17 
18 #include "../include/s3sse.h"
19 
20 #include <libfilezilla/translate.hpp>
21 
22 #include <wx/dirdlg.h>
23 #include <wx/filedlg.h>
24 #include <wx/gbsizer.h>
25 #include <wx/hyperlink.h>
26 #include <wx/statline.h>
27 
28 #include <array>
29 #include <map>
30 
31 namespace {
32 struct ProtocolGroup {
33 	std::wstring name;
34 	std::vector<std::pair<ServerProtocol, std::wstring>> protocols;
35 };
36 
protocolGroups()37 std::array<ProtocolGroup, 2> const& protocolGroups()
38 {
39 	static auto const groups = std::array<ProtocolGroup, 2>{{
40 		{
41 			fztranslate("FTP - File Transfer Protocol"), {
42 				{ FTP, fztranslate("Use explicit FTP over TLS if available") },
43 				{ FTPES, fztranslate("Require explicit FTP over TLS") },
44 				{ FTPS, fztranslate("Require implicit FTP over TLS") },
45 				{ INSECURE_FTP, fztranslate("Only use plain FTP (insecure)") }
46 			}
47 		},
48 		{
49 			fztranslate("WebDAV"), {
50 				{ WEBDAV, fztranslate("Using secure HTTPS") },
51 				{ INSECURE_WEBDAV, fztranslate("Using insecure HTTP") }
52 			}
53 		}
54 	}};
55 	return groups;
56 }
57 
findGroup(ServerProtocol protocol)58 std::pair<std::array<ProtocolGroup, 2>::const_iterator, std::vector<std::pair<ServerProtocol, std::wstring>>::const_iterator> findGroup(ServerProtocol protocol)
59 {
60 	auto const& groups = protocolGroups();
61 	for (auto group = groups.cbegin(); group != groups.cend(); ++group) {
62 		for (auto entry = group->protocols.cbegin(); entry != group->protocols.cend(); ++entry) {
63 			if (entry->first == protocol) {
64 				return std::make_pair(group, entry);
65 			}
66 		}
67 	}
68 
69 	return std::make_pair(groups.cend(), std::vector<std::pair<ServerProtocol, std::wstring>>::const_iterator());
70 }
71 }
72 
GeneralSiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer,std::function<void (ServerProtocol protocol,LogonType logon_type)> const & changeHandler)73 GeneralSiteControls::GeneralSiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer, std::function<void(ServerProtocol protocol, LogonType logon_type)> const& changeHandler)
74     : SiteControls(parent)
75     , changeHandler_(changeHandler)
76 {
77 	if (!sizer.IsColGrowable(0)) {
78 		sizer.AddGrowableCol(0);
79 	}
80 
81 	auto * bag = lay.createGridBag(2);
82 	bag->AddGrowableCol(1);
83 	sizer.Add(bag, 0, wxGROW);
84 
85 	bag->SetEmptyCellSize(wxSize(-bag->GetHGap(), -bag->GetVGap()));
86 
87 	lay.gbNewRow(bag);
88 	lay.gbAdd(bag, new wxStaticText(&parent, nullID, _("Pro&tocol:")), lay.valign);
89 	auto protocols = new wxChoice(&parent, XRCID("ID_PROTOCOL"));
90 	lay.gbAdd(bag, protocols, lay.valigng);
91 
92 	lay.gbNewRow(bag);
93 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_HOST_DESC"), _("&Host:")), lay.valign);
94 	auto * row = lay.createFlex(0, 1);
95 	row->AddGrowableCol(0);
96 	lay.gbAdd(bag, row, lay.valigng);
97 	row->Add(new wxTextCtrlEx(&parent, XRCID("ID_HOST")), lay.valigng);
98 	row->Add(new wxStaticText(&parent, nullID, _("&Port:")), lay.valign);
99 	auto* port = new wxTextCtrlEx(&parent, XRCID("ID_PORT"), wxString(), wxDefaultPosition, wxSize(lay.dlgUnits(27), -1));
100 	port->SetMaxLength(5);
101 	row->Add(port, lay.valign);
102 
103 	lay.gbNewRow(bag);
104 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_ENCRYPTION_DESC"), _("&Encryption:")), lay.valign);
105 	auto brow = new wxBoxSizer(wxHORIZONTAL);
106 	lay.gbAdd(bag, brow, lay.valigng);
107 	brow->Add(new wxChoice(&parent, XRCID("ID_ENCRYPTION")), 1);
108 	brow->Add(new wxHyperlinkCtrl(&parent, XRCID("ID_DOCS"), _("Docs"), L"https://docs.storj.io/how-tos/set-up-filezilla-for-decentralized-file-transfer"), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, lay.dlgUnits(2))->Show(false);
109 	brow->Add(new wxHyperlinkCtrl(&parent, XRCID("ID_SIGNUP"), _("Signup"), L"https://storj.io/signup/?partner=filezilla"), lay.valign)->Show(false);
110 	brow->AddSpacer(0);
111 
112 	lay.gbNewRow(bag);
113 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_EXTRA_HOST_DESC"), L""), lay.valign)->Show(false);
114 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_EXTRA_HOST")), lay.valigng)->Show(false);
115 
116 	lay.gbAddRow(bag, new wxStaticLine(&parent), lay.grow);
117 
118 	lay.gbNewRow(bag);
119 	lay.gbAdd(bag, new wxStaticText(&parent, nullID, _("&Logon Type:")), lay.valign);
120 	auto logonTypes = new wxChoice(&parent, XRCID("ID_LOGONTYPE"));
121 	lay.gbAdd(bag, logonTypes, lay.valigng);
122 
123 	lay.gbNewRow(bag);
124 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_USER_DESC"), _("&User:")), lay.valign);
125 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_USER")), lay.valigng);
126 
127 	lay.gbNewRow(bag);
128 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_EXTRA_USER_DESC"), L""), lay.valign)->Show(false);
129 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_EXTRA_USER")), lay.valigng)->Show(false);
130 
131 	lay.gbNewRow(bag);
132 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_PASS_DESC"), _("Pass&word:")), lay.valign);
133 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_PASS"), L"", wxDefaultPosition, wxDefaultSize, wxTE_PASSWORD), lay.valigng);
134 
135 	lay.gbNewRow(bag);
136 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_ACCOUNT_DESC"), _("&Account:")), lay.valign);
137 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_ACCOUNT")), lay.valigng);
138 
139 	lay.gbNewRow(bag);
140 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_KEYFILE_DESC"), _("&Key file:")), lay.valign)->Show(false);
141 	row = lay.createFlex(0, 1);
142 	row->AddGrowableCol(0);
143 	lay.gbAdd(bag, row, lay.valigng);
144 	row->Add(new wxTextCtrlEx(&parent, XRCID("ID_KEYFILE")), lay.valigng)->Show(false);
145 	auto keyfileBrowse = new wxButton(&parent, XRCID("ID_KEYFILE_BROWSE"), _("Browse..."));
146 	row->Add(keyfileBrowse, lay.valign)->Show(false);
147 
148 	lay.gbNewRow(bag);
149 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_EXTRA_CREDENTIALS_DESC"), L""), lay.valign)->Show(false);
150 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_EXTRA_CREDENTIALS"), L"", wxDefaultPosition, wxDefaultSize, wxTE_PASSWORD), lay.valigng)->Show(false);
151 
152 	lay.gbNewRow(bag);
153 	lay.gbAdd(bag, new wxStaticText(&parent, XRCID("ID_EXTRA_EXTRA_DESC"), L""), lay.valign)->Show(false);
154 	lay.gbAdd(bag, new wxTextCtrlEx(&parent, XRCID("ID_EXTRA_EXTRA")), lay.valigng)->Show(false);
155 
156 	extraParameters_[ParameterSection::host].emplace_back("", XRCCTRL(parent_, "ID_EXTRA_HOST_DESC", wxStaticText), XRCCTRL(parent_, "ID_EXTRA_HOST", wxTextCtrl));
157 	extraParameters_[ParameterSection::user].emplace_back("", XRCCTRL(parent_, "ID_EXTRA_USER_DESC", wxStaticText), XRCCTRL(parent_, "ID_EXTRA_USER", wxTextCtrl));
158 	extraParameters_[ParameterSection::credentials].emplace_back("", XRCCTRL(parent_, "ID_EXTRA_CREDENTIALS_DESC", wxStaticText), XRCCTRL(parent_, "ID_EXTRA_CREDENTIALS", wxTextCtrl));
159 	extraParameters_[ParameterSection::extra].emplace_back("", XRCCTRL(parent_, "ID_EXTRA_EXTRA_DESC", wxStaticText), XRCCTRL(parent_, "ID_EXTRA_EXTRA", wxTextCtrl));
160 
161 	for (auto const& proto : CServer::GetDefaultProtocols()) {
162 		auto const entry = findGroup(proto);
163 		if (entry.first != protocolGroups().cend()) {
164 			if (entry.second == entry.first->protocols.cbegin()) {
165 				mainProtocolListIndex_[proto] = protocols->Append(entry.first->name);
166 			}
167 			else {
168 				mainProtocolListIndex_[proto] = mainProtocolListIndex_[entry.first->protocols.front().first];
169 			}
170 		}
171 		else {
172 			mainProtocolListIndex_[proto] = protocols->Append(CServer::GetProtocolName(proto));
173 		}
174 	}
175 
176 	for (int i = 0; i < static_cast<int>(LogonType::count); ++i) {
177 		logonTypes->Append(GetNameFromLogonType(static_cast<LogonType>(i)));
178 	}
179 
180 	protocols->Bind(wxEVT_CHOICE, [this](wxEvent const&) {
181 		auto p = GetProtocol();
182 		SetProtocol(p);
183 		if (changeHandler_) {
184 			changeHandler_(p, GetLogonType());
185 		}
186 	});
187 
188 	logonTypes->Bind(wxEVT_CHOICE, [this](wxEvent const&) {
189 		if (changeHandler_) {
190 			changeHandler_(GetProtocol(), GetLogonType());
191 		}
192 	});
193 
194 	keyfileBrowse->Bind(wxEVT_BUTTON, [this](wxEvent const&) {
195 		wxString wildcards(_T("PPK files|*.ppk|PEM files|*.pem|All files|*.*"));
196 		wxFileDialog dlg(&parent_, _("Choose a key file"), wxString(), wxString(), wildcards, wxFD_OPEN|wxFD_FILE_MUST_EXIST);
197 
198 		if (dlg.ShowModal() == wxID_OK) {
199 			std::wstring keyFilePath = dlg.GetPath().ToStdWstring();
200 			// If the selected file was a PEM file, LoadKeyFile() will automatically convert it to PPK
201 			// and tell us the new location.
202 			CFZPuttyGenInterface fzpg(&parent_);
203 
204 			std::wstring keyFileComment, keyFileData;
205 			if (fzpg.LoadKeyFile(keyFilePath, false, keyFileComment, keyFileData)) {
206 				XRCCTRL(parent_, "ID_KEYFILE", wxTextCtrl)->ChangeValue(keyFilePath);
207 	#if USE_MAC_SANDBOX
208 				OSXSandboxUserdirs::Get().AddFile(keyFilePath);
209 	#endif
210 			}
211 			else {
212 				xrc_call(parent_, "ID_KEYFILE", &wxWindow::SetFocus);
213 			}
214 		}
215 	});
216 }
217 
SetSite(Site const & site)218 void GeneralSiteControls::SetSite(Site const& site)
219 {
220 	if (!site) {
221 		// Empty all site information
222 		xrc_call(parent_, "ID_HOST", &wxTextCtrl::ChangeValue, wxString());
223 		xrc_call(parent_, "ID_PORT", &wxTextCtrl::ChangeValue, wxString());
224 		SetProtocol(FTP);
225 		xrc_call(parent_, "ID_USER", &wxTextCtrl::ChangeValue, wxString());
226 		xrc_call(parent_, "ID_PASS", &wxTextCtrl::ChangeValue, wxString());
227 		xrc_call(parent_, "ID_PASS", &wxTextCtrl::SetHint, wxString());
228 		xrc_call(parent_, "ID_ACCOUNT", &wxTextCtrl::ChangeValue, wxString());
229 		xrc_call(parent_, "ID_KEYFILE", &wxTextCtrl::ChangeValue, wxString());
230 
231 		xrc_call(parent_, "ID_LOGONTYPE", &wxChoice::SetStringSelection, GetNameFromLogonType(logonType_));
232 	}
233 	else {
234 		xrc_call(parent_, "ID_HOST", &wxTextCtrl::ChangeValue, site.Format(ServerFormat::host_only));
235 
236 		unsigned int port = site.server.GetPort();
237 		if (port != CServer::GetDefaultPort(site.server.GetProtocol())) {
238 			xrc_call(parent_, "ID_PORT", &wxTextCtrl::ChangeValue, wxString::Format(_T("%d"), port));
239 		}
240 		else {
241 			xrc_call(parent_, "ID_PORT", &wxTextCtrl::ChangeValue, wxString());
242 		}
243 
244 		ServerProtocol protocol = site.server.GetProtocol();
245 		SetProtocol(protocol);
246 
247 		xrc_call(parent_, "ID_LOGONTYPE", &wxChoice::SetStringSelection, GetNameFromLogonType(site.credentials.logonType_));
248 
249 		xrc_call(parent_, "ID_USER", &wxTextCtrl::ChangeValue, site.server.GetUser());
250 		xrc_call(parent_, "ID_ACCOUNT", &wxTextCtrl::ChangeValue, site.credentials.account_);
251 
252 		std::wstring pass = site.credentials.GetPass();
253 
254 		if (logonType_ != LogonType::anonymous && logonType_ != LogonType::interactive && (protocol != SFTP || logonType_ != LogonType::key)) {
255 			if (site.credentials.encrypted_) {
256 				xrc_call(parent_, "ID_PASS", &wxTextCtrl::ChangeValue, wxString());
257 
258 				// @translator: Keep this string as short as possible
259 				xrc_call(parent_, "ID_PASS", &wxTextCtrl::SetHint, _("Leave empty to keep existing password."));
260 				for (auto & control : extraParameters_[ParameterSection::credentials]) {
261 					std::get<2>(control)->SetHint(_("Leave empty to keep existing data."));
262 				}
263 			}
264 			else {
265 				xrc_call(parent_, "ID_PASS", &wxTextCtrl::ChangeValue, pass);
266 				xrc_call(parent_, "ID_PASS", &wxTextCtrl::SetHint, wxString());
267 
268 				auto it = extraParameters_[ParameterSection::credentials].begin();
269 
270 				auto const& traits = ExtraServerParameterTraits(protocol);
271 				for (auto const& trait : traits) {
272 					if (trait.section_ != ParameterSection::credentials) {
273 						continue;
274 					}
275 
276 					std::get<2>(*it)->ChangeValue(site.credentials.GetExtraParameter(trait.name_));
277 					++it;
278 				}
279 			}
280 		}
281 		else {
282 			xrc_call(parent_, "ID_PASS", &wxTextCtrl::ChangeValue, wxString());
283 			xrc_call(parent_, "ID_PASS", &wxTextCtrl::SetHint, wxString());
284 		}
285 
286 		std::vector<GeneralSiteControls::Parameter>::iterator paramIt[ParameterSection::section_count];
287 		for (int i = 0; i < ParameterSection::section_count; ++i) {
288 			paramIt[i] = extraParameters_[i].begin();
289 		}
290 		auto const& traits = ExtraServerParameterTraits(protocol);
291 		for (auto const& trait : traits) {
292 			if (trait.section_ == ParameterSection::credentials || trait.section_ == ParameterSection::custom) {
293 				continue;
294 			}
295 
296 			std::wstring value = site.server.GetExtraParameter(trait.name_);
297 			std::get<2>(*paramIt[trait.section_])->ChangeValue(value.empty() ? trait.default_ : value);
298 			++paramIt[trait.section_];
299 		}
300 
301 		xrc_call(parent_, "ID_KEYFILE", &wxTextCtrl::ChangeValue, site.credentials.keyFile_);
302 	}
303 }
304 
SetControlVisibility(ServerProtocol protocol,LogonType type)305 void GeneralSiteControls::SetControlVisibility(ServerProtocol protocol, LogonType type)
306 {
307 	protocol_ = protocol;
308 	logonType_ = type;
309 
310 	auto const group = findGroup(protocol);
311 	bool const isFtp = group.first != protocolGroups().cend() && group.first->protocols.front().first == FTP;
312 
313 	xrc_call(parent_, "ID_ENCRYPTION_DESC", &wxStaticText::Show, group.first != protocolGroups().cend());
314 	xrc_call(parent_, "ID_ENCRYPTION", &wxChoice::Show, group.first != protocolGroups().cend());
315 
316 	xrc_call(parent_, "ID_DOCS", &wxControl::Show, protocol == STORJ || protocol == STORJ_GRANT);
317 	xrc_call(parent_, "ID_SIGNUP", &wxControl::Show, protocol == STORJ || protocol == STORJ_GRANT);
318 
319 	auto const supportedlogonTypes = GetSupportedLogonTypes(protocol);
320 	assert(!supportedlogonTypes.empty());
321 
322 	auto choice = XRCCTRL(parent_, "ID_LOGONTYPE", wxChoice);
323 	choice->Clear();
324 
325 	if (std::find(supportedlogonTypes.cbegin(), supportedlogonTypes.cend(), type) == supportedlogonTypes.cend()) {
326 		type = supportedlogonTypes.front();
327 	}
328 
329 	for (auto const supportedLogonType : supportedlogonTypes) {
330 		choice->Append(GetNameFromLogonType(supportedLogonType));
331 		if (supportedLogonType == type) {
332 			choice->SetSelection(choice->GetCount() - 1);
333 		}
334 	}
335 
336 	bool const hasUser = ProtocolHasUser(protocol) && type != LogonType::anonymous;
337 	bool const hasPw = type != LogonType::anonymous && type != LogonType::interactive && (protocol != SFTP || type != LogonType::key) && (protocol != S3 || type != LogonType::profile);
338 
339 
340 	xrc_call(parent_, "ID_USER_DESC", &wxStaticText::Show, hasUser);
341 	xrc_call(parent_, "ID_USER", &wxTextCtrl::Show, hasUser);
342 	xrc_call(parent_, "ID_PASS_DESC", &wxStaticText::Show, hasPw);
343 	xrc_call(parent_, "ID_PASS", &wxTextCtrl::Show, hasPw);
344 	xrc_call(parent_, "ID_ACCOUNT_DESC", &wxStaticText::Show, isFtp && type == LogonType::account);
345 	xrc_call(parent_, "ID_ACCOUNT", &wxTextCtrl::Show, isFtp && type == LogonType::account);
346 	xrc_call(parent_, "ID_KEYFILE_DESC", &wxStaticText::Show, protocol == SFTP && type == LogonType::key);
347 	xrc_call(parent_, "ID_KEYFILE", &wxTextCtrl::Show, protocol == SFTP && type == LogonType::key);
348 	xrc_call(parent_, "ID_KEYFILE_BROWSE", &wxButton::Show, protocol == SFTP && type == LogonType::key);
349 
350 	wxString hostLabel = _("&Host:");
351 	wxString hostHint;
352 	wxString userHint;
353 	wxString userLabel = _("&User:");
354 	wxString passLabel = _("Pass&word:");
355 	switch (protocol) {
356 	case STORJ:
357 		// @translator: Keep short
358 		hostLabel = _("S&atellite:");
359 		// @translator: Keep short
360 		userLabel = _("API &Key:");
361 		// @translator: Keep short
362 		passLabel = _("Encr&yption Passphrase:");
363 		break;
364 	case STORJ_GRANT:
365 		// @translator: Keep short
366 		hostLabel = _("S&atellite:");
367 		// @translator: Keep short
368 		passLabel = _("Access &Grant:");
369 		break;
370 	case S3:
371 		// @translator: Keep short
372 		userLabel = _("&Access key ID:");
373 		if (type == LogonType::profile) {
374 			userLabel = _("&Profile");
375 		}
376 		// @translator: Keep short
377 		passLabel = _("Secret Access &Key:");
378 		break;
379 	case AZURE_FILE:
380 	case AZURE_BLOB:
381 		// @translator: Keep short
382 		userLabel = _("Storage &account:");
383 		passLabel = _("Access &Key:");
384 		break;
385 	case GOOGLE_CLOUD:
386 		userLabel = _("Pro&ject ID:");
387 		break;
388 	case SWIFT:
389 	case RACKSPACE:
390 		// @translator: Keep short
391 		hostLabel = _("Identity &host:");
392 		// @translator: Keep short
393 		hostHint = _("Host name of identity service");
394 		userLabel = _("Pro&ject:");
395 		// @translator: Keep short
396 		userHint = _("Project (or tenant) name or ID");
397 		break;
398 	case B2:
399 		// @translator: Keep short
400 		userLabel = _("&Account ID:");
401 		// @translator: Keep short
402 		passLabel = _("Application &Key:");
403 		break;
404 	default:
405 		break;
406 	}
407 	xrc_call(parent_, "ID_HOST_DESC", &wxStaticText::SetLabel, hostLabel);
408 	xrc_call(parent_, "ID_HOST", &wxTextCtrl::SetHint, hostHint);
409 	xrc_call(parent_, "ID_USER_DESC", &wxStaticText::SetLabel, userLabel);
410 	xrc_call(parent_, "ID_PASS_DESC", &wxStaticText::SetLabel, passLabel);
411 	xrc_call(parent_, "ID_USER", &wxTextCtrl::SetHint, userHint);
412 
413 	auto InsertRow = [this](std::vector<GeneralSiteControls::Parameter> & rows, std::string const &name, bool password) {
414 
415 		if (rows.empty()) {
416 			return rows.end();
417 		}
418 
419 		wxWindow* const parent = std::get<1>(rows.back())->GetParent();
420 		wxGridBagSizer* const sizer = dynamic_cast<wxGridBagSizer*>(std::get<1>(rows.back())->GetContainingSizer());
421 
422 		if (!sizer) {
423 			return rows.end();
424 		}
425 		auto pos = sizer->GetItemPosition(std::get<1>(rows.back()));
426 
427 		for (int row = sizer->GetRows() - 1; row > pos.GetRow(); --row) {
428 			auto left = sizer->FindItemAtPosition(wxGBPosition(row, 0));
429 			auto right = sizer->FindItemAtPosition(wxGBPosition(row, 1));
430 			if (!left) {
431 				break;
432 			}
433 			left->SetPos(wxGBPosition(row + 1, 0));
434 			if (right) {
435 				right->SetPos(wxGBPosition(row + 1, 1));
436 			}
437 		}
438 		auto label = new wxStaticText(parent, wxID_ANY, L"");
439 		auto text = new wxTextCtrlEx(parent, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, password ? wxTE_PASSWORD : 0);
440 		sizer->Add(label, wxGBPosition(pos.GetRow() + 1, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
441 		sizer->Add(text, wxGBPosition(pos.GetRow() + 1, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL|wxGROW);
442 
443 		rows.emplace_back(name, label, text);
444 		return rows.end() - 1;
445 	};
446 
447 	auto SetLabel = [](wxStaticText & label, ServerProtocol const, std::string const& name) {
448 		if (name == "login_hint") {
449 			label.SetLabel(_("Login (optional):"));
450 		}
451 		else if (name == "identpath") {
452 			// @translator: Keep short
453 			label.SetLabel(_("Identity service path:"));
454 		}
455 		else if (name == "identuser") {
456 			label.SetLabel(_("&User:"));
457 		}
458 		else {
459 			label.SetLabel(fz::to_wstring_from_utf8(name));
460 		}
461 	};
462 
463 	std::vector<GeneralSiteControls::Parameter>::iterator paramIt[ParameterSection::section_count];
464 	for (int i = 0; i < ParameterSection::section_count; ++i) {
465 		paramIt[i] = extraParameters_[i].begin();
466 	}
467 
468 	std::map<std::pair<std::string, ParameterSection::type>, std::wstring> values;
469 	for (int i = 0; i < ParameterSection::section_count; ++i) {
470 		for (auto const& row : extraParameters_[i]) {
471 			auto const& name = std::get<0>(row);
472 			if (!name.empty()) {
473 				auto value = std::get<2>(row)->GetValue().ToStdWstring();
474 				if (!value.empty()) {
475 					values[std::make_pair(name, static_cast<ParameterSection::type>(i))] = value;
476 				}
477 			}
478 		}
479 	}
480 
481 	std::vector<ParameterTraits> const& parameterTraits = ExtraServerParameterTraits(protocol);
482 	for (auto const& trait : parameterTraits) {
483 		if (trait.section_ == ParameterSection::custom) {
484 			continue;
485 		}
486 		auto & parameters = extraParameters_[trait.section_];
487 		auto & it = paramIt[trait.section_];
488 
489 		if (it == parameters.cend()) {
490 			it = InsertRow(parameters, trait.name_, trait.section_ == ParameterSection::credentials);
491 		}
492 
493 		if (it == parameters.cend()) {
494 			continue;
495 		}
496 
497 		std::get<0>(*it) = trait.name_;
498 		std::get<1>(*it)->Show();
499 		SetLabel(*std::get<1>(*it), protocol, trait.name_);
500 
501 		auto * pValue = std::get<2>(*it);
502 		pValue->Show();
503 		auto valueIt = values.find(std::make_pair(trait.name_, trait.section_));
504 		if (valueIt != values.cend()) {
505 			pValue->ChangeValue(valueIt->second);
506 		}
507 		else {
508 			pValue->ChangeValue(wxString());
509 		}
510 		pValue->SetHint(trait.hint_);
511 
512 		++it;
513 	}
514 
515 	auto encSizer = xrc_call(parent_, "ID_ENCRYPTION", &wxWindow::GetContainingSizer);
516 	encSizer->Show(encSizer->GetItemCount() - 1, paramIt[ParameterSection::host] == extraParameters_[ParameterSection::host].cbegin());
517 
518 	for (int i = 0; i < ParameterSection::section_count; ++i) {
519 		for (; paramIt[i] != extraParameters_[i].cend(); ++paramIt[i]) {
520 			std::get<0>(*paramIt[i]).clear();
521 			std::get<1>(*paramIt[i])->Hide();
522 			std::get<2>(*paramIt[i])->Hide();
523 		}
524 	}
525 
526 	auto keyfileSizer = xrc_call(parent_, "ID_KEYFILE_DESC", &wxStaticText::GetContainingSizer);
527 	if (keyfileSizer) {
528 		keyfileSizer->CalcMin();
529 		keyfileSizer->Layout();
530 	}
531 }
532 
UpdateSite(Site & site,bool silent)533 bool GeneralSiteControls::UpdateSite(Site & site, bool silent)
534 {
535 	ServerProtocol protocol = GetProtocol();
536 	if (protocol == UNKNOWN) {
537 		// How can this happen?
538 		return false;
539 	}
540 
541 	std::wstring user = xrc_call(parent_, "ID_USER", &wxTextCtrl::GetValue).ToStdWstring();
542 	std::wstring pw = xrc_call(parent_, "ID_PASS", &wxTextCtrl::GetValue).ToStdWstring();
543 
544 	{
545 		std::wstring host = xrc_call(parent_, "ID_HOST", &wxTextCtrl::GetValue).ToStdWstring();
546 		std::wstring port = xrc_call(parent_, "ID_PORT", &wxTextCtrl::GetValue).ToStdWstring();
547 
548 		if (host.empty()) {
549 			if (!silent) {
550 				XRCCTRL(parent_, "ID_HOST", wxTextCtrl)->SetFocus();
551 				wxMessageBoxEx(_("You have to enter a hostname."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
552 			}
553 			return false;
554 		}
555 
556 		Site parsedSite;
557 		std::wstring error;
558 		site.m_default_bookmark.m_remoteDir = CServerPath();
559 		if (!parsedSite.ParseUrl(host, port, user, pw, error, site.m_default_bookmark.m_remoteDir, protocol)) {
560 			if (!silent) {
561 				XRCCTRL(parent_, "ID_HOST", wxTextCtrl)->SetFocus();
562 				wxMessageBoxEx(error, _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
563 			}
564 			return false;
565 		}
566 
567 		protocol = parsedSite.server.GetProtocol();
568 		site.server.SetProtocol(protocol);
569 		site.server.SetHost(parsedSite.server.GetHost(), parsedSite.server.GetPort());
570 
571 		if (!parsedSite.server.GetUser().empty()) {
572 			user = parsedSite.server.GetUser();
573 		}
574 		if (!parsedSite.credentials.GetPass().empty()) {
575 			pw = parsedSite.credentials.GetPass();
576 		}
577 	}
578 
579 	auto logon_type = GetLogonType();
580 	auto const supportedlogonTypes = GetSupportedLogonTypes(protocol);
581 	if (std::find(supportedlogonTypes.cbegin(), supportedlogonTypes.cend(), logon_type) == supportedlogonTypes.cend()) {
582 		logon_type = supportedlogonTypes.front();
583 	}
584 
585 	if (COptions::Get()->get_int(OPTION_DEFAULT_KIOSKMODE) != 0 &&
586 			!predefined_ &&
587 	        (logon_type == LogonType::account || logon_type == LogonType::normal))
588 	{
589 		if (!silent) {
590 			wxString msg;
591 			if (COptions::Get()->get_int(OPTION_DEFAULT_KIOSKMODE) != 0 && COptions::Get()->predefined(OPTION_DEFAULT_KIOSKMODE)) {
592 				msg = _("Saving of password has been disabled by your system administrator.");
593 			}
594 			else {
595 				msg = _("Saving of passwords has been disabled by you.");
596 			}
597 			msg += _T("\n");
598 			msg += _("'Normal' and 'Account' logontypes are not available. Your entry has been changed to 'Ask for password'.");
599 			wxMessageBoxEx(msg, _("Site Manager - Cannot remember password"), wxICON_INFORMATION, wxGetTopLevelParent(&parent_));
600 		}
601 		logon_type = LogonType::ask;
602 	}
603 	site.SetLogonType(logon_type);
604 
605 	// At this point we got:
606 	// - Valid protocol, host, port, logontype
607 	// - Optional remotePath
608 	// - Optional, unvalidated username, password
609 
610 	if (!ProtocolHasUser(protocol) || logon_type == LogonType::anonymous) {
611 		user.clear();
612 	}
613 	else if (logon_type != LogonType::ask &&
614 	         logon_type != LogonType::interactive &&
615 	         user.empty())
616 	{
617 		// Require username for non-anonymous, non-ask logon type
618 		if (!silent) {
619 			XRCCTRL(parent_, "ID_USER", wxTextCtrl)->SetFocus();
620 			wxMessageBoxEx(_("You have to specify a user name"), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
621 		}
622 		return false;
623 	}
624 
625 	// We don't allow username of only spaces, confuses both users and XML libraries
626 	if (!user.empty()) {
627 		bool space_only = true;
628 		for (auto const& c : user) {
629 			if (c != ' ') {
630 				space_only = false;
631 				break;
632 			}
633 		}
634 		if (space_only) {
635 			if (!silent) {
636 				XRCCTRL(parent_, "ID_USER", wxTextCtrl)->SetFocus();
637 				wxMessageBoxEx(_("Username cannot be a series of spaces"), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
638 			}
639 			return false;
640 		}
641 	}
642 	site.SetUser(user);
643 
644 	// At this point username has been validated
645 
646 	// Require account for account logon type
647 	if (logon_type == LogonType::account) {
648 		std::wstring account = XRCCTRL(parent_, "ID_ACCOUNT", wxTextCtrl)->GetValue().ToStdWstring();
649 		if (account.empty()) {
650 			if (!silent) {
651 				XRCCTRL(parent_, "ID_ACCOUNT", wxTextCtrl)->SetFocus();
652 				wxMessageBoxEx(_("You have to enter an account name"), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
653 			}
654 			return false;
655 		}
656 		site.credentials.account_ = account;
657 	}
658 
659 	// In key file logon type, check that the provided key file exists
660 	if (logon_type == LogonType::key) {
661 		std::wstring keyFile = xrc_call(parent_, "ID_KEYFILE", &wxTextCtrl::GetValue).ToStdWstring();
662 		if (keyFile.empty()) {
663 			if (!silent) {
664 				wxMessageBoxEx(_("You have to enter a key file path"), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
665 				xrc_call(parent_, "ID_KEYFILE", &wxWindow::SetFocus);
666 			}
667 			return false;
668 		}
669 
670 		// Check (again) that the key file is in the correct format since it might have been changed in the meantime
671 		CFZPuttyGenInterface cfzg(wxGetTopLevelParent(&parent_));
672 
673 		std::wstring keyFileComment, keyFileData;
674 		if (!cfzg.LoadKeyFile(keyFile, silent, keyFileComment, keyFileData)) {
675 			if (!silent) {
676 				xrc_call(parent_, "ID_KEYFILE", &wxWindow::SetFocus);
677 			}
678 			return false;
679 		}
680 
681 		site.credentials.keyFile_ = keyFile;
682 	}
683 
684 	site.server.ClearExtraParameters();
685 
686 	std::vector<ParameterTraits> const& parameterTraits = ExtraServerParameterTraits(protocol);
687 	for (auto const& trait : parameterTraits) {
688 		if (trait.section_ == ParameterSection::custom || trait.section_ == ParameterSection::credentials) {
689 			continue;
690 		}
691 		for (auto const& row : extraParameters_[trait.section_]) {
692 			if (std::get<0>(row) == trait.name_) {
693 				std::wstring value = std::get<2>(row)->GetValue().ToStdWstring();
694 				if (!(trait.flags_ & ParameterTraits::optional)) {
695 					if (value.empty()) {
696 						if (!silent) {
697 							std::get<2>(row)->SetFocus();
698 							wxMessageBoxEx(_("You need to enter a value."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
699 						}
700 						return false;
701 					}
702 				}
703 				site.server.SetExtraParameter(trait.name_, value);
704 				break;
705 			}
706 		}
707 	}
708 
709 	if (protocol == S3 && logon_type == LogonType::profile) {
710 		pw.clear();
711 	}
712 
713 #if ENABLE_STORJ
714 		if (protocol == STORJ_GRANT && logon_type == LogonType::normal) {
715 			fz::trim(pw);
716 			if (pw.empty() && !site.credentials.encrypted_) {
717 				wxMessageBoxEx(_("You need to enter an access grant."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
718 				xrc_call(parent_, "ID_PASS", &wxWindow::SetFocus);
719 			}
720 			else if (!pw.empty()) {
721 				CStorjKeyInterface ki(wxGetTopLevelParent(&parent_));
722 				auto [valid, satellite] = ki.ValidateGrant(pw, silent);
723 				if (valid) {
724 					if (!satellite.empty()) {
725 						size_t pos = satellite.rfind('@');
726 						if (pos != std::wstring::npos) {
727 							satellite = satellite.substr(pos + 1);
728 						}
729 						pos = satellite.rfind(':');
730 						std::wstring port = L"7777";
731 						if (pos != std::wstring::npos) {
732 							port = satellite.substr(pos + 1);
733 							satellite = satellite.substr(0, pos);
734 						}
735 						site.server.SetHost(satellite, fz::to_integral<unsigned short>(port, 7777));
736 					}
737 				}
738 				else {
739 					if (!silent) {
740 						XRCCTRL(parent_, "ID_PASS", wxWindow)->SetFocus();
741 						wxString msg = wxString::Format(_("You have to enter a valid access grant.\nError: %s"), satellite);
742 						wxMessageBoxEx(msg, _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
743 					}
744 					return false;
745 				}
746 			}
747 		}
748 #endif
749 
750 	if (site.credentials.encrypted_) {
751 		if (!pw.empty()) {
752 			site.credentials.encrypted_ = fz::public_key();
753 			site.credentials.SetPass(pw);
754 		}
755 	}
756 	else {
757 		site.credentials.SetPass(pw);
758 	}
759 
760 	return true;
761 }
762 
GetLogonType() const763 LogonType GeneralSiteControls::GetLogonType() const
764 {
765 	return GetLogonTypeFromName(xrc_call(parent_, "ID_LOGONTYPE", &wxChoice::GetStringSelection).ToStdWstring());
766 }
767 
SetProtocol(ServerProtocol protocol)768 void GeneralSiteControls::SetProtocol(ServerProtocol protocol)
769 {
770 	if (protocol == UNKNOWN) {
771 		protocol = FTP;
772 	}
773 	wxChoice* pProtocol = XRCCTRL(parent_, "ID_PROTOCOL", wxChoice);
774 	wxChoice* pEncryption = XRCCTRL(parent_, "ID_ENCRYPTION", wxChoice);
775 	wxStaticText* pEncryptionDesc = XRCCTRL(parent_, "ID_ENCRYPTION_DESC", wxStaticText);
776 
777 	auto const entry = findGroup(protocol);
778 	if (entry.first != protocolGroups().cend()) {
779 		pEncryption->Clear();
780 		for (auto const& prot : entry.first->protocols) {
781 			std::wstring name = prot.second;
782 			if (!CServer::ProtocolHasFeature(prot.first, ProtocolFeature::Security)) {
783 				name += ' ';
784 				name += 0x26a0; // Unicode's warning emoji
785 				name += 0xfe0f; // Variant selector, makes it colorful
786 			}
787 			pEncryption->AppendString(name);
788 		}
789 		pEncryption->Show();
790 		pEncryptionDesc->Show();
791 		pEncryption->SetSelection(entry.second - entry.first->protocols.cbegin());
792 	}
793 	else {
794 		pEncryption->Hide();
795 		pEncryptionDesc->Hide();
796 	}
797 
798 	auto const protoIt = mainProtocolListIndex_.find(protocol);
799 	if (protoIt != mainProtocolListIndex_.cend()) {
800 		pProtocol->SetSelection(protoIt->second);
801 	}
802 	else if (protocol != ServerProtocol::UNKNOWN) {
803 		auto const entry = findGroup(protocol);
804 		if (entry.first != protocolGroups().cend()) {
805 			mainProtocolListIndex_[protocol] = pProtocol->Append(entry.first->name);
806 			for (auto const& sub : entry.first->protocols) {
807 				mainProtocolListIndex_[sub.first] = mainProtocolListIndex_[protocol];
808 			}
809 		}
810 		else {
811 			mainProtocolListIndex_[protocol] = pProtocol->Append(CServer::GetProtocolName(protocol));
812 		}
813 
814 		pProtocol->SetSelection(mainProtocolListIndex_[protocol]);
815 	}
816 	else {
817 		pProtocol->SetSelection(mainProtocolListIndex_[FTP]);
818 	}
819 	UpdateHostFromDefaults(GetProtocol());
820 }
821 
GetProtocol() const822 ServerProtocol GeneralSiteControls::GetProtocol() const
823 {
824 	int const sel = xrc_call(parent_, "ID_PROTOCOL", &wxChoice::GetSelection);
825 
826 	ServerProtocol protocol = UNKNOWN;
827 	for (auto const it : mainProtocolListIndex_) {
828 		if (it.second == sel) {
829 			protocol = it.first;
830 			break;
831 		}
832 	}
833 
834 	auto const group = findGroup(protocol);
835 	if (group.first != protocolGroups().cend()) {
836 		int encSel = xrc_call(parent_, "ID_ENCRYPTION", &wxChoice::GetSelection);
837 		if (encSel < 0 || encSel >= static_cast<int>(group.first->protocols.size())) {
838 			encSel = 0;
839 		}
840 		protocol = group.first->protocols[encSel].first;
841 	}
842 
843 	return protocol;
844 }
845 
UpdateHostFromDefaults(ServerProtocol const newProtocol)846 void GeneralSiteControls::UpdateHostFromDefaults(ServerProtocol const newProtocol)
847 {
848 	if (newProtocol != protocol_) {
849 		auto const oldDefault = std::get<0>(GetDefaultHost(protocol_));
850 		auto const newDefault = GetDefaultHost(newProtocol);
851 
852 		std::wstring const host = xrc_call(parent_, "ID_HOST", &wxTextCtrl::GetValue).ToStdWstring();
853 		if (host.empty() || host == oldDefault) {
854 			xrc_call(parent_, "ID_HOST", &wxTextCtrl::ChangeValue, std::get<0>(newDefault));
855 		}
856 		xrc_call(parent_, "ID_HOST", &wxTextCtrl::SetHint, std::get<1>(newDefault));
857 	}
858 }
859 
SetControlState()860 void GeneralSiteControls::SetControlState()
861 {
862 	xrc_call(parent_, "ID_HOST", &wxWindow::Enable, !predefined_);
863 	xrc_call(parent_, "ID_PORT", &wxWindow::Enable, !predefined_);
864 	xrc_call(parent_, "ID_PROTOCOL", &wxWindow::Enable, !predefined_);
865 	xrc_call(parent_, "ID_ENCRYPTION", &wxWindow::Enable, !predefined_);
866 	xrc_call(parent_, "ID_LOGONTYPE", &wxWindow::Enable, !predefined_);
867 
868 	xrc_call(parent_, "ID_USER", &wxTextCtrl::Enable, !predefined_ && logonType_ != LogonType::anonymous);
869 	xrc_call(parent_, "ID_PASS", &wxTextCtrl::Enable, !predefined_ && (logonType_ == LogonType::normal || logonType_ == LogonType::account));
870 	xrc_call(parent_, "ID_ACCOUNT", &wxTextCtrl::Enable, !predefined_ && logonType_ == LogonType::account);
871 	xrc_call(parent_, "ID_KEYFILE", &wxTextCtrl::Enable, !predefined_ && logonType_ == LogonType::key);
872 	xrc_call(parent_, "ID_KEYFILE_BROWSE", &wxButton::Enable, !predefined_ && logonType_ == LogonType::key);
873 
874 	for (int i = 0; i < ParameterSection::section_count; ++i) {
875 		for (auto & param : extraParameters_[i]) {
876 			std::get<2>(param)->Enable(!predefined_);
877 		}
878 	}
879 }
880 
AdvancedSiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer)881 AdvancedSiteControls::AdvancedSiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer)
882     : SiteControls(parent)
883 {
884 	if (!sizer.IsColGrowable(0)) {
885 		sizer.AddGrowableCol(0);
886 	}
887 
888 	auto* row = lay.createFlex(0, 1);
889 	sizer.Add(row);
890 
891 	row->Add(new wxStaticText(&parent, XRCID("ID_SERVERTYPE_LABEL"), _("Server &type:")), lay.valign);
892 
893 	auto types = new wxChoice(&parent, XRCID("ID_SERVERTYPE"));
894 	row->Add(types, lay.valign);
895 
896 	for (int i = 0; i < SERVERTYPE_MAX; ++i) {
897 		types->Append(CServer::GetNameFromServerType(static_cast<ServerType>(i)));
898 	}
899 
900 	sizer.AddSpacer(0);
901 	sizer.Add(new wxCheckBox(&parent, XRCID("ID_BYPASSPROXY"), _("B&ypass proxy")));
902 
903 	sizer.Add(new wxStaticLine(&parent), lay.grow);
904 
905 	sizer.Add(new wxStaticText(&parent, nullID, _("Default &local directory:")));
906 
907 	row = lay.createFlex(0, 1);
908 	sizer.Add(row, lay.grow);
909 	row->AddGrowableCol(0);
910 	auto localDir = new wxTextCtrlEx(&parent, XRCID("ID_LOCALDIR"));
911 	row->Add(localDir, lay.valigng);
912 	auto browse = new wxButton(&parent, XRCID("ID_BROWSE"), _("&Browse..."));
913 	row->Add(browse, lay.valign);
914 
915 	browse->Bind(wxEVT_BUTTON, [localDir, p = &parent](wxEvent const&) {
916 		wxDirDialog dlg(wxGetTopLevelParent(p), _("Choose the default local directory"), localDir->GetValue(), wxDD_NEW_DIR_BUTTON);
917 		if (dlg.ShowModal() == wxID_OK) {
918 			localDir->ChangeValue(dlg.GetPath());
919 		}
920 	});
921 
922 	sizer.AddSpacer(0);
923 	sizer.Add(new wxStaticText(&parent, nullID, _("Default r&emote directory:")));
924 	sizer.Add(new wxTextCtrlEx(&parent, XRCID("ID_REMOTEDIR")), lay.grow);
925 	sizer.AddSpacer(0);
926 	sizer.Add(new wxCheckBox(&parent, XRCID("ID_SYNC"), _("&Use synchronized browsing")));
927 	sizer.Add(new wxCheckBox(&parent, XRCID("ID_COMPARISON"), _("Directory comparison")));
928 
929 	sizer.Add(new wxStaticLine(&parent), lay.grow);
930 
931 	sizer.Add(new wxStaticText(&parent, nullID, _("&Adjust server time, offset by:")));
932 	row = lay.createFlex(0, 1);
933 	sizer.Add(row);
934 	auto* hours = new wxSpinCtrlEx(&parent, XRCID("ID_TIMEZONE_HOURS"), wxString(), wxDefaultPosition, wxSize(lay.dlgUnits(26), -1));
935 	hours->SetRange(-24, 24);
936 	hours->SetMaxLength(3);
937 	row->Add(hours, lay.valign);
938 	row->Add(new wxStaticText(&parent, nullID, _("Hours,")), lay.valign);
939 	auto* minutes = new wxSpinCtrlEx(&parent, XRCID("ID_TIMEZONE_MINUTES"), wxString(), wxDefaultPosition, wxSize(lay.dlgUnits(26), -1));
940 	minutes->SetRange(-59, 59);
941 	minutes->SetMaxLength(3);
942 	row->Add(minutes, lay.valign);
943 	row->Add(new wxStaticText(&parent, nullID, _("Minutes")), lay.valign);
944 }
945 
SetSite(Site const & site)946 void AdvancedSiteControls::SetSite(Site const& site)
947 {
948 	xrc_call(parent_, "ID_SERVERTYPE", &wxWindow::Enable, !predefined_);
949 	xrc_call(parent_, "ID_BYPASSPROXY", &wxWindow::Enable, !predefined_);
950 	xrc_call(parent_, "ID_SYNC", &wxWindow::Enable, !predefined_);
951 	xrc_call(parent_, "ID_COMPARISON", &wxCheckBox::Enable, !predefined_);
952 	xrc_call(parent_, "ID_LOCALDIR", &wxWindow::Enable, !predefined_);
953 	xrc_call(parent_, "ID_BROWSE", &wxWindow::Enable, !predefined_);
954 	xrc_call(parent_, "ID_REMOTEDIR", &wxWindow::Enable, !predefined_);
955 	xrc_call(parent_, "ID_TIMEZONE_HOURS", &wxWindow::Enable, !predefined_);
956 	xrc_call(parent_, "ID_TIMEZONE_MINUTES", &wxWindow::Enable, !predefined_);
957 
958 	if (site) {
959 		xrc_call(parent_, "ID_SERVERTYPE", &wxChoice::SetSelection, site.server.GetType());
960 		xrc_call(parent_, "ID_BYPASSPROXY", &wxCheckBox::SetValue, site.server.GetBypassProxy());
961 		xrc_call(parent_, "ID_LOCALDIR", &wxTextCtrl::ChangeValue, site.m_default_bookmark.m_localDir);
962 		xrc_call(parent_, "ID_REMOTEDIR", &wxTextCtrl::ChangeValue, site.m_default_bookmark.m_remoteDir.GetPath());
963 		xrc_call(parent_, "ID_SYNC", &wxCheckBox::SetValue, site.m_default_bookmark.m_sync);
964 		xrc_call(parent_, "ID_COMPARISON", &wxCheckBox::SetValue, site.m_default_bookmark.m_comparison);
965 		xrc_call<wxSpinCtrl, int>(parent_, "ID_TIMEZONE_HOURS", &wxSpinCtrl::SetValue, site.server.GetTimezoneOffset() / 60);
966 		xrc_call<wxSpinCtrl, int>(parent_, "ID_TIMEZONE_MINUTES", &wxSpinCtrl::SetValue, site.server.GetTimezoneOffset() % 60);
967 	}
968 	else {
969 		xrc_call(parent_, "ID_SERVERTYPE", &wxChoice::SetSelection, 0);
970 		xrc_call(parent_, "ID_BYPASSPROXY", &wxCheckBox::SetValue, false);
971 		xrc_call(parent_, "ID_SYNC", &wxCheckBox::SetValue, false);
972 		xrc_call(parent_, "ID_COMPARISON", &wxCheckBox::SetValue, false);
973 		xrc_call(parent_, "ID_LOCALDIR", &wxTextCtrl::ChangeValue, wxString());
974 		xrc_call(parent_, "ID_REMOTEDIR", &wxTextCtrl::ChangeValue, wxString());
975 		xrc_call<wxSpinCtrl, int>(parent_, "ID_TIMEZONE_HOURS", &wxSpinCtrl::SetValue, 0);
976 		xrc_call<wxSpinCtrl, int>(parent_, "ID_TIMEZONE_MINUTES", &wxSpinCtrl::SetValue, 0);
977 	}
978 }
979 
SetControlVisibility(ServerProtocol protocol,LogonType)980 void AdvancedSiteControls::SetControlVisibility(ServerProtocol protocol, LogonType)
981 {
982 	bool const hasServerType = CServer::ProtocolHasFeature(protocol, ProtocolFeature::ServerType);
983 	xrc_call(parent_, "ID_SERVERTYPE_LABEL", &wxWindow::Show, hasServerType);
984 	xrc_call(parent_, "ID_SERVERTYPE", &wxWindow::Show, hasServerType);
985 	auto * serverTypeSizer = xrc_call(parent_, "ID_SERVERTYPE_LABEL", &wxWindow::GetContainingSizer)->GetContainingWindow()->GetSizer();
986 	serverTypeSizer->CalcMin();
987 	serverTypeSizer->Layout();
988 }
989 
UpdateSite(Site & site,bool silent)990 bool AdvancedSiteControls::UpdateSite(Site & site, bool silent)
991 {
992 	ServerType serverType = DEFAULT;
993 	if (!site.m_default_bookmark.m_remoteDir.empty()) {
994 		if (site.server.HasFeature(ProtocolFeature::ServerType)) {
995 			serverType = site.m_default_bookmark.m_remoteDir.GetType();
996 		}
997 		else if (site.m_default_bookmark.m_remoteDir.GetType() != DEFAULT && site.m_default_bookmark.m_remoteDir.GetType() != UNIX) {
998 			site.m_default_bookmark.m_remoteDir = CServerPath();
999 		}
1000 	}
1001 	else {
1002 		if (site.server.HasFeature(ProtocolFeature::ServerType)) {
1003 			serverType = CServer::GetServerTypeFromName(xrc_call(parent_, "ID_SERVERTYPE", &wxChoice::GetStringSelection).ToStdWstring());
1004 		}
1005 	}
1006 
1007 	site.server.SetType(serverType);
1008 
1009 	if (xrc_call(parent_, "ID_BYPASSPROXY", &wxCheckBox::GetValue)) {
1010 		site.server.SetBypassProxy(true);
1011 	}
1012 	else {
1013 		site.server.SetBypassProxy(false);
1014 	}
1015 
1016 	if (site.m_default_bookmark.m_remoteDir.empty()) {
1017 		std::wstring const remotePathRaw = XRCCTRL(parent_, "ID_REMOTEDIR", wxTextCtrl)->GetValue().ToStdWstring();
1018 		if (!remotePathRaw.empty()) {
1019 			site.m_default_bookmark.m_remoteDir.SetType(serverType);
1020 			if (!site.m_default_bookmark.m_remoteDir.SetPath(remotePathRaw)) {
1021 				if (!silent) {
1022 					XRCCTRL(parent_, "ID_REMOTEDIR", wxTextCtrl)->SetFocus();
1023 					wxMessageBoxEx(_("Default remote path cannot be parsed. Make sure it is a valid absolute path for the selected server type."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1024 				}
1025 				return false;
1026 			}
1027 		}
1028 	}
1029 
1030 	std::wstring const localPath = XRCCTRL(parent_, "ID_LOCALDIR", wxTextCtrl)->GetValue().ToStdWstring();
1031 	site.m_default_bookmark.m_localDir = localPath;
1032 	if (XRCCTRL(parent_, "ID_SYNC", wxCheckBox)->GetValue()) {
1033 		if (site.m_default_bookmark.m_remoteDir.empty() || localPath.empty()) {
1034 			if (!silent) {
1035 				XRCCTRL(parent_, "ID_SYNC", wxCheckBox)->SetFocus();
1036 				wxMessageBoxEx(_("You need to enter both a local and a remote path to enable synchronized browsing for this site."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1037 			}
1038 			return false;
1039 		}
1040 	}
1041 
1042 
1043 	site.m_default_bookmark.m_sync = xrc_call(parent_, "ID_SYNC", &wxCheckBox::GetValue);
1044 	site.m_default_bookmark.m_comparison = xrc_call(parent_, "ID_COMPARISON", &wxCheckBox::GetValue);
1045 
1046 	int hours = xrc_call(parent_, "ID_TIMEZONE_HOURS", &wxSpinCtrl::GetValue);
1047 	int minutes = xrc_call(parent_, "ID_TIMEZONE_MINUTES", &wxSpinCtrl::GetValue);
1048 
1049 	site.server.SetTimezoneOffset(hours * 60 + minutes);
1050 
1051 	return true;
1052 }
1053 
CharsetSiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer)1054 CharsetSiteControls::CharsetSiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer)
1055     : SiteControls(parent)
1056 {
1057 	sizer.Add(new wxStaticText(&parent, nullID, _("The server uses following charset encoding for filenames:")));
1058 	auto rbAuto = new wxRadioButton(&parent, XRCID("ID_CHARSET_AUTO"), _("&Autodetect"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
1059 	sizer.Add(rbAuto);
1060 	sizer.Add(new wxStaticText(&parent, nullID, _("Uses UTF-8 if the server supports it, else uses local charset.")), 0, wxLEFT, 18);
1061 
1062 	auto rbUtf8 = new wxRadioButton(&parent, XRCID("ID_CHARSET_UTF8"), _("Force &UTF-8"));
1063 	sizer.Add(rbUtf8);
1064 	auto rbCustom = new wxRadioButton(&parent, XRCID("ID_CHARSET_CUSTOM"), _("Use &custom charset"));
1065 	sizer.Add(rbCustom);
1066 
1067 	auto * row = lay.createFlex(0, 1);
1068 	row->Add(new wxStaticText(&parent, nullID, _("&Encoding:")), lay.valign);
1069 	auto * encoding = new wxTextCtrlEx(&parent, XRCID("ID_ENCODING"));
1070 	row->Add(encoding, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 18);
1071 	sizer.Add(row);
1072 	sizer.AddSpacer(lay.dlgUnits(6));
1073 	sizer.Add(new wxStaticText(&parent, nullID, _("Using the wrong charset can result in filenames not displaying properly.")));
1074 
1075 	rbAuto->Bind(wxEVT_RADIOBUTTON, [encoding](wxEvent const&){ encoding->Disable(); });
1076 	rbUtf8->Bind(wxEVT_RADIOBUTTON, [encoding](wxEvent const&){ encoding->Disable(); });
1077 	rbCustom->Bind(wxEVT_RADIOBUTTON, [encoding](wxEvent const&){ encoding->Enable(); });
1078 }
1079 
SetSite(Site const & site)1080 void CharsetSiteControls::SetSite(Site const& site)
1081 {
1082 	xrc_call(parent_, "ID_CHARSET_AUTO", &wxWindow::Enable, !predefined_);
1083 	xrc_call(parent_, "ID_CHARSET_UTF8", &wxWindow::Enable, !predefined_);
1084 	xrc_call(parent_, "ID_CHARSET_CUSTOM", &wxWindow::Enable, !predefined_);
1085 	xrc_call(parent_, "ID_ENCODING", &wxWindow::Enable, !predefined_);
1086 
1087 	if (!site) {
1088 		xrc_call(parent_, "ID_CHARSET_AUTO", &wxRadioButton::SetValue, true);
1089 		xrc_call(parent_, "ID_ENCODING", &wxTextCtrl::ChangeValue, wxString());
1090 		xrc_call(parent_, "ID_ENCODING", &wxTextCtrl::Enable, false);
1091 	}
1092 	else {
1093 		switch (site.server.GetEncodingType()) {
1094 		default:
1095 		case ENCODING_AUTO:
1096 			xrc_call(parent_, "ID_CHARSET_AUTO", &wxRadioButton::SetValue, true);
1097 			break;
1098 		case ENCODING_UTF8:
1099 			xrc_call(parent_, "ID_CHARSET_UTF8", &wxRadioButton::SetValue, true);
1100 			break;
1101 		case ENCODING_CUSTOM:
1102 			xrc_call(parent_, "ID_CHARSET_CUSTOM", &wxRadioButton::SetValue, true);
1103 			break;
1104 		}
1105 		xrc_call(parent_, "ID_ENCODING", &wxTextCtrl::Enable, !predefined_ && site.server.GetEncodingType() == ENCODING_CUSTOM);
1106 		xrc_call(parent_, "ID_ENCODING", &wxTextCtrl::ChangeValue, site.server.GetCustomEncoding());
1107 	}
1108 }
1109 
UpdateSite(Site & site,bool silent)1110 bool CharsetSiteControls::UpdateSite(Site & site, bool silent)
1111 {
1112 	if (CServer::ProtocolHasFeature(site.server.GetProtocol(), ProtocolFeature::Charset)) {
1113 		if (xrc_call(parent_, "ID_CHARSET_UTF8", &wxRadioButton::GetValue)) {
1114 			site.server.SetEncodingType(ENCODING_UTF8);
1115 		}
1116 		else if (xrc_call(parent_, "ID_CHARSET_CUSTOM", &wxRadioButton::GetValue)) {
1117 			std::wstring encoding = xrc_call(parent_, "ID_ENCODING", &wxTextCtrl::GetValue).ToStdWstring();
1118 
1119 			if (encoding.empty()) {
1120 				if (!silent) {
1121 					XRCCTRL(parent_, "ID_ENCODING", wxTextCtrl)->SetFocus();
1122 					wxMessageBoxEx(_("Need to specify a character encoding"), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1123 				}
1124 				return false;
1125 			}
1126 
1127 			site.server.SetEncodingType(ENCODING_CUSTOM, encoding);
1128 		}
1129 		else {
1130 			site.server.SetEncodingType(ENCODING_AUTO);
1131 		}
1132 	}
1133 	else {
1134 		site.server.SetEncodingType(ENCODING_AUTO);
1135 	}
1136 
1137 	return true;
1138 }
1139 
TransferSettingsSiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer)1140 TransferSettingsSiteControls::TransferSettingsSiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer)
1141     : SiteControls(parent)
1142 {
1143 	sizer.Add(new wxStaticText(&parent, XRCID("ID_TRANSFERMODE_LABEL"), _("&Transfer mode:")));
1144 	auto * row = lay.createFlex(0, 1);
1145 	sizer.Add(row);
1146 	row->Add(new wxRadioButton(&parent, XRCID("ID_TRANSFERMODE_DEFAULT"), _("D&efault"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP), lay.valign);
1147 	row->Add(new wxRadioButton(&parent, XRCID("ID_TRANSFERMODE_ACTIVE"), _("&Active")), lay.valign);
1148 	row->Add(new wxRadioButton(&parent, XRCID("ID_TRANSFERMODE_PASSIVE"), _("&Passive")), lay.valign);
1149 	sizer.AddSpacer(0);
1150 
1151 	auto limit = new wxCheckBox(&parent, XRCID("ID_LIMITMULTIPLE"), _("&Limit number of simultaneous connections"));
1152 	sizer.Add(limit);
1153 	row = lay.createFlex(0, 1);
1154 	sizer.Add(row, 0, wxLEFT, lay.dlgUnits(10));
1155 	row->Add(new wxStaticText(&parent, nullID, _("&Maximum number of connections:")), lay.valign);
1156 	auto * spin = new wxSpinCtrlEx(&parent, XRCID("ID_MAXMULTIPLE"), wxString(), wxDefaultPosition, wxSize(lay.dlgUnits(26), -1));
1157 	spin->SetMaxLength(2);
1158 	spin->SetRange(1, 10);
1159 	row->Add(spin, lay.valign);
1160 
1161 	limit->Bind(wxEVT_CHECKBOX, [spin](wxCommandEvent const& ev){ spin->Enable(ev.IsChecked()); });
1162 }
1163 
SetSite(Site const & site)1164 void TransferSettingsSiteControls::SetSite(Site const& site)
1165 {
1166 	xrc_call(parent_, "ID_TRANSFERMODE_DEFAULT", &wxWindow::Enable, !predefined_);
1167 	xrc_call(parent_, "ID_TRANSFERMODE_ACTIVE", &wxWindow::Enable, !predefined_);
1168 	xrc_call(parent_, "ID_TRANSFERMODE_PASSIVE", &wxWindow::Enable, !predefined_);
1169 	xrc_call(parent_, "ID_LIMITMULTIPLE", &wxWindow::Enable, !predefined_);
1170 
1171 	if (!site) {
1172 		xrc_call(parent_, "ID_TRANSFERMODE_DEFAULT", &wxRadioButton::SetValue, true);
1173 		xrc_call(parent_, "ID_LIMITMULTIPLE", &wxCheckBox::SetValue, false);
1174 		xrc_call(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::Enable, false);
1175 		xrc_call<wxSpinCtrl, int>(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::SetValue, 1);
1176 	}
1177 	else {
1178 		if (CServer::ProtocolHasFeature(site.server.GetProtocol(), ProtocolFeature::TransferMode)) {
1179 			PasvMode pasvMode = site.server.GetPasvMode();
1180 			if (pasvMode == MODE_ACTIVE) {
1181 				xrc_call(parent_, "ID_TRANSFERMODE_ACTIVE", &wxRadioButton::SetValue, true);
1182 			}
1183 			else if (pasvMode == MODE_PASSIVE) {
1184 				xrc_call(parent_, "ID_TRANSFERMODE_PASSIVE", &wxRadioButton::SetValue, true);
1185 			}
1186 			else {
1187 				xrc_call(parent_, "ID_TRANSFERMODE_DEFAULT", &wxRadioButton::SetValue, true);
1188 			}
1189 		}
1190 
1191 		int const maxMultiple = site.server.MaximumMultipleConnections();
1192 		xrc_call(parent_, "ID_LIMITMULTIPLE", &wxCheckBox::SetValue, maxMultiple != 0);
1193 		if (maxMultiple != 0) {
1194 			xrc_call(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::Enable, !predefined_);
1195 			xrc_call<wxSpinCtrl, int>(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::SetValue, maxMultiple);
1196 		}
1197 		else {
1198 			xrc_call(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::Enable, false);
1199 			xrc_call<wxSpinCtrl, int>(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::SetValue, 1);
1200 		}
1201 
1202 	}
1203 }
1204 
UpdateSite(Site & site,bool)1205 bool TransferSettingsSiteControls::UpdateSite(Site & site, bool)
1206 {
1207 	if (CServer::ProtocolHasFeature(site.server.GetProtocol(), ProtocolFeature::TransferMode)) {
1208 		if (xrc_call(parent_, "ID_TRANSFERMODE_ACTIVE", &wxRadioButton::GetValue)) {
1209 			site.server.SetPasvMode(MODE_ACTIVE);
1210 		}
1211 		else if (xrc_call(parent_, "ID_TRANSFERMODE_PASSIVE", &wxRadioButton::GetValue)) {
1212 			site.server.SetPasvMode(MODE_PASSIVE);
1213 		}
1214 		else {
1215 			site.server.SetPasvMode(MODE_DEFAULT);
1216 		}
1217 	}
1218 	else {
1219 		site.server.SetPasvMode(MODE_DEFAULT);
1220 	}
1221 
1222 	if (xrc_call(parent_, "ID_LIMITMULTIPLE", &wxCheckBox::GetValue)) {
1223 		site.server.MaximumMultipleConnections(xrc_call(parent_, "ID_MAXMULTIPLE", &wxSpinCtrl::GetValue));
1224 	}
1225 	else {
1226 		site.server.MaximumMultipleConnections(0);
1227 	}
1228 
1229 	return true;
1230 }
1231 
SetControlVisibility(ServerProtocol protocol,LogonType)1232 void TransferSettingsSiteControls::SetControlVisibility(ServerProtocol protocol, LogonType)
1233 {
1234 	bool const hasTransferMode = CServer::ProtocolHasFeature(protocol, ProtocolFeature::TransferMode);
1235 	xrc_call(parent_, "ID_TRANSFERMODE_DEFAULT", &wxWindow::Show, hasTransferMode);
1236 	xrc_call(parent_, "ID_TRANSFERMODE_ACTIVE", &wxWindow::Show, hasTransferMode);
1237 	xrc_call(parent_, "ID_TRANSFERMODE_PASSIVE", &wxWindow::Show, hasTransferMode);
1238 	auto* transferModeLabel = XRCCTRL(parent_, "ID_TRANSFERMODE_LABEL", wxStaticText);
1239 	transferModeLabel->Show(hasTransferMode);
1240 	transferModeLabel->GetContainingSizer()->CalcMin();
1241 	transferModeLabel->GetContainingSizer()->Layout();
1242 }
1243 
S3SiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer)1244 S3SiteControls::S3SiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer)
1245     : SiteControls(parent)
1246 {
1247 	if (!sizer.IsColGrowable(0)) {
1248 		sizer.AddGrowableCol(0);
1249 	}
1250 
1251 	sizer.Add(new wxStaticText(&parent, nullID, _("Options:")));
1252 	auto * options_row = lay.createFlex(2);
1253 	options_row->AddGrowableCol(1);
1254 	sizer.Add(options_row, 0, wxLEFT|wxGROW, lay.indent);
1255 	options_row->Add(new wxStaticText(&parent, nullID, _("Re&gion:")), lay.valign);
1256 	options_row->Add(new wxTextCtrlEx(&parent, XRCID("ID_S3_REGION")), lay.valigng);
1257 
1258 	sizer.Add(new wxStaticLine(&parent), lay.grow);
1259 	sizer.Add(new wxStaticText(&parent, nullID, _("Server Side Encryption:")));
1260 
1261 	auto none = new wxRadioButton(&parent, XRCID("ID_S3_NOENCRYPTION"), _("N&o encryption"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
1262 	sizer.Add(none);
1263 
1264 	auto aes = new wxRadioButton(&parent, XRCID("ID_S3_AES256"), _("&AWS S3 encryption"));
1265 	sizer.Add(aes);
1266 
1267 	auto kms = new wxRadioButton(&parent, XRCID("ID_S3_AWSKMS"), _("AWS &KMS encryption"));
1268 	sizer.Add(kms);
1269 
1270 	auto * key_row = lay.createFlex(2);
1271 	key_row->AddGrowableCol(1);
1272 	sizer.Add(key_row, 0, wxLEFT|wxGROW, lay.indent);
1273 	key_row->Add(new wxStaticText(&parent, nullID, _("&Select a key:")), lay.valign);
1274 	auto * choice = new wxChoice(&parent, XRCID("ID_S3_KMSKEY"));
1275 	choice->Append(_("Default (AWS/S3)"));
1276 	choice->Append(_("Custom KMS ARN"));
1277 	key_row->Add(choice, lay.valigng);
1278 	key_row->Add(new wxStaticText(&parent, nullID, _("C&ustom KMS ARN:")), lay.valign);
1279 	key_row->Add(new wxTextCtrlEx(&parent, XRCID("ID_S3_CUSTOM_KMS")), lay.valigng);
1280 
1281 	auto customer = new wxRadioButton(&parent, XRCID("ID_S3_CUSTOMER_ENCRYPTION"), _("Cus&tomer encryption"));
1282 	sizer.Add(customer);
1283 	key_row = lay.createFlex(2);
1284 	key_row->AddGrowableCol(1);
1285 	sizer.Add(key_row, 0, wxLEFT | wxGROW, lay.indent);
1286 	key_row->Add(new wxStaticText(&parent, nullID, _("Customer Ke&y:")), lay.valign);
1287 	key_row->Add(new wxTextCtrlEx(&parent, XRCID("ID_S3_CUSTOMER_KEY")), lay.valigng);
1288 
1289 	sizer.Add(new wxStaticLine(&parent), lay.grow);
1290 	sizer.Add(new wxStaticText(&parent, nullID, _("Security Token Service:")));
1291 
1292 	auto * sts_row = lay.createFlex(2);
1293 	sts_row->AddGrowableCol(1);
1294 	sizer.Add(sts_row, 0, wxLEFT|wxGROW, lay.indent);
1295 	sts_row->Add(new wxStaticText(&parent, nullID, _("Ro&le ARN:")), lay.valign);
1296 	sts_row->Add(new wxTextCtrlEx(&parent, XRCID("ID_S3_ROLE_ARN")), lay.valigng);
1297 	sts_row->Add(new wxStaticText(&parent, nullID, _("MFA D&evice Serial:")), lay.valign);
1298 	sts_row->Add(new wxTextCtrlEx(&parent, XRCID("ID_S3_MFA_SERIAL")), lay.valigng);
1299 
1300 	auto l = [this](wxEvent const&) { SetControlState(); };
1301 	none->Bind(wxEVT_RADIOBUTTON, l);
1302 	aes->Bind(wxEVT_RADIOBUTTON, l);
1303 	kms->Bind(wxEVT_RADIOBUTTON, l);
1304 	customer->Bind(wxEVT_RADIOBUTTON, l);
1305 	choice->Bind(wxEVT_CHOICE, l);
1306 }
1307 
SetControlState()1308 void S3SiteControls::SetControlState()
1309 {
1310 	bool enableKey{};
1311 	bool enableKMS{};
1312 	bool enableCustomer{};
1313 	if (xrc_call(parent_, "ID_S3_AWSKMS", &wxRadioButton::GetValue)) {
1314 		enableKey = true;
1315 		if (xrc_call(parent_, "ID_S3_KMSKEY", &wxChoice::GetSelection) == static_cast<int>(s3_sse::KmsKey::CUSTOM)) {
1316 			enableKMS = true;
1317 		}
1318 	}
1319 	else if (xrc_call(parent_, "ID_S3_CUSTOMER_ENCRYPTION", &wxRadioButton::GetValue)) {
1320 		enableCustomer = true;
1321 	}
1322 	xrc_call(parent_, "ID_S3_KMSKEY", &wxWindow::Enable, !predefined_ && enableKey);
1323 	xrc_call(parent_, "ID_S3_CUSTOM_KMS", &wxWindow::Enable, !predefined_ && enableKMS);
1324 	xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxWindow::Enable, !predefined_ && enableCustomer);
1325 }
1326 
SetSite(Site const & site)1327 void S3SiteControls::SetSite(Site const& site)
1328 {
1329 	xrc_call(parent_, "ID_S3_KMSKEY", &wxWindow::Enable, !predefined_);
1330 	xrc_call(parent_, "ID_S3_NOENCRYPTION", &wxWindow::Enable, !predefined_);
1331 	xrc_call(parent_, "ID_S3_AES256", &wxWindow::Enable, !predefined_);
1332 	xrc_call(parent_, "ID_S3_AWSKMS", &wxWindow::Enable, !predefined_);
1333 	xrc_call(parent_, "ID_S3_CUSTOMER_ENCRYPTION", &wxWindow::Enable, !predefined_);
1334 
1335 	if (site.server.GetProtocol() == S3) {
1336 		auto region = site.server.GetExtraParameter("region");
1337 		xrc_call(parent_, "ID_S3_REGION", &wxTextCtrl::ChangeValue, region);
1338 
1339 		xrc_call(parent_, "ID_S3_KMSKEY", &wxChoice::SetSelection, static_cast<int>(s3_sse::KmsKey::DEFAULT));
1340 		auto sse_algorithm = site.server.GetExtraParameter("ssealgorithm");
1341 		if (sse_algorithm.empty()) {
1342 			xrc_call(parent_, "ID_S3_NOENCRYPTION", &wxRadioButton::SetValue, true);
1343 		}
1344 		else if (sse_algorithm == "AES256") {
1345 			xrc_call(parent_, "ID_S3_AES256", &wxRadioButton::SetValue, true);
1346 		}
1347 		else if (sse_algorithm == "aws:kms") {
1348 			xrc_call(parent_, "ID_S3_AWSKMS", &wxRadioButton::SetValue, true);
1349 			auto sse_kms_key = site.server.GetExtraParameter("ssekmskey");
1350 			if (!sse_kms_key.empty()) {
1351 				xrc_call(parent_, "ID_S3_KMSKEY", &wxChoice::SetSelection, static_cast<int>(s3_sse::KmsKey::CUSTOM));
1352 				xrc_call(parent_, "ID_S3_CUSTOM_KMS", &wxTextCtrl::ChangeValue, sse_kms_key);
1353 			}
1354 			else {
1355 				xrc_call(parent_, "ID_S3_KMSKEY", &wxChoice::SetSelection, static_cast<int>(s3_sse::KmsKey::DEFAULT));
1356 			}
1357 		}
1358 		else if (sse_algorithm == "customer") {
1359 			xrc_call(parent_, "ID_S3_CUSTOMER_ENCRYPTION", &wxRadioButton::SetValue, true);
1360 			auto customer_key = site.server.GetExtraParameter("ssecustomerkey");
1361 			xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxTextCtrl::ChangeValue, customer_key);
1362 		}
1363 
1364 		auto value = site.server.GetExtraParameter("stsrolearn");
1365 		xrc_call(parent_, "ID_S3_ROLE_ARN", &wxTextCtrl::ChangeValue, value);
1366 		value = site.server.GetExtraParameter("stsmfaserial");
1367 		xrc_call(parent_, "ID_S3_MFA_SERIAL", &wxTextCtrl::ChangeValue, value);
1368 	}
1369 }
1370 
UpdateSite(Site & site,bool silent)1371 bool S3SiteControls::UpdateSite(Site & site, bool silent)
1372 {
1373 	CServer & server = site.server;
1374 	if (server.GetProtocol() == S3) {
1375 		server.SetExtraParameter("region", fz::to_wstring(xrc_call(parent_, "ID_S3_REGION", &wxTextCtrl::GetValue)));
1376 
1377 		if (xrc_call(parent_, "ID_S3_NOENCRYPTION", &wxRadioButton::GetValue)) {
1378 			server.ClearExtraParameter("ssealgorithm");
1379 		}
1380 		else if (xrc_call(parent_, "ID_S3_AES256", &wxRadioButton::GetValue)) {
1381 			server.SetExtraParameter("ssealgorithm", L"AES256");
1382 		}
1383 		else if (xrc_call(parent_, "ID_S3_AWSKMS", &wxRadioButton::GetValue)) {
1384 			server.SetExtraParameter("ssealgorithm", L"aws:kms");
1385 			if (xrc_call(parent_, "ID_S3_KMSKEY", &wxChoice::GetSelection) == static_cast<int>(s3_sse::KmsKey::CUSTOM)) {
1386 				auto keyId = xrc_call(parent_, "ID_S3_CUSTOM_KMS", &wxTextCtrl::GetValue).ToStdWstring();
1387 				if (keyId.empty()) {
1388 					if (!silent) {
1389 						xrc_call(parent_, "ID_S3_CUSTOM_KMS", &wxWindow::SetFocus);
1390 						wxMessageBoxEx(_("Custom KMS ARN id cannot be empty."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1391 					}
1392 					return false;
1393 				}
1394 				server.SetExtraParameter("ssekmskey", fz::to_wstring(xrc_call(parent_, "ID_S3_CUSTOM_KMS", &wxTextCtrl::GetValue)));
1395 			}
1396 		}
1397 		else if (xrc_call(parent_, "ID_S3_CUSTOMER_ENCRYPTION", &wxRadioButton::GetValue)) {
1398 			auto keyId = xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxTextCtrl::GetValue).ToStdString();
1399 			if (keyId.empty()) {
1400 				if (!silent) {
1401 					xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxWindow::SetFocus);
1402 					wxMessageBoxEx(_("Custom key cannot be empty."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1403 				}
1404 				return false;
1405 			}
1406 
1407 			std::string const base64prefix = "base64:";
1408 			if (fz::starts_with(keyId, base64prefix)) {
1409 				keyId = keyId.substr(base64prefix.size());
1410 				keyId = fz::base64_decode_s(keyId);
1411 			}
1412 
1413 			if (keyId.size() != 32) {		// 256-bit encryption key
1414 				if (!silent) {
1415 					xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxWindow::SetFocus);
1416 					wxMessageBoxEx(_("Custom key length must be 256-bit."), _("Site Manager - Invalid data"), wxICON_EXCLAMATION, wxGetTopLevelParent(&parent_));
1417 				}
1418 				return false;
1419 			}
1420 
1421 			server.SetExtraParameter("ssealgorithm", L"customer");
1422 			server.SetExtraParameter("ssecustomerkey", fz::to_wstring(xrc_call(parent_, "ID_S3_CUSTOMER_KEY", &wxTextCtrl::GetValue)));
1423 		}
1424 
1425 		auto value = fz::to_wstring(xrc_call(parent_, "ID_S3_ROLE_ARN", &wxTextCtrl::GetValue));
1426 		if (!value.empty()) {
1427 			server.SetExtraParameter("stsrolearn", value);
1428 			value = fz::to_wstring(xrc_call(parent_, "ID_S3_MFA_SERIAL", &wxTextCtrl::GetValue));
1429 			if (!value.empty()) {
1430 				server.SetExtraParameter("stsmfaserial", value);
1431 			}
1432 		}
1433 	}
1434 
1435 	return true;
1436 }
1437 
SwiftSiteControls(wxWindow & parent,DialogLayout const & lay,wxFlexGridSizer & sizer)1438 SwiftSiteControls::SwiftSiteControls(wxWindow & parent, DialogLayout const& lay, wxFlexGridSizer & sizer)
1439 	: SiteControls(parent)
1440 {
1441 	if (!sizer.IsColGrowable(0)) {
1442 		sizer.AddGrowableCol(0);
1443 	}
1444 
1445 	sizer.Add(new wxStaticText(&parent, nullID, _("Identity (Keystone):")));
1446 
1447 	auto keystone3 = new wxCheckBox(&parent, XRCID("ID_SWIFT_KEYSTONE_V3"), _("&Version 3"));
1448 	sizer.Add(keystone3);
1449 
1450 	auto *keyRow = lay.createFlex(2);
1451 	keyRow->AddGrowableCol(1);
1452 	sizer.Add(keyRow, 0, wxLEFT|wxGROW, lay.indent);
1453 	keyRow->Add(new wxStaticText(&parent, nullID, _("&Domain:")), lay.valign);
1454 	keyRow->Add(new wxTextCtrlEx(&parent, XRCID("ID_SWIFT_DOMAIN")), lay.valigng);
1455 
1456 	auto l = [this](wxEvent const&) { SetControlState(); };
1457 	keystone3->Bind(wxEVT_CHECKBOX, l);
1458 }
1459 
SetControlState()1460 void SwiftSiteControls::SetControlState()
1461 {
1462 	auto v3 = xrc_call(parent_, "ID_SWIFT_KEYSTONE_V3", &wxCheckBox::GetValue);
1463 
1464 	xrc_call(parent_, "ID_SWIFT_DOMAIN", &wxWindow::Enable, v3);
1465 }
1466 
SetSite(Site const & site)1467 void SwiftSiteControls::SetSite(Site const& site)
1468 {
1469 	if (site.server.GetProtocol() == SWIFT) {
1470 		bool v3{};
1471 		auto pv3 = site.server.GetExtraParameter("keystone_version");
1472 		if (pv3.empty()) {
1473 			v3 = fz::starts_with(site.server.GetExtraParameter("identpath"), std::wstring(L"/v3"));
1474 		}
1475 		else {
1476 			v3 = pv3 == L"3";
1477 		}
1478 		xrc_call(parent_, "ID_SWIFT_KEYSTONE_V3", &wxCheckBox::SetValue, v3);
1479 		auto domain = site.server.GetExtraParameter("domain");
1480 		if (domain.empty()) {
1481 			domain = L"Default";
1482 		}
1483 		xrc_call(parent_, "ID_SWIFT_DOMAIN", &wxTextCtrl::ChangeValue, domain);
1484 	}
1485 }
1486 
UpdateSite(Site & site,bool)1487 bool SwiftSiteControls::UpdateSite(Site & site, bool)
1488 {
1489 	CServer &server = site.server;
1490 	if (server.GetProtocol() == SWIFT) {
1491 		auto v3 = xrc_call(parent_, "ID_SWIFT_KEYSTONE_V3", &wxCheckBox::GetValue);
1492 		if (v3) {
1493 			server.SetExtraParameter("keystone_version", L"3");
1494 		}
1495 		else {
1496 			server.SetExtraParameter("keystone_version", L"2");
1497 		}
1498 
1499 		if (v3) {
1500 			auto domain = fz::to_wstring(xrc_call(parent_, "ID_SWIFT_DOMAIN", &wxTextCtrl::GetValue));
1501 			server.SetExtraParameter("domain", domain);
1502 		}
1503 		else {
1504 			server.ClearExtraParameter("domain");
1505 		}
1506 	}
1507 
1508 	return true;
1509 }
1510 
DropboxSiteControls(wxWindow & parent,DialogLayout const &,wxFlexGridSizer & sizer)1511 DropboxSiteControls::DropboxSiteControls(wxWindow & parent, DialogLayout const&, wxFlexGridSizer & sizer)
1512 	: SiteControls(parent)
1513 {
1514 	if (!sizer.IsColGrowable(0)) {
1515 		sizer.AddGrowableCol(0);
1516 	}
1517 
1518 	sizer.Add(new wxStaticText(&parent, nullID, _("Dropbox for Business:")));
1519 
1520 	auto root_ns = new wxCheckBox(&parent, XRCID("ID_USE_ROOT_NS"), _("Use &team root namespace"));
1521 	sizer.Add(root_ns);
1522 }
1523 
SetSite(Site const & site)1524 void DropboxSiteControls::SetSite(Site const& site)
1525 {
1526 	if (site.server.GetProtocol() == DROPBOX) {
1527 		auto root_ns = site.server.GetExtraParameter("root_namespace");
1528 		xrc_call(parent_, "ID_USE_ROOT_NS", &wxCheckBox::SetValue, root_ns == L"1");
1529 	}
1530 }
1531 
UpdateSite(Site & site,bool)1532 bool DropboxSiteControls::UpdateSite(Site & site, bool)
1533 {
1534 	CServer & server = site.server;
1535 	if (server.GetProtocol() == DROPBOX) {
1536 		if (xrc_call(parent_, "ID_USE_ROOT_NS", &wxCheckBox::GetValue)) {
1537 			server.SetExtraParameter("root_namespace", L"1");
1538 		}
1539 		else {
1540 			server.ClearExtraParameter("root_namespace");
1541 		}
1542 	}
1543 
1544 	return true;
1545 }
1546