/**************************************************************************** * apps/netutils/ftpc/ftpc_listdir.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include "ftpc_config.h" #include #include #include #include #include #include #include #include #include "netutils/ftpc.h" #include "ftpc_internal.h" /**************************************************************************** * Private Types ****************************************************************************/ typedef void (*callback_t)(FAR const char *name, FAR void *arg); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ftpc_dircount * * Description: * This callback simply counts the number of names in the directory. * ****************************************************************************/ static void ftpc_dircount(FAR const char *name, FAR void *arg) { FAR unsigned int *dircount = (FAR unsigned int *)arg; (*dircount)++; } /**************************************************************************** * Name: ftpc_addname * * Description: * This callback adds a name to the directory listing. * ****************************************************************************/ static void ftpc_addname(FAR const char *name, FAR void *arg) { FAR struct ftpc_dirlist_s *dirlist = (FAR struct ftpc_dirlist_s *)arg; unsigned int nnames = dirlist->nnames; dirlist->name[nnames] = strdup(name); dirlist->nnames = nnames + 1; } /**************************************************************************** * Name: ftpc_nlstparse * * Description: * Parse the NLST directory response. The NLST response consists of a * sequence of pathnames. Each pathname is terminated by \r\n. * * If a pathname starts with a slash, it represents the pathname. If a * pathname does not start with a slash, it represents the pathname * obtained by concatenating the pathname of the directory and the * pathname. * * IF NLST of directory /pub produces foo\r\nbar\r\n, it refers to the * pathnames /pub/foo and /pub/bar. * ****************************************************************************/ static void ftpc_nlstparse(FAR FILE *instream, callback_t callback, FAR void *arg) { char buffer[CONFIG_FTP_MAXPATH + 1]; /* Read every filename from the temporary file */ for (; ; ) { /* Read the next line from the file */ if (!fgets(buffer, CONFIG_FTP_MAXPATH, instream)) { break; } /* Remove any trailing CR-LF from the line */ ftpc_stripcrlf(buffer); /* Check for empty file names */ if (buffer[0] == '\0') { break; } ninfo("File: %s\n", buffer); /* Perform the callback operation */ callback(buffer, arg); } } /**************************************************************************** * Name: ftpc_recvdir * * Description: * Get the directory listing. * ****************************************************************************/ static int ftpc_recvdir(FAR struct ftpc_session_s *session, FAR FILE *outstream) { int ret; /* Verify that we are still connected to the server */ if (!ftpc_connected(session)) { nerr("ERROR: Not connected to server\n"); return ERROR; } /* Setup for the transfer */ ftpc_xfrreset(session); ret = ftpc_xfrinit(session); if (ret != OK) { return ERROR; } /* Send the "NLST" command. Normally the server responds with a mark * using code 150: * * - "150 File status okay; about to open data connection" * * It then stops accepting new connections, attempts to * send the contents of the directory over the data connection, and * closes the data connection. */ ret = ftpc_cmd(session, "NLST"); if (ret != OK) { return ERROR; } /* In active mode, we need to accept a connection on the data socket * (in passive mode, we have already connected the data channel to * the FTP server). */ if (!FTPC_IS_PASSIVE(session)) { ret = ftpc_sockaccept(&session->dacceptor, &session->data); if (ret != OK) { nerr("ERROR: ftpc_sockaccept() failed: %d\n", errno); return ERROR; } } /* Receive the NLST directory list */ ret = ftpc_recvtext(session, session->data.instream, outstream); ftpc_sockclose(&session->data); if (ret != OK) { return ERROR; } /* Get the server reply. After closing the data connection, the should * accept the request with: * * - "226 Closing data connection" if the entire directory was * successfully transmitted; * * Or reject it with: * * - "425 Can't open data connection" if no TCP connection was established * - "426 - Connection closed; transfer aborted" if the TCP connection was * established but then broken by the client or by network failure * - "451 - Requested action aborted: local error in processing" if the * server had trouble reading the directory from disk. * * The server may reject the LIST or NLST request (with code 450 or 550) * without first responding with a mark. In this case the server does not * touch the data connection. */ fptc_getreply(session); return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ftpc_listdir * * Description: * Get a simple directory listing using NLST: * * NLST [ ] * * We could do much, much more here using the LIST or MLST/MLSD commands, * but the parsing is a bitch. See http://cr.yp.to/ftpparse.html * * NOTE: We expect to receive only well structured directory paths. Tilde * expansion "~/xyz" and relative paths (abc/def) because we do have * special knowledge about the home and current directories. But otherwise * the paths are expected to be pre-sanitized: No . or .. in paths, * no multiple consecutive '/' in paths, etc. * ****************************************************************************/ FAR struct ftpc_dirlist_s *ftpc_listdir(SESSION handle, FAR const char *dirpath) { FAR struct ftpc_session_s *session = (FAR struct ftpc_session_s *)handle; struct ftpc_dirlist_s *dirlist; FILE *filestream; FAR char *absrpath; FAR char *tmpfname; bool iscurrdir; unsigned int nnames; int allocsize; int ret; /* If no remote current directory, we are not logged in. */ if (!session->currdir) { return NULL; } /* Get the absolute path to the directory */ absrpath = ftpc_absrpath(session, dirpath); if (!absrpath) { return NULL; } ftpc_stripslash(absrpath); /* Is the directory also the remote current working directory? */ iscurrdir = (strcmp(absrpath, session->currdir) == 0); /* Create a temporary file to hold the directory listing */ ret = asprintf(&tmpfname, "%s/TMP%d.dat", CONFIG_FTP_TMPDIR, getpid()); if (ret < 0) { free(absrpath); return NULL; } filestream = fopen(tmpfname, "w+"); if (!filestream) { nerr("ERROR: Failed to create %s: %d\n", tmpfname, errno); free(absrpath); free(tmpfname); return NULL; } /* "CWD" first so that we get the directory contents, not the * directory itself. */ if (!iscurrdir) { ret = ftpc_cmd(session, "CWD %s", absrpath); if (ret != OK) { nerr("ERROR: CWD to %s failed\n", absrpath); } } /* Send the NLST command with no arguments to get the entire contents of * the directory. */ ret = ftpc_recvdir(session, filestream); /* Go back to the correct current working directory */ if (!iscurrdir) { int tmpret = ftpc_cmd(session, "CWD %s", session->currdir); if (tmpret != OK) { nerr("ERROR: CWD back to %s failed\n", session->currdir); } } /* Did we successfully receive the directory listing? */ dirlist = NULL; if (ret == OK) { /* Count the number of names in the temporary file */ rewind(filestream); nnames = 0; ftpc_nlstparse(filestream, ftpc_dircount, &nnames); if (!nnames) { nwarn("WARNING: Nothing found in directory\n"); goto errout; } ninfo("nnames: %d\n", nnames); /* Allocate and initialize a directory container */ allocsize = SIZEOF_FTPC_DIRLIST(nnames); dirlist = (struct ftpc_dirlist_s *)malloc(allocsize); if (!dirlist) { nerr("ERROR: Failed to allocate dirlist\n"); goto errout; } /* Then copy all of the directory strings into the container */ rewind(filestream); dirlist->nnames = 0; ftpc_nlstparse(filestream, ftpc_addname, dirlist); DEBUGASSERT(nnames == dirlist->nnames); } errout: fclose(filestream); free(absrpath); unlink(tmpfname); free(tmpfname); return dirlist; } /**************************************************************************** * Name: ftpc_dirfree * * Description: * Release the allocated directory listing. * ****************************************************************************/ void ftpc_dirfree(FAR struct ftpc_dirlist_s *dirlist) { int i; if (dirlist) { /* Free each directory name in the directory container */ for (i = 0; i < dirlist->nnames; i++) { /* NULL means that the caller stole the string */ if (dirlist->name[i]) { free(dirlist->name[i]); } } /* Then free the container itself */ free(dirlist); } }