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