1 /* Parsing FTP `ls' output.
2 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
6 This file is part of GNU Wget.
8 GNU Wget is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 GNU Wget is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with Wget. If not, see <http://www.gnu.org/licenses/>.
21 Additional permission under GNU GPL version 3 section 7
23 If you modify this program, or any covered work, by linking or
24 combining it with the OpenSSL project's OpenSSL library (or a
25 modified version of that library), containing parts covered by the
26 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
27 grants you additional permission to convey the resulting work.
28 Corresponding Source for a non-source form of such a combination
29 shall include the source code for the parts of OpenSSL used as well
30 as that of the covered work. */
43 #include "convert.h" /* for html_quote_string prototype */
44 #include "retr.h" /* for output_stream */
46 /* Converts symbolic permissions to number-style ones, e.g. string
47 rwxr-xr-x to 755. For now, it knows nothing of
48 setuid/setgid/sticky. ACLs are ignored. */
50 symperms (const char *s)
56 for (i = 0; i < 3; i++, s += 3)
59 perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
60 (s[2] == 'x' || s[2] == 's'));
66 /* Cleans a line of text so that it can be consistently parsed. Destroys
67 <CR> and <LF> in case that thay occur at the end of the line and
68 replaces all <TAB> character with <SPACE>. Returns the length of the
71 clean_line (char *line, int len)
73 if (len <= 0) return 0;
75 while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
80 for ( ; *line ; line++ ) if (*line == '\t') *line = ' ';
85 /* Convert the Un*x-ish style directory listing stored in FILE to a
86 linked list of fileinfo (system-independent) entries. The contents
87 of FILE are considered to be produced by the standard Unix `ls -la'
88 output (whatever that might be). BSD (no group) and SYSV (with
89 group) listings are handled.
91 The time stamps are stored in a separate variable, time_t
92 compatible (I hope). The timezones are ignored. */
93 static struct fileinfo *
94 ftp_parse_unix_ls (const char *file, int ignore_perms)
97 static const char *months[] = {
98 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
99 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
101 int next, len, i, error, ignore;
102 int year, month, day; /* for time analysis */
103 int hour, min, sec, ptype;
104 struct tm timestruct, *tnow;
108 char *line = NULL, *tok, *ptok; /* tokenizer */
109 struct fileinfo *dir, *l, cur; /* list creation */
111 fp = fopen (file, "rb");
114 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
119 /* Line loop to end of file: */
120 while ((len = getline (&line, &bufsize, fp)) > 0)
122 len = clean_line (line, len);
123 /* Skip if total... */
124 if (!strncasecmp (line, "total", 5))
126 /* Get the first token (permissions). */
127 tok = strtok (line, " ");
134 /* Decide whether we deal with a file or a directory. */
138 cur.type = FT_PLAINFILE;
139 DEBUGP (("PLAINFILE; "));
142 cur.type = FT_DIRECTORY;
143 DEBUGP (("DIRECTORY; "));
146 cur.type = FT_SYMLINK;
147 DEBUGP (("SYMLINK; "));
150 cur.type = FT_UNKNOWN;
151 DEBUGP (("UNKNOWN; "));
166 /*cur.perms = 1023;*/ /* #### What is this? --hniksic */
169 DEBUGP (("implicit perms %0o; ", cur.perms));
173 cur.perms = symperms (tok + 1);
174 DEBUGP (("perms %0o; ", cur.perms));
177 error = ignore = 0; /* Erroneous and ignoring entries are
178 treated equally for now. */
179 year = hour = min = sec = 0; /* Silence the compiler. */
183 /* While there are tokens on the line, parse them. Next is the
184 number of tokens left until the filename.
186 Use the month-name token as the "anchor" (the place where the
187 position wrt the file name is "known"). When a month name is
188 encountered, `next' is set to 5. Also, the preceding
189 characters are parsed to get the file size.
191 This tactic is quite dubious when it comes to
192 internationalization issues (non-English month names), but it
196 (tok = strtok (NULL, " ")) != NULL)
199 if (next < 0) /* a month name was not encountered */
201 for (i = 0; i < 12; i++)
202 if (!strcmp (tok, months[i]))
204 /* If we got a month, it means the token before it is the
205 size, and the filename is three tokens away. */
210 /* Parse the previous token with str_to_wgint. */
213 /* Something has gone wrong during parsing. */
218 size = str_to_wgint (ptok, NULL, 10);
219 if (size == WGINT_MAX && errno == ERANGE)
220 /* Out of range -- ignore the size. #### Should
221 we refuse to start the download. */
225 DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
229 DEBUGP (("month: %s; ", months[month]));
232 else if (next == 4) /* days */
234 if (tok[1]) /* two-digit... */
235 day = 10 * (*tok - '0') + tok[1] - '0';
236 else /* ...or one-digit */
238 DEBUGP (("day: %d; ", day));
242 /* This ought to be either the time, or the year. Let's
245 If we have a number x, it's a year. If we have x:y,
246 it's hours and minutes. If we have x:y:z, z are
249 min = hour = sec = 0;
250 /* We must deal with digits. */
251 if (c_isdigit (*tok))
253 /* Suppose it's year. */
254 for (; c_isdigit (*tok); tok++)
255 year = (*tok - '0') + 10 * year;
258 /* This means these were hours! */
263 /* Get the minutes... */
264 for (; c_isdigit (*tok); tok++)
265 min = (*tok - '0') + 10 * min;
268 /* ...and the seconds. */
270 for (; c_isdigit (*tok); tok++)
271 sec = (*tok - '0') + 10 * sec;
276 DEBUGP (("year: %d (no tm); ", year));
278 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
280 else if (next == 2) /* The file name */
285 /* Since the file name may contain a SPC, it is possible
286 for strtok to handle it wrong. */
287 fnlen = strlen (tok);
288 if (fnlen < len - (tok - line))
290 /* So we have a SPC in the file name. Restore the
293 /* If the file is a symbolic link, it should have a
295 if (cur.type == FT_SYMLINK)
297 p = strstr (tok, " -> ");
303 cur.linkto = xstrdup (p + 4);
304 DEBUGP (("link to: %s\n", cur.linkto));
305 /* And separate it from the file name. */
309 /* If we have the filename, add it to the list of files or
311 /* "." and ".." are an exception! */
312 if (!strcmp (tok, ".") || !strcmp (tok, ".."))
314 DEBUGP (("\nIgnoring `.' and `..'; "));
318 /* Some FTP sites choose to have ls -F as their default
319 LIST output, which marks the symlinks with a trailing
320 `@', directory names with a trailing `/' and
321 executables with a trailing `*'. This is no problem
322 unless encountering a symbolic link ending with `@',
323 or an executable ending with `*' on a server without
324 default -F output. I believe these cases are very
326 fnlen = strlen (tok); /* re-calculate `fnlen' */
327 cur.name = xmalloc (fnlen + 1);
328 memcpy (cur.name, tok, fnlen + 1);
331 if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
333 cur.name[fnlen - 1] = '\0';
334 DEBUGP (("trailing `/' on dir.\n"));
336 else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
338 cur.name[fnlen - 1] = '\0';
339 DEBUGP (("trailing `@' on link.\n"));
341 else if (cur.type == FT_PLAINFILE
342 && (cur.perms & 0111)
343 && cur.name[fnlen - 1] == '*')
345 cur.name[fnlen - 1] = '\0';
346 DEBUGP (("trailing `*' on exec.\n"));
357 if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
360 DEBUGP (("%s\n", cur.name ? cur.name : ""));
364 DEBUGP (("Skipping.\n"));
365 xfree_null (cur.name);
366 xfree_null (cur.linkto);
372 l = dir = xnew (struct fileinfo);
373 memcpy (l, &cur, sizeof (cur));
374 l->prev = l->next = NULL;
379 l->next = xnew (struct fileinfo);
381 memcpy (l, &cur, sizeof (cur));
384 /* Get the current time. */
385 timenow = time (NULL);
386 tnow = localtime (&timenow);
387 /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr). */
388 timestruct.tm_sec = sec;
389 timestruct.tm_min = min;
390 timestruct.tm_hour = hour;
391 timestruct.tm_mday = day;
392 timestruct.tm_mon = month;
395 /* Some listings will not specify the year if it is "obvious"
396 that the file was from the previous year. E.g. if today
397 is 97-01-12, and you see a file of Dec 15th, its year is
398 1996, not 1997. Thanks to Vladimir Volovich for
400 if (month > tnow->tm_mon)
401 timestruct.tm_year = tnow->tm_year - 1;
403 timestruct.tm_year = tnow->tm_year;
406 timestruct.tm_year = year;
407 if (timestruct.tm_year >= 1900)
408 timestruct.tm_year -= 1900;
409 timestruct.tm_wday = 0;
410 timestruct.tm_yday = 0;
411 timestruct.tm_isdst = -1;
412 l->tstamp = mktime (×truct); /* store the time-stamp */
421 static struct fileinfo *
422 ftp_parse_winnt_ls (const char *file)
426 int year, month, day; /* for time analysis */
429 struct tm timestruct;
431 char *line = NULL, *tok; /* tokenizer */
433 struct fileinfo *dir, *l, cur; /* list creation */
435 fp = fopen (file, "rb");
438 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
443 /* Line loop to end of file: */
444 while ((len = getline (&line, &bufsize, fp)) > 0)
446 len = clean_line (line, len);
448 /* Name begins at 39 column of the listing if date presented in `mm-dd-yy'
449 format or at 41 column if date presented in `mm-dd-yyyy' format. Thus,
450 we cannot extract name before we parse date. Using this information we
451 also can recognize filenames that begin with a series of space
452 characters (but who really wants to use such filenames anyway?). */
453 if (len < 40) continue;
454 filename = line + 39;
456 /* First column: mm-dd-yy or mm-dd-yyyy. Should atoi() on the month fail,
457 january will be assumed. */
458 tok = strtok(line, "-");
459 if (tok == NULL) continue;
460 month = atoi(tok) - 1;
461 if (month < 0) month = 0;
462 tok = strtok(NULL, "-");
463 if (tok == NULL) continue;
465 tok = strtok(NULL, " ");
466 if (tok == NULL) continue;
468 /* Assuming the epoch starting at 1.1.1970 */
473 else if (year >= 1900)
478 /* Now it is possible to determine the position of the first symbol in
480 cur.name = xstrdup(filename);
481 DEBUGP (("Name: '%s'\n", cur.name));
484 /* Second column: hh:mm[AP]M, listing does not contain value for
486 tok = strtok(NULL, ":");
487 if (tok == NULL) continue;
489 tok = strtok(NULL, "M");
490 if (tok == NULL) continue;
492 /* Adjust hour from AM/PM. Just for the record, the sequence goes
493 11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
495 if (hour == 12) hour = 0;
496 if (*tok == 'P') hour += 12;
498 DEBUGP (("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n",
499 year+1900, month, day, hour, min));
501 /* Build the time-stamp (copy & paste from above) */
502 timestruct.tm_sec = 0;
503 timestruct.tm_min = min;
504 timestruct.tm_hour = hour;
505 timestruct.tm_mday = day;
506 timestruct.tm_mon = month;
507 timestruct.tm_year = year;
508 timestruct.tm_wday = 0;
509 timestruct.tm_yday = 0;
510 timestruct.tm_isdst = -1;
511 cur.tstamp = mktime (×truct); /* store the time-stamp */
512 cur.ptype = TT_HOUR_MIN;
514 DEBUGP (("Timestamp: %ld\n", cur.tstamp));
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
520 tok = strtok(NULL, " ");
521 if (tok == NULL) continue;
522 while ((tok != NULL) && (*tok == '\0')) tok = strtok(NULL, " ");
523 if (tok == NULL) continue;
526 cur.type = FT_DIRECTORY;
529 DEBUGP (("Directory\n"));
534 cur.type = FT_PLAINFILE;
536 size = str_to_wgint (tok, NULL, 10);
537 if (size == WGINT_MAX && errno == ERANGE)
538 cur.size = 0; /* overflow */
542 DEBUGP (("File, size %s bytes\n", number_to_static_string (cur.size)));
547 /* And put everything into the linked list */
550 l = dir = xnew (struct fileinfo);
551 memcpy (l, &cur, sizeof (cur));
552 l->prev = l->next = NULL;
557 l->next = xnew (struct fileinfo);
559 memcpy (l, &cur, sizeof (cur));
571 /* Convert the VMS-style directory listing stored in "file" to a
572 linked list of fileinfo (system-independent) entries. The contents
573 of FILE are considered to be produced by the standard VMS
574 "DIRECTORY [/SIZE [= ALL]] /DATE [/OWNER] [/PROTECTION]" command,
575 more or less. (Different VMS FTP servers may have different headers,
576 and may not supply the same data, but all should be subsets of this.)
578 VMS normally provides local (server) time and date information.
579 Define the logical name or environment variable
580 "WGET_TIMEZONE_DIFFERENTIAL" (seconds) to adjust the receiving local
581 times if different from the remote local times.
584 Added code to eliminate "^" escape characters from ODS5 extended file
585 names. The TCPIP FTP server (V5.4) seems to prefer requests which do
586 not use the escaped names which it provides.
589 #define VMS_DEFAULT_PROT_FILE 0644
590 #define VMS_DEFAULT_PROT_DIR 0755
595 Delete ODS5 extended file name escape characters ("^") in the
597 Note that the current scheme does not handle all EFN cases, but it
598 could be made more complicated.
601 static void eat_carets( char *str)
602 /* char *str; Source pointer. */
604 char *strd; /* Destination pointer. */
609 /* Skip ahead to the first "^", if any. */
610 while ((*str != '\0') && (*str != '^'))
613 /* If no caret was found, quit early. */
616 /* Shift characters leftward as carets are found. */
623 /* Found a caret. Skip it, and check the next character. */
625 prop = char_prop[ uchr];
628 /* Hex digit. Get char code from this and next hex digit. */
631 hdgt = uchr- '0'; /* '0' - '9' -> 0 - 9. */
635 hdgt = ((uchr- 'A')& 7)+ 10; /* [Aa] - [Ff] -> 10 - 15. */
637 hdgt <<= 4; /* X16. */
638 uchr = *(++str); /* Next char must be hex digit. */
641 uchr = hdgt+ uchr- '0';
645 uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
648 else if (uchr == '_')
650 /* Convert escaped "_" to " ". */
653 else if (uchr == '/')
655 /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
656 /* Note that this is a left-over from Info-ZIP code, and is
657 probably of little value here, except perhaps to avoid
658 directory confusion which an unconverted slash might cause.
662 /* Else, not a hex digit. Must be a simple escaped character
663 (or Unicode, which is not yet handled here).
666 /* Else, not a caret. Use as-is. */
669 /* Advance destination and source pointers. */
673 /* Terminate the destination string. */
679 static struct fileinfo *
680 ftp_parse_vms_ls (const char *file)
687 struct tm *timestruct;
690 char *line = NULL, *tok; /* tokenizer */
691 struct fileinfo *dir, *l, cur; /* list creation */
693 fp = fopen (file, "r");
696 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
701 /* Skip blank lines, Directory heading, and more blank lines. */
703 for (j = 0; (i = getline (&line, &bufsize, fp)) > 0; )
705 i = clean_line (line, i);
707 continue; /* Ignore blank line. */
709 if ((j == 0) && (line[i - 1] == ']'))
711 /* Found Directory heading line. Next non-blank line
715 else if (!strncmp (line, "Total of ", 9))
717 /* Found "Total of ..." footing line. No valid data
718 will follow (empty directory). */
719 i = 0; /* Arrange for early exit. */
724 break; /* Must be significant data. */
728 /* Read remainder of file until the next blank line or EOF. */
734 /* The first token is the file name. After a long name, other
735 data may be on the following line. A valid directory name ends
736 in ".DIR;1" (any case), although some VMS FTP servers may omit
737 the version number (";1").
740 tok = strtok(line, " ");
741 if (tok == NULL) tok = line;
742 DEBUGP (("file name: '%s'\n", tok));
744 /* Stripping the version number on a VMS system would be wrong.
745 It may be foolish on a non-VMS system, too, but that's someone
746 else's problem. (Define PRESERVE_VMS_VERSIONS for proper
747 operation on other operating systems.)
750 ODS5 extended file names may contain escaped semi-colons, so
751 the version number is identified as right-side decimal digits
752 led by a non-escaped semi-colon. It may be absent.
755 #if (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS))
756 for (p = tok + strlen (tok); (--p > tok) && c_isdigit( *p); );
757 if ((*p == ';') && (*(p- 1) != '^'))
761 #endif /* (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS)) */
764 Eliminate "^" escape characters from ODS5 extended file name.
765 (A caret is invalid in an ODS2 name, so this is always safe.)
768 DEBUGP (("file name-^: '%s'\n", tok));
770 /* Differentiate between a directory and any other file. A VMS
771 listing may not include file protections (permissions). Set a
772 default permissions value (according to the file type), which
773 may be overwritten later. Store directory names without the
774 ".DIR;1" file type and version number, as the plain name is
775 what will work in a CWD command.
778 if (!strncasecmp((tok + (len - 4)), ".DIR", 4))
780 *(tok+ (len - 4)) = '\0'; /* Discard ".DIR". */
781 cur.type = FT_DIRECTORY;
782 cur.perms = VMS_DEFAULT_PROT_DIR;
783 DEBUGP (("Directory (nv)\n"));
785 else if (!strncasecmp ((tok + (len - 6)), ".DIR;1", 6))
787 *(tok+ (len - 6)) = '\0'; /* Discard ".DIR;1". */
788 cur.type = FT_DIRECTORY;
789 cur.perms = VMS_DEFAULT_PROT_DIR;
790 DEBUGP (("Directory (v)\n"));
794 cur.type = FT_PLAINFILE;
795 cur.perms = VMS_DEFAULT_PROT_FILE;
798 cur.name = xstrdup (tok);
799 DEBUGP (("Name: '%s'\n", cur.name));
801 /* Null the date and time string. */
804 /* VMS lacks symbolic links. */
807 /* VMS reports file sizes in (512-byte) disk blocks, not bytes,
808 hence useless for an integrity check based on byte-count.
813 /* Get token 2, if any. A long name may force all other data onto
814 a second line. If needed, read the second line.
817 tok = strtok (NULL, " ");
820 DEBUGP (("Getting additional line.\n"));
821 i = getline (&line, &bufsize, fp);
824 DEBUGP (("EOF. Leaving listing parser.\n"));
828 /* Second line must begin with " ". Otherwise, it's a first
829 line (and we may be confused).
831 i = clean_line (line, i);
834 /* Blank line. End of significant file listing. */
835 DEBUGP (("Blank line. Leaving listing parser.\n"));
838 else if (line[0] != ' ')
840 DEBUGP (("Non-blank in column 1. Must be a new file name?\n"));
845 tok = strtok (line, " ");
848 /* Unexpected non-empty but apparently blank line. */
849 DEBUGP (("Null token. Leaving listing parser.\n"));
855 /* Analyze tokens. (Order is not significant, except date must
858 Size: ddd or ddd/ddd (where "ddd" is a decimal number)
860 Time: HH:MM or HH:MM:SS or HH:MM:SS.CC
861 Owner: [user] or [user,group]
862 Protection: (ppp,ppp,ppp,ppp) (where "ppp" is "RWED" or some
863 subset thereof, for System, Owner, Group, World.
865 If permission is lacking, info may be replaced by the string:
866 "No privilege for attempted operation".
870 DEBUGP (("Token: >%s<: ", tok));
872 if ((strlen (tok) < 12) && (strchr( tok, '-') != NULL))
875 DEBUGP (("Date.\n"));
876 strcpy( date_str, tok);
877 strcat( date_str, " ");
879 else if ((strlen (tok) < 12) && (strchr( tok, ':') != NULL))
885 (sizeof( date_str)- strlen (date_str) - 1));
886 DEBUGP (("Date time: >%s<\n", date_str));
888 else if (strchr (tok, '[') != NULL)
890 /* Owner. (Ignore.) */
891 DEBUGP (("Owner.\n"));
893 else if (strchr (tok, '(') != NULL)
895 /* Protections (permissions). */
898 for (i = 0; i < strlen(tok); i++)
932 DEBUGP (("Prot. perms = %0o.\n", cur.perms));
936 /* Nondescript. Probably size(s), probably in blocks.
937 Could be "No privilege ..." message. (Ignore.)
939 DEBUGP (("Ignored (size?).\n"));
942 tok = strtok (NULL, " ");
945 /* Tokens exhausted. Interpret the data, and fill in the
948 /* Fill tm timestruct according to date-time string. Fractional
949 seconds are ignored. Default to current time, if conversion
952 timenow = time( NULL);
953 timestruct = localtime( &timenow );
954 strptime( date_str, "%d-%b-%Y %H:%M:%S", timestruct);
956 /* Convert struct tm local time to time_t local time. */
957 timenow = mktime (timestruct);
958 /* Offset local time according to environment variable (seconds). */
959 if ((tok = getenv ( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
962 DEBUGP (("Time differential = %d.\n", dt));
972 cur.tstamp = timenow; /* Store the time-stamp. */
973 DEBUGP (("Timestamp: %ld\n", cur.tstamp));
974 cur.ptype = TT_HOUR_MIN;
976 /* Add the data for this item to the linked list, */
979 l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
980 memcpy (l, &cur, sizeof (cur));
981 l->prev = l->next = NULL;
986 l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
988 memcpy (l, &cur, sizeof (cur));
992 i = getline (&line, &bufsize, fp);
995 i = clean_line (line, i);
998 /* Blank line. End of significant file listing. */
1010 /* This function switches between the correct parsing routine depending on
1011 the SYSTEM_TYPE. The system type should be based on the result of the
1012 "SYST" response of the FTP server. According to this repsonse we will
1013 use on of the three different listing parsers that cover the most of FTP
1014 servers used nowadays. */
1017 ftp_parse_ls (const char *file, const enum stype system_type)
1019 switch (system_type)
1022 return ftp_parse_unix_ls (file, 0);
1025 /* Detect whether the listing is simulating the UNIX format */
1028 fp = fopen (file, "rb");
1031 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1036 /* If the first character of the file is '0'-'9', it's WINNT
1038 if (c >= '0' && c <='9')
1039 return ftp_parse_winnt_ls (file);
1041 return ftp_parse_unix_ls (file, 1);
1044 return ftp_parse_vms_ls (file);
1046 return ftp_parse_unix_ls (file, 1);
1048 logprintf (LOG_NOTQUIET, _("\
1049 Unsupported listing type, trying Unix listing parser.\n"));
1050 return ftp_parse_unix_ls (file, 0);
1054 /* Stuff for creating FTP index. */
1056 /* The function creates an HTML index containing references to given
1057 directories and files on the appropriate host. The references are
1060 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1064 char *htcldir; /* HTML-clean dir name */
1065 char *htclfile; /* HTML-clean file name */
1066 char *urlclfile; /* URL-clean file name */
1070 fp = fopen (file, "wb");
1073 logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1081 char *tmpu, *tmpp; /* temporary, clean user and passwd */
1083 tmpu = url_escape (u->user);
1084 tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1086 upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1088 upwd = concat_strings (tmpu, "@", (char *) 0);
1093 upwd = xstrdup ("");
1095 htcldir = html_quote_string (u->dir);
1097 fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1098 fprintf (fp, "<html>\n<head>\n<title>");
1099 fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1100 fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1101 fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1102 fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1107 if (f->tstamp != -1)
1109 /* #### Should we translate the months? Or, even better, use
1111 static const char *months[] = {
1112 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1113 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1115 time_t tstamp = f->tstamp;
1116 struct tm *ptm = localtime (&tstamp);
1118 fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1120 if (f->ptype == TT_HOUR_MIN)
1121 fprintf (fp, "%02d:%02d ", ptm->tm_hour, ptm->tm_min);
1126 fprintf (fp, _("time unknown "));
1130 fprintf (fp, _("File "));
1133 fprintf (fp, _("Directory "));
1136 fprintf (fp, _("Link "));
1139 fprintf (fp, _("Not sure "));
1142 htclfile = html_quote_string (f->name);
1143 urlclfile = url_escape_unsafe_and_reserved (f->name);
1144 fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1147 /* XXX: Should probably URL-escape dir components here, rather
1148 * than just HTML-escape, for consistency with the next bit where
1149 * we use urlclfile for the file component. Anyway, this is safer
1150 * than what we had... */
1151 fprintf (fp, "%s", htcldir);
1154 fprintf (fp, "%s", urlclfile);
1155 if (f->type == FT_DIRECTORY)
1157 fprintf (fp, "\">%s", htclfile);
1158 if (f->type == FT_DIRECTORY)
1160 fprintf (fp, "</a> ");
1161 if (f->type == FT_PLAINFILE)
1162 fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1163 else if (f->type == FT_SYMLINK)
1164 fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1170 fprintf (fp, "</pre>\n</body>\n</html>\n");