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