summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libftpconn/unix.c107
1 files changed, 80 insertions, 27 deletions
diff --git a/libftpconn/unix.c b/libftpconn/unix.c
index d1004429..1737bff7 100644
--- a/libftpconn/unix.c
+++ b/libftpconn/unix.c
@@ -27,7 +27,9 @@
#include <grp.h>
#include <sys/time.h>
#include <netinet/in.h>
+#ifdef HAVE_HURD_HURD_TYPES_H
#include <hurd/hurd_types.h>
+#endif
#include <ftpconn.h>
@@ -108,8 +110,9 @@ strlaxcmp (const char *p, const char *q)
/* Try to convert an error message in TXT into an error code. POSS_ERRS
contains a list of likely errors to try; if no other clue is found, the
first thing in poss_errs is returned. */
-error_t ftp_conn_unix_interp_err (struct ftp_conn *conn, const char *txt,
- const error_t *poss_errs)
+error_t
+ftp_conn_unix_interp_err (struct ftp_conn *conn, const char *txt,
+ const error_t *poss_errs)
{
const char *p;
const error_t *e;
@@ -140,6 +143,9 @@ struct get_stats_state
size_t name_alloced; /* Allocated size of NAME (>= NAME_LEN). */
int name_partial; /* True if NAME isn't complete. */
+ int contents; /* Are we looking for directory contents? */
+ int added_slash; /* Did we prefix the name with `./'? */
+
struct stat stat; /* Last read stat info. */
int start; /* True if at beginning of output. */
@@ -148,43 +154,65 @@ struct get_stats_state
char buf[7000];
};
-/* Start an operation to get a list of file-stat structures for NAME (this
- is often similar to ftp_conn_start_dir, but with OS-specific flags), and
+
+
+/* Start an operation to get a list of file-stat structures for NAME (this is
+ often similar to ftp_conn_start_dir, but with OS-specific flags), and
return a file-descriptor for reading on, and a state structure in STATE
- suitable for passing to cont_get_stats. FORCE_DIR controls what happens if
- NAME refers to a directory: if FORCE_DIR is false, STATS will contain
- entries for all files *in* NAME, and if FORCE_DIR is true, it will
- contain just a single entry for NAME itself (or an error will be
- returned when this isn't possible). */
+ suitable for passing to cont_get_stats. If CONTENTS is true, NAME must
+ refer to a directory, and the contents will be returned, otherwise, the
+ (single) result will refer to NAME. */
error_t
ftp_conn_unix_start_get_stats (struct ftp_conn *conn,
- const char *name, int force_dir,
+ const char *name, int contents,
int *fd, void **state)
{
error_t err;
+ size_t req_len;
+ char *req;
struct get_stats_state *s = malloc (sizeof (struct get_stats_state));
+ const char *flags = contents ? "-A" : "-Ad";
+ const char *slash = strchr (name, '/');
if (! s)
return ENOMEM;
- if (force_dir)
+ if (strcspn (name, "*? \t\n{}$`\\\"'") < strlen (name))
+ /* NAME contains some metacharacters, which makes the behavior of various
+ ftp servers unpredictable, so punt. */
{
- char *dir_op;
+ free (s);
+ return EINVAL;
+ }
- if (asprintf (&dir_op, "-d %s", name) <= 0)
- return errno;
+ /* We pack the ls options and the name into the list argument, in REQ,
+ which will do the right thing on most unix ftp servers. */
- err = ftp_conn_start_dir (conn, dir_op, fd);
+ /* Space needed for REQ. */
+ req_len = strlen (flags) + 1 + strlen (name) + 1;
- free (dir_op);
- }
- else
- err = ftp_conn_start_dir (conn, name, fd);
+ /* If NAME doesn't contain a slash, we prepend `./' to it so that we can
+ tell from the results whether it's a directory or not. */
+ if (! slash)
+ req_len += 2;
+
+ req = malloc (req_len);
+ if (! req)
+ return ENOMEM;
+
+ snprintf (req, req_len, "%s %s%s", flags, slash ? "" : "./", name);
+
+ /* Make the actual request. */
+ err = ftp_conn_start_dir (conn, req, fd);
+
+ free (req);
if (err)
free (s);
else
{
+ s->contents = contents;
+ s->added_slash = !slash;
s->name = 0;
s->name_len = s->name_alloced = 0;
s->name_partial = 0;
@@ -226,7 +254,9 @@ drwxrwxrwt 7 34 archive 512 May 1 14:28 /tmp
bzero (stat, sizeof *stat);
+#ifdef FSTYPE_FTP
stat->st_fstype = FSTYPE_FTP;
+#endif
/* File format (S_IFMT) bits. */
switch (*p++)
@@ -309,12 +339,12 @@ drwxrwxrwt 7 34 archive 512 May 1 14:28 /tmp
#define SKIP_WS() \
while (isspace (*p)) p++;
-#define PARSE_INT() ({ \
- unsigned u = strtoul (p, &e, 10); \
- if (e == p || isalnum (*e)) \
- return EGRATUITOUS; \
- p = e; \
- u; \
+#define PARSE_INT() ({ \
+ unsigned u = strtoul (p, &e, 10); \
+ if (e == p || isalnum (*e)) \
+ return EGRATUITOUS; \
+ p = e; \
+ u; \
})
/* Link count. */
@@ -341,7 +371,10 @@ drwxrwxrwt 7 34 archive 512 May 1 14:28 /tmp
p = e;
}
+
+#ifdef HAVE_STAT_ST_AUTHOR
stat->st_author = stat->st_uid;
+#endif
/* File group. */
SKIP_WS ();
@@ -539,12 +572,13 @@ ftp_conn_unix_cont_get_stats (struct ftp_conn *conn, int fd, void *state,
if (nl)
{
+ char *name = s->name;
char *symlink_target = 0;
if (S_ISLNK (s->stat.st_mode))
/* A symlink, see if we can find the link target. */
{
- symlink_target = strstr (s->name, " -> ");
+ symlink_target = strstr (name, " -> ");
if (symlink_target)
{
*symlink_target = '\0';
@@ -552,9 +586,25 @@ ftp_conn_unix_cont_get_stats (struct ftp_conn *conn, int fd, void *state,
}
}
+ if (strchr (name, '/'))
+ if (s->contents)
+ /* We know that the name originally request had a slash in it
+ (because we added one if necessary), so if a name in the
+ listing has one too, it can't be the contents of a
+ directory; if this is the case and we wanted the contents,
+ this must not be a directory. */
+ {
+ err = ENOTDIR;
+ goto finished;
+ }
+ else if (s->added_slash)
+ /* S->name must be the same name we passed; if we added a `./'
+ prefix, removed it so the client gets back what it passed. */
+ name += 2;
+
/* Call the callback function to process the current entry; it is
responsible for freeing S->name and SYMLINK_TARGET. */
- err = (*add_stat) (s->name, &s->stat, symlink_target, hook);
+ err = (*add_stat) (name, &s->stat, symlink_target, hook);
if (err)
goto finished;
@@ -597,5 +647,8 @@ finished:
free (s);
close (fd);
+ if (err && rd > 0)
+ ftp_conn_abort (conn);
+
return err;
}