+
+int ftp_rmdir(struct ftp *ftp, const char *dirname)
+{
+ return -1;
+}
+
+int ftp_delete(struct ftp *ftp, const char *fname)
+{
+ return -1;
+}
+
+struct recvbuf {
+ char *buf;
+ long size, bufsz;
+};
+
+int ftp_list(struct ftp *ftp)
+{
+ struct recvbuf *rbuf;
+
+ if(!(rbuf = malloc(sizeof *rbuf))) {
+ errmsg("failed to allocate receive buffer\n");
+ return -1;
+ }
+ rbuf->size = 0;
+ rbuf->bufsz = 1024;
+ if(!(rbuf->buf = malloc(rbuf->bufsz))) {
+ free(rbuf);
+ errmsg("failed to allocate receive buffer\n");
+ return -1;
+ }
+
+ sendcmd(ftp, "list");
+ ftp->cproc = cproc_list;
+ ftp->dproc = dproc_list;
+ ftp->cproc_cls = ftp->dproc_cls = rbuf;
+ return 0;
+}
+
+int ftp_retrieve(struct ftp *ftp, const char *fname)
+{
+ return -1;
+}
+
+int ftp_store(struct ftp *ftp, const char *fname)
+{
+ return -1;
+}
+
+static int get_quoted_text(const char *str, char *buf)
+{
+ int len;
+ const char *src, *end;
+
+ if(!(src = strchr(str, '"'))) {
+ return -1;
+ }
+ src++;
+ end = src;
+ while(*end && *end != '"') end++;
+ if(!*end) return -1;
+
+ len = end - src;
+ memcpy(buf, src, len);
+ buf[len] = 0;
+ return 0;
+}
+
+static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ if(code != 200) {
+ errmsg("ftp_active failed\n");
+ ftp_close(ftp);
+ } else {
+ ftp->status = FTP_CONN_ACT;
+ }
+ return 0;
+}
+
+static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ char *dirname;
+
+ if(code != 257) {
+ warnmsg("pwd failed\n");
+ return -1;
+ }
+
+ dirname = alloca(strlen(buf) + 1);
+ if(get_quoted_text(buf, dirname) == -1) {
+ warnmsg("pwd: invalid response: %s\n", buf);
+ return -1;
+ }
+
+ free(ftp->curdir[FTP_REMOTE]);
+ ftp->curdir[FTP_REMOTE] = strdup_nf(dirname);
+ ftp->modified = FTP_MOD_REMDIR;
+ return 0;
+}
+
+static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ if(code != 250) {
+ warnmsg("cwd failed\n");
+ return -1;
+ }
+
+ ftp_queue(ftp, FTP_PWD, 0);
+ ftp_queue(ftp, FTP_LIST, 0);
+ return 0;
+}
+
+static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ if(code < 200) {
+ /* expect more */
+ return 1;
+ }
+
+ if(code >= 400) {
+ errmsg("failed to retrieve directory listing\n");
+ }
+ return 0;
+}
+
+#define SKIP_FIELD(p) \
+ do { \
+ while(*(p) && *(p) != '\n' && !isspace(*(p))) (p)++; \
+ while(*(p) && *(p) != '\n' && isspace(*(p))) (p)++; \
+ } while(0)
+
+static int parse_dirent(struct ftp_dirent *ent, const char *line)
+{
+ int len;
+ const char *ptr = line;
+ const char *end;
+
+ if(!(end = strchr(line, '\r')) && !(end = strchr(line, '\n'))) {
+ return -1;
+ }
+
+ if(line[0] == 'd') {
+ ent->type = FTP_DIR;
+ } else {
+ ent->type = FTP_FILE;
+ }
+
+ SKIP_FIELD(ptr); /* skip mode */
+ SKIP_FIELD(ptr); /* skip links */
+ SKIP_FIELD(ptr); /* skip owner */
+ SKIP_FIELD(ptr); /* skip group */
+
+ if(ent->type == FTP_FILE) {
+ ent->size = atoi(ptr);
+ }
+ SKIP_FIELD(ptr); /* skip size */
+ SKIP_FIELD(ptr); /* skip month */
+ SKIP_FIELD(ptr); /* skip day */
+ SKIP_FIELD(ptr); /* skip year */
+
+ if(ptr >= end) return -1;
+
+ len = end - ptr;
+ ent->name = malloc(len + 1);
+ memcpy(ent->name, ptr, len);
+ ent->name[len] = 0;
+
+ return 0;
+}
+
+static int direntcmp(const void *a, const void *b)
+{
+ const struct ftp_dirent *da = a, *db = b;
+
+ if(da->type == db->type) {
+ return strcmp(da->name, db->name);
+ }
+ return da->type == FTP_DIR ? -1 : 1;
+}
+
+static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls)
+{
+ int num;
+ struct recvbuf *rbuf = cls;
+
+ if(sz == 0) {
+ /* EOF condition, we got the whole list, update directory entries */
+ char *ptr = rbuf->buf;
+ char *end = rbuf->buf + rbuf->size;
+ struct ftp_dirent ent;
+
+ darr_clear(ftp->dirent[FTP_REMOTE]);
+
+ while(ptr < end) {
+ if(parse_dirent(&ent, ptr) != -1) {
+ darr_push(ftp->dirent[FTP_REMOTE], &ent);
+ }
+ while(ptr < end && *ptr != '\n' && *ptr != '\r') ptr++;
+ while(ptr < end && (*ptr == '\r' || *ptr == '\n')) ptr++;
+ }
+ ftp->modified |= FTP_MOD_REMDIR;
+
+ free(rbuf->buf);
+ free(rbuf);
+ ftp->dproc = 0;
+
+ num = darr_size(ftp->dirent[FTP_REMOTE]);
+ qsort(ftp->dirent[FTP_REMOTE], num, sizeof *ftp->dirent[FTP_REMOTE], direntcmp);
+ return;
+ }
+
+ if(rbuf->size + sz > rbuf->bufsz) {
+ char *tmp;
+ int newsz = rbuf->bufsz << 1;
+
+ if(!(tmp = realloc(rbuf->buf, newsz))) {
+ errmsg("failed to resize receive buffer\n");
+ return;
+ }
+ rbuf->buf = tmp;
+ rbuf->bufsz = newsz;
+ }
+
+ memcpy(rbuf->buf + rbuf->size, buf, sz);
+ rbuf->size += sz;
+}
+
+const char *ftp_curdir(struct ftp *ftp, int whichdir)
+{
+ return ftp->curdir[whichdir];
+}
+
+int ftp_num_dirent(struct ftp *ftp, int whichdir)
+{
+ return darr_size(ftp->dirent[whichdir]);
+}
+
+struct ftp_dirent *ftp_dirent(struct ftp *ftp, int whichdir, int idx)
+{
+ return ftp->dirent[whichdir] + idx;
+}