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