FreeRDP
Loading...
Searching...
No Matches
clipboard.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/collections.h>
24#include <winpr/wlog.h>
25
26#include <winpr/clipboard.h>
27
28#include "clipboard.h"
29
30#include "synthetic_file.h"
31
32#include "../log.h"
33#define TAG WINPR_TAG("clipboard")
34
35const char* const mime_text_plain = "text/plain";
36const char* const mime_text_utf8 = "text/plain;charset=utf-8";
37const char* const mime_text_UTF8_STRING = "UTF8_STRING";
38
47static const char* CF_STANDARD_STRINGS[] = {
48 "CF_RAW", /* 0 */
49 "CF_TEXT", /* 1 */
50 "CF_BITMAP", /* 2 */
51 "CF_METAFILEPICT", /* 3 */
52 "CF_SYLK", /* 4 */
53 "CF_DIF", /* 5 */
54 "CF_TIFF", /* 6 */
55 "CF_OEMTEXT", /* 7 */
56 "CF_DIB", /* 8 */
57 "CF_PALETTE", /* 9 */
58 "CF_PENDATA", /* 10 */
59 "CF_RIFF", /* 11 */
60 "CF_WAVE", /* 12 */
61 "CF_UNICODETEXT", /* 13 */
62 "CF_ENHMETAFILE", /* 14 */
63 "CF_HDROP", /* 15 */
64 "CF_LOCALE", /* 16 */
65 "CF_DIBV5" /* 17 */
66};
67
68const char* ClipboardGetFormatIdString(UINT32 formatId)
69{
70 if (formatId < ARRAYSIZE(CF_STANDARD_STRINGS))
71 return CF_STANDARD_STRINGS[formatId];
72 return "CF_REGISTERED_FORMAT";
73}
74
75static wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId,
76 const char* name)
77{
78 wClipboardFormat* format = nullptr;
79
80 if (!clipboard)
81 return nullptr;
82
83 if (formatId)
84 {
85 for (UINT32 index = 0; index < clipboard->numFormats; index++)
86 {
87 wClipboardFormat* cformat = &clipboard->formats[index];
88 if (formatId == cformat->formatId)
89 {
90 format = cformat;
91 break;
92 }
93 }
94 }
95 else if (name)
96 {
97 for (UINT32 index = 0; index < clipboard->numFormats; index++)
98 {
99 wClipboardFormat* cformat = &clipboard->formats[index];
100 if (!cformat->formatName)
101 continue;
102
103 if (strcmp(name, cformat->formatName) == 0)
104 {
105 format = cformat;
106 break;
107 }
108 }
109 }
110 else
111 {
112 /* special "CF_RAW" case */
113 if (clipboard->numFormats > 0)
114 {
115 format = &clipboard->formats[0];
116
117 if (format->formatId)
118 return nullptr;
119
120 if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0))
121 return format;
122 }
123 }
124
125 return format;
126}
127
128static wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId)
129{
130 if (!format)
131 return nullptr;
132
133 for (UINT32 index = 0; index < format->numSynthesizers; index++)
134 {
135 wClipboardSynthesizer* synthesizer = &(format->synthesizers[index]);
136
137 if (formatId == synthesizer->syntheticId)
138 return synthesizer;
139 }
140
141 return nullptr;
142}
143
144void ClipboardLock(wClipboard* clipboard)
145{
146 if (!clipboard)
147 return;
148
149 EnterCriticalSection(&(clipboard->lock));
150}
151
152void ClipboardUnlock(wClipboard* clipboard)
153{
154 if (!clipboard)
155 return;
156
157 LeaveCriticalSection(&(clipboard->lock));
158}
159
160BOOL ClipboardEmpty(wClipboard* clipboard)
161{
162 if (!clipboard)
163 return FALSE;
164
165 if (clipboard->data)
166 {
167 free(clipboard->data);
168 clipboard->data = nullptr;
169 }
170
171 clipboard->size = 0;
172 clipboard->formatId = 0;
173 clipboard->sequenceNumber++;
174 return TRUE;
175}
176
177UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard)
178{
179 if (!clipboard)
180 return 0;
181
182 return clipboard->numFormats;
183}
184
185UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
186{
187 UINT32* pFormatIds = nullptr;
188 wClipboardFormat* format = nullptr;
189
190 if (!clipboard)
191 return 0;
192
193 if (!ppFormatIds)
194 return 0;
195
196 pFormatIds = *ppFormatIds;
197
198 if (!pFormatIds)
199 {
200 pFormatIds = calloc(clipboard->numFormats, sizeof(UINT32));
201
202 if (!pFormatIds)
203 return 0;
204
205 *ppFormatIds = pFormatIds;
206 }
207
208 for (UINT32 index = 0; index < clipboard->numFormats; index++)
209 {
210 format = &(clipboard->formats[index]);
211 pFormatIds[index] = format->formatId;
212 }
213
214 return clipboard->numFormats;
215}
216
217UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name)
218{
219 wClipboardFormat* format = nullptr;
220
221 if (!clipboard)
222 return 0;
223
224 format = ClipboardFindFormat(clipboard, 0, name);
225
226 if (format)
227 return format->formatId;
228
229 if ((clipboard->numFormats + 1) >= clipboard->maxFormats)
230 {
231 UINT32 numFormats = clipboard->maxFormats * 2;
232 wClipboardFormat* tmpFormat = nullptr;
233 tmpFormat =
234 (wClipboardFormat*)realloc(clipboard->formats, numFormats * sizeof(wClipboardFormat));
235
236 if (!tmpFormat)
237 return 0;
238
239 clipboard->formats = tmpFormat;
240 clipboard->maxFormats = numFormats;
241 }
242
243 format = &(clipboard->formats[clipboard->numFormats]);
244 ZeroMemory(format, sizeof(wClipboardFormat));
245
246 if (name)
247 {
248 format->formatName = _strdup(name);
249
250 if (!format->formatName)
251 return 0;
252 }
253
254 format->formatId = clipboard->nextFormatId++;
255 clipboard->numFormats++;
256 return format->formatId;
257}
258
259WINPR_ATTR_NODISCARD
260static wClipboardSynthesizer* registerSynthesizer(wClipboard* clipboard, UINT32 formatId,
261 UINT32 syntheticId)
262{
263 if (!clipboard)
264 {
265 WLog_WARN(TAG,
266 "Failed to register synthesizer for %s[0x%04" PRIx32 "] to %s [0x%04" PRIx32
267 "]: clipboard=NULL",
268 ClipboardGetFormatName(clipboard, formatId), formatId,
269 ClipboardGetFormatName(clipboard, syntheticId), syntheticId);
270 return nullptr;
271 }
272
273 WLog_DBG(TAG, "Registering synthesizer for %s[0x%04" PRIx32 "] to %s [0x%04" PRIx32 "]",
274 ClipboardGetFormatName(clipboard, formatId), formatId,
275 ClipboardGetFormatName(clipboard, syntheticId), syntheticId);
276
277 wClipboardFormat* format = ClipboardFindFormat(clipboard, formatId, nullptr);
278
279 if (!format)
280 {
281 WLog_WARN(TAG,
282 "Failed to register synthesizer for %s[0x%04" PRIx32 "] to %s [0x%04" PRIx32
283 "]: format=NULL",
284 ClipboardGetFormatName(clipboard, formatId), formatId,
285 ClipboardGetFormatName(clipboard, syntheticId), syntheticId);
286 return nullptr;
287 }
288
289 if (format->formatId == syntheticId)
290 {
291 WLog_WARN(TAG,
292 "Failed to register synthesizer for %s[0x%04" PRIx32 "] to %s [0x%04" PRIx32
293 "]: formats equal",
294 ClipboardGetFormatName(clipboard, formatId), formatId,
295 ClipboardGetFormatName(clipboard, syntheticId), syntheticId);
296 return nullptr;
297 }
298
299 wClipboardSynthesizer* synthesizer = ClipboardFindSynthesizer(format, formatId);
300
301 if (!synthesizer)
302 {
303 UINT32 numSynthesizers = format->numSynthesizers + 1;
304 wClipboardSynthesizer* tmpSynthesizer = (wClipboardSynthesizer*)realloc(
305 format->synthesizers, numSynthesizers * sizeof(wClipboardSynthesizer));
306
307 if (!tmpSynthesizer)
308 {
309 WLog_WARN(TAG,
310 "Failed to register synthesizer for %s[0x%04" PRIx32 "] to %s [0x%04" PRIx32
311 "]: failed to allocate new synthesizer",
312 ClipboardGetFormatName(clipboard, formatId), formatId,
313 ClipboardGetFormatName(clipboard, syntheticId), syntheticId);
314 return nullptr;
315 }
316
317 format->synthesizers = tmpSynthesizer;
318 format->numSynthesizers = numSynthesizers;
319 const UINT32 index = numSynthesizers - 1;
320 synthesizer = &(format->synthesizers[index]);
321 }
322 return synthesizer;
323}
324
325BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId,
326 CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
327{
328 wClipboardSynthesizer* synthesizer = registerSynthesizer(clipboard, formatId, syntheticId);
329 if (!synthesizer)
330 return FALSE;
331
332 synthesizer->syntheticId = syntheticId;
333 synthesizer->pfnSynthesize = pfnSynthesize;
334 return TRUE;
335}
336
337BOOL ClipboardRegisterSynthesizerEx(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId,
338 CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
339{
340 wClipboardSynthesizer* synthesizer = registerSynthesizer(clipboard, formatId, syntheticId);
341 if (!synthesizer)
342 return FALSE;
343
344 synthesizer->syntheticId = syntheticId;
345 synthesizer->pfnSynthesizeEx = pfnSynthesize;
346 return TRUE;
347}
348
349UINT32 ClipboardCountFormats(wClipboard* clipboard)
350{
351 UINT32 count = 0;
352 wClipboardFormat* format = nullptr;
353
354 if (!clipboard)
355 return 0;
356
357 format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
358
359 if (!format)
360 return 0;
361
362 count = 1 + format->numSynthesizers;
363 return count;
364}
365
366UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
367{
368 UINT32 count = 0;
369 UINT32* pFormatIds = nullptr;
370 wClipboardFormat* format = nullptr;
371 wClipboardSynthesizer* synthesizer = nullptr;
372
373 if (!clipboard)
374 return 0;
375
376 format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
377
378 if (!format)
379 return 0;
380
381 count = 1 + format->numSynthesizers;
382
383 if (!ppFormatIds)
384 return 0;
385
386 pFormatIds = *ppFormatIds;
387
388 if (!pFormatIds)
389 {
390 pFormatIds = calloc(count, sizeof(UINT32));
391
392 if (!pFormatIds)
393 return 0;
394
395 *ppFormatIds = pFormatIds;
396 }
397
398 pFormatIds[0] = format->formatId;
399
400 for (UINT32 index = 1; index < count; index++)
401 {
402 synthesizer = &(format->synthesizers[index - 1]);
403 pFormatIds[index] = synthesizer->syntheticId;
404 }
405
406 return count;
407}
408
409static void ClipboardUninitFormats(wClipboard* clipboard)
410{
411 WINPR_ASSERT(clipboard);
412 for (UINT32 formatId = 0; formatId < clipboard->numFormats; formatId++)
413 {
414 wClipboardFormat* format = &clipboard->formats[formatId];
415 free(format->formatName);
416 free(format->synthesizers);
417 format->formatName = nullptr;
418 format->synthesizers = nullptr;
419 }
420}
421
422static BOOL ClipboardInitFormats(wClipboard* clipboard)
423{
424 UINT32 formatId = 0;
425 wClipboardFormat* format = nullptr;
426
427 if (!clipboard)
428 return FALSE;
429
430 for (formatId = 0; formatId < CF_MAX; formatId++, clipboard->numFormats++)
431 {
432 format = &(clipboard->formats[clipboard->numFormats]);
433 ZeroMemory(format, sizeof(wClipboardFormat));
434 format->formatId = formatId;
435 format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]);
436
437 if (!format->formatName)
438 goto error;
439 }
440
441 if (!ClipboardInitSynthesizers(clipboard))
442 goto error;
443
444 return TRUE;
445error:
446
447 ClipboardUninitFormats(clipboard);
448 return FALSE;
449}
450
451UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
452{
453 wClipboardFormat* format = nullptr;
454
455 if (!clipboard)
456 return 0;
457
458 format = ClipboardFindFormat(clipboard, 0, name);
459
460 if (!format)
461 return 0;
462
463 return format->formatId;
464}
465
466const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId)
467{
468 wClipboardFormat* format = nullptr;
469
470 if (!clipboard)
471 return nullptr;
472
473 format = ClipboardFindFormat(clipboard, formatId, nullptr);
474
475 if (!format)
476 return nullptr;
477
478 return format->formatName;
479}
480
481void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize)
482{
483 void* pDstData = nullptr;
484
485 if (!clipboard || !pSize)
486 {
487 WLog_ERR(TAG, "Invalid parameters clipboard=%p, pSize=%p",
488 WINPR_CXX_COMPAT_CAST(const void*, clipboard),
489 WINPR_CXX_COMPAT_CAST(const void*, pSize));
490 return nullptr;
491 }
492
493 *pSize = 0;
494 wClipboardFormat* format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
495
496 if (!format)
497 {
498 WLog_ERR(TAG, "Format [0x%08" PRIx32 "] not found", clipboard->formatId);
499 return nullptr;
500 }
501
502 const UINT32 SrcSize = clipboard->size;
503 void* pSrcData = clipboard->data;
504
505 if (formatId == format->formatId)
506 {
507 UINT32 DstSize = SrcSize;
508 pDstData = malloc(DstSize);
509
510 if (!pDstData)
511 return nullptr;
512
513 CopyMemory(pDstData, pSrcData, SrcSize);
514 *pSize = DstSize;
515 }
516 else
517 {
518 wClipboardSynthesizer* synthesizer = ClipboardFindSynthesizer(format, formatId);
519
520 if (!synthesizer || !(synthesizer->pfnSynthesize || synthesizer->pfnSynthesizeEx))
521 {
522 WLog_ERR(TAG, "No synthesizer for format %s [0x%08" PRIx32 "] --> %s [0x%08" PRIx32 "]",
523 ClipboardGetFormatName(clipboard, clipboard->formatId), clipboard->formatId,
524 ClipboardGetFormatName(clipboard, formatId), formatId);
525 return nullptr;
526 }
527
528 UINT32 DstSize = SrcSize;
529 if (synthesizer->pfnSynthesizeEx)
530 pDstData = synthesizer->pfnSynthesizeEx(clipboard, synthesizer->syntheticId, pSrcData,
531 &DstSize);
532 else
533 pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize);
534 if (pDstData)
535 *pSize = DstSize;
536 }
537
538 WLog_DBG(TAG, "getting formatId=%s [0x%08" PRIx32 "] data=%p, size=%" PRIu32,
539 ClipboardGetFormatName(clipboard, formatId), formatId, pDstData, *pSize);
540 return pDstData;
541}
542
543BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size)
544{
545 wClipboardFormat* format = nullptr;
546
547 WLog_DBG(TAG, "setting formatId=%s [0x%08" PRIx32 "], size=%" PRIu32,
548 ClipboardGetFormatName(clipboard, formatId), formatId, size);
549 if (!clipboard)
550 return FALSE;
551
552 format = ClipboardFindFormat(clipboard, formatId, nullptr);
553
554 if (!format)
555 return FALSE;
556
557 free(clipboard->data);
558
559 /* append 2 WCHAR zero bytes. Prevent problems with odd input data sizes. */
560 clipboard->data = calloc(size + 2ull * sizeof(WCHAR), sizeof(char));
561
562 if (!clipboard->data)
563 return FALSE;
564
565 memcpy(clipboard->data, data, size);
566
567 /* For string values we don“t know if they are '\0' terminated.
568 * so set the size to the full length in bytes (e.g. string length + 1)
569 */
570 switch (formatId)
571 {
572 case CF_TEXT:
573 case CF_OEMTEXT:
574 clipboard->size = (UINT32)(strnlen(clipboard->data, size) + 1UL);
575 break;
576 case CF_UNICODETEXT:
577 clipboard->size =
578 (UINT32)((_wcsnlen(clipboard->data, size / sizeof(WCHAR)) + 1UL) * sizeof(WCHAR));
579 break;
580 default:
581 clipboard->size = size;
582 break;
583 }
584
585 clipboard->formatId = formatId;
586 clipboard->sequenceNumber++;
587 return TRUE;
588}
589
590UINT64 ClipboardGetOwner(wClipboard* clipboard)
591{
592 if (!clipboard)
593 return 0;
594
595 return clipboard->ownerId;
596}
597
598void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
599{
600 if (!clipboard)
601 return;
602
603 clipboard->ownerId = ownerId;
604}
605
606wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
607{
608 if (!clipboard)
609 return nullptr;
610
611 return &clipboard->delegate;
612}
613
614static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
615{
616 /*
617 * There can be only one local file subsystem active.
618 * Return as soon as initialization succeeds.
619 */
620 if (ClipboardInitSyntheticFileSubsystem(clipboard))
621 {
622 WLog_DBG(TAG, "initialized synthetic local file subsystem");
623 return;
624 }
625 else
626 {
627 WLog_WARN(TAG, "failed to initialize synthetic local file subsystem");
628 }
629
630 WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
631}
632
633wClipboard* ClipboardCreate(void)
634{
635 wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard));
636
637 if (!clipboard)
638 return nullptr;
639
640 clipboard->nextFormatId = 0xC000;
641 clipboard->sequenceNumber = 0;
642
643 if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
644 goto fail;
645
646 clipboard->numFormats = 0;
647 clipboard->maxFormats = 64;
648 clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
649
650 if (!clipboard->formats)
651 goto fail;
652
653 if (!ClipboardInitFormats(clipboard))
654 goto fail;
655
656 clipboard->delegate.clipboard = clipboard;
657 ClipboardInitLocalFileSubsystem(clipboard);
658 return clipboard;
659fail:
660 ClipboardDestroy(clipboard);
661 return nullptr;
662}
663
664void ClipboardDestroy(wClipboard* clipboard)
665{
666 if (!clipboard)
667 return;
668
669 ArrayList_Free(clipboard->localFiles);
670 clipboard->localFiles = nullptr;
671
672 ClipboardUninitFormats(clipboard);
673
674 free(clipboard->data);
675 clipboard->data = nullptr;
676 clipboard->size = 0;
677 clipboard->numFormats = 0;
678 free(clipboard->formats);
679 DeleteCriticalSection(&(clipboard->lock));
680 free(clipboard);
681}
682
683static BOOL is_dos_drive(const char* path, size_t len)
684{
685 if (len < 2)
686 return FALSE;
687
688 WINPR_ASSERT(path);
689 if (path[1] == ':' || path[1] == '|')
690 {
691 if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z')))
692 return TRUE;
693 }
694 return FALSE;
695}
696
697char* parse_uri_to_local_file(const char* uri, size_t uri_len)
698{
699 // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
700 const char prefix[] = "file:";
701 const char prefixTraditional[] = "file://";
702 const char* localName = nullptr;
703 size_t localLen = 0;
704 char* buffer = nullptr;
705 const size_t prefixLen = strnlen(prefix, sizeof(prefix));
706 const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional));
707
708 WINPR_ASSERT(uri || (uri_len == 0));
709
710 WLog_VRB(TAG, "processing URI: %.*s", WINPR_ASSERTING_INT_CAST(int, uri_len), uri);
711
712 if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen) != 0)
713 {
714 WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
715 return nullptr;
716 }
717
718 do
719 {
720 /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F
721 * - The minimal representation of a local file in a DOS- or Windows-
722 * based environment with no authority field and an absolute path
723 * that begins with a drive letter.
724 *
725 * "file:c:/path/to/file"
726 *
727 * - Regular DOS or Windows file URIs with vertical line characters in
728 * the drive letter construct.
729 *
730 * "file:c|/path/to/file"
731 *
732 */
733 if (uri[prefixLen] != '/')
734 {
735
736 if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen))
737 {
738 // Dos and Windows file URI
739 localName = &uri[prefixLen];
740 localLen = uri_len - prefixLen;
741 break;
742 }
743 else
744 {
745 WLog_ERR(TAG, "URI format are not supported: %s", uri);
746 return nullptr;
747 }
748 }
749
750 /*
751 * - The minimal representation of a local file with no authority field
752 * and an absolute path that begins with a slash "/". For example:
753 *
754 * "file:/path/to/file"
755 *
756 */
757 else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/'))
758 {
759 if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1))
760 {
761 // Dos and Windows file URI
762 localName = (uri + prefixLen + 1);
763 localLen = uri_len - prefixLen - 1;
764 }
765 else
766 {
767 localName = &uri[prefixLen];
768 localLen = uri_len - prefixLen;
769 }
770 break;
771 }
772
773 /*
774 * - A traditional file URI for a local file with an empty authority.
775 *
776 * "file:///path/to/file"
777 */
778 if ((uri_len < prefixTraditionalLen) ||
779 strncmp(uri, prefixTraditional, prefixTraditionalLen) != 0)
780 {
781 WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
782 return nullptr;
783 }
784
785 localName = &uri[prefixTraditionalLen];
786 localLen = uri_len - prefixTraditionalLen;
787
788 if (localLen < 1)
789 {
790 WLog_ERR(TAG, "empty 'file:' URI schemes are not supported");
791 return nullptr;
792 }
793
794 /*
795 * "file:///c:/path/to/file"
796 * "file:///c|/path/to/file"
797 */
798 if (localName[0] != '/')
799 {
800 WLog_ERR(TAG, "URI format are not supported: %s", uri);
801 return nullptr;
802 }
803
804 if (is_dos_drive(&localName[1], localLen - 1))
805 {
806 localName++;
807 localLen--;
808 }
809
810 } while (0);
811
812 buffer = winpr_str_url_decode(localName, localLen);
813 if (buffer)
814 {
815 if (buffer[1] == '|' &&
816 ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z')))
817 buffer[1] = ':';
818 return buffer;
819 }
820
821 return nullptr;
822}