source: trunk/essentials/net-misc/wget/src/ftp-ls.c

Last change on this file was 3440, checked in by bird, 18 years ago

wget 1.10.2

File size: 25.9 KB
Line 
1/* Parsing FTP `ls' output.
2 Copyright (C) 1995, 1996, 1997, 2000, 2001
3 Free Software Foundation, Inc.
4
5This file is part of GNU Wget.
6
7GNU Wget is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12GNU Wget is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with Wget; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21In addition, as a special exception, the Free Software Foundation
22gives permission to link the code of its release of Wget with the
23OpenSSL project's "OpenSSL" library (or with modified versions of it
24that use the same license as the "OpenSSL" library), and distribute
25the linked executables. You must obey the GNU General Public License
26in all respects for all of the code used other than "OpenSSL". If you
27modify this file, you may extend this exception to your version of the
28file, but you are not obligated to do so. If you do not wish to do
29so, delete this exception statement from your version. */
30
31#include <config.h>
32
33#include <stdio.h>
34#include <stdlib.h>
35#ifdef HAVE_STRING_H
36# include <string.h>
37#else
38# include <strings.h>
39#endif
40#ifdef HAVE_UNISTD_H
41# include <unistd.h>
42#endif
43#include <sys/types.h>
44#include <errno.h>
45
46#include "wget.h"
47#include "utils.h"
48#include "ftp.h"
49#include "url.h"
50#include "convert.h" /* for html_quote_string prototype */
51
52extern FILE *output_stream;
53
54/* Converts symbolic permissions to number-style ones, e.g. string
55 rwxr-xr-x to 755. For now, it knows nothing of
56 setuid/setgid/sticky. ACLs are ignored. */
57static int
58symperms (const char *s)
59{
60 int perms = 0, i;
61
62 if (strlen (s) < 9)
63 return 0;
64 for (i = 0; i < 3; i++, s += 3)
65 {
66 perms <<= 3;
67 perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
68 (s[2] == 'x' || s[2] == 's'));
69 }
70 return perms;
71}
72
73
74/* Cleans a line of text so that it can be consistently parsed. Destroys
75 <CR> and <LF> in case that thay occur at the end of the line and
76 replaces all <TAB> character with <SPACE>. Returns the length of the
77 modified line. */
78static int
79clean_line(char *line)
80{
81 int len = strlen (line);
82 if (!len) return 0;
83 if (line[len - 1] == '\n')
84 line[--len] = '\0';
85 if (line[len - 1] == '\r')
86 line[--len] = '\0';
87 for ( ; *line ; line++ ) if (*line == '\t') *line = ' ';
88 return len;
89}
90
91/* Convert the Un*x-ish style directory listing stored in FILE to a
92 linked list of fileinfo (system-independent) entries. The contents
93 of FILE are considered to be produced by the standard Unix `ls -la'
94 output (whatever that might be). BSD (no group) and SYSV (with
95 group) listings are handled.
96
97 The time stamps are stored in a separate variable, time_t
98 compatible (I hope). The timezones are ignored. */
99static struct fileinfo *
100ftp_parse_unix_ls (const char *file, int ignore_perms)
101{
102 FILE *fp;
103 static const char *months[] = {
104 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
105 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
106 };
107 int next, len, i, error, ignore;
108 int year, month, day; /* for time analysis */
109 int hour, min, sec;
110 struct tm timestruct, *tnow;
111 time_t timenow;
112
113 char *line, *tok, *ptok; /* tokenizer */
114 struct fileinfo *dir, *l, cur; /* list creation */
115
116 fp = fopen (file, "rb");
117 if (!fp)
118 {
119 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
120 return NULL;
121 }
122 dir = l = NULL;
123
124 /* Line loop to end of file: */
125 while ((line = read_whole_line (fp)) != NULL)
126 {
127 len = clean_line (line);
128 /* Skip if total... */
129 if (!strncasecmp (line, "total", 5))
130 {
131 xfree (line);
132 continue;
133 }
134 /* Get the first token (permissions). */
135 tok = strtok (line, " ");
136 if (!tok)
137 {
138 xfree (line);
139 continue;
140 }
141
142 cur.name = NULL;
143 cur.linkto = NULL;
144
145 /* Decide whether we deal with a file or a directory. */
146 switch (*tok)
147 {
148 case '-':
149 cur.type = FT_PLAINFILE;
150 DEBUGP (("PLAINFILE; "));
151 break;
152 case 'd':
153 cur.type = FT_DIRECTORY;
154 DEBUGP (("DIRECTORY; "));
155 break;
156 case 'l':
157 cur.type = FT_SYMLINK;
158 DEBUGP (("SYMLINK; "));
159 break;
160 default:
161 cur.type = FT_UNKNOWN;
162 DEBUGP (("UNKNOWN; "));
163 break;
164 }
165
166 if (ignore_perms)
167 {
168 switch (cur.type)
169 {
170 case FT_PLAINFILE:
171 cur.perms = 0644;
172 break;
173 case FT_DIRECTORY:
174 cur.perms = 0755;
175 break;
176 default:
177 /*cur.perms = 1023;*/ /* #### What is this? --hniksic */
178 cur.perms = 0644;
179 }
180 DEBUGP (("implicit perms %0o; ", cur.perms));
181 }
182 else
183 {
184 cur.perms = symperms (tok + 1);
185 DEBUGP (("perms %0o; ", cur.perms));
186 }
187
188 error = ignore = 0; /* Erroneous and ignoring entries are
189 treated equally for now. */
190 year = hour = min = sec = 0; /* Silence the compiler. */
191 month = day = 0;
192 next = -1;
193 /* While there are tokens on the line, parse them. Next is the
194 number of tokens left until the filename.
195
196 Use the month-name token as the "anchor" (the place where the
197 position wrt the file name is "known"). When a month name is
198 encountered, `next' is set to 5. Also, the preceding
199 characters are parsed to get the file size.
200
201 This tactic is quite dubious when it comes to
202 internationalization issues (non-English month names), but it
203 works for now. */
204 ptok = line;
205 while (ptok = tok,
206 (tok = strtok (NULL, " ")) != NULL)
207 {
208 --next;
209 if (next < 0) /* a month name was not encountered */
210 {
211 for (i = 0; i < 12; i++)
212 if (!strcmp (tok, months[i]))
213 break;
214 /* If we got a month, it means the token before it is the
215 size, and the filename is three tokens away. */
216 if (i != 12)
217 {
218 wgint size;
219
220 /* Back up to the beginning of the previous token
221 and parse it with str_to_wgint. */
222 char *t = ptok;
223 while (t > line && ISDIGIT (*t))
224 --t;
225 if (t == line)
226 {
227 /* Something has gone wrong during parsing. */
228 error = 1;
229 break;
230 }
231 errno = 0;
232 size = str_to_wgint (t, NULL, 10);
233 if (size == WGINT_MAX && errno == ERANGE)
234 /* Out of range -- ignore the size. #### Should
235 we refuse to start the download. */
236 cur.size = 0;
237 else
238 cur.size = size;
239
240 month = i;
241 next = 5;
242 DEBUGP (("month: %s; ", months[month]));
243 }
244 }
245 else if (next == 4) /* days */
246 {
247 if (tok[1]) /* two-digit... */
248 day = 10 * (*tok - '0') + tok[1] - '0';
249 else /* ...or one-digit */
250 day = *tok - '0';
251 DEBUGP (("day: %d; ", day));
252 }
253 else if (next == 3)
254 {
255 /* This ought to be either the time, or the year. Let's
256 be flexible!
257
258 If we have a number x, it's a year. If we have x:y,
259 it's hours and minutes. If we have x:y:z, z are
260 seconds. */
261 year = 0;
262 min = hour = sec = 0;
263 /* We must deal with digits. */
264 if (ISDIGIT (*tok))
265 {
266 /* Suppose it's year. */
267 for (; ISDIGIT (*tok); tok++)
268 year = (*tok - '0') + 10 * year;
269 if (*tok == ':')
270 {
271 /* This means these were hours! */
272 hour = year;
273 year = 0;
274 ++tok;
275 /* Get the minutes... */
276 for (; ISDIGIT (*tok); tok++)
277 min = (*tok - '0') + 10 * min;
278 if (*tok == ':')
279 {
280 /* ...and the seconds. */
281 ++tok;
282 for (; ISDIGIT (*tok); tok++)
283 sec = (*tok - '0') + 10 * sec;
284 }
285 }
286 }
287 if (year)
288 DEBUGP (("year: %d (no tm); ", year));
289 else
290 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
291 }
292 else if (next == 2) /* The file name */
293 {
294 int fnlen;
295 char *p;
296
297 /* Since the file name may contain a SPC, it is possible
298 for strtok to handle it wrong. */
299 fnlen = strlen (tok);
300 if (fnlen < len - (tok - line))
301 {
302 /* So we have a SPC in the file name. Restore the
303 original. */
304 tok[fnlen] = ' ';
305 /* If the file is a symbolic link, it should have a
306 ` -> ' somewhere. */
307 if (cur.type == FT_SYMLINK)
308 {
309 p = strstr (tok, " -> ");
310 if (!p)
311 {
312 error = 1;
313 break;
314 }
315 cur.linkto = xstrdup (p + 4);
316 DEBUGP (("link to: %s\n", cur.linkto));
317 /* And separate it from the file name. */
318 *p = '\0';
319 }
320 }
321 /* If we have the filename, add it to the list of files or
322 directories. */
323 /* "." and ".." are an exception! */
324 if (!strcmp (tok, ".") || !strcmp (tok, ".."))
325 {
326 DEBUGP (("\nIgnoring `.' and `..'; "));
327 ignore = 1;
328 break;
329 }
330 /* Some FTP sites choose to have ls -F as their default
331 LIST output, which marks the symlinks with a trailing
332 `@', directory names with a trailing `/' and
333 executables with a trailing `*'. This is no problem
334 unless encountering a symbolic link ending with `@',
335 or an executable ending with `*' on a server without
336 default -F output. I believe these cases are very
337 rare. */
338 fnlen = strlen (tok); /* re-calculate `fnlen' */
339 cur.name = (char *)xmalloc (fnlen + 1);
340 memcpy (cur.name, tok, fnlen + 1);
341 if (fnlen)
342 {
343 if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
344 {
345 cur.name[fnlen - 1] = '\0';
346 DEBUGP (("trailing `/' on dir.\n"));
347 }
348 else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
349 {
350 cur.name[fnlen - 1] = '\0';
351 DEBUGP (("trailing `@' on link.\n"));
352 }
353 else if (cur.type == FT_PLAINFILE
354 && (cur.perms & 0111)
355 && cur.name[fnlen - 1] == '*')
356 {
357 cur.name[fnlen - 1] = '\0';
358 DEBUGP (("trailing `*' on exec.\n"));
359 }
360 } /* if (fnlen) */
361 else
362 error = 1;
363 break;
364 }
365 else
366 abort ();
367 } /* while */
368
369 if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
370 error = 1;
371
372 DEBUGP (("\n"));
373
374 if (error || ignore)
375 {
376 DEBUGP (("Skipping.\n"));
377 xfree_null (cur.name);
378 xfree_null (cur.linkto);
379 xfree (line);
380 continue;
381 }
382
383 if (!dir)
384 {
385 l = dir = xnew (struct fileinfo);
386 memcpy (l, &cur, sizeof (cur));
387 l->prev = l->next = NULL;
388 }
389 else
390 {
391 cur.prev = l;
392 l->next = xnew (struct fileinfo);
393 l = l->next;
394 memcpy (l, &cur, sizeof (cur));
395 l->next = NULL;
396 }
397 /* Get the current time. */
398 timenow = time (NULL);
399 tnow = localtime (&timenow);
400 /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr). */
401 timestruct.tm_sec = sec;
402 timestruct.tm_min = min;
403 timestruct.tm_hour = hour;
404 timestruct.tm_mday = day;
405 timestruct.tm_mon = month;
406 if (year == 0)
407 {
408 /* Some listings will not specify the year if it is "obvious"
409 that the file was from the previous year. E.g. if today
410 is 97-01-12, and you see a file of Dec 15th, its year is
411 1996, not 1997. Thanks to Vladimir Volovich for
412 mentioning this! */
413 if (month > tnow->tm_mon)
414 timestruct.tm_year = tnow->tm_year - 1;
415 else
416 timestruct.tm_year = tnow->tm_year;
417 }
418 else
419 timestruct.tm_year = year;
420 if (timestruct.tm_year >= 1900)
421 timestruct.tm_year -= 1900;
422 timestruct.tm_wday = 0;
423 timestruct.tm_yday = 0;
424 timestruct.tm_isdst = -1;
425 l->tstamp = mktime (&timestruct); /* store the time-stamp */
426
427 xfree (line);
428 }
429
430 fclose (fp);
431 return dir;
432}
433
434static struct fileinfo *
435ftp_parse_winnt_ls (const char *file)
436{
437 FILE *fp;
438 int len;
439 int year, month, day; /* for time analysis */
440 int hour, min;
441 struct tm timestruct;
442
443 char *line, *tok; /* tokenizer */
444 struct fileinfo *dir, *l, cur; /* list creation */
445
446 fp = fopen (file, "rb");
447 if (!fp)
448 {
449 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
450 return NULL;
451 }
452 dir = l = NULL;
453
454 /* Line loop to end of file: */
455 while ((line = read_whole_line (fp)) != NULL)
456 {
457 len = clean_line (line);
458
459 /* Extracting name is a bit of black magic and we have to do it
460 before `strtok' inserted extra \0 characters in the line
461 string. For the moment let us just suppose that the name starts at
462 column 39 of the listing. This way we could also recognize
463 filenames that begin with a series of space characters (but who
464 really wants to use such filenames anyway?). */
465 if (len < 40) continue;
466 tok = line + 39;
467 cur.name = xstrdup(tok);
468 DEBUGP(("Name: '%s'\n", cur.name));
469
470 /* First column: mm-dd-yy. Should atoi() on the month fail, january
471 will be assumed. */
472 tok = strtok(line, "-");
473 if (tok == NULL) continue;
474 month = atoi(tok) - 1;
475 if (month < 0) month = 0;
476 tok = strtok(NULL, "-");
477 if (tok == NULL) continue;
478 day = atoi(tok);
479 tok = strtok(NULL, " ");
480 if (tok == NULL) continue;
481 year = atoi(tok);
482 /* Assuming the epoch starting at 1.1.1970 */
483 if (year <= 70) year += 100;
484
485 /* Second column: hh:mm[AP]M, listing does not contain value for
486 seconds */
487 tok = strtok(NULL, ":");
488 if (tok == NULL) continue;
489 hour = atoi(tok);
490 tok = strtok(NULL, "M");
491 if (tok == NULL) continue;
492 min = atoi(tok);
493 /* Adjust hour from AM/PM. Just for the record, the sequence goes
494 11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
495 tok+=2;
496 if (hour == 12) hour = 0;
497 if (*tok == 'P') hour += 12;
498
499 DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n",
500 year+1900, month, day, hour, min));
501
502 /* Build the time-stamp (copy & paste from above) */
503 timestruct.tm_sec = 0;
504 timestruct.tm_min = min;
505 timestruct.tm_hour = hour;
506 timestruct.tm_mday = day;
507 timestruct.tm_mon = month;
508 timestruct.tm_year = year;
509 timestruct.tm_wday = 0;
510 timestruct.tm_yday = 0;
511 timestruct.tm_isdst = -1;
512 cur.tstamp = mktime (&timestruct); /* store the time-stamp */
513
514 DEBUGP(("Timestamp: %ld\n", cur.tstamp));
515
516 /* Third column: Either file length, or <DIR>. We also set the
517 permissions (guessed as 0644 for plain files and 0755 for
518 directories as the listing does not give us a clue) and filetype
519 here. */
520 tok = strtok(NULL, " ");
521 if (tok == NULL) continue;
522 while ((tok != NULL) && (*tok == '\0')) tok = strtok(NULL, " ");
523 if (tok == NULL) continue;
524 if (*tok == '<')
525 {
526 cur.type = FT_DIRECTORY;
527 cur.size = 0;
528 cur.perms = 0755;
529 DEBUGP(("Directory\n"));
530 }
531 else
532 {
533 wgint size;
534 cur.type = FT_PLAINFILE;
535 errno = 0;
536 size = str_to_wgint (tok, NULL, 10);
537 if (size == WGINT_MAX && errno == ERANGE)
538 cur.size = 0; /* overflow */
539 else
540 cur.size = size;
541 cur.perms = 0644;
542 DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
543 }
544
545 cur.linkto = NULL;
546
547 /* And put everything into the linked list */
548 if (!dir)
549 {
550 l = dir = xnew (struct fileinfo);
551 memcpy (l, &cur, sizeof (cur));
552 l->prev = l->next = NULL;
553 }
554 else
555 {
556 cur.prev = l;
557 l->next = xnew (struct fileinfo);
558 l = l->next;
559 memcpy (l, &cur, sizeof (cur));
560 l->next = NULL;
561 }
562
563 xfree (line);
564 }
565
566 fclose(fp);
567 return dir;
568}
569
570/* Converts VMS symbolic permissions to number-style ones, e.g. string
571 RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
572 (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
573static int
574vmsperms (const char *s)
575{
576 int perms = 0;
577
578 do
579 {
580 switch (*s) {
581 case ',': perms <<= 3; break;
582 case 'R': perms |= 4; break;
583 case 'W': perms |= 2; break;
584 case 'D': perms |= 2; break;
585 case 'E': perms |= 1; break;
586 default: DEBUGP(("wrong VMS permissons!\n"));
587 }
588 }
589 while (*++s);
590 return perms;
591}
592
593
594static struct fileinfo *
595ftp_parse_vms_ls (const char *file)
596{
597 FILE *fp;
598 /* #### A third copy of more-or-less the same array ? */
599 static const char *months[] = {
600 "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
601 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
602 };
603 int i;
604 int year, month, day; /* for time analysis */
605 int hour, min, sec;
606 struct tm timestruct;
607
608 char *line, *tok; /* tokenizer */
609 struct fileinfo *dir, *l, cur; /* list creation */
610
611 fp = fopen (file, "rb");
612 if (!fp)
613 {
614 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
615 return NULL;
616 }
617 dir = l = NULL;
618
619 /* Skip empty line. */
620 line = read_whole_line (fp);
621 xfree_null (line);
622
623 /* Skip "Directory PUB$DEVICE[PUB]" */
624 line = read_whole_line (fp);
625 xfree_null (line);
626
627 /* Skip empty line. */
628 line = read_whole_line (fp);
629 xfree_null (line);
630
631 /* Line loop to end of file: */
632 while ((line = read_whole_line (fp)) != NULL)
633 {
634 char *p;
635 i = clean_line (line);
636 if (!i)
637 {
638 xfree (line);
639 break;
640 }
641
642 /* First column: Name. A bit of black magic again. The name my be
643 either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
644 line. Therefore we will first try to get the complete name
645 until the first space character; if it fails, we assume that the name
646 occupies the whole line. After that we search for the version
647 separator ";", we remove it and check the extension of the file;
648 extension .DIR denotes directory. */
649
650 tok = strtok(line, " ");
651 if (tok == NULL) tok = line;
652 DEBUGP(("file name: '%s'\n", tok));
653 for (p = tok ; *p && *p != ';' ; p++);
654 if (*p == ';') *p = '\0';
655 p = tok + strlen(tok) - 4;
656 if (!strcmp(p, ".DIR")) *p = '\0';
657 cur.name = xstrdup(tok);
658 DEBUGP(("Name: '%s'\n", cur.name));
659
660 /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
661 the file size to zero as the listing does tell us only the size in
662 filesystem blocks - for an integrity check (when mirroring, for
663 example) we would need the size in bytes. */
664
665 if (! *p)
666 {
667 cur.type = FT_DIRECTORY;
668 cur.size = 0;
669 DEBUGP(("Directory\n"));
670 }
671 else
672 {
673 cur.type = FT_PLAINFILE;
674 DEBUGP(("File\n"));
675 }
676
677 cur.size = 0;
678
679 /* Second column, if exists, or the first column of the next line
680 contain file size in blocks. We will skip it. */
681
682 tok = strtok(NULL, " ");
683 if (tok == NULL)
684 {
685 DEBUGP(("Getting additional line\n"));
686 xfree (line);
687 line = read_whole_line (fp);
688 if (!line)
689 {
690 DEBUGP(("empty line read, leaving listing parser\n"));
691 break;
692 }
693 i = clean_line (line);
694 if (!i)
695 {
696 DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
697 xfree (line);
698 break;
699 }
700 tok = strtok(line, " ");
701 }
702 DEBUGP(("second token: '%s'\n", tok));
703
704 /* Third/Second column: Date DD-MMM-YYYY. */
705
706 tok = strtok(NULL, "-");
707 if (tok == NULL) continue;
708 DEBUGP(("day: '%s'\n",tok));
709 day = atoi(tok);
710 tok = strtok(NULL, "-");
711 if (!tok)
712 {
713 /* If the server produces garbage like
714 'EA95_0PS.GZ;1 No privilege for attempted operation'
715 the first strtok(NULL, "-") will return everything until the end
716 of the line and only the next strtok() call will return NULL. */
717 DEBUGP(("nonsense in VMS listing, skipping this line\n"));
718 xfree (line);
719 break;
720 }
721 for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
722 /* Uknown months are mapped to January */
723 month = i % 12 ;
724 tok = strtok (NULL, " ");
725 if (tok == NULL) continue;
726 year = atoi (tok) - 1900;
727 DEBUGP(("date parsed\n"));
728
729 /* Fourth/Third column: Time hh:mm[:ss] */
730 tok = strtok (NULL, " ");
731 if (tok == NULL) continue;
732 min = sec = 0;
733 p = tok;
734 hour = atoi (p);
735 for (; *p && *p != ':'; ++p);
736 if (*p)
737 min = atoi (++p);
738 for (; *p && *p != ':'; ++p);
739 if (*p)
740 sec = atoi (++p);
741
742 DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n",
743 year+1900, month, day, hour, min, sec));
744
745 /* Build the time-stamp (copy & paste from above) */
746 timestruct.tm_sec = sec;
747 timestruct.tm_min = min;
748 timestruct.tm_hour = hour;
749 timestruct.tm_mday = day;
750 timestruct.tm_mon = month;
751 timestruct.tm_year = year;
752 timestruct.tm_wday = 0;
753 timestruct.tm_yday = 0;
754 timestruct.tm_isdst = -1;
755 cur.tstamp = mktime (&timestruct); /* store the time-stamp */
756
757 DEBUGP(("Timestamp: %ld\n", cur.tstamp));
758
759 /* Skip the fifth column */
760
761 tok = strtok(NULL, " ");
762 if (tok == NULL) continue;
763
764 /* Sixth column: Permissions */
765
766 tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
767 if (tok == NULL) continue;
768 tok = strtok(NULL, ")");
769 if (tok == NULL)
770 {
771 DEBUGP(("confusing VMS permissions, skipping line\n"));
772 xfree (line);
773 continue;
774 }
775 /* Permissons have the format "RWED,RWED,RE" */
776 cur.perms = vmsperms(tok);
777 DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
778
779 cur.linkto = NULL;
780
781 /* And put everything into the linked list */
782 if (!dir)
783 {
784 l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
785 memcpy (l, &cur, sizeof (cur));
786 l->prev = l->next = NULL;
787 }
788 else
789 {
790 cur.prev = l;
791 l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
792 l = l->next;
793 memcpy (l, &cur, sizeof (cur));
794 l->next = NULL;
795 }
796
797 xfree (line);
798 }
799
800 fclose (fp);
801 return dir;
802}
803
804
805/* This function switches between the correct parsing routine depending on
806 the SYSTEM_TYPE. The system type should be based on the result of the
807 "SYST" response of the FTP server. According to this repsonse we will
808 use on of the three different listing parsers that cover the most of FTP
809 servers used nowadays. */
810
811struct fileinfo *
812ftp_parse_ls (const char *file, const enum stype system_type)
813{
814 switch (system_type)
815 {
816 case ST_UNIX:
817 return ftp_parse_unix_ls (file, 0);
818 case ST_WINNT:
819 {
820 /* Detect whether the listing is simulating the UNIX format */
821 FILE *fp;
822 int c;
823 fp = fopen (file, "rb");
824 if (!fp)
825 {
826 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
827 return NULL;
828 }
829 c = fgetc(fp);
830 fclose(fp);
831 /* If the first character of the file is '0'-'9', it's WINNT
832 format. */
833 if (c >= '0' && c <='9')
834 return ftp_parse_winnt_ls (file);
835 else
836 return ftp_parse_unix_ls (file, 1);
837 }
838 case ST_VMS:
839 return ftp_parse_vms_ls (file);
840 case ST_MACOS:
841 return ftp_parse_unix_ls (file, 1);
842 default:
843 logprintf (LOG_NOTQUIET, _("\
844Unsupported listing type, trying Unix listing parser.\n"));
845 return ftp_parse_unix_ls (file, 0);
846 }
847}
848
849
850/* Stuff for creating FTP index. */
851
852/* The function creates an HTML index containing references to given
853 directories and files on the appropriate host. The references are
854 FTP. */
855uerr_t
856ftp_index (const char *file, struct url *u, struct fileinfo *f)
857{
858 FILE *fp;
859 char *upwd;
860 char *htclfile; /* HTML-clean file name */
861
862 if (!output_stream)
863 {
864 fp = fopen (file, "wb");
865 if (!fp)
866 {
867 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
868 return FOPENERR;
869 }
870 }
871 else
872 fp = output_stream;
873 if (u->user)
874 {
875 char *tmpu, *tmpp; /* temporary, clean user and passwd */
876
877 tmpu = url_escape (u->user);
878 tmpp = u->passwd ? url_escape (u->passwd) : NULL;
879 if (tmpp)
880 upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
881 else
882 upwd = concat_strings (tmpu, "@", (char *) 0);
883 xfree (tmpu);
884 xfree_null (tmpp);
885 }
886 else
887 upwd = xstrdup ("");
888 fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
889 fprintf (fp, "<html>\n<head>\n<title>");
890 fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
891 fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
892 fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
893 fprintf (fp, "</h1>\n<hr>\n<pre>\n");
894 while (f)
895 {
896 fprintf (fp, " ");
897 if (f->tstamp != -1)
898 {
899 /* #### Should we translate the months? Or, even better, use
900 ISO 8601 dates? */
901 static const char *months[] = {
902 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
903 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
904 };
905 struct tm *ptm = localtime ((time_t *)&f->tstamp);
906
907 fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
908 ptm->tm_mday);
909 if (ptm->tm_hour)
910 fprintf (fp, "%02d:%02d ", ptm->tm_hour, ptm->tm_min);
911 else
912 fprintf (fp, " ");
913 }
914 else
915 fprintf (fp, _("time unknown "));
916 switch (f->type)
917 {
918 case FT_PLAINFILE:
919 fprintf (fp, _("File "));
920 break;
921 case FT_DIRECTORY:
922 fprintf (fp, _("Directory "));
923 break;
924 case FT_SYMLINK:
925 fprintf (fp, _("Link "));
926 break;
927 default:
928 fprintf (fp, _("Not sure "));
929 break;
930 }
931 htclfile = html_quote_string (f->name);
932 fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
933 if (*u->dir != '/')
934 putc ('/', fp);
935 fprintf (fp, "%s", u->dir);
936 if (*u->dir)
937 putc ('/', fp);
938 fprintf (fp, "%s", htclfile);
939 if (f->type == FT_DIRECTORY)
940 putc ('/', fp);
941 fprintf (fp, "\">%s", htclfile);
942 if (f->type == FT_DIRECTORY)
943 putc ('/', fp);
944 fprintf (fp, "</a> ");
945 if (f->type == FT_PLAINFILE)
946 fprintf (fp, _(" (%s bytes)"), with_thousand_seps (f->size));
947 else if (f->type == FT_SYMLINK)
948 fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
949 putc ('\n', fp);
950 xfree (htclfile);
951 f = f->next;
952 }
953 fprintf (fp, "</pre>\n</body>\n</html>\n");
954 xfree (upwd);
955 if (!output_stream)
956 fclose (fp);
957 else
958 fflush (fp);
959 return FTPOK;
960}
Note: See TracBrowser for help on using the repository browser.