source: filezilla/trunk/fuentes/src/engine/directorylistingparser.cpp @ 3185

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

Update new version: 3.15.02

File size: 66.1 KB
Line 
1#include <filezilla.h>
2#include "directorylistingparser.h"
3#include "ControlSocket.h"
4
5#include <algorithm>
6#include <vector>
7
8std::map<std::wstring, int> CDirectoryListingParser::m_MonthNamesMap;
9
10//#define LISTDEBUG_MVS
11//#define LISTDEBUG
12#ifdef LISTDEBUG
13static char const data[][150]={
14        "" // Has to be terminated with empty string
15};
16
17#endif
18
19namespace {
20struct ObjectCache
21{
22        CRefcountObject<std::wstring> const& get(std::wstring const& v)
23        {
24                auto it = std::lower_bound(cache.begin(), cache.end(), v);
25
26                if (it == cache.end() || !(*it == v)) {
27                        it = cache.emplace(it, v);
28                }
29                return *it;
30        }
31
32        // Vector coupled with binary search and sorted insertion is fastest
33        // alternative as we expect a relatively low amount of inserts.
34        // Note that we cannot use set, as it it cannot search based on a different type.
35        std::vector<CRefcountObject<std::wstring>> cache;
36};
37
38
39ObjectCache objcache;
40}
41
42class CToken final
43{
44protected:
45        enum TokenInformation
46        {
47                Unknown,
48                Yes,
49                No
50        };
51
52public:
53        CToken() = default;
54
55        enum t_numberBase
56        {
57                decimal,
58                hex
59        };
60
61        CToken(wchar_t const* p, unsigned int len)
62                : m_pToken(p)
63                , m_len(len)
64        {}
65
66        wchar_t const* GetToken() const
67        {
68                return m_pToken;
69        }
70
71        unsigned int GetLength() const
72        {
73                return m_len;
74        }
75
76        std::wstring GetString() const
77        {
78                if (!m_pToken || !m_len) {
79                        return std::wstring();
80                }
81                else {
82                        return std::wstring(m_pToken, m_len);
83                }
84        }
85
86        bool IsNumeric(t_numberBase base = decimal)
87        {
88                switch (base)
89                {
90                case decimal:
91                default:
92                        if (m_numeric == Unknown)
93                        {
94                                m_numeric = Yes;
95                                for (unsigned int i = 0; i < m_len; ++i)
96                                        if (m_pToken[i] < '0' || m_pToken[i] > '9')
97                                        {
98                                                m_numeric = No;
99                                                break;
100                                        }
101                        }
102                        return m_numeric == Yes;
103                case hex:
104                        for (unsigned int i = 0; i < m_len; ++i)
105                        {
106                                const char c = m_pToken[i];
107                                if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && (c < 'a' || c > 'f'))
108                                        return false;
109                        }
110                        return true;
111                }
112        }
113
114        bool IsNumeric(unsigned int start, unsigned int len)
115        {
116                for (unsigned int i = start; i < std::min(start + len, m_len); ++i)
117                        if (m_pToken[i] < '0' || m_pToken[i] > '9')
118                                return false;
119                return true;
120        }
121
122        bool IsLeftNumeric()
123        {
124                if (m_leftNumeric == Unknown) {
125                        if (m_len < 2)
126                                m_leftNumeric = No;
127                        else if (m_pToken[0] < '0' || m_pToken[0] > '9')
128                                m_leftNumeric = No;
129                        else
130                                m_leftNumeric = Yes;
131                }
132                return m_leftNumeric == Yes;
133        }
134
135        bool IsRightNumeric()
136        {
137                if (m_rightNumeric == Unknown) {
138                        if (m_len < 2)
139                                m_rightNumeric = No;
140                        else if (m_pToken[m_len - 1] < '0' || m_pToken[m_len - 1] > '9')
141                                m_rightNumeric = No;
142                        else
143                                m_rightNumeric = Yes;
144                }
145                return m_rightNumeric == Yes;
146        }
147
148        int Find(const wchar_t* chr, int start = 0) const
149        {
150                if (!chr)
151                        return -1;
152
153                for (unsigned int i = start; i < m_len; ++i) {
154                        for (int c = 0; chr[c]; ++c) {
155                                if (m_pToken[i] == chr[c])
156                                        return i;
157                        }
158                }
159                return -1;
160        }
161
162        int Find(wchar_t chr, int start = 0) const
163        {
164                if (!m_pToken)
165                        return -1;
166
167                for (unsigned int i = start; i < m_len; ++i)
168                        if (m_pToken[i] == chr)
169                                return i;
170
171                return -1;
172        }
173
174        int64_t GetNumber(unsigned int start, int len)
175        {
176                if (len == -1)
177                        len = m_len - start;
178                if (len < 1)
179                        return -1;
180
181                if (start + static_cast<unsigned int>(len) > m_len)
182                        return -1;
183
184                if (m_pToken[start] < '0' || m_pToken[start] > '9')
185                        return -1;
186
187                int64_t number = 0;
188                for (unsigned int i = start; i < (start + len); ++i)
189                {
190                        if (m_pToken[i] < '0' || m_pToken[i] > '9')
191                                break;
192                        number *= 10;
193                        number += m_pToken[i] - '0';
194                }
195                return number;
196        }
197
198        int64_t GetNumber(t_numberBase base = decimal)
199        {
200                switch (base) {
201                default:
202                case decimal:
203                        if (m_number == -1) {
204                                if (IsNumeric() || IsLeftNumeric()) {
205                                        m_number = 0;
206                                        for (unsigned int i = 0; i < m_len; ++i) {
207                                                if (m_pToken[i] < '0' || m_pToken[i] > '9')
208                                                        break;
209                                                m_number *= 10;
210                                                m_number += m_pToken[i] - '0';
211                                        }
212                                }
213                                else if (IsRightNumeric()) {
214                                        m_number = 0;
215                                        int start = m_len - 1;
216                                        while (m_pToken[start - 1] >= '0' && m_pToken[start - 1] <= '9')
217                                                --start;
218                                        for (unsigned int i = start; i < m_len; ++i)
219                                        {
220                                                m_number *= 10;
221                                                m_number += m_pToken[i] - '0';
222                                        }
223                                }
224                        }
225                        return m_number;
226                case hex:
227                        {
228                                int64_t number = 0;
229                                for (unsigned int i = 0; i < m_len; ++i) {
230                                        const wchar_t& c = m_pToken[i];
231                                        if (c >= '0' && c <= '9') {
232                                                number *= 16;
233                                                number += c - '0';
234                                        }
235                                        else if (c >= 'a' && c <= 'f') {
236                                                number *= 16;
237                                                number += c - '0' + 10;
238                                        }
239                                        else if (c >= 'A' && c <= 'F') {
240                                                number *= 16;
241                                                number += c - 'A' + 10;
242                                        }
243                                        else
244                                                return -1;
245                                }
246                                return number;
247                        }
248                }
249        }
250
251        wchar_t operator[](unsigned int n) const
252        {
253                if (n >= m_len)
254                        return 0;
255
256                return m_pToken[n];
257        }
258
259protected:
260        wchar_t const* m_pToken{};
261        unsigned int m_len{};
262
263        TokenInformation m_numeric{Unknown};
264        TokenInformation m_leftNumeric{Unknown};
265        TokenInformation m_rightNumeric{Unknown};
266        int64_t m_number{-1};
267};
268
269class CLine
270{
271public:
272        CLine(wchar_t* p, int len = -1, int trailing_whitespace = 0)
273        {
274                m_pLine = p;
275                if (len >= 0)
276                        m_len = len;
277                else
278                        m_len = fz::strlen(p);
279
280                m_parsePos = 0;
281
282                m_Tokens.reserve(10);
283                m_LineEndTokens.reserve(10);
284                m_trailing_whitespace = trailing_whitespace;
285        }
286
287        ~CLine()
288        {
289                delete [] m_pLine;
290
291                std::vector<CToken *>::iterator iter;
292                for (iter = m_Tokens.begin(); iter != m_Tokens.end(); ++iter)
293                        delete *iter;
294                for (iter = m_LineEndTokens.begin(); iter != m_LineEndTokens.end(); ++iter)
295                        delete *iter;
296        }
297
298        bool GetToken(unsigned int n, CToken &token, bool toEnd = false, bool include_whitespace = false)
299        {
300                n += offset_;
301                if (!toEnd) {
302                        if (m_Tokens.size() > n) {
303                                token = *(m_Tokens[n]);
304                                return true;
305                        }
306
307                        int start = m_parsePos;
308                        while (m_parsePos < m_len) {
309                                if (m_pLine[m_parsePos] == ' ' || m_pLine[m_parsePos] == '\t') {
310                                        CToken *pToken = new CToken(m_pLine + start, m_parsePos - start);
311                                        m_Tokens.push_back(pToken);
312
313                                        while (m_parsePos < m_len && (m_pLine[m_parsePos] == ' ' || m_pLine[m_parsePos] == '\t'))
314                                                ++m_parsePos;
315
316                                        if (m_Tokens.size() > n) {
317                                                token = *(m_Tokens[n]);
318                                                return true;
319                                        }
320
321                                        start = m_parsePos;
322                                }
323                                ++m_parsePos;
324                        }
325                        if (m_parsePos != start) {
326                                CToken *pToken = new CToken(m_pLine + start, m_parsePos - start);
327                                        m_Tokens.push_back(pToken);
328                        }
329
330                        if (m_Tokens.size() > n) {
331                                token = *(m_Tokens[n]);
332                                return true;
333                        }
334
335                        return false;
336                }
337                else {
338                        if (include_whitespace) {
339                                int prev = n - offset_;
340                                if (prev)
341                                        --prev;
342
343                                CToken ref;
344                                if (!GetToken(prev, ref))
345                                        return false;
346                                wchar_t const* p = ref.GetToken() + ref.GetLength() + 1;
347
348                                auto const newLen = m_len - (p - m_pLine);
349                                if (newLen <= 0) {
350                                        return false;
351                                }
352                                token = CToken(p, newLen);
353                                return true;
354                        }
355
356                        if (m_LineEndTokens.size() > n) {
357                                token = *(m_LineEndTokens[n]);
358                                return true;
359                        }
360
361                        if (m_Tokens.size() <= n)
362                                if (!GetToken(n - offset_, token))
363                                        return false;
364
365                        for (unsigned int i = static_cast<unsigned int>(m_LineEndTokens.size()); i <= n; ++i) {
366                                const CToken *refToken = m_Tokens[i];
367                                const wchar_t* p = refToken->GetToken();
368                                auto const newLen = m_len - (p - m_pLine) - m_trailing_whitespace;
369                                if (newLen <= 0) {
370                                        return false;
371                                }
372                                CToken *pToken = new CToken(p, newLen);
373                                m_LineEndTokens.push_back(pToken);
374                        }
375                        token = *(m_LineEndTokens[n]);
376                        return true;
377                }
378        };
379
380        CLine *Concat(const CLine *pLine) const
381        {
382                int newLen = m_len + pLine->m_len + 1;
383                wchar_t* p = new wchar_t[newLen];
384                memcpy(p, m_pLine, m_len * sizeof(wchar_t));
385                p[m_len] = ' ';
386                memcpy(p + m_len + 1, pLine->m_pLine, pLine->m_len * sizeof(wchar_t));
387
388                return new CLine(p, m_len + pLine->m_len + 1, pLine->m_trailing_whitespace);
389        }
390
391        void SetTokenOffset(unsigned int offset)
392        {
393                offset_ = offset;
394        }
395
396protected:
397        std::vector<CToken *> m_Tokens;
398        std::vector<CToken *> m_LineEndTokens;
399        int m_parsePos;
400        int m_len;
401        int m_trailing_whitespace;
402        wchar_t* m_pLine;
403        unsigned int offset_{};
404};
405
406CDirectoryListingParser::CDirectoryListingParser(CControlSocket* pControlSocket, const CServer& server, listingEncoding::type encoding, bool sftp_mode)
407        : m_pControlSocket(pControlSocket)
408        , m_currentOffset(0)
409        , m_totalData()
410        , m_prevLine(0)
411        , m_server(server)
412        , m_fileListOnly(true)
413        , m_maybeMultilineVms(false)
414        , m_listingEncoding(encoding)
415        , sftp_mode_(sftp_mode)
416{
417        if (m_MonthNamesMap.empty()) {
418                //Fill the month names map
419
420                //English month names
421                m_MonthNamesMap[_T("jan")] = 1;
422                m_MonthNamesMap[_T("feb")] = 2;
423                m_MonthNamesMap[_T("mar")] = 3;
424                m_MonthNamesMap[_T("apr")] = 4;
425                m_MonthNamesMap[_T("may")] = 5;
426                m_MonthNamesMap[_T("jun")] = 6;
427                m_MonthNamesMap[_T("june")] = 6;
428                m_MonthNamesMap[_T("jul")] = 7;
429                m_MonthNamesMap[_T("july")] = 7;
430                m_MonthNamesMap[_T("aug")] = 8;
431                m_MonthNamesMap[_T("sep")] = 9;
432                m_MonthNamesMap[_T("sept")] = 9;
433                m_MonthNamesMap[_T("oct")] = 10;
434                m_MonthNamesMap[_T("nov")] = 11;
435                m_MonthNamesMap[_T("dec")] = 12;
436
437                //Numerical values for the month
438                m_MonthNamesMap[_T("1")] = 1;
439                m_MonthNamesMap[_T("01")] = 1;
440                m_MonthNamesMap[_T("2")] = 2;
441                m_MonthNamesMap[_T("02")] = 2;
442                m_MonthNamesMap[_T("3")] = 3;
443                m_MonthNamesMap[_T("03")] = 3;
444                m_MonthNamesMap[_T("4")] = 4;
445                m_MonthNamesMap[_T("04")] = 4;
446                m_MonthNamesMap[_T("5")] = 5;
447                m_MonthNamesMap[_T("05")] = 5;
448                m_MonthNamesMap[_T("6")] = 6;
449                m_MonthNamesMap[_T("06")] = 6;
450                m_MonthNamesMap[_T("7")] = 7;
451                m_MonthNamesMap[_T("07")] = 7;
452                m_MonthNamesMap[_T("8")] = 8;
453                m_MonthNamesMap[_T("08")] = 8;
454                m_MonthNamesMap[_T("9")] = 9;
455                m_MonthNamesMap[_T("09")] = 9;
456                m_MonthNamesMap[_T("10")] = 10;
457                m_MonthNamesMap[_T("11")] = 11;
458                m_MonthNamesMap[_T("12")] = 12;
459
460                //German month names
461                m_MonthNamesMap[_T("mrz")] = 3;
462                m_MonthNamesMap[_T("m\xe4r")] = 3;
463                m_MonthNamesMap[_T("m\xe4rz")] = 3;
464                m_MonthNamesMap[_T("mai")] = 5;
465                m_MonthNamesMap[_T("juni")] = 6;
466                m_MonthNamesMap[_T("juli")] = 7;
467                m_MonthNamesMap[_T("okt")] = 10;
468                m_MonthNamesMap[_T("dez")] = 12;
469
470                //Austrian month names
471                m_MonthNamesMap[_T("j\xe4n")] = 1;
472
473                //French month names
474                m_MonthNamesMap[_T("janv")] = 1;
475                m_MonthNamesMap[_T("f\xe9") _T("b")] = 1;
476                m_MonthNamesMap[_T("f\xe9v")] = 2;
477                m_MonthNamesMap[_T("fev")] = 2;
478                m_MonthNamesMap[_T("f\xe9vr")] = 2;
479                m_MonthNamesMap[_T("fevr")] = 2;
480                m_MonthNamesMap[_T("mars")] = 3;
481                m_MonthNamesMap[_T("mrs")] = 3;
482                m_MonthNamesMap[_T("avr")] = 4;
483                m_MonthNamesMap[_T("avril")] = 4;
484                m_MonthNamesMap[_T("juin")] = 6;
485                m_MonthNamesMap[_T("juil")] = 7;
486                m_MonthNamesMap[_T("jui")] = 7;
487                m_MonthNamesMap[_T("ao\xfb")] = 8;
488                m_MonthNamesMap[_T("ao\xfbt")] = 8;
489                m_MonthNamesMap[_T("aout")] = 8;
490                m_MonthNamesMap[_T("d\xe9") _T("c")] = 12;
491                m_MonthNamesMap[_T("dec")] = 12;
492
493                //Italian month names
494                m_MonthNamesMap[_T("gen")] = 1;
495                m_MonthNamesMap[_T("mag")] = 5;
496                m_MonthNamesMap[_T("giu")] = 6;
497                m_MonthNamesMap[_T("lug")] = 7;
498                m_MonthNamesMap[_T("ago")] = 8;
499                m_MonthNamesMap[_T("set")] = 9;
500                m_MonthNamesMap[_T("ott")] = 10;
501                m_MonthNamesMap[_T("dic")] = 12;
502
503                //Spanish month names
504                m_MonthNamesMap[_T("ene")] = 1;
505                m_MonthNamesMap[_T("fbro")] = 2;
506                m_MonthNamesMap[_T("mzo")] = 3;
507                m_MonthNamesMap[_T("ab")] = 4;
508                m_MonthNamesMap[_T("abr")] = 4;
509                m_MonthNamesMap[_T("agto")] = 8;
510                m_MonthNamesMap[_T("sbre")] = 9;
511                m_MonthNamesMap[_T("obre")] = 9;
512                m_MonthNamesMap[_T("nbre")] = 9;
513                m_MonthNamesMap[_T("dbre")] = 9;
514
515                //Polish month names
516                m_MonthNamesMap[_T("sty")] = 1;
517                m_MonthNamesMap[_T("lut")] = 2;
518                m_MonthNamesMap[_T("kwi")] = 4;
519                m_MonthNamesMap[_T("maj")] = 5;
520                m_MonthNamesMap[_T("cze")] = 6;
521                m_MonthNamesMap[_T("lip")] = 7;
522                m_MonthNamesMap[_T("sie")] = 8;
523                m_MonthNamesMap[_T("wrz")] = 9;
524                m_MonthNamesMap[_T("pa\x9f")] = 10;
525                m_MonthNamesMap[_T("pa\xbc")] = 10; // ISO-8859-2
526                m_MonthNamesMap[_T("paz")] = 10; // ASCII
527                m_MonthNamesMap[_T("pa\xc5\xba")] = 10; // UTF-8
528                m_MonthNamesMap[_T("pa\x017a")] = 10; // some servers send this
529                m_MonthNamesMap[_T("lis")] = 11;
530                m_MonthNamesMap[_T("gru")] = 12;
531
532                //Russian month names
533                m_MonthNamesMap[_T("\xff\xed\xe2")] = 1;
534                m_MonthNamesMap[_T("\xf4\xe5\xe2")] = 2;
535                m_MonthNamesMap[_T("\xec\xe0\xf0")] = 3;
536                m_MonthNamesMap[_T("\xe0\xef\xf0")] = 4;
537                m_MonthNamesMap[_T("\xec\xe0\xe9")] = 5;
538                m_MonthNamesMap[_T("\xe8\xfe\xed")] = 6;
539                m_MonthNamesMap[_T("\xe8\xfe\xeb")] = 7;
540                m_MonthNamesMap[_T("\xe0\xe2\xe3")] = 8;
541                m_MonthNamesMap[_T("\xf1\xe5\xed")] = 9;
542                m_MonthNamesMap[_T("\xee\xea\xf2")] = 10;
543                m_MonthNamesMap[_T("\xed\xee\xff")] = 11;
544                m_MonthNamesMap[_T("\xe4\xe5\xea")] = 12;
545
546                //Dutch month names
547                m_MonthNamesMap[_T("mrt")] = 3;
548                m_MonthNamesMap[_T("mei")] = 5;
549
550                //Portuguese month names
551                m_MonthNamesMap[_T("out")] = 10;
552
553                //Finnish month names
554                m_MonthNamesMap[_T("tammi")] = 1;
555                m_MonthNamesMap[_T("helmi")] = 2;
556                m_MonthNamesMap[_T("maalis")] = 3;
557                m_MonthNamesMap[_T("huhti")] = 4;
558                m_MonthNamesMap[_T("touko")] = 5;
559                m_MonthNamesMap[_T("kes\xe4")] = 6;
560                m_MonthNamesMap[_T("hein\xe4")] = 7;
561                m_MonthNamesMap[_T("elo")] = 8;
562                m_MonthNamesMap[_T("syys")] = 9;
563                m_MonthNamesMap[_T("loka")] = 10;
564                m_MonthNamesMap[_T("marras")] = 11;
565                m_MonthNamesMap[_T("joulu")] = 12;
566
567                //Slovenian month names
568                m_MonthNamesMap[_T("avg")] = 8;
569
570                //Icelandic
571                m_MonthNamesMap[_T("ma\x00ed")] = 5;
572                m_MonthNamesMap[_T("j\x00fan")] = 6;
573                m_MonthNamesMap[_T("j\x00fal")] = 7;
574                m_MonthNamesMap[_T("\x00e1g")] = 8;
575                m_MonthNamesMap[_T("n\x00f3v")] = 11;
576                m_MonthNamesMap[_T("des")] = 12;
577
578                //Lithuanian
579                m_MonthNamesMap[_T("sau")] = 1;
580                m_MonthNamesMap[_T("vas")] = 2;
581                m_MonthNamesMap[_T("kov")] = 3;
582                m_MonthNamesMap[_T("bal")] = 4;
583                m_MonthNamesMap[_T("geg")] = 5;
584                m_MonthNamesMap[_T("bir")] = 6;
585                m_MonthNamesMap[_T("lie")] = 7;
586                m_MonthNamesMap[_T("rgp")] = 8;
587                m_MonthNamesMap[_T("rgs")] = 9;
588                m_MonthNamesMap[_T("spa")] = 10;
589                m_MonthNamesMap[_T("lap")] = 11;
590                m_MonthNamesMap[_T("grd")] = 12;
591
592                // Hungarian
593                m_MonthNamesMap[_T("szept")] = 9;
594
595                //There are more languages and thus month
596                //names, but as long as nobody reports a
597                //problem, I won't add them, there are way
598                //too many languages
599
600                // Some servers send a combination of month name and number,
601                // Add corresponding numbers to the month names.
602                std::map<std::wstring, int> combo;
603                for (auto iter = m_MonthNamesMap.begin(); iter != m_MonthNamesMap.end(); ++iter) {
604                        // January could be 1 or 0, depends how the server counts
605                        combo[wxString::Format(_T("%s%02d"), iter->first, iter->second).ToStdWstring()] = iter->second;
606                        combo[wxString::Format(_T("%s%02d"), iter->first, iter->second - 1).ToStdWstring()] = iter->second;
607                        if (iter->second < 10)
608                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second).ToStdWstring()] = iter->second;
609                        else
610                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second % 10).ToStdWstring()] = iter->second;
611                        if (iter->second <= 10)
612                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second - 1).ToStdWstring()] = iter->second;
613                        else
614                                combo[wxString::Format(_T("%s%d"), iter->first, (iter->second - 1) % 10).ToStdWstring()] = iter->second;
615                }
616                m_MonthNamesMap.insert(combo.begin(), combo.end());
617
618                m_MonthNamesMap[_T("1")] = 1;
619                m_MonthNamesMap[_T("2")] = 2;
620                m_MonthNamesMap[_T("3")] = 3;
621                m_MonthNamesMap[_T("4")] = 4;
622                m_MonthNamesMap[_T("5")] = 5;
623                m_MonthNamesMap[_T("6")] = 6;
624                m_MonthNamesMap[_T("7")] = 7;
625                m_MonthNamesMap[_T("8")] = 8;
626                m_MonthNamesMap[_T("9")] = 9;
627                m_MonthNamesMap[_T("10")] = 10;
628                m_MonthNamesMap[_T("11")] = 11;
629                m_MonthNamesMap[_T("12")] = 12;
630        }
631
632#ifdef LISTDEBUG
633        for (unsigned int i = 0; data[i][0]; ++i)
634        {
635                unsigned int len = (unsigned int)strlen(data[i]);
636                char *pData = new char[len + 3];
637                strcpy(pData, data[i]);
638                strcat(pData, "\r\n");
639                AddData(pData, len + 2);
640        }
641#endif
642}
643
644CDirectoryListingParser::~CDirectoryListingParser()
645{
646        for (auto iter = m_DataList.begin(); iter != m_DataList.end(); ++iter)
647                delete [] iter->p;
648
649        delete m_prevLine;
650}
651
652bool CDirectoryListingParser::ParseData(bool partial)
653{
654        DeduceEncoding();
655
656        bool error = false;
657        CLine *pLine = GetLine(partial, error);
658        while (pLine) {
659                bool res = ParseLine(*pLine, m_server.GetType(), false);
660                if (!res) {
661                        if (m_prevLine) {
662                                CLine* pConcatenatedLine = m_prevLine->Concat(pLine);
663                                res = ParseLine(*pConcatenatedLine, m_server.GetType(), true);
664                                delete pConcatenatedLine;
665                                delete m_prevLine;
666
667                                if (res) {
668                                        delete pLine;
669                                        m_prevLine = 0;
670                                }
671                                else
672                                        m_prevLine = pLine;
673                        }
674                        else if (!sftp_mode_) {
675                                m_prevLine = pLine;
676                        }
677                        else {
678                                delete pLine;
679                        }
680                }
681                else {
682                        delete m_prevLine;
683                        m_prevLine = 0;
684                        delete pLine;
685                }
686                pLine = GetLine(partial, error);
687        };
688
689        return !error;
690}
691
692CDirectoryListing CDirectoryListingParser::Parse(const CServerPath &path)
693{
694        CDirectoryListing listing;
695        listing.path = path;
696        listing.m_firstListTime = fz::monotonic_clock::now();
697
698        if (!ParseData(false)){
699                listing.m_flags |= CDirectoryListing::listing_failed;
700                return listing;
701        }
702
703        if (!m_fileList.empty()) {
704                wxASSERT(m_entryList.empty());
705
706                listing.SetCount(m_fileList.size());
707                unsigned int i = 0;
708                for (auto const& file : m_fileList) {
709                        CDirentry entry;
710                        entry.name = file;
711                        entry.flags = 0;
712                        entry.size = -1;
713                        listing[i++] = entry;
714                }
715        }
716        else {
717                listing.Assign(m_entryList);
718        }
719
720        return listing;
721}
722
723bool CDirectoryListingParser::ParseLine(CLine &line, const enum ServerType serverType, bool concatenated)
724{
725        CRefcountObject<CDirentry> refEntry;
726        CDirentry & entry = refEntry.Get();
727
728        bool res;
729        int ires;
730
731        if (sftp_mode_) {
732                line.SetTokenOffset(1);
733        }
734
735        if (serverType == ZVM) {
736                res = ParseAsZVM(line, entry);
737                if (res)
738                        goto done;
739        }
740        else if (serverType == HPNONSTOP) {
741                res = ParseAsHPNonstop(line, entry);
742                if (res)
743                        goto done;
744        }
745
746        ires = ParseAsMlsd(line, entry);
747        if (ires == 1)
748                goto done;
749        else if (ires == 2)
750                goto skip;
751        res = ParseAsUnix(line, entry, true); // Common 'ls -l'
752        if (res)
753                goto done;
754        res = ParseAsDos(line, entry);
755        if (res)
756                goto done;
757        res = ParseAsEplf(line, entry);
758        if (res)
759                goto done;
760        res = ParseAsVms(line, entry);
761        if (res)
762                goto done;
763        res = ParseOther(line, entry);
764        if (res)
765                goto done;
766        res = ParseAsIbm(line, entry);
767        if (res)
768                goto done;
769        res = ParseAsWfFtp(line, entry);
770        if (res)
771                goto done;
772        res = ParseAsIBM_MVS(line, entry);
773        if (res)
774                goto done;
775        res = ParseAsIBM_MVS_PDS(line, entry);
776        if (res)
777                goto done;
778        res = ParseAsOS9(line, entry);
779        if (res)
780                goto done;
781#ifndef LISTDEBUG_MVS
782        if (serverType == MVS)
783#endif //LISTDEBUG_MVS
784        {
785                res = ParseAsIBM_MVS_Migrated(line, entry);
786                if (res)
787                        goto done;
788                res = ParseAsIBM_MVS_PDS2(line, entry);
789                if (res)
790                        goto done;
791                res = ParseAsIBM_MVS_Tape(line, entry);
792                if (res)
793                        goto done;
794        }
795        res = ParseAsUnix(line, entry, false); // 'ls -l' but without the date/time
796        if (res)
797                goto done;
798
799        // Some servers just send a list of filenames. If a line could not be parsed,
800        // check if it's a filename. If that's the case, store it for later, else clear
801        // list of stored files.
802        // If parsing finishes and no entries could be parsed and none of the lines
803        // contained a space, assume it's a raw filelisting.
804
805        if (!concatenated) {
806                CToken token;
807                if (!line.GetToken(0, token, true) || token.Find(' ') != -1) {
808                        m_maybeMultilineVms = false;
809                        m_fileList.clear();
810                        m_fileListOnly = false;
811                }
812                else {
813                        m_maybeMultilineVms = token.Find(';') != -1;
814                        if (m_fileListOnly)
815                                m_fileList.emplace_back(token.GetString());
816                }
817        }
818        else
819                m_maybeMultilineVms = false;
820
821        return false;
822done:
823
824        m_maybeMultilineVms = false;
825        m_fileList.clear();
826        m_fileListOnly = false;
827
828        // Don't add . or ..
829        if (entry.name == _T(".") || entry.name == _T(".."))
830                return true;
831
832        if (serverType == VMS && entry.is_dir()) {
833                // Trim version information from directories
834                auto pos = entry.name.rfind(';');
835                if (pos != std::wstring::npos && pos > 0)
836                        entry.name = entry.name.substr(0, pos);
837        }
838
839        if (sftp_mode_) {
840                line.SetTokenOffset(0);
841
842                CToken t;
843                if (line.GetToken(0, t)) {
844                        int64_t seconds = t.GetNumber();
845                        if (seconds > 0 && seconds <= 0xffffffffll) {
846                                fz::datetime time(static_cast<time_t>(seconds), fz::datetime::seconds);
847                                if (time.empty()) {
848                                        entry.time = time;
849                                }
850                        }
851                }
852        }
853
854        {
855                auto const timezoneOffset = m_server.GetTimezoneOffset();
856                if (timezoneOffset) {
857                        entry.time += fz::duration::from_minutes(timezoneOffset);
858                }
859        }
860
861        m_entryList.emplace_back(std::move(refEntry));
862
863skip:
864        m_maybeMultilineVms = false;
865        m_fileList.clear();
866        m_fileListOnly = false;
867
868        return true;
869}
870
871bool CDirectoryListingParser::ParseAsUnix(CLine &line, CDirentry &entry, bool expect_date)
872{
873        int index = 0;
874        CToken token;
875        if (!line.GetToken(index, token))
876                return false;
877
878        wchar_t chr = token[0];
879        if (chr != 'b' &&
880                chr != 'c' &&
881                chr != 'd' &&
882                chr != 'l' &&
883                chr != 'p' &&
884                chr != 's' &&
885                chr != '-')
886                return false;
887
888        std::wstring permissions = token.GetString();
889
890        entry.flags = 0;
891
892        if (chr == 'd' || chr == 'l')
893                entry.flags |= CDirentry::flag_dir;
894
895        if (chr == 'l')
896                entry.flags |= CDirentry::flag_link;
897
898        // Check for netware servers, which split the permissions into two parts
899        bool netware = false;
900        if (token.GetLength() == 1) {
901                if (!line.GetToken(++index, token))
902                        return false;
903                permissions += _T(" ") + token.GetString();
904                netware = true;
905        }
906
907        int numOwnerGroup = 3;
908        if (!netware) {
909                // Filter out link count, we don't need it
910                if (!line.GetToken(++index, token))
911                        return false;
912
913                if (!token.IsNumeric())
914                        --index;
915        }
916
917        // Repeat until numOwnerGroup is 0 since not all servers send every possible field
918        int startindex = index;
919        do {
920                // Reset index
921                index = startindex;
922
923                std::wstring ownerGroup;
924                for (int i = 0; i < numOwnerGroup; ++i) {
925                        if (!line.GetToken(++index, token))
926                                return false;
927                        if (i)
928                                ownerGroup += _T(" ");
929                        ownerGroup += token.GetString();
930                }
931
932                if (!line.GetToken(++index, token))
933                        return false;
934
935                // Check for concatenated groupname and size fields
936                if (!ParseComplexFileSize(token, entry.size)) {
937                        if (!token.IsRightNumeric())
938                                continue;
939                        entry.size = token.GetNumber();
940                }
941
942                // Append missing group to ownerGroup
943                if (!token.IsNumeric() && token.IsRightNumeric()) {
944                        if (!ownerGroup.empty())
945                                ownerGroup += _T(" ");
946
947                        std::wstring const group = token.GetString();
948                        int i;
949                        for (i = group.size() - 1;
950                                 i >= 0 && group[i] >= '0' && group[i] <= '9';
951                                 --i) {}
952
953                        ownerGroup += group.substr(0, i + 1);
954                }
955
956                if (expect_date) {
957                        entry.time = fz::datetime();
958                        if (!ParseUnixDateTime(line, index, entry))
959                                continue;
960                }
961
962                // Get the filename
963                if (!line.GetToken(++index, token, 1))
964                        continue;
965
966                entry.name = token.GetString();
967
968                // Filter out special chars at the end of the filenames
969                chr = token[token.GetLength() - 1];
970                if (chr == '/' ||
971                        chr == '|' ||
972                        chr == '*')
973                        entry.name.pop_back();
974
975                if (entry.is_link()) {
976                        size_t pos;
977                        if ((pos = entry.name.find(_T(" -> "))) != std::wstring::npos) {
978                                entry.target = CSparseOptional<std::wstring>(entry.name.substr(pos + 4));
979                                entry.name = entry.name.substr(0, pos);
980                        }
981                }
982
983                entry.time += m_timezoneOffset;
984
985                entry.permissions = objcache.get(permissions);
986                entry.ownerGroup = objcache.get(ownerGroup);
987                return true;
988        }
989        while (numOwnerGroup--);
990
991        return false;
992}
993
994bool CDirectoryListingParser::ParseUnixDateTime(CLine & line, int &index, CDirentry &entry)
995{
996        bool mayHaveTime = true;
997        bool bHasYearAndTime = false;
998
999        CToken token;
1000
1001        // Get the month date field
1002        CToken dateMonth;
1003        if (!line.GetToken(++index, token))
1004                return false;
1005
1006        int year = -1;
1007        int month = -1;
1008        int day = -1;
1009        long hour = -1;
1010        long minute = -1;
1011
1012        // Some servers use the following date formats:
1013        // 26-05 2002, 2002-10-14, 01-jun-99 or 2004.07.15
1014        // slashes instead of dashes are also possible
1015        int pos = token.Find(_T("-/."));
1016        if (pos != -1) {
1017                int pos2 = token.Find(_T("-/."), pos + 1);
1018                if (pos2 == -1) {
1019                        if (token[pos] != '.') {
1020                                // something like 26-05 2002
1021                                day = token.GetNumber(pos + 1, token.GetLength() - pos - 1);
1022                                if (day < 1 || day > 31)
1023                                        return false;
1024                                dateMonth = CToken(token.GetToken(), pos);
1025                        }
1026                        else
1027                                dateMonth = token;
1028                }
1029                else if (token[pos] != token[pos2])
1030                        return false;
1031                else {
1032                        if (!ParseShortDate(token, entry))
1033                                return false;
1034
1035                        if (token[pos] == '.')
1036                                return true;
1037
1038                        tm t = entry.time.get_tm(fz::datetime::utc);
1039                        year = t.tm_year + 1900;
1040                        month = t.tm_mon + 1;
1041                        day = t.tm_mday;
1042                }
1043        }
1044        else if (token.IsNumeric()) {
1045                if (token.GetNumber() > 1000 && token.GetNumber() < 10000) {
1046                        // Two possible variants:
1047                        // 1) 2005 3 13
1048                        // 2) 2005 13 3
1049                        // assume first one.
1050                        year = token.GetNumber();
1051                        if (!line.GetToken(++index, dateMonth))
1052                                return false;
1053                        mayHaveTime = false;
1054                }
1055                else
1056                        dateMonth = token;
1057        }
1058        else {
1059                if (token.IsLeftNumeric() && (unsigned int)token[token.GetLength() - 1] > 127 &&
1060                        token.GetNumber() > 1000)
1061                {
1062                        if (token.GetNumber() > 10000)
1063                                return false;
1064
1065                        // Asian date format: 2005xxx 5xx 20xxx with some non-ascii characters following
1066                        year = token.GetNumber();
1067                        if (!line.GetToken(++index, dateMonth))
1068                                return false;
1069                        mayHaveTime = false;
1070                }
1071                else
1072                        dateMonth = token;
1073        }
1074
1075        if (day < 1) {
1076                // Get day field
1077                if (!line.GetToken(++index, token))
1078                        return false;
1079
1080                int dateDay;
1081
1082                // Check for non-numeric day
1083                if (!token.IsNumeric() && !token.IsLeftNumeric()) {
1084                        int offset = 0;
1085                        if (dateMonth.GetString().back() == '.')
1086                                ++offset;
1087                        if (!dateMonth.IsNumeric(0, dateMonth.GetLength() - offset))
1088                                return false;
1089                        dateDay = dateMonth.GetNumber(0, dateMonth.GetLength() - offset);
1090                        dateMonth = token;
1091                }
1092                else if( token.GetLength() == 5 && token[2] == ':' && token.IsRightNumeric() ) {
1093                        // This is a time. We consumed too much already.
1094                        return false;
1095                }
1096                else {
1097                        dateDay = token.GetNumber();
1098                        if (token[token.GetLength() - 1] == ',')
1099                                bHasYearAndTime = true;
1100                }
1101
1102                if (dateDay < 1 || dateDay > 31)
1103                        return false;
1104                day = dateDay;
1105        }
1106
1107        if (month < 1) {
1108                std::wstring strMonth = dateMonth.GetString();
1109                if (dateMonth.IsLeftNumeric() && (unsigned int)strMonth[strMonth.size() - 1] > 127) {
1110                        // Most likely an Asian server sending some unknown language specific
1111                        // suffix at the end of the monthname. Filter it out.
1112                        int i;
1113                        for (i = strMonth.size() - 1; i > 0; --i) {
1114                                if (strMonth[i] >= '0' && strMonth[i] <= '9')
1115                                        break;
1116                        }
1117                        strMonth = strMonth.substr(0, i + 1);
1118                }
1119                // Check month name
1120                while (!strMonth.empty() && (strMonth.back() == ',' || strMonth.back() == '.'))
1121                        strMonth.pop_back();
1122                if (!GetMonthFromName(strMonth, month))
1123                        return false;
1124        }
1125
1126        // Get time/year field
1127        if (!line.GetToken(++index, token))
1128                return false;
1129
1130        pos = token.Find(_T(":.-"));
1131        if (pos != -1 && mayHaveTime) {
1132                // token is a time
1133                if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
1134                        return false;
1135
1136                wxString str = token.GetString();
1137                if (!str.Left(pos).ToLong(&hour))
1138                        return false;
1139                if (!str.Mid(pos + 1).ToLong(&minute))
1140                        return false;
1141
1142                if (hour < 0 || hour > 23)
1143                        return false;
1144                if (minute < 0 || minute > 59)
1145                        return false;
1146
1147                // Some servers use times only for files newer than 6 months
1148                if( year <= 0 ) {
1149                        wxASSERT( month != -1 && day != -1 );
1150                        tm const t = fz::datetime::now().get_tm(fz::datetime::utc);
1151                        year = t.tm_year + 1900;
1152                        int const currentDayOfYear = t.tm_mday + 31 * t.tm_mon;
1153                        int const fileDayOfYear = day + 31 * (month - 1);
1154
1155                        // We have to compare with an offset of one. In the worst case,
1156                        // the server's timezone might be up to 24 hours ahead of the
1157                        // client.
1158                        // Problem: Servers which do send the time but not the year even
1159                        // one day away from getting 1 year old. This is far more uncommon
1160                        // however.
1161                        if ((currentDayOfYear + 1) < fileDayOfYear)
1162                                year -= 1;
1163                }
1164        }
1165        else if (year <= 0) {
1166                // token is a year
1167                if (!token.IsNumeric() && !token.IsLeftNumeric())
1168                        return false;
1169
1170                year = token.GetNumber();
1171
1172                if (year > 3000)
1173                        return false;
1174                if (year < 1000)
1175                        year += 1900;
1176
1177                if (bHasYearAndTime) {
1178                        if (!line.GetToken(++index, token))
1179                                return false;
1180
1181                        if (token.Find(':') == 2 && token.GetLength() == 5 && token.IsLeftNumeric() && token.IsRightNumeric()) {
1182                                pos = token.Find(':');
1183                                // token is a time
1184                                if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
1185                                        return false;
1186
1187                                wxString str = token.GetString();
1188
1189                                if (!str.Left(pos).ToLong(&hour))
1190                                        return false;
1191                                if (!str.Mid(pos + 1).ToLong(&minute))
1192                                        return false;
1193
1194                                if (hour < 0 || hour > 23)
1195                                        return false;
1196                                if (minute < 0 || minute > 59)
1197                                        return false;
1198                        }
1199                        else
1200                                --index;
1201                }
1202        }
1203        else
1204                --index;
1205
1206        if (!entry.time.set(fz::datetime::utc, year, month, day, hour, minute)) {
1207                return false;
1208        }
1209
1210        return true;
1211}
1212
1213bool CDirectoryListingParser::ParseShortDate(CToken &token, CDirentry &entry, bool saneFieldOrder /*=false*/)
1214{
1215        if (token.GetLength() < 1)
1216                return false;
1217
1218        bool gotYear = false;
1219        bool gotMonth = false;
1220        bool gotDay = false;
1221        bool gotMonthName = false;
1222
1223        int year = 0;
1224        int month = 0;
1225        int day = 0;
1226
1227        int pos = token.Find(_T("-./"));
1228        if (pos < 1)
1229                return false;
1230
1231        if (!token.IsNumeric(0, pos)) {
1232                // Seems to be monthname-dd-yy
1233
1234                // Check month name
1235                std::wstring const dateMonth = token.GetString().substr(0, pos);
1236                if (!GetMonthFromName(dateMonth, month))
1237                        return false;
1238                gotMonth = true;
1239                gotMonthName = true;
1240        }
1241        else if (pos == 4) {
1242                // Seems to be yyyy-mm-dd
1243                year = token.GetNumber(0, pos);
1244                if (year < 1900 || year > 3000)
1245                        return false;
1246                gotYear = true;
1247        }
1248        else if (pos <= 2) {
1249                int64_t value = token.GetNumber(0, pos);
1250                if (token[pos] == '.') {
1251                        // Maybe dd.mm.yyyy
1252                        if (value < 1 || value > 31)
1253                                return false;
1254                        day = value;
1255                        gotDay = true;
1256                }
1257                else {
1258                        if (saneFieldOrder) {
1259                                year = value;
1260                                if (year < 50)
1261                                        year += 2000;
1262                                else
1263                                        year += 1900;
1264                                gotYear = true;
1265                        }
1266                        else {
1267                                // Detect mm-dd-yyyy or mm/dd/yyyy and
1268                                // dd-mm-yyyy or dd/mm/yyyy
1269                                if (value < 1)
1270                                        return false;
1271                                if (value > 12) {
1272                                        if (value > 31)
1273                                                return false;
1274
1275                                        day = value;
1276                                        gotDay = true;
1277                                }
1278                                else {
1279                                        month = value;
1280                                        gotMonth = true;
1281                                }
1282                        }
1283                }
1284        }
1285        else
1286                return false;
1287
1288        int pos2 = token.Find(_T("-./"), pos + 1);
1289        if (pos2 == -1 || (pos2 - pos) == 1)
1290                return false;
1291        if (static_cast<size_t>(pos2) == (token.GetLength() - 1))
1292                return false;
1293
1294        // If we already got the month and the second field is not numeric,
1295        // change old month into day and use new token as month
1296        if (!token.IsNumeric(pos + 1, pos2 - pos - 1) && gotMonth) {
1297                if (gotMonthName)
1298                        return false;
1299
1300                if (gotDay)
1301                        return false;
1302
1303                gotDay = true;
1304                gotMonth = false;
1305                day = month;
1306        }
1307
1308        if (gotYear || gotDay) {
1309                // Month field in yyyy-mm-dd or dd-mm-yyyy
1310                // Check month name
1311                std::wstring dateMonth = token.GetString().substr(pos + 1, pos2 - pos - 1);
1312                if (!GetMonthFromName(dateMonth, month))
1313                        return false;
1314                gotMonth = true;
1315        }
1316        else {
1317                int64_t value = token.GetNumber(pos + 1, pos2 - pos - 1);
1318                // Day field in mm-dd-yyyy
1319                if (value < 1 || value > 31)
1320                        return false;
1321                day = value;
1322                gotDay = true;
1323        }
1324
1325        int64_t value = token.GetNumber(pos2 + 1, token.GetLength() - pos2 - 1);
1326        if (gotYear) {
1327                // Day field in yyy-mm-dd
1328                if (value <= 0 || value > 31)
1329                        return false;
1330                day = value;
1331                gotDay = true;
1332        }
1333        else {
1334                if (value < 0 || value > 9999)
1335                        return false;
1336
1337                if (value < 50)
1338                        value += 2000;
1339                else if (value < 1000)
1340                        value += 1900;
1341                year = value;
1342
1343                gotYear = true;
1344        }
1345
1346        wxASSERT(gotYear);
1347        wxASSERT(gotMonth);
1348        wxASSERT(gotDay);
1349
1350        if (!entry.time.set(fz::datetime::utc, year, month, day)) {
1351                return false;
1352        }
1353
1354        return true;
1355}
1356
1357bool CDirectoryListingParser::ParseAsDos(CLine &line, CDirentry &entry)
1358{
1359        int index = 0;
1360        CToken token;
1361
1362        // Get first token, has to be a valid date
1363        if (!line.GetToken(index, token))
1364                return false;
1365
1366        entry.flags = 0;
1367
1368        if (!ParseShortDate(token, entry))
1369                return false;
1370
1371        // Extract time
1372        if (!line.GetToken(++index, token))
1373                return false;
1374
1375        if (!ParseTime(token, entry))
1376                return false;
1377
1378        // If next token is <DIR>, entry is a directory
1379        // else, it should be the filesize.
1380        if (!line.GetToken(++index, token))
1381                return false;
1382
1383        if (token.GetString() == _T("<DIR>")) {
1384                entry.flags |= CDirentry::flag_dir;
1385                entry.size = -1;
1386        }
1387        else if (token.IsNumeric() || token.IsLeftNumeric()) {
1388                // Convert size, filter out separators
1389                int64_t size = 0;
1390                int len = token.GetLength();
1391                for (int i = 0; i < len; ++i) {
1392                        char chr = token[i];
1393                        if (chr == ',' || chr == '.')
1394                                continue;
1395                        if (chr < '0' || chr > '9')
1396                                return false;
1397
1398                        size *= 10;
1399                        size += chr - '0';
1400                }
1401                entry.size = size;
1402        }
1403        else
1404                return false;
1405
1406        // Extract filename
1407        if (!line.GetToken(++index, token, true))
1408                return false;
1409        entry.name = token.GetString();
1410
1411        entry.target.clear();
1412        entry.ownerGroup = objcache.get(std::wstring());
1413        entry.permissions = entry.ownerGroup;
1414        entry.time += m_timezoneOffset;
1415
1416        return true;
1417}
1418
1419bool CDirectoryListingParser::ParseTime(CToken &token, CDirentry &entry)
1420{
1421        if (!entry.has_date())
1422                return false;
1423
1424        int pos = token.Find(':');
1425        if (pos < 1 || static_cast<unsigned int>(pos) >= (token.GetLength() - 1))
1426                return false;
1427
1428        int64_t hour = token.GetNumber(0, pos);
1429        if (hour < 0 || hour > 23)
1430                return false;
1431
1432        // See if we got seconds
1433        int pos2 = token.Find(':', pos + 1);
1434        int len;
1435        if (pos2 == -1)
1436                len = -1;
1437        else
1438                len = pos2 - pos - 1;
1439
1440        if (!len)
1441                return false;
1442
1443        int64_t minute = token.GetNumber(pos + 1, len);
1444        if (minute < 0 || minute > 59)
1445                return false;
1446
1447        int64_t seconds = -1;
1448        if (pos2 != -1) {
1449                // Parse seconds
1450                seconds = token.GetNumber(pos2 + 1, -1);
1451                if (seconds < 0 || seconds > 60)
1452                        return false;
1453        }
1454
1455        // Convert to 24h format
1456        if (!token.IsRightNumeric()) {
1457                if (token[token.GetLength() - 2] == 'P') {
1458                        if (hour < 12)
1459                                hour += 12;
1460                }
1461                else
1462                        if (hour == 12)
1463                                hour = 0;
1464        }
1465
1466        return entry.time.imbue_time(hour, minute, seconds);
1467}
1468
1469bool CDirectoryListingParser::ParseAsEplf(CLine &line, CDirentry &entry)
1470{
1471        CToken token;
1472        if (!line.GetToken(0, token, true))
1473                return false;
1474
1475        if (token[0] != '+')
1476                return false;
1477
1478        int pos = token.Find('\t');
1479        if (pos == -1 || static_cast<size_t>(pos) == (token.GetLength() - 1))
1480                return false;
1481
1482        entry.name = token.GetString().substr(pos + 1);
1483
1484        entry.flags = 0;
1485        entry.size = -1;
1486
1487        std::wstring permissions;
1488
1489        int fact = 1;
1490        while (fact < pos) {
1491                int separator = token.Find(',', fact);
1492                int len;
1493                if (separator == -1)
1494                        len = pos - fact;
1495                else
1496                        len = separator - fact;
1497
1498                if (!len) {
1499                        ++fact;
1500                        continue;
1501                }
1502
1503                char type = token[fact];
1504
1505                if (type == '/')
1506                        entry.flags |= CDirentry::flag_dir;
1507                else if (type == 's')
1508                        entry.size = token.GetNumber(fact + 1, len - 1);
1509                else if (type == 'm') {
1510                        int64_t number = token.GetNumber(fact + 1, len - 1);
1511                        if (number < 0)
1512                                return false;
1513                        entry.time = fz::datetime(static_cast<time_t>(number), fz::datetime::seconds);
1514                }
1515                else if (type == 'u' && len > 2 && token[fact + 1] == 'p')
1516                        permissions = token.GetString().substr(fact + 2, len - 2);
1517
1518                fact += len + 1;
1519        }
1520
1521        entry.permissions = objcache.get(permissions);
1522        entry.ownerGroup = objcache.get(std::wstring());
1523        return true;
1524}
1525
1526namespace {
1527std::wstring Unescape(const std::wstring& str, wchar_t escape)
1528{
1529        std::wstring res;
1530        for (unsigned int i = 0; i < str.size(); ++i) {
1531                wchar_t c = str[i];
1532                if (c == escape) {
1533                        ++i;
1534                        if (i == str.size() || !str[i]) {
1535                                break;
1536                        }
1537                        c = str[i];
1538                }
1539                res += c;
1540        }
1541
1542        return res;
1543}
1544}
1545
1546bool CDirectoryListingParser::ParseAsVms(CLine &line, CDirentry &entry)
1547{
1548        CToken token;
1549        int index = 0;
1550
1551        if (!line.GetToken(index, token))
1552                return false;
1553
1554        int pos = token.Find(';');
1555        if (pos == -1)
1556                return false;
1557
1558        entry.flags = 0;
1559
1560        if (pos > 4 && token.GetString().substr(pos - 4, 4) == _T(".DIR")) {
1561                entry.flags |= CDirentry::flag_dir;
1562                if (token.GetString().substr(pos) == _T(";1"))
1563                        entry.name = token.GetString().substr(0, pos - 4);
1564                else
1565                        entry.name = token.GetString().substr(0, pos - 4) + token.GetString().substr(pos);
1566        }
1567        else
1568                entry.name = token.GetString();
1569
1570        // Some VMS servers escape special characters like additional dots with ^
1571        entry.name = Unescape(entry.name, '^');
1572
1573        if (!line.GetToken(++index, token))
1574                return false;
1575
1576        std::wstring ownerGroup;
1577        std::wstring permissions;
1578
1579        // This field can either be the filesize, a username (at least that's what I think) enclosed in [] or a date.
1580        if (!token.IsNumeric() && !token.IsLeftNumeric()) {
1581                // Must be username
1582                const int len = token.GetLength();
1583                if (len < 3 || token[0] != '[' || token[len - 1] != ']')
1584                        return false;
1585                ownerGroup = token.GetString().substr(1, len - 2);
1586
1587                if (!line.GetToken(++index, token))
1588                        return false;
1589                if (!token.IsNumeric() && !token.IsLeftNumeric())
1590                        return false;
1591        }
1592
1593        // Current token is either size or date
1594        bool gotSize = false;
1595        pos = token.Find('/');
1596
1597        if (!pos)
1598                return false;
1599
1600        if (token.IsNumeric() || (pos != -1 && token.Find('/', pos + 1) == -1)) {
1601                // Definitely size
1602                CToken sizeToken;
1603                if (pos == -1)
1604                        sizeToken = token;
1605                else
1606                        sizeToken = CToken(token.GetToken(), pos);
1607                if (!ParseComplexFileSize(sizeToken, entry.size, 512))
1608                        return false;
1609                gotSize = true;
1610
1611                if (!line.GetToken(++index, token))
1612                        return false;
1613        }
1614        else if (pos == -1 && token.IsLeftNumeric()) {
1615                // Perhaps size
1616                if (ParseComplexFileSize(token, entry.size, 512)) {
1617                        gotSize = true;
1618
1619                        if (!line.GetToken(++index, token))
1620                                return false;
1621                }
1622        }
1623
1624        // Get date
1625        if (!ParseShortDate(token, entry))
1626                return false;
1627
1628        // Get time
1629        if (!line.GetToken(++index, token))
1630                return true;
1631
1632        if (!ParseTime(token, entry)) {
1633                int len = token.GetLength();
1634                if (token[0] == '[' && token[len - 1] != ']')
1635                        return false;
1636                if (token[0] == '(' && token[len - 1] != ')')
1637                        return false;
1638                if (token[0] != '[' && token[len - 1] == ']')
1639                        return false;
1640                if (token[0] != '(' && token[len - 1] == ')')
1641                        return false;
1642                --index;
1643        }
1644
1645        if (!gotSize) {
1646                // Get size
1647                if (!line.GetToken(++index, token))
1648                        return false;
1649
1650                if (!token.IsNumeric() && !token.IsLeftNumeric())
1651                        return false;
1652
1653                pos = token.Find('/');
1654                if (!pos)
1655                        return false;
1656
1657                CToken sizeToken;
1658                if (pos == -1)
1659                        sizeToken = token;
1660                else
1661                        sizeToken = CToken(token.GetToken(), pos);
1662                if (!ParseComplexFileSize(sizeToken, entry.size, 512))
1663                        return false;
1664        }
1665
1666        // Owner / group and permissions
1667        while (line.GetToken(++index, token)) {
1668                const int len = token.GetLength();
1669                if (len > 2 && token[0] == '(' && token[len - 1] == ')') {
1670                        if (!permissions.empty())
1671                                permissions += _T(" ");
1672                        permissions += token.GetString().substr(1, len - 2);
1673                }
1674                else if (len > 2 && token[0] == '[' && token[len - 1] == ']') {
1675                        if (!ownerGroup.empty())
1676                                ownerGroup += _T(" ");
1677                        ownerGroup += token.GetString().substr(1, len - 2);
1678                }
1679                else {
1680                        if (!ownerGroup.empty())
1681                                ownerGroup += _T(" ");
1682                        ownerGroup += token.GetString();
1683                }
1684        }
1685        entry.permissions = objcache.get(permissions);
1686        entry.ownerGroup = objcache.get(ownerGroup);
1687
1688        entry.time += m_timezoneOffset;
1689
1690        return true;
1691}
1692
1693bool CDirectoryListingParser::ParseAsIbm(CLine &line, CDirentry &entry)
1694{
1695        int index = 0;
1696
1697        // Get owner
1698        CToken ownerGroupToken;
1699        if (!line.GetToken(index, ownerGroupToken))
1700                return false;
1701
1702        // Get size
1703        CToken token;
1704        if (!line.GetToken(++index, token))
1705                return false;
1706
1707        if (!token.IsNumeric())
1708                return false;
1709
1710        entry.size = token.GetNumber();
1711
1712        // Get date
1713        if (!line.GetToken(++index, token))
1714                return false;
1715
1716        entry.flags = 0;
1717
1718        if (!ParseShortDate(token, entry))
1719                return false;
1720
1721        // Get time
1722        if (!line.GetToken(++index, token))
1723                return false;
1724
1725        if (!ParseTime(token, entry))
1726                return false;
1727
1728        // Get filename
1729        if (!line.GetToken(index + 2, token, 1))
1730                return false;
1731
1732        entry.name = token.GetString();
1733        if (token[token.GetLength() - 1] == '/') {
1734                entry.name.pop_back();
1735                entry.flags |= CDirentry::flag_dir;
1736        }
1737
1738        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
1739        entry.permissions = objcache.get(std::wstring());
1740
1741        entry.time += m_timezoneOffset;
1742
1743        return true;
1744}
1745
1746bool CDirectoryListingParser::ParseOther(CLine &line, CDirentry &entry)
1747{
1748        int index = 0;
1749        CToken firstToken;
1750
1751        if (!line.GetToken(index, firstToken))
1752                return false;
1753
1754        if (!firstToken.IsNumeric())
1755                return false;
1756
1757        // Possible formats: Numerical unix, VShell or OS/2
1758
1759        CToken token;
1760        if (!line.GetToken(++index, token))
1761                return false;
1762
1763        entry.flags = 0;
1764
1765        // If token is a number, than it's the numerical Unix style format,
1766        // else it's the VShell, OS/2 or nortel.VxWorks format
1767        if (token.IsNumeric()) {
1768                if (firstToken.GetLength() >= 2 && firstToken[1] == '4')
1769                        entry.flags |= CDirentry::flag_dir;
1770
1771                std::wstring ownerGroup = token.GetString();
1772
1773                if (!line.GetToken(++index, token))
1774                        return false;
1775
1776                ownerGroup += _T(" ") + token.GetString();
1777
1778                // Get size
1779                if (!line.GetToken(++index, token))
1780                        return false;
1781
1782                if (!token.IsNumeric())
1783                        return false;
1784
1785                entry.size = token.GetNumber();
1786
1787                // Get date/time
1788                if (!line.GetToken(++index, token))
1789                        return false;
1790
1791                int64_t number = token.GetNumber();
1792                if (number < 0)
1793                        return false;
1794                entry.time = fz::datetime(static_cast<time_t>(number), fz::datetime::seconds);
1795
1796                // Get filename
1797                if (!line.GetToken(++index, token, true))
1798                        return false;
1799
1800                entry.name = token.GetString();
1801                entry.target.clear();
1802
1803                entry.permissions = objcache.get(firstToken.GetString());
1804                entry.ownerGroup = objcache.get(ownerGroup);
1805        }
1806        else {
1807                // Possible conflict with multiline VMS listings
1808                if (m_maybeMultilineVms)
1809                        return false;
1810
1811                // VShell, OS/2 or nortel.VxWorks style format
1812                entry.size = firstToken.GetNumber();
1813
1814                // Get date
1815                std::wstring dateMonth = token.GetString();
1816                int month = 0;
1817                if (!GetMonthFromName(dateMonth, month)) {
1818                        // OS/2 or nortel.VxWorks
1819                        int skippedCount = 0;
1820                        do {
1821                                if (token.GetString() == _T("DIR"))
1822                                        entry.flags |= CDirentry::flag_dir;
1823                                else if (token.Find(_T("-/.")) != -1)
1824                                        break;
1825
1826                                ++skippedCount;
1827
1828                                if (!line.GetToken(++index, token))
1829                                        return false;
1830                        } while (true);
1831
1832                        if (!ParseShortDate(token, entry))
1833                                return false;
1834
1835                        // Get time
1836                        if (!line.GetToken(++index, token))
1837                                return false;
1838
1839                        if (!ParseTime(token, entry))
1840                                return false;
1841
1842                        // Get filename
1843                        if (!line.GetToken(++index, token, true))
1844                                return false;
1845
1846                        entry.name = token.GetString();
1847                        if (entry.name.size() >= 5) {
1848                                std::wstring type = fz::str_tolower_ascii(entry.name.substr(entry.name.size() - 5));
1849                                if (!skippedCount && type == _T("<dir>")) {
1850                                        entry.flags |= CDirentry::flag_dir;
1851                                        entry.name = entry.name.substr(0, entry.name.size() - 5);
1852                                        while (!entry.name.empty() && entry.name.back() == ' ')
1853                                                entry.name.pop_back();
1854                                }
1855                        }
1856                }
1857                else {
1858                        // Get day
1859                        if (!line.GetToken(++index, token))
1860                                return false;
1861
1862                        if (!token.IsNumeric() && !token.IsLeftNumeric())
1863                                return false;
1864
1865                        int64_t day = token.GetNumber();
1866                        if (day < 0 || day > 31)
1867                                return false;
1868
1869                        // Get Year
1870                        if (!line.GetToken(++index, token))
1871                                return false;
1872
1873                        if (!token.IsNumeric())
1874                                return false;
1875
1876                        int64_t year = token.GetNumber();
1877                        if (year < 50)
1878                                year += 2000;
1879                        else if (year < 1000)
1880                                year += 1900;
1881
1882                        if (!entry.time.set(fz::datetime::utc, year, month, day)) {
1883                                return false;
1884                        }
1885
1886                        // Get time
1887                        if (!line.GetToken(++index, token))
1888                                return false;
1889
1890                        if (!ParseTime(token, entry))
1891                                return false;
1892
1893                        // Get filename
1894                        if (!line.GetToken(++index, token, 1))
1895                                return false;
1896
1897                        entry.name = token.GetString();
1898                        char chr = token[token.GetLength() - 1];
1899                        if (chr == '/' || chr == '\\') {
1900                                entry.flags |= CDirentry::flag_dir;
1901                                entry.name.pop_back();
1902                        }
1903                }
1904                entry.target.clear();
1905                entry.ownerGroup = objcache.get(std::wstring());
1906                entry.permissions = entry.ownerGroup;
1907                entry.time += m_timezoneOffset;
1908        }
1909
1910        return true;
1911}
1912
1913bool CDirectoryListingParser::AddData(char *pData, int len)
1914{
1915        ConvertEncoding( pData, len );
1916
1917        m_DataList.emplace_back(pData, len);
1918        m_totalData += len;
1919
1920        if (m_totalData < 512)
1921                return true;
1922
1923        return ParseData(true);
1924}
1925
1926bool CDirectoryListingParser::AddLine(wchar_t const* pLine)
1927{
1928        if (m_pControlSocket)
1929                m_pControlSocket->LogMessageRaw(MessageType::RawList, pLine);
1930
1931        while (*pLine == ' ' || *pLine == '\t')
1932                ++pLine;
1933
1934        if (!*pLine)
1935                return false;
1936
1937        const int len = fz::strlen(pLine);
1938
1939        wchar_t* p = new wchar_t[len + 1];
1940
1941        wcscpy(p, pLine);
1942
1943        CLine line(p, len);
1944
1945        ParseLine(line, m_server.GetType(), false);
1946
1947        return true;
1948}
1949
1950CLine *CDirectoryListingParser::GetLine(bool breakAtEnd /*=false*/, bool &error)
1951{
1952        while (!m_DataList.empty()) {
1953                // Trim empty lines and spaces
1954                auto iter = m_DataList.begin();
1955                int len = iter->len;
1956                while (iter->p[m_currentOffset] == '\r' || iter->p[m_currentOffset] == '\n' || iter->p[m_currentOffset] == ' ' || iter->p[m_currentOffset] == '\t' || !iter->p[m_currentOffset]) {
1957                        ++m_currentOffset;
1958                        if (m_currentOffset >= len) {
1959                                delete [] iter->p;
1960                                ++iter;
1961                                m_currentOffset = 0;
1962                                if (iter == m_DataList.end()) {
1963                                        m_DataList.clear();
1964                                        return 0;
1965                                }
1966                                len = iter->len;
1967                        }
1968                }
1969                m_DataList.erase(m_DataList.begin(), iter);
1970                iter = m_DataList.begin();
1971
1972                // Remember start offset and find next linebreak
1973                int startpos = m_currentOffset;
1974                int reslen = 0;
1975
1976                int emptylen = 0;
1977
1978                int currentOffset = m_currentOffset;
1979                while (iter->p[currentOffset] != '\n' && iter->p[currentOffset] != '\r' && iter->p[currentOffset]) {
1980                        if (iter->p[currentOffset] == ' ' || iter->p[currentOffset] == '\t')
1981                                ++emptylen;
1982                        else
1983                                emptylen = 0;
1984                        ++reslen;
1985
1986                        ++currentOffset;
1987                        if (currentOffset >= len) {
1988                                ++iter;
1989                                if (iter == m_DataList.end()) {
1990                                        if (reslen > 10000) {
1991                                                if (m_pControlSocket) {
1992                                                        m_pControlSocket->LogMessage(MessageType::Error, _("Received a line exceeding 10000 characters, aborting."));
1993                                                }
1994                                                error = true;
1995                                                return 0;
1996                                        }
1997                                        if (breakAtEnd)
1998                                                return 0;
1999                                        break;
2000                                }
2001                                len = iter->len;
2002                                currentOffset = 0;
2003                        }
2004                }
2005
2006                if (reslen > 10000) {
2007                        if (m_pControlSocket) {
2008                                m_pControlSocket->LogMessage(MessageType::Error, _("Received a line exceeding 10000 characters, aborting."));
2009                        }
2010                        error = true;
2011                        return 0;
2012                }
2013                m_currentOffset = currentOffset;
2014
2015                // Reslen is now the length of the line, including any terminating whitespace
2016                int const buflen = reslen + 1;
2017                char *res = new char[buflen];
2018                res[reslen] = 0;
2019
2020                int respos = 0;
2021
2022                // Copy line data
2023                auto i = m_DataList.begin();
2024                while (i != iter && reslen) {
2025                        int copylen = i->len - startpos;
2026                        if (copylen > reslen)
2027                                copylen = reslen;
2028                        memcpy(&res[respos], &i->p[startpos], copylen);
2029                        reslen -= copylen;
2030                        respos += i->len - startpos;
2031                        startpos = 0;
2032
2033                        delete [] i->p;
2034                        ++i;
2035                }
2036                ;
2037                // Copy last chunk
2038                if (iter != m_DataList.end() && reslen) {
2039                        int copylen = m_currentOffset-startpos;
2040                        if (copylen > reslen)
2041                                copylen = reslen;
2042                        memcpy(&res[respos], &iter->p[startpos], copylen);
2043                        if (reslen >= iter->len) {
2044                                delete [] iter->p;
2045                                m_DataList.erase(m_DataList.begin(), ++iter);
2046                        }
2047                        else
2048                                m_DataList.erase(m_DataList.begin(), iter);
2049                }
2050                else
2051                        m_DataList.erase(m_DataList.begin(), iter);
2052
2053                size_t lineLength{};
2054                wchar_t* buffer;
2055                if (m_pControlSocket) {
2056                        buffer = m_pControlSocket->ConvToLocalBuffer(res, buflen, lineLength);
2057                        m_pControlSocket->LogMessageRaw(MessageType::RawList, buffer);
2058                }
2059                else {
2060                        wxString str(res, wxConvUTF8);
2061                        if (str.empty()) {
2062                                str = wxString(res, wxConvLocal);
2063                                if (str.empty())
2064                                        str = wxString(res, wxConvISO8859_1);
2065                        }
2066                        lineLength = str.Len() + 1;
2067                        buffer = new wchar_t[str.Len() + 1];
2068                        wcscpy(buffer, str.c_str());
2069                }
2070                delete [] res;
2071
2072                if (!buffer) {
2073                        // Line contained no usable data, start over
2074                        continue;
2075                }
2076
2077                return new CLine(buffer, lineLength - 1, emptylen);
2078        }
2079
2080        return 0;
2081}
2082
2083bool CDirectoryListingParser::ParseAsWfFtp(CLine &line, CDirentry &entry)
2084{
2085        int index = 0;
2086        CToken token;
2087
2088        // Get filename
2089        if (!line.GetToken(index++, token))
2090                return false;
2091
2092        entry.name = token.GetString();
2093
2094        // Get filesize
2095        if (!line.GetToken(index++, token))
2096                return false;
2097
2098        if (!token.IsNumeric())
2099                return false;
2100
2101        entry.size = token.GetNumber();
2102
2103        entry.flags = 0;
2104
2105        // Parse date
2106        if (!line.GetToken(index++, token))
2107                return false;
2108
2109        if (!ParseShortDate(token, entry))
2110                return false;
2111
2112        // Unused token
2113        if (!line.GetToken(index++, token))
2114                return false;
2115
2116        if (token.GetString().back() != '.')
2117                return false;
2118
2119        // Parse time
2120        if (!line.GetToken(index++, token, true))
2121                return false;
2122
2123        if (!ParseTime(token, entry))
2124                return false;
2125
2126        entry.ownerGroup = objcache.get(std::wstring());
2127        entry.permissions = entry.ownerGroup;
2128        entry.time += m_timezoneOffset;
2129
2130        return true;
2131}
2132
2133bool CDirectoryListingParser::ParseAsIBM_MVS(CLine &line, CDirentry &entry)
2134{
2135        int index = 0;
2136        CToken token;
2137
2138        // volume
2139        if (!line.GetToken(index++, token))
2140                return false;
2141
2142        // unit
2143        if (!line.GetToken(index++, token))
2144                return false;
2145
2146        // Referred date
2147        if (!line.GetToken(index++, token))
2148                return false;
2149
2150        entry.flags = 0;
2151        if (token.GetString() != _T("**NONE**") && !ParseShortDate(token, entry)) {
2152                // Perhaps of the following type:
2153                // TSO004 3390 VSAM FOO.BAR
2154                if (token.GetString() != _T("VSAM"))
2155                        return false;
2156
2157                if (!line.GetToken(index++, token))
2158                        return false;
2159
2160                entry.name = token.GetString();
2161                if (entry.name.find(' ') != std::wstring::npos)
2162                        return false;
2163
2164                entry.size = -1;
2165                entry.ownerGroup = objcache.get(std::wstring());
2166                entry.permissions = entry.ownerGroup;
2167
2168                return true;
2169        }
2170
2171        // ext
2172        if (!line.GetToken(index++, token))
2173                return false;
2174        if (!token.IsNumeric())
2175                return false;
2176
2177        int prevLen = token.GetLength();
2178
2179        // used
2180        if (!line.GetToken(index++, token))
2181                return false;
2182        if (token.IsNumeric() || token.GetString() == _T("????") || token.GetString() == _T("++++") ) {
2183                // recfm
2184                if (!line.GetToken(index++, token))
2185                        return false;
2186                if (token.IsNumeric())
2187                        return false;
2188        }
2189        else {
2190                if (prevLen < 6)
2191                        return false;
2192        }
2193
2194        // lrecl
2195        if (!line.GetToken(index++, token))
2196                return false;
2197        if (!token.IsNumeric())
2198                return false;
2199
2200        // blksize
2201        if (!line.GetToken(index++, token))
2202                return false;
2203        if (!token.IsNumeric())
2204                return false;
2205
2206        // dsorg
2207        if (!line.GetToken(index++, token))
2208                return false;
2209
2210        if (token.GetString() == _T("PO") || token.GetString() == _T("PO-E"))
2211        {
2212                entry.flags |= CDirentry::flag_dir;
2213                entry.size = -1;
2214        }
2215        else
2216                entry.size = 100;
2217
2218        // name of dataset or sequential file
2219        if (!line.GetToken(index++, token, true))
2220                return false;
2221
2222        entry.name = token.GetString();
2223
2224        entry.ownerGroup = objcache.get(std::wstring());
2225        entry.permissions = entry.ownerGroup;
2226
2227        return true;
2228}
2229
2230bool CDirectoryListingParser::ParseAsIBM_MVS_PDS(CLine &line, CDirentry &entry)
2231{
2232        int index = 0;
2233        CToken token;
2234
2235        // pds member name
2236        if (!line.GetToken(index++, token))
2237                return false;
2238        entry.name = token.GetString();
2239
2240        // vv.mm
2241        if (!line.GetToken(index++, token))
2242                return false;
2243
2244        entry.flags = 0;
2245
2246        // creation date
2247        if (!line.GetToken(index++, token))
2248                return false;
2249        if (!ParseShortDate(token, entry))
2250                return false;
2251
2252        // modification date
2253        if (!line.GetToken(index++, token))
2254                return false;
2255        if (!ParseShortDate(token, entry))
2256                return false;
2257
2258        // modification time
2259        if (!line.GetToken(index++, token))
2260                return false;
2261        if (!ParseTime(token, entry))
2262                return false;
2263
2264        // size
2265        if (!line.GetToken(index++, token))
2266                return false;
2267        if (!token.IsNumeric())
2268                return false;
2269        entry.size = token.GetNumber();
2270
2271        // init
2272        if (!line.GetToken(index++, token))
2273                return false;
2274        if (!token.IsNumeric())
2275                return false;
2276
2277        // mod
2278        if (!line.GetToken(index++, token))
2279                return false;
2280        if (!token.IsNumeric())
2281                return false;
2282
2283        // id
2284        if (!line.GetToken(index++, token, true))
2285                return false;
2286
2287        entry.ownerGroup = objcache.get(std::wstring());
2288        entry.permissions = entry.ownerGroup;
2289        entry.time += m_timezoneOffset;
2290
2291        return true;
2292}
2293
2294bool CDirectoryListingParser::ParseAsIBM_MVS_Migrated(CLine &line, CDirentry &entry)
2295{
2296        // Migrated MVS file
2297        // "Migrated                            SOME.NAME"
2298
2299        int index = 0;
2300        CToken token;
2301        if (!line.GetToken(index, token))
2302                return false;
2303
2304        std::wstring s = fz::str_tolower_ascii(token.GetString());
2305        if (s != _T("migrated"))
2306                return false;
2307
2308        if (!line.GetToken(++index, token))
2309                return false;
2310
2311        entry.name = token.GetString();
2312
2313        if (line.GetToken(++index, token))
2314                return false;
2315
2316        entry.flags = 0;
2317        entry.size = -1;
2318        entry.ownerGroup = objcache.get(std::wstring());
2319        entry.permissions = entry.ownerGroup;
2320
2321        return true;
2322}
2323
2324bool CDirectoryListingParser::ParseAsIBM_MVS_PDS2(CLine &line, CDirentry &entry)
2325{
2326        int index = 0;
2327        CToken token;
2328        if (!line.GetToken(index, token))
2329                return false;
2330
2331        entry.name = token.GetString();
2332
2333        entry.flags = 0;
2334        entry.ownerGroup = objcache.get(std::wstring());
2335        entry.permissions = entry.ownerGroup;
2336        entry.size = -1;
2337
2338        if (!line.GetToken(++index, token))
2339                return true;
2340
2341        entry.size = token.GetNumber(CToken::hex);
2342        if (entry.size == -1)
2343                return false;
2344
2345        // Unused hexadecimal token
2346        if (!line.GetToken(++index, token))
2347                return false;
2348        if (!token.IsNumeric(CToken::hex))
2349                return false;
2350
2351        // Unused numeric token
2352        if (!line.GetToken(++index, token))
2353                return false;
2354        if (!token.IsNumeric())
2355                return false;
2356
2357        int start = ++index;
2358        while (line.GetToken(index, token)) {
2359                ++index;
2360        }
2361        if ((index - start < 2))
2362                return false;
2363        --index;
2364
2365        if (!line.GetToken(index, token)) {
2366                return false;
2367        }
2368        if (!token.IsNumeric() && (token.GetString() != _T("ANY")))
2369                return false;
2370
2371        if (!line.GetToken(index - 1, token)) {
2372                return false;
2373        }
2374        if (!token.IsNumeric() && (token.GetString() != _T("ANY")))
2375                return false;
2376
2377        for (int i = start; i < index - 1; ++i) {
2378                if (!line.GetToken(i, token)) {
2379                        return false;
2380                }
2381                int len = token.GetLength();
2382                for (int j = 0; j < len; ++j)
2383                        if (token[j] < 'A' || token[j] > 'Z')
2384                                return false;
2385        }
2386
2387        return true;
2388}
2389
2390bool CDirectoryListingParser::ParseAsIBM_MVS_Tape(CLine &line, CDirentry &entry)
2391{
2392        int index = 0;
2393        CToken token;
2394
2395        // volume
2396        if (!line.GetToken(index++, token))
2397                return false;
2398
2399        // unit
2400        if (!line.GetToken(index++, token))
2401                return false;
2402
2403        std::wstring s = fz::str_tolower_ascii(token.GetString());
2404        if (s != _T("tape"))
2405                return false;
2406
2407        // dsname
2408        if (!line.GetToken(index++, token))
2409                return false;
2410
2411        entry.name = token.GetString();
2412        entry.flags = 0;
2413        entry.ownerGroup = objcache.get(std::wstring());
2414        entry.permissions = objcache.get(std::wstring());
2415        entry.size = -1;
2416
2417        if (line.GetToken(index++, token))
2418                return false;
2419
2420        return true;
2421}
2422
2423bool CDirectoryListingParser::ParseComplexFileSize(CToken& token, int64_t& size, int blocksize /*=-1*/)
2424{
2425        if (token.IsNumeric()) {
2426                size = token.GetNumber();
2427                if (blocksize != -1)
2428                        size *= blocksize;
2429
2430                return true;
2431        }
2432
2433        int len = token.GetLength();
2434
2435        char last = token[len - 1];
2436        if (last == 'B' || last == 'b')
2437        {
2438                if (len == 1)
2439                        return false;
2440
2441                char c = token[--len - 1];
2442                if (c < '0' || c > '9')
2443                {
2444                        --len;
2445                        last = c;
2446                }
2447                else
2448                        last = 0;
2449        }
2450        else if (last >= '0' && last <= '9')
2451                last = 0;
2452        else
2453        {
2454                if (--len == 0)
2455                        return false;
2456        }
2457
2458        size = 0;
2459
2460        int dot = -1;
2461        for (int i = 0; i < len; ++i)
2462        {
2463                char c = token[i];
2464                if (c >= '0' && c <= '9')
2465                {
2466                        size *= 10;
2467                        size += c - '0';
2468                }
2469                else if (c == '.')
2470                {
2471                        if (dot != -1)
2472                                return false;
2473                        dot = len - i - 1;
2474                }
2475                else
2476                        return false;
2477        }
2478        switch (last)
2479        {
2480        case 'k':
2481        case 'K':
2482                size *= 1024;
2483                break;
2484        case 'm':
2485        case 'M':
2486                size *= 1024 * 1024;
2487                break;
2488        case 'g':
2489        case 'G':
2490                size *= 1024 * 1024 * 1024;
2491                break;
2492        case 't':
2493        case 'T':
2494                size *= 1024 * 1024;
2495                size *= 1024 * 1024;
2496                break;
2497        case 'b':
2498        case 'B':
2499                break;
2500        case 0:
2501                if (blocksize != -1)
2502                        size *= blocksize;
2503                break;
2504        default:
2505                return false;
2506        }
2507        while (dot-- > 0)
2508                size /= 10;
2509
2510        return true;
2511}
2512
2513int CDirectoryListingParser::ParseAsMlsd(CLine &line, CDirentry &entry)
2514{
2515        // MLSD format as described here: http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt
2516
2517        // Parsing is done strict, abort on slightest error.
2518
2519        CToken token;
2520
2521        if (!line.GetToken(0, token))
2522                return 0;
2523
2524        std::wstring const facts = token.GetString();
2525        if (facts.empty())
2526                return 0;
2527
2528        entry.flags = 0;
2529        entry.size = -1;
2530
2531        std::wstring owner, group, uid, gid;
2532        std::wstring ownerGroup;
2533        std::wstring permissions;
2534
2535        size_t start = 0;
2536        while (start < facts.size()) {
2537                auto delim = facts.find(';', start);
2538                if (delim == std::wstring::npos) {
2539                        delim = facts.size();
2540                }
2541                else if (delim < start + 3) {
2542                        return 0;
2543                }
2544
2545                auto const pos = facts.find('=', start);
2546                if (pos == std::wstring::npos || pos < start + 1 || pos > delim)
2547                        return 0;
2548
2549                std::wstring factname = fz::str_tolower_ascii(facts.substr(start, pos - start));
2550                std::wstring value = facts.substr(pos + 1, delim - pos - 1);
2551                if (factname == _T("type")) {
2552                        auto colonPos = value.find(':');
2553                        std::wstring valuePrefix;
2554                        if (colonPos == std::wstring::npos)
2555                                valuePrefix = fz::str_tolower_ascii(value);
2556                        else
2557                                valuePrefix = fz::str_tolower_ascii(value.substr(0, colonPos));
2558
2559                        if (valuePrefix == _T("dir") && colonPos == std::wstring::npos)
2560                                entry.flags |= CDirentry::flag_dir;
2561                        else if (valuePrefix == _T("os.unix=slink") || valuePrefix == _T("os.unix=symlink")) {
2562                                entry.flags |= CDirentry::flag_dir | CDirentry::flag_link;
2563                                if (colonPos != std::wstring::npos)
2564                                        entry.target = CSparseOptional<std::wstring>(value.substr(colonPos));
2565                        }
2566                        else if ((valuePrefix == _T("cdir") || valuePrefix == _T("pdir")) && colonPos == std::wstring::npos) {
2567                                // Current and parent directory, don't parse it
2568                                return 2;
2569                        }
2570                }
2571                else if (factname == _T("size")) {
2572                        entry.size = 0;
2573
2574                        for (unsigned int i = 0; i < value.size(); ++i) {
2575                                if (value[i] < '0' || value[i] > '9')
2576                                        return 0;
2577                                entry.size *= 10;
2578                                entry.size += value[i] - '0';
2579                        }
2580                }
2581                else if (factname == _T("modify") ||
2582                        (!entry.has_date() && factname == _T("create")))
2583                {
2584                        entry.time = fz::datetime(value, fz::datetime::utc);
2585                        if (!entry.time.empty()) {
2586                                return 0;
2587                        }
2588                }
2589                else if (factname == _T("perm")) {
2590                        if (!value.empty()) {
2591                                if (!permissions.empty())
2592                                        permissions = value + _T(" (") + permissions + _T(")");
2593                                else
2594                                        permissions = value;
2595                        }
2596                }
2597                else if (factname == _T("unix.mode")) {
2598                        if (!permissions.empty())
2599                                permissions = permissions + _T(" (") + value + _T(")");
2600                        else
2601                                permissions = value;
2602                }
2603                else if (factname == _T("unix.owner") || factname == _T("unix.user"))
2604                        owner = value;
2605                else if (factname == _T("unix.group"))
2606                        group = value;
2607                else if (factname == _T("unix.uid"))
2608                        uid = value;
2609                else if (factname == _T("unix.gid"))
2610                        gid = value;
2611
2612                start = delim + 1;
2613        }
2614
2615        // The order of the facts is undefined, so assemble ownerGroup in correct
2616        // order
2617        if (!owner.empty())
2618                ownerGroup += owner;
2619        else if (!uid.empty())
2620                ownerGroup += uid;
2621        if (!group.empty())
2622                ownerGroup += _T(" ") + group;
2623        else if (!gid.empty())
2624                ownerGroup += _T(" ") + gid;
2625
2626        if (!line.GetToken(1, token, true, true))
2627                return 0;
2628
2629        entry.name = token.GetString();
2630        entry.ownerGroup = objcache.get(ownerGroup);
2631        entry.permissions = objcache.get(permissions);
2632
2633        return 1;
2634}
2635
2636bool CDirectoryListingParser::ParseAsOS9(CLine &line, CDirentry &entry)
2637{
2638        int index = 0;
2639
2640        // Get owner
2641        CToken ownerGroupToken;
2642        if (!line.GetToken(index++, ownerGroupToken))
2643                return false;
2644
2645        // Make sure it's number.number
2646        int pos = ownerGroupToken.Find('.');
2647        if (pos == -1 || !pos || pos == ((int)ownerGroupToken.GetLength() - 1))
2648                return false;
2649
2650        if (!ownerGroupToken.IsNumeric(0, pos))
2651                return false;
2652
2653        if (!ownerGroupToken.IsNumeric(pos + 1, ownerGroupToken.GetLength() - pos - 1))
2654                return false;
2655
2656        entry.flags = 0;
2657
2658        // Get date
2659        CToken token;
2660        if (!line.GetToken(index++, token))
2661                return false;
2662
2663        if (!ParseShortDate(token, entry, true))
2664                return false;
2665
2666        // Unused token
2667        if (!line.GetToken(index++, token))
2668                return false;
2669
2670        // Get perms
2671        CToken permToken;
2672        if (!line.GetToken(index++, permToken))
2673                return false;
2674
2675        if (permToken[0] == 'd')
2676                entry.flags |= CDirentry::flag_dir;
2677
2678        // Unused token
2679        if (!line.GetToken(index++, token))
2680                return false;
2681
2682        // Get Size
2683        if (!line.GetToken(index++, token))
2684                return false;
2685
2686        if (!token.IsNumeric())
2687                return false;
2688
2689        entry.size = token.GetNumber();
2690
2691        // Filename
2692        if (!line.GetToken(index++, token, true))
2693                return false;
2694
2695        entry.name = token.GetString();
2696        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
2697        entry.permissions = objcache.get(permToken.GetString());
2698
2699        return true;
2700}
2701
2702void CDirectoryListingParser::Reset()
2703{
2704        for (auto iter = m_DataList.begin(); iter != m_DataList.end(); ++iter)
2705                delete [] iter->p;
2706        m_DataList.clear();
2707
2708        delete m_prevLine;
2709        m_prevLine = 0;
2710
2711        m_entryList.clear();
2712        m_fileList.clear();
2713        m_currentOffset = 0;
2714        m_fileListOnly = true;
2715        m_maybeMultilineVms = false;
2716}
2717
2718bool CDirectoryListingParser::ParseAsZVM(CLine &line, CDirentry &entry)
2719{
2720        int index = 0;
2721        CToken token;
2722
2723        // Get name
2724        if (!line.GetToken(index, token))
2725                return false;
2726
2727        entry.name = token.GetString();
2728
2729        // Get filename extension
2730        if (!line.GetToken(++index, token))
2731                return false;
2732        entry.name += _T(".") + token.GetString();
2733
2734        // File format. Unused
2735        if (!line.GetToken(++index, token))
2736                return false;
2737        std::wstring format = token.GetString();
2738        if (format != _T("V") && format != _T("F"))
2739                return false;
2740
2741        // Record length
2742        if (!line.GetToken(++index, token))
2743                return false;
2744
2745        if (!token.IsNumeric())
2746                return false;
2747
2748        entry.size = token.GetNumber();
2749
2750        // Number of records
2751        if (!line.GetToken(++index, token))
2752                return false;
2753
2754        if (!token.IsNumeric())
2755                return false;
2756
2757        entry.size *= token.GetNumber();
2758
2759        // Unused (Block size?)
2760        if (!line.GetToken(++index, token))
2761                return false;
2762
2763        if (!token.IsNumeric())
2764                return false;
2765
2766        entry.flags = 0;
2767
2768        // Date
2769        if (!line.GetToken(++index, token))
2770                return false;
2771
2772        if (!ParseShortDate(token, entry, true))
2773                return false;
2774
2775        // Time
2776        if (!line.GetToken(++index, token))
2777                return false;
2778
2779        if (!ParseTime(token, entry))
2780                return false;
2781
2782        // Owner
2783        CToken ownerGroupToken;
2784        if (!line.GetToken(++index, ownerGroupToken))
2785                return false;
2786
2787        // No further token!
2788        if (line.GetToken(++index, token))
2789                return false;
2790
2791        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
2792        entry.permissions = objcache.get(std::wstring());
2793        entry.target.clear();
2794        entry.time += m_timezoneOffset;
2795
2796        return true;
2797}
2798
2799bool CDirectoryListingParser::ParseAsHPNonstop(CLine &line, CDirentry &entry)
2800{
2801        int index = 0;
2802        CToken token;
2803
2804        // Get name
2805        if (!line.GetToken(index, token))
2806                return false;
2807
2808        entry.name = token.GetString();
2809
2810        // File code, numeric, unsuded
2811        if (!line.GetToken(++index, token))
2812                return false;
2813        if (!token.IsNumeric())
2814                return false;
2815
2816        // Size
2817        if (!line.GetToken(++index, token))
2818                return false;
2819        if (!token.IsNumeric())
2820                return false;
2821
2822        entry.size = token.GetNumber();
2823
2824        entry.flags = 0;
2825
2826        // Date
2827        if (!line.GetToken(++index, token))
2828                return false;
2829        if (!ParseShortDate(token, entry, false))
2830                return false;
2831
2832        // Time
2833        if (!line.GetToken(++index, token))
2834                return false;
2835        if (!ParseTime(token, entry))
2836                return false;
2837
2838        // Owner
2839        if (!line.GetToken(++index, token))
2840                return false;
2841        std::wstring ownerGroup = token.GetString();
2842
2843        if (token[token.GetLength() - 1] == ',') {
2844                // Owner, part 2
2845                if (!line.GetToken(++index, token))
2846                        return false;
2847                ownerGroup += _T(" ") + token.GetString();
2848        }
2849
2850        // Permissions
2851        CToken permToken;
2852        if (!line.GetToken(++index, permToken))
2853                return false;
2854
2855        // Nothing
2856        if (line.GetToken(++index, token))
2857                return false;
2858
2859        entry.permissions = objcache.get(permToken.GetString());
2860        entry.ownerGroup = objcache.get(ownerGroup);
2861
2862        return true;
2863}
2864
2865bool CDirectoryListingParser::GetMonthFromName(const std::wstring& name, int &month)
2866{
2867        std::wstring lower = fz::str_tolower_ascii(name);
2868        auto iter = m_MonthNamesMap.find(lower);
2869        if (iter == m_MonthNamesMap.end())
2870                return false;
2871
2872        month = iter->second;
2873
2874        return true;
2875}
2876
2877char ebcdic_table[256] = {
2878        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 0
2879        ' ',  ' ',  ' ',  ' ',  ' ',  '\n', ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '\n', // 1
2880        ' ',  ' ',  ' ',  ' ',  ' ',  '\n', ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 2
2881        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 3
2882        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '.',  '<',  '(',  '+',  '|',  // 4
2883        '&',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '!',  '$',  '*',  ')',  ';',  ' ',  // 5
2884        '-',  '/',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '|',  ',',  '%',  '_',  '>',  '?',  // 6
2885        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '`',  ':',  '#',  '@',  '\'', '=',  '"',  // 7
2886        ' ',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 8
2887        ' ',  'j',  'k',  'l',  'm',  'n',  'o',  'p',  'q',  'r',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 9
2888        ' ',  '~',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // a
2889        '^',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '[',  ']',  ' ',  ' ',  ' ',  ' ',  // b
2890        '{',  'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',  'I',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // c
2891        '}',  'J',  'K',  'L',  'M',  'N',  'O',  'P',  'Q',  'R',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // d
2892        '\\', ' ',  'S',  'T',  'U',  'V',  'W',  'X',  'Y',  'Z',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // e
2893        '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ' ',  ' ',  ' ',  ' ',  ' ',  ' '   // f
2894};
2895
2896void CDirectoryListingParser::ConvertEncoding(char *pData, int len)
2897{
2898        if (m_listingEncoding != listingEncoding::ebcdic)
2899                return;
2900
2901        for (int i = 0; i < len; ++i) {
2902                pData[i] = ebcdic_table[static_cast<unsigned char>(pData[i])];
2903        }
2904}
2905
2906void CDirectoryListingParser::DeduceEncoding()
2907{
2908        if (m_listingEncoding != listingEncoding::unknown)
2909                return;
2910
2911        int count[256];
2912
2913        memset(&count, 0, sizeof(int)*256);
2914
2915        for (auto const& data : m_DataList) {
2916                for (int i = 0; i < data.len; ++i)
2917                        ++count[static_cast<unsigned char>(data.p[i])];
2918        }
2919
2920        int count_normal = 0;
2921        int count_ebcdic = 0;
2922        for (int i = '0'; i <= '9'; ++i) {
2923                count_normal += count[i];
2924        }
2925        for (int i = 'a'; i <= 'z'; ++i) {
2926                count_normal += count[i];
2927        }
2928        for (int i = 'A'; i <= 'Z'; ++i) {
2929                count_normal += count[i];
2930        }
2931
2932        for (int i = 0x81; i <= 0x89; ++i) {
2933                count_ebcdic += count[i];
2934        }
2935        for (int i = 0x91; i <= 0x99; ++i) {
2936                count_ebcdic += count[i];
2937        }
2938        for (int i = 0xa2; i <= 0xa9; ++i) {
2939                count_ebcdic += count[i];
2940        }
2941        for (int i = 0xc1; i <= 0xc9; ++i) {
2942                count_ebcdic += count[i];
2943        }
2944        for (int i = 0xd1; i <= 0xd9; ++i) {
2945                count_ebcdic += count[i];
2946        }
2947        for (int i = 0xe2; i <= 0xe9; ++i) {
2948                count_ebcdic += count[i];
2949        }
2950        for (int i = 0xf0; i <= 0xf9; ++i) {
2951                count_ebcdic += count[i];
2952        }
2953
2954
2955        if ((count[0x1f] || count[0x15] || count[0x25]) && !count[0x0a] && count[static_cast<unsigned char>('@')] && count[static_cast<unsigned char>('@')] > count[static_cast<unsigned char>(' ')] && count_ebcdic > count_normal) {
2956                if (m_pControlSocket) {
2957                        m_pControlSocket->LogMessage(MessageType::Status, _("Received a directory listing which appears to be encoded in EBCDIC."));
2958                }
2959                m_listingEncoding = listingEncoding::ebcdic;
2960                for (auto it = m_DataList.begin(); it != m_DataList.end(); ++it)
2961                        ConvertEncoding(it->p, it->len);
2962        }
2963        else
2964                m_listingEncoding = listingEncoding::normal;
2965}
Note: See TracBrowser for help on using the repository browser.