+static int ftp_open_dir(URLContext *h)
+{
+ FTPContext *s = h->priv_data;
+ int ret;
+
+ if ((ret = ftp_connect(h, h->filename)) < 0)
+ goto fail;
+ if ((ret = ftp_set_dir(s)) < 0)
+ goto fail;
+ if ((ret = ftp_connect_data_connection(h)) < 0)
+ goto fail;
+ if ((ret = ftp_list(s)) < 0)
+ goto fail;
+ s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
+ if (!s->dir_buffer) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ s->dir_buffer[0] = 0;
+ if (s->conn_data && s->state == LISTING_DIR)
+ return 0;
+ fail:
+ ffurl_closep(&s->conn_control);
+ ffurl_closep(&s->conn_data);
+ return ret;
+}
+
+static int64_t ftp_parse_date(const char *date)
+{
+ struct tm tv;
+ memset(&tv, 0, sizeof(struct tm));
+ av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
+ return INT64_C(1000000) * av_timegm(&tv);
+}
+
+static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
+{
+ next->name = av_strdup(line);
+ return 0;
+}
+
+static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
+{
+ char *fact, *value;
+ ff_dlog(NULL, "%s\n", mlsd);
+ while(fact = av_strtok(mlsd, ";", &mlsd)) {
+ if (fact[0] == ' ') {
+ next->name = av_strdup(&fact[1]);
+ continue;
+ }
+ fact = av_strtok(fact, "=", &value);
+ if (!av_strcasecmp(fact, "type")) {
+ if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
+ return 1;
+ if (!av_strcasecmp(value, "dir"))
+ next->type = AVIO_ENTRY_DIRECTORY;
+ else if (!av_strcasecmp(value, "file"))
+ next->type = AVIO_ENTRY_FILE;
+ else if (!av_strcasecmp(value, "OS.unix=slink:"))
+ next->type = AVIO_ENTRY_SYMBOLIC_LINK;
+ } else if (!av_strcasecmp(fact, "modify")) {
+ next->modification_timestamp = ftp_parse_date(value);
+ } else if (!av_strcasecmp(fact, "UNIX.mode")) {
+ next->filemode = strtoumax(value, NULL, 8);
+ } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
+ next->user_id = strtoumax(value, NULL, 10);
+ else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
+ next->group_id = strtoumax(value, NULL, 10);
+ else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
+ next->size = strtoll(value, NULL, 10);
+ }
+ return 0;
+}
+
+/**
+ * @return 0 on success, negative on error, positive on entry to discard.
+ */
+static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
+{
+ FTPContext *s = h->priv_data;
+
+ switch (s->listing_method) {
+ case MLSD:
+ return ftp_parse_entry_mlsd(line, next);
+ case NLST:
+ return ftp_parse_entry_nlst(line, next);
+ case UNKNOWN_METHOD:
+ default:
+ return -1;
+ }
+}
+
+static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
+{
+ FTPContext *s = h->priv_data;
+ char *start, *found;
+ int ret, retried;
+
+ do {
+ retried = 0;
+ start = s->dir_buffer + s->dir_buffer_offset;
+ while (!(found = strstr(start, "\n"))) {
+ if (retried)
+ return AVERROR(EIO);
+ s->dir_buffer_size -= s->dir_buffer_offset;
+ s->dir_buffer_offset = 0;
+ if (s->dir_buffer_size)
+ memmove(s->dir_buffer, start, s->dir_buffer_size);
+ ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
+ if (ret < 0)
+ return ret;
+ if (!ret) {
+ *next = NULL;
+ return 0;
+ }
+ s->dir_buffer_size += ret;
+ s->dir_buffer[s->dir_buffer_size] = 0;
+ start = s->dir_buffer;
+ retried = 1;
+ }
+ s->dir_buffer_offset += (found + 1 - start);
+ found[0] = 0;
+ if (found > start && found[-1] == '\r')
+ found[-1] = 0;
+
+ *next = ff_alloc_dir_entry();
+ if (!*next)
+ return AVERROR(ENOMEM);
+ (*next)->utf8 = s->utf8;
+ ret = ftp_parse_entry(h, start, *next);
+ if (ret) {
+ avio_free_directory_entry(next);
+ if (ret < 0)
+ return ret;
+ }
+ } while (ret > 0);
+ return 0;
+}
+
+static int ftp_close_dir(URLContext *h)
+{
+ FTPContext *s = h->priv_data;
+ av_freep(&s->dir_buffer);
+ ffurl_closep(&s->conn_control);
+ ffurl_closep(&s->conn_data);
+ return 0;
+}
+
+static int ftp_delete(URLContext *h)
+{
+ FTPContext *s = h->priv_data;
+ char command[MAX_URL_SIZE];
+ static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
+ static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
+ int ret;
+
+ if ((ret = ftp_connect(h, h->filename)) < 0)
+ goto cleanup;
+
+ snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
+ if (ftp_send_command(s, command, del_codes, NULL) == 250) {
+ ret = 0;
+ goto cleanup;
+ }
+
+ snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
+ if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
+ ret = 0;
+ else
+ ret = AVERROR(EIO);
+
+cleanup:
+ ftp_close(h);
+ return ret;
+}
+
+static int ftp_move(URLContext *h_src, URLContext *h_dst)
+{
+ FTPContext *s = h_src->priv_data;
+ char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
+ static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
+ static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
+ int ret;
+
+ if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
+ goto cleanup;
+
+ snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
+ if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
+ ret = AVERROR(EIO);
+ goto cleanup;
+ }
+
+ av_url_split(0, 0, 0, 0, 0, 0, 0,
+ path, sizeof(path),
+ h_dst->filename);
+ snprintf(command, sizeof(command), "RNTO %s\r\n", path);
+ if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
+ ret = 0;
+ else
+ ret = AVERROR(EIO);
+
+cleanup:
+ ftp_close(h_src);
+ return ret;
+}
+