. */
+static int
+vmsperms (const char *s)
+{
+ int perms = 0;
+
+ do
+ {
+ switch (*s) {
+ case ',': perms <<= 3; break;
+ case 'R': perms |= 4; break;
+ case 'W': perms |= 2; break;
+ case 'D': perms |= 2; break;
+ case 'E': perms |= 1; break;
+ default: DEBUGP(("wrong VMS permissons!\n"));
+ }
+ }
+ while (*++s);
+ return perms;
+}
+
+
+static struct fileinfo *
+ftp_parse_vms_ls (const char *file)
+{
+ FILE *fp;
+ /* #### A third copy of more-or-less the same array ? */
+ static const char *months[] = {
+ "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+ "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
+ };
+ int i;
+ int year, month, day; /* for time analysis */
+ int hour, min, sec;
+ struct tm timestruct;
+
+ char *line, *tok; /* tokenizer */
+ struct fileinfo *dir, *l, cur; /* list creation */
+
+ fp = fopen (file, "rb");
+ if (!fp)
+ {
+ logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
+ return NULL;
+ }
+ dir = l = NULL;
+
+ /* Skip empty line. */
+ line = read_whole_line (fp);
+ xfree_null (line);
+
+ /* Skip "Directory PUB$DEVICE[PUB]" */
+ line = read_whole_line (fp);
+ xfree_null (line);
+
+ /* Skip empty line. */
+ line = read_whole_line (fp);
+ xfree_null (line);
+
+ /* Line loop to end of file: */
+ while ((line = read_whole_line (fp)) != NULL)
+ {
+ char *p;
+ i = clean_line (line);
+ if (!i)
+ {
+ xfree (line);
+ break;
+ }
+
+ /* First column: Name. A bit of black magic again. The name my be
+ either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
+ line. Therefore we will first try to get the complete name
+ until the first space character; if it fails, we assume that the name
+ occupies the whole line. After that we search for the version
+ separator ";", we remove it and check the extension of the file;
+ extension .DIR denotes directory. */
+
+ tok = strtok(line, " ");
+ if (tok == NULL) tok = line;
+ DEBUGP(("file name: '%s'\n", tok));
+ for (p = tok ; *p && *p != ';' ; p++)
+ ;
+ if (*p == ';') *p = '\0';
+ p = tok + strlen(tok) - 4;
+ if (!strcmp(p, ".DIR")) *p = '\0';
+ cur.name = xstrdup(tok);
+ DEBUGP(("Name: '%s'\n", cur.name));
+
+ /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
+ the file size to zero as the listing does tell us only the size in
+ filesystem blocks - for an integrity check (when mirroring, for
+ example) we would need the size in bytes. */
+
+ if (! *p)
+ {
+ cur.type = FT_DIRECTORY;
+ cur.size = 0;
+ DEBUGP(("Directory\n"));
+ }
+ else
+ {
+ cur.type = FT_PLAINFILE;
+ DEBUGP(("File\n"));
+ }
+
+ cur.size = 0;
+
+ /* Second column, if exists, or the first column of the next line
+ contain file size in blocks. We will skip it. */
+
+ tok = strtok(NULL, " ");
+ if (tok == NULL)
+ {
+ DEBUGP(("Getting additional line\n"));
+ xfree (line);
+ line = read_whole_line (fp);
+ if (!line)
+ {
+ DEBUGP(("empty line read, leaving listing parser\n"));
+ break;
+ }
+ i = clean_line (line);
+ if (!i)
+ {
+ DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
+ xfree (line);
+ break;
+ }
+ tok = strtok(line, " ");
+ }
+ DEBUGP(("second token: '%s'\n", tok));
+
+ /* Third/Second column: Date DD-MMM-YYYY. */
+
+ tok = strtok(NULL, "-");
+ if (tok == NULL) continue;
+ DEBUGP(("day: '%s'\n",tok));
+ day = atoi(tok);
+ tok = strtok(NULL, "-");
+ if (!tok)
+ {
+ /* If the server produces garbage like
+ 'EA95_0PS.GZ;1 No privilege for attempted operation'
+ the first strtok(NULL, "-") will return everything until the end
+ of the line and only the next strtok() call will return NULL. */
+ DEBUGP(("nonsense in VMS listing, skipping this line\n"));
+ xfree (line);
+ break;
+ }
+ for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
+ /* Uknown months are mapped to January */
+ month = i % 12 ;
+ tok = strtok (NULL, " ");
+ if (tok == NULL) continue;
+ year = atoi (tok) - 1900;
+ DEBUGP(("date parsed\n"));
+
+ /* Fourth/Third column: Time hh:mm[:ss] */
+ tok = strtok (NULL, " ");
+ if (tok == NULL) continue;
+ min = sec = 0;
+ p = tok;
+ hour = atoi (p);
+ for (; *p && *p != ':'; ++p)
+ ;
+ if (*p)
+ min = atoi (++p);
+ for (; *p && *p != ':'; ++p)
+ ;
+ if (*p)
+ sec = atoi (++p);
+
+ DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n",
+ year+1900, month, day, hour, min, sec));
+
+ /* Build the time-stamp (copy & paste from above) */
+ timestruct.tm_sec = sec;
+ timestruct.tm_min = min;
+ timestruct.tm_hour = hour;
+ timestruct.tm_mday = day;
+ timestruct.tm_mon = month;
+ timestruct.tm_year = year;
+ timestruct.tm_wday = 0;
+ timestruct.tm_yday = 0;
+ timestruct.tm_isdst = -1;
+ cur.tstamp = mktime (×truct); /* store the time-stamp */
+
+ DEBUGP(("Timestamp: %ld\n", cur.tstamp));
+
+ /* Skip the fifth column */
+
+ tok = strtok(NULL, " ");
+ if (tok == NULL) continue;
+
+ /* Sixth column: Permissions */
+
+ tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
+ if (tok == NULL) continue;
+ tok = strtok(NULL, ")");
+ if (tok == NULL)
+ {
+ DEBUGP(("confusing VMS permissions, skipping line\n"));
+ xfree (line);
+ continue;
+ }
+ /* Permissons have the format "RWED,RWED,RE" */
+ cur.perms = vmsperms(tok);
+ DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
+
+ cur.linkto = NULL;
+
+ /* And put everything into the linked list */
+ if (!dir)
+ {
+ l = dir = xnew (struct fileinfo);
+ memcpy (l, &cur, sizeof (cur));
+ l->prev = l->next = NULL;
+ }
+ else
+ {
+ cur.prev = l;
+ l->next = xnew (struct fileinfo);
+ l = l->next;
+ memcpy (l, &cur, sizeof (cur));
+ l->next = NULL;
+ }
+
+ xfree (line);
+ }
+
+ fclose (fp);
+ return dir;
+}
+
+
+/* This function switches between the correct parsing routine depending on
+ the SYSTEM_TYPE. The system type should be based on the result of the
+ "SYST" response of the FTP server. According to this repsonse we will
+ use on of the three different listing parsers that cover the most of FTP
+ servers used nowadays. */
- Since we currently support only the Unix FTP servers, this function
- simply returns the result of ftp_parse_unix_ls(). */
struct fileinfo *
-ftp_parse_ls (const char *file)
+ftp_parse_ls (const char *file, const enum stype system_type)
+{
+ switch (system_type)
+ {
+ case ST_UNIX:
+ return ftp_parse_unix_ls (file, 0);
+ case ST_WINNT:
+ {
+ /* Detect whether the listing is simulating the UNIX format */
+ FILE *fp;
+ int c;
+ fp = fopen (file, "rb");
+ if (!fp)
+ {
+ logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
+ return NULL;
+ }
+ c = fgetc(fp);
+ fclose(fp);
+ /* If the first character of the file is '0'-'9', it's WINNT
+ format. */
+ if (c >= '0' && c <='9')
+ return ftp_parse_winnt_ls (file);
+ else
+ return ftp_parse_unix_ls (file, 1);
+ }
+ case ST_VMS:
+ return ftp_parse_vms_ls (file);
+ case ST_MACOS:
+ return ftp_parse_unix_ls (file, 1);
+ default:
+ logprintf (LOG_NOTQUIET, _("\
+Unsupported listing type, trying Unix listing parser.\n"));
+ return ftp_parse_unix_ls (file, 0);
+ }
+}
+
+/* Stuff for creating FTP index. */
+
+/* The function creates an HTML index containing references to given
+ directories and files on the appropriate host. The references are
+ FTP. */
+uerr_t
+ftp_index (const char *file, struct url *u, struct fileinfo *f)
{
- return ftp_parse_unix_ls (file);
+ FILE *fp;
+ char *upwd;
+ char *htcldir; /* HTML-clean dir name */
+ char *htclfile; /* HTML-clean file name */
+ char *urlclfile; /* URL-clean file name */
+
+ if (!output_stream)
+ {
+ fp = fopen (file, "wb");
+ if (!fp)
+ {
+ logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
+ return FOPENERR;
+ }
+ }
+ else
+ fp = output_stream;
+ if (u->user)
+ {
+ char *tmpu, *tmpp; /* temporary, clean user and passwd */
+
+ tmpu = url_escape (u->user);
+ tmpp = u->passwd ? url_escape (u->passwd) : NULL;
+ if (tmpp)
+ upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
+ else
+ upwd = concat_strings (tmpu, "@", (char *) 0);
+ xfree (tmpu);
+ xfree_null (tmpp);
+ }
+ else
+ upwd = xstrdup ("");
+
+ htcldir = html_quote_string (u->dir);
+
+ fprintf (fp, "\n");
+ fprintf (fp, "\n\n");
+ fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
+ fprintf (fp, "\n\n\n");
+ fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
+ fprintf (fp, "
\n
\n\n");
+
+ while (f)
+ {
+ fprintf (fp, " ");
+ if (f->tstamp != -1)
+ {
+ /* #### Should we translate the months? Or, even better, use
+ ISO 8601 dates? */
+ static const char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ time_t tstamp = f->tstamp;
+ struct tm *ptm = localtime (&tstamp);
+
+ fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
+ ptm->tm_mday);
+ if (ptm->tm_hour)
+ fprintf (fp, "%02d:%02d ", ptm->tm_hour, ptm->tm_min);
+ else
+ fprintf (fp, " ");
+ }
+ else
+ fprintf (fp, _("time unknown "));
+ switch (f->type)
+ {
+ case FT_PLAINFILE:
+ fprintf (fp, _("File "));
+ break;
+ case FT_DIRECTORY:
+ fprintf (fp, _("Directory "));
+ break;
+ case FT_SYMLINK:
+ fprintf (fp, _("Link "));
+ break;
+ default:
+ fprintf (fp, _("Not sure "));
+ break;
+ }
+ htclfile = html_quote_string (f->name);
+ urlclfile = url_escape_unsafe_and_reserved (f->name);
+ fprintf (fp, "host, u->port);
+ if (*u->dir != '/')
+ putc ('/', fp);
+ /* XXX: Should probably URL-escape dir components here, rather
+ * than just HTML-escape, for consistency with the next bit where
+ * we use urlclfile for the file component. Anyway, this is safer
+ * than what we had... */
+ fprintf (fp, "%s", htcldir);
+ if (*u->dir)
+ putc ('/', fp);
+ fprintf (fp, "%s", urlclfile);
+ if (f->type == FT_DIRECTORY)
+ putc ('/', fp);
+ fprintf (fp, "\">%s", htclfile);
+ if (f->type == FT_DIRECTORY)
+ putc ('/', fp);
+ fprintf (fp, " ");
+ if (f->type == FT_PLAINFILE)
+ fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
+ else if (f->type == FT_SYMLINK)
+ fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
+ putc ('\n', fp);
+ xfree (htclfile);
+ xfree (urlclfile);
+ f = f->next;
+ }
+ fprintf (fp, "
\n\n\n");
+ xfree (htcldir);
+ xfree (upwd);
+ if (!output_stream)
+ fclose (fp);
+ else
+ fflush (fp);
+ return FTPOK;
}