diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in index 32c93cb4..0f58c0ce 100644 --- a/docs/man/sesman.ini.5.in +++ b/docs/man/sesman.ini.5.in @@ -290,6 +290,14 @@ features:- .P - copying-and-pasting of files .RE +.TP +\fBUseNautilus3FlistFormat\fR=\fI[false|true]\fR +Defaults to \fIfalse\fR. +Set to \fItrue\fR to make file copy-paste compatible with Nautilus from +GNOME 3 versions later than 3.29.92. Do not use this for any other reason. + +This setting will be removed in a later version of xrdp, when GNOME 3 is +no longer supported. .SH "SESSIONS VARIABLES" All entries in the \fB[SessionVariables]\fR section are set as diff --git a/sesman/chansrv/chansrv_config.c b/sesman/chansrv/chansrv_config.c index ad8789ec..adbecb5e 100644 --- a/sesman/chansrv/chansrv_config.c +++ b/sesman/chansrv/chansrv_config.c @@ -39,7 +39,7 @@ #define DEFAULT_ENABLE_FUSE_MOUNT 1 #define DEFAULT_FUSE_MOUNT_NAME "xrdp-client" #define DEFAULT_FILE_UMASK 077 - +#define DEFAULT_USE_NAUTILUS3_FLIST_FORMAT 0 /** * Type used for passing a logging function about */ @@ -161,7 +161,7 @@ read_config_chansrv(log_func_t logmsg, { cfg->enable_fuse_mount = g_text2bool(value); } - if (g_strcasecmp(name, "FuseMountName") == 0) + else if (g_strcasecmp(name, "FuseMountName") == 0) { g_free(cfg->fuse_mount_name); cfg->fuse_mount_name = g_strdup(value); @@ -176,6 +176,10 @@ read_config_chansrv(log_func_t logmsg, { cfg->file_umask = strtol(value, NULL, 0); } + else if (g_strcasecmp(name, "UseNautilus3FlistFormat") == 0) + { + cfg->use_nautilus3_flist_format = g_text2bool(value); + } } return error; @@ -206,6 +210,7 @@ new_config(void) cfg->restrict_outbound_clipboard = DEFAULT_RESTRICT_OUTBOUND_CLIPBOARD; cfg->fuse_mount_name = fuse_mount_name; cfg->file_umask = DEFAULT_FILE_UMASK; + cfg->use_nautilus3_flist_format = DEFAULT_USE_NAUTILUS3_FLIST_FORMAT; } return cfg; @@ -290,6 +295,8 @@ config_dump(struct config_chansrv *config) g_bool2text(config->enable_fuse_mount)); g_writeln(" FuseMountName: %s", config->fuse_mount_name); g_writeln(" FileMask: 0%o", config->file_umask); + g_writeln(" Nautilus 3 Flist Format: %s", + g_bool2text(config->use_nautilus3_flist_format)); } /******************************************************************************/ diff --git a/sesman/chansrv/chansrv_config.h b/sesman/chansrv/chansrv_config.h index 3d0ff379..f4acd592 100644 --- a/sesman/chansrv/chansrv_config.h +++ b/sesman/chansrv/chansrv_config.h @@ -36,6 +36,9 @@ struct config_chansrv char *fuse_mount_name; /** FileUmask from sesman.ini */ mode_t file_umask; + + /** Whether to use nautilus3-compatible file lists for the clipboard */ + int use_nautilus3_flist_format; }; diff --git a/sesman/chansrv/clipboard.c b/sesman/chansrv/clipboard.c index a4192072..36ddc39f 100644 --- a/sesman/chansrv/clipboard.c +++ b/sesman/chansrv/clipboard.c @@ -1199,6 +1199,95 @@ clipboard_process_data_response_for_image(struct stream *s, return 0; } +/**************************************************************************//** + * Process a CB_FORMAT_DATA_RESPONSE for an X client requesting a file list + * + * @param s Stream containing CLIPRDR_FILELIST ([MS-RDPECLIP]) + * @param clip_msg_status msgFlags from Clipboard PDU Header + * @param clip_msg_len dataLen from Clipboard PDU Header + * + * @return Status + */ +static int +clipboard_process_data_response_for_file(struct stream *s, + int clip_msg_status, + int clip_msg_len) +{ + XSelectionRequestEvent *lxev; + int rv = 0; + + LOG_DEVEL(LOG_LEVEL_TRACE, "clipboard_process_data_response_for_file: "); + lxev = &g_saved_selection_req_event; + + const int flist_size = 1024 * 1024; + g_free(g_clip_c2s.data); + g_clip_c2s.data = (char *)g_malloc(flist_size, 0); + if (g_clip_c2s.data == NULL) + { + LOG(LOG_LEVEL_ERROR, "clipboard_process_data_response_for_file: " + "Can't allocate memory"); + rv = 1; + } + /* text/uri-list */ + else if (g_clip_c2s.type == g_file_atom1) + { + rv = clipboard_c2s_in_files(s, g_clip_c2s.data, flist_size, + "file://"); + } + /* x-special/gnome-copied-files */ + else if (g_clip_c2s.type == g_file_atom2) + { + g_strcpy(g_clip_c2s.data, "copy\n"); + rv = clipboard_c2s_in_files(s, g_clip_c2s.data + 5, flist_size - 5, + "file://"); + } + else if ((g_clip_c2s.type == XA_STRING) || + (g_clip_c2s.type == g_utf8_atom)) + { + if (g_cfg->use_nautilus3_flist_format) + { + /* + * This file list format is only used by GNOME 3 + * versions >= 3.29.92. It is not used by GNOME 4. Remove + * this workaround when GNOME 3 is no longer supported by + * long-term distros */ +#define LIST_PREFIX "x-special/nautilus-clipboard\ncopy\n" +#define LIST_PREFIX_LEN (sizeof(LIST_PREFIX) - 1) + g_strcpy(g_clip_c2s.data, LIST_PREFIX); + rv = clipboard_c2s_in_files(s, + g_clip_c2s.data + LIST_PREFIX_LEN, + flist_size - LIST_PREFIX_LEN - 1, + "file://"); + g_strcat(g_clip_c2s.data, "\n"); +#undef LIST_PREFIX_LEN +#undef LIST_PREFIX + } + else + { + rv = clipboard_c2s_in_files(s, g_clip_c2s.data, flist_size, ""); + } + } + else + { + LOG_DEVEL(LOG_LEVEL_ERROR, + "clipboard_process_data_response_for_file: " + "Unrecognised target"); + rv = 1; + } + + if (rv != 0 && g_clip_c2s.data != NULL) + { + g_clip_c2s.data[0] = '\0'; + } + + g_clip_c2s.total_bytes = + (g_clip_c2s.data == NULL) ? 0 : g_strlen(g_clip_c2s.data); + g_clip_c2s.read_bytes_done = g_clip_c2s.total_bytes; + clipboard_provide_selection_c2s(lxev, lxev->target); + + return rv; +} + /*****************************************************************************/ /* client to server */ /* sent as a reply to CB_FORMAT_DATA_REQUEST; used to indicate whether @@ -1227,26 +1316,8 @@ clipboard_process_data_response(struct stream *s, int clip_msg_status, } if (g_clip_c2s.xrdp_clip_type == XRDP_CB_FILE) { - g_free(g_clip_c2s.data); - g_clip_c2s.data = (char *)g_malloc(1024 * 1024, 1); - /* text/uri-list */ - if (g_clip_c2s.type == g_file_atom1) - { - clipboard_c2s_in_files(s, g_clip_c2s.data); - } - /* x-special/gnome-copied-files */ - else if (g_clip_c2s.type == g_file_atom2) - { - g_strcpy(g_clip_c2s.data, "copy\n"); - clipboard_c2s_in_files(s, g_clip_c2s.data + 5); - } - else - { - LOG_DEVEL(LOG_LEVEL_ERROR, "clipboard_process_data_response: error"); - } - g_clip_c2s.total_bytes = g_strlen(g_clip_c2s.data); - g_clip_c2s.read_bytes_done = g_clip_c2s.total_bytes; - clipboard_provide_selection_c2s(lxev, lxev->target); + clipboard_process_data_response_for_file(s, clip_msg_status, + clip_msg_len); return 0; } LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_process_data_response: " @@ -2168,11 +2239,24 @@ clipboard_event_selection_request(XEvent *xevent) } else if ((lxev->target == XA_STRING) || (lxev->target == g_utf8_atom)) { - g_memcpy(&g_saved_selection_req_event, lxev, - sizeof(g_saved_selection_req_event)); - g_clip_c2s.type = lxev->target; - g_clip_c2s.xrdp_clip_type = XRDP_CB_TEXT; - clipboard_send_data_request(CB_FORMAT_UNICODETEXT); + if (clipboard_find_format_id(g_file_format_id) >= 0) + { + LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: " + "text requested when files available"); + g_memcpy(&g_saved_selection_req_event, lxev, + sizeof(g_saved_selection_req_event)); + g_clip_c2s.type = lxev->target; + g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE; + clipboard_send_data_request(g_file_format_id); + } + else + { + g_memcpy(&g_saved_selection_req_event, lxev, + sizeof(g_saved_selection_req_event)); + g_clip_c2s.type = lxev->target; + g_clip_c2s.xrdp_clip_type = XRDP_CB_TEXT; + clipboard_send_data_request(CB_FORMAT_UNICODETEXT); + } return 0; } else if (lxev->target == g_image_bmp_atom) diff --git a/sesman/chansrv/clipboard_file.c b/sesman/chansrv/clipboard_file.c index c317da79..c5ed50d4 100644 --- a/sesman/chansrv/clipboard_file.c +++ b/sesman/chansrv/clipboard_file.c @@ -597,33 +597,41 @@ clipboard_c2s_in_file_info(struct stream *s, struct clip_file_desc *cfd) } /*****************************************************************************/ -/* See [MS-RDPECLIP] 2.2.5.2.3 */ int -clipboard_c2s_in_files(struct stream *s, char *file_list) +clipboard_c2s_in_files(struct stream *s, char *file_list, int file_list_size, + const char *fprefix) { - int cItems; + int citems; int lindex; int str_len; - int file_count; struct clip_file_desc cfd; char *ptr; + char *last; /* Last writeable char in buffer */ + int dropped_files = 0; /* # files we can't add to buffer */ - if (!s_check_rem(s, 4)) + if (file_list_size < 1) { - LOG_DEVEL(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: parse error"); + LOG(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: No space in string"); return 1; } - in_uint32_le(s, cItems); - if (cItems > 64 * 1024) /* sanity check */ + if (!s_check_rem(s, 4)) { - LOG_DEVEL(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: error cItems %d too big", cItems); + LOG(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: parse error"); + return 1; + } + in_uint32_le(s, citems); + if (citems < 0 || citems > 64 * 1024) /* sanity check */ + { + LOG(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: " + "Bad number of files in list (%d)", citems); return 1; } xfuse_clear_clip_dir(); - LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_c2s_in_files: cItems %d", cItems); - file_count = 0; + LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_c2s_in_files: cItems %d", citems); ptr = file_list; - for (lindex = 0; lindex < cItems; lindex++) + last = file_list + file_list_size - 1; + + for (lindex = 0; lindex < citems; lindex++) { g_memset(&cfd, 0, sizeof(struct clip_file_desc)); if (clipboard_c2s_in_file_info(s, &cfd) != 0) @@ -633,37 +641,62 @@ clipboard_c2s_in_files(struct stream *s, char *file_list) if ((g_pos(cfd.cFileName, "\\") >= 0) || (cfd.fileAttributes & CB_FILE_ATTRIBUTE_DIRECTORY)) { - LOG_DEVEL(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: skipping directory not " - "supported [%s]", cfd.cFileName); + LOG(LOG_LEVEL_WARNING, "clipboard_c2s_in_files: skipping " + "directory not supported [%s]", cfd.cFileName); continue; } + + /* Have we already run out of room in the list? */ + if (dropped_files > 0) + { + dropped_files += 1; + continue; + } + + /* Room for this file? */ + str_len = (ptr == file_list) ? 0 : 1; /* Delimiter */ + str_len += g_strlen(fprefix); /* e.g. "file://" */ + str_len += g_strlen(g_fuse_clipboard_path); + str_len += 1; /* '/' */ + str_len += g_strlen(cfd.cFileName); + if (str_len > (last - ptr)) + { + dropped_files += 1; + continue; + } + if (xfuse_add_clip_dir_item(cfd.cFileName, 0, cfd.fileSizeLow, lindex) == -1) { - LOG_DEVEL(LOG_LEVEL_ERROR, "clipboard_c2s_in_files: failed to add clip dir item"); + LOG(LOG_LEVEL_WARNING, "clipboard_c2s_in_files: " + "failed to add clip dir item %s", cfd.cFileName); continue; } - if (file_count > 0) + if (ptr > file_list) { - *ptr = '\n'; - ptr++; + *ptr++ = '\n'; } - file_count++; - g_strcpy(ptr, "file://"); - ptr += 7; + str_len = g_strlen(fprefix); + g_strcpy(ptr, fprefix); + ptr += str_len; str_len = g_strlen(g_fuse_clipboard_path); g_strcpy(ptr, g_fuse_clipboard_path); ptr += str_len; - - *ptr = '/'; - ptr++; + *ptr++ = '/'; str_len = g_strlen(cfd.cFileName); g_strcpy(ptr, cfd.cFileName); ptr += str_len; } - *ptr = 0; + *ptr = '\0'; + + if (dropped_files > 0) + { + LOG(LOG_LEVEL_WARNING, "clipboard_c2s_in_files: " + "Dropped %d files from the clip buffer due to insufficient space", + dropped_files); + } return 0; } diff --git a/sesman/chansrv/clipboard_file.h b/sesman/chansrv/clipboard_file.h index 16377b0e..0cb9994c 100644 --- a/sesman/chansrv/clipboard_file.h +++ b/sesman/chansrv/clipboard_file.h @@ -30,8 +30,26 @@ clipboard_process_file_request(struct stream *s, int clip_msg_status, int clipboard_process_file_response(struct stream *s, int clip_msg_status, int clip_msg_len); +/** + * Process a CLIPRDR_FILELIST - see [MS-RDPECLIP] 2.2.5.2.3 + * + * Files in the list are added to the xfs filesystem in the clipboard + * directory. The filenames names are added to the 'file_list' for the user. + * Files are prefixed with '/' and separated by '\n'. + * + * If the list is not big enough, whole filenames are omitted, and a warning + * message is logged. This is not an error. + * + * @param s Input stream containing CLIPRDR_FILELIST + * @param file_list Output buffer for filenames + * @param file_list_size Size of buffer, including space for '\0'. + * @param fprefix Prefix for each file in the file list (e.g. "file://") + * + * @return Zero for success. + */ int -clipboard_c2s_in_files(struct stream *s, char *file_list); +clipboard_c2s_in_files(struct stream *s, char *file_list, int file_list_size, + const char *fprefix); int clipboard_request_file_size(int stream_id, int lindex); diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in index 59f99774..7b8f0880 100644 --- a/sesman/sesman.ini.in +++ b/sesman/sesman.ini.in @@ -125,6 +125,10 @@ FuseMountName=thinclient_drives FileUmask=077 ; Can be used to disable FUSE functionality - see sesman.ini(5) #EnableFuseMount=false +; Uncomment this line only if you are using GNOME 3 versions 3.29.92 +; and up, and you wish to cut-paste files between Nautilus and Windows. Do +; not use this setting for GNOME 4, or other file managers +#UseNautilus3FlistFormat=true [ChansrvLogging] ; Note: one log file is created per display and the LogFile config value