Merge branch '4642_fix_overflow'
[midnight-commander.git] / src / vfs / ftpfs / ftpfs_parse_ls.c
blob84e6177f1660c3cee5afa76e47802b69e137b850
1 /*
2 Virtual File System: FTP file system
4 Copyright (C) 2015-2025
5 The Free Software Foundation, Inc.
7 Written by: Andrew Borodin <aborodin@vmail.ru>, 2013
9 This file is part of the Midnight Commander.
11 The Midnight Commander is free software: you can redistribute it
12 and/or modify it under the terms of the GNU General Public License as
13 published by the Free Software Foundation, either version 3 of the License,
14 or (at your option) any later version.
16 The Midnight Commander is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <https://www.gnu.org/licenses/>.
25 /** \file
26 * \brief Source: Virtual File System: FTP file system
27 * \author Andrew Borodin
28 * \date 2015
30 * Parser of ftp long file list (reply to "LIST -la" command).
31 * Borrowed from lftp project (https://lftp.yar.ru).
32 * Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net)
35 #include <config.h>
37 #include <ctype.h> // isdigit()
38 #include <stdio.h> // sscanf()
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h> // mode_t
42 #include <time.h>
43 #include <unistd.h>
44 #include <sys/types.h>
46 #include "lib/global.h"
48 #include "lib/vfs/vfs.h"
49 #include "lib/vfs/utilvfs.h"
51 #include "ftpfs.h"
53 /*** global variables ****************************************************************************/
55 /*** file scope macro definitions ****************************************************************/
57 #define number_of_parsers 7
59 #define NO_SIZE ((off_t) (-1L))
60 #define NO_DATE ((time_t) (-1L))
62 #define FIRST_TOKEN strtok (line, " \t")
63 #define NEXT_TOKEN strtok (NULL, " \t")
64 #define FIRST_TOKEN_R strtok_r (line, " \t", &next)
65 #define NEXT_TOKEN_R strtok_r (NULL, " \t", &next)
67 #define ERR2 \
68 do \
69 { \
70 (*err)++; \
71 return FALSE; \
72 } \
73 while (FALSE)
75 /*** file scope type declarations ****************************************************************/
77 typedef enum
79 UNKNOWN = 0,
80 DIRECTORY,
81 SYMLINK,
82 NORMAL
83 } filetype;
85 typedef gboolean (*ftpfs_line_parser) (char *line, struct stat *s, char **filename, char **linkname,
86 int *err);
88 /*** forward declarations (file scope functions) *************************************************/
90 static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename,
91 char **linkname, int *err);
92 static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename,
93 char **linkname, int *err);
94 static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename,
95 char **linkname, int *err);
96 static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename,
97 char **linkname, int *err);
98 static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename,
99 char **linkname, int *err);
100 static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename,
101 char **linkname, int *err);
102 static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
103 char **linkname, int *err);
105 /*** file scope variables ************************************************************************/
107 static time_t rawnow;
108 static struct tm now;
110 static ftpfs_line_parser line_parsers[number_of_parsers] = {
111 ftpfs_parse_long_list_UNIX, ftpfs_parse_long_list_NT, ftpfs_parse_long_list_EPLF,
112 ftpfs_parse_long_list_MLSD, ftpfs_parse_long_list_AS400, ftpfs_parse_long_list_OS2,
113 ftpfs_parse_long_list_MacWebStar
116 /* --------------------------------------------------------------------------------------------- */
117 /*** file scope functions ************************************************************************/
118 /* --------------------------------------------------------------------------------------------- */
120 static inline uid_t
121 ftpfs_get_uid (const char *s)
123 uid_t u;
125 if (*s < '0' || *s > '9')
126 u = vfs_finduid (s);
127 else
128 u = (uid_t) atol (s);
130 return u;
133 /* --------------------------------------------------------------------------------------------- */
135 static inline gid_t
136 ftpfs_get_gid (const char *s)
138 gid_t g;
140 if (*s < '0' || *s > '9')
141 g = vfs_findgid (s);
142 else
143 g = (gid_t) atol (s);
145 return g;
148 /* --------------------------------------------------------------------------------------------- */
150 static void
151 ftpfs_init_time (void)
153 time (&rawnow);
154 now = *localtime (&rawnow);
157 /* --------------------------------------------------------------------------------------------- */
159 static int
160 guess_year (int month, int day, int hour, int minute)
162 int year;
164 (void) hour;
165 (void) minute;
167 year = now.tm_year + 1900;
169 if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
170 year--;
172 return year;
175 /* --------------------------------------------------------------------------------------------- */
177 static gboolean
178 parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
180 if (year_or_time[2] == ':')
182 if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
183 return FALSE;
185 *year = -1;
187 else
189 if (sscanf (year_or_time, "%d", year) != 1)
190 return FALSE;
192 *hour = *minute = 0;
195 return TRUE;
198 /* --------------------------------------------------------------------------------------------- */
200 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
201 than local timezone (mktime assumes the latter).
203 Contributed by Roger Beeman <beeman@cisco.com>, with the help of
204 Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */
205 static time_t
206 mktime_from_utc (const struct tm *t)
208 struct tm tc;
209 time_t tl, tb;
211 memcpy (&tc, t, sizeof (struct tm));
213 /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
214 * dependent errors. */
216 tc.tm_isdst = 0;
218 tl = mktime (&tc);
219 if (tl == -1)
220 return (-1);
222 tb = mktime (gmtime (&tl));
224 return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
227 /* --------------------------------------------------------------------------------------------- */
229 static time_t
230 ftpfs_convert_date (const char *s)
232 struct tm tm;
233 int year, month, day, hour, minute, second;
234 int skip = 0;
235 int n;
237 memset (&tm, 0, sizeof (tm));
239 n = sscanf (s, "%4d%n", &year, &skip);
241 /* try to workaround server's y2k bug *
242 * I hope in the next 300 years the y2k bug will be finally fixed :) */
243 if (n == 1 && year >= 1910 && year <= 1930)
245 n = sscanf (s, "%5d%n", &year, &skip);
246 year = year - 19100 + 2000;
249 if (n != 1)
250 return NO_DATE;
252 n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
254 if (n != 5)
255 return NO_DATE;
257 tm.tm_year = year - 1900;
258 tm.tm_mon = month - 1;
259 tm.tm_mday = day;
260 tm.tm_hour = hour;
261 tm.tm_min = minute;
262 tm.tm_sec = second;
264 return mktime_from_utc (&tm);
267 /* --------------------------------------------------------------------------------------------- */
270 -rwxr-xr-x 1 lav root 4771 Sep 12 1996 install-sh
271 -rw-r--r-- 1 lav root 1349 Feb 2 14:10 lftp.lsm
272 drwxr-xr-x 4 lav root 1024 Feb 22 15:32 lib
273 lrwxrwxrwx 1 lav root 33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
275 NOTE: group may be missing.
278 static gboolean
279 parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
281 char *next = NULL;
282 char *t;
283 mode_t type, mode = 0;
284 char *group_or_size;
285 struct tm date;
286 const char *day_of_month;
287 gboolean year_anomaly = FALSE;
288 char *name;
290 // parse perms
291 t = FIRST_TOKEN_R;
292 if (t == NULL)
293 return FALSE;
295 if (!vfs_parse_filetype (t, NULL, &type))
296 return FALSE;
298 if (vfs_parse_fileperms (t + 1, NULL, &mode))
299 mode |= type;
301 s->st_mode = mode;
303 // link count
304 t = NEXT_TOKEN_R;
305 if (t == NULL)
306 return FALSE;
307 s->st_nlink = atol (t);
309 // user
310 t = NEXT_TOKEN_R;
311 if (t == NULL)
312 return FALSE;
314 s->st_uid = ftpfs_get_uid (t);
316 // group or size
317 group_or_size = NEXT_TOKEN_R;
319 // size or month
320 t = NEXT_TOKEN_R;
321 if (t == NULL)
322 return FALSE;
323 if (isdigit ((unsigned char) *t))
325 // it's size, so the previous was group:
326 long long size;
327 int n;
329 s->st_gid = ftpfs_get_gid (group_or_size);
331 if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0')
332 s->st_size = (off_t) size;
333 t = NEXT_TOKEN_R;
334 if (t == NULL)
335 return FALSE;
337 else
339 // it was month, so the previous was size:
340 long long size;
341 int n;
343 if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
344 s->st_size = (off_t) size;
347 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
348 s->st_blksize = 512;
349 #endif
350 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
351 s->st_blocks = (s->st_size + 511) / 512;
352 #endif
354 memset (&date, 0, sizeof (date));
356 if (!vfs_parse_month (t, &date))
357 date.tm_mon = 0;
359 day_of_month = NEXT_TOKEN_R;
360 if (day_of_month == NULL)
361 return FALSE;
362 date.tm_mday = atoi (day_of_month);
364 // time or year
365 t = NEXT_TOKEN_R;
366 if (t == NULL)
367 return FALSE;
368 date.tm_isdst = -1;
369 date.tm_hour = date.tm_min = 0;
370 date.tm_sec = 30;
372 if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2)
373 date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
374 else
376 if (day_of_month + strlen (day_of_month) + 1 == t)
377 year_anomaly = TRUE;
378 date.tm_year = atoi (t) - 1900;
379 /* We don't know the hour. Set it to something other than 0, or
380 * DST -1 will end up changing the date. */
381 date.tm_hour = 12;
382 date.tm_min = 0;
383 date.tm_sec = 0;
386 s->st_mtime = mktime (&date);
387 // Use resulting time value
388 s->st_atime = s->st_ctime = s->st_mtime;
390 name = strtok_r (NULL, "", &next);
391 if (name == NULL)
392 return FALSE;
394 // there are ls which output extra space after year
395 if (year_anomaly && *name == ' ')
396 name++;
398 if (!S_ISLNK (s->st_mode))
399 *linkname = NULL;
400 else
402 char *arrow;
404 for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
405 if (arrow != name && arrow[4] != '\0')
407 *arrow = '\0';
408 *linkname = g_strdup (arrow + 4);
409 break;
413 *filename = g_strdup (name);
415 return TRUE;
418 /* --------------------------------------------------------------------------------------------- */
420 static gboolean
421 ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
423 int tmp;
424 gboolean ret;
426 if (sscanf (line, "total %d", &tmp) == 1)
427 return FALSE;
429 if (strncasecmp (line, "Status of ", 10) == 0)
430 return FALSE; // STAT output
432 ret = parse_ls_line (line, s, filename, linkname);
433 if (!ret)
434 (*err)++;
436 return ret;
439 /* --------------------------------------------------------------------------------------------- */
442 07-13-98 09:06PM <DIR> aix
443 07-13-98 09:06PM <DIR> hpux
444 07-13-98 09:06PM <DIR> linux
445 07-13-98 09:06PM <DIR> ncr
446 07-13-98 09:06PM <DIR> solaris
447 03-18-98 06:01AM 2109440 nlxb318e.tar
448 07-02-98 11:17AM 13844 Whatsnew.txt
451 static gboolean
452 ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
454 char *t;
455 int month, day, year, hour, minute;
456 char am;
457 struct tm tms;
458 long long size;
460 t = FIRST_TOKEN;
461 if (t == NULL)
462 ERR2;
463 if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
464 ERR2;
465 if (year >= 70)
466 year += 1900;
467 else
468 year += 2000;
470 t = NEXT_TOKEN;
471 if (t == NULL)
472 ERR2;
473 am = 'A'; // AM/PM is optional
474 if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2)
475 ERR2;
477 t = NEXT_TOKEN;
478 if (t == NULL)
479 ERR2;
481 if (am == 'P') // PM - after noon
483 hour += 12;
484 if (hour == 24)
485 hour = 0;
488 tms.tm_sec = 30; // seconds after the minute [0, 61]
489 tms.tm_min = minute; // minutes after the hour [0, 59]
490 tms.tm_hour = hour; // hour since midnight [0, 23]
491 tms.tm_mday = day; // day of the month [1, 31]
492 tms.tm_mon = month - 1; // months since January [0, 11]
493 tms.tm_year = year - 1900; // years since 1900
494 tms.tm_isdst = -1;
496 s->st_mtime = mktime (&tms);
497 // Use resulting time value
498 s->st_atime = s->st_ctime = s->st_mtime;
500 if (strcmp (t, "<DIR>") == 0)
501 s->st_mode = S_IFDIR;
502 else
504 s->st_mode = S_IFREG;
505 if (sscanf (t, "%lld", &size) != 1)
506 ERR2;
507 s->st_size = (off_t) size;
510 t = strtok (NULL, "");
511 if (t == NULL)
512 ERR2;
513 while (*t == ' ')
514 t++;
515 if (*t == '\0')
516 ERR2;
518 *filename = g_strdup (t);
519 *linkname = NULL;
521 return TRUE;
524 /* --------------------------------------------------------------------------------------------- */
527 +i774.71425,m951188401,/, users
528 +i774.49602,m917883130,r,s79126, jgr_www2.exe
530 starts with +
531 comma separated
532 first character of field is type:
533 i - ?
534 m - modification time
535 / - means directory
536 r - means plain file
537 s - size
538 up - permissions in octal
539 \t - file name follows.
542 static gboolean
543 ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
545 size_t len;
546 const char *b;
547 const char *name = NULL;
548 size_t name_len = 0;
549 off_t size = NO_SIZE;
550 time_t date = NO_DATE;
551 long date_l;
552 long long size_ll;
553 gboolean dir = FALSE;
554 gboolean type_known = FALSE;
555 int perms = -1;
556 const char *scan;
557 ssize_t scan_len;
559 len = strlen (line);
560 b = line;
562 if (len < 2 || b[0] != '+')
563 ERR2;
565 scan = b + 1;
566 scan_len = len - 1;
568 while (scan != NULL && scan_len > 0)
570 const char *comma;
572 switch (*scan)
574 case '\t': // the rest is file name
575 name = scan + 1;
576 name_len = scan_len - 1;
577 scan = NULL;
578 break;
579 case 's':
580 if (sscanf (scan + 1, "%lld", &size_ll) != 1)
581 break;
582 size = size_ll;
583 break;
584 case 'm':
585 if (sscanf (scan + 1, "%ld", &date_l) != 1)
586 break;
587 date = date_l;
588 break;
589 case '/':
590 dir = TRUE;
591 type_known = TRUE;
592 break;
593 case 'r':
594 dir = FALSE;
595 type_known = TRUE;
596 break;
597 case 'i':
598 break;
599 case 'u':
600 if (scan[1] == 'p') // permissions
601 if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1)
602 perms = -1;
603 break;
604 default:
605 name = NULL;
606 scan = NULL;
607 break;
609 if (scan == NULL || scan_len == 0)
610 break;
612 comma = (const char *) memchr (scan, ',', scan_len);
613 if (comma == NULL)
614 break;
616 scan_len -= comma + 1 - scan;
617 scan = comma + 1;
620 if (name == NULL || !type_known)
621 ERR2;
623 *filename = g_strndup (name, name_len);
624 *linkname = NULL;
626 if (size != NO_SIZE)
627 s->st_size = size;
628 if (date != NO_DATE)
630 s->st_mtime = date;
631 // Use resulting time value
632 s->st_atime = s->st_ctime = s->st_mtime;
634 if (type_known)
635 s->st_mode = dir ? S_IFDIR : S_IFREG;
636 if (perms != -1)
637 s->st_mode |= perms; // FIXME
639 return TRUE;
642 /* --------------------------------------------------------------------------------------------- */
644 Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
645 Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
646 Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
647 Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
648 Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
649 modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
650 modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
651 modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
654 static gboolean
655 ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
657 const char *name = NULL;
658 off_t size = NO_SIZE;
659 time_t date = NO_DATE;
660 const char *owner = NULL;
661 const char *group = NULL;
662 filetype type = UNKNOWN;
663 int perms = -1;
664 char *space;
665 char *tok;
667 space = strstr (line, "; ");
668 if (space != NULL)
670 name = space + 2;
671 *space = '\0';
673 else
675 // NcFTPd does not put a semicolon after last fact, workaround it
676 space = strchr (line, ' ');
677 if (space == NULL)
678 ERR2;
679 name = space + 1;
680 *space = '\0';
683 for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
685 if (strcasecmp (tok, "Type=cdir") == 0 || strcasecmp (tok, "Type=pdir") == 0
686 || strcasecmp (tok, "Type=dir") == 0)
688 type = DIRECTORY;
689 continue;
691 if (strcasecmp (tok, "Type=file") == 0)
693 type = NORMAL;
694 continue;
696 if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
698 type = SYMLINK;
699 continue;
701 if (strncasecmp (tok, "Modify=", 7) == 0)
703 date = ftpfs_convert_date (tok + 7);
704 continue;
706 if (strncasecmp (tok, "Size=", 5) == 0)
708 long long size_ll;
710 if (sscanf (tok + 5, "%lld", &size_ll) == 1)
711 size = size_ll;
712 continue;
714 if (strncasecmp (tok, "Perm=", 5) == 0)
716 perms = 0;
717 for (tok += 5; *tok != '\0'; tok++)
719 switch (g_ascii_tolower (*tok))
721 case 'e':
722 perms |= 0111;
723 break;
724 case 'l':
725 perms |= 0444;
726 break;
727 case 'r':
728 perms |= 0444;
729 break;
730 case 'c':
731 perms |= 0200;
732 break;
733 case 'w':
734 perms |= 0200;
735 break;
736 default:
737 break;
740 continue;
742 if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
744 if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
745 perms = -1;
746 continue;
748 if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
750 owner = tok + 11;
751 continue;
753 if (strncasecmp (tok, "UNIX.group=", 11) == 0)
755 group = tok + 11;
756 continue;
758 if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
760 if (owner == NULL)
761 owner = tok + 9;
762 continue;
764 if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
766 if (group == NULL)
767 group = tok + 9;
768 continue;
771 if (name == NULL || name[0] == '\0' || type == UNKNOWN)
772 ERR2;
774 *filename = g_strdup (name);
775 *linkname = NULL;
777 if (size != NO_SIZE)
778 s->st_size = size;
779 if (date != NO_DATE)
781 s->st_mtime = date;
782 // Use resulting time value
783 s->st_atime = s->st_ctime = s->st_mtime;
785 switch (type)
787 case DIRECTORY:
788 s->st_mode = S_IFDIR;
789 break;
790 case SYMLINK:
791 s->st_mode = S_IFLNK;
792 break;
793 case NORMAL:
794 s->st_mode = S_IFREG;
795 break;
796 default:
797 g_assert_not_reached ();
799 if (perms != -1)
800 s->st_mode |= perms; // FIXME
801 if (owner != NULL)
802 s->st_uid = ftpfs_get_uid (owner);
803 if (group != NULL)
804 s->st_gid = ftpfs_get_gid (group);
806 return TRUE;
809 /* --------------------------------------------------------------------------------------------- */
812 ASUSER 8192 04/26/05 13:54:16 *DIR dir/
813 ASUSER 8192 04/26/05 13:57:34 *DIR dir1/
814 ASUSER 365255 02/28/01 15:41:40 *STMF readme.txt
815 ASUSER 8489625 03/18/03 09:37:00 *STMF saved.zip
816 ASUSER 365255 02/28/01 15:41:40 *STMF unist.old
819 static gboolean
820 ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
822 char *t;
823 char *user;
824 long long size;
825 int month, day, year, hour, minute, second;
826 struct tm tms;
827 time_t mtime;
828 mode_t type;
829 char *slash;
831 t = FIRST_TOKEN;
832 if (t == NULL)
833 ERR2;
834 user = t;
836 t = NEXT_TOKEN;
837 if (t == NULL)
838 ERR2;
839 if (sscanf (t, "%lld", &size) != 1)
840 ERR2;
842 t = NEXT_TOKEN;
843 if (t == NULL)
844 ERR2;
845 if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3)
846 ERR2;
847 if (year >= 70)
848 year += 1900;
849 else
850 year += 2000;
852 t = NEXT_TOKEN;
853 if (t == NULL)
854 ERR2;
855 if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
856 ERR2;
858 t = NEXT_TOKEN;
859 if (t == NULL)
860 ERR2;
862 tms.tm_sec = second; // seconds after the minute [0, 61]
863 tms.tm_min = minute; // minutes after the hour [0, 59]
864 tms.tm_hour = hour; // hour since midnight [0, 23]
865 tms.tm_mday = day; // day of the month [1, 31]
866 tms.tm_mon = month - 1; // months since January [0, 11]
867 tms.tm_year = year - 1900; // years since 1900
868 tms.tm_isdst = -1;
869 mtime = mktime (&tms);
871 t = NEXT_TOKEN;
872 if (t == NULL)
873 ERR2;
874 if (strcmp (t, "*DIR") == 0)
875 type = S_IFDIR;
876 else
877 type = S_IFREG;
879 t = strtok (NULL, "");
880 if (t == NULL)
881 ERR2;
882 while (*t == ' ')
883 t++;
884 if (*t == '\0')
885 ERR2;
887 *linkname = NULL;
889 slash = strchr (t, '/');
890 if (slash != NULL)
892 if (slash == t)
893 return FALSE;
895 *slash = '\0';
896 type = S_IFDIR;
897 if (slash[1] != '\0')
899 *filename = g_strdup (t);
900 s->st_mode = type; // FIXME
901 return TRUE;
905 *filename = g_strdup (t);
906 s->st_mode = type;
907 s->st_size = (off_t) size;
908 s->st_mtime = mtime;
909 // Use resulting time value
910 s->st_atime = s->st_ctime = s->st_mtime;
911 s->st_uid = ftpfs_get_uid (user);
913 return TRUE;
916 /* --------------------------------------------------------------------------------------------- */
919 0 DIR 06-27-96 11:57 PROTOCOL
920 169 11-29-94 09:20 SYSLEVEL.MPT
923 static gboolean
924 ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
926 char *t;
927 long long size;
928 int month, day, year, hour, minute;
929 struct tm tms;
931 t = FIRST_TOKEN;
932 if (t == NULL)
933 ERR2;
935 if (sscanf (t, "%lld", &size) != 1)
936 ERR2;
937 s->st_size = (off_t) size;
939 t = NEXT_TOKEN;
940 if (t == NULL)
941 ERR2;
942 s->st_mode = S_IFREG;
943 if (strcmp (t, "DIR") == 0)
945 s->st_mode = S_IFDIR;
946 t = NEXT_TOKEN;
948 if (t == NULL)
949 ERR2;
952 if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
953 ERR2;
954 if (year >= 70)
955 year += 1900;
956 else
957 year += 2000;
959 t = NEXT_TOKEN;
960 if (t == NULL)
961 ERR2;
962 if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
963 ERR2;
965 tms.tm_sec = 30; // seconds after the minute [0, 61]
966 tms.tm_min = minute; // minutes after the hour [0, 59]
967 tms.tm_hour = hour; // hour since midnight [0, 23]
968 tms.tm_mday = day; // day of the month [1, 31]
969 tms.tm_mon = month - 1; // months since January [0, 11]
970 tms.tm_year = year - 1900; // years since 1900
971 tms.tm_isdst = -1;
972 s->st_mtime = mktime (&tms);
973 // Use resulting time value
974 s->st_atime = s->st_ctime = s->st_mtime;
976 t = strtok (NULL, "");
977 if (t == NULL)
978 ERR2;
979 while (*t == ' ')
980 t++;
981 if (*t == '\0')
982 ERR2;
983 *filename = g_strdup (t);
984 *linkname = NULL;
986 return TRUE;
989 /* --------------------------------------------------------------------------------------------- */
991 static gboolean
992 ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename, char **linkname,
993 int *err)
995 char *t;
996 mode_t type, mode;
997 struct tm date;
998 const char *day_of_month;
999 char *name;
1001 t = FIRST_TOKEN;
1002 if (t == NULL)
1003 ERR2;
1005 if (!vfs_parse_filetype (t, NULL, &type))
1006 ERR2;
1008 s->st_mode = type;
1010 if (!vfs_parse_fileperms (t + 1, NULL, &mode))
1011 ERR2;
1012 // permissions are meaningless here
1014 // "folder" or 0
1015 t = NEXT_TOKEN;
1016 if (t == NULL)
1017 ERR2;
1019 if (strcmp (t, "folder") != 0)
1021 long long size;
1023 // size?
1024 t = NEXT_TOKEN;
1025 if (t == NULL)
1026 ERR2;
1027 // size
1028 t = NEXT_TOKEN;
1029 if (t == NULL)
1030 ERR2;
1031 if (!isdigit ((unsigned char) *t))
1032 ERR2;
1034 if (sscanf (t, "%lld", &size) == 1)
1035 s->st_size = (off_t) size;
1037 else
1039 // ??
1040 t = NEXT_TOKEN;
1041 if (t == NULL)
1042 ERR2;
1045 // month
1046 t = NEXT_TOKEN;
1047 if (t == NULL)
1048 ERR2;
1050 memset (&date, 0, sizeof (date));
1052 if (!vfs_parse_month (t, &date))
1053 ERR2;
1055 day_of_month = NEXT_TOKEN;
1056 if (day_of_month == NULL)
1057 ERR2;
1059 date.tm_mday = atoi (day_of_month);
1061 // time or year
1062 t = NEXT_TOKEN;
1063 if (t == NULL)
1064 ERR2;
1065 if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min))
1066 ERR2;
1068 date.tm_isdst = -1;
1069 date.tm_sec = 30;
1070 if (date.tm_year == -1)
1071 date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
1072 else
1073 date.tm_hour = 12;
1075 s->st_mtime = mktime (&date);
1076 // Use resulting time value
1077 s->st_atime = s->st_ctime = s->st_mtime;
1079 name = strtok (NULL, "");
1080 if (name == NULL)
1081 ERR2;
1083 // no symlinks on Mac, but anyway
1084 if (!S_ISLNK (s->st_mode))
1085 *linkname = NULL;
1086 else
1088 char *arrow;
1090 for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
1091 if (arrow != name && arrow[4] != '\0')
1093 *arrow = '\0';
1094 *linkname = g_strdup (arrow + 4);
1095 break;
1099 *filename = g_strdup (name);
1101 return TRUE;
1104 /* --------------------------------------------------------------------------------------------- */
1105 /*** public functions ****************************************************************************/
1106 /* --------------------------------------------------------------------------------------------- */
1108 GSList *
1109 ftpfs_parse_long_list (struct vfs_class *me, struct vfs_s_inode *dir, GSList *buf, int *err_ret)
1111 int err[number_of_parsers];
1112 GSList *set[number_of_parsers]; // arrays of struct vfs_s_entry
1113 size_t i;
1114 GSList *bufp;
1115 ftpfs_line_parser guessed_parser = NULL;
1116 GSList **the_set = NULL;
1117 int *the_err = NULL;
1118 int *best_err1 = &err[0];
1119 int *best_err2 = &err[1];
1121 ftpfs_init_time ();
1123 if (err_ret != NULL)
1124 *err_ret = 0;
1126 memset (&err, 0, sizeof (err));
1127 memset (&set, 0, sizeof (set));
1129 for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
1131 char *b = (char *) bufp->data;
1132 size_t blen;
1134 blen = strlen (b);
1136 if (b[blen - 1] == '\r')
1138 b[blen - 1] = '\0';
1139 blen--;
1142 if (blen == 0)
1143 continue;
1145 if (guessed_parser == NULL)
1147 for (i = 0; i < number_of_parsers; i++)
1149 struct vfs_s_entry *info;
1150 gboolean ok;
1151 char *tmp_line;
1152 int nlink;
1154 // parser can clobber the line - work on a copy
1155 tmp_line = g_strndup (b, blen);
1157 info = vfs_s_generate_entry (me, NULL, dir, 0);
1158 nlink = info->ino->st.st_nlink;
1159 ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name,
1160 &info->ino->linkname, &err[i]);
1161 if (ok && strchr (info->name, '/') == NULL)
1163 info->ino->st.st_nlink = nlink; // Ouch, we need to preserve our counts :-(
1164 set[i] = g_slist_prepend (set[i], info);
1166 else
1167 vfs_s_free_entry (me, info);
1169 g_free (tmp_line);
1171 if (*best_err1 > err[i])
1172 best_err1 = &err[i];
1173 if (*best_err2 > err[i] && best_err1 != &err[i])
1174 best_err2 = &err[i];
1176 if (*best_err1 > 16)
1177 goto leave; // too many errors with best parser
1180 if (*best_err2 > (*best_err1 + 1) * 16)
1182 i = (size_t) (best_err1 - err);
1183 guessed_parser = line_parsers[i];
1184 the_set = &set[i];
1185 the_err = &err[i];
1188 else
1190 struct vfs_s_entry *info;
1191 gboolean ok;
1192 char *tmp_line;
1193 int nlink;
1195 // parser can clobber the line - work on a copy
1196 tmp_line = g_strndup (b, blen);
1198 info = vfs_s_generate_entry (me, NULL, dir, 0);
1199 nlink = info->ino->st.st_nlink;
1200 ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname,
1201 the_err);
1202 if (ok && strchr (info->name, '/') == NULL)
1204 info->ino->st.st_nlink = nlink; // Ouch, we need to preserve our counts :-(
1205 *the_set = g_slist_prepend (*the_set, info);
1207 else
1208 vfs_s_free_entry (me, info);
1210 g_free (tmp_line);
1214 if (the_set == NULL)
1216 i = best_err1 - err;
1217 the_set = &set[i];
1218 the_err = &err[i];
1221 leave:
1222 for (i = 0; i < number_of_parsers; i++)
1223 if (&set[i] != the_set)
1225 for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
1226 vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
1228 g_slist_free (set[i]);
1231 if (err_ret != NULL && the_err != NULL)
1232 *err_ret = *the_err;
1234 return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
1237 /* --------------------------------------------------------------------------------------------- */