--- modules/ftp/ftp_commands.c 2009-09-21 15:09:19.000000000 -0400 +++ modules/ftp/ftp_commands.c 2011-06-22 03:00:31.000000000 -0400 @@ -24,6 +24,8 @@ #include "mod_ftp.h" #include "ftp_internal.h" -#include "apr_version.h" -#include "apr_network_io.h" +#include +#include +#include +#include #include "http_vhost.h" @@ -665,22 +667,66 @@ static int common_list(request_rec *r, c apr_status_t rv; apr_size_t nbytes; - char *varg = apr_pstrdup(r->pool, arg); const char *test, *sl; int res; int decend = 0; + apr_getopt_t *options; + char **argv, option; + const char *optval; + char * const *p; + int argc; + /* Sort by name ascending by default */ + int reverse = 0; + enum ftp_sort sortby = SORT_BY_NAME; /* Why not SORT_NONE? */ + + /* TODO: improve error reporting with detailed status explanation */ + if ((res = apr_tokenize_to_argv(arg, &argv, r->pool))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, + "Attempt to tokenize string `%s' failed", arg); + return res; + } + for (p = argv, argc = 0; *p; p++) + argc++; - while (*varg == '-') - { - if (ftp_parse2(r->pool, varg, &word, &varg, FTP_KEEP_WHITESPACE)) { - varg = word; - break; - } - /* More Cowbell! TODO: expand the accepted dash patterns */ - if (ap_strchr(word, 'l')) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Split `%s' into %d word%s", + arg, argc, argc == 1 ? "" : "s"); + + /* + * apr_getopt ignores argv[0] thinking, it is the program's name, + * but we want it to start with it, so we shift argv and argc by one: + */ + if ((res = apr_getopt_init(&options, r->pool, argc + 1, (const char **)argv - 1))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, + "apr_getopt_init failed"); + return res; + } + + /* Disable apr_getopt's own error-reporting: */ + options->errfn = NULL; + + while (apr_getopt(options, "Slrtcu", &option, &optval) != APR_EOF) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking option `%c'", option); + switch(option) { + case 'l': dashl = 1; - } - /* -- 'end of dash-opts' by convention allows patterns like '-*' */ - if (ap_strchr(word + 1, '-')) { - break; + continue; + case 'r': + reverse = 1; + continue; + case 't': + sortby = SORT_BY_MTIME; + continue; + case 'c': + sortby = SORT_BY_CTIME; + continue; + case 'u': + sortby = SORT_BY_ATIME; + continue; + case 'S': + sortby = SORT_BY_SIZE; + continue; + default: + /* TODO: communicate this back to the client too */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, + "Ignoring unrecognized listing option `%c'", option); } } @@ -696,5 +748,9 @@ static int common_list(request_rec *r, c } - arg = varg; + arg = argv[options->ind - 1] ? argv[options->ind - 1] : ""; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "%s-listing `%s', " + "sorting by %d (%sscending)", is_list ? "Long" : "Short", + arg ? arg : "nill!", (int)sortby, reverse ? "de" : "a"); if (is_list && (ap_strchr_c(arg, '*') != NULL)) @@ -768,5 +824,5 @@ static int common_list(request_rec *r, c /* Construct the sorted array of directory contents */ - if ((direntry = ftp_direntry_get(r, pattern)) == NULL) { + if ((direntry = ftp_direntry_get(r, pattern, sortby, reverse)) == NULL) { fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, ftp_escape_control_text(arg, r->pool)); --- modules/ftp/ftp_internal.h 2009-09-17 14:06:43.000000000 -0400 +++ modules/ftp/ftp_internal.h 2011-06-22 02:59:53.000000000 -0400 @@ -160,4 +160,13 @@ typedef enum { } ftp_loginlimit_t; +enum ftp_sort { + SORT_BY_NAME, + SORT_BY_MTIME, + SORT_BY_ATIME, + SORT_BY_CTIME, + SORT_BY_SIZE, + SORT_NONE +}; + /* Directory entry structure. Used for directory listings */ typedef struct ftp_direntry @@ -170,4 +179,5 @@ typedef struct ftp_direntry apr_off_t size; apr_off_t csize; + apr_time_t atime, mtime, ctime; apr_int32_t nlink; struct ftp_direntry *child; /* For descending */ @@ -254,5 +264,6 @@ int ftp_limitlogin_loggedout(conn_rec *c int ftp_eprt_decode(apr_int32_t *family, char **addr, apr_port_t *port, char *arg); -struct ftp_direntry *ftp_direntry_get(request_rec *r, const char *pattern); +struct ftp_direntry *ftp_direntry_get(request_rec *r, const char *pattern, + enum ftp_sort sortby, int reverse); void ftp_set_authorization(request_rec *r); --- modules/ftp/ftp_util.c 2009-09-17 17:36:36.000000000 -0400 +++ modules/ftp/ftp_util.c 2011-06-22 03:05:05.000000000 -0400 @@ -210,4 +210,7 @@ static struct ftp_direntry *ftp_direntry dirent->size = rr->finfo.size; dirent->csize = rr->finfo.csize; + dirent->ctime = rr->finfo.ctime; + dirent->atime = rr->finfo.atime; + dirent->mtime = rr->finfo.mtime; dirent->modestring = apr_pstrdup(r->pool, ftp_modestring_get( @@ -267,6 +270,6 @@ static struct ftp_direntry *ftp_direntry * be greater than d2. */ -static int ftp_dsortf(struct ftp_direntry ** d1, - struct ftp_direntry ** d2) +static int ftp_dsortf(const struct ftp_direntry ** d1, + const struct ftp_direntry ** d2) { /* Simple sort based on filename */ @@ -274,4 +277,32 @@ static int ftp_dsortf(struct ftp_direntr } +static int ftp_dsortf_desc(const struct ftp_direntry ** d1, + const struct ftp_direntry ** d2) +{ + /* Simple sort based on filename, descending */ + return strcmp((*d2)->name, (*d1)->name); +} + +#define FTP_DSORT_NUM(field) \ +static int ftp_dsort_##field(const struct ftp_direntry ** d1, \ + const struct ftp_direntry ** d2) \ +{ \ + if ((*d1)->field == (*d2)->field) \ + return 0; \ + return (*d1)->field < (*d2)->field ? 1 : -1; \ +} \ +static int ftp_dsort_desc_##field(const struct ftp_direntry ** d1, \ + const struct ftp_direntry ** d2) \ +{ \ + if ((*d1)->field == (*d2)->field) \ + return 0; \ + return (*d1)->field > (*d2)->field ? 1 : -1; \ +} + +FTP_DSORT_NUM(mtime) +FTP_DSORT_NUM(ctime) +FTP_DSORT_NUM(atime) +FTP_DSORT_NUM(size) + /* ftp_direntry_get: Return an array of ftp_direntry structures based * on the uri stored in the request rec. An extra @@ -283,5 +310,6 @@ static int ftp_dsortf(struct ftp_direntr * Returns: The sorted array of directory entries on success, NULL otherwise */ -struct ftp_direntry *ftp_direntry_get(request_rec *r, const char *pattern) +struct ftp_direntry *ftp_direntry_get(request_rec *r, const char *pattern, + enum ftp_sort sortby, int reverse) { struct ftp_direntry *p, *head, *current, **a; @@ -293,4 +321,14 @@ struct ftp_direntry *ftp_direntry_get(re const char *path, *search; + int (*compar[])(const struct ftp_direntry **, + const struct ftp_direntry **) = { + /* The order here must match the enum ftp_sort! */ + ftp_dsortf, ftp_dsortf_desc, + ftp_dsort_mtime, ftp_dsort_desc_mtime, + ftp_dsort_atime, ftp_dsort_desc_atime, + ftp_dsort_ctime, ftp_dsort_desc_ctime, + ftp_dsort_size, ftp_dsort_desc_size + }; + /* * The actual search pattern, used to determine if we should recurse into @@ -374,5 +412,5 @@ struct ftp_direntry *ftp_direntry_get(re const char *newpattern = apr_pstrcat(r->pool, fname, "/*", NULL); - p->child = ftp_direntry_get(r, newpattern); + p->child = ftp_direntry_get(r, newpattern, sortby, reverse); } else { @@ -396,6 +434,9 @@ struct ftp_direntry *ftp_direntry_get(re } num = i; - qsort((void *) a, num, sizeof(struct ftp_direntry *), - (int (*) (const void *, const void *)) ftp_dsortf); + if (sortby != SORT_NONE) { + qsort((void *) a, num, sizeof(struct ftp_direntry *), + (int (*)(const void *, + const void *))compar[sortby * 2 + reverse]); + } /* Re-construct the list from the sorted list */