wClipboard/posix: add directories to file list
authorilammy <a.lozovsky@gmail.com>
Sat, 8 Apr 2017 23:29:51 +0000 (02:29 +0300)
committerilammy <a.lozovsky@gmail.com>
Sun, 9 Apr 2017 00:15:49 +0000 (03:15 +0300)
text/uri-list contains only the files which were immediately selected by
the user. However, we need to enumerate *all* files and directories to
be pasted in CLIPRDR_FILELIST. Thus we need to walk through the
directories and add their content to the file list as well.

We use readdir() function to traverse the directory entries. It has more
sane interface than readdir_r(), but lacks (standardized) thread-safety
guarantees.  However, most C liraries guarantee that so we can use it.
There is no compile-time check because it cannot be made robust. You
deserve a crash here if you are using a C library developed by people
who cannot keep their unhealthy addiction to global state under control.

Note that recursive traversal is also a good opportunity to maintain
good remote names. We just need to concatenate the directory paths and
file names correctly.

However, this recursion has one caveat: it is not bounded, so if the
file system contains a loop then we will crash due to a stack overflow.
We could track symlink loops (and hardlinks too if we try hard) to avoid
the crash, but I think it's not a common thing to do so we can ignore
this possibility.

winpr/libwinpr/clipboard/posix.c

index 4d92317..bf7e855 100644 (file)
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 #include <errno.h>
 
+#include <dirent.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -169,6 +170,153 @@ error:
        return NULL;
 }
 
+static char* concat_local_name(const char* dir, const char* file)
+{
+       size_t len_dir = 0;
+       size_t len_file = 0;
+       char* buffer = NULL;
+
+       len_dir = strlen(dir);
+       len_file = strlen(file);
+
+       buffer = calloc(len_dir + 1 + len_file + 1, sizeof(char));
+       if (!buffer)
+               return NULL;
+
+       memcpy(buffer, dir, len_dir * sizeof(char));
+       buffer[len_dir] = '/';
+       memcpy(buffer + len_dir + 1, file, len_file * sizeof(char));
+
+       return buffer;
+}
+
+static WCHAR* concat_remote_name(const WCHAR* dir, const WCHAR* file)
+{
+       size_t len_dir = 0;
+       size_t len_file = 0;
+       WCHAR* buffer = NULL;
+
+       len_dir = _wcslen(dir);
+       len_file = _wcslen(file);
+
+       buffer = calloc(len_dir + 1 + len_file + 1, sizeof(WCHAR));
+       if (!buffer)
+               return NULL;
+
+       memcpy(buffer, dir, len_dir * sizeof(WCHAR));
+       buffer[len_dir] = L'\\';
+       memcpy(buffer + len_dir + 1, file, len_file * sizeof(WCHAR));
+
+       return buffer;
+}
+
+static BOOL add_file_to_list(const char* local_name, const WCHAR* remote_name, wArrayList* files);
+
+static BOOL add_directory_entry_to_list(const char* local_dir_name, const WCHAR* remote_dir_name,
+               const struct dirent* entry, wArrayList* files)
+{
+       BOOL result = FALSE;
+       char* local_name = NULL;
+       WCHAR* remote_name = NULL;
+       WCHAR* remote_base_name = NULL;
+
+       /* Skip special directory entries. */
+       if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0))
+               return TRUE;
+
+       /*
+        * As noted in add_file_to_list(), it is not always correct to assume
+        * that that file names are encoded in UTF-8. However, this is the
+        * most sane thing to do at the moment.
+        */
+       if (!ConvertToUnicode(CP_UTF8, 0, entry->d_name, -1, &remote_base_name, 0))
+       {
+               WLog_ERR(TAG, "Unicode conversion failed for %s", entry->d_name);
+               return FALSE;
+       }
+
+       local_name = concat_local_name(local_dir_name, entry->d_name);
+       remote_name = concat_remote_name(remote_dir_name, remote_base_name);
+
+       if (local_name && remote_name)
+               result = add_file_to_list(local_name, remote_name, files);
+
+       free(remote_base_name);
+       free(remote_name);
+       free(local_name);
+
+       return result;
+}
+
+static BOOL do_add_directory_contents_to_list(const char* local_name, const WCHAR* remote_name,
+               DIR* dirp, wArrayList* files)
+{
+       /*
+        * For some reason POSIX does not require readdir() to be thread-safe.
+        * However, readdir_r() has really insane interface and is pretty bad
+        * replacement for it. Fortunately, most C libraries guarantee thread-
+        * safety of readdir() when it is used for distinct directory streams.
+        *
+        * Thus we can use readdir() in multithreaded applications if we are
+        * sure that it will not corrupt some global data. It would be nice
+        * to have a compile-time check for this here, but some C libraries
+        * do not provide a #define because of reasons (I'm looking at you,
+        * musl). We should not be breaking people's builds because of that,
+        * so we do nothing and proceed with fingers crossed.
+        */
+
+       for (;;)
+       {
+               struct dirent* entry = NULL;
+
+               errno = 0;
+               entry = readdir(dirp);
+               if (!entry)
+               {
+                       int err = errno;
+                       if (!err)
+                               break;
+
+                       WLog_ERR(TAG, "failed to read directory: %s", strerror(err));
+                       return FALSE;
+               }
+
+               if (!add_directory_entry_to_list(local_name, remote_name, entry, files))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+static BOOL add_directory_contents_to_list(const char* local_name, const WCHAR* remote_name,
+               wArrayList* files)
+{
+       BOOL result = FALSE;
+       DIR* dirp = NULL;
+
+#ifdef WITH_DEBUG_WCLIPBOARD
+       WLog_DBG(TAG, "adding directory: %s", local_name);
+#endif
+
+       dirp = opendir(local_name);
+       if (!dirp)
+       {
+               int err = errno;
+               WLog_ERR(TAG, "failed to open directory %s: %s", local_name, strerror(err));
+               goto out;
+       }
+
+       result = do_add_directory_contents_to_list(local_name, remote_name, dirp, files);
+
+       if (closedir(dirp))
+       {
+               int err = errno;
+               WLog_WARN(TAG, "failed to close directory: %s", strerror(err));
+       }
+out:
+       return result;
+}
+
 static BOOL add_file_to_list(const char* local_name, const WCHAR* remote_name, wArrayList* files)
 {
        struct posix_file* file = NULL;
@@ -188,6 +336,16 @@ static BOOL add_file_to_list(const char* local_name, const WCHAR* remote_name, w
                return FALSE;
        }
 
+       if (file->is_directory)
+       {
+               /*
+                * This is effectively a recursive call, but we do not track
+                * recursion depth, thus filesystem loops can cause a crash.
+                */
+               if (!add_directory_contents_to_list(local_name, remote_name, files))
+                       return FALSE;
+       }
+
        return TRUE;
 }