1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 /**************************************************************************
22                                 TODO
23  **************************************************************************
24 
25  *************************************************************************/
26 #include "ftpdirp.hxx"
27 #include <osl/time.h>
28 
29 
30 using namespace ftp;
31 
ascii_isWhitespace(sal_Unicode ch)32 static bool ascii_isWhitespace( sal_Unicode ch )
33 {
34     return ((ch <= 0x20) && ch);
35 }
36 
37 
38 /*========================================================================
39  *
40  * FTPDirectoryParser implementation.
41  *
42  *======================================================================*/
43 /*
44  * parseDOS.
45  * Accepts one of two styles:
46  *
47  * 1  *WSP 1*2DIGIT ("." / "-") 1*2DIGIT ("." / "-") 1*4DIGIT 1*WSP
48  *    1*2DIGIT ":" 1*2DIGIT [*WSP ("A" / "P") "M"] 1*WSP
49  *    ((DIGIT *(DIGIT / "." / ",")) / "<DIR>") 1*WSP 1*OCTET
50  *
51  *    interpreted as: mm.dd.yy hh:mm (size / <DIR>) name
52  *
53  * 2  *WSP 1*DIGIT 1*WSP *(1*CHAR *WSP) *1("DIR" 1*WSP) 1*2DIGIT "-" 1*2DIGIT
54  *    "-" 1*4DIGIT 1*WSP 1*2DIGIT ":" 1*2DIGIT 1*WSP 1*OCTET
55  *
56  *    interpreted as: size attribs DIR mm-dd-yy hh:mm name
57  */
58 
parseDOS(FTPDirentry & rEntry,const sal_Char * pBuffer)59 bool FTPDirectoryParser::parseDOS (
60     FTPDirentry &rEntry,
61     const sal_Char  *pBuffer)
62 {
63     bool   bDirectory = false;
64     sal_uInt32 nSize = 0;
65     sal_uInt16 nYear = 0;
66     sal_uInt16 nMonth = 0;
67     sal_uInt16 nDay = 0;
68     sal_uInt16 nHour = 0;
69     sal_uInt16 nMinute = 0;
70 
71     enum StateType
72     {
73         STATE_INIT_LWS,
74         STATE_MONTH_OR_SIZE,
75         STATE_1_DAY, STATE_1_YEAR, STATE_1_YEAR_LWS, STATE_1_HOUR,
76         STATE_1_MINUTE, STATE_1_MINUTE_LWS, STATE_1_AP,
77         STATE_1_APM, STATE_1_LESS, STATE_1_D, STATE_1_DI,
78         STATE_1_DIR, STATE_1_SIZE,
79         STATE_2_SIZE, STATE_2_SIZE_LWS, STATE_2_ATTRIB,
80         STATE_2_D, STATE_2_DI, STATE_2_DIR_LWS,
81         STATE_2_MONTH, STATE_2_DAY, STATE_2_YEAR, STATE_2_YEAR_LWS,
82         STATE_2_HOUR, STATE_2_MINUTE,
83         STATE_LWS_NAME,
84         STATE_ERROR
85     };
86 
87     int nDigits = 0;
88     enum StateType eState = STATE_INIT_LWS;
89     for (const sal_Char *p = pBuffer;
90          eState != STATE_ERROR && *p;
91          ++p)
92     {
93         switch (eState)
94         {
95             case STATE_INIT_LWS:
96                 if (*p >= '0' && *p <= '9')
97                 {
98                     nMonth = *p - '0';
99                     nDigits = 1;
100                     eState = STATE_MONTH_OR_SIZE;
101                 }
102                 else if (!ascii_isWhitespace(*p))
103                     eState = STATE_ERROR;
104                 break;
105 
106             case STATE_MONTH_OR_SIZE:
107                 if (*p >= '0' && *p <= '9')
108                 {
109                     nMonth = 10 * nMonth + (*p - '0');
110                     if (nDigits < 2)
111                         ++nDigits;
112                     else
113                     {
114                         nSize = nMonth;
115                         nMonth = 0;
116                         eState = STATE_2_SIZE;
117                     }
118                 }
119                 else if (ascii_isWhitespace(*p))
120                 {
121                     nSize = nMonth;
122                     nMonth = 0;
123                     eState = STATE_2_SIZE_LWS;
124                 }
125                 else if ((*p == '.' || *p == '-') && nMonth && nMonth <= 12)
126                 {
127                     nDigits = 0;
128                     eState = STATE_1_DAY;
129                 }
130                 else
131                     eState = STATE_ERROR;
132                 break;
133 
134             case STATE_1_DAY:
135                 if (*p >= '0' && *p <= '9')
136                     if (nDigits < 2)
137                     {
138                         nDay = 10 * nDay + (*p - '0');
139                         ++nDigits;
140                     }
141                     else
142                         eState = STATE_ERROR;
143                 else if ((*p == '.' || *p == '-') && nDay && nDay <= 31)
144                 {
145                     nDigits = 0;
146                     eState = STATE_1_YEAR;
147                 }
148                 else
149                     eState = STATE_ERROR;
150                 break;
151 
152             case STATE_1_YEAR:
153                 if (*p >= '0' && *p <= '9')
154                 {
155                     if (nDigits < 4)
156                     {
157                         nYear = 10 * nYear + (*p - '0');
158                         ++nDigits;
159                     }
160                     else
161                         eState = STATE_ERROR;
162                 }
163                 else
164                 {
165                     if (ascii_isWhitespace(*p))
166                         eState = STATE_1_YEAR_LWS;
167                     else
168                         eState = STATE_ERROR;
169                 }
170                 break;
171 
172             case STATE_1_YEAR_LWS:
173                 if (*p >= '0' && *p <= '9')
174                 {
175                     nHour = *p - '0';
176                     nDigits = 1;
177                     eState = STATE_1_HOUR;
178                 }
179                 else if (!ascii_isWhitespace(*p))
180                     eState = STATE_ERROR;
181                 break;
182 
183             case STATE_1_HOUR:
184                 if (*p >= '0' && *p <= '9')
185                     if (nDigits < 2)
186                     {
187                         nHour = 10 * nHour + (*p - '0');
188                         ++nDigits;
189                     }
190                     else
191                         eState = STATE_ERROR;
192                 else if (*p == ':' && nHour < 24)
193                 {
194                     nDigits = 0;
195                     eState = STATE_1_MINUTE;
196                 }
197                 else
198                     eState = STATE_ERROR;
199                 break;
200 
201             case STATE_1_MINUTE:
202                 if (*p >= '0' && *p <= '9')
203                     if (nDigits < 2)
204                     {
205                         nMinute = 10 * nMinute + (*p - '0');
206                         ++nDigits;
207                     }
208                     else
209                         eState = STATE_ERROR;
210                 else if ((*p == 'a' || *p == 'A') && nMinute < 60)
211                     if (nHour >= 1 && nHour <= 11)
212                         eState = STATE_1_AP;
213                     else if (nHour == 12)
214                     {
215                         nHour = 0;
216                         eState = STATE_1_AP;
217                     }
218                     else
219                         eState = STATE_ERROR;
220                 else if ((*p == 'p' || *p == 'P') && nMinute < 60)
221                     if (nHour >= 1 && nHour <= 11)
222                     {
223                         nHour += 12;
224                         eState = STATE_1_AP;
225                     }
226                     else if (nHour == 12)
227                         eState = STATE_1_AP;
228                     else
229                         eState = STATE_ERROR;
230                 else if (ascii_isWhitespace(*p) && (nMinute < 60))
231                     eState = STATE_1_MINUTE_LWS;
232                 else
233                     eState = STATE_ERROR;
234                 break;
235 
236             case STATE_1_MINUTE_LWS:
237                 if (*p == 'a' || *p == 'A')
238                     if (nHour >= 1 && nHour <= 11)
239                         eState = STATE_1_AP;
240                     else if (nHour == 12)
241                     {
242                         nHour = 0;
243                         eState = STATE_1_AP;
244                     }
245                     else
246                         eState = STATE_ERROR;
247                 else if (*p == 'p' || *p == 'P')
248                     if (nHour >= 1 && nHour <= 11)
249                     {
250                         nHour += 12;
251                         eState = STATE_1_AP;
252                     }
253                     else if (nHour == 12)
254                         eState = STATE_1_AP;
255                     else
256                         eState = STATE_ERROR;
257                 else if (*p == '<')
258                     eState = STATE_1_LESS;
259                 else if (*p >= '0' && *p <= '9')
260                 {
261                     nSize = *p - '0';
262                     eState = STATE_1_SIZE;
263                 }
264                 else if (!ascii_isWhitespace(*p))
265                     eState = STATE_ERROR;
266                 break;
267 
268             case STATE_1_AP:
269                 eState = *p == 'm' || *p == 'M' ? STATE_1_APM : STATE_ERROR;
270                 break;
271 
272             case STATE_1_APM:
273                 if (*p == '<')
274                     eState = STATE_1_LESS;
275                 else if (*p >= '0' && *p <= '9')
276                 {
277                     nSize = *p - '0';
278                     eState = STATE_1_SIZE;
279                 }
280                 else if (!ascii_isWhitespace(*p))
281                     eState = STATE_ERROR;
282                 break;
283 
284             case STATE_1_LESS:
285                 eState = *p == 'd' || *p == 'D' ? STATE_1_D : STATE_ERROR;
286                 break;
287 
288             case STATE_1_D:
289                 eState = *p == 'i' || *p == 'I' ? STATE_1_DI : STATE_ERROR;
290                 break;
291 
292             case STATE_1_DI:
293                 eState = *p == 'r' || *p == 'R' ? STATE_1_DIR : STATE_ERROR;
294                 break;
295 
296             case STATE_1_DIR:
297                 if (*p == '>')
298                 {
299                     bDirectory = true;
300                     eState = STATE_LWS_NAME;
301                 }
302                 else
303                     eState = STATE_ERROR;
304                 break;
305 
306             case STATE_1_SIZE:
307                 if (*p >= '0' && *p <= '9')
308                     nSize = 10 * nSize + (*p - '0');
309                 else if (ascii_isWhitespace(*p))
310                     eState = STATE_LWS_NAME;
311                 else
312                     eState = STATE_ERROR;
313                 break;
314 
315             case STATE_2_SIZE:
316                 if (*p >= '0' && *p <= '9')
317                     nSize = 10 * nSize + (*p - '0');
318                 else if (ascii_isWhitespace(*p))
319                     eState = STATE_2_SIZE_LWS;
320                 else
321                     eState = STATE_ERROR;
322                 break;
323 
324             case STATE_2_SIZE_LWS:
325                 if (*p == 'd' || *p == 'D')
326                     eState = STATE_2_D;
327                 else if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))
328                     eState = STATE_2_ATTRIB;
329                 else if (*p >= '0' && *p <= '9')
330                 {
331                     nMonth = *p - '0';
332                     nDigits = 1;
333                     eState = STATE_2_MONTH;
334                 }
335                 else if (!ascii_isWhitespace(*p))
336                     eState = STATE_ERROR;
337                 break;
338 
339             case STATE_2_ATTRIB:
340                 if (ascii_isWhitespace(*p))
341                     eState = STATE_2_SIZE_LWS;
342                 else if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z'))
343                     eState = STATE_ERROR;
344                 break;
345 
346             case STATE_2_D:
347                 if (*p == 'i' || *p == 'I')
348                     eState = STATE_2_DI;
349                 else if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))
350                     eState = STATE_2_ATTRIB;
351                 else if (ascii_isWhitespace(*p))
352                     eState = STATE_2_SIZE_LWS;
353                 else
354                     eState = STATE_ERROR;
355                 break;
356 
357             case STATE_2_DI:
358                 if (*p == 'r' || *p == 'R')
359                 {
360                     bDirectory = true;
361                     eState = STATE_2_DIR_LWS;
362                 }
363                 else
364                 {
365                     if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))
366                         eState = STATE_2_ATTRIB;
367                     else if (ascii_isWhitespace(*p))
368                         eState = STATE_2_SIZE_LWS;
369                     else
370                         eState = STATE_ERROR;
371                 }
372                 break;
373 
374             case STATE_2_DIR_LWS:
375                 if (*p >= '0' && *p <= '9')
376                 {
377                     nMonth = *p - '0';
378                     nDigits = 1;
379                     eState = STATE_2_MONTH;
380                 }
381                 else if (!ascii_isWhitespace(*p))
382                     eState = STATE_ERROR;
383                 break;
384 
385             case STATE_2_MONTH:
386                 if (*p >= '0' && *p <= '9')
387                     if (nDigits < 2)
388                     {
389                         nMonth = 10 * nMonth + (*p - '0');
390                         ++nDigits;
391                     }
392                     else
393                         eState = STATE_ERROR;
394                 else if (*p == '-' && nMonth && nMonth <= 12)
395                 {
396                     nDigits = 0;
397                     eState = STATE_2_DAY;
398                 }
399                 else
400                     eState = STATE_ERROR;
401                 break;
402 
403             case STATE_2_DAY:
404                 if (*p >= '0' && *p <= '9')
405                     if (nDigits < 2)
406                     {
407                         nDay = 10 * nDay + (*p - '0');
408                         ++nDigits;
409                     }
410                     else
411                         eState = STATE_ERROR;
412                 else if (*p == '-' && nDay && nDay <= 31)
413                 {
414                     nDigits = 0;
415                     eState = STATE_2_YEAR;
416                 }
417                 else
418                     eState = STATE_ERROR;
419                 break;
420 
421             case STATE_2_YEAR:
422                 if (*p >= '0' && *p <= '9')
423                 {
424                     if (nDigits < 4)
425                     {
426                         nYear = 10 * nYear + (*p - '0');
427                         ++nDigits;
428                     }
429                     else
430                         eState = STATE_ERROR;
431                 }
432                 else
433                 {
434                     if (ascii_isWhitespace(*p))
435                         eState = STATE_2_YEAR_LWS;
436                     else
437                         eState = STATE_ERROR;
438                 }
439                 break;
440 
441             case STATE_2_YEAR_LWS:
442                 if (*p >= '0' && *p <= '9')
443                 {
444                     nHour = *p - '0';
445                     nDigits = 1;
446                     eState = STATE_2_HOUR;
447                 }
448                 else if (!ascii_isWhitespace(*p))
449                     eState = STATE_ERROR;
450                 break;
451 
452             case STATE_2_HOUR:
453                 if (*p >= '0' && *p <= '9')
454                     if (nDigits < 2)
455                     {
456                         nHour = 10 * nHour + (*p - '0');
457                         ++nDigits;
458                     }
459                     else
460                         eState = STATE_ERROR;
461                 else if (*p == ':' && nHour < 24)
462                 {
463                     nDigits = 0;
464                     eState = STATE_2_MINUTE;
465                 }
466                 else
467                     eState = STATE_ERROR;
468                 break;
469 
470             case STATE_2_MINUTE:
471                 if (*p >= '0' && *p <= '9')
472                 {
473                     if (nDigits < 2)
474                     {
475                         nMinute = 10 * nMinute + (*p - '0');
476                         ++nDigits;
477                     }
478                     else
479                         eState = STATE_ERROR;
480                 }
481                 else
482                 {
483                     if (ascii_isWhitespace(*p) && (nMinute < 60))
484                         eState = STATE_LWS_NAME;
485                     else
486                         eState = STATE_ERROR;
487                 }
488                 break;
489 
490             case STATE_LWS_NAME:
491                 if (!ascii_isWhitespace(*p))
492                 {
493                     setPath (rEntry.m_aName, p);
494                     if (bDirectory)
495                         rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISDIR;
496                     rEntry.m_nSize = nSize;
497 
498                     setYear (rEntry.m_aDate, nYear);
499 
500                     rEntry.m_aDate.SetMonth(nMonth);
501                     rEntry.m_aDate.SetDay(nDay);
502                     rEntry.m_aDate.SetHour(nHour);
503                     rEntry.m_aDate.SetMin(nMinute);
504 
505                     return true;
506                 }
507                 break;
508             case STATE_ERROR:
509                 break;
510         }
511     }
512 
513     return false;
514 }
515 
516 /*
517  * parseVMS.
518  * Directory entries may span one or two lines:
519  *
520  *   entry: *lws name *1(*lws <NEWLINE>) 1*lws size 1*lws datetime rest
521  *
522  *   name: filename "." filetype ";" version
523  *   filename: 1*39fchar
524  *   filetype: 1*39fchar
525  *   version: non0digit *digit
526  *
527  *   size: "0" / non0digit *digit
528  *
529  *   datetime: date 1*lwsp time
530  *   date: day "-" month "-" year
531  *   day: (*1"0" non0digit) / ("1"-"2" digit) / ("3" "0"-"1")
532  *   month: "JAN" / "FEB" / "MAR" / "APR" / "MAY" / "JUN" / "JUL" / "AUG"
533  *        / "SEP" / "OCT" / "NOV" / "DEC" ; all case insensitive
534  *   year: 2digit / 4digit
535  *   time: hour ":" minute
536  *   hour: ((*1"0" / "1") digit) / ("2" "0"-"3")
537  *   minute: "0"-"5" digit
538  *
539  *   rest: *1(lws *<ANY>)
540  *
541  *   lws: <TAB> / <SPACE>
542  *   non0digit: "1"-"9"
543  *   digit: "0" / non0digit
544  *   fchar: "A"-"Z" / "a"-"z" / digit / "-" / "_" / "$"
545  *
546  * For directories, the returned name is the <filename> part; for non-
547  * directory files, the returned name is the <filename "." filetype> part.
548  * An entry is a directory iff its filetype is "DIR" (ignoring case).
549  *
550  * The READ, WRITE, and ISLINK mode bits are not supported.
551  *
552  * The returned size is the <size> part, multiplied by 512, and with the high
553  * order bits truncated to fit into a sal_uInt32.
554  *
555  */
parseVMS(FTPDirentry & rEntry,const sal_Char * pBuffer)556 bool FTPDirectoryParser::parseVMS (
557     FTPDirentry &rEntry,
558     const sal_Char  *pBuffer)
559 {
560     static OUString aFirstLineName;
561     static bool bFirstLineDir = false;
562 
563     for (bool bFirstLine = true;; bFirstLine = false)
564     {
565         const sal_Char *p = pBuffer;
566         if (bFirstLine)
567         {
568             // Skip <*lws> part:
569             while (*p == '\t' || *p == ' ')
570                 ++p;
571 
572             // Parse <filename "."> part:
573             const sal_Char *pFileName = p;
574             while ((*p >= 'A' && *p <= 'Z') ||
575                    (*p >= 'a' && *p <= 'z') ||
576                    (*p >= '0' && *p <= '9') ||
577                    *p == '-' || *p == '_' || *p == '$')
578                 ++p;
579 
580             if (*p != '.' || p == pFileName || p - pFileName > 39)
581             {
582                 if (!aFirstLineName.isEmpty())
583                     continue;
584                 else
585                     return false;
586             }
587 
588             // Parse <filetype ";"> part:
589             const sal_Char *pFileType = ++p;
590             while ((*p >= 'A' && *p <= 'Z') ||
591                    (*p >= 'a' && *p <= 'z') ||
592                    (*p >= '0' && *p <= '9') ||
593                    *p == '-' || *p == '_' || *p == '$')
594                 ++p;
595 
596             if (*p != ';' || p == pFileName || p - pFileName > 39)
597             {
598                 if (!aFirstLineName.isEmpty())
599                     continue;
600                 else
601                     return false;
602             }
603             ++p;
604 
605             // Set entry's name and mode (ISDIR flag):
606             if ((p - pFileType == 4) &&
607                 (pFileType[0] == 'D' || pFileType[0] == 'd') &&
608                 (pFileType[1] == 'I' || pFileType[1] == 'i') &&
609                 (pFileType[2] == 'R' || pFileType[2] == 'r')    )
610             {
611                 setPath (rEntry.m_aName, pFileName, (pFileType - pFileName));
612                 rEntry.m_nMode = INETCOREFTP_FILEMODE_ISDIR;
613             }
614             else
615             {
616                 setPath (rEntry.m_aName, pFileName, (p - pFileName));
617                 rEntry.m_nMode = 0;
618             }
619 
620             // Skip <version> part:
621             if (*p < '1' || *p > '9')
622             {
623                 if (!aFirstLineName.isEmpty())
624                     continue;
625                 else
626                     return false;
627             }
628             ++p;
629             while (*p >= '0' && *p <= '9')
630                 ++p;
631 
632             // Parse <1*lws> or <*lws <NEWLINE>> part:
633             bool bLWS = false;
634             while (*p == '\t' || *p == ' ')
635             {
636                 bLWS = true;
637                 ++p;
638             }
639             if (*p)
640             {
641                 if (!bLWS)
642                 {
643                     if (!aFirstLineName.isEmpty())
644                         continue;
645                     else
646                         return false;
647                 }
648             }
649             else
650             {
651                 /*
652                  * First line of entry spanning two lines,
653                  * wait for second line.
654                  */
655                 aFirstLineName = rEntry.m_aName;
656                 bFirstLineDir =
657                     ((rEntry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) != 0);
658                 return false;
659             }
660         }
661         else
662         {
663             /*
664              * Second line of entry spanning two lines,
665              * restore entry's name and mode (ISDIR flag).
666              */
667             rEntry.m_aName = aFirstLineName;
668             rEntry.m_nMode = (bFirstLineDir ? INETCOREFTP_FILEMODE_ISDIR : 0);
669 
670             // Skip <1*lws> part:
671             if (*p != '\t' && *p != ' ')
672                 return false;
673             ++p;
674             while (*p == '\t' || *p == ' ')
675                 ++p;
676         }
677 
678         // Parse <size> part and set entry's size:
679         if (*p < '0' || *p > '9')
680             return false;
681         sal_uInt32 nSize = *p - '0';
682         if (*p++ != '0')
683             while (*p >= '0' && *p <= '9')
684                 nSize = 10 * rEntry.m_nSize + (*p++ - '0');
685         rEntry.m_nSize = 512 * nSize;
686 
687         // Skip <1*lws> part:
688         if (*p != '\t' && *p != ' ')
689             return false;
690         ++p;
691         while (*p == '\t' || *p == ' ')
692             ++p;
693 
694         // Parse <day "-"> part and set entry date's day:
695         sal_uInt16 nDay;
696         if (*p == '0')
697         {
698             ++p;
699             if (*p < '1' || *p > '9')
700                 return false;
701             nDay = *p++ - '0';
702         }
703         else if (*p == '1' || *p == '2')
704         {
705             nDay = *p++ - '0';
706             if (*p >= '0' && *p <= '9')
707                 nDay = 10 * nDay + (*p++ - '0');
708         }
709         else if (*p == '3')
710         {
711             ++p;
712             nDay = (*p == '0' || *p == '1') ? 30 + (*p++ - '0') : 3;
713         }
714         else if (*p >= '4' && *p <= '9')
715             nDay = *p++ - '0';
716         else
717             return false;
718 
719         rEntry.m_aDate.SetDay(nDay);
720         if (*p++ != '-')
721             return false;
722 
723         // Parse <month "-"> part and set entry date's month:
724         sal_Char const * pMonth = p;
725         sal_Int32 const monthLen = 3;
726         for (int i = 0; i < monthLen; ++i)
727         {
728             if (!((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')))
729                 return false;
730             ++p;
731         }
732         if (rtl_str_compareIgnoreAsciiCase_WithLength(
733                 pMonth, monthLen, "JAN", monthLen) == 0)
734             rEntry.m_aDate.SetMonth(1);
735         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
736                      pMonth, monthLen, "FEB", monthLen) == 0)
737             rEntry.m_aDate.SetMonth(2);
738         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
739                      pMonth, monthLen, "MAR", monthLen) == 0)
740             rEntry.m_aDate.SetMonth(3);
741         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
742                      pMonth, monthLen, "APR", monthLen) == 0)
743             rEntry.m_aDate.SetMonth(4);
744         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
745                      pMonth, monthLen, "MAY", monthLen) == 0)
746             rEntry.m_aDate.SetMonth(5);
747         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
748                      pMonth, monthLen, "JUN", monthLen) == 0)
749             rEntry.m_aDate.SetMonth(6);
750         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
751                      pMonth, monthLen, "JUL", monthLen) == 0)
752             rEntry.m_aDate.SetMonth(7);
753         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
754                      pMonth, monthLen, "AUG", monthLen) == 0)
755             rEntry.m_aDate.SetMonth(8);
756         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
757                      pMonth, monthLen, "SEP", monthLen) == 0)
758             rEntry.m_aDate.SetMonth(9);
759         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
760                      pMonth, monthLen, "OCT", monthLen) == 0)
761             rEntry.m_aDate.SetMonth(10);
762         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
763                      pMonth, monthLen, "NOV", monthLen) == 0)
764             rEntry.m_aDate.SetMonth(11);
765         else if (rtl_str_compareIgnoreAsciiCase_WithLength(
766                      pMonth, monthLen, "DEC", monthLen) == 0)
767             rEntry.m_aDate.SetMonth(12);
768         else
769             return false;
770         if (*p++ != '-')
771             return false;
772 
773         // Parse <year> part and set entry date's year:
774         sal_uInt16 nYear = 0;
775         for (int i = 0; i < 2; ++i)
776         {
777             if (*p < '0' || *p > '9')
778                 return false;
779             nYear = 10 * nYear + (*p++ - '0');
780         }
781         if (*p >= '0' && *p <= '9')
782         {
783             nYear = 10 * nYear + (*p++ - '0');
784             if (*p < '0' || *p > '9')
785                 return false;
786             nYear = 10 * nYear + (*p++ - '0');
787         }
788         setYear (rEntry.m_aDate, nYear);
789 
790         // Skip <1*lws> part:
791         if (*p != '\t' && *p != ' ')
792             return false;
793         ++p;
794         while (*p == '\t' || *p == ' ')
795             ++p;
796 
797         // Parse <hour ":"> part and set entry time's hour:
798         sal_uInt16 nHour;
799         if (*p == '0' || *p == '1')
800         {
801             nHour = *p++ - '0';
802             if (*p >= '0' && *p <= '9')
803                 nHour = 10 * nHour + (*p++ - '0');
804         }
805         else if (*p == '2')
806         {
807             ++p;
808             nHour = (*p >= '0' && *p <= '3') ? 20 + (*p++ - '0') : 2;
809         }
810         else if (*p >= '3' && *p <= '9')
811             nHour = *p++ - '0';
812         else
813             return false;
814 
815         rEntry.m_aDate.SetHour(nHour);
816         if (*p++ != ':')
817             return false;
818 
819         /*
820          * Parse <minute> part and set entry time's minutes,
821          * seconds (0), and nanoseconds (0).
822          */
823         if (*p < '0' || *p > '5')
824             return false;
825 
826         sal_uInt16 nMinute = *p++ - '0';
827         if (*p < '0' || *p > '9')
828             return false;
829 
830         nMinute = 10 * nMinute + (*p++ - '0');
831         rEntry.m_aDate.SetMin(nMinute);
832         rEntry.m_aDate.SetSec(0);
833         rEntry.m_aDate.SetNanoSec(0);
834 
835         // Skip <rest> part:
836         return !*p || *p == '\t' || *p == ' ';
837     }
838 }
839 
840 /*
841  * parseUNIX
842  */
parseUNIX(FTPDirentry & rEntry,const sal_Char * pBuffer)843 bool FTPDirectoryParser::parseUNIX (
844     FTPDirentry &rEntry,
845     const sal_Char  *pBuffer)
846 {
847     const sal_Char *p1, *p2;
848     p1 = p2 = pBuffer;
849 
850     if (!((*p1 == '-') || (*p1 == 'd') || (*p1 == 'l')))
851         return false;
852 
853     // 1st column: FileMode.
854     if (*p1 == 'd')
855         rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISDIR;
856 
857     if (*p1 == 'l')
858         rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISLINK;
859 
860     // Skip to end of column and set rights by the way
861     while (*p1 && !ascii_isWhitespace(*p1)) {
862         if(*p1 == 'r')
863             rEntry.m_nMode |= INETCOREFTP_FILEMODE_READ;
864         else if(*p1 == 'w')
865             rEntry.m_nMode |= INETCOREFTP_FILEMODE_WRITE;
866         p1++;
867     }
868 
869     /*
870      * Scan for the sequence of size and date fields:
871      *   *LWS 1*DIGIT 1*LWS 3CHAR 1*LWS 1*2DIGIT 1*LWS
872      *   (4DIGIT / (1*2DIGIT ":" 2DIGIT)) 1*LWS
873      */
874     enum Mode
875     {
876         FOUND_NONE, FOUND_SIZE, FOUND_MONTH, FOUND_DAY, FOUND_YEAR_TIME
877     };
878 
879     const sal_Char *pDayStart = nullptr;
880     const sal_Char *pDayEnd = nullptr;
881     Mode eMode;
882     for (eMode = FOUND_NONE; *p1 && eMode != FOUND_YEAR_TIME; p1 = p2 + 1)
883     {
884         while (*p1 && ascii_isWhitespace(*p1))
885             ++p1;
886         p2 = p1;
887         while (*p2 && !ascii_isWhitespace(*p2))
888             ++p2;
889 
890         switch (eMode)
891         {
892             case FOUND_NONE:
893                 if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize))
894                     eMode = FOUND_SIZE;
895                 break;
896 
897             case FOUND_SIZE:
898                 if (parseUNIX_isMonthField (p1, p2, rEntry.m_aDate))
899                     eMode = FOUND_MONTH;
900                 else if (!parseUNIX_isSizeField (p1, p2, rEntry.m_nSize))
901                     eMode = FOUND_NONE;
902                 break;
903 
904             case FOUND_MONTH:
905                 if (parseUNIX_isDayField (p1, p2, rEntry.m_aDate))
906                 {
907                     pDayStart = p1;
908                     pDayEnd = p2;
909                     eMode = FOUND_DAY;
910                 }
911                 else if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize))
912                     eMode = FOUND_SIZE;
913                 else
914                     eMode = FOUND_NONE;
915                 break;
916 
917             case FOUND_DAY:
918                 if (parseUNIX_isYearTimeField (p1, p2, rEntry.m_aDate))
919                     eMode = FOUND_YEAR_TIME;
920                 else if (
921                     parseUNIX_isSizeField (
922                         pDayStart, pDayEnd, rEntry.m_nSize) &&
923                     parseUNIX_isMonthField (
924                         p1, p2, rEntry.m_aDate))
925                     eMode = FOUND_MONTH;
926                 else if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize))
927                     eMode = FOUND_SIZE;
928                 else
929                     eMode = FOUND_NONE;
930                 break;
931             // coverity[dead_error_begin] - following conditions exist to avoid compiler warning
932             case FOUND_YEAR_TIME:
933                 break;
934         }
935     }
936 
937     if (eMode == FOUND_YEAR_TIME)
938     {
939         // 9th column: FileName (rest of line).
940         while (*p1 && ascii_isWhitespace(*p1)) p1++;
941         setPath (rEntry.m_aName, p1);
942 
943         // Done.
944         return true;
945     }
946     return false;
947 }
948 
949 /*
950  * parseUNIX_isSizeField.
951  */
parseUNIX_isSizeField(const sal_Char * pStart,const sal_Char * pEnd,sal_uInt32 & rSize)952 bool FTPDirectoryParser::parseUNIX_isSizeField (
953     const sal_Char *pStart,
954     const sal_Char *pEnd,
955     sal_uInt32     &rSize)
956 {
957     if (!*pStart || !*pEnd || pStart == pEnd)
958         return false;
959 
960     rSize = 0;
961     if (*pStart >= '0' && *pStart <= '9')
962     {
963         for (; pStart < pEnd; ++pStart)
964             if ((*pStart >= '0') && (*pStart <= '9'))
965                 rSize = 10 * rSize + (*pStart - '0');
966             else
967                 return false;
968         return true;
969     }
970     else
971     {
972         /*
973          * For a combination of long group name and large file size,
974          * some FTPDs omit LWS between those two columns.
975          */
976         int nNonDigits = 0;
977         int nDigits = 0;
978 
979         for (; pStart < pEnd; ++pStart)
980             if ((*pStart >= '1') && (*pStart <= '9'))
981             {
982                 ++nDigits;
983                 rSize = 10 * rSize + (*pStart - '0');
984             }
985             else if ((*pStart == '0') && nDigits)
986             {
987                 ++nDigits;
988                 rSize *= 10;
989             }
990             else if ((*pStart > ' ') && (sal::static_int_cast<sal_uInt8>(*pStart) <= '\x7F'))
991             {
992                 nNonDigits += nDigits + 1;
993                 nDigits = 0;
994                 rSize = 0;
995             }
996             else
997                 return false;
998         return ((nNonDigits >= 9) && (nDigits >= 7));
999     }
1000 }
1001 
1002 /*
1003  * parseUNIX_isMonthField.
1004  */
parseUNIX_isMonthField(const sal_Char * pStart,const sal_Char * pEnd,DateTime & rDateTime)1005 bool FTPDirectoryParser::parseUNIX_isMonthField (
1006     const sal_Char *pStart,
1007     const sal_Char *pEnd,
1008     DateTime       &rDateTime)
1009 {
1010     if (!*pStart || !*pEnd || pStart + 3 != pEnd)
1011         return false;
1012 
1013     if ((pStart[0] == 'j' || pStart[0] == 'J') &&
1014         (pStart[1] == 'a' || pStart[1] == 'A') &&
1015         (pStart[2] == 'n' || pStart[2] == 'N')    )
1016     {
1017         rDateTime.SetMonth(1);
1018         return true;
1019     }
1020     if ((pStart[0] == 'f' || pStart[0] == 'F') &&
1021         (pStart[1] == 'e' || pStart[1] == 'E') &&
1022         (pStart[2] == 'b' || pStart[2] == 'B')    )
1023     {
1024         rDateTime.SetMonth(2);
1025         return true;
1026     }
1027     if ((pStart[0] == 'm' || pStart[0] == 'M') &&
1028         (pStart[1] == 'a' || pStart[1] == 'A') &&
1029         (pStart[2] == 'r' || pStart[2] == 'R')    )
1030     {
1031         rDateTime.SetMonth(3);
1032         return true;
1033     }
1034     if ((pStart[0] == 'a' || pStart[0] == 'A') &&
1035         (pStart[1] == 'p' || pStart[1] == 'P') &&
1036         (pStart[2] == 'r' || pStart[2] == 'R')    )
1037     {
1038         rDateTime.SetMonth(4);
1039         return true;
1040     }
1041     if ((pStart[0] == 'm' || pStart[0] == 'M') &&
1042         (pStart[1] == 'a' || pStart[1] == 'A') &&
1043         (pStart[2] == 'y' || pStart[2] == 'Y')    )
1044     {
1045         rDateTime.SetMonth(5);
1046         return true;
1047     }
1048     if ((pStart[0] == 'j' || pStart[0] == 'J') &&
1049         (pStart[1] == 'u' || pStart[1] == 'U') &&
1050         (pStart[2] == 'n' || pStart[2] == 'N')    )
1051     {
1052         rDateTime.SetMonth(6);
1053         return true;
1054     }
1055     if ((pStart[0] == 'j' || pStart[0] == 'J') &&
1056         (pStart[1] == 'u' || pStart[1] == 'U') &&
1057         (pStart[2] == 'l' || pStart[2] == 'L')    )
1058     {
1059         rDateTime.SetMonth(7);
1060         return true;
1061     }
1062     if ((pStart[0] == 'a' || pStart[0] == 'A') &&
1063         (pStart[1] == 'u' || pStart[1] == 'U') &&
1064         (pStart[2] == 'g' || pStart[2] == 'G')    )
1065     {
1066         rDateTime.SetMonth(8);
1067         return true;
1068     }
1069     if ((pStart[0] == 's' || pStart[0] == 'S') &&
1070         (pStart[1] == 'e' || pStart[1] == 'E') &&
1071         (pStart[2] == 'p' || pStart[2] == 'P')    )
1072     {
1073         rDateTime.SetMonth(9);
1074         return true;
1075     }
1076     if ((pStart[0] == 'o' || pStart[0] == 'O') &&
1077         (pStart[1] == 'c' || pStart[1] == 'C') &&
1078         (pStart[2] == 't' || pStart[2] == 'T')    )
1079     {
1080         rDateTime.SetMonth(10);
1081         return true;
1082     }
1083     if ((pStart[0] == 'n' || pStart[0] == 'N') &&
1084         (pStart[1] == 'o' || pStart[1] == 'O') &&
1085         (pStart[2] == 'v' || pStart[2] == 'V')    )
1086     {
1087         rDateTime.SetMonth(11);
1088         return true;
1089     }
1090     if ((pStart[0] == 'd' || pStart[0] == 'D') &&
1091         (pStart[1] == 'e' || pStart[1] == 'E') &&
1092         (pStart[2] == 'c' || pStart[2] == 'C')    )
1093     {
1094         rDateTime.SetMonth(12);
1095         return true;
1096     }
1097     return false;
1098 }
1099 
1100 /*
1101  * parseUNIX_isDayField.
1102  */
parseUNIX_isDayField(const sal_Char * pStart,const sal_Char * pEnd,DateTime & rDateTime)1103 bool FTPDirectoryParser::parseUNIX_isDayField (
1104     const sal_Char *pStart,
1105     const sal_Char *pEnd,
1106     DateTime       &rDateTime)
1107 {
1108     if (!*pStart || !*pEnd || pStart == pEnd)
1109         return false;
1110     if (*pStart < '0' || *pStart > '9')
1111         return false;
1112 
1113     sal_uInt16 nDay = *pStart - '0';
1114     if (pStart + 1 < pEnd)
1115     {
1116         if (pStart + 2 != pEnd || pStart[1] < '0' || pStart[1] > '9')
1117             return false;
1118         nDay = 10 * nDay + (pStart[1] - '0');
1119     }
1120     if (!nDay || nDay > 31)
1121         return false;
1122 
1123     rDateTime.SetDay(nDay);
1124     return true;
1125 }
1126 
1127 /*
1128  * parseUNIX_isYearTimeField.
1129  */
parseUNIX_isYearTimeField(const sal_Char * pStart,const sal_Char * pEnd,DateTime & rDateTime)1130 bool FTPDirectoryParser::parseUNIX_isYearTimeField (
1131     const sal_Char *pStart,
1132     const sal_Char *pEnd,
1133     DateTime       &rDateTime)
1134 {
1135     if (!*pStart || !*pEnd || pStart == pEnd ||
1136         *pStart < '0' || *pStart > '9')
1137         return false;
1138 
1139     sal_uInt16 nNumber = *pStart - '0';
1140     ++pStart;
1141 
1142     if (pStart == pEnd)
1143         return false;
1144     if (*pStart == ':')
1145         return parseUNIX_isTime (pStart, pEnd, nNumber, rDateTime);
1146     if (*pStart < '0' || *pStart > '9')
1147         return false;
1148 
1149     nNumber = 10 * nNumber + (*pStart - '0');
1150     ++pStart;
1151 
1152     if (pStart == pEnd)
1153         return false;
1154     if (*pStart == ':')
1155         return parseUNIX_isTime (pStart, pEnd, nNumber, rDateTime);
1156     if (*pStart < '0' || *pStart > '9')
1157         return false;
1158 
1159     nNumber = 10 * nNumber + (*pStart - '0');
1160     ++pStart;
1161 
1162     if (pStart == pEnd || *pStart < '0' || *pStart > '9')
1163         return false;
1164 
1165     nNumber = 10 * nNumber + (*pStart - '0');
1166     if (pStart + 1 != pEnd || nNumber < 1970)
1167         return false;
1168 
1169     rDateTime.SetYear(nNumber);
1170     rDateTime.SetTime();
1171     return true;
1172 }
1173 
1174 /*
1175  * parseUNIX_isTime.
1176  */
parseUNIX_isTime(const sal_Char * pStart,const sal_Char * pEnd,sal_uInt16 nHour,DateTime & rDateTime)1177 bool FTPDirectoryParser::parseUNIX_isTime (
1178     const sal_Char *pStart,
1179     const sal_Char *pEnd,
1180     sal_uInt16      nHour,
1181     DateTime       &rDateTime)
1182 {
1183     if ((nHour     > 23 ) || (pStart + 3 != pEnd) ||
1184         (pStart[1] < '0') || (pStart[1] > '5')    ||
1185         (pStart[2] < '0') || (pStart[2] > '9')       )
1186         return false;
1187 
1188     sal_uInt16 nMin = 10 * (pStart[1] - '0') + (pStart[2] - '0');
1189 
1190     rDateTime.SetHour (nHour);
1191     rDateTime.SetMin (nMin);
1192     rDateTime.SetSec (0);
1193     rDateTime.SetNanoSec (0);
1194 
1195 //      Date aCurDate;
1196 //      if (rDateTime.GetMonth() > aCurDate.GetMonth())
1197 //          rDateTime.SetYear(aCurDate.GetYear() - 1);
1198 //      else
1199 //          rDateTime.SetYear(aCurDate.GetYear());
1200 //      return sal_True;
1201 
1202     TimeValue aTimeVal;
1203     osl_getSystemTime(&aTimeVal);
1204     oslDateTime aCurrDateTime;
1205     osl_getDateTimeFromTimeValue(&aTimeVal,&aCurrDateTime);
1206 
1207     if (rDateTime.GetMonth() > aCurrDateTime.Month)
1208         rDateTime.SetYear(aCurrDateTime.Year - 1);
1209     else
1210         rDateTime.SetYear(aCurrDateTime.Year);
1211     return true;
1212 }
1213 
1214 /*
1215  * setYear.
1216  *
1217  * Two-digit years are taken as within 50 years back and 49 years forward
1218  * (both ends inclusive) from the current year. The returned date is not
1219  * checked for validity of the given day in the given month and year.
1220  *
1221  */
setYear(DateTime & rDateTime,sal_uInt16 nYear)1222 void FTPDirectoryParser::setYear (
1223     DateTime &rDateTime, sal_uInt16 nYear)
1224 {
1225     if (nYear < 100)
1226     {
1227         TimeValue aTimeVal;
1228         osl_getSystemTime(&aTimeVal);
1229         oslDateTime aCurrDateTime;
1230         osl_getDateTimeFromTimeValue(&aTimeVal,&aCurrDateTime);
1231         sal_uInt16 nCurrentYear = aCurrDateTime.Year;
1232 //        sal_uInt16 nCurrentYear = Date().GetYear();
1233         sal_uInt16 nCurrentCentury = nCurrentYear / 100;
1234         nCurrentYear %= 100;
1235         if (nCurrentYear < 50)
1236             if (nYear <= nCurrentYear)
1237                 nYear += nCurrentCentury * 100;
1238             else if (nYear < nCurrentYear + 50)
1239                 nYear += nCurrentCentury * 100;
1240             else
1241                 nYear += (nCurrentCentury - 1) * 100;
1242         else
1243             if (nYear >= nCurrentYear)
1244                 nYear += nCurrentCentury * 100;
1245             else if (nYear >= nCurrentYear - 50)
1246                 nYear += nCurrentCentury * 100;
1247             else
1248                 nYear += (nCurrentCentury + 1) * 100;
1249     }
1250 
1251     rDateTime.SetYear(nYear);
1252 }
1253 
1254 /*
1255  * setPath.
1256  */
setPath(OUString & rPath,const sal_Char * value,sal_Int32 length)1257 bool FTPDirectoryParser::setPath (
1258     OUString &rPath, const sal_Char *value, sal_Int32 length)
1259 {
1260     if (value)
1261     {
1262         if (length < 0)
1263             length = rtl_str_getLength (value);
1264         rPath = OUString (value, length, RTL_TEXTENCODING_UTF8);
1265     }
1266     return (!!value);
1267 }
1268 
1269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1270