/** * FreeRDP: A Remote Desktop Protocol Implementation * Windows Clipboard Redirection * * Copyright 2012 Jason Champion * * Licensed 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "wf_cliprdr.h" extern BOOL WINAPI AddClipboardFormatListener(_In_ HWND hwnd); extern BOOL WINAPI RemoveClipboardFormatListener(_In_ HWND hwnd); #define WM_CLIPRDR_MESSAGE (WM_USER + 156) #define OLE_SETCLIPBOARD 1 /* this macro will update _p pointer */ #define Read_UINT32(_p, _v) do { _v = \ (UINT32)(*_p) + \ (((UINT32)(*(_p + 1))) << 8) + \ (((UINT32)(*(_p + 2))) << 16) + \ (((UINT32)(*(_p + 3))) << 24); \ _p += 4; } while (0) /* this macro will NOT update _p pointer */ #define Write_UINT32(_p, _v) do { \ *(_p) = (_v) & 0xFF; \ *(_p + 1) = ((_v) >> 8) & 0xFF; \ *(_p + 2) = ((_v) >> 16) & 0xFF; \ *(_p + 3) = ((_v) >> 24) & 0xFF; } while (0) BOOL wf_create_file_obj(cliprdrContext *cliprdr, IDataObject **ppDataObject); void wf_destroy_file_obj(IDataObject *instance); static UINT32 get_local_format_id_by_name(cliprdrContext *cliprdr, void *format_name) { formatMapping *map; int i; for (i = 0; i < cliprdr->map_size; i++) { map = &cliprdr->format_mappings[i]; if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0) { if (map->name) { if (memcmp(map->name, format_name, wcslen((LPCWSTR)format_name)) == 0) return map->local_format_id; } } } return 0; } static INLINE BOOL file_transferring(cliprdrContext *cliprdr) { return !!get_local_format_id_by_name(cliprdr, L"FileGroupDescriptorW"); } static UINT32 get_remote_format_id(cliprdrContext *cliprdr, UINT32 local_format) { formatMapping *map; int i; for (i = 0; i < cliprdr->map_size; i++) { map = &cliprdr->format_mappings[i]; if (map->local_format_id == local_format) { return map->remote_format_id; } } return local_format; } static void map_ensure_capacity(cliprdrContext *cliprdr) { if (cliprdr->map_size >= cliprdr->map_capacity) { cliprdr->format_mappings = (formatMapping *)realloc(cliprdr->format_mappings, sizeof(formatMapping) * cliprdr->map_capacity * 2); cliprdr->map_capacity *= 2; } } static void clear_format_map(cliprdrContext *cliprdr) { formatMapping *map; int i; if (cliprdr->format_mappings) { for (i = 0; i < cliprdr->map_capacity; i++) { map = &cliprdr->format_mappings[i]; map->remote_format_id = 0; map->local_format_id = 0; if (map->name) { free(map->name); map->name = NULL; } } } cliprdr->map_size= 0; } /* 2.2.2.3 Client Temporary Directory PDU (CLIPRDR_TEMP_DIRECTORY) The Temporary Directory PDU is an optional PDU sent from the client to the server. This PDU informs the server of a location on the client file system that MUST be used to deposit files being copied to the client. The location MUST be accessible by the server to be useful. Section 3.1.1.3 specifies how direct file access impacts file copy and paste. */ int cliprdr_send_tempdir(cliprdrContext *cliprdr) { RDP_CB_TEMPDIR_EVENT *cliprdr_event; cliprdr_event = (RDP_CB_TEMPDIR_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_TemporaryDirectory, NULL, NULL); if (!cliprdr_event) return -1; /* Sending the TEMP path would only be valid iff the path is accessible from the server. This should perhaps to change to a command line parameter value */ GetEnvironmentVariableW(L"TEMP", (LPWSTR)cliprdr_event->dirname, 260); return freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); } static void cliprdr_send_format_list(cliprdrContext *cliprdr) { RDP_CB_FORMAT_LIST_EVENT *cliprdr_event; BYTE *format_data; int format = 0; int data_size; int format_count; int len = 0; int namelen; int stream_file_transferring = FALSE; if (!OpenClipboard(cliprdr->hwndClipboard)) { DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); return; } format_count = CountClipboardFormats(); data_size = format_count * (4 + MAX_PATH * 2); format_data = (BYTE*) calloc(1, data_size); assert(format_data != NULL); while (format = EnumClipboardFormats(format)) { Write_UINT32(format_data + len, format); len += 4; if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0) { if (format >= CF_MAX) { namelen = GetClipboardFormatNameW(format, (LPWSTR)(format_data + len), MAX_PATH); if ((wcscmp((LPWSTR)(format_data + len), L"FileNameW") == 0) || (wcscmp((LPWSTR)(format_data + len), L"FileName") == 0) || (wcscmp((LPWSTR)(format_data + len), CFSTR_FILEDESCRIPTORW) == 0)) { stream_file_transferring = TRUE; } len += namelen * sizeof(WCHAR); } len += 2; /* end of Unicode string */ } else { if (format >= CF_MAX) { static WCHAR wName[MAX_PATH] = {0}; int wLen; ZeroMemory(wName, MAX_PATH*2); wLen = GetClipboardFormatNameW(format, wName, MAX_PATH); if (wLen < 16) { memcpy(format_data + len, wName, wLen * sizeof(WCHAR)); } else { memcpy(format_data + len, wName, 32); /* truncate the long name to 32 bytes */ } } len += 32; } } CloseClipboard(); cliprdr_event = (RDP_CB_FORMAT_LIST_EVENT *) freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_FormatList, NULL, NULL); if (stream_file_transferring) { cliprdr_event->raw_format_data_size = 4 + 42; cliprdr_event->raw_format_data = (BYTE*) calloc(1, cliprdr_event->raw_format_data_size); format = RegisterClipboardFormatW(L"FileGroupDescriptorW"); Write_UINT32(cliprdr_event->raw_format_data, format); wcscpy_s((WCHAR*)(cliprdr_event->raw_format_data + 4), (cliprdr_event->raw_format_data_size - 4) / 2, L"FileGroupDescriptorW"); } else { cliprdr_event->raw_format_data = (BYTE*) calloc(1, len); assert(cliprdr_event->raw_format_data != NULL); CopyMemory(cliprdr_event->raw_format_data, format_data, len); cliprdr_event->raw_format_data_size = len; } free(format_data); freerdp_channels_send_event(cliprdr->channels, (wMessage*) cliprdr_event); } int cliprdr_send_data_request(cliprdrContext *cliprdr, UINT32 format) { RDP_CB_DATA_REQUEST_EVENT *cliprdr_event; int ret; cliprdr_event = (RDP_CB_DATA_REQUEST_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_DataRequest, NULL, NULL); if (!cliprdr_event) return -1; cliprdr_event->format = get_remote_format_id(cliprdr, format); ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); if (ret != 0) return -1; WaitForSingleObject(cliprdr->response_data_event, INFINITE); ResetEvent(cliprdr->response_data_event); return 0; } int cliprdr_send_lock(cliprdrContext *cliprdr) { RDP_CB_LOCK_CLIPDATA_EVENT *cliprdr_event; int ret; if ((cliprdr->capabilities & CB_CAN_LOCK_CLIPDATA) == 1) { cliprdr_event = (RDP_CB_LOCK_CLIPDATA_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_LockClipdata, NULL, NULL); if (!cliprdr_event) return -1; cliprdr_event->clipDataId = 0; ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); if (ret != 0) return -1; } return 0; } int cliprdr_send_unlock(cliprdrContext *cliprdr) { RDP_CB_UNLOCK_CLIPDATA_EVENT *cliprdr_event; int ret; if ((cliprdr->capabilities & CB_CAN_LOCK_CLIPDATA) == 1) { cliprdr_event = (RDP_CB_UNLOCK_CLIPDATA_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_UnLockClipdata, NULL, NULL); if (!cliprdr_event) return -1; cliprdr_event->clipDataId = 0; ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); if (ret != 0) return -1; } return 0; } int cliprdr_send_request_filecontents(cliprdrContext *cliprdr, void *streamid, int index, int flag, DWORD positionhigh, DWORD positionlow, ULONG nreq) { RDP_CB_FILECONTENTS_REQUEST_EVENT *cliprdr_event; int ret; cliprdr_event = (RDP_CB_FILECONTENTS_REQUEST_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_FilecontentsRequest, NULL, NULL); if (!cliprdr_event) return -1; cliprdr_event->streamId = (UINT32)streamid; cliprdr_event->lindex = index; cliprdr_event->dwFlags = flag; cliprdr_event->nPositionLow = positionlow; cliprdr_event->nPositionHigh = positionhigh; cliprdr_event->cbRequested = nreq; cliprdr_event->clipDataId = 0; ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); if (ret != 0) return -1; WaitForSingleObject(cliprdr->req_fevent, INFINITE); ResetEvent(cliprdr->req_fevent); return 0; } int cliprdr_send_response_filecontents(cliprdrContext *cliprdr, UINT32 streamid, UINT32 size, BYTE *data) { RDP_CB_FILECONTENTS_RESPONSE_EVENT *cliprdr_event; int ret; cliprdr_event = (RDP_CB_FILECONTENTS_RESPONSE_EVENT *)freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_FilecontentsResponse, NULL, NULL); if (!cliprdr_event) return -1; cliprdr_event->streamId = streamid; cliprdr_event->size = size; cliprdr_event->data = data; ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event); if (ret != 0) return -1; return 0; } static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { static cliprdrContext *cliprdr = NULL; switch (Msg) { case WM_CREATE: DEBUG_CLIPRDR("info: %s - WM_CREATE", __FUNCTION__); cliprdr = (cliprdrContext *)((CREATESTRUCT *)lParam)->lpCreateParams; if (!AddClipboardFormatListener(hWnd)) { DEBUG_CLIPRDR("error: AddClipboardFormatListener failed with %#x.", GetLastError()); } cliprdr->hwndClipboard = hWnd; break; case WM_CLOSE: DEBUG_CLIPRDR("info: %s - WM_CLOSE", __FUNCTION__); RemoveClipboardFormatListener(hWnd); break; case WM_CLIPBOARDUPDATE: DEBUG_CLIPRDR("info: %s - WM_CLIPBOARDUPDATE", __FUNCTION__); if (cliprdr->channel_initialized) { if ((GetClipboardOwner() != cliprdr->hwndClipboard) && (S_FALSE == OleIsCurrentClipboard(cliprdr->data_obj))) { if (!cliprdr->hmem) { cliprdr->hmem = GlobalFree(cliprdr->hmem); } cliprdr_send_format_list(cliprdr); } } break; case WM_RENDERALLFORMATS: DEBUG_CLIPRDR("info: %s - WM_RENDERALLFORMATS", __FUNCTION__); /* discard all contexts in clipboard */ if (!OpenClipboard(cliprdr->hwndClipboard)) { DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); break; } EmptyClipboard(); CloseClipboard(); break; case WM_RENDERFORMAT: DEBUG_CLIPRDR("info: %s - WM_RENDERFORMAT", __FUNCTION__); if (cliprdr_send_data_request(cliprdr, (UINT32)wParam) != 0) { DEBUG_CLIPRDR("error: cliprdr_send_data_request failed."); break; } if (SetClipboardData((UINT) wParam, cliprdr->hmem) == NULL) { DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError()); cliprdr->hmem = GlobalFree(cliprdr->hmem); } /* Note: GlobalFree() is not needed when success */ break; case WM_CLIPRDR_MESSAGE: DEBUG_CLIPRDR("info: %s - WM_CLIPRDR_MESSAGE", __FUNCTION__); switch (wParam) { case OLE_SETCLIPBOARD: DEBUG_CLIPRDR("info: %s - OLE_SETCLIPBOARD", __FUNCTION__); if (wf_create_file_obj(cliprdr, &cliprdr->data_obj)) if (OleSetClipboard(cliprdr->data_obj) != S_OK) wf_destroy_file_obj(cliprdr->data_obj); break; default: break; } break; case WM_DESTROYCLIPBOARD: case WM_ASKCBFORMATNAME: case WM_HSCROLLCLIPBOARD: case WM_PAINTCLIPBOARD: case WM_SIZECLIPBOARD: case WM_VSCROLLCLIPBOARD: default: return DefWindowProc(hWnd, Msg, wParam, lParam); } return 0; } static int create_cliprdr_window(cliprdrContext *cliprdr) { WNDCLASSEX wnd_cls; ZeroMemory(&wnd_cls, sizeof(WNDCLASSEX)); wnd_cls.cbSize = sizeof(WNDCLASSEX); wnd_cls.style = CS_OWNDC; wnd_cls.lpfnWndProc = cliprdr_proc; wnd_cls.cbClsExtra = 0; wnd_cls.cbWndExtra = 0; wnd_cls.hIcon = NULL; wnd_cls.hCursor = NULL; wnd_cls.hbrBackground = NULL; wnd_cls.lpszMenuName = NULL; wnd_cls.lpszClassName = L"ClipboardHiddenMessageProcessor"; wnd_cls.hInstance = GetModuleHandle(NULL); wnd_cls.hIconSm = NULL; RegisterClassEx(&wnd_cls); cliprdr->hwndClipboard = CreateWindowEx(WS_EX_LEFT, L"ClipboardHiddenMessageProcessor", L"rdpclip", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), cliprdr); if (cliprdr->hwndClipboard == NULL) { DEBUG_CLIPRDR("error: CreateWindowEx failed with %x.", GetLastError()); return -1; } return 0; } static void *cliprdr_thread_func(void *arg) { int ret; MSG msg; BOOL mcode; cliprdrContext* cliprdr = (cliprdrContext*) arg; OleInitialize(0); if ((ret = create_cliprdr_window(cliprdr)) != 0) { DEBUG_CLIPRDR("error: create clipboard window failed."); return NULL; } while ((mcode = GetMessage(&msg, 0, 0, 0)) != 0) { if (mcode == -1) { DEBUG_CLIPRDR("error: clipboard thread GetMessage failed."); break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } OleUninitialize(); return NULL; } static void clear_file_array(cliprdrContext *cliprdr) { int i; /* clear file_names array */ if (cliprdr->file_names) { for (i = 0; i < cliprdr->nFiles; i++) { if (cliprdr->file_names[i]) { free(cliprdr->file_names[i]); cliprdr->file_names[i] = NULL; } } } /* clear fileDescriptor array */ if (cliprdr->fileDescriptor) { for (i = 0; i < cliprdr->nFiles; i++) { if (cliprdr->fileDescriptor[i]) { free(cliprdr->fileDescriptor[i]); cliprdr->fileDescriptor[i] = NULL; } } } cliprdr->nFiles = 0; } void wf_cliprdr_init(wfContext* wfc, rdpChannels* channels) { cliprdrContext *cliprdr; if (!wfc->instance->settings->RedirectClipboard) { wfc->cliprdr_context = NULL; DEBUG_WARN( "clipboard is not redirected.\n"); return; } wfc->cliprdr_context = (cliprdrContext *) calloc(1, sizeof(cliprdrContext)); cliprdr = (cliprdrContext *) wfc->cliprdr_context; assert(cliprdr != NULL); cliprdr->channels = channels; cliprdr->channel_initialized = FALSE; cliprdr->map_capacity = 32; cliprdr->map_size = 0; cliprdr->format_mappings = (formatMapping *)calloc(1, sizeof(formatMapping) * cliprdr->map_capacity); assert(cliprdr->format_mappings != NULL); cliprdr->file_array_size = 32; cliprdr->file_names = (wchar_t **)calloc(1, cliprdr->file_array_size * sizeof(wchar_t *)); cliprdr->fileDescriptor = (FILEDESCRIPTORW **)calloc(1, cliprdr->file_array_size * sizeof(FILEDESCRIPTORW *)); cliprdr->response_data_event = CreateEvent(NULL, TRUE, FALSE, L"response_data_event"); assert(cliprdr->response_data_event != NULL); cliprdr->req_fevent = CreateEvent(NULL, TRUE, FALSE, L"request_filecontents_event"); cliprdr->ID_FILEDESCRIPTORW = RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW); cliprdr->ID_FILECONTENTS = RegisterClipboardFormatW(CFSTR_FILECONTENTS); cliprdr->ID_PREFERREDDROPEFFECT = RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT); cliprdr->cliprdr_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)cliprdr_thread_func, cliprdr, 0, NULL); assert(cliprdr->cliprdr_thread != NULL); } void wf_cliprdr_uninit(wfContext* wfc) { cliprdrContext *cliprdr = (cliprdrContext *) wfc->cliprdr_context; if (!cliprdr) return; if (cliprdr->hwndClipboard) PostMessage(cliprdr->hwndClipboard, WM_QUIT, 0, 0); if (cliprdr->cliprdr_thread) { WaitForSingleObject(cliprdr->cliprdr_thread, INFINITE); CloseHandle(cliprdr->cliprdr_thread); } if (cliprdr->response_data_event) CloseHandle(cliprdr->response_data_event); clear_file_array(cliprdr); clear_format_map(cliprdr); if (cliprdr->file_names) free(cliprdr->file_names); if (cliprdr->fileDescriptor) free(cliprdr->fileDescriptor); if (cliprdr->format_mappings) free(cliprdr->format_mappings); free(cliprdr); } static void wf_cliprdr_process_cb_clip_caps_event(wfContext *wfc, RDP_CB_CLIP_CAPS *caps_event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; cliprdr->capabilities = caps_event->capabilities; } static void wf_cliprdr_process_cb_monitor_ready_event(wfContext *wfc, RDP_CB_MONITOR_READY_EVENT *ready_event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; #if 0 /*Disabled since the current function only sends the temp directory which is not guaranteed to be accessible to the server */ cliprdr_send_tempdir(cliprdr); #endif cliprdr->channel_initialized = TRUE; cliprdr_send_format_list(wfc->cliprdr_context); } static BOOL wf_cliprdr_get_file_contents(wchar_t *file_name, BYTE *buffer, int positionLow, int positionHigh, int nRequested, unsigned int *puSize) { HANDLE hFile; DWORD nGet; if (file_name == NULL || buffer == NULL || puSize == NULL) { DEBUG_WARN( "get file contents Invalid Arguments.\n"); return FALSE; } hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } SetFilePointer(hFile, positionLow, (PLONG)&positionHigh, FILE_BEGIN); if (!ReadFile(hFile, buffer, nRequested, &nGet, NULL)) { DWORD err = GetLastError(); DEBUG_CLIPRDR("ReadFile failed with 0x%x.", err); } CloseHandle(hFile); *puSize = nGet; return TRUE; } /* path_name has a '\' at the end. e.g. c:\newfolder\, file_name is c:\newfolder\new.txt */ static FILEDESCRIPTORW *wf_cliprdr_get_file_descriptor(WCHAR* file_name, int pathLen) { FILEDESCRIPTORW *fd; HANDLE hFile; fd = (FILEDESCRIPTORW*) malloc(sizeof(FILEDESCRIPTORW)); if (!fd) return NULL; ZeroMemory(fd, sizeof(FILEDESCRIPTORW)); hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile == INVALID_HANDLE_VALUE) { free(fd); return NULL; } fd->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI; fd->dwFileAttributes = GetFileAttributes(file_name); if (!GetFileTime(hFile, NULL, NULL, &fd->ftLastWriteTime)) { fd->dwFlags &= ~FD_WRITESTIME; } fd->nFileSizeLow = GetFileSize(hFile, &fd->nFileSizeHigh); wcscpy_s(fd->cFileName, sizeof(fd->cFileName) / 2, file_name + pathLen); CloseHandle(hFile); return fd; } static void wf_cliprdr_array_ensure_capacity(cliprdrContext *cliprdr) { if (cliprdr->nFiles == cliprdr->file_array_size) { cliprdr->file_array_size *= 2; cliprdr->fileDescriptor = (FILEDESCRIPTORW **)realloc(cliprdr->fileDescriptor, cliprdr->file_array_size * sizeof(FILEDESCRIPTORW *)); cliprdr->file_names = (wchar_t **)realloc(cliprdr->file_names, cliprdr->file_array_size * sizeof(wchar_t *)); } } static void wf_cliprdr_add_to_file_arrays(cliprdrContext *cliprdr, WCHAR *full_file_name, int pathLen) { /* add to name array */ cliprdr->file_names[cliprdr->nFiles] = (LPWSTR) malloc(MAX_PATH); wcscpy_s(cliprdr->file_names[cliprdr->nFiles], MAX_PATH, full_file_name); /* add to descriptor array */ cliprdr->fileDescriptor[cliprdr->nFiles] = wf_cliprdr_get_file_descriptor(full_file_name, pathLen); cliprdr->nFiles++; wf_cliprdr_array_ensure_capacity(cliprdr); } static void wf_cliprdr_traverse_directory(cliprdrContext *cliprdr, wchar_t *Dir, int pathLen) { WIN32_FIND_DATA FindFileData; HANDLE hFind; wchar_t DirSpec[MAX_PATH]; StringCchCopy(DirSpec,MAX_PATH,Dir); StringCchCat(DirSpec,MAX_PATH,TEXT("\\*")); hFind = FindFirstFile(DirSpec,&FindFileData); if(hFind == INVALID_HANDLE_VALUE) { DEBUG_CLIPRDR("FindFirstFile failed with 0x%x.", GetLastError()); return; } while(FindNextFile(hFind, &FindFileData)) { if((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && wcscmp(FindFileData.cFileName,L".") == 0 || wcscmp(FindFileData.cFileName,L"..") == 0) { continue; } if((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) !=0 ) { wchar_t DirAdd[MAX_PATH]; StringCchCopy(DirAdd,MAX_PATH,Dir); StringCchCat(DirAdd,MAX_PATH,TEXT("\\")); StringCchCat(DirAdd,MAX_PATH,FindFileData.cFileName); wf_cliprdr_add_to_file_arrays(cliprdr, DirAdd, pathLen); wf_cliprdr_traverse_directory(cliprdr, DirAdd, pathLen); } else { WCHAR fileName[MAX_PATH]; StringCchCopy(fileName,MAX_PATH,Dir); StringCchCat(fileName,MAX_PATH,TEXT("\\")); StringCchCat(fileName,MAX_PATH,FindFileData.cFileName); wf_cliprdr_add_to_file_arrays(cliprdr, fileName, pathLen); } } FindClose(hFind); } static void wf_cliprdr_process_cb_data_request_event(wfContext* wfc, RDP_CB_DATA_REQUEST_EVENT* event) { HANDLE hClipdata; int size = 0; char* buff = NULL; char* globlemem = NULL; UINT32 local_format; cliprdrContext* cliprdr = (cliprdrContext*) wfc->cliprdr_context; RDP_CB_DATA_RESPONSE_EVENT* response_event; local_format = event->format; if (local_format == FORMAT_ID_PALETTE) { /* TODO: implement this */ DEBUG_CLIPRDR("FORMAT_ID_PALETTE is not supported yet."); } else if (local_format == FORMAT_ID_METAFILE) { /* TODO: implement this */ DEBUG_CLIPRDR("FORMAT_ID_MATEFILE is not supported yet."); } else if (local_format == RegisterClipboardFormatW(L"FileGroupDescriptorW")) { HRESULT result; LPDATAOBJECT dataObj; FORMATETC format_etc; STGMEDIUM stg_medium; DROPFILES *dropFiles; int len; int i; wchar_t *wFileName; unsigned int uSize; DEBUG_CLIPRDR("file descriptors request."); result = OleGetClipboard(&dataObj); if (!SUCCEEDED(result)) { DEBUG_CLIPRDR("OleGetClipboard failed."); } ZeroMemory(&format_etc, sizeof(FORMATETC)); ZeroMemory(&stg_medium, sizeof(STGMEDIUM)); /* try to get FileGroupDescriptorW struct from OLE */ format_etc.cfFormat = local_format; format_etc.tymed = TYMED_HGLOBAL; format_etc.dwAspect = 1; format_etc.lindex = -1; format_etc.ptd = 0; result = IDataObject_GetData(dataObj, &format_etc, &stg_medium); if (SUCCEEDED(result)) { DEBUG_CLIPRDR("Got FileGroupDescriptorW."); globlemem = (char *)GlobalLock(stg_medium.hGlobal); uSize = GlobalSize(stg_medium.hGlobal); size = uSize; buff = (char*) malloc(uSize); CopyMemory(buff, globlemem, uSize); GlobalUnlock(stg_medium.hGlobal); ReleaseStgMedium(&stg_medium); clear_file_array(cliprdr); } else { /* get DROPFILES struct from OLE */ format_etc.cfFormat = CF_HDROP; format_etc.tymed = TYMED_HGLOBAL; format_etc.dwAspect = 1; format_etc.lindex = -1; result = IDataObject_GetData(dataObj, &format_etc, &stg_medium); if (!SUCCEEDED(result)) { DEBUG_CLIPRDR("dataObj->GetData failed."); } globlemem = (char *)GlobalLock(stg_medium.hGlobal); if (globlemem == NULL) { GlobalUnlock(stg_medium.hGlobal); ReleaseStgMedium(&stg_medium); cliprdr->nFiles = 0; goto exit; } uSize = GlobalSize(stg_medium.hGlobal); dropFiles = (DROPFILES *)malloc(uSize); memcpy(dropFiles, globlemem, uSize); GlobalUnlock(stg_medium.hGlobal); ReleaseStgMedium(&stg_medium); clear_file_array(cliprdr); if (dropFiles->fWide) { wchar_t *p; int str_len; int offset; int pathLen; /* dropFiles contains file names */ for (wFileName = (wchar_t *)((char *)dropFiles + dropFiles->pFiles); (len = wcslen(wFileName)) > 0; wFileName += len + 1) { /* get path name */ str_len = wcslen(wFileName); offset = str_len; /* find the last '\' in full file name */ for (p = wFileName + offset; *p != L'\\'; p--) { ; } p += 1; pathLen = wcslen(wFileName) - wcslen(p); wf_cliprdr_add_to_file_arrays(cliprdr, wFileName, pathLen); if ((cliprdr->fileDescriptor[cliprdr->nFiles - 1]->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { /* this is a directory */ wf_cliprdr_traverse_directory(cliprdr, wFileName, pathLen); } } } else { char *p; for (p = (char *)((char *)dropFiles + dropFiles->pFiles); (len = strlen(p)) > 0; p += len + 1, cliprdr->nFiles++) { int cchWideChar; cchWideChar = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, NULL, 0); cliprdr->file_names[cliprdr->nFiles] = (LPWSTR)malloc(cchWideChar); MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, cliprdr->file_names[cliprdr->nFiles], cchWideChar); if (cliprdr->nFiles == cliprdr->file_array_size) { cliprdr->file_array_size *= 2; cliprdr->fileDescriptor = (FILEDESCRIPTORW **)realloc(cliprdr->fileDescriptor, cliprdr->file_array_size * sizeof(FILEDESCRIPTORW *)); cliprdr->file_names = (wchar_t **)realloc(cliprdr->file_names, cliprdr->file_array_size * sizeof(wchar_t *)); } } } exit: size = 4 + cliprdr->nFiles * sizeof(FILEDESCRIPTORW); buff = (char *)malloc(size); Write_UINT32(buff, cliprdr->nFiles); for (i = 0; i < cliprdr->nFiles; i++) { if (cliprdr->fileDescriptor[i]) { memcpy(buff + 4 + i * sizeof(FILEDESCRIPTORW), cliprdr->fileDescriptor[i], sizeof(FILEDESCRIPTORW)); } } } IDataObject_Release(dataObj); } else { if (!OpenClipboard(cliprdr->hwndClipboard)) { DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); return; } hClipdata = GetClipboardData(event->format); if (!hClipdata) { DEBUG_CLIPRDR("GetClipboardData failed."); CloseClipboard(); return; } globlemem = (char*) GlobalLock(hClipdata); size = (int) GlobalSize(hClipdata); buff = (char*) malloc(size); memcpy(buff, globlemem, size); GlobalUnlock(hClipdata); CloseClipboard(); } response_event = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_DataResponse, NULL, NULL); response_event->data = (BYTE *)buff; response_event->size = size; freerdp_channels_send_event(cliprdr->channels, (wMessage*) response_event); /* Note: don't free buffer here. */ } static void wf_cliprdr_process_cb_format_list_event(wfContext *wfc, RDP_CB_FORMAT_LIST_EVENT *event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; UINT32 left_size = event->raw_format_data_size; int i = 0; BYTE *p; BYTE* end_mark; BOOL format_forbidden = FALSE; /* ignore the formats member in event struct, only parsing raw_format_data */ p = event->raw_format_data; end_mark = p + event->raw_format_data_size; clear_format_map(cliprdr); if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0) { while (left_size >= 6) { formatMapping *map; BYTE* tmp; int name_len; map = &cliprdr->format_mappings[i++]; Read_UINT32(p, map->remote_format_id); map->name = NULL; /* get name_len */ for (tmp = p, name_len = 0; tmp + 1 < end_mark; tmp += 2, name_len += 2) { if (*((unsigned short*) tmp) == 0) break; } if (name_len > 0) { map->name = malloc(name_len + 2); memcpy(map->name, p, name_len + 2); map->local_format_id = RegisterClipboardFormatW((LPCWSTR)map->name); } else { map->local_format_id = map->remote_format_id; } left_size -= name_len + 4 + 2; p += name_len + 2; /* p's already +4 when Read_UINT32() is called. */ cliprdr->map_size++; map_ensure_capacity(cliprdr); } } else { UINT32 k; for (k = 0; k < event->raw_format_data_size / 36; k++) { int name_len; formatMapping* map; map = &cliprdr->format_mappings[i++]; Read_UINT32(p, map->remote_format_id); map->name = NULL; if (event->raw_format_unicode) { /* get name_len, in bytes, if the file name is truncated, no terminated null will be included. */ for (name_len = 0; name_len < 32; name_len += 2) { if (*((unsigned short*) (p + name_len)) == 0) break; } if (name_len > 0) { map->name = calloc(1, name_len + 2); memcpy(map->name, p, name_len); map->local_format_id = RegisterClipboardFormatW((LPCWSTR)map->name); } else { map->local_format_id = map->remote_format_id; } } else { /* get name_len, in bytes, if the file name is truncated, no terminated null will be included. */ for (name_len = 0; name_len < 32; name_len += 1) { if (*((unsigned char*) (p + name_len)) == 0) break; } if (name_len > 0) { map->name = calloc(1, name_len + 1); memcpy(map->name, p, name_len); map->local_format_id = RegisterClipboardFormatA((LPCSTR)map->name); } else { map->local_format_id = map->remote_format_id; } } p += 32; /* p's already +4 when Read_UINT32() is called. */ cliprdr->map_size++; map_ensure_capacity(cliprdr); } } if (file_transferring(cliprdr)) { PostMessage(cliprdr->hwndClipboard, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, 0); } else { if (!OpenClipboard(cliprdr->hwndClipboard)) return; if (EmptyClipboard()) for (i = 0; i < cliprdr->map_size; i++) SetClipboardData(cliprdr->format_mappings[i].local_format_id, NULL); CloseClipboard(); } } static void wf_cliprdr_process_cb_data_response_event(wfContext *wfc, RDP_CB_DATA_RESPONSE_EVENT *event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; HANDLE hMem; char *buff; hMem = GlobalAlloc(GMEM_FIXED, event->size); buff = (char *) GlobalLock(hMem); memcpy(buff, event->data, event->size); GlobalUnlock(hMem); cliprdr->hmem = hMem; SetEvent(cliprdr->response_data_event); } static void wf_cliprdr_process_cb_filecontents_request_event(wfContext *wfc, RDP_CB_FILECONTENTS_REQUEST_EVENT *event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; UINT32 uSize = 0; BYTE *pData = NULL; HRESULT hRet = S_OK; FORMATETC vFormatEtc; LPDATAOBJECT pDataObj = NULL; STGMEDIUM vStgMedium; LPSTREAM pStream = NULL; BOOL bIsStreamFile = TRUE; static LPSTREAM pStreamStc = NULL; static UINT32 uStreamIdStc = 0; pData = (BYTE *)calloc(1, event->cbRequested); if (!pData) goto error; hRet = OleGetClipboard(&pDataObj); if (!SUCCEEDED(hRet)) { DEBUG_WARN( "filecontents: get ole clipboard failed.\n"); goto error; } ZeroMemory(&vFormatEtc, sizeof(FORMATETC)); ZeroMemory(&vStgMedium, sizeof(STGMEDIUM)); vFormatEtc.cfFormat = cliprdr->ID_FILECONTENTS; vFormatEtc.tymed = TYMED_ISTREAM; vFormatEtc.dwAspect = 1; vFormatEtc.lindex = event->lindex; vFormatEtc.ptd = NULL; if (uStreamIdStc != event->streamId || pStreamStc == NULL) { LPENUMFORMATETC pEnumFormatEtc; ULONG CeltFetched; FORMATETC vFormatEtc2; if (pStreamStc != NULL) { IStream_Release(pStreamStc); pStreamStc = NULL; } bIsStreamFile = FALSE; hRet = IDataObject_EnumFormatEtc(pDataObj, DATADIR_GET, &pEnumFormatEtc); if (hRet == S_OK) { do { hRet = IEnumFORMATETC_Next(pEnumFormatEtc, 1, &vFormatEtc2, &CeltFetched); if (hRet == S_OK) { if (vFormatEtc2.cfFormat == cliprdr->ID_FILECONTENTS) { hRet = IDataObject_GetData(pDataObj, &vFormatEtc, &vStgMedium); if (hRet == S_OK) { pStreamStc = vStgMedium.pstm; uStreamIdStc = event->streamId; bIsStreamFile = TRUE; } break; } } } while (hRet == S_OK); } } if (bIsStreamFile == TRUE) { if (event->dwFlags == 0x00000001) /* FILECONTENTS_SIZE */ { STATSTG vStatStg; ZeroMemory(&vStatStg, sizeof(STATSTG)); hRet = IStream_Stat(pStreamStc, &vStatStg, STATFLAG_NONAME); if (hRet == S_OK) { Write_UINT32(pData, vStatStg.cbSize.LowPart); Write_UINT32(pData + 4, vStatStg.cbSize.HighPart); uSize = event->cbRequested; } } else if (event->dwFlags == 0x00000002) /* FILECONTENTS_RANGE */ { LARGE_INTEGER dlibMove; ULARGE_INTEGER dlibNewPosition; dlibMove.HighPart = event->nPositionHigh; dlibMove.LowPart = event->nPositionLow; hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); if (SUCCEEDED(hRet)) { hRet = IStream_Read(pStreamStc, pData, event->cbRequested, (PULONG)&uSize); } } } else // is local file { if (event->dwFlags == 0x00000001) /* FILECONTENTS_SIZE */ { Write_UINT32(pData, cliprdr->fileDescriptor[event->lindex]->nFileSizeLow); Write_UINT32(pData + 4, cliprdr->fileDescriptor[event->lindex]->nFileSizeHigh); uSize = event->cbRequested; } else if (event->dwFlags == 0x00000002) /* FILECONTENTS_RANGE */ { BOOL bRet; bRet = wf_cliprdr_get_file_contents(cliprdr->file_names[event->lindex], pData, event->nPositionLow, event->nPositionHigh, event->cbRequested, &uSize); if (bRet == FALSE) { DEBUG_WARN( "get file contents failed.\n"); uSize = 0; goto error; } } } IDataObject_Release(pDataObj); if (uSize == 0) { free(pData); pData = NULL; } cliprdr_send_response_filecontents(cliprdr, event->streamId, uSize, pData); return; error: if (pData) { free(pData); pData = NULL; } if (pDataObj) { IDataObject_Release(pDataObj); pDataObj = NULL; } DEBUG_WARN( "filecontents: send failed response.\n"); cliprdr_send_response_filecontents(cliprdr, event->streamId, 0, NULL); return; } static void wf_cliprdr_process_cb_filecontents_response_event(wfContext *wfc, RDP_CB_FILECONTENTS_RESPONSE_EVENT *event) { cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context; cliprdr->req_fsize = event->size; cliprdr->req_fdata = (char *)malloc(event->size); memcpy(cliprdr->req_fdata, event->data, event->size); SetEvent(cliprdr->req_fevent); } static void wf_cliprdr_process_cb_lock_clipdata_event(wfContext *wfc, RDP_CB_LOCK_CLIPDATA_EVENT *event) { } static void wf_cliprdr_process_cb_unlock_clipdata_event(wfContext *wfc, RDP_CB_UNLOCK_CLIPDATA_EVENT *event) { } void wf_process_cliprdr_event(wfContext *wfc, wMessage *event) { switch (GetMessageType(event->id)) { case CliprdrChannel_ClipCaps: wf_cliprdr_process_cb_clip_caps_event(wfc, (RDP_CB_CLIP_CAPS *)event); break; case CliprdrChannel_MonitorReady: wf_cliprdr_process_cb_monitor_ready_event(wfc, (RDP_CB_MONITOR_READY_EVENT *)event); break; case CliprdrChannel_FormatList: wf_cliprdr_process_cb_format_list_event(wfc, (RDP_CB_FORMAT_LIST_EVENT *) event); break; case CliprdrChannel_DataRequest: wf_cliprdr_process_cb_data_request_event(wfc, (RDP_CB_DATA_REQUEST_EVENT *) event); break; case CliprdrChannel_DataResponse: wf_cliprdr_process_cb_data_response_event(wfc, (RDP_CB_DATA_RESPONSE_EVENT *) event); break; case CliprdrChannel_FilecontentsRequest: wf_cliprdr_process_cb_filecontents_request_event(wfc, (RDP_CB_FILECONTENTS_REQUEST_EVENT *) event); break; case CliprdrChannel_FilecontentsResponse: wf_cliprdr_process_cb_filecontents_response_event(wfc, (RDP_CB_FILECONTENTS_RESPONSE_EVENT *) event); break; case CliprdrChannel_LockClipdata: wf_cliprdr_process_cb_lock_clipdata_event(wfc, (RDP_CB_LOCK_CLIPDATA_EVENT *) event); break; case CliprdrChannel_UnLockClipdata: wf_cliprdr_process_cb_unlock_clipdata_event(wfc, (RDP_CB_UNLOCK_CLIPDATA_EVENT *) event); break; default: break; } } BOOL wf_cliprdr_process_selection_notify(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { return TRUE; } BOOL wf_cliprdr_process_selection_request(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { return TRUE; } BOOL wf_cliprdr_process_selection_clear(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { return TRUE; } BOOL wf_cliprdr_process_property_notify(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { return TRUE; } void wf_cliprdr_check_owner(wfContext* wfc) { }