root/src/vfs/ftpfs/ftpfs_parse_ls.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. ftpfs_get_uid
  2. ftpfs_get_gid
  3. ftpfs_init_time
  4. guess_year
  5. parse_year_or_time
  6. mktime_from_utc
  7. ftpfs_convert_date
  8. parse_ls_line
  9. ftpfs_parse_long_list_UNIX
  10. ftpfs_parse_long_list_NT
  11. ftpfs_parse_long_list_EPLF
  12. ftpfs_parse_long_list_MLSD
  13. ftpfs_parse_long_list_AS400
  14. ftpfs_parse_long_list_OS2
  15. ftpfs_parse_long_list_MacWebStar
  16. ftpfs_parse_long_list

   1 /*
   2    Virtual File System: FTP file system
   3 
   4    Copyright (C) 2015-2024
   5    The Free Software Foundation, Inc.
   6 
   7    Written by: Andrew Borodin <aborodin@vmail.ru>, 2013
   8 
   9    This file is part of the Midnight Commander.
  10 
  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.
  15 
  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.
  20 
  21    You should have received a copy of the GNU General Public License
  22    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  23  */
  24 
  25 /** \file
  26  *  \brief Source: Virtual File System: FTP file system
  27  *  \author Andrew Borodin
  28  *  \date 2015
  29  *
  30  *  Parser of ftp long file list (reply to "LIST -la" command).
  31  *  Borrowed from lftp project (http://http://lftp.yar.ru/).
  32  *  Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net)
  33  */
  34 
  35 #include <config.h>
  36 
  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>
  45 
  46 #include "lib/global.h"
  47 
  48 #include "lib/vfs/vfs.h"
  49 #include "lib/vfs/utilvfs.h"
  50 
  51 #include "ftpfs.h"
  52 
  53 /*** global variables ****************************************************************************/
  54 
  55 /*** file scope macro definitions ****************************************************************/
  56 
  57 #define number_of_parsers 7
  58 
  59 #define NO_SIZE     ((off_t) (-1L))
  60 #define NO_DATE     ((time_t) (-1L))
  61 
  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)
  66 
  67 #define ERR2 do { (*err)++; return FALSE; } while (FALSE)
  68 
  69 /*** file scope type declarations ****************************************************************/
  70 
  71 typedef enum
  72 {
  73     UNKNOWN = 0,
  74     DIRECTORY,
  75     SYMLINK,
  76     NORMAL
  77 } filetype;
  78 
  79 typedef gboolean (*ftpfs_line_parser) (char *line, struct stat * s, char **filename,
  80                                        char **linkname, int *err);
  81 
  82 /*** forward declarations (file scope functions) *************************************************/
  83 
  84 static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename,
  85                                             char **linkname, int *err);
  86 static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename,
  87                                           char **linkname, int *err);
  88 static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename,
  89                                             char **linkname, int *err);
  90 static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename,
  91                                             char **linkname, int *err);
  92 static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename,
  93                                              char **linkname, int *err);
  94 static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename,
  95                                            char **linkname, int *err);
  96 static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
  97                                                   char **linkname, int *err);
  98 
  99 /*** file scope variables ************************************************************************/
 100 
 101 static time_t rawnow;
 102 static struct tm now;
 103 
 104 static ftpfs_line_parser line_parsers[number_of_parsers] = {
 105     ftpfs_parse_long_list_UNIX,
 106     ftpfs_parse_long_list_NT,
 107     ftpfs_parse_long_list_EPLF,
 108     ftpfs_parse_long_list_MLSD,
 109     ftpfs_parse_long_list_AS400,
 110     ftpfs_parse_long_list_OS2,
 111     ftpfs_parse_long_list_MacWebStar
 112 };
 113 
 114 /* --------------------------------------------------------------------------------------------- */
 115 /*** file scope functions ************************************************************************/
 116 /* --------------------------------------------------------------------------------------------- */
 117 
 118 static inline uid_t
 119 ftpfs_get_uid (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 120 {
 121     uid_t u;
 122 
 123     if (*s < '0' || *s > '9')
 124         u = vfs_finduid (s);
 125     else
 126         u = (uid_t) atol (s);
 127 
 128     return u;
 129 }
 130 
 131 /* --------------------------------------------------------------------------------------------- */
 132 
 133 static inline gid_t
 134 ftpfs_get_gid (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 135 {
 136     gid_t g;
 137 
 138     if (*s < '0' || *s > '9')
 139         g = vfs_findgid (s);
 140     else
 141         g = (gid_t) atol (s);
 142 
 143     return g;
 144 }
 145 
 146 /* --------------------------------------------------------------------------------------------- */
 147 
 148 static void
 149 ftpfs_init_time (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 150 {
 151     time (&rawnow);
 152     now = *localtime (&rawnow);
 153 }
 154 
 155 /* --------------------------------------------------------------------------------------------- */
 156 
 157 static int
 158 guess_year (int month, int day, int hour, int minute)
     /* [previous][next][first][last][top][bottom][index][help]  */
 159 {
 160     int year;
 161 
 162     (void) hour;
 163     (void) minute;
 164 
 165     year = now.tm_year + 1900;
 166 
 167     if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
 168         year--;
 169 
 170     return year;
 171 }
 172 
 173 /* --------------------------------------------------------------------------------------------- */
 174 
 175 static gboolean
 176 parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
     /* [previous][next][first][last][top][bottom][index][help]  */
 177 {
 178     if (year_or_time[2] == ':')
 179     {
 180         if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
 181             return FALSE;
 182 
 183         *year = -1;
 184     }
 185     else
 186     {
 187         if (sscanf (year_or_time, "%d", year) != 1)
 188             return FALSE;
 189 
 190         *hour = *minute = 0;
 191     }
 192 
 193     return TRUE;
 194 }
 195 
 196 /* --------------------------------------------------------------------------------------------- */
 197 
 198 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
 199    than local timezone (mktime assumes the latter).
 200 
 201    Contributed by Roger Beeman <beeman@cisco.com>, with the help of
 202    Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO.  */
 203 static time_t
 204 mktime_from_utc (const struct tm *t)
     /* [previous][next][first][last][top][bottom][index][help]  */
 205 {
 206     struct tm tc;
 207     time_t tl, tb;
 208 
 209     memcpy (&tc, t, sizeof (struct tm));
 210 
 211     /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
 212      * dependent errors. */
 213 
 214     tc.tm_isdst = 0;
 215 
 216     tl = mktime (&tc);
 217     if (tl == -1)
 218         return (-1);
 219 
 220     tb = mktime (gmtime (&tl));
 221 
 222     return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
 223 }
 224 
 225 /* --------------------------------------------------------------------------------------------- */
 226 
 227 static time_t
 228 ftpfs_convert_date (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 229 {
 230     struct tm tm;
 231     int year, month, day, hour, minute, second;
 232     int skip = 0;
 233     int n;
 234 
 235     memset (&tm, 0, sizeof (tm));
 236 
 237     n = sscanf (s, "%4d%n", &year, &skip);
 238 
 239     /* try to workaround server's y2k bug *
 240      * I hope in the next 300 years the y2k bug will be finally fixed :) */
 241     if (n == 1 && year >= 1910 && year <= 1930)
 242     {
 243         n = sscanf (s, "%5d%n", &year, &skip);
 244         year = year - 19100 + 2000;
 245     }
 246 
 247     if (n != 1)
 248         return NO_DATE;
 249 
 250     n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
 251 
 252     if (n != 5)
 253         return NO_DATE;
 254 
 255     tm.tm_year = year - 1900;
 256     tm.tm_mon = month - 1;
 257     tm.tm_mday = day;
 258     tm.tm_hour = hour;
 259     tm.tm_min = minute;
 260     tm.tm_sec = second;
 261 
 262     return mktime_from_utc (&tm);
 263 }
 264 
 265 /* --------------------------------------------------------------------------------------------- */
 266 
 267 /*
 268    -rwxr-xr-x   1 lav      root         4771 Sep 12  1996 install-sh
 269    -rw-r--r--   1 lav      root         1349 Feb  2 14:10 lftp.lsm
 270    drwxr-xr-x   4 lav      root         1024 Feb 22 15:32 lib
 271    lrwxrwxrwx   1 lav      root           33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
 272 
 273    NOTE: group may be missing.
 274  */
 275 
 276 static gboolean
 277 parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
     /* [previous][next][first][last][top][bottom][index][help]  */
 278 {
 279     char *next = NULL;
 280     char *t;
 281     mode_t type, mode = 0;
 282     char *group_or_size;
 283     struct tm date;
 284     const char *day_of_month;
 285     gboolean year_anomaly = FALSE;
 286     char *name;
 287 
 288     /* parse perms */
 289     t = FIRST_TOKEN_R;
 290     if (t == NULL)
 291         return FALSE;
 292 
 293     if (!vfs_parse_filetype (t, NULL, &type))
 294         return FALSE;
 295 
 296     if (vfs_parse_fileperms (t + 1, NULL, &mode))
 297         mode |= type;
 298 
 299     s->st_mode = mode;
 300 
 301     /* link count */
 302     t = NEXT_TOKEN_R;
 303     if (t == NULL)
 304         return FALSE;
 305     s->st_nlink = atol (t);
 306 
 307     /* user */
 308     t = NEXT_TOKEN_R;
 309     if (t == NULL)
 310         return FALSE;
 311 
 312     s->st_uid = ftpfs_get_uid (t);
 313 
 314     /* group or size */
 315     group_or_size = NEXT_TOKEN_R;
 316 
 317     /* size or month */
 318     t = NEXT_TOKEN_R;
 319     if (t == NULL)
 320         return FALSE;
 321     if (isdigit ((unsigned char) *t))
 322     {
 323         /* it's size, so the previous was group: */
 324         long long size;
 325         int n;
 326 
 327         s->st_gid = ftpfs_get_gid (group_or_size);
 328 
 329         if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0')
 330             s->st_size = (off_t) size;
 331         t = NEXT_TOKEN_R;
 332         if (t == NULL)
 333             return FALSE;
 334     }
 335     else
 336     {
 337         /*  it was month, so the previous was size: */
 338         long long size;
 339         int n;
 340 
 341         if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
 342             s->st_size = (off_t) size;
 343     }
 344 
 345 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
 346     s->st_blksize = 512;
 347 #endif
 348 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
 349     s->st_blocks = (s->st_size + 511) / 512;
 350 #endif
 351 
 352     memset (&date, 0, sizeof (date));
 353 
 354     if (!vfs_parse_month (t, &date))
 355         date.tm_mon = 0;
 356 
 357     day_of_month = NEXT_TOKEN_R;
 358     if (day_of_month == NULL)
 359         return FALSE;
 360     date.tm_mday = atoi (day_of_month);
 361 
 362     /* time or year */
 363     t = NEXT_TOKEN_R;
 364     if (t == NULL)
 365         return FALSE;
 366     date.tm_isdst = -1;
 367     date.tm_hour = date.tm_min = 0;
 368     date.tm_sec = 30;
 369 
 370     if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2)
 371         date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
 372     else
 373     {
 374         if (day_of_month + strlen (day_of_month) + 1 == t)
 375             year_anomaly = TRUE;
 376         date.tm_year = atoi (t) - 1900;
 377         /* We don't know the hour.  Set it to something other than 0, or
 378          * DST -1 will end up changing the date. */
 379         date.tm_hour = 12;
 380         date.tm_min = 0;
 381         date.tm_sec = 0;
 382     }
 383 
 384     s->st_mtime = mktime (&date);
 385     /* Use resulting time value */
 386     s->st_atime = s->st_ctime = s->st_mtime;
 387 
 388     name = strtok_r (NULL, "", &next);
 389     if (name == NULL)
 390         return FALSE;
 391 
 392     /* there are ls which output extra space after year. */
 393     if (year_anomaly && *name == ' ')
 394         name++;
 395 
 396     if (!S_ISLNK (s->st_mode))
 397         *linkname = NULL;
 398     else
 399     {
 400         char *arrow;
 401 
 402         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
 403             if (arrow != name && arrow[4] != '\0')
 404             {
 405                 *arrow = '\0';
 406                 *linkname = g_strdup (arrow + 4);
 407                 break;
 408             }
 409     }
 410 
 411     *filename = g_strdup (name);
 412 
 413     return TRUE;
 414 }
 415 
 416 /* --------------------------------------------------------------------------------------------- */
 417 
 418 static gboolean
 419 ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 420 {
 421     int tmp;
 422     gboolean ret;
 423 
 424     if (sscanf (line, "total %d", &tmp) == 1)
 425         return FALSE;
 426 
 427     if (strncasecmp (line, "Status of ", 10) == 0)
 428         return FALSE;           /* STAT output. */
 429 
 430     ret = parse_ls_line (line, s, filename, linkname);
 431     if (!ret)
 432         (*err)++;
 433 
 434     return ret;
 435 }
 436 
 437 /* --------------------------------------------------------------------------------------------- */
 438 
 439 /*
 440    07-13-98  09:06PM       <DIR>          aix
 441    07-13-98  09:06PM       <DIR>          hpux
 442    07-13-98  09:06PM       <DIR>          linux
 443    07-13-98  09:06PM       <DIR>          ncr
 444    07-13-98  09:06PM       <DIR>          solaris
 445    03-18-98  06:01AM              2109440 nlxb318e.tar
 446    07-02-98  11:17AM                13844 Whatsnew.txt
 447  */
 448 
 449 static gboolean
 450 ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     char *t;
 453     int month, day, year, hour, minute;
 454     char am;
 455     struct tm tms;
 456     long long size;
 457 
 458     t = FIRST_TOKEN;
 459     if (t == NULL)
 460         ERR2;
 461     if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
 462         ERR2;
 463     if (year >= 70)
 464         year += 1900;
 465     else
 466         year += 2000;
 467 
 468     t = NEXT_TOKEN;
 469     if (t == NULL)
 470         ERR2;
 471     am = 'A';                   /* AM/PM is optional */
 472     if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2)
 473         ERR2;
 474 
 475     t = NEXT_TOKEN;
 476     if (t == NULL)
 477         ERR2;
 478 
 479     if (am == 'P')              /* PM - after noon */
 480     {
 481         hour += 12;
 482         if (hour == 24)
 483             hour = 0;
 484     }
 485 
 486     tms.tm_sec = 30;            /* seconds after the minute [0, 61]  */
 487     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
 488     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
 489     tms.tm_mday = day;          /* day of the month [1, 31] */
 490     tms.tm_mon = month - 1;     /* months since January [0, 11] */
 491     tms.tm_year = year - 1900;  /* years since 1900 */
 492     tms.tm_isdst = -1;
 493 
 494 
 495     s->st_mtime = mktime (&tms);
 496     /* Use resulting time value */
 497     s->st_atime = s->st_ctime = s->st_mtime;
 498 
 499     if (strcmp (t, "<DIR>") == 0)
 500         s->st_mode = S_IFDIR;
 501     else
 502     {
 503         s->st_mode = S_IFREG;
 504         if (sscanf (t, "%lld", &size) != 1)
 505             ERR2;
 506         s->st_size = (off_t) size;
 507     }
 508 
 509     t = strtok (NULL, "");
 510     if (t == NULL)
 511         ERR2;
 512     while (*t == ' ')
 513         t++;
 514     if (*t == '\0')
 515         ERR2;
 516 
 517     *filename = g_strdup (t);
 518     *linkname = NULL;
 519 
 520     return TRUE;
 521 }
 522 
 523 /* --------------------------------------------------------------------------------------------- */
 524 
 525 /*
 526    +i774.71425,m951188401,/,       users
 527    +i774.49602,m917883130,r,s79126,        jgr_www2.exe
 528 
 529    starts with +
 530    comma separated
 531    first character of field is type:
 532    i - ?
 533    m - modification time
 534    / - means directory
 535    r - means plain file
 536    s - size
 537    up - permissions in octal
 538    \t - file name follows.
 539  */
 540 
 541 static gboolean
 542 ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 543 {
 544     size_t len;
 545     const char *b;
 546     const char *name = NULL;
 547     size_t name_len = 0;
 548     off_t size = NO_SIZE;
 549     time_t date = NO_DATE;
 550     long date_l;
 551     long long size_ll;
 552     gboolean dir = FALSE;
 553     gboolean type_known = FALSE;
 554     int perms = -1;
 555     const char *scan;
 556     ssize_t scan_len;
 557 
 558     len = strlen (line);
 559     b = line;
 560 
 561     if (len < 2 || b[0] != '+')
 562         ERR2;
 563 
 564     scan = b + 1;
 565     scan_len = len - 1;
 566 
 567     while (scan != NULL && scan_len > 0)
 568     {
 569         const char *comma;
 570 
 571         switch (*scan)
 572         {
 573         case '\t':             /* the rest is file name. */
 574             name = scan + 1;
 575             name_len = scan_len - 1;
 576             scan = NULL;
 577             break;
 578         case 's':
 579             if (sscanf (scan + 1, "%lld", &size_ll) != 1)
 580                 break;
 581             size = size_ll;
 582             break;
 583         case 'm':
 584             if (sscanf (scan + 1, "%ld", &date_l) != 1)
 585                 break;
 586             date = date_l;
 587             break;
 588         case '/':
 589             dir = TRUE;
 590             type_known = TRUE;
 591             break;
 592         case 'r':
 593             dir = FALSE;
 594             type_known = TRUE;
 595             break;
 596         case 'i':
 597             break;
 598         case 'u':
 599             if (scan[1] == 'p') /* permissions. */
 600                 if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1)
 601                     perms = -1;
 602             break;
 603         default:
 604             name = NULL;
 605             scan = NULL;
 606             break;
 607         }
 608         if (scan == NULL || scan_len == 0)
 609             break;
 610 
 611         comma = (const char *) memchr (scan, ',', scan_len);
 612         if (comma == NULL)
 613             break;
 614 
 615         scan_len -= comma + 1 - scan;
 616         scan = comma + 1;
 617     }
 618 
 619     if (name == NULL || !type_known)
 620         ERR2;
 621 
 622     *filename = g_strndup (name, name_len);
 623     *linkname = NULL;
 624 
 625     if (size != NO_SIZE)
 626         s->st_size = size;
 627     if (date != NO_DATE)
 628     {
 629         s->st_mtime = date;
 630         /* Use resulting time value */
 631         s->st_atime = s->st_ctime = s->st_mtime;
 632     }
 633     if (type_known)
 634         s->st_mode = dir ? S_IFDIR : S_IFREG;
 635     if (perms != -1)
 636         s->st_mode |= perms;    /* FIXME */
 637 
 638     return TRUE;
 639 }
 640 
 641 /* --------------------------------------------------------------------------------------------- */
 642 /*
 643    Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
 644    Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
 645    Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
 646    Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
 647    Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
 648    modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
 649    modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
 650    modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
 651  */
 652 
 653 static gboolean
 654 ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 655 {
 656     const char *name = NULL;
 657     off_t size = NO_SIZE;
 658     time_t date = NO_DATE;
 659     const char *owner = NULL;
 660     const char *group = NULL;
 661     filetype type = UNKNOWN;
 662     int perms = -1;
 663     char *space;
 664     char *tok;
 665 
 666     space = strstr (line, "; ");
 667     if (space != NULL)
 668     {
 669         name = space + 2;
 670         *space = '\0';
 671     }
 672     else
 673     {
 674         /* NcFTPd does not put a semicolon after last fact, workaround it. */
 675         space = strchr (line, ' ');
 676         if (space == NULL)
 677             ERR2;
 678         name = space + 1;
 679         *space = '\0';
 680     }
 681 
 682     for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
 683     {
 684         if (strcasecmp (tok, "Type=cdir") == 0
 685             || strcasecmp (tok, "Type=pdir") == 0 || strcasecmp (tok, "Type=dir") == 0)
 686         {
 687             type = DIRECTORY;
 688             continue;
 689         }
 690         if (strcasecmp (tok, "Type=file") == 0)
 691         {
 692             type = NORMAL;
 693             continue;
 694         }
 695         if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
 696         {
 697             type = SYMLINK;
 698             continue;
 699         }
 700         if (strncasecmp (tok, "Modify=", 7) == 0)
 701         {
 702             date = ftpfs_convert_date (tok + 7);
 703             continue;
 704         }
 705         if (strncasecmp (tok, "Size=", 5) == 0)
 706         {
 707             long long size_ll;
 708 
 709             if (sscanf (tok + 5, "%lld", &size_ll) == 1)
 710                 size = size_ll;
 711             continue;
 712         }
 713         if (strncasecmp (tok, "Perm=", 5) == 0)
 714         {
 715             perms = 0;
 716             for (tok += 5; *tok != '\0'; tok++)
 717             {
 718                 switch (g_ascii_tolower (*tok))
 719                 {
 720                 case 'e':
 721                     perms |= 0111;
 722                     break;
 723                 case 'l':
 724                     perms |= 0444;
 725                     break;
 726                 case 'r':
 727                     perms |= 0444;
 728                     break;
 729                 case 'c':
 730                     perms |= 0200;
 731                     break;
 732                 case 'w':
 733                     perms |= 0200;
 734                     break;
 735                 default:
 736                     break;
 737                 }
 738             }
 739             continue;
 740         }
 741         if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
 742         {
 743             if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
 744                 perms = -1;
 745             continue;
 746         }
 747         if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
 748         {
 749             owner = tok + 11;
 750             continue;
 751         }
 752         if (strncasecmp (tok, "UNIX.group=", 11) == 0)
 753         {
 754             group = tok + 11;
 755             continue;
 756         }
 757         if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
 758         {
 759             if (owner == NULL)
 760                 owner = tok + 9;
 761             continue;
 762         }
 763         if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
 764         {
 765             if (group == NULL)
 766                 group = tok + 9;
 767             continue;
 768         }
 769     }
 770     if (name == NULL || name[0] == '\0' || type == UNKNOWN)
 771         ERR2;
 772 
 773     *filename = g_strdup (name);
 774     *linkname = NULL;
 775 
 776     if (size != NO_SIZE)
 777         s->st_size = size;
 778     if (date != NO_DATE)
 779     {
 780         s->st_mtime = date;
 781         /* Use resulting time value */
 782         s->st_atime = s->st_ctime = s->st_mtime;
 783     }
 784     switch (type)
 785     {
 786     case DIRECTORY:
 787         s->st_mode = S_IFDIR;
 788         break;
 789     case SYMLINK:
 790         s->st_mode = S_IFLNK;
 791         break;
 792     case NORMAL:
 793         s->st_mode = S_IFREG;
 794         break;
 795     default:
 796         g_assert_not_reached ();
 797     }
 798     if (perms != -1)
 799         s->st_mode |= perms;    /* FIXME */
 800     if (owner != NULL)
 801         s->st_uid = ftpfs_get_uid (owner);
 802     if (group != NULL)
 803         s->st_gid = ftpfs_get_gid (group);
 804 
 805     return TRUE;
 806 }
 807 
 808 /* --------------------------------------------------------------------------------------------- */
 809 
 810 /*
 811    ASUSER          8192 04/26/05 13:54:16 *DIR       dir/
 812    ASUSER          8192 04/26/05 13:57:34 *DIR       dir1/
 813    ASUSER        365255 02/28/01 15:41:40 *STMF      readme.txt
 814    ASUSER       8489625 03/18/03 09:37:00 *STMF      saved.zip
 815    ASUSER        365255 02/28/01 15:41:40 *STMF      unist.old
 816  */
 817 
 818 static gboolean
 819 ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 820 {
 821     char *t;
 822     char *user;
 823     long long size;
 824     int month, day, year, hour, minute, second;
 825     struct tm tms;
 826     time_t mtime;
 827     mode_t type;
 828     char *slash;
 829 
 830     t = FIRST_TOKEN;
 831     if (t == NULL)
 832         ERR2;
 833     user = t;
 834 
 835     t = NEXT_TOKEN;
 836     if (t == NULL)
 837         ERR2;
 838     if (sscanf (t, "%lld", &size) != 1)
 839         ERR2;
 840 
 841     t = NEXT_TOKEN;
 842     if (t == NULL)
 843         ERR2;
 844     if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3)
 845         ERR2;
 846     if (year >= 70)
 847         year += 1900;
 848     else
 849         year += 2000;
 850 
 851     t = NEXT_TOKEN;
 852     if (t == NULL)
 853         ERR2;
 854     if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
 855         ERR2;
 856 
 857     t = NEXT_TOKEN;
 858     if (t == NULL)
 859         ERR2;
 860 
 861     tms.tm_sec = second;        /* seconds after the minute [0, 61]  */
 862     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
 863     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
 864     tms.tm_mday = day;          /* day of the month [1, 31] */
 865     tms.tm_mon = month - 1;     /* months since January [0, 11] */
 866     tms.tm_year = year - 1900;  /* years since 1900 */
 867     tms.tm_isdst = -1;
 868     mtime = mktime (&tms);
 869 
 870     t = NEXT_TOKEN;
 871     if (t == NULL)
 872         ERR2;
 873     if (strcmp (t, "*DIR") == 0)
 874         type = S_IFDIR;
 875     else
 876         type = S_IFREG;
 877 
 878     t = strtok (NULL, "");
 879     if (t == NULL)
 880         ERR2;
 881     while (*t == ' ')
 882         t++;
 883     if (*t == '\0')
 884         ERR2;
 885 
 886     *linkname = NULL;
 887 
 888     slash = strchr (t, '/');
 889     if (slash != NULL)
 890     {
 891         if (slash == t)
 892             return FALSE;
 893 
 894         *slash = '\0';
 895         type = S_IFDIR;
 896         if (slash[1] != '\0')
 897         {
 898             *filename = g_strdup (t);
 899             s->st_mode = type;  /* FIXME */
 900             return TRUE;
 901         }
 902     }
 903 
 904     *filename = g_strdup (t);
 905     s->st_mode = type;
 906     s->st_size = (off_t) size;
 907     s->st_mtime = mtime;
 908     /* Use resulting time value */
 909     s->st_atime = s->st_ctime = s->st_mtime;
 910     s->st_uid = ftpfs_get_uid (user);
 911 
 912     return TRUE;
 913 }
 914 
 915 /* --------------------------------------------------------------------------------------------- */
 916 
 917 /*
 918    0          DIR  06-27-96  11:57  PROTOCOL
 919    169               11-29-94  09:20  SYSLEVEL.MPT
 920  */
 921 
 922 static gboolean
 923 ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 924 {
 925     char *t;
 926     long long size;
 927     int month, day, year, hour, minute;
 928     struct tm tms;
 929 
 930     t = FIRST_TOKEN;
 931     if (t == NULL)
 932         ERR2;
 933 
 934     if (sscanf (t, "%lld", &size) != 1)
 935         ERR2;
 936     s->st_size = (off_t) size;
 937 
 938     t = NEXT_TOKEN;
 939     if (t == NULL)
 940         ERR2;
 941     s->st_mode = S_IFREG;
 942     if (strcmp (t, "DIR") == 0)
 943     {
 944         s->st_mode = S_IFDIR;
 945         t = NEXT_TOKEN;
 946 
 947         if (t == NULL)
 948             ERR2;
 949     }
 950 
 951     if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
 952         ERR2;
 953     if (year >= 70)
 954         year += 1900;
 955     else
 956         year += 2000;
 957 
 958     t = NEXT_TOKEN;
 959     if (t == NULL)
 960         ERR2;
 961     if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
 962         ERR2;
 963 
 964     tms.tm_sec = 30;            /* seconds after the minute [0, 61]  */
 965     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
 966     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
 967     tms.tm_mday = day;          /* day of the month [1, 31] */
 968     tms.tm_mon = month - 1;     /* months since January [0, 11] */
 969     tms.tm_year = year - 1900;  /* years since 1900 */
 970     tms.tm_isdst = -1;
 971     s->st_mtime = mktime (&tms);
 972     /* Use resulting time value */
 973     s->st_atime = s->st_ctime = s->st_mtime;
 974 
 975     t = strtok (NULL, "");
 976     if (t == NULL)
 977         ERR2;
 978     while (*t == ' ')
 979         t++;
 980     if (*t == '\0')
 981         ERR2;
 982     *filename = g_strdup (t);
 983     *linkname = NULL;
 984 
 985     return TRUE;
 986 }
 987 
 988 /* --------------------------------------------------------------------------------------------- */
 989 
 990 static gboolean
 991 ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
     /* [previous][next][first][last][top][bottom][index][help]  */
 992                                   char **linkname, int *err)
 993 {
 994     char *t;
 995     mode_t type, mode;
 996     struct tm date;
 997     const char *day_of_month;
 998     char *name;
 999 
1000     t = FIRST_TOKEN;
1001     if (t == NULL)
1002         ERR2;
1003 
1004     if (!vfs_parse_filetype (t, NULL, &type))
1005         ERR2;
1006 
1007     s->st_mode = type;
1008 
1009     if (!vfs_parse_fileperms (t + 1, NULL, &mode))
1010         ERR2;
1011     /* permissions are meaningless here. */
1012 
1013     /* "folder" or 0 */
1014     t = NEXT_TOKEN;
1015     if (t == NULL)
1016         ERR2;
1017 
1018     if (strcmp (t, "folder") != 0)
1019     {
1020         long long size;
1021 
1022         /* size? */
1023         t = NEXT_TOKEN;
1024         if (t == NULL)
1025             ERR2;
1026         /* size */
1027         t = NEXT_TOKEN;
1028         if (t == NULL)
1029             ERR2;
1030         if (!isdigit ((unsigned char) *t))
1031             ERR2;
1032 
1033         if (sscanf (t, "%lld", &size) == 1)
1034             s->st_size = (off_t) size;
1035     }
1036     else
1037     {
1038         /* ?? */
1039         t = NEXT_TOKEN;
1040         if (t == NULL)
1041             ERR2;
1042     }
1043 
1044     /* month */
1045     t = NEXT_TOKEN;
1046     if (t == NULL)
1047         ERR2;
1048 
1049     memset (&date, 0, sizeof (date));
1050 
1051     if (!vfs_parse_month (t, &date))
1052         ERR2;
1053 
1054     day_of_month = NEXT_TOKEN;
1055     if (day_of_month == NULL)
1056         ERR2;
1057 
1058     date.tm_mday = atoi (day_of_month);
1059 
1060     /* time or year */
1061     t = NEXT_TOKEN;
1062     if (t == NULL)
1063         ERR2;
1064     if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min))
1065         ERR2;
1066 
1067     date.tm_isdst = -1;
1068     date.tm_sec = 30;
1069     if (date.tm_year == -1)
1070         date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
1071     else
1072         date.tm_hour = 12;
1073 
1074     s->st_mtime = mktime (&date);
1075     /* Use resulting time value */
1076     s->st_atime = s->st_ctime = s->st_mtime;
1077 
1078     name = strtok (NULL, "");
1079     if (name == NULL)
1080         ERR2;
1081 
1082     /* no symlinks on Mac, but anyway. */
1083     if (!S_ISLNK (s->st_mode))
1084         *linkname = NULL;
1085     else
1086     {
1087         char *arrow;
1088 
1089         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
1090             if (arrow != name && arrow[4] != '\0')
1091             {
1092                 *arrow = '\0';
1093                 *linkname = g_strdup (arrow + 4);
1094                 break;
1095             }
1096     }
1097 
1098     *filename = g_strdup (name);
1099 
1100     return TRUE;
1101 }
1102 
1103 /* --------------------------------------------------------------------------------------------- */
1104 /*** public functions ****************************************************************************/
1105 /* --------------------------------------------------------------------------------------------- */
1106 
1107 GSList *
1108 ftpfs_parse_long_list (struct vfs_class * me, struct vfs_s_inode * dir, GSList * buf, int *err_ret)
     /* [previous][next][first][last][top][bottom][index][help]  */
