1 #include "site_manager.h"
2 #include "fz_paths.h"
3 #include "ipcmutex.h"
4 
5 #include <libfilezilla/translate.hpp>
6 
7 #include <cstring>
8 
Load(std::wstring const & settings_file,CSiteManagerXmlHandler & handler,std::wstring & error)9 bool site_manager::Load(std::wstring const& settings_file, CSiteManagerXmlHandler& handler, std::wstring& error)
10 {
11 	CXmlFile file(settings_file);
12 	auto document = file.Load();
13 	if (!document) {
14 		error = file.GetError();;
15 		return false;
16 	}
17 
18 	auto element = document.child("Servers");
19 	if (!element) {
20 		return true;
21 	}
22 
23 	return Load(element, handler);
24 }
25 
Load(pugi::xml_node element,CSiteManagerXmlHandler & handler)26 bool site_manager::Load(pugi::xml_node element, CSiteManagerXmlHandler& handler)
27 {
28 	if (!element) {
29 		return false;
30 	}
31 
32 	for (auto child = element.first_child(); child; child = child.next_sibling()) {
33 		if (!strcmp(child.name(), "Folder")) {
34 			std::wstring name = GetTextElement_Trimmed(child);
35 			if (name.empty()) {
36 				continue;
37 			}
38 
39 			const bool expand = GetTextAttribute(child, "expanded") != L"0";
40 			if (!handler.AddFolder(name.substr(0, 255), expand)) {
41 				return false;
42 			}
43 			Load(child, handler);
44 			if (!handler.LevelUp()) {
45 				return false;
46 			}
47 		}
48 		else if (!strcmp(child.name(), "Server")) {
49 			std::unique_ptr<Site> data = ReadServerElement(child);
50 
51 			if (data) {
52 				handler.AddSite(std::move(data));
53 			}
54 		}
55 	}
56 
57 	return true;
58 }
59 
UpdateOneDrivePath(CServerPath & path)60 void site_manager::UpdateOneDrivePath(CServerPath& path)
61 {
62 	if (!path.empty()) {
63 		auto const & remoteDir = path.GetPath();
64 		if (!(fz::starts_with(remoteDir, fztranslate("/SharePoint")) ||
65 			fz::starts_with(remoteDir, fztranslate("/Groups")) ||
66 			fz::starts_with(remoteDir, fztranslate("/Sites")) ||
67 			fz::starts_with(remoteDir, fztranslate("/My Drives")))) {
68 
69 			path = CServerPath(fztranslate("/My Drives/OneDrive") + remoteDir);
70 		}
71 	}
72 }
73 
UpdateGoogleDrivePath(CServerPath & path)74 void site_manager::UpdateGoogleDrivePath(CServerPath& path)
75 {
76 	if (!path.empty()) {
77 		auto const & remoteDir = path;
78 		if (remoteDir == CServerPath(fztranslate("/Team drives"))) {
79 			path = CServerPath(fztranslate("/Shared drives"));
80 		}
81 		else if (remoteDir.IsSubdirOf(CServerPath(fztranslate("/Team drives")), false)) {
82 			CServerPath newRemoteDir(fztranslate("/Shared drives"));
83 
84 			std::deque<std::wstring> segments;
85 			CServerPath dir = remoteDir;
86 			while (dir.HasParent()) {
87 				segments.push_back(dir.GetLastSegment());
88 				dir.MakeParent();
89 			}
90 
91 			segments.pop_back();
92 			while (!segments.empty()) {
93 				newRemoteDir.AddSegment(segments.back());
94 				segments.pop_back();
95 			}
96 
97 			path = newRemoteDir;
98 		}
99 	}
100 }
101 
ReadBookmarkElement(Bookmark & bookmark,pugi::xml_node element)102 bool site_manager::ReadBookmarkElement(Bookmark & bookmark, pugi::xml_node element)
103 {
104 	bookmark.m_localDir = GetTextElement(element, "LocalDir");
105 	bookmark.m_remoteDir.SetSafePath(GetTextElement(element, "RemoteDir"));
106 
107 	if (bookmark.m_localDir.empty() && bookmark.m_remoteDir.empty()) {
108 		return false;
109 	}
110 
111 	if (!bookmark.m_localDir.empty() && !bookmark.m_remoteDir.empty()) {
112 		bookmark.m_sync = GetTextElementBool(element, "SyncBrowsing", false);
113 	}
114 
115 	bookmark.m_comparison = GetTextElementBool(element, "DirectoryComparison", false);
116 
117 	return true;
118 }
119 
GetColourFromIndex(int i)120 site_colour site_manager::GetColourFromIndex(int i)
121 {
122 	if (i < 0 || static_cast<unsigned int>(i) >= static_cast<unsigned int>(site_colour::end_of_list)) {
123 		return site_colour::none;
124 	}
125 	return site_colour(i);
126 }
127 
ReadServerElement(pugi::xml_node element)128 std::unique_ptr<Site> site_manager::ReadServerElement(pugi::xml_node element)
129 {
130 	auto data = std::make_unique<Site>();
131 	if (!::GetServer(element, *data)) {
132 		return nullptr;
133 	}
134 	if (data->GetName().empty()) {
135 		return nullptr;
136 	}
137 
138 	data->comments_ = GetTextElement(element, "Comments");
139 	data->m_colour = GetColourFromIndex(GetTextElementInt(element, "Colour"));
140 
141 	ReadBookmarkElement(data->m_default_bookmark, element);
142 	if (data->server.GetProtocol() == ONEDRIVE) {
143 		UpdateOneDrivePath(data->m_default_bookmark.m_remoteDir);
144 	}
145 	else if (data->server.GetProtocol() == GOOGLE_DRIVE) {
146 		UpdateGoogleDrivePath(data->m_default_bookmark.m_remoteDir);
147 	}
148 
149 	// Bookmarks
150 	for (auto bookmark = element.child("Bookmark"); bookmark; bookmark = bookmark.next_sibling("Bookmark")) {
151 		std::wstring name = GetTextElement_Trimmed(bookmark, "Name");
152 		if (name.empty()) {
153 			continue;
154 		}
155 
156 		Bookmark bookmarkData;
157 		if (ReadBookmarkElement(bookmarkData, bookmark)) {
158 			if (data->server.GetProtocol() == ONEDRIVE) {
159 				UpdateOneDrivePath(bookmarkData.m_remoteDir);
160 			}
161 			else if (data->server.GetProtocol() == GOOGLE_DRIVE) {
162 				UpdateGoogleDrivePath(bookmarkData.m_remoteDir);
163 			}
164 
165 			bookmarkData.m_name = name.substr(0, 255);
166 			data->m_bookmarks.push_back(bookmarkData);
167 		}
168 	}
169 
170 	return data;
171 }
172 
LoadPredefined(CLocalPath const & defaults_dir,CSiteManagerXmlHandler & handler)173 bool site_manager::LoadPredefined(CLocalPath const& defaults_dir, CSiteManagerXmlHandler& handler)
174 {
175 	if (defaults_dir.empty()) {
176 		return false;
177 	}
178 
179 	std::wstring const name(defaults_dir.GetPath() + L"fzdefaults.xml");
180 	CXmlFile file(name);
181 
182 	auto document = file.Load();
183 	if (!document) {
184 		return false;
185 	}
186 
187 	auto element = document.child("Servers");
188 	if (!element) {
189 		return false;
190 	}
191 
192 	if (!Load(element, handler)) {
193 		return false;
194 	}
195 
196 	return true;
197 }
198 
UnescapeSitePath(std::wstring path,std::vector<std::wstring> & result)199 bool site_manager::UnescapeSitePath(std::wstring path, std::vector<std::wstring>& result)
200 {
201 	result.clear();
202 
203 	std::wstring name;
204 	wchar_t const* p = path.c_str();
205 
206 	// Undo escapement
207 	bool lastBackslash = false;
208 	while (*p) {
209 		const wchar_t& c = *p;
210 		if (c == '\\') {
211 			if (lastBackslash) {
212 				name += L"\\";
213 				lastBackslash = false;
214 			}
215 			else {
216 				lastBackslash = true;
217 			}
218 		}
219 		else if (c == '/') {
220 			if (lastBackslash) {
221 				name += L"/";
222 				lastBackslash = 0;
223 			}
224 			else {
225 				if (!name.empty()) {
226 					result.push_back(name);
227 				}
228 				name.clear();
229 			}
230 		}
231 		else {
232 			name += *p;
233 		}
234 		++p;
235 	}
236 	if (lastBackslash) {
237 		return false;
238 	}
239 	if (!name.empty()) {
240 		result.push_back(name);
241 	}
242 
243 	return !result.empty();
244 }
245 
EscapeSegment(std::wstring segment)246 std::wstring site_manager::EscapeSegment(std::wstring segment)
247 {
248 	fz::replace_substrings(segment, L"\\", L"\\\\");
249 	fz::replace_substrings(segment, L"/", L"\\/");
250 	return segment;
251 }
252 
BuildPath(wchar_t root,std::vector<std::wstring> const & segments)253 std::wstring site_manager::BuildPath(wchar_t root, std::vector<std::wstring> const& segments)
254 {
255 	std::wstring ret;
256 	ret += root;
257 	for (auto const& segment : segments) {
258 		ret += L"/" + EscapeSegment(segment);
259 	}
260 
261 	return ret;
262 }
263 
GetSiteByPath(app_paths const & paths,std::wstring sitePath,std::wstring & error)264 std::pair<std::unique_ptr<Site>, Bookmark> site_manager::GetSiteByPath(app_paths const& paths, std::wstring sitePath, std::wstring& error)
265 {
266 	std::pair<std::unique_ptr<Site>, Bookmark> ret;
267 	wchar_t c = sitePath.empty() ? 0 : sitePath[0];
268 	if (c != '0' && c != '1') {
269 		error = fz::translate("Site path has to begin with 0 or 1.");
270 		return ret;
271 	}
272 
273 	sitePath = sitePath.substr(1);
274 
275 	// We have to synchronize access to sitemanager.xml so that multiple processed don't write
276 	// to the same file or one is reading while the other one writes.
277 	CInterProcessMutex mutex(MUTEX_SITEMANAGER);
278 
279 	CXmlFile file;
280 	if (c == '0') {
281 		file.SetFileName(paths.settings_file(L"sitemanager"));
282 	}
283 	else {
284 		CLocalPath const defaultsDir = paths.defaults_path;
285 		if (defaultsDir.empty()) {
286 			error = fz::translate("Site does not exist.");
287 			return ret;
288 		}
289 		file.SetFileName(defaultsDir.GetPath() + L"fzdefaults.xml");
290 	}
291 
292 	auto document = file.Load();
293 	if (!document) {
294 		error = fz::translate("Error loading xml file");
295 		return ret;
296 	}
297 
298 	auto element = document.child("Servers");
299 	if (!element) {
300 		error = fz::translate("Site does not exist.");
301 		return ret;
302 	}
303 
304 	std::vector<std::wstring> segments;
305 	if (!UnescapeSitePath(sitePath, segments) || segments.empty()) {
306 		error = fz::translate("Site path is malformed.");
307 		return ret;
308 	}
309 
310 	auto child = GetElementByPath(element, segments);
311 	if (!child) {
312 		error = fz::translate("Site does not exist.");
313 		return ret;
314 	}
315 
316 	pugi::xml_node bookmark;
317 	if (!strcmp(child.name(), "Bookmark")) {
318 		bookmark = child;
319 		child = child.parent();
320 		segments.pop_back();
321 	}
322 
323 	ret.first = ReadServerElement(child);
324 	if (!ret.first) {
325 		error = fz::translate("Could not read server item.");
326 	}
327 	else {
328 		if (bookmark) {
329 			Bookmark bm;
330 			if (ReadBookmarkElement(bm, bookmark)) {
331 				ret.second = bm;
332 			}
333 		}
334 		else {
335 			ret.second = ret.first->m_default_bookmark;
336 		}
337 
338 		ret.first->SetSitePath(BuildPath(c, segments));
339 	}
340 
341 	return ret;
342 }
343 
GetElementByPath(pugi::xml_node node,std::vector<std::wstring> const & segments)344 pugi::xml_node site_manager::GetElementByPath(pugi::xml_node node, std::vector<std::wstring> const& segments)
345 {
346 	for (auto const& segment : segments) {
347 		pugi::xml_node child;
348 		for (child = node.first_child(); child; child = child.next_sibling()) {
349 			if (strcmp(child.name(), "Server") && strcmp(child.name(), "Folder") && strcmp(child.name(), "Bookmark")) {
350 				continue;
351 			}
352 
353 			std::wstring name = GetTextElement_Trimmed(child, "Name");
354 			if (name.empty()) {
355 				name = GetTextElement_Trimmed(child);
356 			}
357 			if (name.empty()) {
358 				continue;
359 			}
360 
361 			if (name == segment) {
362 				break;
363 			}
364 		}
365 		if (!child) {
366 			return pugi::xml_node();
367 		}
368 
369 		node = child;
370 		continue;
371 	}
372 
373 	return node;
374 }
375