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