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-2025
   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 <https://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 (https://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                                                                                       \
  68     do                                                                                             \
  69     {                                                                                              \
  70         (*err)++;                                                                                  \
  71         return FALSE;                                                                              \
  72     }                                                                                              \
  73     while (FALSE)
  74 
  75 /*** file scope type declarations ****************************************************************/
  76 
  77 typedef enum
  78 {
  79     UNKNOWN = 0,
  80     DIRECTORY,
  81     SYMLINK,
  82     NORMAL
  83 } filetype;
  84 
  85 typedef gboolean (*ftpfs_line_parser) (char *line, struct stat *s, char **filename, char **linkname,
  86                                        int *err);
  87 
  88 /*** forward declarations (file scope functions) *************************************************/
  89 
  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);
 104 
 105 /*** file scope variables ************************************************************************/
 106 
 107 static time_t rawnow;
 108 static struct tm now;
 109 
 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
 114 };
 115 
 116 /* --------------------------------------------------------------------------------------------- */
 117 /*** file scope functions ************************************************************************/
 118 /* --------------------------------------------------------------------------------------------- */
 119 
 120 static inline uid_t
 121 ftpfs_get_uid (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 122 {
 123     uid_t u;
 124 
 125     if (*s < '0' || *s > '9')
 126         u = vfs_finduid (s);
 127     else
 128         u = (uid_t) atol (s);
 129 
 130     return u;
 131 }
 132 
 133 /* --------------------------------------------------------------------------------------------- */
 134 
 135 static inline gid_t
 136 ftpfs_get_gid (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 137 {
 138     gid_t g;
 139 
 140     if (*s < '0' || *s > '9')
 141         g = vfs_findgid (s);
 142     else
 143         g = (gid_t) atol (s);
 144 
 145     return g;
 146 }
 147 
 148 /* --------------------------------------------------------------------------------------------- */
 149 
 150 static void
 151 ftpfs_init_time (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 152 {
 153     time (&rawnow);
 154     now = *localtime (&rawnow);
 155 }
 156 
 157 /* --------------------------------------------------------------------------------------------- */
 158 
 159 static int
 160 guess_year (int month, int day, int hour, int minute)
     /* [previous][next][first][last][top][bottom][index][help]  */
 161 {
 162     int year;
 163 
 164     (void) hour;
 165     (void) minute;
 166 
 167     year = now.tm_year + 1900;
 168 
 169     if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
 170         year--;
 171 
 172     return year;
 173 }
 174 
 175 /* --------------------------------------------------------------------------------------------- */
 176 
 177 static gboolean
 178 parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
     /* [previous][next][first][last][top][bottom][index][help]  */
 179 {
 180     if (year_or_time[2] == ':')
 181     {
 182         if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
 183             return FALSE;
 184 
 185         *year = -1;
 186     }
 187     else
 188     {
 189         if (sscanf (year_or_time, "%d", year) != 1)
 190             return FALSE;
 191 
 192         *hour = *minute = 0;
 193     }
 194 
 195     return TRUE;
 196 }
 197 
 198 /* --------------------------------------------------------------------------------------------- */
 199 
 200 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
 201    than local timezone (mktime assumes the latter).
 202 
 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)
     /* [previous][next][first][last][top][bottom][index][help]  */
 207 {
 208     struct tm tc;
 209     time_t tl, tb;
 210 
 211     memcpy (&tc, t, sizeof (struct tm));
 212 
 213     /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
 214      * dependent errors. */
 215 
 216     tc.tm_isdst = 0;
 217 
 218     tl = mktime (&tc);
 219     if (tl == -1)
 220         return (-1);
 221 
 222     tb = mktime (gmtime (&tl));
 223 
 224     return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
 225 }
 226 
 227 /* --------------------------------------------------------------------------------------------- */
 228 
 229 static time_t
 230 ftpfs_convert_date (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 231 {
 232     struct tm tm;
 233     int year, month, day, hour, minute, second;
 234     int skip = 0;
 235     int n;
 236 
 237     memset (&tm, 0, sizeof (tm));
 238 
 239     n = sscanf (s, "%4d%n", &year, &skip);
 240 
 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)
 244     {
 245         n = sscanf (s, "%5d%n", &year, &skip);
 246         year = year - 19100 + 2000;
 247     }
 248 
 249     if (n != 1)
 250         return NO_DATE;
 251 
 252     n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
 253 
 254     if (n != 5)
 255         return NO_DATE;
 256 
 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;
 263 
 264     return mktime_from_utc (&tm);
 265 }
 266 
 267 /* --------------------------------------------------------------------------------------------- */
 268 
 269 /*
 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
 274 
 275    NOTE: group may be missing.
 276  */
 277 
 278 static gboolean
 279 parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
     /* [previous][next][first][last][top][bottom][index][help]  */
 280 {
 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;
 289 
 290     // parse perms
 291     t = FIRST_TOKEN_R;
 292     if (t == NULL)
 293         return FALSE;
 294 
 295     if (!vfs_parse_filetype (t, NULL, &type))
 296         return FALSE;
 297 
 298     if (vfs_parse_fileperms (t + 1, NULL, &mode))
 299         mode |= type;
 300 
 301     s->st_mode = mode;
 302 
 303     // link count
 304     t = NEXT_TOKEN_R;
 305     if (t == NULL)
 306         return FALSE;
 307     s->st_nlink = atol (t);
 308 
 309     // user
 310     t = NEXT_TOKEN_R;
 311     if (t == NULL)
 312         return FALSE;
 313 
 314     s->st_uid = ftpfs_get_uid (t);
 315 
 316     // group or size
 317     group_or_size = NEXT_TOKEN_R;
 318 
 319     // size or month
 320     t = NEXT_TOKEN_R;
 321     if (t == NULL)
 322         return FALSE;
 323     if (isdigit ((unsigned char) *t))
 324     {
 325         // it's size, so the previous was group:
 326         long long size;
 327         int n;
 328 
 329         s->st_gid = ftpfs_get_gid (group_or_size);
 330 
 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;
 336     }
 337     else
 338     {
 339         //  it was month, so the previous was size:
 340         long long size;
 341         int n;
 342 
 343         if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
 344             s->st_size = (off_t) size;
 345     }
 346 
 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
 353 
 354     memset (&date, 0, sizeof (date));
 355 
 356     if (!vfs_parse_month (t, &date))
 357         date.tm_mon = 0;
 358 
 359     day_of_month = NEXT_TOKEN_R;
 360     if (day_of_month == NULL)
 361         return FALSE;
 362     date.tm_mday = atoi (day_of_month);
 363 
 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;
 371 
 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
 375     {
 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;
 384     }
 385 
 386     s->st_mtime = mktime (&date);
 387     // Use resulting time value
 388     s->st_atime = s->st_ctime = s->st_mtime;
 389 
 390     name = strtok_r (NULL, "", &next);
 391     if (name == NULL)
 392         return FALSE;
 393 
 394     // there are ls which output extra space after year
 395     if (year_anomaly && *name == ' ')
 396         name++;
 397 
 398     if (!S_ISLNK (s->st_mode))
 399         *linkname = NULL;
 400     else
 401     {
 402         char *arrow;
 403 
 404         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
 405             if (arrow != name && arrow[4] != '\0')
 406             {
 407                 *arrow = '\0';
 408                 *linkname = g_strdup (arrow + 4);
 409                 break;
 410             }
 411     }
 412 
 413     *filename = g_strdup (name);
 414 
 415     return TRUE;
 416 }
 417 
 418 /* --------------------------------------------------------------------------------------------- */
 419 
 420 static gboolean
 421 ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 422 {
 423     int tmp;
 424     gboolean ret;
 425 
 426     if (sscanf (line, "total %d", &tmp) == 1)
 427         return FALSE;
 428 
 429     if (strncasecmp (line, "Status of ", 10) == 0)
 430         return FALSE;  // STAT output
 431 
 432     ret = parse_ls_line (line, s, filename, linkname);
 433     if (!ret)
 434         (*err)++;
 435 
 436     return ret;
 437 }
 438 
 439 /* --------------------------------------------------------------------------------------------- */
 440 
 441 /*
 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
 449  */
 450 
 451 static gboolean
 452 ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 453 {
 454     char *t;
 455     int month, day, year, hour, minute;
 456     char am;
 457     struct tm tms;
 458     long long size;
 459 
 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;
 469 
 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;
 476 
 477     t = NEXT_TOKEN;
 478     if (t == NULL)
 479         ERR2;
 480 
 481     if (am == 'P')  // PM - after noon
 482     {
 483         hour += 12;
 484         if (hour == 24)
 485             hour = 0;
 486     }
 487 
 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;
 495 
 496     s->st_mtime = mktime (&tms);
 497     // Use resulting time value
 498     s->st_atime = s->st_ctime = s->st_mtime;
 499 
 500     if (strcmp (t, "<DIR>") == 0)
 501         s->st_mode = S_IFDIR;
 502     else
 503     {
 504         s->st_mode = S_IFREG;
 505         if (sscanf (t, "%lld", &size) != 1)
 506             ERR2;
 507         s->st_size = (off_t) size;
 508     }
 509 
 510     t = strtok (NULL, "");
 511     if (t == NULL)
 512         ERR2;
 513     while (*t == ' ')
 514         t++;
 515     if (*t == '\0')
 516         ERR2;
 517 
 518     *filename = g_strdup (t);
 519     *linkname = NULL;
 520 
 521     return TRUE;
 522 }
 523 
 524 /* --------------------------------------------------------------------------------------------- */
 525 
 526 /*
 527    +i774.71425,m951188401,/,       users
 528    +i774.49602,m917883130,r,s79126,        jgr_www2.exe
 529 
 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.
 540  */
 541 
 542 static gboolean
 543 ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 544 {
 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;
 558 
 559     len = strlen (line);
 560     b = line;
 561 
 562     if (len < 2 || b[0] != '+')
 563         ERR2;
 564 
 565     scan = b + 1;
 566     scan_len = len - 1;
 567 
 568     while (scan != NULL && scan_len > 0)
 569     {
 570         const char *comma;
 571 
 572         switch (*scan)
 573         {
 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;
 608         }
 609         if (scan == NULL || scan_len == 0)
 610             break;
 611 
 612         comma = (const char *) memchr (scan, ',', scan_len);
 613         if (comma == NULL)
 614             break;
 615 
 616         scan_len -= comma + 1 - scan;
 617         scan = comma + 1;
 618     }
 619 
 620     if (name == NULL || !type_known)
 621         ERR2;
 622 
 623     *filename = g_strndup (name, name_len);
 624     *linkname = NULL;
 625 
 626     if (size != NO_SIZE)
 627         s->st_size = size;
 628     if (date != NO_DATE)
 629     {
 630         s->st_mtime = date;
 631         // Use resulting time value
 632         s->st_atime = s->st_ctime = s->st_mtime;
 633     }
 634     if (type_known)
 635         s->st_mode = dir ? S_IFDIR : S_IFREG;
 636     if (perms != -1)
 637         s->st_mode |= perms;  // FIXME
 638 
 639     return TRUE;
 640 }
 641 
 642 /* --------------------------------------------------------------------------------------------- */
 643 /*
 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
 652  */
 653 
 654 static gboolean
 655 ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 656 {
 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;
 666 
 667     space = strstr (line, "; ");
 668     if (space != NULL)
 669     {
 670         name = space + 2;
 671         *space = '\0';
 672     }
 673     else
 674     {
 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';
 681     }
 682 
 683     for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
 684     {
 685         if (strcasecmp (tok, "Type=cdir") == 0 || strcasecmp (tok, "Type=pdir") == 0
 686             || strcasecmp (tok, "Type=dir") == 0)
 687         {
 688             type = DIRECTORY;
 689             continue;
 690         }
 691         if (strcasecmp (tok, "Type=file") == 0)
 692         {
 693             type = NORMAL;
 694             continue;
 695         }
 696         if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
 697         {
 698             type = SYMLINK;
 699             continue;
 700         }
 701         if (strncasecmp (tok, "Modify=", 7) == 0)
 702         {
 703             date = ftpfs_convert_date (tok + 7);
 704             continue;
 705         }
 706         if (strncasecmp (tok, "Size=", 5) == 0)
 707         {
 708             long long size_ll;
 709 
 710             if (sscanf (tok + 5, "%lld", &size_ll) == 1)
 711                 size = size_ll;
 712             continue;
 713         }
 714         if (strncasecmp (tok, "Perm=", 5) == 0)
 715         {
 716             perms = 0;
 717             for (tok += 5; *tok != '\0'; tok++)
 718             {
 719                 switch (g_ascii_tolower (*tok))
 720                 {
 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;
 738                 }
 739             }
 740             continue;
 741         }
 742         if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
 743         {
 744             if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
 745                 perms = -1;
 746             continue;
 747         }
 748         if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
 749         {
 750             owner = tok + 11;
 751             continue;
 752         }
 753         if (strncasecmp (tok, "UNIX.group=", 11) == 0)
 754         {
 755             group = tok + 11;
 756             continue;
 757         }
 758         if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
 759         {
 760             if (owner == NULL)
 761                 owner = tok + 9;
 762             continue;
 763         }
 764         if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
 765         {
 766             if (group == NULL)
 767                 group = tok + 9;
 768             continue;
 769         }
 770     }
 771     if (name == NULL || name[0] == '\0' || type == UNKNOWN)
 772         ERR2;
 773 
 774     *filename = g_strdup (name);
 775     *linkname = NULL;
 776 
 777     if (size != NO_SIZE)
 778         s->st_size = size;
 779     if (date != NO_DATE)
 780     {
 781         s->st_mtime = date;
 782         // Use resulting time value
 783         s->st_atime = s->st_ctime = s->st_mtime;
 784     }
 785     switch (type)
 786     {
 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 ();
 798     }
 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);
 805 
 806     return TRUE;
 807 }
 808 
 809 /* --------------------------------------------------------------------------------------------- */
 810 
 811 /*
 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
 817  */
 818 
 819 static gboolean
 820 ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 821 {
 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;
 830 
 831     t = FIRST_TOKEN;
 832     if (t == NULL)
 833         ERR2;
 834     user = t;
 835 
 836     t = NEXT_TOKEN;
 837     if (t == NULL)
 838         ERR2;
 839     if (sscanf (t, "%lld", &size) != 1)
 840         ERR2;
 841 
 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;
 851 
 852     t = NEXT_TOKEN;
 853     if (t == NULL)
 854         ERR2;
 855     if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
 856         ERR2;
 857 
 858     t = NEXT_TOKEN;
 859     if (t == NULL)
 860         ERR2;
 861 
 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);
 870 
 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;
 878 
 879     t = strtok (NULL, "");
 880     if (t == NULL)
 881         ERR2;
 882     while (*t == ' ')
 883         t++;
 884     if (*t == '\0')
 885         ERR2;
 886 
 887     *linkname = NULL;
 888 
 889     slash = strchr (t, '/');
 890     if (slash != NULL)
 891     {
 892         if (slash == t)
 893             return FALSE;
 894 
 895         *slash = '\0';
 896         type = S_IFDIR;
 897         if (slash[1] != '\0')
 898         {
 899             *filename = g_strdup (t);
 900             s->st_mode = type;  // FIXME
 901             return TRUE;
 902         }
 903     }
 904 
 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);
 912 
 913     return TRUE;
 914 }
 915 
 916 /* --------------------------------------------------------------------------------------------- */
 917 
 918 /*
 919    0          DIR  06-27-96  11:57  PROTOCOL
 920    169               11-29-94  09:20  SYSLEVEL.MPT
 921  */
 922 
 923 static gboolean
 924 ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
     /* [previous][next][first][last][top][bottom][index][help]  */
 925 {
 926     char *t;
 927     long long size;
 928     int month, day, year, hour, minute;
 929     struct tm tms;
 930 
 931     t = FIRST_TOKEN;
 932     if (t == NULL)
 933         ERR2;
 934 
 935     if (sscanf (t, "%lld", &size) != 1)
 936         ERR2;
 937     s->st_size = (off_t) size;
 938 
 939     t = NEXT_TOKEN;
 940     if (t == NULL)
 941         ERR2;
 942     s->st_mode = S_IFREG;
 943     if (strcmp (t, "DIR") == 0)
 944     {
 945         s->st_mode = S_IFDIR;
 946         t = NEXT_TOKEN;
 947 
 948         if (t == NULL)
 949             ERR2;
 950     }
 951 
 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;
 958 
 959     t = NEXT_TOKEN;
 960     if (t == NULL)
 961         ERR2;
 962     if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
 963         ERR2;
 964 
 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;
 975 
 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;
 985 
 986     return TRUE;
 987 }
 988 
 989 /* --------------------------------------------------------------------------------------------- */
 990 
 991 static gboolean
 992 ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename, char **linkname,
     /* [previous][next][first][last][top][bottom][index][help]  */
 993                                   int *err)
 994 {
 995     char *t;
 996     mode_t type, mode;
 997     struct tm date;
 998     const char *day_of_month;
 999     char *name;
1000 
1001     t = FIRST_TOKEN;
1002     if (t == NULL)
1003         ERR2;
1004 
1005     if (!vfs_parse_filetype (t, NULL, &type))
1006         ERR2;
1007 
1008     s->st_mode = type;
1009 
1010     if (!vfs_parse_fileperms (t + 1, NULL, &mode))
1011         ERR2;
1012     // permissions are meaningless here
1013 
1014     // "folder" or 0
1015     t = NEXT_TOKEN;
1016     if (t == NULL)
1017         ERR2;
1018 
1019     if (strcmp (t, "folder") != 0)
1020     {
1021         long long size;
1022 
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;
1033 
1034         if (sscanf (t, "%lld", &size) == 1)
1035             s->st_size = (off_t) size;
1036     }
1037     else
1038     {
1039         // ??
1040         t = NEXT_TOKEN;
1041         if (t == NULL)
1042             ERR2;
1043     }
1044 
1045     // month
1046     t = NEXT_TOKEN;
1047     if (t == NULL)
1048         ERR2;
1049 
1050     memset (&date, 0, sizeof (date));
1051 
1052     if (!vfs_parse_month (t, &date))
1053         ERR2;
1054 
1055     day_of_month = NEXT_TOKEN;
1056     if (day_of_month == NULL)
1057         ERR2;
1058 
1059     date.tm_mday = atoi (day_of_month);
1060 
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;
1067 
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;
1074 
1075     s->st_mtime = mktime (&date);
1076     // Use resulting time value
1077     s->st_atime = s->st_ctime = s->st_mtime;
1078 
1079     name = strtok (NULL, "");
1080     if (name == NULL)
1081         ERR2;
1082 
1083     // no symlinks on Mac, but anyway
1084     if (!S_ISLNK (s->st_mode))
1085         *linkname = NULL;
1086     else
1087     {
1088         char *arrow;
1089 
1090         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
1091             if (arrow != name && arrow[4] != '\0')
1092             {
1093                 *arrow = '\0';
1094                 *linkname = g_strdup (arrow + 4);
1095                 break;
1096             }
1097     }
1098 
1099     *filename = g_strdup (name);
1100 
1101     return TRUE;
1102 }
1103 
1104 /* --------------------------------------------------------------------------------------------- */
1105 /*** public functions ****************************************************************************/
1106 /* --------------------------------------------------------------------------------------------- */
1107 
1108 GSList *
1109 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]  */
1110 {
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];
1120 
1121     ftpfs_init_time ();
1122 
1123     if (err_ret != NULL)
1124         *err_ret = 0;
1125 
1126     memset (&err, 0, sizeof (err));
1127     memset (&set, 0, sizeof (set));
1128 
1129     for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
1130     {
1131         char *b = (char *) bufp->data;
1132         size_t blen;
1133 
1134         blen = strlen (b);
1135 
1136         if (b[blen - 1] == '\r')
1137         {
1138             b[blen - 1] = '\0';
1139             blen--;
1140         }
1141 
1142         if (blen == 0)
1143             continue;
1144 
1145         if (guessed_parser == NULL)
1146         {
1147             for (i = 0; i < number_of_parsers; i++)
1148             {
1149                 struct vfs_s_entry *info;
1150                 gboolean ok;
1151                 char *tmp_line;
1152                 int nlink;
1153 
1154                 // parser can clobber the line - work on a copy
1155                 tmp_line = g_strndup (b, blen);
1156 
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)
1162                 {
1163                     info->ino->st.st_nlink = nlink;  // Ouch, we need to preserve our counts :-(
1164                     set[i] = g_slist_prepend (set[i], info);
1165                 }
1166                 else
1167                     vfs_s_free_entry (me, info);
1168 
1169                 g_free (tmp_line);
1170 
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];
1175 
1176                 if (*best_err1 > 16)
1177                     goto leave;  // too many errors with best parser
1178             }
1179 
1180             if (*best_err2 > (*best_err1 + 1) * 16)
1181             {
1182                 i = (size_t) (best_err1 - err);
1183                 guessed_parser = line_parsers[i];
1184                 the_set = &set[i];
1185                 the_err = &err[i];
1186             }
1187         }
1188         else
1189         {
1190             struct vfs_s_entry *info;
1191             gboolean ok;
1192             char *tmp_line;
1193             int nlink;
1194 
1195             // parser can clobber the line - work on a copy
1196             tmp_line = g_strndup (b, blen);
1197 
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)
1203             {
1204                 info->ino->st.st_nlink = nlink;  // Ouch, we need to preserve our counts :-(
1205                 *the_set = g_slist_prepend (*the_set, info);
1206             }
1207             else
1208                 vfs_s_free_entry (me, info);
1209 
1210             g_free (tmp_line);
1211         }
1212     }
1213 
1214     if (the_set == NULL)
1215     {
1216         i = best_err1 - err;
1217         the_set = &set[i];
1218         the_err = &err[i];
1219     }
1220 
1221 leave:
1222     for (i = 0; i < number_of_parsers; i++)
1223         if (&set[i] != the_set)
1224         {
1225             for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
1226                 vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
1227 
1228             g_slist_free (set[i]);
1229         }
1230 
1231     if (err_ret != NULL && the_err != NULL)
1232         *err_ret = *the_err;
1233 
1234     return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
1235 }
1236 
1237 /* --------------------------------------------------------------------------------------------- */

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