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