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