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