/*
**  Copyright 2000-2004 University of Illinois Board of Trustees
**  Copyright 2000-2004 Mark D. Roth
**  All rights reserved.
**
**  list_parse_mlsd.c - MSLD-style FTP directory parsing code
**
**  Mark D. Roth <roth@feep.net>
*/

#include <internal.h>

#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifdef STDC_HEADERS
# include <string.h>
# include <stdlib.h>
#endif

#ifdef MAJOR_IN_MKDEV
# include <sys/mkdev.h>
#else
# ifdef MAJOR_IN_SYSMACROS
#  include <sys/sysmacros.h>
# endif
#endif

/* needed for strcasecmp() on some platforms */
#include <strings.h>


/*
** keep this list in sync with facts supported by _ftp_list_parse_mlsd()
*/
int
_ftp_opts_mlsd(FTP *ftp)
{
	int i;

	i = _ftp_opts(ftp, "MLST",
		      "size;modify;type;perm;"
		      "UNIX.mode;UNIX.owner;UNIX.group;UNIX.uid;UNIX.gid;");

	/*
	** if the server is broken enough to support FEAT but
	** not OPTS, then we'll punt by ignoring the error
	** and doing the best we can with whatever facts it provides
	*/
	if (i == -1
	    && errno == ENOSYS)
		i = 0;

	return i;
}


