From 27774deaf5a86cae063b14cba849c0ac83b68dac Mon Sep 17 00:00:00 2001 From: James Menzies <32961246+JamesMenzies@users.noreply.github.com> Date: Thu, 10 Jun 2021 13:07:59 +0100 Subject: [PATCH 1/2] [test] adding unit tests for Quoted Printable Parsing --- .../QuotedPrintable-NSEncoding.ics | 183 ++++++++++++++++++ .../Serialization/QuotedPrintableContent.ics | 66 +++++++ src/Ical.Net.CoreUnitTests/IcsFiles.cs | 2 + .../QuotedPrintableDeserializationTests.cs | 109 +++++++++++ .../QuotedPrintableStringSerializerTests.cs | 117 +++++++++++ 5 files changed, 477 insertions(+) create mode 100644 src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintable-NSEncoding.ics create mode 100644 src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintableContent.ics create mode 100644 src/Ical.Net.CoreUnitTests/QuotedPrintableDeserializationTests.cs create mode 100644 src/Ical.Net.CoreUnitTests/QuotedPrintableStringSerializerTests.cs diff --git a/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintable-NSEncoding.ics b/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintable-NSEncoding.ics new file mode 100644 index 000000000..ac715716b --- /dev/null +++ b/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintable-NSEncoding.ics @@ -0,0 +1,183 @@ +BEGIN:VCALENDAR +VERSION:1.0 +BEGIN:VEVENT +DTSTART:20070303T150000 +DTEND:20070303T170000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =90=EC=8D=E8 - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6810 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=93=99=81X=97=CD=97=A4=8F=E3=8B=A3=8BZ=8F=EA +END:VEVENT +BEGIN:VEVENT +DTSTART:20070311T140300 +DTEND:20070311T160300 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - G=91=E5=8D=E3 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6822 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070317T160300 +DTEND:20070317T180300 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =90=E7=97t - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6828 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=83t=83N=83_=93d=8Eq=83A=83=8A=81[=83i +END:VEVENT +BEGIN:VEVENT +DTSTART:20070331T190400 +DTEND:20070331T210400 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =90_=8C=CB - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6840 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=83z=81[=83=80=83Y=83X=83^=83W=83A=83=80=90_=8C=CB +END:VEVENT +BEGIN:VEVENT +DTSTART:20070407T160000 +DTEND:20070407T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =91=E5=8B{ +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6847 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070414T140000 +DTEND:20070414T160000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =89=A1=95lFC - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6853 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=93=FA=8EY=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070421T190000 +DTEND:20070421T210000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =90=B4=90=85 - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6866 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=93=FA=96{=95=BD=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070429T160000 +DTEND:20070429T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =89Y=98a +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6878 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070503T160000 +DTEND:20070503T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 FC=93=8C=8B=9E - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6882 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=96=A1=82=CC=91f=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070506T150000 +DTEND:20070506T170000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =89=A1=95lFM +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6892 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070512T190000 +DTEND:20070512T210000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =94=D6=93c +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6901 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070519T140000 +DTEND:20070519T160000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =90V=8A=83 - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6907 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=93=8C=96k=93d=97=CD=83r=83b=83O=83X=83=8F=83=93=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070526T160000 +DTEND:20070526T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =8Db=95{ +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6918 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070609T150000 +DTEND:20070609T170000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =91=E5=95=AA - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6927 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8B=E3=8FB=90=CE=96=FB=83h=81[=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070616T160000 +DTEND:20070616T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =8DL=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6936 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070620T190000 +DTEND:20070620T210000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =94=90 - =8E=AD=93=87 +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6943 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=93=FA=97=A7=94=90=83T=83b=83J=81[=8F=EA +END:VEVENT +BEGIN:VEVENT +DTSTART:20070624T160000 +DTEND:20070624T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - =96=BC=8C=C3=89=AE +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6959 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +BEGIN:VEVENT +DTSTART:20070630T160000 +DTEND:20070630T180000 +SUMMARY;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +J1 =8E=AD=93=87 - FC=93=8C=8B=9E +DESCRIPTION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +http://soccer.phew.homeip.net/match/?mid=3D6962 +LOCATION;CHARSET=SJIS;ENCODING=QUOTED-PRINTABLE:= +=8C=A7=97=A7=83J=83V=83}=83T=83b=83J=81[=83X=83^=83W=83A=83=80 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintableContent.ics b/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintableContent.ics new file mode 100644 index 000000000..c5210a52c --- /dev/null +++ b/src/Ical.Net.CoreUnitTests/Calendars/Serialization/QuotedPrintableContent.ics @@ -0,0 +1,66 @@ +BEGIN:VCALENDAR +VERSION:1.0 +PRODID:vCal ID default +TZ:-04:00 +BEGIN:VEVENT +UID:content://com.android.calendar/events/123353 +DESCRIPTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:=0D=0A=2D=2D=20=44=6F=20=6E=6F=74=20=64=65=6C=65=74=65=20=6F=72=20=63= +=68=61=6E=67=65=20=61=6E=79=20=6F=66=20=74=68=65=20=66=6F=6C=6C=6F=77= +=69=6E=67=20=74=65=78=74=2E=20=2D=2D=0D=0A=0D=0A=4A=6F=69=6E=20=57=65= +=62=45=78=20=6D=65=65=74=69=6E=67=3C=68=74=74=70=73=3A=2F=2F=63=61=70= +=63=6F=6D=2E=77=65=62=65=78=2E=63=6F=6D=2F=63=61=70=63=6F=6D=2F=6A=2E= +=70=68=70=3F=4D=54=49=44=3D=6D=63=35=65=38=31=30=33=39=63=65=33=34=34= +=35=62=37=31=32=33=31=66=65=39=63=33=37=61=62=39=30=31=33=3E=0D=0A=4D= +=65=65=74=69=6E=67=20=6E=75=6D=62=65=72=20=28=61=63=63=65=73=73=20=63= +=6F=64=65=29=3A=20=37=34=35=20=34=39=31=20=32=35=36=0D=0A=4D=65=65=74= +=69=6E=67=20=70=61=73=73=77=6F=72=64=3A=20=51=37=32=64=71=42=70=6D=0D= +=0A=0D=0A=4A=6F=69=6E=20=66=72=6F=6D=20=61=20=76=69=64=65=6F=20=73=79= +=73=74=65=6D=20=6F=72=20=61=70=70=6C=69=63=61=74=69=6F=6E=0D=0A=44=69= +=61=6C=20=37=34=35=34=39=31=32=35=36=40=63=61=70=63=6F=6D=2E=77=65=62= +=65=78=2E=63=6F=6D=3C=73=69=70=3A=37=34=35=34=39=31=32=35=36=40=63=61= +=70=63=6F=6D=2E=77=65=62=65=78=2E=63=6F=6D=3E=0D=0A=0D=0A=0D=0A=4A=6F= +=69=6E=20=62=79=20=70=68=6F=6E=65=0D=0A=2B=31=20=32=31=30=20=36=30=36= +=20=39=34=36=36=20=55=53=20=54=6F=6C=6C=0D=0A=2B=31=20=38=36=36=20=32= +=38=32=20=37=33=36=36=20=55=53=20=54=6F=6C=6C=20=46=72=65=65=0D=0A=47= +=6C=6F=62=61=6C=20=63=61=6C=6C=2D=69=6E=20=6E=75=6D=62=65=72=73=3C=68= +=74=74=70=73=3A=2F=2F=63=61=70=63=6F=6D=2E=77=65=62=65=78=2E=63=6F=6D= +=2F=63=61=70=63=6F=6D=2F=67=6C=6F=62=61=6C=63=61=6C=6C=69=6E=2E=70=68= +=70=3F=73=65=72=76=69=63=65=54=79=70=65=3D=4D=43=26=45=44=3D=34=38=33= +=33=39=30=35=35=32=26=74=6F=6C=6C=46=72=65=65=3D=31=3E=20=20=7C=20=20= +=54=6F=6C=6C=2D=66=72=65=65=20=63=61=6C=6C=69=6E=67=20=72=65=73=74=72= +=69=63=74=69=6F=6E=73=3C=68=74=74=70=73=3A=2F=2F=77=77=77=2E=77=65=62= +=65=78=2E=63=6F=6D=2F=70=64=66=2F=74=6F=6C=6C=66=72=65=65=5F=72=65=73= +=74=72=69=63=74=69=6F=6E=73=2E=70=64=66=3E=0D=0A=0D=0A=0D=0A=43=61=6E= +=27=74=20=6A=6F=69=6E=20=74=68=65=20=6D=65=65=74=69=6E=67=3F=3C=68=74= +=74=70=73=3A=2F=2F=68=65=6C=70=2E=77=65=62=65=78=2E=63=6F=6D=2F=64=6F= +=63=73=2F=44=4F=43=2D=35=34=31=32=3E=0D=0A=0D=0A=49=66=20=79=6F=75=20= +=61=72=65=20=61=20=68=6F=73=74=2C=20=67=6F=20=68=65=72=65=3C=68=74=74= +=70=73=3A=2F=2F=63=61=70=63=6F=6D=2E=77=65=62=65=78=2E=63=6F=6D=2F=63= +=61=70=63=6F=6D=2F=6A=2E=70=68=70=3F=4D=54=49=44=3D=6D=32=31=63=63=66= +=32=38=33=62=65=63=33=33=30=66=61=63=33=37=31=32=66=30=39=37=63=63=65= +=61=30=36=64=3E=20=74=6F=20=76=69=65=77=20=68=6F=73=74=20=69=6E=66=6F= +=72=6D=61=74=69=6F=6E=2E=0D=0A=0D=0A=49=4D=50=4F=52=54=41=4E=54=20=4E= +=4F=54=49=43=45=3A=20=50=6C=65=61=73=65=20=6E=6F=74=65=20=74=68=61=74= +=20=74=68=69=73=20=57=65=62=45=78=20=73=65=72=76=69=63=65=20=61=6C=6C= +=6F=77=73=20=61=75=64=69=6F=20=61=6E=64=20=6F=74=68=65=72=20=69=6E=66= +=6F=72=6D=61=74=69=6F=6E=20=73=65=6E=74=20=64=75=72=69=6E=67=20=74=68= +=65=20=73=65=73=73=69=6F=6E=20=74=6F=20=62=65=20=72=65=63=6F=72=64=65= +=64=2C=20=77=68=69=63=68=20=6D=61=79=20=62=65=20=64=69=73=63=6F=76=65= +=72=61=62=6C=65=20=69=6E=20=61=20=6C=65=67=61=6C=20=6D=61=74=74=65=72= +=2E=20=42=79=20=6A=6F=69=6E=69=6E=67=20=74=68=69=73=20=73=65=73=73=69= +=6F=6E=2C=20=79=6F=75=20=61=75=74=6F=6D=61=74=69=63=61=6C=6C=79=20=63= +=6F=6E=73=65=6E=74=20=74=6F=20=73=75=63=68=20=72=65=63=6F=72=64=69=6E= +=67=73=2E=20=49=66=20=79=6F=75=20=64=6F=20=6E=6F=74=20=63=6F=6E=73=65= +=6E=74=20=74=6F=20=62=65=69=6E=67=20=72=65=63=6F=72=64=65=64=2C=20=64= +=69=73=63=75=73=73=20=79=6F=75=72=20=63=6F=6E=63=65=72=6E=73=20=77=69= +=74=68=20=74=68=65=20=68=6F=73=74=20=6F=72=20=64=6F=20=6E=6F=74=20=6A= +=6F=69=6E=20=74=68=65=20=73=65=73=73=69=6F=6E=2E=0D=0A=0D=0A=0D=0A +DTEND:20160725T183000Z +DTSTART:20160725T173000Z +LOCATION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:=42=32=2F=46=32=2F=43=61=72=69=62=62=65=61=6E +COMPLETED:20160725T183000Z +SUMMARY;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:=57=65=62=65=78=20=66=6F=72=20=6D=74=67=20=77=2F=20=41=72=6D +ALARM:20160725T171500Z;;1; +END:VEVENT +END:VCALENDAR + diff --git a/src/Ical.Net.CoreUnitTests/IcsFiles.cs b/src/Ical.Net.CoreUnitTests/IcsFiles.cs index 907c14e96..fed82db5b 100644 --- a/src/Ical.Net.CoreUnitTests/IcsFiles.cs +++ b/src/Ical.Net.CoreUnitTests/IcsFiles.cs @@ -68,6 +68,8 @@ internal static string ReadStream(string manifestResource) internal static string Event2 => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.Event2.ics"); internal static string Event3 => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.Event3.ics"); internal static string Event4 => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.Event4.ics"); + internal static string QuotedPrintableContent => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.QuotedPrintableContent.ics"); + internal static string QuotedPrintableSJISEncoding => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.QuotedPrintable-NSEncoding.ics"); internal static string EventStatus => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.EventStatus.ics"); internal static string GeographicLocation1 => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.GeographicLocation1.ics"); internal static string Google1 => ReadStream("Ical.Net.CoreUnitTests.Calendars.Serialization.Google1.ics"); diff --git a/src/Ical.Net.CoreUnitTests/QuotedPrintableDeserializationTests.cs b/src/Ical.Net.CoreUnitTests/QuotedPrintableDeserializationTests.cs new file mode 100644 index 000000000..cdb79f018 --- /dev/null +++ b/src/Ical.Net.CoreUnitTests/QuotedPrintableDeserializationTests.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Serialization.DataTypes; +using NUnit.Framework; + +namespace Ical.Net.CoreUnitTests +{ + [TestFixture] + public class QuotedPrintableDeserializationTests + { + + [Test, Category("Deserialization")] + public void QuotedPrintable_Decription() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + StringAssert.StartsWith("\r\n-- Do not delete or change any of the following text. --", evt.Description); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_Location() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual("B2/F2/Caribbean", evt.Location); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_Alarms() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual(1, evt.Alarms.Count()); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_UID() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual("content://com.android.calendar/events/123353", evt.Uid); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_Summary() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual("Webex for mtg w/ Arm", evt.Summary); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_Start() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual("2016-07-25T17:30:00 UTC", evt.Start.ToString("yyyy-MM-ddTHH:mm:ss", null)); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_End() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.AreEqual("2016-07-25T18:30:00 UTC", evt.End.ToString("yyyy-MM-ddTHH:mm:ss", null)); + } + + [Test, Category("Deserialization")] + public void QuotedPrintable_Complete() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.QuotedPrintableContent)).Cast().Single(); + Assert.AreEqual(1, iCal.Events.Count); + + var evt = iCal.Events.First(); + + Assert.IsInstanceOf(evt.Properties["COMPLETED"].Value); + var completed = evt.Properties["COMPLETED"].Value as IDateTime; + Assert.AreEqual("2016-07-25T18:30:00 UTC", completed.ToString("yyyy-MM-ddTHH:mm:ss", null)); + } + } +} diff --git a/src/Ical.Net.CoreUnitTests/QuotedPrintableStringSerializerTests.cs b/src/Ical.Net.CoreUnitTests/QuotedPrintableStringSerializerTests.cs new file mode 100644 index 000000000..7545f94f7 --- /dev/null +++ b/src/Ical.Net.CoreUnitTests/QuotedPrintableStringSerializerTests.cs @@ -0,0 +1,117 @@ +using Ical.Net.Serialization.DataTypes; + +using NUnit.Framework; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ical.Net.CoreUnitTests +{ + [TestFixture] + public class QuotedPrintableStringSerializerTests + { + [Test, Category("Serialization")] + public void MixedQuotedPrintable() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "

Normal 0 false false false MicrosoftInternetExplorer4

=0D=0A
"; + var expectedString = "

Normal 0 false false false MicrosoftInternetExplorer4

\r\n
"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintableNewLine() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "=20=0D=0A"; + var expectedString = " \r\n"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintable_Test1() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "If you believe that truth=3Dbeauty, then surely mathematics is the most bea=\r\nutiful branch of philosophy."; + var expectedString = "If you believe that truth=beauty, then surely mathematics is the most beautiful branch of philosophy."; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintable_Test2() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "fooI=C3=B1t=C3=ABrn=C3=A2ti=C3=B4n=C3=A0liz=C3=A6ti=C3=B8n=E2=98=83=F0=9F=\r\n=92=A9bar"; + var expectedString = "fooI\xF1t\xEBrn\xE2ti\xF4n\xE0liz\xE6ti\xF8n\u2603\uD83D\uDCA9bar"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintable_Test3() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "foo=0D=0Abar"; + var expectedString = "foo\r\nbar"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintable_Trailing_Whitespace_Test() + { + var serializer = new QuotedPrintableStringSerializer(); + + var sourceString = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx= \r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=\t\t \r\nxx"; + var expectedString = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + [Test, Category("Serialization")] + public void QuotedPrintable_SJIS_Test3() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // the SJIS code page is NOT included in .netstandard by default. + //in order to use these code pages they must be registered. + + var serializer = new QuotedPrintableStringSerializer("SJIS"); + + var sourceString = "=83z=81[=83=80=83Y=83X=83^=83W=83A=83=80=90_=8C=CB"; + var expectedString = "ホームズスタジアム神戸"; + var reader = new StringReader(sourceString); + + var decodedString = serializer.Deserialize(reader); + + Assert.AreEqual(expectedString, decodedString); + } + + } +} From 75dc1e6bc200805884107da3ca2fcb2d1d07543e Mon Sep 17 00:00:00 2001 From: James Menzies <32961246+JamesMenzies@users.noreply.github.com> Date: Thu, 10 Jun 2021 13:08:28 +0100 Subject: [PATCH 2/2] [feature] adding support for Quoted Printable fields. * string parsers to support line continuation when Encoding is quoted printable AND line ends with = * handle fields with encoding quoted printable string fields. * support utf8, utf16, and SJIS quoted printable encoding. --- .../DataTypes/QuotedPrintableString.cs | 13 +++ src/Ical.Net/Serialization/DataTypeMapper.cs | 12 ++ .../DataTypeSerializerFactory.cs | 4 + .../QuotedPrintableStringSerializer.cs | 109 ++++++++++++++++++ .../Serialization/EncodingProvider.cs | 37 ++++++ .../Serialization/SerializerFactory.cs | 4 + .../Serialization/SimpleDeserializer.cs | 25 +++- 7 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/Ical.Net/DataTypes/QuotedPrintableString.cs create mode 100644 src/Ical.Net/Serialization/DataTypes/QuotedPrintableStringSerializer.cs diff --git a/src/Ical.Net/DataTypes/QuotedPrintableString.cs b/src/Ical.Net/DataTypes/QuotedPrintableString.cs new file mode 100644 index 000000000..aa180b43c --- /dev/null +++ b/src/Ical.Net/DataTypes/QuotedPrintableString.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ical.Net.DataTypes +{ + public class QuotedPrintableString : IEncodableDataType + { + public string Encoding { get; set; } + + public string Value { get; set; } + } +} diff --git a/src/Ical.Net/Serialization/DataTypeMapper.cs b/src/Ical.Net/Serialization/DataTypeMapper.cs index 51d175e41..99aaf91b5 100644 --- a/src/Ical.Net/Serialization/DataTypeMapper.cs +++ b/src/Ical.Net/Serialization/DataTypeMapper.cs @@ -135,6 +135,18 @@ public virtual Type GetPropertyMapping(object obj) return null; } + if (p.Parameters.ContainsKey("ENCODING")) + { + var encoding = p.Parameters.Get("ENCODING"); + switch(encoding?.ToUpperInvariant()) + { + case "QUOTED-PRINTABLE": + return typeof(QuotedPrintableString); + default: + break; + } + } + if (!_propertyMap.TryGetValue(p.Name, out var m)) { return null; diff --git a/src/Ical.Net/Serialization/DataTypeSerializerFactory.cs b/src/Ical.Net/Serialization/DataTypeSerializerFactory.cs index 3fd983437..0ed2ea9e7 100644 --- a/src/Ical.Net/Serialization/DataTypeSerializerFactory.cs +++ b/src/Ical.Net/Serialization/DataTypeSerializerFactory.cs @@ -78,6 +78,10 @@ public virtual ISerializer Build(Type objectType, SerializationContext ctx) { s = new WeekDaySerializer(ctx); } + else if (typeof(QuotedPrintableString).IsAssignableFrom(objectType)) + { + s = new QuotedPrintableStringSerializer(ctx); + } // Default to a string serializer, which simply calls // ToString() on the value to serialize it. else diff --git a/src/Ical.Net/Serialization/DataTypes/QuotedPrintableStringSerializer.cs b/src/Ical.Net/Serialization/DataTypes/QuotedPrintableStringSerializer.cs new file mode 100644 index 000000000..9d306c1bf --- /dev/null +++ b/src/Ical.Net/Serialization/DataTypes/QuotedPrintableStringSerializer.cs @@ -0,0 +1,109 @@ +using Ical.Net.DataTypes; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ical.Net.Serialization.DataTypes +{ + public class QuotedPrintableStringSerializer : EncodableDataTypeSerializer + { + private readonly QuotedPrintableString _encoding; + + public QuotedPrintableStringSerializer() + { + _encoding = new QuotedPrintableString{ + Encoding = "UTF8" + }; + } + + public QuotedPrintableStringSerializer(string charset) + { + _encoding = new QuotedPrintableString + { + Encoding = charset + }; + } + + public QuotedPrintableStringSerializer(SerializationContext ctx) : base(ctx) + { + if (ctx.Peek() is Ical.Net.CalendarProperty calPro && calPro.Parameters.ContainsKey("CHARSET")) + { + var charset = calPro.Parameters.Get("CHARSET"); + _encoding = _encoding = new QuotedPrintableString + { + Encoding = charset + }; + } + else + { + _encoding = new QuotedPrintableString + { + Encoding = "UTF8" + }; + } + } + + public override Type TargetType => typeof(QuotedPrintableString); + + public override object Deserialize(TextReader tr) + { + if (tr == null) + { + return null; + } + + var value = tr.ReadToEnd(); + + var bytes = new List(value.Length); + + for (int i = 0; i < value.Length; i++) + { + var c = value[i]; + switch (c) + { + case '=': + if (!ConsumeWhiteSpace(ref i, ref value)) + { + var sHex = value.Substring(i + 1, 2); + int hex = Convert.ToInt32(sHex, 16); + byte b = Convert.ToByte(hex); + bytes.Add(b); + i += 2; + } + break; + default: + { + var b = Convert.ToByte(c); + bytes.Add(b); + } + break; + } + } + return Encode(_encoding, bytes.ToArray()); + //return _encoding.GetString(bytes, 0 , index); + } + + private bool ConsumeWhiteSpace(ref int index, ref string value) + { + bool foundws = false; + var max = value.Length - 1; + while (index < max && char.IsWhiteSpace(value[index + 1])) + { + index++; + foundws = true; + } + return foundws; + } + + public override string SerializeToString(object obj) + { + if (obj == null) + { + return null; + } + throw new NotImplementedException(); + } + } +} diff --git a/src/Ical.Net/Serialization/EncodingProvider.cs b/src/Ical.Net/Serialization/EncodingProvider.cs index 9c27b7055..e8cc60b4b 100644 --- a/src/Ical.Net/Serialization/EncodingProvider.cs +++ b/src/Ical.Net/Serialization/EncodingProvider.cs @@ -100,6 +100,19 @@ protected string Encode8Bit(byte[] data) } } + protected string EncodeUnicode(byte[] data) + { + try + { + var utf16 = new UnicodeEncoding(); + return utf16.GetString(data); + } + catch + { + return null; + } + } + protected string EncodeBase64(byte[] data) { try @@ -112,6 +125,19 @@ protected string EncodeBase64(byte[] data) } } + protected string EncodeSJIS(byte[] data) + { + try + { + var sjis = Encoding.GetEncoding("shift-jis"); + return sjis.GetString(data); + } + catch + { + return null; + } + } + protected virtual EncoderDelegate GetEncoderFor(string encoding) { if (encoding == null) @@ -124,9 +150,20 @@ protected virtual EncoderDelegate GetEncoderFor(string encoding) case "7BIT": return Encode7Bit; case "8BIT": + case "UTF8": + case "UTF-8": return Encode8Bit; + case "UNICODE": + case "UTF16": + case "UTF-16": + return EncodeUnicode; case "BASE64": return EncodeBase64; + case "SJIS": + case "S-JIS": + case "SHIFT_JIS": + case "SHIFT-JIS": + return EncodeSJIS; default: return null; } diff --git a/src/Ical.Net/Serialization/SerializerFactory.cs b/src/Ical.Net/Serialization/SerializerFactory.cs index a9df8edcf..b7316022f 100644 --- a/src/Ical.Net/Serialization/SerializerFactory.cs +++ b/src/Ical.Net/Serialization/SerializerFactory.cs @@ -50,6 +50,10 @@ public virtual ISerializer Build(Type objectType, SerializationContext ctx) { s = new ParameterSerializer(ctx); } + else if (typeof(QuotedPrintableString).IsAssignableFrom(objectType)) + { + s = new QuotedPrintableStringSerializer(ctx); + } else if (typeof (string).IsAssignableFrom(objectType)) { s = new StringSerializer(ctx); diff --git a/src/Ical.Net/Serialization/SimpleDeserializer.cs b/src/Ical.Net/Serialization/SimpleDeserializer.cs index 30ff08bcc..7012e40b7 100644 --- a/src/Ical.Net/Serialization/SimpleDeserializer.cs +++ b/src/Ical.Net/Serialization/SimpleDeserializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Runtime.Serialization; using System.Text; @@ -180,7 +181,9 @@ private void SetPropertyValue(SerializationContext context, CalendarProperty pro private static IEnumerable GetContentLines(TextReader reader) { + var culture = CultureInfo.InvariantCulture; var currentLine = new StringBuilder(); + bool isQuotedPrintable = false; while (true) { var nextLine = reader.ReadLine(); @@ -194,23 +197,41 @@ private static IEnumerable GetContentLines(TextReader reader) continue; } - if ((nextLine[0] == ' ' || nextLine[0] == '\t')) + if (nextLine[0] == ' ' || nextLine[0] == '\t') { currentLine.Append(nextLine, 1, nextLine.Length - 1); } + else if (isQuotedPrintable) + { + isQuotedPrintable = nextLine[nextLine.Length - 1] == '='; + var trimTail = isQuotedPrintable ? 1 : 0; + currentLine.Append(nextLine, 0, nextLine.Length - trimTail); + } else { if (currentLine.Length > 0) { yield return currentLine.ToString(); + isQuotedPrintable = false; } currentLine.Clear(); - currentLine.Append(nextLine); + + if (culture.CompareInfo.IndexOf(nextLine, ";ENCODING=QUOTED-PRINTABLE", CompareOptions.IgnoreCase) >= 0 + && nextLine[nextLine.Length - 1] == '=') + { + isQuotedPrintable = true; + currentLine.Append(nextLine, 0, nextLine.Length - 1); + } + else + { + currentLine.Append(nextLine); + } } } if (currentLine.Length > 0) { yield return currentLine.ToString(); + isQuotedPrintable = false; } } }