FreeRDP
Loading...
Searching...
No Matches
cliprdr_utils.c
1
22#include <winpr/stream.h>
23#include <freerdp/utils/cliprdr_utils.h>
24#include <freerdp/channels/cliprdr.h>
25
26#include <freerdp/log.h>
27#define TAG FREERDP_TAG("utils." CLIPRDR_SVC_CHANNEL_NAME)
28
29#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 8 + 520)
30#define CLIPRDR_MAX_FILE_SIZE (2U * 1024 * 1024 * 1024)
31
32static UINT64 filetime_to_uint64(FILETIME value)
33{
34 UINT64 converted = 0;
35 converted |= (UINT32)value.dwHighDateTime;
36 converted <<= 32;
37 converted |= (UINT32)value.dwLowDateTime;
38 return converted;
39}
40
41static FILETIME uint64_to_filetime(UINT64 value)
42{
43 FILETIME converted;
44 converted.dwLowDateTime = (UINT32)(value >> 0);
45 converted.dwHighDateTime = (UINT32)(value >> 32);
46 return converted;
47}
48
61UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length,
62 FILEDESCRIPTORW** file_descriptor_array, UINT32* file_descriptor_count)
63{
64 UINT result = NO_ERROR;
65 UINT32 count = 0;
66 wStream sbuffer = WINPR_C_ARRAY_INIT;
67
68 if (!format_data || !file_descriptor_array || !file_descriptor_count)
69 return ERROR_BAD_ARGUMENTS;
70
71 wStream* s = Stream_StaticConstInit(&sbuffer, format_data, format_data_length);
72 if (!s)
73 return ERROR_NOT_ENOUGH_MEMORY;
74
75 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
76 {
77 result = ERROR_INCORRECT_SIZE;
78 goto out;
79 }
80
81 Stream_Read_UINT32(s, count); /* cItems (4 bytes) */
82
83 if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, count, CLIPRDR_FILEDESCRIPTOR_SIZE))
84 {
85 result = ERROR_INCORRECT_SIZE;
86 goto out;
87 }
88
89 *file_descriptor_count = count;
90 *file_descriptor_array = calloc(count, sizeof(FILEDESCRIPTORW));
91 if (!*file_descriptor_array)
92 {
93 result = ERROR_NOT_ENOUGH_MEMORY;
94 goto out;
95 }
96
97 for (UINT32 i = 0; i < count; i++)
98 {
99 FILEDESCRIPTORW* file = &((*file_descriptor_array)[i]);
100
101 if (!cliprdr_read_filedescriptor(s, file))
102 goto out;
103 }
104
105 if (Stream_GetRemainingLength(s) > 0)
106 WLog_WARN(TAG, "packed file list has %" PRIuz " excess bytes",
107 Stream_GetRemainingLength(s));
108out:
109
110 return result;
111}
112
113WINPR_ATTR_NODISCARD
114static BOOL contains_dotdot(const WCHAR* path, size_t path_length)
115{
116 if (path_length < 2)
117 return FALSE;
118
119 size_t tstlen = path_length;
120 const WCHAR* tst = path;
121 do
122 {
123 tst = winpr_wcsnchr(tst, tstlen, '.');
124 if (!tst)
125 return FALSE;
126 const size_t diff = WINPR_ASSERTING_INT_CAST(size_t, tst - path);
127 tstlen = _wcsnlen(tst, path_length - diff);
128 if (tstlen < 2)
129 return FALSE;
130 if (tst[1] != '.')
131 {
132 tst++;
133 tstlen--;
134 continue;
135 }
136
137 /* Filter .. sequences in file or directory names */
138 if ((tst == path) || (*(tst - 1) == L'/') || (*(tst - 1) == L'\\'))
139 {
140 if (tst + 2 < path + path_length)
141 {
142 if ((tst[2] == '/') || (tst[2] == '\\') || (tst[2] == '\0'))
143 return TRUE;
144 }
145 else
146 return TRUE;
147 }
148 tst += 2;
149 } while (TRUE);
150
151 return FALSE;
152}
153
154BOOL cliprdr_read_filedescriptor(wStream* s, FILEDESCRIPTORW* descriptor)
155{
156 UINT64 tmp = 0;
157 WINPR_ASSERT(descriptor);
158
159 if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(FILEDESCRIPTORW)))
160 return FALSE;
161
162 Stream_Read_UINT32(s, descriptor->dwFlags); /* flags (4 bytes) */
163 Stream_Read_UINT32(s, descriptor->clsid.Data1);
164 Stream_Read_UINT16(s, descriptor->clsid.Data2);
165 Stream_Read_UINT16(s, descriptor->clsid.Data3);
166 Stream_Read(s, &descriptor->clsid.Data4, sizeof(descriptor->clsid.Data4));
167 Stream_Read_INT32(s, descriptor->sizel.cx);
168 Stream_Read_INT32(s, descriptor->sizel.cy);
169 Stream_Read_INT32(s, descriptor->pointl.x);
170 Stream_Read_INT32(s, descriptor->pointl.y);
171 Stream_Read_UINT32(s, descriptor->dwFileAttributes); /* fileAttributes (4 bytes) */
172 Stream_Read_UINT64(s, tmp); /* ftCreationTime (8 bytes) */
173 descriptor->ftCreationTime = uint64_to_filetime(tmp);
174 Stream_Read_UINT64(s, tmp); /* ftLastAccessTime (8 bytes) */
175 descriptor->ftLastAccessTime = uint64_to_filetime(tmp);
176 Stream_Read_UINT64(s, tmp); /* lastWriteTime (8 bytes) */
177 descriptor->ftLastWriteTime = uint64_to_filetime(tmp);
178 Stream_Read_UINT32(s, descriptor->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
179 Stream_Read_UINT32(s, descriptor->nFileSizeLow); /* fileSizeLow (4 bytes) */
180 if (!Stream_Read_UTF16_String(s, descriptor->cFileName,
181 ARRAYSIZE(descriptor->cFileName))) /* cFileName (520 bytes) */
182 return FALSE;
183 if (contains_dotdot(descriptor->cFileName, ARRAYSIZE(descriptor->cFileName)))
184 return FALSE;
185 return TRUE;
186}
187
188BOOL cliprdr_write_filedescriptor(wStream* s, const FILEDESCRIPTORW* descriptor)
189{
190 WINPR_ASSERT(descriptor);
191
192 if (!Stream_EnsureRemainingCapacity(s, sizeof(FILEDESCRIPTORW)))
193 return FALSE;
194
195 Stream_Write_UINT32(s, descriptor->dwFlags); /* flags (4 bytes) */
196
197 Stream_Write_UINT32(s, descriptor->clsid.Data1);
198 Stream_Write_UINT16(s, descriptor->clsid.Data2);
199 Stream_Write_UINT16(s, descriptor->clsid.Data3);
200 Stream_Write(s, &descriptor->clsid.Data4, sizeof(descriptor->clsid.Data4));
201 Stream_Write_INT32(s, descriptor->sizel.cx);
202 Stream_Write_INT32(s, descriptor->sizel.cy);
203 Stream_Write_INT32(s, descriptor->pointl.x);
204 Stream_Write_INT32(s, descriptor->pointl.y);
205 Stream_Write_UINT32(s, descriptor->dwFileAttributes); /* fileAttributes (4 bytes) */
206 Stream_Write_UINT64(s, filetime_to_uint64(descriptor->ftCreationTime));
207 Stream_Write_UINT64(s, filetime_to_uint64(descriptor->ftLastAccessTime));
208 Stream_Write_UINT64(
209 s, filetime_to_uint64(descriptor->ftLastWriteTime)); /* lastWriteTime (8 bytes) */
210 Stream_Write_UINT32(s, descriptor->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
211 Stream_Write_UINT32(s, descriptor->nFileSizeLow); /* fileSizeLow (4 bytes) */
212 return Stream_Write_UTF16_String(s, descriptor->cFileName,
213 ARRAYSIZE(descriptor->cFileName)); /* cFileName (520 bytes) */
214}
215
228UINT cliprdr_serialize_file_list(const FILEDESCRIPTORW* file_descriptor_array,
229 UINT32 file_descriptor_count, BYTE** format_data,
230 UINT32* format_data_length)
231{
232 return cliprdr_serialize_file_list_ex(CB_STREAM_FILECLIP_ENABLED, file_descriptor_array,
233 file_descriptor_count, format_data, format_data_length);
234}
235
236UINT cliprdr_serialize_file_list_ex(UINT32 flags, const FILEDESCRIPTORW* file_descriptor_array,
237 UINT32 file_descriptor_count, BYTE** format_data,
238 UINT32* format_data_length)
239{
240 UINT result = NO_ERROR;
241 size_t len = 0;
242 wStream* s = nullptr;
243
244 if (!file_descriptor_array || !format_data || !format_data_length)
245 return ERROR_BAD_ARGUMENTS;
246
247 if ((flags & CB_STREAM_FILECLIP_ENABLED) == 0)
248 {
249 WLog_WARN(TAG, "No file clipboard support annouonced!");
250 return ERROR_BAD_ARGUMENTS;
251 }
252
253 s = Stream_New(nullptr, 4 + file_descriptor_count * CLIPRDR_FILEDESCRIPTOR_SIZE);
254 if (!s)
255 return ERROR_NOT_ENOUGH_MEMORY;
256
257 Stream_Write_UINT32(s, file_descriptor_count); /* cItems (4 bytes) */
258
259 for (UINT32 i = 0; i < file_descriptor_count; i++)
260 {
261 const FILEDESCRIPTORW* file = &file_descriptor_array[i];
262
263 /*
264 * There is a known issue with Windows server getting stuck in
265 * an infinite loop when downloading files that are larger than
266 * 2 gigabytes. Do not allow clients to send such file lists.
267 *
268 * https://support.microsoft.com/en-us/help/2258090
269 */
270 if ((flags & CB_HUGE_FILE_SUPPORT_ENABLED) == 0)
271 {
272 if ((file->nFileSizeHigh > 0) || (file->nFileSizeLow >= CLIPRDR_MAX_FILE_SIZE))
273 {
274 WLog_ERR(TAG, "cliprdr does not support files over 2 GB");
275 result = ERROR_FILE_TOO_LARGE;
276 goto error;
277 }
278 }
279
280 if (!cliprdr_write_filedescriptor(s, file))
281 goto error;
282 }
283
284 Stream_SealLength(s);
285
286 *format_data = Stream_Buffer(s);
287 Stream_GetLength(s, len);
288 if (len > UINT32_MAX)
289 goto error;
290
291 *format_data_length = (UINT32)len;
292
293 Stream_Free(s, FALSE);
294
295 return result;
296
297error:
298 Stream_Free(s, TRUE);
299
300 return result;
301}