/* parse MLSD directories */
int
_ftp_list_parse_mlsd(FTP *ftp, char *buf, file_info_t *fip)
{
	char *factp, *valp, *nextp = buf;
	unsigned long ul;
	unsigned int ui;
	struct tm tt;

#ifdef DEBUG
	printf("==> _ftp_list_parse_mlsd(ftp=0x%lx (%s), buf=0x%lx, "
	       "fip=0x%lx)\n", ftp, ftp->ftp_host, buf, fip);
#endif

	factp = strchr(buf, ' ');
	if (factp == NULL)
	{
#ifdef DEBUG
		printf("<== _ftp_list_parse_mlsd(): no space character "
		       "in entry - returning FLP_IGNORE\n");
#endif
		return FLP_IGNORE;
	}
	*factp++ = '\0';
	strlcpy(fip->fi_filename, factp, sizeof(fip->fi_filename));

	while ((factp = strsep(&nextp, ";")) != NULL)
	{
		if (*factp == '\0')
			continue;

		valp = factp;
		factp = strsep(&valp, "=");
		
		if (strcasecmp(factp, "size") == 0)
		{
			sscanf(valp, "%lu", &ul);
			fip->fi_stat.fs_size = (off_t)ul;
			continue;
		}

		if (strcasecmp(factp, "modify") == 0)
		{
			memset(&tt, 0, sizeof(tt));
			sscanf(valp, "%4d%2d%2d%2d%2d%2d",
			       &tt.tm_year, &tt.tm_mon, &tt.tm_mday,
			       &tt.tm_hour, &tt.tm_min, &tt.tm_sec);
			tt.tm_year -= 1900;
			tt.tm_mon--;
			fip->fi_stat.fs_mtime = mktime(&tt);
			continue;
		}

		if (strcasecmp(factp, "type") == 0)
		{
			/*
			** clear S_IFMT bits, in case the type was set before
			*/
			fip->fi_stat.fs_mode &= ~S_IFMT;

			if (strcasecmp(valp, "cdir") == 0)
			{
				strlcpy(fip->fi_filename, ".",
					sizeof(fip->fi_filename));
				fip->fi_stat.fs_mode |= S_IFDIR;
				continue;
			}

			if (strcasecmp(valp, "pdir") == 0)
			{
				strlcpy(fip->fi_filename, "..",
					sizeof(fip->fi_filename));
				fip->fi_stat.fs_mode |= S_IFDIR;
				continue;
			}

			if (strcasecmp(valp, "dir") == 0)
			{
				fip->fi_stat.fs_mode |= S_IFDIR;
				continue;
			}

			if (strcasecmp(valp, "file") == 0)
			{
				fip->fi_stat.fs_mode |= S_IFREG;
				continue;
			}

			if (strncasecmp(valp, "os.unix=", 8) == 0)
			{
				valp += 8;

				if (strncasecmp(valp, "slink:", 6) == 0)
				{
					fip->fi_stat.fs_mode |= S_IFLNK;
					valp += 6;
					strlcpy(fip->fi_linkto, valp,
						sizeof(fip->fi_linkto));
					continue;
				}

				if (strncasecmp(valp, "chr-", 4) == 0)
				{
					fip->fi_stat.fs_mode |= S_IFCHR;
					/* FIXME: add fs_rdev field? */
					continue;
				}

				if (strncasecmp(valp, "blk-", 4) == 0)
				{
					fip->fi_stat.fs_mode |= S_IFCHR;
					/* FIXME: add fs_rdev field? */
					continue;
				}

				continue;
			}

			/* for unknown types, ignore the entire entry */
#ifdef DEBUG
			printf("<== _ftp_list_parse_mlsd(): "
			       "unknown type \"%s\" - returning FLP_IGNORE\n",
			       valp);
#endif
			return FLP_IGNORE;
		}

		if (strcasecmp(factp, "perm") == 0)
		{
			/*
			** if the mode bits are already set, don't
			** overwrite them
			** (this allows the UNIX.mode fact to take precedence)
			*/
			if (fip->fi_stat.fs_mode & ~S_IFMT != 0)
				continue;

			for (; *valp != '\0'; valp++)
			{
				switch (*valp)
				{
				case 'a':	/* file: APPE */
				case 'w':	/* file: STOR */
				case 'c':	/* [cp]?dir: STOU, STOR */
					fip->fi_stat.fs_mode |= 0222;
					break;

				case 'e':	/* [cp]?dir: CWD */
					fip->fi_stat.fs_mode |= 0111;
					break;

				case 'l':	/* [cp]?dir: MLSD, LIST, NLST */
				case 'r':	/* file: RETR */
					fip->fi_stat.fs_mode |= 0444;
					break;

				default:
				case 'd':	/* any: DELE, RMD */
				case 'f':	/* any: RNFR */
				case 'm':	/* [cp]?dir: MKD (subdirs) */
				case 'p':	/* [cp]?dir: RMD (subdirs) */
					break;
				}
			}

			continue;
		}

		if (strncasecmp(factp, "UNIX.", 5) == 0)
		{
			factp += 5;

			if (strcasecmp(factp, "mode") == 0)
			{
				/* clear any bits that may already be set */
				fip->fi_stat.fs_mode &= S_IFMT;

				sscanf(valp, "%lo", &ui);
				fip->fi_stat.fs_mode |= (mode_t)ui;
				continue;
			}

			if (strcasecmp(factp, "owner") == 0)
			{
				strlcpy(fip->fi_stat.fs_username, valp,
					sizeof(fip->fi_stat.fs_username));
				continue;
			}

			if (strcasecmp(factp, "group") == 0)
			{
				strlcpy(fip->fi_stat.fs_groupname, valp,
					sizeof(fip->fi_stat.fs_groupname));
				continue;
			}

			if (strcasecmp(factp, "uid") == 0)
			{
				/*
				** skip if already set
				** (this allows the UNIX.owner fact
				** to take precedence)
				*/
				if (fip->fi_stat.fs_username[0] != '\0')
					continue;

				strlcpy(fip->fi_stat.fs_username, valp,
					sizeof(fip->fi_stat.fs_username));
				continue;
			}

			if (strcasecmp(factp, "gid") == 0)
			{
				/*
				** skip if already set
				** (this allows the UNIX.group fact
				** to take precedence)
				*/
				if (fip->fi_stat.fs_groupname[0] != '\0')
					continue;

				strlcpy(fip->fi_stat.fs_groupname, valp,
					sizeof(fip->fi_stat.fs_groupname));
				continue;
			}

			continue;
		}

		/* skip any other facts */
		continue;
	}

	if (fip->fi_stat.fs_username[0] == '\0')
		strlcpy(fip->fi_stat.fs_username, "-1",
			sizeof(fip->fi_stat.fs_username));
	if (fip->fi_stat.fs_groupname[0] == '\0')
		strlcpy(fip->fi_stat.fs_groupname, "-1",
			sizeof(fip->fi_stat.fs_groupname));
	if (fip->fi_stat.fs_nlink == 0)
		fip->fi_stat.fs_nlink = 1;

#ifdef DEBUG
	printf("<== _ftp_list_parse_mlsd(): success\n");
#endif
	return FLP_VALID;
}




syntax highlighted by Code2HTML, v. 0.9.1