Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2002 Cyrus Patel <cyp (at) fb14.uni-mainz.de>
      3  *           (C) 2007 Apple Inc. All rights reserved.
      4  *
      5  * This library is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU Lesser General Public
      7  * License 2.1 as published by the Free Software Foundation.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  */
     19 
     20 // This was originally Mozilla code, titled ParseFTPList.cpp
     21 // Original version of this file can currently be found at: http://mxr.mozilla.org/mozilla1.8/source/netwerk/streamconv/converters/ParseFTPList.cpp
     22 
     23 #include "config.h"
     24 #if ENABLE(FTPDIR)
     25 #include "FTPDirectoryParser.h"
     26 
     27 #if PLATFORM(QT)
     28 #include <QDateTime>
     29 // On Windows, use the threadsafe *_r functions provided by pthread.
     30 #elif OS(WINDOWS) && (USE(PTHREADS) || HAVE(PTHREAD_H))
     31 #include <pthread.h>
     32 #endif
     33 
     34 #include <wtf/ASCIICType.h>
     35 #include <stdio.h>
     36 
     37 using namespace WTF;
     38 
     39 namespace WebCore {
     40 #if PLATFORM(QT) && defined(Q_WS_WIN32)
     41 
     42 // Replacement for gmtime_r() which is not available on MinGW.
     43 // We use this on Win32 Qt platform for portability.
     44 struct tm gmtimeQt(const QDateTime& input)
     45 {
     46     tm result;
     47 
     48     QDate date(input.date());
     49     result.tm_year = date.year() - 1900;
     50     result.tm_mon = date.month();
     51     result.tm_mday = date.day();
     52     result.tm_wday = date.dayOfWeek();
     53     result.tm_yday = date.dayOfYear();
     54 
     55     QTime time(input.time());
     56     result.tm_sec = time.second();
     57     result.tm_min = time.minute();
     58     result.tm_hour = time.hour();
     59 
     60     return result;
     61 }
     62 
     63 static struct tm *gmtimeQt(const time_t *const timep, struct tm *result)
     64 {
     65     const QDateTime dt(QDateTime::fromTime_t(*timep));
     66     *result = WebCore::gmtimeQt(dt);
     67     return result;
     68 }
     69 
     70 #define gmtime_r(x, y) gmtimeQt(x, y)
     71 #elif OS(WINDOWS) && !defined(gmtime_r)
     72 #if defined(_MSC_VER) && (_MSC_VER >= 1400)
     73 #define gmtime_r(x, y) gmtime_s((y), (x))
     74 #else /* !_MSC_VER */
     75 #define gmtime_r(x,y) (gmtime(x)?(*(y)=*gmtime(x),(y)):0)
     76 #endif
     77 #endif
     78 
     79 static inline FTPEntryType ParsingFailed(ListState& state)
     80 {
     81   if (state.parsedOne || state.listStyle) /* junk if we fail to parse */
     82     return FTPJunkEntry;      /* this time but had previously parsed sucessfully */
     83   return FTPMiscEntry;        /* its part of a comment or error message */
     84 }
     85 
     86 FTPEntryType parseOneFTPLine(const char* line, ListState& state, ListResult& result)
     87 {
     88   result.clear();
     89 
     90   if (!line)
     91     return FTPJunkEntry;
     92 
     93   state.numLines++;
     94 
     95   /* carry buffer is only valid from one line to the next */
     96   unsigned int carry_buf_len = state.carryBufferLength;
     97   state.carryBufferLength = 0;
     98 
     99   unsigned linelen = 0;
    100 
    101   /* strip leading whitespace */
    102   while (*line == ' ' || *line == '\t')
    103     line++;
    104 
    105   /* line is terminated at first '\0' or '\n' */
    106   const char* p = line;
    107   while (*p && *p != '\n')
    108     p++;
    109   linelen = p - line;
    110 
    111   if (linelen > 0 && *p == '\n' && *(p-1) == '\r')
    112     linelen--;
    113 
    114   /* DON'T strip trailing whitespace. */
    115 
    116   if (linelen > 0)
    117   {
    118     static const char *month_names = "JanFebMarAprMayJunJulAugSepOctNovDec";
    119     const char *tokens[16]; /* 16 is more than enough */
    120     unsigned int toklen[(sizeof(tokens)/sizeof(tokens[0]))];
    121     unsigned int linelen_sans_wsp;  // line length sans whitespace
    122     unsigned int numtoks = 0;
    123     unsigned int tokmarker = 0; /* extra info for lstyle handler */
    124     unsigned int month_num = 0;
    125     char tbuf[4];
    126     int lstyle = 0;
    127 
    128     if (carry_buf_len) /* VMS long filename carryover buffer */
    129     {
    130       tokens[0] = state.carryBuffer;
    131       toklen[0] = carry_buf_len;
    132       numtoks++;
    133     }
    134 
    135     unsigned int pos = 0;
    136     while (pos < linelen && numtoks < (sizeof(tokens)/sizeof(tokens[0])) )
    137     {
    138       while (pos < linelen &&
    139             (line[pos] == ' ' || line[pos] == '\t' || line[pos] == '\r'))
    140         pos++;
    141       if (pos < linelen)
    142       {
    143         tokens[numtoks] = &line[pos];
    144         while (pos < linelen &&
    145            (line[pos] != ' ' && line[pos] != '\t' && line[pos] != '\r'))
    146           pos++;
    147         if (tokens[numtoks] != &line[pos])
    148         {
    149           toklen[numtoks] = (&line[pos] - tokens[numtoks]);
    150           numtoks++;
    151         }
    152       }
    153     }
    154 
    155     if (!numtoks)
    156       return ParsingFailed(state);
    157 
    158     linelen_sans_wsp = &(tokens[numtoks-1][toklen[numtoks-1]]) - tokens[0];
    159     if (numtoks == (sizeof(tokens)/sizeof(tokens[0])) )
    160     {
    161       pos = linelen;
    162       while (pos > 0 && (line[pos-1] == ' ' || line[pos-1] == '\t'))
    163         pos--;
    164       linelen_sans_wsp = pos;
    165     }
    166 
    167     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    168 #if defined(SUPPORT_EPLF)
    169     /* EPLF handling must come somewhere before /bin/dls handling. */
    170     if (!lstyle && (!state.listStyle || state.listStyle == 'E'))
    171     {
    172       if (*line == '+' && linelen > 4 && numtoks >= 2)
    173       {
    174         pos = 1;
    175         while (pos < (linelen-1))
    176         {
    177           p = &line[pos++];
    178           if (*p == '/')
    179             result.type = FTPDirectoryEntry; /* its a dir */
    180           else if (*p == 'r')
    181             result.type = FTPFileEntry; /* its a file */
    182           else if (*p == 'm')
    183           {
    184             if (isASCIIDigit(line[pos]))
    185             {
    186               while (pos < linelen && isASCIIDigit(line[pos]))
    187                 pos++;
    188               if (pos < linelen && line[pos] == ',')
    189               {
    190                 unsigned long long seconds = 0;
    191                 sscanf(p + 1, "%llu", &seconds);
    192                 time_t t = static_cast<time_t>(seconds);
    193 
    194                 // FIXME: This code has the year 2038 bug
    195                 gmtime_r(&t, &result.modifiedTime);
    196                 result.modifiedTime.tm_year += 1900;
    197               }
    198             }
    199           }
    200           else if (*p == 's')
    201           {
    202             if (isASCIIDigit(line[pos]))
    203             {
    204               while (pos < linelen && isASCIIDigit(line[pos]))
    205                 pos++;
    206               if (pos < linelen && line[pos] == ',')
    207                 result.fileSize = String(p + 1, &line[pos] - p + 1);
    208             }
    209           }
    210           else if (isASCIIAlpha(*p)) /* 'i'/'up' or unknown "fact" (property) */
    211           {
    212             while (pos < linelen && *++p != ',')
    213               pos++;
    214           }
    215           else if (*p != '\t' || (p+1) != tokens[1])
    216           {
    217             break; /* its not EPLF after all */
    218           }
    219           else
    220           {
    221             state.parsedOne = true;
    222             state.listStyle = lstyle = 'E';
    223 
    224             p = &(line[linelen_sans_wsp]);
    225             result.filename = tokens[1];
    226             result.filenameLength = p - tokens[1];
    227 
    228             if (!result.type) /* access denied */
    229             {
    230               result.type = FTPFileEntry; /* is assuming 'f'ile correct? */
    231               return FTPJunkEntry;            /* NO! junk it. */
    232             }
    233             return result.type;
    234           }
    235           if (pos >= (linelen-1) || line[pos] != ',')
    236             break;
    237           pos++;
    238         } /* while (pos < linelen) */
    239         result.clear();
    240       } /* if (*line == '+' && linelen > 4 && numtoks >= 2) */
    241     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'E')) */
    242 #endif /* SUPPORT_EPLF */
    243 
    244     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    245 
    246 #if defined(SUPPORT_VMS)
    247     if (!lstyle && (!state.listStyle || state.listStyle == 'V'))
    248     {                          /* try VMS Multinet/UCX/CMS server */
    249       /*
    250        * Legal characters in a VMS file/dir spec are [A-Z0-9$.-_~].
    251        * '$' cannot begin a filename and `-' cannot be used as the first
    252        * or last character. '.' is only valid as a directory separator
    253        * and <file>.<type> separator. A canonical filename spec might look
    254        * like this: DISK$VOL:[DIR1.DIR2.DIR3]FILE.TYPE;123
    255        * All VMS FTP servers LIST in uppercase.
    256        *
    257        * We need to be picky about this in order to support
    258        * multi-line listings correctly.
    259       */
    260       if (!state.parsedOne &&
    261           (numtoks == 1 || (numtoks == 2 && toklen[0] == 9 &&
    262                             memcmp(tokens[0], "Directory", 9)==0 )))
    263       {
    264         /* If no dirstyle has been detected yet, and this line is a
    265          * VMS list's dirname, then turn on VMS dirstyle.
    266          * eg "ACA:[ANONYMOUS]", "DISK$FTP:[ANONYMOUS]", "SYS$ANONFTP:"
    267         */
    268         p = tokens[0];
    269         pos = toklen[0];
    270         if (numtoks == 2)
    271         {
    272           p = tokens[1];
    273           pos = toklen[1];
    274         }
    275         pos--;
    276         if (pos >= 3)
    277         {
    278           while (pos > 0 && p[pos] != '[')
    279           {
    280             pos--;
    281             if (p[pos] == '-' || p[pos] == '$')
    282             {
    283               if (pos == 0 || p[pos-1] == '[' || p[pos-1] == '.' ||
    284                   (p[pos] == '-' && (p[pos+1] == ']' || p[pos+1] == '.')))
    285                 break;
    286             }
    287             else if (p[pos] != '.' && p[pos] != '~' &&
    288                      !isASCIIDigit(p[pos]) && !isASCIIAlpha(p[pos]))
    289               break;
    290             else if (isASCIIAlpha(p[pos]) && p[pos] != toASCIIUpper(p[pos]))
    291               break;
    292           }
    293           if (pos > 0)
    294           {
    295             pos--;
    296             if (p[pos] != ':' || p[pos+1] != '[')
    297               pos = 0;
    298           }
    299         }
    300         if (pos > 0 && p[pos] == ':')
    301         {
    302           while (pos > 0)
    303           {
    304             pos--;
    305             if (p[pos] != '$' && p[pos] != '_' && p[pos] != '-' &&
    306                 p[pos] != '~' && !isASCIIDigit(p[pos]) && !isASCIIAlpha(p[pos]))
    307               break;
    308             else if (isASCIIAlpha(p[pos]) && p[pos] != toASCIIUpper(p[pos]))
    309               break;
    310           }
    311           if (pos == 0)
    312           {
    313             state.listStyle = 'V';
    314             return FTPJunkEntry; /* its junk */
    315           }
    316         }
    317         /* fallthrough */
    318       }
    319       else if ((tokens[0][toklen[0]-1]) != ';')
    320       {
    321         if (numtoks == 1 && (state.listStyle == 'V' && !carry_buf_len))
    322           lstyle = 'V';
    323         else if (numtoks < 4)
    324           ;
    325         else if (toklen[1] >= 10 && memcmp(tokens[1], "%RMS-E-PRV", 10) == 0)
    326           lstyle = 'V';
    327         else if ((&line[linelen] - tokens[1]) >= 22 &&
    328                   memcmp(tokens[1], "insufficient privilege", 22) == 0)
    329           lstyle = 'V';
    330         else if (numtoks != 4 && numtoks != 6)
    331           ;
    332         else if (numtoks == 6 && (
    333                  toklen[5] < 4 || *tokens[5] != '(' ||        /* perms */
    334                            (tokens[5][toklen[5]-1]) != ')'  ))
    335           ;
    336         else if (  (toklen[2] == 10 || toklen[2] == 11) &&
    337                         (tokens[2][toklen[2]-5]) == '-' &&
    338                         (tokens[2][toklen[2]-9]) == '-' &&
    339         (((toklen[3]==4 || toklen[3]==5 || toklen[3]==7 || toklen[3]==8) &&
    340                         (tokens[3][toklen[3]-3]) == ':' ) ||
    341          ((toklen[3]==10 || toklen[3]==11 ) &&
    342                         (tokens[3][toklen[3]-3]) == '.' )
    343         ) &&  /* time in [H]H:MM[:SS[.CC]] format */
    344                                     isASCIIDigit(*tokens[1]) && /* size */
    345                                     isASCIIDigit(*tokens[2]) && /* date */
    346                                     isASCIIDigit(*tokens[3])    /* time */
    347                 )
    348         {
    349           lstyle = 'V';
    350         }
    351         if (lstyle == 'V')
    352         {
    353           /*
    354           * MultiNet FTP:
    355           *   LOGIN.COM;2                 1   4-NOV-1994 04:09 [ANONYMOUS] (RWE,RWE,,)
    356           *   PUB.DIR;1                   1  27-JAN-1994 14:46 [ANONYMOUS] (RWE,RWE,RE,RWE)
    357           *   README.FTP;1        %RMS-E-PRV, insufficient privilege or file protection violation
    358           *   ROUSSOS.DIR;1               1  27-JAN-1994 14:48 [CS,ROUSSOS] (RWE,RWE,RE,R)
    359           *   S67-50903.JPG;1           328  22-SEP-1998 16:19 [ANONYMOUS] (RWED,RWED,,)
    360           * UCX FTP:
    361           *   CII-MANUAL.TEX;1  213/216  29-JAN-1996 03:33:12  [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
    362           * CMU/VMS-IP FTP
    363           *   [VMSSERV.FILES]ALARM.DIR;1 1/3 5-MAR-1993 18:09
    364           * TCPware FTP
    365           *   FOO.BAR;1 4 5-MAR-1993 18:09:01.12
    366           * Long filename example:
    367           *   THIS-IS-A-LONG-VMS-FILENAME.AND-THIS-IS-A-LONG-VMS-FILETYPE\r\n
    368           *                    213[/nnn]  29-JAN-1996 03:33[:nn]  [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
    369           */
    370           tokmarker = 0;
    371           p = tokens[0];
    372           pos = 0;
    373           if (*p == '[' && toklen[0] >= 4) /* CMU style */
    374           {
    375             if (p[1] != ']')
    376             {
    377               p++;
    378               pos++;
    379             }
    380             while (lstyle && pos < toklen[0] && *p != ']')
    381             {
    382               if (*p != '$' && *p != '.' && *p != '_' && *p != '-' &&
    383                   *p != '~' && !isASCIIDigit(*p) && !isASCIIAlpha(*p))
    384                 lstyle = 0;
    385               pos++;
    386               p++;
    387             }
    388             if (lstyle && pos < (toklen[0]-1))
    389             {
    390               /* ']' was found and there is at least one character after it */
    391               ASSERT(*p == ']');
    392               pos++;
    393               p++;
    394               tokmarker = pos; /* length of leading "[DIR1.DIR2.etc]" */
    395             } else {
    396               /* not a CMU style listing */
    397               lstyle = 0;
    398             }
    399           }
    400           while (lstyle && pos < toklen[0] && *p != ';')
    401           {
    402             if (*p != '$' && *p != '.' && *p != '_' && *p != '-' &&
    403                 *p != '~' && !isASCIIDigit(*p) && !isASCIIAlpha(*p))
    404               lstyle = 0;
    405             else if (isASCIIAlpha(*p) && *p != toASCIIUpper(*p))
    406               lstyle = 0;
    407             p++;
    408             pos++;
    409           }
    410           if (lstyle && *p == ';')
    411           {
    412             if (pos == 0 || pos == (toklen[0]-1))
    413               lstyle = 0;
    414             for (pos++;lstyle && pos < toklen[0];pos++)
    415             {
    416               if (!isASCIIDigit(tokens[0][pos]))
    417                 lstyle = 0;
    418             }
    419           }
    420           pos = (p - tokens[0]); /* => fnlength sans ";####" */
    421           pos -= tokmarker;      /* => fnlength sans "[DIR1.DIR2.etc]" */
    422           p = &(tokens[0][tokmarker]); /* offset of basename */
    423 
    424           if (!lstyle || pos == 0 || pos > 80) /* VMS filenames can't be longer than that */
    425           {
    426             lstyle = 0;
    427           }
    428           else if (numtoks == 1)
    429           {
    430             /* if VMS has been detected and there is only one token and that
    431              * token was a VMS filename then this is a multiline VMS LIST entry.
    432             */
    433             if (pos >= (sizeof(state.carryBuffer)-1))
    434               pos = (sizeof(state.carryBuffer)-1); /* shouldn't happen */
    435             memcpy( state.carryBuffer, p, pos );
    436             state.carryBufferLength = pos;
    437             return FTPJunkEntry; /* tell caller to treat as junk */
    438           }
    439           else if (isASCIIDigit(*tokens[1])) /* not no-privs message */
    440           {
    441             for (pos = 0; lstyle && pos < (toklen[1]); pos++)
    442             {
    443               if (!isASCIIDigit((tokens[1][pos])) && (tokens[1][pos]) != '/')
    444                 lstyle = 0;
    445             }
    446             if (lstyle && numtoks > 4) /* Multinet or UCX but not CMU */
    447             {
    448               for (pos = 1; lstyle && pos < (toklen[5]-1); pos++)
    449               {
    450                 p = &(tokens[5][pos]);
    451                 if (*p!='R' && *p!='W' && *p!='E' && *p!='D' && *p!=',')
    452                   lstyle = 0;
    453               }
    454             }
    455           }
    456         } /* passed initial tests */
    457       } /* else if ((tokens[0][toklen[0]-1]) != ';') */
    458 
    459       if (lstyle == 'V')
    460       {
    461         state.parsedOne = true;
    462         state.listStyle = lstyle;
    463 
    464         if (isASCIIDigit(*tokens[1]))  /* not permission denied etc */
    465         {
    466           /* strip leading directory name */
    467           if (*tokens[0] == '[') /* CMU server */
    468           {
    469             pos = toklen[0]-1;
    470             p = tokens[0]+1;
    471             while (*p != ']')
    472             {
    473               p++;
    474               pos--;
    475             }
    476             toklen[0] = --pos;
    477             tokens[0] = ++p;
    478           }
    479           pos = 0;
    480           while (pos < toklen[0] && (tokens[0][pos]) != ';')
    481             pos++;
    482 
    483           result.caseSensitive = true;
    484           result.type = FTPFileEntry;
    485           result.filename = tokens[0];
    486           result.filenameLength = pos;
    487 
    488           if (pos > 4)
    489           {
    490             p = &(tokens[0][pos-4]);
    491             if (p[0] == '.' && p[1] == 'D' && p[2] == 'I' && p[3] == 'R')
    492             {
    493               result.filenameLength -= 4;
    494               result.type = FTPDirectoryEntry;
    495             }
    496           }
    497 
    498           if (result.type != FTPDirectoryEntry)
    499           {
    500             /* #### or used/allocated form. If used/allocated form, then
    501              * 'used' is the size in bytes if and only if 'used'<=allocated.
    502              * If 'used' is size in bytes then it can be > 2^32
    503              * If 'used' is not size in bytes then it is size in blocks.
    504             */
    505             pos = 0;
    506             while (pos < toklen[1] && (tokens[1][pos]) != '/')
    507               pos++;
    508 
    509 /*
    510  * I've never seen size come back in bytes, its always in blocks, and
    511  * the following test fails. So, always perform the "size in blocks".
    512  * I'm leaving the "size in bytes" code if'd out in case we ever need
    513  * to re-instate it.
    514 */
    515 #if 0
    516             if (pos < toklen[1] && ( (pos<<1) > (toklen[1]-1) ||
    517                  (strtoul(tokens[1], (char **)0, 10) >
    518                   strtoul(tokens[1]+pos+1, (char **)0, 10))        ))
    519             {                                   /* size is in bytes */
    520               if (pos > (sizeof(result.fe_size)-1))
    521                 pos = sizeof(result.fe_size)-1;
    522               memcpy( result.fe_size, tokens[1], pos );
    523               result.fe_size[pos] = '\0';
    524             }
    525             else /* size is in blocks */
    526 #endif
    527             {
    528               /* size requires multiplication by blocksize.
    529                *
    530                * We could assume blocksize is 512 (like Lynx does) and
    531                * shift by 9, but that might not be right. Even if it
    532                * were, doing that wouldn't reflect what the file's
    533                * real size was. The sanest thing to do is not use the
    534                * LISTing's filesize, so we won't (like ftpmirror).
    535                *
    536                * ulltoa(((unsigned long long)fsz)<<9, result.fe_size, 10);
    537                *
    538                * A block is always 512 bytes on OpenVMS, compute size.
    539                * So its rounded up to the next block, so what, its better
    540                * than not showing the size at all.
    541                * A block is always 512 bytes on OpenVMS, compute size.
    542                * So its rounded up to the next block, so what, its better
    543                * than not showing the size at all.
    544               */
    545               uint64_t size = strtoul(tokens[1], NULL, 10) * 512;
    546               result.fileSize = String::number(size);
    547             }
    548 
    549           } /* if (result.type != FTPDirectoryEntry) */
    550 
    551           p = tokens[2] + 2;
    552           if (*p == '-')
    553             p++;
    554           tbuf[0] = p[0];
    555           tbuf[1] = toASCIILower(p[1]);
    556           tbuf[2] = toASCIILower(p[2]);
    557           month_num = 0;
    558           for (pos = 0; pos < (12*3); pos+=3)
    559           {
    560             if (tbuf[0] == month_names[pos+0] &&
    561                 tbuf[1] == month_names[pos+1] &&
    562                 tbuf[2] == month_names[pos+2])
    563               break;
    564             month_num++;
    565           }
    566           if (month_num >= 12)
    567             month_num = 0;
    568           result.modifiedTime.tm_mon = month_num;
    569           result.modifiedTime.tm_mday = atoi(tokens[2]);
    570           result.modifiedTime.tm_year = atoi(p+4); // NSPR wants year as XXXX
    571 
    572           p = tokens[3] + 2;
    573           if (*p == ':')
    574             p++;
    575           if (p[2] == ':')
    576             result.modifiedTime.tm_sec = atoi(p+3);
    577           result.modifiedTime.tm_hour = atoi(tokens[3]);
    578           result.modifiedTime.tm_min  = atoi(p);
    579 
    580           return result.type;
    581 
    582         } /* if (isASCIIDigit(*tokens[1])) */
    583 
    584         return FTPJunkEntry; /* junk */
    585 
    586       } /* if (lstyle == 'V') */
    587     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'V')) */
    588 #endif
    589 
    590     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    591 
    592 #if defined(SUPPORT_CMS)
    593     /* Virtual Machine/Conversational Monitor System (IBM Mainframe) */
    594     if (!lstyle && (!state.listStyle || state.listStyle == 'C'))  /* VM/CMS */
    595     {
    596       /* LISTing according to mirror.pl
    597        * Filename FileType  Fm Format Lrecl  Records Blocks Date      Time
    598        * LASTING  GLOBALV   A1 V      41     21     1       9/16/91   15:10:32
    599        * J43401   NETLOG    A0 V      77     1      1       9/12/91   12:36:04
    600        * PROFILE  EXEC      A1 V      17     3      1       9/12/91   12:39:07
    601        * DIRUNIX  SCRIPT    A1 V      77     1216   17      1/04/93   20:30:47
    602        * MAIL     PROFILE   A2 F      80     1      1       10/14/92  16:12:27
    603        * BADY2K   TEXT      A0 V      1      1      1       1/03/102  10:11:12
    604        * AUTHORS            A1 DIR    -      -      -       9/20/99   10:31:11
    605        *
    606        * LISTing from vm.marist.edu and vm.sc.edu
    607        * 220-FTPSERVE IBM VM Level 420 at VM.MARIST.EDU, 04:58:12 EDT WEDNESDAY 2002-07-10
    608        * AUTHORS           DIR        -          -          - 1999-09-20 10:31:11 -
    609        * HARRINGTON        DIR        -          -          - 1997-02-12 15:33:28 -
    610        * PICS              DIR        -          -          - 2000-10-12 15:43:23 -
    611        * SYSFILE           DIR        -          -          - 2000-07-20 17:48:01 -
    612        * WELCNVT  EXEC     V         72          9          1 1999-09-20 17:16:18 -
    613        * WELCOME  EREADME  F         80         21          1 1999-12-27 16:19:00 -
    614        * WELCOME  README   V         82         21          1 1999-12-27 16:19:04 -
    615        * README   ANONYMOU V         71         26          1 1997-04-02 12:33:20 TCP291
    616        * README   ANONYOLD V         71         15          1 1995-08-25 16:04:27 TCP291
    617       */
    618       if (numtoks >= 7 && (toklen[0]+toklen[1]) <= 16)
    619       {
    620         for (pos = 1; !lstyle && (pos+5) < numtoks; pos++)
    621         {
    622           p = tokens[pos];
    623           if ((toklen[pos] == 1 && (*p == 'F' || *p == 'V')) ||
    624               (toklen[pos] == 3 && *p == 'D' && p[1] == 'I' && p[2] == 'R'))
    625           {
    626             if (toklen[pos+5] == 8 && (tokens[pos+5][2]) == ':' &&
    627                                       (tokens[pos+5][5]) == ':'   )
    628             {
    629               p = tokens[pos+4];
    630               if ((toklen[pos+4] == 10 && p[4] == '-' && p[7] == '-') ||
    631                   (toklen[pos+4] >= 7 && toklen[pos+4] <= 9 &&
    632                             p[((p[1]!='/')?(2):(1))] == '/' &&
    633                             p[((p[1]!='/')?(5):(4))] == '/'))
    634                /* Y2K bugs possible ("7/06/102" or "13/02/101") */
    635               {
    636                 if ( (*tokens[pos+1] == '-' &&
    637                       *tokens[pos+2] == '-' &&
    638                       *tokens[pos+3] == '-')  ||
    639                       (isASCIIDigit(*tokens[pos+1]) &&
    640                        isASCIIDigit(*tokens[pos+2]) &&
    641                        isASCIIDigit(*tokens[pos+3])) )
    642                 {
    643                   lstyle = 'C';
    644                   tokmarker = pos;
    645                 }
    646               }
    647             }
    648           }
    649         } /* for (pos = 1; !lstyle && (pos+5) < numtoks; pos++) */
    650       } /* if (numtoks >= 7) */
    651 
    652       /* extra checking if first pass */
    653       if (lstyle && !state.listStyle)
    654       {
    655         for (pos = 0, p = tokens[0]; lstyle && pos < toklen[0]; pos++, p++)
    656         {
    657           if (isASCIIAlpha(*p) && toASCIIUpper(*p) != *p)
    658             lstyle = 0;
    659         }
    660         for (pos = tokmarker+1; pos <= tokmarker+3; pos++)
    661         {
    662           if (!(toklen[pos] == 1 && *tokens[pos] == '-'))
    663           {
    664             for (p = tokens[pos]; lstyle && p<(tokens[pos]+toklen[pos]); p++)
    665             {
    666               if (!isASCIIDigit(*p))
    667                 lstyle = 0;
    668             }
    669           }
    670         }
    671         for (pos = 0, p = tokens[tokmarker+4];
    672              lstyle && pos < toklen[tokmarker+4]; pos++, p++)
    673         {
    674           if (*p == '/')
    675           {
    676             /* There may be Y2K bugs in the date. Don't simplify to
    677              * pos != (len-3) && pos != (len-6) like time is done.
    678             */
    679             if ((tokens[tokmarker+4][1]) == '/')
    680             {
    681               if (pos != 1 && pos != 4)
    682                 lstyle = 0;
    683             }
    684             else if (pos != 2 && pos != 5)
    685               lstyle = 0;
    686           }
    687           else if (*p != '-' && !isASCIIDigit(*p))
    688             lstyle = 0;
    689           else if (*p == '-' && pos != 4 && pos != 7)
    690             lstyle = 0;
    691         }
    692         for (pos = 0, p = tokens[tokmarker+5];
    693              lstyle && pos < toklen[tokmarker+5]; pos++, p++)
    694         {
    695           if (*p != ':' && !isASCIIDigit(*p))
    696             lstyle = 0;
    697           else if (*p == ':' && pos != (toklen[tokmarker+5]-3)
    698                              && pos != (toklen[tokmarker+5]-6))
    699             lstyle = 0;
    700         }
    701       } /* initial if() */
    702 
    703       if (lstyle == 'C')
    704       {
    705         state.parsedOne = true;
    706         state.listStyle = lstyle;
    707 
    708         p = tokens[tokmarker+4];
    709         if (toklen[tokmarker+4] == 10) /* newstyle: YYYY-MM-DD format */
    710         {
    711           result.modifiedTime.tm_year = atoi(p+0) - 1900;
    712           result.modifiedTime.tm_mon  = atoi(p+5) - 1;
    713           result.modifiedTime.tm_mday = atoi(p+8);
    714         }
    715         else /* oldstyle: [M]M/DD/YY format */
    716         {
    717           pos = toklen[tokmarker+4];
    718           result.modifiedTime.tm_mon  = atoi(p) - 1;
    719           result.modifiedTime.tm_mday = atoi((p+pos)-5);
    720           result.modifiedTime.tm_year = atoi((p+pos)-2);
    721           if (result.modifiedTime.tm_year < 70)
    722             result.modifiedTime.tm_year += 100;
    723         }
    724 
    725         p = tokens[tokmarker+5];
    726         pos = toklen[tokmarker+5];
    727         result.modifiedTime.tm_hour  = atoi(p);
    728         result.modifiedTime.tm_min = atoi((p+pos)-5);
    729         result.modifiedTime.tm_sec = atoi((p+pos)-2);
    730 
    731         result.caseSensitive = true;
    732         result.filename = tokens[0];
    733         result.filenameLength = toklen[0];
    734         result.type  = FTPFileEntry;
    735 
    736         p = tokens[tokmarker];
    737         if (toklen[tokmarker] == 3 && *p=='D' && p[1]=='I' && p[2]=='R')
    738           result.type  = FTPDirectoryEntry;
    739 
    740         if ((/*newstyle*/ toklen[tokmarker+4] == 10 && tokmarker > 1) ||
    741             (/*oldstyle*/ toklen[tokmarker+4] != 10 && tokmarker > 2))
    742         {                            /* have a filetype column */
    743           char *dot;
    744           p = &(tokens[0][toklen[0]]);
    745           memcpy( &dot, &p, sizeof(dot) ); /* NASTY! */
    746           *dot++ = '.';
    747           p = tokens[1];
    748           for (pos = 0; pos < toklen[1]; pos++)
    749             *dot++ = *p++;
    750           result.filenameLength += 1 + toklen[1];
    751         }
    752 
    753         /* oldstyle LISTING:
    754          * files/dirs not on the 'A' minidisk are not RETRievable/CHDIRable
    755         if (toklen[tokmarker+4] != 10 && *tokens[tokmarker-1] != 'A')
    756           return FTPJunkEntry;
    757         */
    758 
    759         /* VM/CMS LISTings have no usable filesize field.
    760          * Have to use the 'SIZE' command for that.
    761         */
    762         return result.type;
    763 
    764       } /* if (lstyle == 'C' && (!state.listStyle || state.listStyle == lstyle)) */
    765     } /* VM/CMS */
    766 #endif
    767 
    768     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    769 
    770 #if defined(SUPPORT_DOS) /* WinNT DOS dirstyle */
    771     if (!lstyle && (!state.listStyle || state.listStyle == 'W'))
    772     {
    773       /*
    774        * "10-23-00  01:27PM       <DIR>          veronist"
    775        * "06-15-00  07:37AM       <DIR>          zoe"
    776        * "07-14-00  01:35PM              2094926 canprankdesk.tif"
    777        * "07-21-00  01:19PM                95077 Jon Kauffman Enjoys the Good Life.jpg"
    778        * "07-21-00  01:19PM                52275 Name Plate.jpg"
    779        * "07-14-00  01:38PM              2250540 Valentineoffprank-HiRes.jpg"
    780       */
    781       if ((numtoks >= 4) && toklen[0] == 8 && toklen[1] == 7 &&
    782           (*tokens[2] == '<' || isASCIIDigit(*tokens[2])) )
    783       {
    784         p = tokens[0];
    785         if ( isASCIIDigit(p[0]) && isASCIIDigit(p[1]) && p[2]=='-' &&
    786              isASCIIDigit(p[3]) && isASCIIDigit(p[4]) && p[5]=='-' &&
    787              isASCIIDigit(p[6]) && isASCIIDigit(p[7]) )
    788         {
    789           p = tokens[1];
    790           if ( isASCIIDigit(p[0]) && isASCIIDigit(p[1]) && p[2]==':' &&
    791                isASCIIDigit(p[3]) && isASCIIDigit(p[4]) &&
    792                (p[5]=='A' || p[5]=='P') && p[6]=='M')
    793           {
    794             lstyle = 'W';
    795             if (!state.listStyle)
    796             {
    797               p = tokens[2];
    798               /* <DIR> or <JUNCTION> */
    799               if (*p != '<' || p[toklen[2]-1] != '>')
    800               {
    801                 for (pos = 1; (lstyle && pos < toklen[2]); pos++)
    802                 {
    803                   if (!isASCIIDigit(*++p))
    804                     lstyle = 0;
    805                 }
    806               }
    807             }
    808           }
    809         }
    810       }
    811 
    812       if (lstyle == 'W')
    813       {
    814         state.parsedOne = true;
    815         state.listStyle = lstyle;
    816 
    817         p = &(line[linelen]); /* line end */
    818         result.caseSensitive = true;
    819         result.filename = tokens[3];
    820         result.filenameLength = p - tokens[3];
    821         result.type = FTPDirectoryEntry;
    822 
    823         if (*tokens[2] != '<') /* not <DIR> or <JUNCTION> */
    824         {
    825           // try to handle correctly spaces at the beginning of the filename
    826           // filesize (token[2]) must end at offset 38
    827           if (tokens[2] + toklen[2] - line == 38) {
    828             result.filename = &(line[39]);
    829             result.filenameLength = p - result.filename;
    830           }
    831           result.type = FTPFileEntry;
    832           pos = toklen[2];
    833           result.fileSize = String(tokens[2], pos);
    834         }
    835         else {
    836           // try to handle correctly spaces at the beginning of the filename
    837           // token[2] must begin at offset 24, the length is 5 or 10
    838           // token[3] must begin at offset 39 or higher
    839           if (tokens[2] - line == 24 && (toklen[2] == 5 || toklen[2] == 10) &&
    840               tokens[3] - line >= 39) {
    841             result.filename = &(line[39]);
    842             result.filenameLength = p - result.filename;
    843           }
    844 
    845           if ((tokens[2][1]) != 'D') /* not <DIR> */
    846           {
    847             result.type = FTPJunkEntry; /* unknown until junc for sure */
    848             if (result.filenameLength > 4)
    849             {
    850               p = result.filename;
    851               for (pos = result.filenameLength - 4; pos > 0; pos--)
    852               {
    853                 if (p[0] == ' ' && p[3] == ' ' && p[2] == '>' &&
    854                     (p[1] == '=' || p[1] == '-'))
    855                 {
    856                   result.type = FTPLinkEntry;
    857                   result.filenameLength = p - result.filename;
    858                   result.linkname = p + 4;
    859                   result.linknameLength = &(line[linelen])
    860                                      - result.linkname;
    861                   break;
    862                 }
    863                 p++;
    864               }
    865             }
    866           }
    867         }
    868 
    869         result.modifiedTime.tm_mon = atoi(tokens[0]+0);
    870         if (result.modifiedTime.tm_mon != 0)
    871         {
    872           result.modifiedTime.tm_mon--;
    873           result.modifiedTime.tm_mday = atoi(tokens[0]+3);
    874           result.modifiedTime.tm_year = atoi(tokens[0]+6);
    875           /* if year has only two digits then assume that
    876                00-79 is 2000-2079
    877                80-99 is 1980-1999 */
    878           if (result.modifiedTime.tm_year < 80)
    879             result.modifiedTime.tm_year += 2000;
    880           else if (result.modifiedTime.tm_year < 100)
    881             result.modifiedTime.tm_year += 1900;
    882         }
    883 
    884         result.modifiedTime.tm_hour = atoi(tokens[1]+0);
    885         result.modifiedTime.tm_min = atoi(tokens[1]+3);
    886         if ((tokens[1][5]) == 'P' && result.modifiedTime.tm_hour < 12)
    887           result.modifiedTime.tm_hour += 12;
    888 
    889         /* the caller should do this (if dropping "." and ".." is desired)
    890         if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
    891             (result.filenameLength == 1 || (result.filenameLength == 2 &&
    892                                       result.filename[1] == '.')))
    893           return FTPJunkEntry;
    894         */
    895 
    896         return result.type;
    897       } /* if (lstyle == 'W' && (!state.listStyle || state.listStyle == lstyle)) */
    898     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'W')) */
    899 #endif
    900 
    901     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    902 
    903 #if defined(SUPPORT_OS2)
    904     if (!lstyle && (!state.listStyle || state.listStyle == 'O')) /* OS/2 test */
    905     {
    906       /* 220 server IBM TCP/IP for OS/2 - FTP Server ver 23:04:36 on Jan 15 1997 ready.
    907       * fixed position, space padded columns. I have only a vague idea
    908       * of what the contents between col 18 and 34 might be: All I can infer
    909       * is that there may be attribute flags in there and there may be
    910       * a " DIR" in there.
    911       *
    912       *          1         2         3         4         5         6
    913       *0123456789012345678901234567890123456789012345678901234567890123456789
    914       *----- size -------|??????????????? MM-DD-YY|  HH:MM| nnnnnnnnn....
    915       *                 0  DIR            04-11-95   16:26  .
    916       *                 0  DIR            04-11-95   16:26  ..
    917       *                 0  DIR            04-11-95   16:26  ADDRESS
    918       *               612  RHSA           07-28-95   16:45  air_tra1.bag
    919       *               195  A              08-09-95   10:23  Alfa1.bag
    920       *                 0  RHS   DIR      04-11-95   16:26  ATTACH
    921       *               372  A              08-09-95   10:26  Aussie_1.bag
    922       *            310992                 06-28-94   09:56  INSTALL.EXE
    923       *                            1         2         3         4
    924       *                  01234567890123456789012345678901234567890123456789
    925       * dirlist from the mirror.pl project, col positions from Mozilla.
    926       */
    927       p = &(line[toklen[0]]);
    928       /* \s(\d\d-\d\d-\d\d)\s+(\d\d:\d\d)\s */
    929       if (numtoks >= 4 && toklen[0] <= 18 && isASCIIDigit(*tokens[0]) &&
    930          (linelen - toklen[0]) >= (53-18)                        &&
    931          p[18-18] == ' ' && p[34-18] == ' '                      &&
    932          p[37-18] == '-' && p[40-18] == '-' && p[43-18] == ' '   &&
    933          p[45-18] == ' ' && p[48-18] == ':' && p[51-18] == ' '   &&
    934          isASCIIDigit(p[35-18]) && isASCIIDigit(p[36-18])        &&
    935          isASCIIDigit(p[38-18]) && isASCIIDigit(p[39-18])        &&
    936          isASCIIDigit(p[41-18]) && isASCIIDigit(p[42-18])        &&
    937          isASCIIDigit(p[46-18]) && isASCIIDigit(p[47-18])        &&
    938          isASCIIDigit(p[49-18]) && isASCIIDigit(p[50-18])
    939       )
    940       {
    941         lstyle = 'O'; /* OS/2 */
    942         if (!state.listStyle)
    943         {
    944           for (pos = 1; lstyle && pos < toklen[0]; pos++)
    945           {
    946             if (!isASCIIDigit(tokens[0][pos]))
    947               lstyle = 0;
    948           }
    949         }
    950       }
    951 
    952       if (lstyle == 'O')
    953       {
    954         state.parsedOne = true;
    955         state.listStyle = lstyle;
    956 
    957         p = &(line[toklen[0]]);
    958 
    959         result.caseSensitive = true;
    960         result.filename = &p[53-18];
    961         result.filenameLength = (&(line[linelen_sans_wsp]))
    962                            - (result.filename);
    963         result.type = FTPFileEntry;
    964 
    965         /* I don't have a real listing to determine exact pos, so scan. */
    966         for (pos = (18-18); pos < ((35-18)-4); pos++)
    967         {
    968           if (p[pos+0] == ' ' && p[pos+1] == 'D' &&
    969               p[pos+2] == 'I' && p[pos+3] == 'R')
    970           {
    971             result.type = FTPDirectoryEntry;
    972             break;
    973           }
    974         }
    975 
    976         if (result.type != FTPDirectoryEntry)
    977         {
    978           pos = toklen[0];
    979           result.fileSize = String(tokens[0], pos);
    980         }
    981 
    982         result.modifiedTime.tm_mon = atoi(&p[35-18]) - 1;
    983         result.modifiedTime.tm_mday = atoi(&p[38-18]);
    984         result.modifiedTime.tm_year = atoi(&p[41-18]);
    985         if (result.modifiedTime.tm_year < 80)
    986           result.modifiedTime.tm_year += 100;
    987         result.modifiedTime.tm_hour = atoi(&p[46-18]);
    988         result.modifiedTime.tm_min = atoi(&p[49-18]);
    989 
    990         /* the caller should do this (if dropping "." and ".." is desired)
    991         if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
    992             (result.filenameLength == 1 || (result.filenameLength == 2 &&
    993                                       result.filename[1] == '.')))
    994           return FTPJunkEntry;
    995         */
    996 
    997         return result.type;
    998       } /* if (lstyle == 'O') */
    999 
   1000     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'O')) */
   1001 #endif
   1002 
   1003     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
   1004 
   1005 #if defined(SUPPORT_LSL)
   1006     if (!lstyle && (!state.listStyle || state.listStyle == 'U')) /* /bin/ls & co. */
   1007     {
   1008       /* UNIX-style listing, without inum and without blocks
   1009        * "-rw-r--r--   1 root     other        531 Jan 29 03:26 README"
   1010        * "dr-xr-xr-x   2 root     other        512 Apr  8  1994 etc"
   1011        * "dr-xr-xr-x   2 root     512 Apr  8  1994 etc"
   1012        * "lrwxrwxrwx   1 root     other          7 Jan 25 00:17 bin -> usr/bin"
   1013        * Also produced by Microsoft's FTP servers for Windows:
   1014        * "----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z"
   1015        * "d---------   1 owner    group               0 May  9 19:45 Softlib"
   1016        * Also WFTPD for MSDOS:
   1017        * "-rwxrwxrwx   1 noone    nogroup      322 Aug 19  1996 message.ftp"
   1018        * Hellsoft for NetWare:
   1019        * "d[RWCEMFA] supervisor            512       Jan 16 18:53    login"
   1020        * "-[RWCEMFA] rhesus             214059       Oct 20 15:27    cx.exe"
   1021        * Newer Hellsoft for NetWare: (netlab2.usu.edu)
   1022        * - [RWCEAFMS] NFAUUser               192 Apr 27 15:21 HEADER.html
   1023        * d [RWCEAFMS] jrd                    512 Jul 11 03:01 allupdates
   1024        * Also NetPresenz for the Mac:
   1025        * "-------r--         326  1391972  1392298 Nov 22  1995 MegaPhone.sit"
   1026        * "drwxrwxr-x               folder        2 May 10  1996 network"
   1027        * Protected directory:
   1028        * "drwx-wx-wt  2 root  wheel  512 Jul  1 02:15 incoming"
   1029        * uid/gid instead of username/groupname:
   1030        * "drwxr-xr-x  2 0  0  512 May 28 22:17 etc"
   1031       */
   1032 
   1033       bool isOldHellsoft = false;
   1034 
   1035       if (numtoks >= 6)
   1036       {
   1037         /* there are two perm formats (Hellsoft/NetWare and *IX strmode(3)).
   1038          * Scan for size column only if the perm format is one or the other.
   1039          */
   1040         if (toklen[0] == 1 || (tokens[0][1]) == '[')
   1041         {
   1042           if (*tokens[0] == 'd' || *tokens[0] == '-')
   1043           {
   1044             pos = toklen[0]-1;
   1045             p = tokens[0] + 1;
   1046             if (pos == 0)
   1047             {
   1048               p = tokens[1];
   1049               pos = toklen[1];
   1050             }
   1051             if ((pos == 9 || pos == 10)        &&
   1052                 (*p == '[' && p[pos-1] == ']') &&
   1053                 (p[1] == 'R' || p[1] == '-')   &&
   1054                 (p[2] == 'W' || p[2] == '-')   &&
   1055                 (p[3] == 'C' || p[3] == '-')   &&
   1056                 (p[4] == 'E' || p[4] == '-'))
   1057             {
   1058               /* rest is FMA[S] or AFM[S] */
   1059               lstyle = 'U'; /* very likely one of the NetWare servers */
   1060               if (toklen[0] == 10)
   1061                 isOldHellsoft = true;
   1062             }
   1063           }
   1064         }
   1065         else if ((toklen[0] == 10 || toklen[0] == 11)
   1066                    && strchr("-bcdlpsw?DFam", *tokens[0]))
   1067         {
   1068           p = &(tokens[0][1]);
   1069           if ((p[0] == 'r' || p[0] == '-') &&
   1070               (p[1] == 'w' || p[1] == '-') &&
   1071               (p[3] == 'r' || p[3] == '-') &&
   1072               (p[4] == 'w' || p[4] == '-') &&
   1073               (p[6] == 'r' || p[6] == '-') &&
   1074               (p[7] == 'w' || p[7] == '-'))
   1075             /* 'x'/p[9] can be S|s|x|-|T|t or implementation specific */
   1076           {
   1077             lstyle = 'U'; /* very likely /bin/ls */
   1078           }
   1079         }
   1080       }
   1081       if (lstyle == 'U') /* first token checks out */
   1082       {
   1083         lstyle = 0;
   1084         for (pos = (numtoks-5); !lstyle && pos > 1; pos--)
   1085         {
   1086           /* scan for: (\d+)\s+([A-Z][a-z][a-z])\s+
   1087            *  (\d\d\d\d|\d\:\d\d|\d\d\:\d\d|\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d)
   1088            *  \s+(.+)$
   1089           */
   1090           if (isASCIIDigit(*tokens[pos]) /* size */
   1091               /* (\w\w\w) */
   1092            && toklen[pos+1] == 3 && isASCIIAlpha(*tokens[pos+1]) &&
   1093               isASCIIAlpha(tokens[pos+1][1]) && isASCIIAlpha(tokens[pos+1][2])
   1094               /* (\d|\d\d) */
   1095            && isASCIIDigit(*tokens[pos+2]) &&
   1096                 (toklen[pos+2] == 1 ||
   1097                   (toklen[pos+2] == 2 && isASCIIDigit(tokens[pos+2][1])))
   1098            && toklen[pos+3] >= 4 && isASCIIDigit(*tokens[pos+3])
   1099               /* (\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
   1100            && (toklen[pos+3] <= 5 || (
   1101                (toklen[pos+3] == 7 || toklen[pos+3] == 8) &&
   1102                (tokens[pos+3][toklen[pos+3]-3]) == ':'))
   1103            && isASCIIDigit(tokens[pos+3][toklen[pos+3]-2])
   1104            && isASCIIDigit(tokens[pos+3][toklen[pos+3]-1])
   1105            && (
   1106               /* (\d\d\d\d) */
   1107                  ((toklen[pos+3] == 4 || toklen[pos+3] == 5) &&
   1108                   isASCIIDigit(tokens[pos+3][1]) &&
   1109                   isASCIIDigit(tokens[pos+3][2])  )
   1110               /* (\d\:\d\d|\d\:\d\d\:\d\d) */
   1111               || ((toklen[pos+3] == 4 || toklen[pos+3] == 7) &&
   1112                   (tokens[pos+3][1]) == ':' &&
   1113                   isASCIIDigit(tokens[pos+3][2]) && isASCIIDigit(tokens[pos+3][3]))
   1114               /* (\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
   1115               || ((toklen[pos+3] == 5 || toklen[pos+3] == 8) &&
   1116                   isASCIIDigit(tokens[pos+3][1]) && (tokens[pos+3][2]) == ':' &&
   1117                   isASCIIDigit(tokens[pos+3][3]) && isASCIIDigit(tokens[pos+3][4]))
   1118               )
   1119            )
   1120           {
   1121             lstyle = 'U'; /* assume /bin/ls or variant format */
   1122             tokmarker = pos;
   1123 
   1124             /* check that size is numeric */
   1125             p = tokens[tokmarker];
   1126             for (unsigned int i = 0; lstyle && i < toklen[tokmarker]; ++i)
   1127             {
   1128               if (!isASCIIDigit(*p++))
   1129                 lstyle = 0;
   1130             }
   1131             if (lstyle)
   1132             {
   1133               month_num = 0;
   1134               p = tokens[tokmarker+1];
   1135               for (unsigned int i = 0; i < (12*3); i+=3)
   1136               {
   1137                 if (p[0] == month_names[i+0] &&
   1138                     p[1] == month_names[i+1] &&
   1139                     p[2] == month_names[i+2])
   1140                   break;
   1141                 month_num++;
   1142               }
   1143               if (month_num >= 12)
   1144                 lstyle = 0;
   1145             }
   1146           } /* relative position test */
   1147         } /* for (pos = (numtoks-5); !lstyle && pos > 1; pos--) */
   1148       } /* if (lstyle == 'U') */
   1149 
   1150       if (lstyle == 'U')
   1151       {
   1152         state.parsedOne = true;
   1153         state.listStyle = lstyle;
   1154 
   1155         result.caseSensitive = false;
   1156         result.type = FTPJunkEntry;
   1157         if (*tokens[0] == 'd' || *tokens[0] == 'D')
   1158           result.type = FTPDirectoryEntry;
   1159         else if (*tokens[0] == 'l')
   1160           result.type = FTPLinkEntry;
   1161         else if (*tokens[0] == '-' || *tokens[0] == 'F')
   1162           result.type = FTPFileEntry; /* (hopefully a regular file) */
   1163 
   1164         if (result.type != FTPDirectoryEntry)
   1165         {
   1166           pos = toklen[tokmarker];
   1167           result.fileSize = String(tokens[tokmarker], pos);
   1168         }
   1169 
   1170         result.modifiedTime.tm_mon  = month_num;
   1171         result.modifiedTime.tm_mday = atoi(tokens[tokmarker+2]);
   1172         if (result.modifiedTime.tm_mday == 0)
   1173           result.modifiedTime.tm_mday++;
   1174 
   1175         p = tokens[tokmarker+3];
   1176         pos = (unsigned int)atoi(p);
   1177         if (p[1] == ':') /* one digit hour */
   1178           p--;
   1179         if (p[2] != ':') /* year */
   1180         {
   1181           result.modifiedTime.tm_year = pos;
   1182         }
   1183         else
   1184         {
   1185           result.modifiedTime.tm_hour = pos;
   1186           result.modifiedTime.tm_min  = atoi(p+3);
   1187           if (p[5] == ':')
   1188             result.modifiedTime.tm_sec = atoi(p+6);
   1189 
   1190           if (!state.now)
   1191           {
   1192             time_t now = time(NULL);
   1193             state.now = now * 1000000.0;
   1194 
   1195             // FIXME: This code has the year 2038 bug
   1196             gmtime_r(&now, &state.nowFTPTime);
   1197             state.nowFTPTime.tm_year += 1900;
   1198           }
   1199 
   1200           result.modifiedTime.tm_year = state.nowFTPTime.tm_year;
   1201           if ( (( state.nowFTPTime.tm_mon << 5) + state.nowFTPTime.tm_mday) <
   1202                ((result.modifiedTime.tm_mon << 5) + result.modifiedTime.tm_mday) )
   1203             result.modifiedTime.tm_year--;
   1204 
   1205         } /* time/year */
   1206 
   1207         // there is exactly 1 space between filename and previous token in all
   1208         // outputs except old Hellsoft
   1209         if (!isOldHellsoft)
   1210           result.filename = tokens[tokmarker+3] + toklen[tokmarker+3] + 1;
   1211         else
   1212           result.filename = tokens[tokmarker+4];
   1213 
   1214         result.filenameLength = (&(line[linelen]))
   1215                            - (result.filename);
   1216 
   1217         if (result.type == FTPLinkEntry && result.filenameLength > 4)
   1218         {
   1219           /* First try to use result.fe_size to find " -> " sequence.
   1220              This can give proper result for cases like "aaa -> bbb -> ccc". */
   1221           unsigned int fileSize = result.fileSize.toUInt();
   1222 
   1223           if (result.filenameLength > (fileSize + 4) &&
   1224               strncmp(result.filename + result.filenameLength - fileSize - 4, " -> ", 4) == 0)
   1225           {
   1226             result.linkname = result.filename + (result.filenameLength - fileSize);
   1227             result.linknameLength = (&(line[linelen])) - (result.linkname);
   1228             result.filenameLength -= fileSize + 4;
   1229           }
   1230           else
   1231           {
   1232             /* Search for sequence " -> " from the end for case when there are
   1233                more occurrences. F.e. if ftpd returns "a -> b -> c" assume
   1234                "a -> b" as a name. Powerusers can remove unnecessary parts
   1235                manually but there is no way to follow the link when some
   1236                essential part is missing. */
   1237             p = result.filename + (result.filenameLength - 5);
   1238             for (pos = (result.filenameLength - 5); pos > 0; pos--)
   1239             {
   1240               if (strncmp(p, " -> ", 4) == 0)
   1241               {
   1242                 result.linkname = p + 4;
   1243                 result.linknameLength = (&(line[linelen]))
   1244                                  - (result.linkname);
   1245                 result.filenameLength = pos;
   1246                 break;
   1247               }
   1248               p--;
   1249             }
   1250           }
   1251         }
   1252 
   1253 #if defined(SUPPORT_LSLF) /* some (very rare) servers return ls -lF */
   1254         if (result.filenameLength > 1)
   1255         {
   1256           p = result.filename[result.filenameLength-1];
   1257           pos = result.type;
   1258           if (pos == 'd') {
   1259              if (*p == '/') result.filenameLength--; /* directory */
   1260           } else if (pos == 'l') {
   1261              if (*p == '@') result.filenameLength--; /* symlink */
   1262           } else if (pos == 'f') {
   1263              if (*p == '*') result.filenameLength--; /* executable */
   1264           } else if (*p == '=' || *p == '%' || *p == '|') {
   1265             result.filenameLength--; /* socket, whiteout, fifo */
   1266           }
   1267         }
   1268 #endif
   1269 
   1270         /* the caller should do this (if dropping "." and ".." is desired)
   1271         if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
   1272             (result.filenameLength == 1 || (result.filenameLength == 2 &&
   1273                                       result.filename[1] == '.')))
   1274           return FTPJunkEntry;
   1275         */
   1276 
   1277         return result.type;
   1278 
   1279       } /* if (lstyle == 'U') */
   1280 
   1281     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'U')) */
   1282 #endif
   1283 
   1284     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
   1285 
   1286 #if defined(SUPPORT_W16) /* 16bit Windows */
   1287     if (!lstyle && (!state.listStyle || state.listStyle == 'w'))
   1288     {       /* old SuperTCP suite FTP server for Win3.1 */
   1289             /* old NetManage Chameleon TCP/IP suite FTP server for Win3.1 */
   1290       /*
   1291       * SuperTCP dirlist from the mirror.pl project
   1292       * mon/day/year separator may be '/' or '-'.
   1293       * .               <DIR>           11-16-94        17:16
   1294       * ..              <DIR>           11-16-94        17:16
   1295       * INSTALL         <DIR>           11-16-94        17:17
   1296       * CMT             <DIR>           11-21-94        10:17
   1297       * DESIGN1.DOC          11264      05-11-95        14:20
   1298       * README.TXT            1045      05-10-95        11:01
   1299       * WPKIT1.EXE          960338      06-21-95        17:01
   1300       * CMT.CSV                  0      07-06-95        14:56
   1301       *
   1302       * Chameleon dirlist guessed from lynx
   1303       * .               <DIR>      Nov 16 1994 17:16
   1304       * ..              <DIR>      Nov 16 1994 17:16
   1305       * INSTALL         <DIR>      Nov 16 1994 17:17
   1306       * CMT             <DIR>      Nov 21 1994 10:17
   1307       * DESIGN1.DOC     11264      May 11 1995 14:20   A
   1308       * README.TXT       1045      May 10 1995 11:01
   1309       * WPKIT1.EXE     960338      Jun 21 1995 17:01   R
   1310       * CMT.CSV             0      Jul 06 1995 14:56   RHA
   1311       */
   1312       if (numtoks >= 4 && toklen[0] < 13 &&
   1313           ((toklen[1] == 5 && *tokens[1] == '<') || isASCIIDigit(*tokens[1])) )
   1314       {
   1315         if (numtoks == 4
   1316          && (toklen[2] == 8 || toklen[2] == 9)
   1317          && (((tokens[2][2]) == '/' && (tokens[2][5]) == '/') ||
   1318              ((tokens[2][2]) == '-' && (tokens[2][5]) == '-'))
   1319          && (toklen[3] == 4 || toklen[3] == 5)
   1320          && (tokens[3][toklen[3]-3]) == ':'
   1321          && isASCIIDigit(tokens[2][0]) && isASCIIDigit(tokens[2][1])
   1322          && isASCIIDigit(tokens[2][3]) && isASCIIDigit(tokens[2][4])
   1323          && isASCIIDigit(tokens[2][6]) && isASCIIDigit(tokens[2][7])
   1324          && (toklen[2] < 9 || isASCIIDigit(tokens[2][8]))
   1325          && isASCIIDigit(tokens[3][toklen[3]-1]) && isASCIIDigit(tokens[3][toklen[3]-2])
   1326          && isASCIIDigit(tokens[3][toklen[3]-4]) && isASCIIDigit(*tokens[3])
   1327          )
   1328         {
   1329           lstyle = 'w';
   1330         }
   1331         else if ((numtoks == 6 || numtoks == 7)
   1332          && toklen[2] == 3 && toklen[3] == 2
   1333          && toklen[4] == 4 && toklen[5] == 5
   1334          && (tokens[5][2]) == ':'
   1335          && isASCIIAlpha(tokens[2][0]) && isASCIIAlpha(tokens[2][1])
   1336          &&                          isASCIIAlpha(tokens[2][2])
   1337          && isASCIIDigit(tokens[3][0]) && isASCIIDigit(tokens[3][1])
   1338          && isASCIIDigit(tokens[4][0]) && isASCIIDigit(tokens[4][1])
   1339          && isASCIIDigit(tokens[4][2]) && isASCIIDigit(tokens[4][3])
   1340          && isASCIIDigit(tokens[5][0]) && isASCIIDigit(tokens[5][1])
   1341          && isASCIIDigit(tokens[5][3]) && isASCIIDigit(tokens[5][4])
   1342          /* could also check that (&(tokens[5][5]) - tokens[2]) == 17 */
   1343         )
   1344         {
   1345           lstyle = 'w';
   1346         }
   1347         if (lstyle && state.listStyle != lstyle) /* first time */
   1348         {
   1349           p = tokens[1];
   1350           if (toklen[1] != 5 || p[0] != '<' || p[1] != 'D' ||
   1351                  p[2] != 'I' || p[3] != 'R' || p[4] != '>')
   1352           {
   1353             for (pos = 0; lstyle && pos < toklen[1]; pos++)
   1354             {
   1355               if (!isASCIIDigit(*p++))
   1356                 lstyle = 0;
   1357             }
   1358           } /* not <DIR> */
   1359         } /* if (first time) */
   1360       } /* if (numtoks == ...) */
   1361 
   1362       if (lstyle == 'w')
   1363       {
   1364         state.parsedOne = true;
   1365         state.listStyle = lstyle;
   1366 
   1367         result.caseSensitive = true;
   1368         result.filename = tokens[0];
   1369         result.filenameLength = toklen[0];
   1370         result.type = FTPDirectoryEntry;
   1371 
   1372         p = tokens[1];
   1373         if (isASCIIDigit(*p))
   1374         {
   1375           result.type = FTPFileEntry;
   1376           pos = toklen[1];
   1377           result.fileSize = String(p, pos);
   1378         }
   1379 
   1380         p = tokens[2];
   1381         if (toklen[2] == 3) /* Chameleon */
   1382         {
   1383           tbuf[0] = toASCIIUpper(p[0]);
   1384           tbuf[1] = toASCIILower(p[1]);
   1385           tbuf[2] = toASCIILower(p[2]);
   1386           for (pos = 0; pos < (12*3); pos+=3)
   1387           {
   1388             if (tbuf[0] == month_names[pos+0] &&
   1389                 tbuf[1] == month_names[pos+1] &&
   1390                 tbuf[2] == month_names[pos+2])
   1391             {
   1392               result.modifiedTime.tm_mon = pos/3;
   1393               result.modifiedTime.tm_mday = atoi(tokens[3]);
   1394               result.modifiedTime.tm_year = atoi(tokens[4]) - 1900;
   1395               break;
   1396             }
   1397           }
   1398           pos = 5; /* Chameleon toknum of date field */
   1399         }
   1400         else
   1401         {
   1402           result.modifiedTime.tm_mon = atoi(p+0)-1;
   1403           result.modifiedTime.tm_mday = atoi(p+3);
   1404           result.modifiedTime.tm_year = atoi(p+6);
   1405           if (result.modifiedTime.tm_year < 80) /* SuperTCP */
   1406             result.modifiedTime.tm_year += 100;
   1407 
   1408           pos = 3; /* SuperTCP toknum of date field */
   1409         }
   1410 
   1411         result.modifiedTime.tm_hour = atoi(tokens[pos]);
   1412         result.modifiedTime.tm_min = atoi(&(tokens[pos][toklen[pos]-2]));
   1413 
   1414         /* the caller should do this (if dropping "." and ".." is desired)
   1415         if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
   1416             (result.filenameLength == 1 || (result.filenameLength == 2 &&
   1417                                       result.filename[1] == '.')))
   1418           return FTPJunkEntry;
   1419         */
   1420 
   1421         return result.type;
   1422       } /* (lstyle == 'w') */
   1423 
   1424     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'w'))  */
   1425 #endif
   1426 
   1427     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
   1428 
   1429 #if defined(SUPPORT_DLS) /* dls -dtR */
   1430     if (!lstyle &&
   1431        (state.listStyle == 'D' || (!state.listStyle && state.numLines == 1)))
   1432        /* /bin/dls lines have to be immediately recognizable (first line) */
   1433     {
   1434       /* I haven't seen an FTP server that delivers a /bin/dls listing,
   1435        * but can infer the format from the lynx and mirror.pl projects.
   1436        * Both formats are supported.
   1437        *
   1438        * Lynx says:
   1439        * README              763  Information about this server\0
   1440        * bin/                  -  \0
   1441        * etc/                  =  \0
   1442        * ls-lR                 0  \0
   1443        * ls-lR.Z               3  \0
   1444        * pub/                  =  Public area\0
   1445        * usr/                  -  \0
   1446        * morgan               14  -> ../real/morgan\0
   1447        * TIMIT.mostlikely.Z\0
   1448        *                   79215  \0
   1449        *
   1450        * mirror.pl says:
   1451        * filename:  ^(\S*)\s+
   1452        * size:      (\-|\=|\d+)\s+
   1453        * month/day: ((\w\w\w\s+\d+|\d+\s+\w\w\w)\s+
   1454        * time/year: (\d+:\d+|\d\d\d\d))\s+
   1455        * rest:      (.+)
   1456        *
   1457        * README              763  Jul 11 21:05  Information about this server
   1458        * bin/                  -  Apr 28  1994
   1459        * etc/                  =  11 Jul 21:04
   1460        * ls-lR                 0   6 Aug 17:14
   1461        * ls-lR.Z               3  05 Sep 1994
   1462        * pub/                  =  Jul 11 21:04  Public area
   1463        * usr/                  -  Sep  7 09:39
   1464        * morgan               14  Apr 18 09:39  -> ../real/morgan
   1465        * TIMIT.mostlikely.Z
   1466        *                   79215  Jul 11 21:04
   1467       */
   1468       if (!state.listStyle && line[linelen-1] == ':' &&
   1469           linelen >= 2 && toklen[numtoks-1] != 1)
   1470       {
   1471         /* code in mirror.pl suggests that a listing may be preceded
   1472          * by a PWD line in the form "/some/dir/names/here:"
   1473          * but does not necessarily begin with '/'. *sigh*
   1474         */
   1475         pos = 0;
   1476         p = line;
   1477         while (pos < (linelen-1))
   1478         {
   1479           /* illegal (or extremely unusual) chars in a dirspec */
   1480           if (*p == '<' || *p == '|' || *p == '>' ||
   1481               *p == '?' || *p == '*' || *p == '\\')
   1482             break;
   1483           if (*p == '/' && pos < (linelen-2) && p[1] == '/')
   1484             break;
   1485           pos++;
   1486           p++;
   1487         }
   1488         if (pos == (linelen-1))
   1489         {
   1490           state.listStyle = 'D';
   1491           return FTPJunkEntry;
   1492         }
   1493       }
   1494 
   1495       if (!lstyle && numtoks >= 2)
   1496       {
   1497         pos = 22; /* pos of (\d+|-|=) if this is not part of a multiline */
   1498         if (state.listStyle && carry_buf_len) /* first is from previous line */
   1499           pos = toklen[1]-1; /* and is 'as-is' (may contain whitespace) */
   1500 
   1501         if (linelen > pos)
   1502         {
   1503           p = &line[pos];
   1504           if ((*p == '-' || *p == '=' || isASCIIDigit(*p)) &&
   1505               ((linelen == (pos+1)) ||
   1506                (linelen >= (pos+3) && p[1] == ' ' && p[2] == ' ')) )
   1507           {
   1508             tokmarker = 1;
   1509             if (!carry_buf_len)
   1510             {
   1511               pos = 1;
   1512               while (pos < numtoks && (tokens[pos]+toklen[pos]) < (&line[23]))
   1513                 pos++;
   1514               tokmarker = 0;
   1515               if ((tokens[pos]+toklen[pos]) == (&line[23]))
   1516                 tokmarker = pos;
   1517             }
   1518             if (tokmarker)
   1519             {
   1520               lstyle = 'D';
   1521               if (*tokens[tokmarker] == '-' || *tokens[tokmarker] == '=')
   1522               {
   1523                 if (toklen[tokmarker] != 1 ||
   1524                    (tokens[tokmarker-1][toklen[tokmarker-1]-1]) != '/')
   1525                   lstyle = 0;
   1526               }
   1527               else
   1528               {
   1529                 for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++)
   1530                 {
   1531                   if (!isASCIIDigit(tokens[tokmarker][pos]))
   1532                     lstyle = 0;
   1533                 }
   1534               }
   1535               if (lstyle && !state.listStyle) /* first time */
   1536               {
   1537                 /* scan for illegal (or incredibly unusual) chars in fname */
   1538                 for (p = tokens[0]; lstyle &&
   1539                      p < &(tokens[tokmarker-1][toklen[tokmarker-1]]); p++)
   1540                 {
   1541                   if (*p == '<' || *p == '|' || *p == '>' ||
   1542                       *p == '?' || *p == '*' || *p == '/' || *p == '\\')
   1543                     lstyle = 0;
   1544                 }
   1545               }
   1546 
   1547             } /* size token found */
   1548           } /* expected chars behind expected size token */
   1549         } /* if (linelen > pos) */
   1550       } /* if (!lstyle && numtoks >= 2) */
   1551 
   1552       if (!lstyle && state.listStyle == 'D' && !carry_buf_len)
   1553       {
   1554         /* the filename of a multi-line entry can be identified
   1555          * correctly only if dls format had been previously established.
   1556          * This should always be true because there should be entries
   1557          * for '.' and/or '..' and/or CWD that precede the rest of the
   1558          * listing.
   1559         */
   1560         pos = linelen;
   1561         if (pos > (sizeof(state.carryBuffer)-1))
   1562           pos = sizeof(state.carryBuffer)-1;
   1563         memcpy( state.carryBuffer, line, pos );
   1564         state.carryBufferLength = pos;
   1565         return FTPJunkEntry;
   1566       }
   1567 
   1568       if (lstyle == 'D')
   1569       {
   1570         state.parsedOne = true;
   1571         state.listStyle = lstyle;
   1572 
   1573         p = &(tokens[tokmarker-1][toklen[tokmarker-1]]);
   1574         result.filename = tokens[0];
   1575         result.filenameLength = p - tokens[0];
   1576         result.type  = FTPFileEntry;
   1577 
   1578         if (result.filename[result.filenameLength-1] == '/')
   1579         {
   1580           if (result.linknameLength == 1)
   1581             result.type = FTPJunkEntry;
   1582           else
   1583           {
   1584             result.filenameLength--;
   1585             result.type  = FTPDirectoryEntry;
   1586           }
   1587         }
   1588         else if (isASCIIDigit(*tokens[tokmarker]))
   1589         {
   1590           pos = toklen[tokmarker];
   1591           result.fileSize = String(tokens[tokmarker], pos);
   1592         }
   1593 
   1594         if ((tokmarker+3) < numtoks &&
   1595               (&(tokens[numtoks-1][toklen[numtoks-1]]) -
   1596                tokens[tokmarker+1]) >= (1+1+3+1+4) )
   1597         {
   1598           pos = (tokmarker+3);
   1599           p = tokens[pos];
   1600           pos = toklen[pos];
   1601 
   1602           if ((pos == 4 || pos == 5)
   1603           &&  isASCIIDigit(*p) && isASCIIDigit(p[pos-1]) && isASCIIDigit(p[pos-2])
   1604           &&  ((pos == 5 && p[2] == ':') ||
   1605                (pos == 4 && (isASCIIDigit(p[1]) || p[1] == ':')))
   1606              )
   1607           {
   1608             month_num = tokmarker+1; /* assumed position of month field */
   1609             pos = tokmarker+2;       /* assumed position of mday field */
   1610             if (isASCIIDigit(*tokens[month_num])) /* positions are reversed */
   1611             {
   1612               month_num++;
   1613               pos--;
   1614             }
   1615             p = tokens[month_num];
   1616             if (isASCIIDigit(*tokens[pos])
   1617             && (toklen[pos] == 1 ||
   1618                   (toklen[pos] == 2 && isASCIIDigit(tokens[pos][1])))
   1619             && toklen[month_num] == 3
   1620             && isASCIIAlpha(*p) && isASCIIAlpha(p[1]) && isASCIIAlpha(p[2])  )
   1621             {
   1622               pos = atoi(tokens[pos]);
   1623               if (pos > 0 && pos <= 31)
   1624               {
   1625                 result.modifiedTime.tm_mday = pos;
   1626                 month_num = 1;
   1627                 for (pos = 0; pos < (12*3); pos+=3)
   1628                 {
   1629                   if (p[0] == month_names[pos+0] &&
   1630                       p[1] == month_names[pos+1] &&
   1631                       p[2] == month_names[pos+2])
   1632                     break;
   1633                   month_num++;
   1634                 }
   1635                 if (month_num > 12)
   1636                   result.modifiedTime.tm_mday = 0;
   1637                 else
   1638                   result.modifiedTime.tm_mon = month_num - 1;
   1639               }
   1640             }
   1641             if (result.modifiedTime.tm_mday)
   1642             {
   1643               tokmarker += 3; /* skip mday/mon/yrtime (to find " -> ") */
   1644               p = tokens[tokmarker];
   1645 
   1646               pos = atoi(p);
   1647               if (pos > 24)
   1648                 result.modifiedTime.tm_year = pos-1900;
   1649               else
   1650               {
   1651                 if (p[1] == ':')
   1652                   p--;
   1653                 result.modifiedTime.tm_hour = pos;
   1654                 result.modifiedTime.tm_min = atoi(p+3);
   1655                 if (!state.now)
   1656                 {
   1657                   time_t now = time(NULL);
   1658                   state.now = now * 1000000.0;
   1659 
   1660                   // FIXME: This code has the year 2038 bug
   1661                   gmtime_r(&now, &state.nowFTPTime);
   1662                   state.nowFTPTime.tm_year += 1900;
   1663                 }
   1664                 result.modifiedTime.tm_year = state.nowFTPTime.tm_year;
   1665                 if ( (( state.nowFTPTime.tm_mon  << 4) + state.nowFTPTime.tm_mday) <
   1666                      ((result.modifiedTime.tm_mon << 4) + result.modifiedTime.tm_mday) )
   1667                   result.modifiedTime.tm_year--;
   1668               } /* got year or time */
   1669             } /* got month/mday */
   1670           } /* may have year or time */
   1671         } /* enough remaining to possibly have date/time */
   1672 
   1673         if (numtoks > (tokmarker+2))
   1674         {
   1675           pos = tokmarker+1;
   1676           p = tokens[pos];
   1677           if (toklen[pos] == 2 && *p == '-' && p[1] == '>')
   1678           {
   1679             p = &(tokens[numtoks-1][toklen[numtoks-1]]);
   1680             result.type  = FTPLinkEntry;
   1681             result.linkname = tokens[pos+1];
   1682             result.linknameLength = p - result.linkname;
   1683             if (result.linknameLength > 1 &&
   1684                 result.linkname[result.linknameLength-1] == '/')
   1685               result.linknameLength--;
   1686           }
   1687         } /* if (numtoks > (tokmarker+2)) */
   1688 
   1689         /* the caller should do this (if dropping "." and ".." is desired)
   1690         if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
   1691             (result.filenameLength == 1 || (result.filenameLength == 2 &&
   1692                                       result.filename[1] == '.')))
   1693           return FTPJunkEntry;
   1694         */
   1695 
   1696         return result.type;
   1697 
   1698       } /* if (lstyle == 'D') */
   1699     } /* if (!lstyle && (!state.listStyle || state.listStyle == 'D')) */
   1700 #endif
   1701 
   1702     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
   1703 
   1704   } /* if (linelen > 0) */
   1705 
   1706   return ParsingFailed(state);
   1707 }
   1708 
   1709 } // namespace WebCore
   1710 
   1711 #endif // ENABLE(FTPDIR)
   1712