source: filezilla/trunk/fuentes/src/interface/xmlfunctions.cpp @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 2 years ago

Update new version: 3.15.02

File size: 17.1 KB
Line 
1#include <filezilla.h>
2#include "buildinfo.h"
3#include "xmlfunctions.h"
4#include "Options.h"
5#include <wx/ffile.h>
6#include <wx/log.h>
7#include <wx/base64.h>
8
9#include <libfilezilla/local_filesys.hpp>
10
11CXmlFile::CXmlFile(wxString const& fileName, wxString const& root)
12{
13        if (!root.empty()) {
14                m_rootName = root;
15        }
16        SetFileName(fileName);
17}
18
19void CXmlFile::SetFileName(const wxString& name)
20{
21        wxASSERT(!name.empty());
22        m_fileName = name;
23        m_modificationTime = fz::datetime();
24}
25
26CXmlFile::~CXmlFile()
27{
28        Close();
29}
30
31pugi::xml_node CXmlFile::Load()
32{
33        Close();
34        m_error.clear();
35
36        wxCHECK(!m_fileName.empty(), m_element);
37
38        wxString redirectedName = GetRedirectedName();
39
40        GetXmlFile(redirectedName);
41        if (!m_element) {
42                wxString err = wxString::Format(_("The file '%s' could not be loaded."), m_fileName);
43                if (m_error.empty()) {
44                        err += wxString(_T("\n")) + _("Make sure the file can be accessed and is a well-formed XML document.");
45                }
46                else {
47                        err += _T("\n") + m_error;
48                }
49
50                // Try the backup file
51                GetXmlFile(redirectedName + _T("~"));
52                if (!m_element) {
53                        // Loading backup failed. If both original and backup file are empty, create new file.
54                        if (fz::local_filesys::get_size(fz::to_native(redirectedName)) <= 0 && fz::local_filesys::get_size(fz::to_native(redirectedName + _T("~"))) <= 0) {
55                                m_error.clear();
56                                CreateEmpty();
57                                m_modificationTime = fz::local_filesys::get_modification_time(fz::to_native(redirectedName));
58                                return m_element;
59                        }
60
61                        // File corrupt and no functional backup, give up.
62                        m_error = err;
63                        m_modificationTime.clear();
64                        return m_element;
65                }
66
67
68                // Loading the backup file succeeded, restore file
69                bool res;
70                {
71                        wxLogNull null;
72                        res = wxCopyFile(redirectedName + _T("~"), redirectedName);
73                }
74                if (!res) {
75                        // Could not restore backup, give up.
76                        Close();
77                        m_error = err;
78                        m_error += _T("\n") + wxString::Format(_("The valid backup file %s could not be restored"), redirectedName + _T("~"));
79                        m_modificationTime.clear();
80                        return m_element;
81                }
82
83                // We no longer need the backup
84                wxRemoveFile(redirectedName + _T("~"));
85                m_error.clear();
86        }
87
88        m_modificationTime = fz::local_filesys::get_modification_time(fz::to_native(redirectedName));
89        return m_element;
90}
91
92bool CXmlFile::Modified()
93{
94        wxCHECK(!m_fileName.empty(), false);
95
96        if (!m_modificationTime.empty())
97                return true;
98
99        fz::datetime const modificationTime = fz::local_filesys::get_modification_time(fz::to_native(m_fileName));
100        if (modificationTime.empty() && modificationTime == m_modificationTime)
101                return false;
102
103        return true;
104}
105
106void CXmlFile::Close()
107{
108        m_element = pugi::xml_node();
109        m_document.reset();
110}
111
112void CXmlFile::UpdateMetadata()
113{
114        if (!m_element || std::string(m_element.name()) != "FileZilla3") {
115                return;
116        }
117
118        SetTextAttribute(m_element, "version", CBuildInfo::GetVersion());
119
120        wxString const platform =
121#ifdef __WXMSW__
122                _T("windows");
123#elif defined(__WXMAX__)
124                _T("mac");
125#else
126                _T("*nix");
127#endif
128        SetTextAttribute(m_element, "platform", platform);
129}
130
131bool CXmlFile::Save(bool printError)
132{
133        m_error.clear();
134
135        wxCHECK(!m_fileName.empty(), false);
136        wxCHECK(m_document, false);
137
138        UpdateMetadata();
139
140        bool res = SaveXmlFile();
141        m_modificationTime = fz::local_filesys::get_modification_time(fz::to_native(m_fileName));
142
143        if (!res && printError) {
144                wxASSERT(!m_error.empty());
145
146                wxString msg = wxString::Format(_("Could not write \"%s\":"), m_fileName);
147                wxMessageBoxEx(msg + _T("\n") + m_error, _("Error writing xml file"), wxICON_ERROR);
148        }
149        return res;
150}
151
152pugi::xml_node CXmlFile::CreateEmpty()
153{
154        Close();
155
156        pugi::xml_node decl = m_document.append_child(pugi::node_declaration);
157        decl.append_attribute("version") = "1.0";
158        decl.append_attribute("encoding") = "UTF-8";
159
160        m_element = m_document.append_child(m_rootName.utf8_str());
161        return m_element;
162}
163
164wxString ConvLocal(const char *value)
165{
166        return wxString(wxConvUTF8.cMB2WC(value), *wxConvCurrent);
167}
168
169pugi::xml_node AddTextElement(pugi::xml_node node, const char* name, const wxString& value, bool overwrite)
170{
171        pugi::xml_node ret;
172
173        wxASSERT(node);
174
175        wxScopedCharBuffer utf8 = value.utf8_str();
176        if (utf8) {
177                ret = AddTextElementRaw(node, name, utf8, overwrite);
178        }
179
180        return ret;
181}
182
183void AddTextElement(pugi::xml_node node, const char* name, int64_t value, bool overwrite)
184{
185        if (overwrite) {
186                node.remove_child(name);
187        }
188        auto child = node.append_child(name);
189        child.text().set(static_cast<long long>(value));
190}
191
192pugi::xml_node AddTextElementRaw(pugi::xml_node node, const char* name, const char* value, bool overwrite)
193{
194        wxASSERT(node);
195        wxASSERT(value);
196
197        if (overwrite) {
198                node.remove_child(name);
199        }
200
201        auto element = node.append_child(name);
202        if (*value)
203                element.text().set(value);
204
205        return element;
206}
207
208void AddTextElement(pugi::xml_node node, const wxString& value)
209{
210        wxASSERT(node);
211        wxASSERT(value);
212
213        wxScopedCharBuffer utf8 = value.utf8_str();
214        if (!utf8)
215                return;
216
217        AddTextElementRaw(node, utf8);
218}
219
220void AddTextElement(pugi::xml_node node, int64_t value)
221{
222        wxASSERT(node);
223        node.text().set(static_cast<long long>(value));
224}
225
226void AddTextElementRaw(pugi::xml_node node, const char* value)
227{
228        wxASSERT(node);
229        wxASSERT(value);
230
231        if (*value)
232                node.text().set(value);
233        else {
234                node.text().set("");
235        }
236}
237
238wxString GetTextElement_Trimmed(pugi::xml_node node, const char* name)
239{
240        wxString t = GetTextElement(node, name);
241        t.Trim(true);
242        t.Trim(false);
243
244        return t;
245}
246
247wxString GetTextElement(pugi::xml_node node, const char* name)
248{
249        wxASSERT(node);
250
251        return ConvLocal(node.child_value(name));
252}
253
254wxString GetTextElement_Trimmed(pugi::xml_node node)
255{
256        wxString t = GetTextElement(node);
257        t.Trim(true);
258        t.Trim(false);
259
260        return t;
261}
262
263wxString GetTextElement(pugi::xml_node node)
264{
265        wxASSERT(node);
266
267        return ConvLocal(node.child_value());
268}
269
270int64_t GetTextElementInt(pugi::xml_node node, const char* name, int defValue /*=0*/)
271{
272        wxASSERT(node);
273        return static_cast<int64_t>(node.child(name).text().as_llong(defValue));
274}
275
276bool GetTextElementBool(pugi::xml_node node, const char* name, bool defValue /*=false*/)
277{
278        wxASSERT(node);
279        return node.child(name).text().as_bool(defValue);
280}
281
282// Opens the specified XML file if it exists or creates a new one otherwise.
283// Returns 0 on error.
284bool CXmlFile::GetXmlFile(wxString const& file)
285{
286        Close();
287
288        if (fz::local_filesys::get_size(fz::to_native(file)) <= 0) {
289                return false;
290        }
291
292        // File exists, open it
293        auto result = m_document.load_file(static_cast<wchar_t const*>(file.c_str()));
294        if (!result) {
295                m_error += wxString::Format(_T("%s at offset %lld."), wxString(result.description()), static_cast<long long>(result.offset));
296                return false;
297        }
298
299        m_element = m_document.child(m_rootName.utf8_str());
300        if (!m_element) {
301                if (m_document.first_child()) { // Beware: parse_declaration and parse_doctype can break this
302                        // Not created by FileZilla3
303                        Close();
304                        m_error = _("Unknown root element, the file does not appear to be generated by FileZilla.");
305                        return false;
306                }
307                m_element = m_document.append_child(m_rootName.utf8_str());
308        }
309
310        return true;
311}
312
313wxString CXmlFile::GetRedirectedName() const
314{
315        wxString redirectedName = m_fileName;
316        bool isLink = false;
317        if (fz::local_filesys::get_file_info(fz::to_native(redirectedName), isLink, 0, 0, 0) == fz::local_filesys::file) {
318                if (isLink) {
319                        CLocalPath target(fz::local_filesys::get_link_target(fz::to_native(redirectedName)));
320                        if (!target.empty()) {
321                                redirectedName = target.GetPath();
322                                redirectedName.RemoveLast();
323                        }
324                }
325        }
326        return redirectedName;
327}
328
329bool CXmlFile::SaveXmlFile()
330{
331        bool exists = false;
332
333        bool isLink = false;
334        int flags = 0;
335
336        wxString redirectedName = GetRedirectedName();
337        if (fz::local_filesys::get_file_info(fz::to_native(redirectedName), isLink, 0, 0, &flags) == fz::local_filesys::file) {
338#ifdef __WXMSW__
339                if (flags & FILE_ATTRIBUTE_HIDDEN)
340                        SetFileAttributes(redirectedName.c_str(), flags & ~FILE_ATTRIBUTE_HIDDEN);
341#endif
342
343                exists = true;
344                bool res;
345                {
346                        wxLogNull null;
347                        res = wxCopyFile(redirectedName, redirectedName + _T("~"));
348                }
349                if (!res) {
350                        m_error = _("Failed to create backup copy of xml file");
351                        return false;
352                }
353        }
354
355        bool success = m_document.save_file(static_cast<wchar_t const*>(redirectedName.c_str()));
356
357        if (!success) {
358                wxRemoveFile(redirectedName);
359                if (exists) {
360                        wxLogNull null;
361                        wxRenameFile(redirectedName + _T("~"), redirectedName);
362                }
363                m_error = _("Failed to write xml file");
364                return false;
365        }
366
367        if (exists)
368                wxRemoveFile(redirectedName + _T("~"));
369
370        return true;
371}
372
373bool GetServer(pugi::xml_node node, CServer& server)
374{
375        wxASSERT(node);
376
377        wxString host = GetTextElement(node, "Host");
378        if (host.empty())
379                return false;
380
381        int port = GetTextElementInt(node, "Port");
382        if (port < 1 || port > 65535)
383                return false;
384
385        if (!server.SetHost(host, port))
386                return false;
387
388        int const protocol = GetTextElementInt(node, "Protocol");
389        if (protocol < 0 || protocol > ServerProtocol::MAX_VALUE) {
390                return false;
391        }
392        server.SetProtocol(static_cast<ServerProtocol>(protocol));
393
394        int type = GetTextElementInt(node, "Type");
395        if (type < 0 || type >= SERVERTYPE_MAX)
396                return false;
397
398        server.SetType((enum ServerType)type);
399
400        int logonType = GetTextElementInt(node, "Logontype");
401        if (logonType < 0)
402                return false;
403
404        server.SetLogonType((enum LogonType)logonType);
405
406        if (server.GetLogonType() != ANONYMOUS) {
407                wxString user = GetTextElement(node, "User");
408
409                wxString pass, key;
410                if ((long)NORMAL == logonType || (long)ACCOUNT == logonType) {
411                        auto  passElement = node.child("Pass");
412                        if (passElement) {
413                                pass = GetTextElement(passElement);
414
415                                wxString encoding = GetTextAttribute(passElement, "encoding");
416
417                                if (encoding == _T("base64")) {
418                                        wxMemoryBuffer buf = wxBase64Decode(pass);
419                                        if (!buf.IsEmpty()) {
420                                                pass = wxString::FromUTF8(static_cast<const char*>(buf.GetData()), buf.GetDataLen());
421                                        }
422                                        else {
423                                                pass.clear();
424                                        }
425                                }
426                                else if (!encoding.empty()) {
427                                        pass.clear();
428                                        server.SetLogonType(ASK);
429                                }
430                        }
431                } else if ((long)KEY == logonType) {
432                        key = GetTextElement(node, "Keyfile");
433
434                        // password should be empty if we're using a key file
435                        pass = wxString();
436
437                        server.SetKeyFile(key);
438                }
439
440                if (!server.SetUser(user, pass))
441                        return false;
442
443                if ((long)ACCOUNT == logonType) {
444                        wxString account = GetTextElement(node, "Account");
445                        if (account.empty())
446                                return false;
447                        if (!server.SetAccount(account))
448                                return false;
449                }
450        }
451
452        int timezoneOffset = GetTextElementInt(node, "TimezoneOffset");
453        if (!server.SetTimezoneOffset(timezoneOffset))
454                return false;
455
456        wxString pasvMode = GetTextElement(node, "PasvMode");
457        if (pasvMode == _T("MODE_PASSIVE"))
458                server.SetPasvMode(MODE_PASSIVE);
459        else if (pasvMode == _T("MODE_ACTIVE"))
460                server.SetPasvMode(MODE_ACTIVE);
461        else
462                server.SetPasvMode(MODE_DEFAULT);
463
464        int maximumMultipleConnections = GetTextElementInt(node, "MaximumMultipleConnections");
465        server.MaximumMultipleConnections(maximumMultipleConnections);
466
467        wxString encodingType = GetTextElement(node, "EncodingType");
468        if (encodingType == _T("Auto"))
469                server.SetEncodingType(ENCODING_AUTO);
470        else if (encodingType == _T("UTF-8"))
471                server.SetEncodingType(ENCODING_UTF8);
472        else if (encodingType == _T("Custom")) {
473                wxString customEncoding = GetTextElement(node, "CustomEncoding");
474                if (customEncoding.empty())
475                        return false;
476                if (!server.SetEncodingType(ENCODING_CUSTOM, customEncoding))
477                        return false;
478        }
479        else
480                server.SetEncodingType(ENCODING_AUTO);
481
482        if (CServer::SupportsPostLoginCommands(server.GetProtocol())) {
483                std::vector<wxString> postLoginCommands;
484                auto element = node.child("PostLoginCommands");
485                if (element) {
486                        for (auto commandElement = element.child("Command"); commandElement; commandElement = commandElement.next_sibling("Command")) {
487                                wxString command = ConvLocal(commandElement.child_value());
488                                if (!command.empty()) {
489                                        postLoginCommands.push_back(command);
490                                }
491                        }
492                }
493                if (!server.SetPostLoginCommands(postLoginCommands))
494                        return false;
495        }
496
497        server.SetBypassProxy(GetTextElementInt(node, "BypassProxy", false) == 1);
498        server.SetName(GetTextElement_Trimmed(node, "Name"));
499
500        if (server.GetName().empty())
501                server.SetName(GetTextElement_Trimmed(node));
502
503        return true;
504}
505
506void SetServer(pugi::xml_node node, const CServer& server)
507{
508        if (!node)
509                return;
510
511        bool kiosk_mode = COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) != 0;
512
513        for (auto child = node.first_child(); child; child = node.first_child()) {
514                node.remove_child(child);
515        }
516
517        AddTextElement(node, "Host", server.GetHost());
518        AddTextElement(node, "Port", server.GetPort());
519        AddTextElement(node, "Protocol", server.GetProtocol());
520        AddTextElement(node, "Type", server.GetType());
521
522        enum LogonType logonType = server.GetLogonType();
523
524        if (server.GetLogonType() != ANONYMOUS) {
525                AddTextElement(node, "User", server.GetUser());
526
527                if (server.GetLogonType() == NORMAL || server.GetLogonType() == ACCOUNT) {
528                        if (kiosk_mode)
529                                logonType = ASK;
530                        else {
531                                wxString pass = server.GetPass();
532                                auto const& buf = pass.utf8_str(); // wxWidgets has such an ugly string API....
533                                std::string utf8(buf.data(), buf.length());
534
535                                wxString base64 = wxBase64Encode(utf8.c_str(), utf8.size());
536                                pugi::xml_node passElement = AddTextElement(node, "Pass", base64);
537                                if (passElement) {
538                                        SetTextAttribute(passElement, "encoding", _T("base64"));
539                                }
540
541                                if (server.GetLogonType() == ACCOUNT)
542                                        AddTextElement(node, "Account", server.GetAccount());
543                        }
544                }
545                else if (server.GetLogonType() == KEY)
546                {
547                        AddTextElement(node, "Keyfile", server.GetKeyFile());
548                }
549        }
550        AddTextElement(node, "Logontype", logonType);
551
552        AddTextElement(node, "TimezoneOffset", server.GetTimezoneOffset());
553        switch (server.GetPasvMode())
554        {
555        case MODE_PASSIVE:
556                AddTextElementRaw(node, "PasvMode", "MODE_PASSIVE");
557                break;
558        case MODE_ACTIVE:
559                AddTextElementRaw(node, "PasvMode", "MODE_ACTIVE");
560                break;
561        default:
562                AddTextElementRaw(node, "PasvMode", "MODE_DEFAULT");
563                break;
564        }
565        AddTextElement(node, "MaximumMultipleConnections", server.MaximumMultipleConnections());
566
567        switch (server.GetEncodingType())
568        {
569        case ENCODING_AUTO:
570                AddTextElementRaw(node, "EncodingType", "Auto");
571                break;
572        case ENCODING_UTF8:
573                AddTextElementRaw(node, "EncodingType", "UTF-8");
574                break;
575        case ENCODING_CUSTOM:
576                AddTextElementRaw(node, "EncodingType", "Custom");
577                AddTextElement(node, "CustomEncoding", server.GetCustomEncoding());
578                break;
579        }
580
581        if (CServer::SupportsPostLoginCommands(server.GetProtocol())) {
582                std::vector<wxString> const& postLoginCommands = server.GetPostLoginCommands();
583                if (!postLoginCommands.empty()) {
584                        auto element = node.append_child("PostLoginCommands");
585                        for (std::vector<wxString>::const_iterator iter = postLoginCommands.begin(); iter != postLoginCommands.end(); ++iter) {
586                                AddTextElement(element, "Command", *iter);
587                        }                               
588                }
589        }
590
591        AddTextElementRaw(node, "BypassProxy", server.GetBypassProxy() ? "1" : "0");
592        const wxString& name = server.GetName();
593        if (!name.empty())
594                AddTextElement(node, "Name", name);
595}
596
597void SetTextAttribute(pugi::xml_node node, const char* name, const wxString& value)
598{
599        wxASSERT(node);
600
601        wxScopedCharBuffer utf8 = value.utf8_str();
602        if (!utf8)
603                return;
604
605        auto attribute = node.attribute(name);
606        if (!attribute) {
607                attribute = node.append_attribute(name);
608        }
609        attribute.set_value(utf8);
610}
611
612wxString GetTextAttribute(pugi::xml_node node, const char* name)
613{
614        wxASSERT(node);
615
616        const char* value = node.attribute(name).value();
617        return ConvLocal(value);
618}
619
620pugi::xml_node FindElementWithAttribute(pugi::xml_node node, const char* element, const char* attribute, const char* value)
621{
622        pugi::xml_node child = element ? node.child(element) : node.first_child();
623        while (child) {
624                const char* nodeVal = child.attribute(attribute).value();
625                if (nodeVal && !strcmp(value, nodeVal))
626                        return child;
627
628                child = element ? child.next_sibling(element) : child.next_sibling();
629        }
630
631        return child;
632}
633
634int GetAttributeInt(pugi::xml_node node, const char* name)
635{
636        return node.attribute(name).as_int();
637}
638
639void SetAttributeInt(pugi::xml_node node, const char* name, int value)
640{
641        auto attribute = node.attribute(name);
642        if (!attribute) {
643                attribute = node.append_attribute(name);
644        }
645        attribute.set_value(value);
646}
647
648namespace {
649struct xml_memory_writer : pugi::xml_writer
650{
651        size_t written{};
652        char* buffer{};
653        size_t remaining{};
654
655        virtual void write(const void* data, size_t size)
656        {
657                if (buffer && size <= remaining) {
658                        memcpy(buffer, data, size);
659                        buffer += size;
660                        remaining -= size;
661                }
662                written += size;
663        }
664};
665}
666
667size_t CXmlFile::GetRawDataLength()
668{
669        if (!m_document)
670                return 0;
671
672        xml_memory_writer writer;
673        m_document.save(writer);
674        return writer.written;
675}
676
677void CXmlFile::GetRawDataHere(char* p, size_t size) // p has to big enough to hold at least GetRawDataLength() bytes
678{
679        if (size) {
680                memset(p, 0, size);
681        }
682        xml_memory_writer writer;
683        writer.buffer = p;
684        writer.remaining = size;
685        m_document.save(writer);
686}
687
688bool CXmlFile::ParseData(char* data)
689{
690        Close();
691        m_document.load_string(data);
692        m_element = m_document.child(m_rootName.mb_str());
693        if (!m_element) {
694                Close();
695        }
696        return !!m_element;
697}
698
699bool CXmlFile::IsFromFutureVersion() const
700{
701        if (!m_element) {
702                return false;
703        }
704        wxString const version = GetTextAttribute(m_element, "version");
705        return CBuildInfo::ConvertToVersionNumber(CBuildInfo::GetVersion().c_str()) < CBuildInfo::ConvertToVersionNumber(version.c_str());
706}
Note: See TracBrowser for help on using the repository browser.