1109 {
1110     int err[number_of_parsers];
1111     GSList *set[number_of_parsers];     /* arrays of struct vfs_s_entry */
1112     size_t i;
1113     GSList *bufp;
1114     ftpfs_line_parser guessed_parser = NULL;
1115     GSList **the_set = NULL;
1116     int *the_err = NULL;
1117     int *best_err1 = &err[0];
1118     int *best_err2 = &err[1];
1119 
1120     ftpfs_init_time ();
1121 
1122     if (err_ret != NULL)
1123         *err_ret = 0;
1124 
1125     memset (&err, 0, sizeof (err));
1126     memset (&set, 0, sizeof (set));
1127 
1128     for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
1129     {
1130         char *b = (char *) bufp->data;
1131         size_t blen;
1132 
1133         blen = strlen (b);
1134 
1135         if (b[blen - 1] == '\r')
1136         {
1137             b[blen - 1] = '\0';
1138             blen--;
1139         }
1140 
1141         if (blen == 0)
1142             continue;
1143 
1144         if (guessed_parser == NULL)
1145         {
1146             for (i = 0; i < number_of_parsers; i++)
1147             {
1148                 struct vfs_s_entry *info;
1149                 gboolean ok;
1150                 char *tmp_line;
1151                 int nlink;
1152 
1153                 /* parser can clobber the line - work on a copy */
1154                 tmp_line = g_strndup (b, blen);
1155 
1156                 info = vfs_s_generate_entry (me, NULL, dir, 0);
1157                 nlink = info->ino->st.st_nlink;
1158                 ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name,
1159                                          &info->ino->linkname, &err[i]);
1160                 if (ok && strchr (info->name, '/') == NULL)
1161                 {
1162                     info->ino->st.st_nlink = nlink;     /* Ouch, we need to preserve our counts :-( */
1163                     set[i] = g_slist_prepend (set[i], info);
1164                 }
1165                 else
1166                     vfs_s_free_entry (me, info);
1167 
1168                 g_free (tmp_line);
1169 
1170                 if (*best_err1 > err[i])
1171                     best_err1 = &err[i];
1172                 if (*best_err2 > err[i] && best_err1 != &err[i])
1173                     best_err2 = &err[i];
1174 
1175                 if (*best_err1 > 16)
1176                     goto leave; /* too many errors with best parser. */
1177             }
1178 
1179             if (*best_err2 > (*best_err1 + 1) * 16)
1180             {
1181                 i = (size_t) (best_err1 - err);
1182                 guessed_parser = line_parsers[i];
1183                 the_set = &set[i];
1184                 the_err = &err[i];
1185             }
1186         }
1187         else
1188         {
1189             struct vfs_s_entry *info;
1190             gboolean ok;
1191             char *tmp_line;
1192             int nlink;
1193 
1194             /* parser can clobber the line - work on a copy */
1195             tmp_line = g_strndup (b, blen);
1196 
1197             info = vfs_s_generate_entry (me, NULL, dir, 0);
1198             nlink = info->ino->st.st_nlink;
1199             ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname,
1200                                  the_err);
1201             if (ok && strchr (info->name, '/') == NULL)
1202             {
1203                 info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
1204                 *the_set = g_slist_prepend (*the_set, info);
1205             }
1206             else
1207                 vfs_s_free_entry (me, info);
1208 
1209             g_free (tmp_line);
1210         }
1211     }
1212 
1213     if (the_set == NULL)
1214     {
1215         i = best_err1 - err;
1216         the_set = &set[i];
1217         the_err = &err[i];
1218     }
1219 
1220   leave:
1221     for (i = 0; i < number_of_parsers; i++)
1222         if (&set[i] != the_set)
1223         {
1224             for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
1225                 vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
1226 
1227             g_slist_free (set[i]);
1228         }
1229 
1230     if (err_ret != NULL && the_err != NULL)
1231         *err_ret = *the_err;
1232 
1233     return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
1234 }
1235 
1236 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */