+
+#define XFER_BUF_SIZE 1024
+
+int ftp_transfer(struct ftp *ftp, struct ftp_transfer *xfer)
+{
+ if(prepare_data(ftp) == -1) {
+ return -1;
+ }
+
+ if(xfer->op == FTP_RETR) {
+ sendcmd(ftp, "retr %s", xfer->rname);
+ ftp->cproc = cproc_xfer;
+ ftp->dproc = dproc_xfer;
+ ftp->cproc_cls = ftp->dproc_cls = xfer;
+ } else {
+ /* TODO */
+ }
+ return 0;
+}
+
+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_pasv(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ const char *str;
+ unsigned int addr[6];
+ struct sockaddr_in sa;
+ unsigned int ipaddr;
+ int port;
+
+ if(code != 227) {
+ errmsg("ftp_passive failed\n");
+ goto nopasv;
+ }
+
+ str = buf;
+ while(*str) {
+ if(sscanf(str, "%u,%u,%u,%u,%u,%u", addr, addr + 1, addr + 2, addr + 3,
+ addr + 4, addr + 5) == 6) {
+ break;
+ }
+ str++;
+ }
+ if(!*str || (addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) >= 256) {
+ errmsg("ftp_passive: failed to parse response: %s\n", buf);
+ goto nopasv;
+ }
+ port = (addr[4] << 8) | addr[5];
+ ipaddr = ((unsigned int)addr[0] << 24) | ((unsigned int)addr[1] << 16) |
+ ((unsigned int)addr[2] << 8) | addr[3];
+
+ if((ftp->data = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
+ fprintf(stderr, "ftp_passive: failed to allocate socket\n");
+ goto nopasv;
+ }
+
+ infomsg("passive mode: %d.%d.%d.%d port %d\n", addr[0], addr[1], addr[2], addr[3],
+ port);
+
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(ipaddr);
+ sa.sin_port = htons(port);
+
+ if(connect(ftp->data, (struct sockaddr*)&sa, sizeof sa) == -1) {
+ errmsg("ftp_passive: failed to connect to %s:%p\n", inet_ntoa(sa.sin_addr), port);
+ closesocket(ftp->data);
+ ftp->data = -1;
+ goto nopasv;
+ }
+
+ ftp->status = FTP_CONN_PASV;
+ return 0;
+
+nopasv:
+ ftp->passive = 0;
+ ftp_active(ftp);
+ 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->curdir = strdup_nf(dirname);
+ ftp->modified = FTP_MOD_DIR;
+ 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;
+}
+
+int ftp_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);
+
+ while(ptr < end) {
+ if(parse_dirent(&ent, ptr) != -1) {
+ darr_push(ftp->dirent, &ent);
+ }
+ while(ptr < end && *ptr != '\n' && *ptr != '\r') ptr++;
+ while(ptr < end && (*ptr == '\r' || *ptr == '\n')) ptr++;
+ }
+ ftp->modified |= FTP_MOD_DIR;
+
+ free(rbuf->buf);
+ free(rbuf);
+ ftp->dproc = 0;
+
+ num = darr_size(ftp->dirent);
+ qsort(ftp->dirent, num, sizeof *ftp->dirent, ftp_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;
+}
+
+static int cproc_xfer(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+ char *ptr;
+ struct ftp_transfer *xfer = cls;
+
+ if(code < 200) {
+ /* expect more */
+ if(code == 150 && (ptr = strchr(buf, '('))) {
+ /* update total size */
+ sscanf(ptr, "(%ld bytes)", &xfer->total);
+ }
+ return 1;
+ }
+
+ if(code >= 400) {
+ errmsg("failed to retrieve file\n");
+ }
+ return 0;
+}
+
+static void dproc_xfer(struct ftp *ftp, const char *buf, int sz, void *cls)
+{
+ struct ftp_transfer *xfer = cls;
+
+ if(xfer->op == FTP_RETR) {
+ if(sz == 0) {
+ /* EOF condition, got the whole file */
+ if(xfer->fp) {
+ fclose(xfer->fp);
+ xfer->fp = 0;
+ }
+
+ if(xfer->done) {
+ xfer->done(ftp, xfer);
+ }
+ return;
+ }
+
+ if(xfer->fp) {
+ ((char*)buf)[sz] = 0;
+ fwrite(buf, 1, sz, xfer->fp);
+
+ } else if(xfer->mem) {
+ int prevsz = darr_size(xfer->mem);
+ darr_resize(xfer->mem, prevsz + sz);
+ memcpy(xfer->mem + prevsz, buf, sz);
+ }
+
+ xfer->count += sz;
+ if(xfer->count > xfer->total) {
+ xfer->count = xfer->total;
+ }
+
+ } else { /* FTP_STOR */
+ /* TODO */
+ }
+}
+
+const char *ftp_curdir(struct ftp *ftp)
+{
+ return ftp->curdir;
+}
+
+int ftp_num_dirent(struct ftp *ftp)
+{
+ return darr_size(ftp->dirent);
+}
+
+struct ftp_dirent *ftp_dirent(struct ftp *ftp, int idx)
+{
+ return ftp->dirent + idx;
+}