Skip to content

Commit 6136882

Browse files
committed
Fix encoding and serialization related issues
1 parent 70bc44f commit 6136882

File tree

1 file changed

+153
-112
lines changed

1 file changed

+153
-112
lines changed

PasteIntoFile/ClipboardContents.cs

Lines changed: 153 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -301,59 +301,44 @@ public override void AddTo(IDataObject data) {
301301
}
302302

303303

304-
/// <summary>
305-
/// Class to hold SVG data
306-
/// </summary>
307-
public class SvgContent : TextLikeContent {
308-
public static readonly string[] EXTENSIONS = { "svg" };
309-
public SvgContent(string xml) : base(xml) { }
310304

311-
public string Xml {
312-
get {
313-
var xml = Text;
314-
if (!xml.StartsWith("<?xml"))
315-
xml = "<?xml version=\"1.0\" encoding=\"" + Encoding.BodyName + "\"?>\n" + xml;
316-
return xml;
317-
}
305+
public class TextLikeContent : BaseContent {
306+
private readonly string[] Formats;
307+
public override string[] Extensions { get; }
308+
309+
public TextLikeContent(string[] formats, string[] extensions, string text) {
310+
Formats = formats;
311+
Extensions = extensions;
312+
Data = text;
318313
}
314+
public string Text => Data as string;
319315

320-
public override string[] Extensions => EXTENSIONS;
321-
public override string Description => Resources.str_preview_svg;
316+
public static readonly Encoding DefaultEncoding = new UTF8Encoding(false); // omit unnecessary BOM bytes
322317

323-
public override void SaveAs(string path, string extension, bool append = false) {
324-
if (append)
325-
throw new AppendNotSupportedException();
326-
switch (NormalizeExtension(extension)) {
327-
case "svg":
328-
default:
329-
Save(path, Xml);
330-
break;
331-
}
332-
}
318+
public override string Description => Resources.str_preview;
333319

334320
public override void AddTo(IDataObject data) {
335-
data.SetData("image/svg+xml", Stream);
321+
AddTo(data, Text);
336322
}
337-
public override string TextPreview(string extension) {
338-
return Xml;
339-
}
340-
}
341-
342323

343-
344-
public abstract class TextLikeContent : BaseContent {
345-
public TextLikeContent(string text) {
346-
Data = text;
324+
protected void AddTo(IDataObject data, string text, Encoding encoding = null) {
325+
foreach (var f in Formats) {
326+
if (DataFormats.GetFormat(f).Id < 32) { // Native formats, handled well by default
327+
data.SetData(f, text);
328+
} else { // Non-native formats
329+
// Manually encode to avoid default object serialization header,
330+
// see https://devblogs.microsoft.com/oldnewthing/20181130-00/?p=100365
331+
data.SetData(f, new MemoryStream((encoding ?? DefaultEncoding).GetBytes(text)));
332+
}
333+
}
347334
}
348-
public string Text => Data as string;
349-
public Stream Stream => new MemoryStream(Encoding.GetBytes(Text));
350-
public static readonly Encoding Encoding = new UTF8Encoding(false); // omit unnecessary BOM bytes
335+
351336
public override void SaveAs(string path, string extension, bool append = false) {
352-
Save(path, Text, append);
337+
Save(path, TextPreview(extension), append);
353338
}
354339

355-
protected static void Save(string path, string text, bool append = false) {
356-
using (var streamWriter = new StreamWriter(path, append, Encoding))
340+
protected static void Save(string path, string text, bool append = false, Encoding encoding = null) {
341+
using (var streamWriter = new StreamWriter(path, append, (encoding ?? DefaultEncoding)))
357342
streamWriter.Write(EnsureNewline(text));
358343
}
359344

@@ -365,27 +350,88 @@ public static string EnsureNewline(string text) {
365350
/// Return a string used for preview
366351
/// </summary>
367352
/// <returns></returns>
368-
public abstract string TextPreview(string extension);
353+
public virtual string TextPreview(string extension) {
354+
return Text;
355+
}
369356
}
370357

371358

372359
public class TextContent : TextLikeContent {
373-
public TextContent(string text) : base(text) { }
374-
public override string[] Extensions => new[] { "txt", "md", "log", "bat", "ps1", "java", "js", "cpp", "cs", "py", "css", "html", "php", "json", "csv" };
360+
public static readonly string[] FORMATS = { DataFormats.Text, DataFormats.UnicodeText };
361+
public static readonly string[] EXTENSIONS = { "txt", "md", "log", "bat", "ps1", "java", "js", "cpp", "cs", "py", "css", "html", "php", "json", "csv" };
362+
363+
public TextContent(string text) : base(FORMATS, EXTENSIONS, text) { }
364+
375365
public override string Description => string.Format(Resources.str_preview_text, Text.Length, Text.Split('\n').Length);
376-
public override void AddTo(IDataObject data) {
377-
data.SetData(DataFormats.Text, Text);
378-
data.SetData(DataFormats.UnicodeText, Text);
366+
367+
}
368+
369+
public class RtfContent : TextLikeContent {
370+
public static readonly string[] FORMATS = { DataFormats.Rtf, "text/rtf" };
371+
public static readonly string[] EXTENSIONS = { "rtf" };
372+
373+
public RtfContent(string text) : base(FORMATS, EXTENSIONS, text) { }
374+
375+
public override string Description => Resources.str_preview_rtf;
376+
}
377+
378+
public class DifContent : TextLikeContent {
379+
public static readonly string[] FORMATS = { DataFormats.Dif };
380+
public static readonly string[] EXTENSIONS = { "dif" };
381+
382+
public DifContent(string text) : base(FORMATS, EXTENSIONS, text) { }
383+
384+
public override string Description => Resources.str_preview_dif;
385+
}
386+
387+
public class SlkContent : TextLikeContent {
388+
public static readonly string[] FORMATS = { DataFormats.SymbolicLink };
389+
public static readonly string[] EXTENSIONS = { "sylk" };
390+
391+
public SlkContent(string text) : base(FORMATS, EXTENSIONS, text) { }
392+
393+
public override string Description => Resources.str_preview_sylk;
394+
}
395+
396+
397+
398+
/// <summary>
399+
/// Class to hold SVG data
400+
/// </summary>
401+
public class SvgContent : TextLikeContent {
402+
public static readonly string[] FORMATS = { "image/svg+xml", "svg" };
403+
public static readonly string[] EXTENSIONS = { "svg" };
404+
405+
public SvgContent(string xml) : base(FORMATS, EXTENSIONS, xml) { }
406+
407+
public string Xml {
408+
get {
409+
var xml = Text;
410+
if (!xml.StartsWith("<?xml"))
411+
xml = "<?xml version=\"1.0\" encoding=\"" + DefaultEncoding.BodyName + "\"?>\n" + xml;
412+
return xml;
413+
}
414+
}
415+
416+
public override string Description => Resources.str_preview_svg;
417+
418+
public override void SaveAs(string path, string extension, bool append = false) {
419+
if (append) throw new AppendNotSupportedException();
420+
base.SaveAs(path, extension, append);
379421
}
422+
380423
public override string TextPreview(string extension) {
381-
return Text;
424+
return Xml;
382425
}
383426
}
384427

385428

386429
public class HtmlContent : TextLikeContent {
387-
public HtmlContent(string text) : base(text) { }
388-
public override string[] Extensions => new[] { "html", "htm", "xhtml" };
430+
public static readonly string[] FORMATS = { DataFormats.Html };
431+
public static readonly string[] EXTENSIONS = { "html", "htm", "xhtml" };
432+
433+
public HtmlContent(string text) : base(FORMATS, EXTENSIONS, text) { }
434+
389435
public override string Description => Resources.str_preview_html;
390436
public override void SaveAs(string path, string extension, bool append = false) {
391437
var html = Text;
@@ -403,25 +449,22 @@ public override void AddTo(IDataObject data) {
403449
"EndHTML:" + STOP + "\r\n" +
404450
"StartFragment:" + START + "\r\n" +
405451
"EndFragment:" + STOP + "\r\n";
406-
var bytecount = Encoding.UTF8.GetByteCount(Text);
452+
var bytecount = DefaultEncoding.GetByteCount(Text);
407453
header = header.Replace(START, (header.Length).ToString().PadLeft(START.Length, '0'));
408454
header = header.Replace(STOP, (header.Length + bytecount).ToString().PadLeft(STOP.Length, '0'));
409455

410-
data.SetData(DataFormats.Html, header + Text);
411-
}
412-
public override string TextPreview(string extension) {
413-
return Text;
456+
AddTo(data, header + Text);
414457
}
458+
415459
}
416460

417461

418462
public class CsvContent : TextLikeContent {
419-
public CsvContent(string text) : base(text) { }
420-
public override string[] Extensions => new[] { "csv", "tsv", "tab", "md" };
463+
public static readonly string[] FORMATS = { DataFormats.CommaSeparatedValue, "text/csv", "text/tab-separated-values" };
464+
public static readonly string[] EXTENSIONS = { "csv", "tsv", "tab", "md" };
465+
466+
public CsvContent(string text) : base(FORMATS, EXTENSIONS, text) { }
421467
public override string Description => Resources.str_preview_csv;
422-
public override void AddTo(IDataObject data) {
423-
data.SetData(DataFormats.CommaSeparatedValue, Text);
424-
}
425468

426469
/// <summary>
427470
/// Heuristically determine the (most likely) delimiter
@@ -477,49 +520,26 @@ public override string TextPreview(string extension) {
477520
case "md":
478521
return AsMarkdown();
479522
default:
480-
return Text;
523+
return base.TextPreview(extension);
481524
}
482525
}
483526

484-
public override void SaveAs(string path, string extension, bool append = false) {
485-
Save(path, TextPreview(extension), append);
486-
}
487-
}
488-
489-
490-
public class GenericTextContent : TextLikeContent {
491-
private readonly string _format;
492-
public GenericTextContent(string format, string extension, string text) : base(text) {
493-
_format = format;
494-
Extensions = new[] { extension };
495-
}
496-
public override string[] Extensions { get; }
497-
public override string Description => Resources.str_preview;
498-
public override void AddTo(IDataObject data) {
499-
data.SetData(_format, Text);
500-
}
501-
public override string TextPreview(string extension) {
502-
return Text;
503-
}
504527
}
505528

506529

507530
public class UrlContent : TextLikeContent {
531+
public static readonly string[] FORMATS = { DataFormats.Text };
508532
public static readonly string[] EXTENSIONS = { "url" };
509-
public UrlContent(string text) : base(text) { }
510-
public override string[] Extensions => EXTENSIONS;
533+
534+
public UrlContent(string text) : base(FORMATS, EXTENSIONS, text) { }
535+
511536
public override string Description => Resources.str_preview_url;
537+
512538
public override void SaveAs(string path, string extension, bool append = false) {
513-
if (append)
514-
throw new AppendNotSupportedException();
539+
if (append) throw new AppendNotSupportedException();
515540
Save(path, "[InternetShortcut]\nURL=" + Text);
516541
}
517-
public override void AddTo(IDataObject data) {
518-
data.SetData(DataFormats.Text, Text);
519-
}
520-
public override string TextPreview(string extension) {
521-
return Text;
522-
}
542+
523543
}
524544

525545

@@ -767,20 +787,12 @@ public static ClipboardContents FromClipboard() {
767787
if (ReadClipboardHtml() is string html)
768788
container.Contents.Add(new HtmlContent(html));
769789

770-
if (ReadClipboardString(DataFormats.CommaSeparatedValue, "text/csv", "text/tab-separated-values") is string csv)
771-
container.Contents.Add(new CsvContent(csv));
772-
773-
if (ReadClipboardString(DataFormats.SymbolicLink) is string lnk)
774-
container.Contents.Add(new GenericTextContent(DataFormats.SymbolicLink, "slk", lnk));
790+
if (ReadClipboardString(CsvContent.FORMATS) is string csv) container.Contents.Add(new CsvContent(csv));
791+
if (ReadClipboardString(SlkContent.FORMATS) is string lnk) container.Contents.Add(new SlkContent(lnk));
792+
if (ReadClipboardString(RtfContent.FORMATS) is string rtf) container.Contents.Add(new RtfContent(rtf));
793+
if (ReadClipboardString(DifContent.FORMATS) is string dif) container.Contents.Add(new DifContent(dif));
794+
if (ReadClipboardString(SvgContent.FORMATS) is string svg) container.Contents.Add(new SvgContent(svg));
775795

776-
if (ReadClipboardString(DataFormats.Rtf, "text/rtf") is string rtf)
777-
container.Contents.Add(new GenericTextContent(DataFormats.Rtf, "rtf", rtf));
778-
779-
if (ReadClipboardString(DataFormats.Dif) is string dif)
780-
container.Contents.Add(new GenericTextContent(DataFormats.Dif, "dif", dif));
781-
782-
if (ReadClipboardString("image/svg+xml", "svg") is string svg)
783-
container.Contents.Add(new SvgContent(svg));
784796

785797
if (Clipboard.ContainsText() && Uri.IsWellFormedUriString(Clipboard.GetText().Trim(), UriKind.Absolute))
786798
container.Contents.Add(new UrlContent(Clipboard.GetText().Trim()));
@@ -824,14 +836,37 @@ private static string ReadClipboardHtml() {
824836

825837
private static string ReadClipboardString(params string[] formats) {
826838
foreach (var format in formats) {
827-
if (!Clipboard.ContainsData(format))
828-
continue;
829-
var data = Clipboard.GetData(format);
830-
switch (data) {
831-
case string str:
832-
return str;
833-
case MemoryStream stream:
834-
return new StreamReader(stream).ReadToEnd().TrimEnd('\0');
839+
// Standard formats with native support
840+
foreach (var simpleFormat in new Dict<string, TextDataFormat> {
841+
{DataFormats.Text, TextDataFormat.Text},
842+
{DataFormats.UnicodeText, TextDataFormat.UnicodeText},
843+
{DataFormats.Html, TextDataFormat.Html},
844+
{DataFormats.Rtf, TextDataFormat.Rtf},
845+
{DataFormats.CommaSeparatedValue, TextDataFormat.CommaSeparatedValue},
846+
}) {
847+
if (string.Equals(format, simpleFormat.Key) && Clipboard.ContainsText(simpleFormat.Value)) {
848+
return Clipboard.GetText(simpleFormat.Value);
849+
}
850+
}
851+
852+
// Other non-standard formats
853+
if (Clipboard.ContainsData(format)) {
854+
switch (Clipboard.GetData(format)) {
855+
// Serialized string
856+
case string str:
857+
return str;
858+
// Raw string
859+
case MemoryStream stream:
860+
var encoding = Encoding.UTF8;
861+
if (stream.Length > 2) {
862+
// Heuristic to tell UTF8 and UTF16 apart
863+
int b0 = stream.ReadByte(), b1 = stream.ReadByte();
864+
if (b0 == 0xFE && b1 == 0xFF) encoding = Encoding.BigEndianUnicode;
865+
if (b0 == 0xFF && b1 == 0xFE || b1 == 0x00) encoding = Encoding.Unicode;
866+
stream.Position = 0;
867+
}
868+
return new StreamReader(stream, encoding).ReadToEnd().TrimEnd('\0');
869+
}
835870
}
836871
}
837872
return null;
@@ -903,6 +938,12 @@ public static ClipboardContents FromFile(string path) {
903938
container.Contents.Add(new GenericTextContent(DataFormats.Rtf, ext, contents));
904939
if (ext == "syk")
905940
container.Contents.Add(new GenericTextContent(DataFormats.SymbolicLink, ext, contents));
941+
if (DifContent.EXTENSIONS.Contains(ext))
942+
container.Contents.Add(new DifContent(contents));
943+
if (RtfContent.EXTENSIONS.Contains(ext))
944+
container.Contents.Add(new RtfContent(contents));
945+
if (SlkContent.EXTENSIONS.Contains(ext))
946+
container.Contents.Add(new SlkContent(contents));
906947

907948
} else {
908949
container.Contents.Add(new TextContent(path));

0 commit comments

Comments
 (0)