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