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

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

First release to xenial

File size: 65.7 KB
Line 
1#include <filezilla.h>
2#include "directorylistingparser.h"
3#include "ControlSocket.h"
4
5#include <algorithm>
6#include <vector>
7
8std::map<wxString, 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<fzstring> const& get(fzstring 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<fzstring>> 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(wxChar const* p, unsigned int len)
62                : m_pToken(p)
63                , m_len(len)
64        {}
65
66        wxChar const* GetToken() const
67        {
68                return m_pToken;
69        }
70
71        unsigned int GetLength() const
72        {
73                return m_len;
74        }
75
76        fzstring GetString() const
77        {
78                if (!m_pToken || !m_len) {
79                        return fzstring();
80                }
81                else {
82                        return fzstring(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 < wxMin(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 wxChar* 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(wxChar 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 wxChar& 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        wxChar operator[](unsigned int n) const
252        {
253                if (n >= m_len)
254                        return 0;
255
256                return m_pToken[n];
257        }
258
259protected:
260        wxChar 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(wxChar* 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 = wxStrlen(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                                wxChar 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 wxChar* 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                wxChar* p = new wxChar[newLen];
384                memcpy(p, m_pLine, m_len * sizeof(wxChar));
385                p[m_len] = ' ';
386                memcpy(p + m_len + 1, pLine->m_pLine, pLine->m_len * sizeof(wxChar));
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        wxChar* 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<wxString, int> combo;
603                for (auto iter = m_MonthNamesMap.begin(); iter != m_MonthNamesMap.end(); ++iter)
604                {
605                        // January could be 1 or 0, depends how the server counts
606                        combo[wxString::Format(_T("%s%02d"), iter->first, iter->second)] = iter->second;
607                        combo[wxString::Format(_T("%s%02d"), iter->first, iter->second - 1)] = iter->second;
608                        if (iter->second < 10)
609                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second)] = iter->second;
610                        else
611                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second % 10)] = iter->second;
612                        if (iter->second <= 10)
613                                combo[wxString::Format(_T("%s%d"), iter->first, iter->second - 1)] = iter->second;
614                        else
615                                combo[wxString::Format(_T("%s%d"), iter->first, (iter->second - 1) % 10)] = iter->second;
616                }
617                m_MonthNamesMap.insert(combo.begin(), combo.end());
618
619                m_MonthNamesMap[_T("1")] = 1;
620                m_MonthNamesMap[_T("2")] = 2;
621                m_MonthNamesMap[_T("3")] = 3;
622                m_MonthNamesMap[_T("4")] = 4;
623                m_MonthNamesMap[_T("5")] = 5;
624                m_MonthNamesMap[_T("6")] = 6;
625                m_MonthNamesMap[_T("7")] = 7;
626                m_MonthNamesMap[_T("8")] = 8;
627                m_MonthNamesMap[_T("9")] = 9;
628                m_MonthNamesMap[_T("10")] = 10;
629                m_MonthNamesMap[_T("11")] = 11;
630                m_MonthNamesMap[_T("12")] = 12;
631        }
632
633#ifdef LISTDEBUG
634        for (unsigned int i = 0; data[i][0]; ++i)
635        {
636                unsigned int len = (unsigned int)strlen(data[i]);
637                char *pData = new char[len + 3];
638                strcpy(pData, data[i]);
639                strcat(pData, "\r\n");
640                AddData(pData, len + 2);
641        }
642#endif
643}
644
645CDirectoryListingParser::~CDirectoryListingParser()
646{
647        for (auto iter = m_DataList.begin(); iter != m_DataList.end(); ++iter)
648                delete [] iter->p;
649
650        delete m_prevLine;
651}
652
653bool CDirectoryListingParser::ParseData(bool partial)
654{
655        DeduceEncoding();
656
657        bool error = false;
658        CLine *pLine = GetLine(partial, error);
659        while (pLine) {
660                bool res = ParseLine(*pLine, m_server.GetType(), false);
661                if (!res) {
662                        if (m_prevLine) {
663                                CLine* pConcatenatedLine = m_prevLine->Concat(pLine);
664                                res = ParseLine(*pConcatenatedLine, m_server.GetType(), true);
665                                delete pConcatenatedLine;
666                                delete m_prevLine;
667
668                                if (res) {
669                                        delete pLine;
670                                        m_prevLine = 0;
671                                }
672                                else
673                                        m_prevLine = pLine;
674                        }
675                        else if (!sftp_mode_) {
676                                m_prevLine = pLine;
677                        }
678                        else {
679                                delete pLine;
680                        }
681                }
682                else {
683                        delete m_prevLine;
684                        m_prevLine = 0;
685                        delete pLine;
686                }
687                pLine = GetLine(partial, error);
688        };
689
690        return !error;
691}
692
693CDirectoryListing CDirectoryListingParser::Parse(const CServerPath &path)
694{
695        CDirectoryListing listing;
696        listing.path = path;
697        listing.m_firstListTime = CMonotonicClock::now();
698
699        if (!ParseData(false)){
700                listing.m_flags |= CDirectoryListing::listing_failed;
701                return listing;
702        }
703
704        if (!m_fileList.empty()) {
705                wxASSERT(m_entryList.empty());
706
707                listing.SetCount(m_fileList.size());
708                unsigned int i = 0;
709                for (auto const& file : m_fileList) {
710                        CDirentry entry;
711                        entry.name = file;
712                        entry.flags = 0;
713                        entry.size = -1;
714                        listing[i++] = entry;
715                }
716        }
717        else {
718                listing.Assign(m_entryList);
719        }
720
721        return listing;
722}
723
724bool CDirectoryListingParser::ParseLine(CLine &line, const enum ServerType serverType, bool concatenated)
725{
726        CRefcountObject<CDirentry> refEntry;
727        CDirentry & entry = refEntry.Get();
728
729        bool res;
730        int ires;
731
732        if (sftp_mode_) {
733                line.SetTokenOffset(1);
734        }
735
736        if (serverType == ZVM) {
737                res = ParseAsZVM(line, entry);
738                if (res)
739                        goto done;
740        }
741        else if (serverType == HPNONSTOP) {
742                res = ParseAsHPNonstop(line, entry);
743                if (res)
744                        goto done;
745        }
746
747        ires = ParseAsMlsd(line, entry);
748        if (ires == 1)
749                goto done;
750        else if (ires == 2)
751                goto skip;
752        res = ParseAsUnix(line, entry, true); // Common 'ls -l'
753        if (res)
754                goto done;
755        res = ParseAsDos(line, entry);
756        if (res)
757                goto done;
758        res = ParseAsEplf(line, entry);
759        if (res)
760                goto done;
761        res = ParseAsVms(line, entry);
762        if (res)
763                goto done;
764        res = ParseOther(line, entry);
765        if (res)
766                goto done;
767        res = ParseAsIbm(line, entry);
768        if (res)
769                goto done;
770        res = ParseAsWfFtp(line, entry);
771        if (res)
772                goto done;
773        res = ParseAsIBM_MVS(line, entry);
774        if (res)
775                goto done;
776        res = ParseAsIBM_MVS_PDS(line, entry);
777        if (res)
778                goto done;
779        res = ParseAsOS9(line, entry);
780        if (res)
781                goto done;
782#ifndef LISTDEBUG_MVS
783        if (serverType == MVS)
784#endif //LISTDEBUG_MVS
785        {
786                res = ParseAsIBM_MVS_Migrated(line, entry);
787                if (res)
788                        goto done;
789                res = ParseAsIBM_MVS_PDS2(line, entry);
790                if (res)
791                        goto done;
792                res = ParseAsIBM_MVS_Tape(line, entry);
793                if (res)
794                        goto done;
795        }
796        res = ParseAsUnix(line, entry, false); // 'ls -l' but without the date/time
797        if (res)
798                goto done;
799
800        // Some servers just send a list of filenames. If a line could not be parsed,
801        // check if it's a filename. If that's the case, store it for later, else clear
802        // list of stored files.
803        // If parsing finishes and no entries could be parsed and none of the lines
804        // contained a space, assume it's a raw filelisting.
805
806        if (!concatenated) {
807                CToken token;
808                if (!line.GetToken(0, token, true) || token.Find(' ') != -1) {
809                        m_maybeMultilineVms = false;
810                        m_fileList.clear();
811                        m_fileListOnly = false;
812                }
813                else {
814                        m_maybeMultilineVms = token.Find(';') != -1;
815                        if (m_fileListOnly)
816                                m_fileList.emplace_back(token.GetString());
817                }
818        }
819        else
820                m_maybeMultilineVms = false;
821
822        return false;
823done:
824
825        m_maybeMultilineVms = false;
826        m_fileList.clear();
827        m_fileListOnly = false;
828
829        // Don't add . or ..
830        if (entry.name == _T(".") || entry.name == _T(".."))
831                return true;
832
833        if (serverType == VMS && entry.is_dir()) {
834                // Trim version information from directories
835                auto pos = entry.name.rfind(';');
836                if (pos != fzstring::npos && pos > 0)
837                        entry.name = entry.name.substr(0, pos);
838        }
839
840        if (sftp_mode_) {
841                line.SetTokenOffset(0);
842
843                CToken t;
844                if (line.GetToken(0, t)) {
845                        int64_t seconds = t.GetNumber();
846                        if (seconds > 0 && seconds <= 0xffffffffll) {
847                                CDateTime time(static_cast<time_t>(seconds), CDateTime::seconds);
848                                if (time.IsValid()) {
849                                        entry.time = time;
850                                }
851                        }
852                }
853        }
854
855        {
856                auto const timezoneOffset = m_server.GetTimezoneOffset();
857                if (timezoneOffset) {
858                        entry.time += duration::from_minutes(timezoneOffset);
859                }
860        }
861
862        m_entryList.emplace_back(std::move(refEntry));
863
864skip:
865        m_maybeMultilineVms = false;
866        m_fileList.clear();
867        m_fileListOnly = false;
868
869        return true;
870}
871
872bool CDirectoryListingParser::ParseAsUnix(CLine &line, CDirentry &entry, bool expect_date)
873{
874        int index = 0;
875        CToken token;
876        if (!line.GetToken(index, token))
877                return false;
878
879        wxChar chr = token[0];
880        if (chr != 'b' &&
881                chr != 'c' &&
882                chr != 'd' &&
883                chr != 'l' &&
884                chr != 'p' &&
885                chr != 's' &&
886                chr != '-')
887                return false;
888
889        fzstring permissions = token.GetString();
890
891        entry.flags = 0;
892
893        if (chr == 'd' || chr == 'l')
894                entry.flags |= CDirentry::flag_dir;
895
896        if (chr == 'l')
897                entry.flags |= CDirentry::flag_link;
898
899        // Check for netware servers, which split the permissions into two parts
900        bool netware = false;
901        if (token.GetLength() == 1) {
902                if (!line.GetToken(++index, token))
903                        return false;
904                permissions += _T(" ") + token.GetString();
905                netware = true;
906        }
907
908        int numOwnerGroup = 3;
909        if (!netware) {
910                // Filter out link count, we don't need it
911                if (!line.GetToken(++index, token))
912                        return false;
913
914                if (!token.IsNumeric())
915                        --index;
916        }
917
918        // Repeat until numOwnerGroup is 0 since not all servers send every possible field
919        int startindex = index;
920        do {
921                // Reset index
922                index = startindex;
923
924                fzstring ownerGroup;
925                for (int i = 0; i < numOwnerGroup; ++i) {
926                        if (!line.GetToken(++index, token))
927                                return false;
928                        if (i)
929                                ownerGroup += _T(" ");
930                        ownerGroup += token.GetString();
931                }
932
933                if (!line.GetToken(++index, token))
934                        return false;
935
936                // Check for concatenated groupname and size fields
937                if (!ParseComplexFileSize(token, entry.size)) {
938                        if (!token.IsRightNumeric())
939                                continue;
940                        entry.size = token.GetNumber();
941                }
942
943                // Append missing group to ownerGroup
944                if (!token.IsNumeric() && token.IsRightNumeric()) {
945                        if (!ownerGroup.empty())
946                                ownerGroup += _T(" ");
947
948                        wxString const group = token.GetString();
949                        int i;
950                        for( i = group.size() - 1;
951                                 i >= 0 && group[i] >= '0' && group[i] <= '9';
952                                 --i ) {}
953
954                        ownerGroup += group.Left(i + 1);
955                }
956
957                if (expect_date) {
958                        entry.time = CDateTime();
959                        if (!ParseUnixDateTime(line, index, entry))
960                                continue;
961                }
962
963                // Get the filename
964                if (!line.GetToken(++index, token, 1))
965                        continue;
966
967                entry.name = token.GetString();
968
969                // Filter out special chars at the end of the filenames
970                chr = token[token.GetLength() - 1];
971                if (chr == '/' ||
972                        chr == '|' ||
973                        chr == '*')
974                        entry.name.pop_back();
975
976                if (entry.is_link()) {
977                        size_t pos;
978                        if ((pos = entry.name.find(_T(" -> "))) != fzstring::npos) {
979                                entry.target = CSparseOptional<fzstring>(entry.name.substr(pos + 4));
980                                entry.name = entry.name.substr(0, pos);
981                        }
982                }
983
984                entry.time += m_timezoneOffset;
985
986                entry.permissions = objcache.get(permissions);
987                entry.ownerGroup = objcache.get(ownerGroup);
988                return true;
989        }
990        while (numOwnerGroup--);
991
992        return false;
993}
994
995bool CDirectoryListingParser::ParseUnixDateTime(CLine & line, int &index, CDirentry &entry)
996{
997        bool mayHaveTime = true;
998        bool bHasYearAndTime = false;
999
1000        CToken token;
1001
1002        // Get the month date field
1003        CToken dateMonth;
1004        if (!line.GetToken(++index, token))
1005                return false;
1006
1007        int year = -1;
1008        int month = -1;
1009        int day = -1;
1010        long hour = -1;
1011        long minute = -1;
1012
1013        // Some servers use the following date formats:
1014        // 26-05 2002, 2002-10-14, 01-jun-99 or 2004.07.15
1015        // slashes instead of dashes are also possible
1016        int pos = token.Find(_T("-/."));
1017        if (pos != -1) {
1018                int pos2 = token.Find(_T("-/."), pos + 1);
1019                if (pos2 == -1) {
1020                        if (token[pos] != '.') {
1021                                // something like 26-05 2002
1022                                day = token.GetNumber(pos + 1, token.GetLength() - pos - 1);
1023                                if (day < 1 || day > 31)
1024                                        return false;
1025                                dateMonth = CToken(token.GetToken(), pos);
1026                        }
1027                        else
1028                                dateMonth = token;
1029                }
1030                else if (token[pos] != token[pos2])
1031                        return false;
1032                else {
1033                        if (!ParseShortDate(token, entry))
1034                                return false;
1035
1036                        if (token[pos] == '.')
1037                                return true;
1038
1039                        tm t = entry.time.GetTm(CDateTime::utc);
1040                        year = t.tm_year + 1900;
1041                        month = t.tm_mon + 1;
1042                        day = t.tm_mday;
1043                }
1044        }
1045        else if (token.IsNumeric()) {
1046                if (token.GetNumber() > 1000 && token.GetNumber() < 10000) {
1047                        // Two possible variants:
1048                        // 1) 2005 3 13
1049                        // 2) 2005 13 3
1050                        // assume first one.
1051                        year = token.GetNumber();
1052                        if (!line.GetToken(++index, dateMonth))
1053                                return false;
1054                        mayHaveTime = false;
1055                }
1056                else
1057                        dateMonth = token;
1058        }
1059        else {
1060                if (token.IsLeftNumeric() && (unsigned int)token[token.GetLength() - 1] > 127 &&
1061                        token.GetNumber() > 1000)
1062                {
1063                        if (token.GetNumber() > 10000)
1064                                return false;
1065
1066                        // Asian date format: 2005xxx 5xx 20xxx with some non-ascii characters following
1067                        year = token.GetNumber();
1068                        if (!line.GetToken(++index, dateMonth))
1069                                return false;
1070                        mayHaveTime = false;
1071                }
1072                else
1073                        dateMonth = token;
1074        }
1075
1076        if (day < 1) {
1077                // Get day field
1078                if (!line.GetToken(++index, token))
1079                        return false;
1080
1081                int dateDay;
1082
1083                // Check for non-numeric day
1084                if (!token.IsNumeric() && !token.IsLeftNumeric()) {
1085                        int offset = 0;
1086                        if (dateMonth.GetString().back() == '.')
1087                                ++offset;
1088                        if (!dateMonth.IsNumeric(0, dateMonth.GetLength() - offset))
1089                                return false;
1090                        dateDay = dateMonth.GetNumber(0, dateMonth.GetLength() - offset);
1091                        dateMonth = token;
1092                }
1093                else if( token.GetLength() == 5 && token[2] == ':' && token.IsRightNumeric() ) {
1094                        // This is a time. We consumed too much already.
1095                        return false;
1096                }
1097                else {
1098                        dateDay = token.GetNumber();
1099                        if (token[token.GetLength() - 1] == ',')
1100                                bHasYearAndTime = true;
1101                }
1102
1103                if (dateDay < 1 || dateDay > 31)
1104                        return false;
1105                day = dateDay;
1106        }
1107
1108        if (month < 1) {
1109                wxString strMonth = dateMonth.GetString();
1110                if (dateMonth.IsLeftNumeric() && (unsigned int)strMonth[strMonth.Length() - 1] > 127) {
1111                        // Most likely an Asian server sending some unknown language specific
1112                        // suffix at the end of the monthname. Filter it out.
1113                        int i;
1114                        for (i = strMonth.Length() - 1; i > 0; --i) {
1115                                if (strMonth[i] >= '0' && strMonth[i] <= '9')
1116                                        break;
1117                        }
1118                        strMonth = strMonth.Left(i + 1);
1119                }
1120                // Check month name
1121                while (strMonth.Right(1) == _T(",") || strMonth.Right(1) == _T("."))
1122                        strMonth.RemoveLast();
1123                if (!GetMonthFromName(strMonth, month))
1124                        return false;
1125        }
1126
1127        // Get time/year field
1128        if (!line.GetToken(++index, token))
1129                return false;
1130
1131        pos = token.Find(_T(":.-"));
1132        if (pos != -1 && mayHaveTime) {
1133                // token is a time
1134                if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
1135                        return false;
1136
1137                wxString str = token.GetString();
1138                if (!str.Left(pos).ToLong(&hour))
1139                        return false;
1140                if (!str.Mid(pos + 1).ToLong(&minute))
1141                        return false;
1142
1143                if (hour < 0 || hour > 23)
1144                        return false;
1145                if (minute < 0 || minute > 59)
1146                        return false;
1147
1148                // Some servers use times only for files newer than 6 months
1149                if( year <= 0 ) {
1150                        wxASSERT( month != -1 && day != -1 );
1151                        tm const t = CDateTime::Now().GetTm(CDateTime::utc);
1152                        year = t.tm_year + 1900;
1153                        int const currentDayOfYear = t.tm_mday + 31 * t.tm_mon;
1154                        int const fileDayOfYear = day + 31 * (month - 1);
1155
1156                        // We have to compare with an offset of one. In the worst case,
1157                        // the server's timezone might be up to 24 hours ahead of the
1158                        // client.
1159                        // Problem: Servers which do send the time but not the year even
1160                        // one day away from getting 1 year old. This is far more uncommon
1161                        // however.
1162                        if ((currentDayOfYear + 1) < fileDayOfYear)
1163                                year -= 1;
1164                }
1165        }
1166        else if (year <= 0) {
1167                // token is a year
1168                if (!token.IsNumeric() && !token.IsLeftNumeric())
1169                        return false;
1170
1171                year = token.GetNumber();
1172
1173                if (year > 3000)
1174                        return false;
1175                if (year < 1000)
1176                        year += 1900;
1177
1178                if (bHasYearAndTime) {
1179                        if (!line.GetToken(++index, token))
1180                                return false;
1181
1182                        if (token.Find(':') == 2 && token.GetLength() == 5 && token.IsLeftNumeric() && token.IsRightNumeric()) {
1183                                pos = token.Find(':');
1184                                // token is a time
1185                                if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
1186                                        return false;
1187
1188                                wxString str = token.GetString();
1189
1190                                if (!str.Left(pos).ToLong(&hour))
1191                                        return false;
1192                                if (!str.Mid(pos + 1).ToLong(&minute))
1193                                        return false;
1194
1195                                if (hour < 0 || hour > 23)
1196                                        return false;
1197                                if (minute < 0 || minute > 59)
1198                                        return false;
1199                        }
1200                        else
1201                                --index;
1202                }
1203        }
1204        else
1205                --index;
1206
1207        if (!entry.time.Set(CDateTime::utc, year, month, day, hour, minute)) {
1208                return false;
1209        }
1210
1211        return true;
1212}
1213
1214bool CDirectoryListingParser::ParseShortDate(CToken &token, CDirentry &entry, bool saneFieldOrder /*=false*/)
1215{
1216        if (token.GetLength() < 1)
1217                return false;
1218
1219        bool gotYear = false;
1220        bool gotMonth = false;
1221        bool gotDay = false;
1222        bool gotMonthName = false;
1223
1224        int year = 0;
1225        int month = 0;
1226        int day = 0;
1227
1228        int pos = token.Find(_T("-./"));
1229        if (pos < 1)
1230                return false;
1231
1232        if (!token.IsNumeric(0, pos)) {
1233                // Seems to be monthname-dd-yy
1234
1235                // Check month name
1236                wxString dateMonth = token.GetString().substr(0, pos);
1237                if (!GetMonthFromName(dateMonth, month))
1238                        return false;
1239                gotMonth = true;
1240                gotMonthName = true;
1241        }
1242        else if (pos == 4) {
1243                // Seems to be yyyy-mm-dd
1244                year = token.GetNumber(0, pos);
1245                if (year < 1900 || year > 3000)
1246                        return false;
1247                gotYear = true;
1248        }
1249        else if (pos <= 2) {
1250                int64_t value = token.GetNumber(0, pos);
1251                if (token[pos] == '.') {
1252                        // Maybe dd.mm.yyyy
1253                        if (value < 1 || value > 31)
1254                                return false;
1255                        day = value;
1256                        gotDay = true;
1257                }
1258                else {
1259                        if (saneFieldOrder) {
1260                                year = value;
1261                                if (year < 50)
1262                                        year += 2000;
1263                                else
1264                                        year += 1900;
1265                                gotYear = true;
1266                        }
1267                        else {
1268                                // Detect mm-dd-yyyy or mm/dd/yyyy and
1269                                // dd-mm-yyyy or dd/mm/yyyy
1270                                if (value < 1)
1271                                        return false;
1272                                if (value > 12) {
1273                                        if (value > 31)
1274                                                return false;
1275
1276                                        day = value;
1277                                        gotDay = true;
1278                                }
1279                                else {
1280                                        month = value;
1281                                        gotMonth = true;
1282                                }
1283                        }
1284                }
1285        }
1286        else
1287                return false;
1288
1289        int pos2 = token.Find(_T("-./"), pos + 1);
1290        if (pos2 == -1 || (pos2 - pos) == 1)
1291                return false;
1292        if (static_cast<size_t>(pos2) == (token.GetLength() - 1))
1293                return false;
1294
1295        // If we already got the month and the second field is not numeric,
1296        // change old month into day and use new token as month
1297        if (!token.IsNumeric(pos + 1, pos2 - pos - 1) && gotMonth) {
1298                if (gotMonthName)
1299                        return false;
1300
1301                if (gotDay)
1302                        return false;
1303
1304                gotDay = true;
1305                gotMonth = false;
1306                day = month;
1307        }
1308
1309        if (gotYear || gotDay) {
1310                // Month field in yyyy-mm-dd or dd-mm-yyyy
1311                // Check month name
1312                wxString dateMonth = token.GetString().substr(pos + 1, pos2 - pos - 1);
1313                if (!GetMonthFromName(dateMonth, month))
1314                        return false;
1315                gotMonth = true;
1316        }
1317        else {
1318                int64_t value = token.GetNumber(pos + 1, pos2 - pos - 1);
1319                // Day field in mm-dd-yyyy
1320                if (value < 1 || value > 31)
1321                        return false;
1322                day = value;
1323                gotDay = true;
1324        }
1325
1326        int64_t value = token.GetNumber(pos2 + 1, token.GetLength() - pos2 - 1);
1327        if (gotYear) {
1328                // Day field in yyy-mm-dd
1329                if (value <= 0 || value > 31)
1330                        return false;
1331                day = value;
1332                gotDay = true;
1333        }
1334        else {
1335                if (value < 0 || value > 9999)
1336                        return false;
1337
1338                if (value < 50)
1339                        value += 2000;
1340                else if (value < 1000)
1341                        value += 1900;
1342                year = value;
1343
1344                gotYear = true;
1345        }
1346
1347        wxASSERT(gotYear);
1348        wxASSERT(gotMonth);
1349        wxASSERT(gotDay);
1350
1351        if (!entry.time.Set(CDateTime::utc, year, month, day)) {
1352                return false;
1353        }
1354
1355        return true;
1356}
1357
1358bool CDirectoryListingParser::ParseAsDos(CLine &line, CDirentry &entry)
1359{
1360        int index = 0;
1361        CToken token;
1362
1363        // Get first token, has to be a valid date
1364        if (!line.GetToken(index, token))
1365                return false;
1366
1367        entry.flags = 0;
1368
1369        if (!ParseShortDate(token, entry))
1370                return false;
1371
1372        // Extract time
1373        if (!line.GetToken(++index, token))
1374                return false;
1375
1376        if (!ParseTime(token, entry))
1377                return false;
1378
1379        // If next token is <DIR>, entry is a directory
1380        // else, it should be the filesize.
1381        if (!line.GetToken(++index, token))
1382                return false;
1383
1384        if (token.GetString() == _T("<DIR>")) {
1385                entry.flags |= CDirentry::flag_dir;
1386                entry.size = -1;
1387        }
1388        else if (token.IsNumeric() || token.IsLeftNumeric()) {
1389                // Convert size, filter out separators
1390                int64_t size = 0;
1391                int len = token.GetLength();
1392                for (int i = 0; i < len; ++i) {
1393                        char chr = token[i];
1394                        if (chr == ',' || chr == '.')
1395                                continue;
1396                        if (chr < '0' || chr > '9')
1397                                return false;
1398
1399                        size *= 10;
1400                        size += chr - '0';
1401                }
1402                entry.size = size;
1403        }
1404        else
1405                return false;
1406
1407        // Extract filename
1408        if (!line.GetToken(++index, token, true))
1409                return false;
1410        entry.name = token.GetString();
1411
1412        entry.target.clear();
1413        entry.ownerGroup = objcache.get(fzstring());
1414        entry.permissions = entry.ownerGroup;
1415        entry.time += m_timezoneOffset;
1416
1417        return true;
1418}
1419
1420bool CDirectoryListingParser::ParseTime(CToken &token, CDirentry &entry)
1421{
1422        if (!entry.has_date())
1423                return false;
1424
1425        int pos = token.Find(':');
1426        if (pos < 1 || static_cast<unsigned int>(pos) >= (token.GetLength() - 1))
1427                return false;
1428
1429        int64_t hour = token.GetNumber(0, pos);
1430        if (hour < 0 || hour > 23)
1431                return false;
1432
1433        // See if we got seconds
1434        int pos2 = token.Find(':', pos + 1);
1435        int len;
1436        if (pos2 == -1)
1437                len = -1;
1438        else
1439                len = pos2 - pos - 1;
1440
1441        if (!len)
1442                return false;
1443
1444        int64_t minute = token.GetNumber(pos + 1, len);
1445        if (minute < 0 || minute > 59)
1446                return false;
1447
1448        int64_t seconds = -1;
1449        if (pos2 != -1) {
1450                // Parse seconds
1451                seconds = token.GetNumber(pos2 + 1, -1);
1452                if (seconds < 0 || seconds > 60)
1453                        return false;
1454        }
1455
1456        // Convert to 24h format
1457        if (!token.IsRightNumeric()) {
1458                if (token[token.GetLength() - 2] == 'P') {
1459                        if (hour < 12)
1460                                hour += 12;
1461                }
1462                else
1463                        if (hour == 12)
1464                                hour = 0;
1465        }
1466
1467        return entry.time.ImbueTime(hour, minute, seconds);
1468}
1469
1470bool CDirectoryListingParser::ParseAsEplf(CLine &line, CDirentry &entry)
1471{
1472        CToken token;
1473        if (!line.GetToken(0, token, true))
1474                return false;
1475
1476        if (token[0] != '+')
1477                return false;
1478
1479        int pos = token.Find('\t');
1480        if (pos == -1 || static_cast<size_t>(pos) == (token.GetLength() - 1))
1481                return false;
1482
1483        entry.name = token.GetString().substr(pos + 1);
1484
1485        entry.flags = 0;
1486        entry.size = -1;
1487
1488        fzstring permissions;
1489
1490        int fact = 1;
1491        while (fact < pos) {
1492                int separator = token.Find(',', fact);
1493                int len;
1494                if (separator == -1)
1495                        len = pos - fact;
1496                else
1497                        len = separator - fact;
1498
1499                if (!len) {
1500                        ++fact;
1501                        continue;
1502                }
1503
1504                char type = token[fact];
1505
1506                if (type == '/')
1507                        entry.flags |= CDirentry::flag_dir;
1508                else if (type == 's')
1509                        entry.size = token.GetNumber(fact + 1, len - 1);
1510                else if (type == 'm') {
1511                        int64_t number = token.GetNumber(fact + 1, len - 1);
1512                        if (number < 0)
1513                                return false;
1514                        entry.time = CDateTime(static_cast<time_t>(number), CDateTime::seconds);
1515                }
1516                else if (type == 'u' && len > 2 && token[fact + 1] == 'p')
1517                        permissions = token.GetString().substr(fact + 2, len - 2);
1518
1519                fact += len + 1;
1520        }
1521
1522        entry.permissions = objcache.get(permissions);
1523        entry.ownerGroup = objcache.get(fzstring());
1524        return true;
1525}
1526
1527namespace {
1528wxString Unescape(const wxString& str, wxChar escape)
1529{
1530        wxString res;
1531        for (unsigned int i = 0; i < str.size(); ++i) {
1532                wxChar c = str[i];
1533                if (c == escape) {
1534                        ++i;
1535                        if (i == str.size() || !str[i]) {
1536                                break;
1537                        }
1538                        c = str[i];
1539                }
1540                res += c;
1541        }
1542
1543        return res;
1544}
1545}
1546
1547bool CDirectoryListingParser::ParseAsVms(CLine &line, CDirentry &entry)
1548{
1549        CToken token;
1550        int index = 0;
1551
1552        if (!line.GetToken(index, token))
1553                return false;
1554
1555        int pos = token.Find(';');
1556        if (pos == -1)
1557                return false;
1558
1559        entry.flags = 0;
1560
1561        if (pos > 4 && token.GetString().substr(pos - 4, 4) == _T(".DIR")) {
1562                entry.flags |= CDirentry::flag_dir;
1563                if (token.GetString().substr(pos) == _T(";1"))
1564                        entry.name = token.GetString().substr(0, pos - 4);
1565                else
1566                        entry.name = token.GetString().substr(0, pos - 4) + token.GetString().substr(pos);
1567        }
1568        else
1569                entry.name = token.GetString();
1570
1571        // Some VMS servers escape special characters like additional dots with ^
1572        entry.name = Unescape(entry.name, '^');
1573
1574        if (!line.GetToken(++index, token))
1575                return false;
1576
1577        fzstring ownerGroup;
1578        fzstring permissions;
1579
1580        // This field can either be the filesize, a username (at least that's what I think) enclosed in [] or a date.
1581        if (!token.IsNumeric() && !token.IsLeftNumeric()) {
1582                // Must be username
1583                const int len = token.GetLength();
1584                if (len < 3 || token[0] != '[' || token[len - 1] != ']')
1585                        return false;
1586                ownerGroup = token.GetString().substr(1, len - 2);
1587
1588                if (!line.GetToken(++index, token))
1589                        return false;
1590                if (!token.IsNumeric() && !token.IsLeftNumeric())
1591                        return false;
1592        }
1593
1594        // Current token is either size or date
1595        bool gotSize = false;
1596        pos = token.Find('/');
1597
1598        if (!pos)
1599                return false;
1600
1601        if (token.IsNumeric() || (pos != -1 && token.Find('/', pos + 1) == -1)) {
1602                // Definitely size
1603                CToken sizeToken;
1604                if (pos == -1)
1605                        sizeToken = token;
1606                else
1607                        sizeToken = CToken(token.GetToken(), pos);
1608                if (!ParseComplexFileSize(sizeToken, entry.size, 512))
1609                        return false;
1610                gotSize = true;
1611
1612                if (!line.GetToken(++index, token))
1613                        return false;
1614        }
1615        else if (pos == -1 && token.IsLeftNumeric()) {
1616                // Perhaps size
1617                if (ParseComplexFileSize(token, entry.size, 512)) {
1618                        gotSize = true;
1619
1620                        if (!line.GetToken(++index, token))
1621                                return false;
1622                }
1623        }
1624
1625        // Get date
1626        if (!ParseShortDate(token, entry))
1627                return false;
1628
1629        // Get time
1630        if (!line.GetToken(++index, token))
1631                return true;
1632
1633        if (!ParseTime(token, entry)) {
1634                int len = token.GetLength();
1635                if (token[0] == '[' && token[len - 1] != ']')
1636                        return false;
1637                if (token[0] == '(' && token[len - 1] != ')')
1638                        return false;
1639                if (token[0] != '[' && token[len - 1] == ']')
1640                        return false;
1641                if (token[0] != '(' && token[len - 1] == ')')
1642                        return false;
1643                --index;
1644        }
1645
1646        if (!gotSize) {
1647                // Get size
1648                if (!line.GetToken(++index, token))
1649                        return false;
1650
1651                if (!token.IsNumeric() && !token.IsLeftNumeric())
1652                        return false;
1653
1654                pos = token.Find('/');
1655                if (!pos)
1656                        return false;
1657
1658                CToken sizeToken;
1659                if (pos == -1)
1660                        sizeToken = token;
1661                else
1662                        sizeToken = CToken(token.GetToken(), pos);
1663                if (!ParseComplexFileSize(sizeToken, entry.size, 512))
1664                        return false;
1665        }
1666
1667        // Owner / group and permissions
1668        while (line.GetToken(++index, token)) {
1669                const int len = token.GetLength();
1670                if (len > 2 && token[0] == '(' && token[len - 1] == ')') {
1671                        if (!permissions.empty())
1672                                permissions += _T(" ");
1673                        permissions += token.GetString().substr(1, len - 2);
1674                }
1675                else if (len > 2 && token[0] == '[' && token[len - 1] == ']') {
1676                        if (!ownerGroup.empty())
1677                                ownerGroup += _T(" ");
1678                        ownerGroup += token.GetString().substr(1, len - 2);
1679                }
1680                else {
1681                        if (!ownerGroup.empty())
1682                                ownerGroup += _T(" ");
1683                        ownerGroup += token.GetString();
1684                }
1685        }
1686        entry.permissions = objcache.get(permissions);
1687        entry.ownerGroup = objcache.get(ownerGroup);
1688
1689        entry.time += m_timezoneOffset;
1690
1691        return true;
1692}
1693
1694bool CDirectoryListingParser::ParseAsIbm(CLine &line, CDirentry &entry)
1695{
1696        int index = 0;
1697
1698        // Get owner
1699        CToken ownerGroupToken;
1700        if (!line.GetToken(index, ownerGroupToken))
1701                return false;
1702
1703        // Get size
1704        CToken token;
1705        if (!line.GetToken(++index, token))
1706                return false;
1707
1708        if (!token.IsNumeric())
1709                return false;
1710
1711        entry.size = token.GetNumber();
1712
1713        // Get date
1714        if (!line.GetToken(++index, token))
1715                return false;
1716
1717        entry.flags = 0;
1718
1719        if (!ParseShortDate(token, entry))
1720                return false;
1721
1722        // Get time
1723        if (!line.GetToken(++index, token))
1724                return false;
1725
1726        if (!ParseTime(token, entry))
1727                return false;
1728
1729        // Get filename
1730        if (!line.GetToken(index + 2, token, 1))
1731                return false;
1732
1733        entry.name = token.GetString();
1734        if (token[token.GetLength() - 1] == '/') {
1735                entry.name.pop_back();
1736                entry.flags |= CDirentry::flag_dir;
1737        }
1738
1739        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
1740        entry.permissions = objcache.get(fzstring());
1741
1742        entry.time += m_timezoneOffset;
1743
1744        return true;
1745}
1746
1747bool CDirectoryListingParser::ParseOther(CLine &line, CDirentry &entry)
1748{
1749        int index = 0;
1750        CToken firstToken;
1751
1752        if (!line.GetToken(index, firstToken))
1753                return false;
1754
1755        if (!firstToken.IsNumeric())
1756                return false;
1757
1758        // Possible formats: Numerical unix, VShell or OS/2
1759
1760        CToken token;
1761        if (!line.GetToken(++index, token))
1762                return false;
1763
1764        entry.flags = 0;
1765
1766        // If token is a number, than it's the numerical Unix style format,
1767        // else it's the VShell, OS/2 or nortel.VxWorks format
1768        if (token.IsNumeric()) {
1769                if (firstToken.GetLength() >= 2 && firstToken[1] == '4')
1770                        entry.flags |= CDirentry::flag_dir;
1771
1772                fzstring ownerGroup = token.GetString();
1773
1774                if (!line.GetToken(++index, token))
1775                        return false;
1776
1777                ownerGroup += _T(" ") + token.GetString();
1778
1779                // Get size
1780                if (!line.GetToken(++index, token))
1781                        return false;
1782
1783                if (!token.IsNumeric())
1784                        return false;
1785
1786                entry.size = token.GetNumber();
1787
1788                // Get date/time
1789                if (!line.GetToken(++index, token))
1790                        return false;
1791
1792                int64_t number = token.GetNumber();
1793                if (number < 0)
1794                        return false;
1795                entry.time = CDateTime(static_cast<time_t>(number), CDateTime::seconds);
1796
1797                // Get filename
1798                if (!line.GetToken(++index, token, true))
1799                        return false;
1800
1801                entry.name = token.GetString();
1802                entry.target.clear();
1803
1804                entry.permissions = objcache.get(firstToken.GetString());
1805                entry.ownerGroup = objcache.get(ownerGroup);
1806        }
1807        else {
1808                // Possible conflict with multiline VMS listings
1809                if (m_maybeMultilineVms)
1810                        return false;
1811
1812                // VShell, OS/2 or nortel.VxWorks style format
1813                entry.size = firstToken.GetNumber();
1814
1815                // Get date
1816                wxString dateMonth = token.GetString();
1817                int month = 0;
1818                if (!GetMonthFromName(dateMonth, month)) {
1819                        // OS/2 or nortel.VxWorks
1820                        int skippedCount = 0;
1821                        do {
1822                                if (token.GetString() == _T("DIR"))
1823                                        entry.flags |= CDirentry::flag_dir;
1824                                else if (token.Find(_T("-/.")) != -1)
1825                                        break;
1826
1827                                ++skippedCount;
1828
1829                                if (!line.GetToken(++index, token))
1830                                        return false;
1831                        } while (true);
1832
1833                        if (!ParseShortDate(token, entry))
1834                                return false;
1835
1836                        // Get time
1837                        if (!line.GetToken(++index, token))
1838                                return false;
1839
1840                        if (!ParseTime(token, entry))
1841                                return false;
1842
1843                        // Get filename
1844                        if (!line.GetToken(++index, token, true))
1845                                return false;
1846
1847                        entry.name = token.GetString();
1848                        if (entry.name.size() >= 5) {
1849                                wxString type = entry.name.substr(entry.name.size() - 5);
1850                                MakeLowerAscii(type);
1851                                if (!skippedCount && type == _T("<dir>")) {
1852                                        entry.flags |= CDirentry::flag_dir;
1853                                        entry.name = entry.name.substr(0, entry.name.size() - 5);
1854                                        while (!entry.name.empty() && entry.name.back() == ' ')
1855                                                entry.name.pop_back();
1856                                }
1857                        }
1858                }
1859                else {
1860                        // Get day
1861                        if (!line.GetToken(++index, token))
1862                                return false;
1863
1864                        if (!token.IsNumeric() && !token.IsLeftNumeric())
1865                                return false;
1866
1867                        int64_t day = token.GetNumber();
1868                        if (day < 0 || day > 31)
1869                                return false;
1870
1871                        // Get Year
1872                        if (!line.GetToken(++index, token))
1873                                return false;
1874
1875                        if (!token.IsNumeric())
1876                                return false;
1877
1878                        int64_t year = token.GetNumber();
1879                        if (year < 50)
1880                                year += 2000;
1881                        else if (year < 1000)
1882                                year += 1900;
1883
1884                        if( !entry.time.Set(CDateTime::utc, year, month, day) ) {
1885                                return false;
1886                        }
1887
1888                        // Get time
1889                        if (!line.GetToken(++index, token))
1890                                return false;
1891
1892                        if (!ParseTime(token, entry))
1893                                return false;
1894
1895                        // Get filename
1896                        if (!line.GetToken(++index, token, 1))
1897                                return false;
1898
1899                        entry.name = token.GetString();
1900                        char chr = token[token.GetLength() - 1];
1901                        if (chr == '/' || chr == '\\') {
1902                                entry.flags |= CDirentry::flag_dir;
1903                                entry.name.pop_back();
1904                        }
1905                }
1906                entry.target.clear();
1907                entry.ownerGroup = objcache.get(fzstring());
1908                entry.permissions = entry.ownerGroup;
1909                entry.time += m_timezoneOffset;
1910        }
1911
1912        return true;
1913}
1914
1915bool CDirectoryListingParser::AddData(char *pData, int len)
1916{
1917        ConvertEncoding( pData, len );
1918
1919        m_DataList.emplace_back(pData, len);
1920        m_totalData += len;
1921
1922        if (m_totalData < 512)
1923                return true;
1924
1925        return ParseData(true);
1926}
1927
1928bool CDirectoryListingParser::AddLine(wxChar const* pLine)
1929{
1930        if (m_pControlSocket)
1931                m_pControlSocket->LogMessageRaw(MessageType::RawList, pLine);
1932
1933        while (*pLine == ' ' || *pLine == '\t')
1934                ++pLine;
1935
1936        if (!*pLine)
1937                return false;
1938
1939        const int len = wxStrlen(pLine);
1940
1941        wxChar* p = new wxChar[len + 1];
1942
1943        wxStrcpy(p, pLine);
1944
1945        CLine line(p, len);
1946
1947        ParseLine(line, m_server.GetType(), false);
1948
1949        return true;
1950}
1951
1952CLine *CDirectoryListingParser::GetLine(bool breakAtEnd /*=false*/, bool &error)
1953{
1954        while (!m_DataList.empty()) {
1955                // Trim empty lines and spaces
1956                auto iter = m_DataList.begin();
1957                int len = iter->len;
1958                while (iter->p[m_currentOffset] == '\r' || iter->p[m_currentOffset] == '\n' || iter->p[m_currentOffset] == ' ' || iter->p[m_currentOffset] == '\t' || !iter->p[m_currentOffset]) {
1959                        ++m_currentOffset;
1960                        if (m_currentOffset >= len) {
1961                                delete [] iter->p;
1962                                ++iter;
1963                                m_currentOffset = 0;
1964                                if (iter == m_DataList.end()) {
1965                                        m_DataList.clear();
1966                                        return 0;
1967                                }
1968                                len = iter->len;
1969                        }
1970                }
1971                m_DataList.erase(m_DataList.begin(), iter);
1972                iter = m_DataList.begin();
1973
1974                // Remember start offset and find next linebreak
1975                int startpos = m_currentOffset;
1976                int reslen = 0;
1977
1978                int emptylen = 0;
1979
1980                int currentOffset = m_currentOffset;
1981                while (iter->p[currentOffset] != '\n' && iter->p[currentOffset] != '\r' && iter->p[currentOffset]) {
1982                        if (iter->p[currentOffset] == ' ' || iter->p[currentOffset] == '\t')
1983                                ++emptylen;
1984                        else
1985                                emptylen = 0;
1986                        ++reslen;
1987
1988                        ++currentOffset;
1989                        if (currentOffset >= len) {
1990                                ++iter;
1991                                if (iter == m_DataList.end()) {
1992                                        if (reslen > 10000) {
1993                                                m_pControlSocket->LogMessage(MessageType::Error, _("Received a line exceeding 10000 characters, aborting."));
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                        m_pControlSocket->LogMessage(MessageType::Error, _("Received a line exceeding 10000 characters, aborting."));
2008                        error = true;
2009                        return 0;
2010                }
2011                m_currentOffset = currentOffset;
2012
2013                // Reslen is now the length of the line, including any terminating whitespace
2014                int const buflen = reslen + 1;
2015                char *res = new char[buflen];
2016                res[reslen] = 0;
2017
2018                int respos = 0;
2019
2020                // Copy line data
2021                auto i = m_DataList.begin();
2022                while (i != iter && reslen) {
2023                        int copylen = i->len - startpos;
2024                        if (copylen > reslen)
2025                                copylen = reslen;
2026                        memcpy(&res[respos], &i->p[startpos], copylen);
2027                        reslen -= copylen;
2028                        respos += i->len - startpos;
2029                        startpos = 0;
2030
2031                        delete [] i->p;
2032                        ++i;
2033                }
2034                ;
2035                // Copy last chunk
2036                if (iter != m_DataList.end() && reslen) {
2037                        int copylen = m_currentOffset-startpos;
2038                        if (copylen > reslen)
2039                                copylen = reslen;
2040                        memcpy(&res[respos], &iter->p[startpos], copylen);
2041                        if (reslen >= iter->len) {
2042                                delete [] iter->p;
2043                                m_DataList.erase(m_DataList.begin(), ++iter);
2044                        }
2045                        else
2046                                m_DataList.erase(m_DataList.begin(), iter);
2047                }
2048                else
2049                        m_DataList.erase(m_DataList.begin(), iter);
2050
2051                size_t lineLength{};
2052                wxChar* buffer;
2053                if (m_pControlSocket) {
2054                        buffer = m_pControlSocket->ConvToLocalBuffer(res, buflen, lineLength);
2055                        m_pControlSocket->LogMessageRaw(MessageType::RawList, buffer);
2056                }
2057                else {
2058                        wxString str(res, wxConvUTF8);
2059                        if (str.empty())
2060                        {
2061                                str = wxString(res, wxConvLocal);
2062                                if (str.empty())
2063                                        str = wxString(res, wxConvISO8859_1);
2064                        }
2065                        lineLength = str.Len() + 1;
2066                        buffer = new wxChar[str.Len() + 1];
2067                        wxStrcpy(buffer, str.c_str());
2068                }
2069                delete [] res;
2070
2071                if (!buffer) {
2072                        // Line contained no usable data, start over
2073                        continue;
2074                }
2075
2076                return new CLine(buffer, lineLength - 1, emptylen);
2077        }
2078
2079        return 0;
2080}
2081
2082bool CDirectoryListingParser::ParseAsWfFtp(CLine &line, CDirentry &entry)
2083{
2084        int index = 0;
2085        CToken token;
2086
2087        // Get filename
2088        if (!line.GetToken(index++, token))
2089                return false;
2090
2091        entry.name = token.GetString();
2092
2093        // Get filesize
2094        if (!line.GetToken(index++, token))
2095                return false;
2096
2097        if (!token.IsNumeric())
2098                return false;
2099
2100        entry.size = token.GetNumber();
2101
2102        entry.flags = 0;
2103
2104        // Parse date
2105        if (!line.GetToken(index++, token))
2106                return false;
2107
2108        if (!ParseShortDate(token, entry))
2109                return false;
2110
2111        // Unused token
2112        if (!line.GetToken(index++, token))
2113                return false;
2114
2115        if (token.GetString().back() != '.')
2116                return false;
2117
2118        // Parse time
2119        if (!line.GetToken(index++, token, true))
2120                return false;
2121
2122        if (!ParseTime(token, entry))
2123                return false;
2124
2125        entry.ownerGroup = objcache.get(fzstring());
2126        entry.permissions = entry.ownerGroup;
2127        entry.time += m_timezoneOffset;
2128
2129        return true;
2130}
2131
2132bool CDirectoryListingParser::ParseAsIBM_MVS(CLine &line, CDirentry &entry)
2133{
2134        int index = 0;
2135        CToken token;
2136
2137        // volume
2138        if (!line.GetToken(index++, token))
2139                return false;
2140
2141        // unit
2142        if (!line.GetToken(index++, token))
2143                return false;
2144
2145        // Referred date
2146        if (!line.GetToken(index++, token))
2147                return false;
2148
2149        entry.flags = 0;
2150        if (token.GetString() != _T("**NONE**") && !ParseShortDate(token, entry)) {
2151                // Perhaps of the following type:
2152                // TSO004 3390 VSAM FOO.BAR
2153                if (token.GetString() != _T("VSAM"))
2154                        return false;
2155
2156                if (!line.GetToken(index++, token))
2157                        return false;
2158
2159                entry.name = token.GetString();
2160                if (entry.name.find(' ') != fzstring::npos)
2161                        return false;
2162
2163                entry.size = -1;
2164                entry.ownerGroup = objcache.get(fzstring());
2165                entry.permissions = entry.ownerGroup;
2166
2167                return true;
2168        }
2169
2170        // ext
2171        if (!line.GetToken(index++, token))
2172                return false;
2173        if (!token.IsNumeric())
2174                return false;
2175
2176        int prevLen = token.GetLength();
2177
2178        // used
2179        if (!line.GetToken(index++, token))
2180                return false;
2181        if (token.IsNumeric() || token.GetString() == _T("????") || token.GetString() == _T("++++") ) {
2182                // recfm
2183                if (!line.GetToken(index++, token))
2184                        return false;
2185                if (token.IsNumeric())
2186                        return false;
2187        }
2188        else {
2189                if (prevLen < 6)
2190                        return false;
2191        }
2192
2193        // lrecl
2194        if (!line.GetToken(index++, token))
2195                return false;
2196        if (!token.IsNumeric())
2197                return false;
2198
2199        // blksize
2200        if (!line.GetToken(index++, token))
2201                return false;
2202        if (!token.IsNumeric())
2203                return false;
2204
2205        // dsorg
2206        if (!line.GetToken(index++, token))
2207                return false;
2208
2209        if (token.GetString() == _T("PO") || token.GetString() == _T("PO-E"))
2210        {
2211                entry.flags |= CDirentry::flag_dir;
2212                entry.size = -1;
2213        }
2214        else
2215                entry.size = 100;
2216
2217        // name of dataset or sequential file
2218        if (!line.GetToken(index++, token, true))
2219                return false;
2220
2221        entry.name = token.GetString();
2222
2223        entry.ownerGroup = objcache.get(fzstring());
2224        entry.permissions = entry.ownerGroup;
2225
2226        return true;
2227}
2228
2229bool CDirectoryListingParser::ParseAsIBM_MVS_PDS(CLine &line, CDirentry &entry)
2230{
2231        int index = 0;
2232        CToken token;
2233
2234        // pds member name
2235        if (!line.GetToken(index++, token))
2236                return false;
2237        entry.name = token.GetString();
2238
2239        // vv.mm
2240        if (!line.GetToken(index++, token))
2241                return false;
2242
2243        entry.flags = 0;
2244
2245        // creation date
2246        if (!line.GetToken(index++, token))
2247                return false;
2248        if (!ParseShortDate(token, entry))
2249                return false;
2250
2251        // modification date
2252        if (!line.GetToken(index++, token))
2253                return false;
2254        if (!ParseShortDate(token, entry))
2255                return false;
2256
2257        // modification time
2258        if (!line.GetToken(index++, token))
2259                return false;
2260        if (!ParseTime(token, entry))
2261                return false;
2262
2263        // size
2264        if (!line.GetToken(index++, token))
2265                return false;
2266        if (!token.IsNumeric())
2267                return false;
2268        entry.size = token.GetNumber();
2269
2270        // init
2271        if (!line.GetToken(index++, token))
2272                return false;
2273        if (!token.IsNumeric())
2274                return false;
2275
2276        // mod
2277        if (!line.GetToken(index++, token))
2278                return false;
2279        if (!token.IsNumeric())
2280                return false;
2281
2282        // id
2283        if (!line.GetToken(index++, token, true))
2284                return false;
2285
2286        entry.ownerGroup = objcache.get(fzstring());
2287        entry.permissions = entry.ownerGroup;
2288        entry.time += m_timezoneOffset;
2289
2290        return true;
2291}
2292
2293bool CDirectoryListingParser::ParseAsIBM_MVS_Migrated(CLine &line, CDirentry &entry)
2294{
2295        // Migrated MVS file
2296        // "Migrated                            SOME.NAME"
2297
2298        int index = 0;
2299        CToken token;
2300        if (!line.GetToken(index, token))
2301                return false;
2302
2303        wxString s = token.GetString();
2304        MakeLowerAscii(s);
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(fzstring());
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(fzstring());
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        wxString s = token.GetString();
2404        MakeLowerAscii(s);
2405        if (s != _T("tape"))
2406                return false;
2407
2408        // dsname
2409        if (!line.GetToken(index++, token))
2410                return false;
2411
2412        entry.name = token.GetString();
2413        entry.flags = 0;
2414        entry.ownerGroup = objcache.get(fzstring());
2415        entry.permissions = objcache.get(fzstring());
2416        entry.size = -1;
2417
2418        if (line.GetToken(index++, token))
2419                return false;
2420
2421        return true;
2422}
2423
2424bool CDirectoryListingParser::ParseComplexFileSize(CToken& token, int64_t& size, int blocksize /*=-1*/)
2425{
2426        if (token.IsNumeric()) {
2427                size = token.GetNumber();
2428                if (blocksize != -1)
2429                        size *= blocksize;
2430
2431                return true;
2432        }
2433
2434        int len = token.GetLength();
2435
2436        char last = token[len - 1];
2437        if (last == 'B' || last == 'b')
2438        {
2439                if (len == 1)
2440                        return false;
2441
2442                char c = token[--len - 1];
2443                if (c < '0' || c > '9')
2444                {
2445                        --len;
2446                        last = c;
2447                }
2448                else
2449                        last = 0;
2450        }
2451        else if (last >= '0' && last <= '9')
2452                last = 0;
2453        else
2454        {
2455                if (--len == 0)
2456                        return false;
2457        }
2458
2459        size = 0;
2460
2461        int dot = -1;
2462        for (int i = 0; i < len; ++i)
2463        {
2464                char c = token[i];
2465                if (c >= '0' && c <= '9')
2466                {
2467                        size *= 10;
2468                        size += c - '0';
2469                }
2470                else if (c == '.')
2471                {
2472                        if (dot != -1)
2473                                return false;
2474                        dot = len - i - 1;
2475                }
2476                else
2477                        return false;
2478        }
2479        switch (last)
2480        {
2481        case 'k':
2482        case 'K':
2483                size *= 1024;
2484                break;
2485        case 'm':
2486        case 'M':
2487                size *= 1024 * 1024;
2488                break;
2489        case 'g':
2490        case 'G':
2491                size *= 1024 * 1024 * 1024;
2492                break;
2493        case 't':
2494        case 'T':
2495                size *= 1024 * 1024;
2496                size *= 1024 * 1024;
2497                break;
2498        case 'b':
2499        case 'B':
2500                break;
2501        case 0:
2502                if (blocksize != -1)
2503                        size *= blocksize;
2504                break;
2505        default:
2506                return false;
2507        }
2508        while (dot-- > 0)
2509                size /= 10;
2510
2511        return true;
2512}
2513
2514int CDirectoryListingParser::ParseAsMlsd(CLine &line, CDirentry &entry)
2515{
2516        // MLSD format as described here: http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt
2517
2518        // Parsing is done strict, abort on slightest error.
2519
2520        CToken token;
2521
2522        if (!line.GetToken(0, token))
2523                return 0;
2524
2525        fzstring const facts = token.GetString();
2526        if (facts.empty())
2527                return 0;
2528
2529        entry.flags = 0;
2530        entry.size = -1;
2531
2532        fzstring owner, group, uid, gid;
2533        fzstring ownerGroup;
2534        fzstring permissions;
2535
2536        size_t start = 0;
2537        while (start < facts.size()) {
2538                auto delim = facts.find(';', start);
2539                if (delim == fzstring::npos) {
2540                        delim = facts.size();
2541                }
2542                else if (delim < start + 3) {
2543                        return 0;
2544                }
2545
2546                auto const pos = facts.find('=', start);
2547                if (pos == fzstring::npos || pos < start + 1 || pos > delim)
2548                        return 0;
2549
2550                fzstring factname = facts.substr(start, pos - start);
2551                MakeLowerAscii(factname);
2552                fzstring value = facts.substr(pos + 1, delim - pos - 1);
2553                if (factname == _T("type")) {
2554                        auto colonPos = value.find(':');
2555                        fzstring valuePrefix;
2556                        if (colonPos == fzstring::npos)
2557                                valuePrefix = value;
2558                        else
2559                                valuePrefix = value.substr(0, colonPos);
2560                        MakeLowerAscii(valuePrefix);
2561
2562                        if (valuePrefix == _T("dir") && colonPos == fzstring::npos)
2563                                entry.flags |= CDirentry::flag_dir;
2564                        else if (valuePrefix == _T("os.unix=slink") || valuePrefix == _T("os.unix=symlink")) {
2565                                entry.flags |= CDirentry::flag_dir | CDirentry::flag_link;
2566                                if (colonPos != fzstring::npos)
2567                                        entry.target = CSparseOptional<fzstring>(value.substr(colonPos));
2568                        }
2569                        else if ((valuePrefix == _T("cdir") || valuePrefix == _T("pdir")) && colonPos == fzstring::npos) {
2570                                // Current and parent directory, don't parse it
2571                                return 2;
2572                        }
2573                }
2574                else if (factname == _T("size")) {
2575                        entry.size = 0;
2576
2577                        for (unsigned int i = 0; i < value.size(); ++i) {
2578                                if (value[i] < '0' || value[i] > '9')
2579                                        return 0;
2580                                entry.size *= 10;
2581                                entry.size += value[i] - '0';
2582                        }
2583                }
2584                else if (factname == _T("modify") ||
2585                        (!entry.has_date() && factname == _T("create")))
2586                {
2587                        entry.time = CDateTime(value, CDateTime::utc);
2588                        if (!entry.time.IsValid()) {
2589                                return 0;
2590                        }
2591                }
2592                else if (factname == _T("perm")) {
2593                        if (!value.empty()) {
2594                                if (!permissions.empty())
2595                                        permissions = value + _T(" (") + permissions + _T(")");
2596                                else
2597                                        permissions = value;
2598                        }
2599                }
2600                else if (factname == _T("unix.mode")) {
2601                        if (!permissions.empty())
2602                                permissions = permissions + _T(" (") + value + _T(")");
2603                        else
2604                                permissions = value;
2605                }
2606                else if (factname == _T("unix.owner") || factname == _T("unix.user"))
2607                        owner = value;
2608                else if (factname == _T("unix.group"))
2609                        group = value;
2610                else if (factname == _T("unix.uid"))
2611                        uid = value;
2612                else if (factname == _T("unix.gid"))
2613                        gid = value;
2614
2615                start = delim + 1;
2616        }
2617
2618        // The order of the facts is undefined, so assemble ownerGroup in correct
2619        // order
2620        if (!owner.empty())
2621                ownerGroup += owner;
2622        else if (!uid.empty())
2623                ownerGroup += uid;
2624        if (!group.empty())
2625                ownerGroup += _T(" ") + group;
2626        else if (!gid.empty())
2627                ownerGroup += _T(" ") + gid;
2628
2629        if (!line.GetToken(1, token, true, true))
2630                return 0;
2631
2632        entry.name = token.GetString();
2633        entry.ownerGroup = objcache.get(ownerGroup);
2634        entry.permissions = objcache.get(permissions);
2635
2636        return 1;
2637}
2638
2639bool CDirectoryListingParser::ParseAsOS9(CLine &line, CDirentry &entry)
2640{
2641        int index = 0;
2642
2643        // Get owner
2644        CToken ownerGroupToken;
2645        if (!line.GetToken(index++, ownerGroupToken))
2646                return false;
2647
2648        // Make sure it's number.number
2649        int pos = ownerGroupToken.Find('.');
2650        if (pos == -1 || !pos || pos == ((int)ownerGroupToken.GetLength() - 1))
2651                return false;
2652
2653        if (!ownerGroupToken.IsNumeric(0, pos))
2654                return false;
2655
2656        if (!ownerGroupToken.IsNumeric(pos + 1, ownerGroupToken.GetLength() - pos - 1))
2657                return false;
2658
2659        entry.flags = 0;
2660
2661        // Get date
2662        CToken token;
2663        if (!line.GetToken(index++, token))
2664                return false;
2665
2666        if (!ParseShortDate(token, entry, true))
2667                return false;
2668
2669        // Unused token
2670        if (!line.GetToken(index++, token))
2671                return false;
2672
2673        // Get perms
2674        CToken permToken;
2675        if (!line.GetToken(index++, permToken))
2676                return false;
2677
2678        if (permToken[0] == 'd')
2679                entry.flags |= CDirentry::flag_dir;
2680
2681        // Unused token
2682        if (!line.GetToken(index++, token))
2683                return false;
2684
2685        // Get Size
2686        if (!line.GetToken(index++, token))
2687                return false;
2688
2689        if (!token.IsNumeric())
2690                return false;
2691
2692        entry.size = token.GetNumber();
2693
2694        // Filename
2695        if (!line.GetToken(index++, token, true))
2696                return false;
2697
2698        entry.name = token.GetString();
2699        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
2700        entry.permissions = objcache.get(permToken.GetString());
2701
2702        return true;
2703}
2704
2705void CDirectoryListingParser::Reset()
2706{
2707        for (auto iter = m_DataList.begin(); iter != m_DataList.end(); ++iter)
2708                delete [] iter->p;
2709        m_DataList.clear();
2710
2711        delete m_prevLine;
2712        m_prevLine = 0;
2713
2714        m_entryList.clear();
2715        m_fileList.clear();
2716        m_currentOffset = 0;
2717        m_fileListOnly = true;
2718        m_maybeMultilineVms = false;
2719}
2720
2721bool CDirectoryListingParser::ParseAsZVM(CLine &line, CDirentry &entry)
2722{
2723        int index = 0;
2724        CToken token;
2725
2726        // Get name
2727        if (!line.GetToken(index, token))
2728                return false;
2729
2730        entry.name = token.GetString();
2731
2732        // Get filename extension
2733        if (!line.GetToken(++index, token))
2734                return false;
2735        entry.name += _T(".") + token.GetString();
2736
2737        // File format. Unused
2738        if (!line.GetToken(++index, token))
2739                return false;
2740        wxString format = token.GetString();
2741        if (format != _T("V") && format != _T("F"))
2742                return false;
2743
2744        // Record length
2745        if (!line.GetToken(++index, token))
2746                return false;
2747
2748        if (!token.IsNumeric())
2749                return false;
2750
2751        entry.size = token.GetNumber();
2752
2753        // Number of records
2754        if (!line.GetToken(++index, token))
2755                return false;
2756
2757        if (!token.IsNumeric())
2758                return false;
2759
2760        entry.size *= token.GetNumber();
2761
2762        // Unused (Block size?)
2763        if (!line.GetToken(++index, token))
2764                return false;
2765
2766        if (!token.IsNumeric())
2767                return false;
2768
2769        entry.flags = 0;
2770
2771        // Date
2772        if (!line.GetToken(++index, token))
2773                return false;
2774
2775        if (!ParseShortDate(token, entry, true))
2776                return false;
2777
2778        // Time
2779        if (!line.GetToken(++index, token))
2780                return false;
2781
2782        if (!ParseTime(token, entry))
2783                return false;
2784
2785        // Owner
2786        CToken ownerGroupToken;
2787        if (!line.GetToken(++index, ownerGroupToken))
2788                return false;
2789
2790        // No further token!
2791        if (line.GetToken(++index, token))
2792                return false;
2793
2794        entry.ownerGroup = objcache.get(ownerGroupToken.GetString());
2795        entry.permissions = objcache.get(fzstring());
2796        entry.target.clear();
2797        entry.time += m_timezoneOffset;
2798
2799        return true;
2800}
2801
2802bool CDirectoryListingParser::ParseAsHPNonstop(CLine &line, CDirentry &entry)
2803{
2804        int index = 0;
2805        CToken token;
2806
2807        // Get name
2808        if (!line.GetToken(index, token))
2809                return false;
2810
2811        entry.name = token.GetString();
2812
2813        // File code, numeric, unsuded
2814        if (!line.GetToken(++index, token))
2815                return false;
2816        if (!token.IsNumeric())
2817                return false;
2818
2819        // Size
2820        if (!line.GetToken(++index, token))
2821                return false;
2822        if (!token.IsNumeric())
2823                return false;
2824
2825        entry.size = token.GetNumber();
2826
2827        entry.flags = 0;
2828
2829        // Date
2830        if (!line.GetToken(++index, token))
2831                return false;
2832        if (!ParseShortDate(token, entry, false))
2833                return false;
2834
2835        // Time
2836        if (!line.GetToken(++index, token))
2837                return false;
2838        if (!ParseTime(token, entry))
2839                return false;
2840
2841        // Owner
2842        if (!line.GetToken(++index, token))
2843                return false;
2844        fzstring ownerGroup = token.GetString();
2845
2846        if (token[token.GetLength() - 1] == ',') {
2847                // Owner, part 2
2848                if (!line.GetToken(++index, token))
2849                        return false;
2850                ownerGroup += _T(" ") + token.GetString();
2851        }
2852
2853        // Permissions
2854        CToken permToken;
2855        if (!line.GetToken(++index, permToken))
2856                return false;
2857
2858        // Nothing
2859        if (line.GetToken(++index, token))
2860                return false;
2861
2862        entry.permissions = objcache.get(permToken.GetString());
2863        entry.ownerGroup = objcache.get(ownerGroup);
2864
2865        return true;
2866}
2867
2868bool CDirectoryListingParser::GetMonthFromName(const wxString& name, int &month)
2869{
2870        auto iter = m_MonthNamesMap.find(name.Lower());
2871        if (iter == m_MonthNamesMap.end())
2872        {
2873                wxString lower(name);
2874                MakeLowerAscii(lower);
2875                iter = m_MonthNamesMap.find(lower);
2876                if (iter == m_MonthNamesMap.end())
2877                        return false;
2878        }
2879
2880        month = iter->second;
2881
2882        return true;
2883}
2884
2885char ebcdic_table[256] = {
2886        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 0
2887        ' ',  ' ',  ' ',  ' ',  ' ',  '\n', ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '\n', // 1
2888        ' ',  ' ',  ' ',  ' ',  ' ',  '\n', ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 2
2889        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 3
2890        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '.',  '<',  '(',  '+',  '|',  // 4
2891        '&',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '!',  '$',  '*',  ')',  ';',  ' ',  // 5
2892        '-',  '/',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '|',  ',',  '%',  '_',  '>',  '?',  // 6
2893        ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '`',  ':',  '#',  '@',  '\'', '=',  '"',  // 7
2894        ' ',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 8
2895        ' ',  'j',  'k',  'l',  'm',  'n',  'o',  'p',  'q',  'r',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // 9
2896        ' ',  '~',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // a
2897        '^',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  '[',  ']',  ' ',  ' ',  ' ',  ' ',  // b
2898        '{',  'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',  'I',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // c
2899        '}',  'J',  'K',  'L',  'M',  'N',  'O',  'P',  'Q',  'R',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // d
2900        '\\', ' ',  'S',  'T',  'U',  'V',  'W',  'X',  'Y',  'Z',  ' ',  ' ',  ' ',  ' ',  ' ',  ' ',  // e
2901        '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ' ',  ' ',  ' ',  ' ',  ' ',  ' '   // f
2902};
2903
2904void CDirectoryListingParser::ConvertEncoding(char *pData, int len)
2905{
2906        if (m_listingEncoding != listingEncoding::ebcdic)
2907                return;
2908
2909        for (int i = 0; i < len; ++i) {
2910                pData[i] = ebcdic_table[static_cast<unsigned char>(pData[i])];
2911        }
2912}
2913
2914void CDirectoryListingParser::DeduceEncoding()
2915{
2916        if (m_listingEncoding != listingEncoding::unknown)
2917                return;
2918
2919        int count[256];
2920
2921        memset(&count, 0, sizeof(int)*256);
2922
2923        for (auto const& data : m_DataList) {
2924                for (int i = 0; i < data.len; ++i)
2925                        ++count[static_cast<unsigned char>(data.p[i])];
2926        }
2927
2928        int count_normal = 0;
2929        int count_ebcdic = 0;
2930        for (int i = '0'; i <= '9'; ++i ) {
2931                count_normal += count[i];
2932        }
2933        for (int i = 'a'; i <= 'z'; ++i ) {
2934                count_normal += count[i];
2935        }
2936        for (int i = 'A'; i <= 'Z'; ++i ) {
2937                count_normal += count[i];
2938        }
2939
2940        for (int i = 0x81; i <= 0x89; ++i ) {
2941                count_ebcdic += count[i];
2942        }
2943        for (int i = 0x91; i <= 0x99; ++i ) {
2944                count_ebcdic += count[i];
2945        }
2946        for (int i = 0xa2; i <= 0xa9; ++i ) {
2947                count_ebcdic += count[i];
2948        }
2949        for (int i = 0xc1; i <= 0xc9; ++i ) {
2950                count_ebcdic += count[i];
2951        }
2952        for (int i = 0xd1; i <= 0xd9; ++i ) {
2953                count_ebcdic += count[i];
2954        }
2955        for (int i = 0xe2; i <= 0xe9; ++i ) {
2956                count_ebcdic += count[i];
2957        }
2958        for (int i = 0xf0; i <= 0xf9; ++i ) {
2959                count_ebcdic += count[i];
2960        }
2961
2962
2963        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)
2964        {
2965                m_pControlSocket->LogMessage(MessageType::Status, _("Received a directory listing which appears to be encoded in EBCDIC."));
2966                m_listingEncoding = listingEncoding::ebcdic;
2967                for (auto it = m_DataList.begin(); it != m_DataList.end(); ++it)
2968                        ConvertEncoding(it->p, it->len);
2969        }
2970        else
2971                m_listingEncoding = listingEncoding::normal;
2972}
Note: See TracBrowser for help on using the repository browser.