Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShin Mao <shmao@microsoft.com>2017-11-29 03:05:56 +0300
committerGitHub <noreply@github.com>2017-11-29 03:05:56 +0300
commit93764c0991d167e3b74f41f1ea129004ce10598c (patch)
tree0ef5d4ff2c76ce6c5418089a858be62876518a2a /src
parentaa7165382b8ca7d3e32c7e91a72fc6496f62704b (diff)
Improve Syndication DateTime Parsing (#25309)
* Improve Syndication DateTime Parsing. Fix #25156 * Enable custom parser. * Enabled StringParser. Added tests for custom DateTimeParser and StringParser. * Enable Custom UriParser. Added tests for custom UriParser. * Removed StringParser. * Addressed CR Feedback. * Unform LocalNames Passed to UriParser.
Diffstat (limited to 'src')
-rw-r--r--src/System.ServiceModel.Syndication/ref/System.ServiceModel.Syndication.cs4
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Atom10FeedFormatter.cs88
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/DateTimeHelper.cs217
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Rss20FeedFormatter.cs259
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeed.cs18
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeedFormatter.cs37
-rw-r--r--src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationItem.cs36
-rw-r--r--src/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs116
-rw-r--r--src/System.ServiceModel.Syndication/tests/TestFeeds/RssSpecCustomParser.xml26
-rw-r--r--src/System.ServiceModel.Syndication/tests/TestFeeds/SimpleAtomFeedCustomParser.xml23
10 files changed, 554 insertions, 270 deletions
diff --git a/src/System.ServiceModel.Syndication/ref/System.ServiceModel.Syndication.cs b/src/System.ServiceModel.Syndication/ref/System.ServiceModel.Syndication.cs
index e463c141e8..7103feeaa0 100644
--- a/src/System.ServiceModel.Syndication/ref/System.ServiceModel.Syndication.cs
+++ b/src/System.ServiceModel.Syndication/ref/System.ServiceModel.Syndication.cs
@@ -391,7 +391,9 @@ namespace System.ServiceModel.Syndication
{
protected SyndicationFeedFormatter() { }
protected SyndicationFeedFormatter(System.ServiceModel.Syndication.SyndicationFeed feedToWrite) { }
+ public System.Func<string, string, string, System.DateTimeOffset> DateTimeParser { get { throw null; } set { } }
public System.ServiceModel.Syndication.SyndicationFeed Feed { get { throw null; } }
+ public System.Func<string, System.UriKind, string, string, System.Uri> UriParser { get { throw null; } set { } }
public abstract string Version { get; }
public abstract bool CanRead(System.Xml.XmlReader reader);
protected internal static System.ServiceModel.Syndication.SyndicationCategory CreateCategory(System.ServiceModel.Syndication.SyndicationFeed feed) { throw null; }
@@ -616,4 +618,4 @@ namespace System.ServiceModel.Syndication
public TContent ReadContent<TContent>(System.Xml.Serialization.XmlSerializer serializer) { throw null; }
protected override void WriteContentsTo(System.Xml.XmlWriter writer) { }
}
-} \ No newline at end of file
+}
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Atom10FeedFormatter.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Atom10FeedFormatter.cs
index a01d6d074d..db52a42c56 100644
--- a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Atom10FeedFormatter.cs
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Atom10FeedFormatter.cs
@@ -21,7 +21,7 @@ namespace System.ServiceModel.Syndication
[XmlRoot(ElementName = Atom10Constants.FeedTag, Namespace = Atom10Constants.Atom10Namespace)]
public class Atom10FeedFormatter : SyndicationFeedFormatter, IXmlSerializable
{
- internal static readonly TimeSpan zeroOffset = new TimeSpan(0, 0, 0);
+ internal static readonly TimeSpan ZeroOffset = new TimeSpan(0, 0, 0);
internal const string XmlNs = "http://www.w3.org/XML/1998/namespace";
internal const string XmlNsNs = "http://www.w3.org/2000/xmlns/";
private static readonly XmlQualifiedName s_atom10Href = new XmlQualifiedName(Atom10Constants.HrefTag, string.Empty);
@@ -71,6 +71,11 @@ namespace System.ServiceModel.Syndication
_feedType = feedToWrite.GetType();
}
+ internal override Func<string, string, string, DateTimeOffset> GetDefaultDateTimeParser()
+ {
+ return DateTimeHelper.DefaultAtom10DateTimeParser;
+ }
+
public bool PreserveAttributeExtensions
{
get { return _preserveAttributeExtensions; }
@@ -304,7 +309,7 @@ namespace System.ServiceModel.Syndication
}
else if (reader.IsStartElement(Atom10Constants.LogoTag, Atom10Constants.Atom10Namespace))
{
- result.ImageUrl = new Uri(reader.ReadElementString(), UriKind.RelativeOrAbsolute);
+ result.ImageUrl = UriParser(reader.ReadElementString(), UriKind.RelativeOrAbsolute, Atom10Constants.LogoTag, Atom10Constants.Atom10Namespace);
}
else if (reader.IsStartElement(Atom10Constants.RightsTag, Atom10Constants.Atom10Namespace))
{
@@ -321,7 +326,16 @@ namespace System.ServiceModel.Syndication
else if (reader.IsStartElement(Atom10Constants.UpdatedTag, Atom10Constants.Atom10Namespace))
{
reader.ReadStartElement();
- result.LastUpdatedTime = DateFromString(reader.ReadString(), reader);
+ string dtoString = reader.ReadString();
+ try
+ {
+ result.LastUpdatedTime = DateFromString(dtoString, reader);
+ }
+ catch (XmlException e)
+ {
+ result.LastUpdatedTimeException = e;
+ }
+
reader.ReadEndElement();
}
else
@@ -360,7 +374,16 @@ namespace System.ServiceModel.Syndication
else if (reader.IsStartElement(Atom10Constants.PublishedTag, Atom10Constants.Atom10Namespace))
{
reader.ReadStartElement();
- result.PublishDate = DateFromString(reader.ReadString(), reader);
+ string dtoString = reader.ReadString();
+ try
+ {
+ result.PublishDate = DateFromString(dtoString, reader);
+ }
+ catch (XmlException e)
+ {
+ result.PublishDateException = e;
+ }
+
reader.ReadEndElement();
}
else if (reader.IsStartElement(Atom10Constants.RightsTag, Atom10Constants.Atom10Namespace))
@@ -384,7 +407,16 @@ namespace System.ServiceModel.Syndication
else if (reader.IsStartElement(Atom10Constants.UpdatedTag, Atom10Constants.Atom10Namespace))
{
reader.ReadStartElement();
- result.LastUpdatedTime = DateFromString(reader.ReadString(), reader);
+ string dtoString = reader.ReadString();
+ try
+ {
+ result.LastUpdatedTime = DateFromString(dtoString, reader);
+ }
+ catch (XmlException e)
+ {
+ result.LastUpdatedTimeException = e;
+ }
+
reader.ReadEndElement();
}
else
@@ -624,6 +656,8 @@ namespace System.ServiceModel.Syndication
}
}
reader.MoveToElement();
+ string localName = reader.LocalName;
+ string nameSpace = reader.NamespaceURI;
string val = (kind == TextSyndicationContentKind.XHtml) ? reader.ReadInnerXml() : reader.ReadElementString();
TextSyndicationContent result = new TextSyndicationContent(val, kind);
if (attrs != null)
@@ -641,7 +675,7 @@ namespace System.ServiceModel.Syndication
private string AsString(DateTimeOffset dateTime)
{
- if (dateTime.Offset == zeroOffset)
+ if (dateTime.Offset == ZeroOffset)
{
return dateTime.ToUniversalTime().ToString(Rfc3339UTCDateTimeFormat, CultureInfo.InvariantCulture);
}
@@ -651,44 +685,6 @@ namespace System.ServiceModel.Syndication
}
}
- private DateTimeOffset DateFromString(string dateTimeString, XmlReader reader)
- {
- dateTimeString = dateTimeString.Trim();
- if (dateTimeString.Length < 20)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
- new XmlException(FeedUtils.AddLineInfo(reader,
- SR.ErrorParsingDateTime)));
- }
- if (dateTimeString[19] == '.')
- {
- // remove any fractional seconds, we choose to ignore them
- int i = 20;
- while (dateTimeString.Length > i && char.IsDigit(dateTimeString[i]))
- {
- ++i;
- }
- dateTimeString = dateTimeString.Substring(0, 19) + dateTimeString.Substring(i);
- }
- DateTimeOffset localTime;
- if (DateTimeOffset.TryParseExact(dateTimeString, Rfc3339LocalDateTimeFormat,
- CultureInfo.InvariantCulture.DateTimeFormat,
- DateTimeStyles.None, out localTime))
- {
- return localTime;
- }
- DateTimeOffset utcTime;
- if (DateTimeOffset.TryParseExact(dateTimeString, Rfc3339UTCDateTimeFormat,
- CultureInfo.InvariantCulture.DateTimeFormat,
- DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out utcTime))
- {
- return utcTime;
- }
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
- new XmlException(FeedUtils.AddLineInfo(reader,
- SR.ErrorParsingDateTime)));
- }
-
private void ReadCategory(XmlReader reader, SyndicationCategory category)
{
ReadCategory(reader, category, this.Version, this.PreserveAttributeExtensions, this.PreserveElementExtensions, _maxExtensionSize);
@@ -732,7 +728,7 @@ namespace System.ServiceModel.Syndication
if (!string.IsNullOrEmpty(src))
{
- result = new UrlSyndicationContent(new Uri(src, UriKind.RelativeOrAbsolute), type);
+ result = new UrlSyndicationContent(UriParser(src, UriKind.RelativeOrAbsolute, Atom10Constants.ContentTag, Atom10Constants.Atom10Namespace), type);
bool isEmpty = reader.IsEmptyElement;
if (reader.HasAttributes)
{
@@ -1085,7 +1081,7 @@ namespace System.ServiceModel.Syndication
link.MediaType = mediaType;
link.RelationshipType = relationship;
link.Title = title;
- link.Uri = (val != null) ? new Uri(val, UriKind.RelativeOrAbsolute) : null;
+ link.Uri = (val != null) ? UriParser(val, UriKind.RelativeOrAbsolute, Atom10Constants.LinkTag, Atom10Constants.Atom10Namespace) : null;
}
private SyndicationLink ReadLinkFrom(XmlReader reader, SyndicationFeed feed)
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/DateTimeHelper.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/DateTimeHelper.cs
new file mode 100644
index 0000000000..9ca15bf074
--- /dev/null
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/DateTimeHelper.cs
@@ -0,0 +1,217 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Text;
+
+namespace System.ServiceModel.Syndication
+{
+ internal static class DateTimeHelper
+ {
+ private const string Rfc3339DateTimeFormat = "yyyy-MM-ddTHH:mm:ssK";
+
+ public static DateTimeOffset DefaultRss20DateTimeParser(string dateTimeString, string localName, string ns)
+ {
+ DateTimeOffset dto;
+
+ // First check if DateTimeOffset default parsing can parse the date
+ if (DateTimeOffset.TryParse(dateTimeString, out dto))
+ {
+ return dto;
+ }
+
+ // RSS specifies RFC822
+ if (Rfc822DateTimeParser(dateTimeString, out dto))
+ {
+ return dto;
+ }
+
+ // Event though RCS3339 is for Atom, someone might be using this for RSS
+ if (Rfc3339DateTimeParser(dateTimeString, out dto))
+ {
+ return dto;
+ }
+
+ // Unable to parse - using a default date;
+ throw new FormatException(SR.ErrorParsingDateTime);
+ }
+
+ public static DateTimeOffset DefaultAtom10DateTimeParser(string dateTimeString, string localName, string ns)
+ {
+ if (Rfc3339DateTimeParser(dateTimeString, out DateTimeOffset dto))
+ {
+ return dto;
+ }
+
+ throw new FormatException(SR.ErrorParsingDateTime);
+ }
+
+ private static bool Rfc3339DateTimeParser(string dateTimeString, out DateTimeOffset dto)
+ {
+ dateTimeString = dateTimeString.Trim();
+ if (dateTimeString.Length < 20)
+ {
+ return false;
+ }
+
+ if (dateTimeString[19] == '.')
+ {
+ // remove any fractional seconds, we choose to ignore them
+ int i = 20;
+ while (dateTimeString.Length > i && char.IsDigit(dateTimeString[i]))
+ {
+ ++i;
+ }
+
+ dateTimeString = dateTimeString.Substring(0, 19) + dateTimeString.Substring(i);
+ }
+
+ return DateTimeOffset.TryParseExact(dateTimeString, Rfc3339DateTimeFormat, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.None, out dto);
+ }
+
+ private static bool Rfc822DateTimeParser(string dateTimeString, out DateTimeOffset dto)
+ {
+ StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim());
+ if (dateTimeStringBuilder.Length < 18)
+ {
+ return false;
+ }
+
+ int timeZoneStartIndex;
+ for (timeZoneStartIndex = dateTimeStringBuilder.Length - 1; dateTimeStringBuilder[timeZoneStartIndex] != ' '; timeZoneStartIndex--)
+ ;
+ timeZoneStartIndex++;
+
+ int timeZoneLength = dateTimeStringBuilder.Length - timeZoneStartIndex;
+ string timeZoneSuffix = dateTimeStringBuilder.ToString(timeZoneStartIndex, timeZoneLength);
+ dateTimeStringBuilder.Remove(timeZoneStartIndex, timeZoneLength);
+ bool isUtc;
+ dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc));
+ string wellFormattedString = dateTimeStringBuilder.ToString();
+
+ DateTimeOffset theTime;
+ string[] parseFormat =
+ {
+ "ddd, dd MMMM yyyy HH:mm:ss zzz",
+ "dd MMMM yyyy HH:mm:ss zzz",
+ "ddd, dd MMM yyyy HH:mm:ss zzz",
+ "dd MMM yyyy HH:mm:ss zzz",
+
+ "ddd, dd MMMM yyyy HH:mm zzz",
+ "dd MMMM yyyy HH:mm zzz",
+ "ddd, dd MMM yyyy HH:mm zzz",
+ "dd MMM yyyy HH:mm zzz",
+
+ // The original RFC822 spec listed 2 digit years. RFC1123 updated the format to include 4 digit years and states that you should use 4 digits.
+ // Technically RSS2.0 specifies RFC822 but it's presumed that RFC1123 will be used as we're now past Y2K and everyone knows better. The 4 digit
+ // formats are listed first for performance reasons as it's presumed they will be more likely to match first.
+ "ddd, dd MMMM yy HH:mm:ss zzz",
+ "dd MMMM yyyy HH:mm:ss zzz",
+ "ddd, dd MMM yy HH:mm:ss zzz",
+ "dd MMM yyyy HH:mm:ss zzz",
+
+ "ddd, dd MMMM yy HH:mm zzz",
+ "dd MMMM yyyy HH:mm zzz",
+ "ddd, dd MMM yy HH:mm zzz",
+ "dd MMM yyyy HH:mm zzz"
+ };
+
+ if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat,
+ CultureInfo.InvariantCulture.DateTimeFormat,
+ (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime))
+ {
+ dto = theTime;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc)
+ {
+ isUtc = false;
+ // return a string in "-08:00" format
+ if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-')
+ {
+ // the time zone is supposed to be 4 digits but some feeds omit the initial 0
+ StringBuilder result = new StringBuilder(rfc822TimeZone);
+ if (result.Length == 4)
+ {
+ // the timezone is +/-HMM. Convert to +/-HHMM
+ result.Insert(1, '0');
+ }
+ result.Insert(3, ':');
+ return result.ToString();
+ }
+ switch (rfc822TimeZone)
+ {
+ case "UT":
+ case "Z":
+ isUtc = true;
+ return "-00:00";
+ case "GMT":
+ return "-00:00";
+ case "A":
+ return "-01:00";
+ case "B":
+ return "-02:00";
+ case "C":
+ return "-03:00";
+ case "D":
+ case "EDT":
+ return "-04:00";
+ case "E":
+ case "EST":
+ case "CDT":
+ return "-05:00";
+ case "F":
+ case "CST":
+ case "MDT":
+ return "-06:00";
+ case "G":
+ case "MST":
+ case "PDT":
+ return "-07:00";
+ case "H":
+ case "PST":
+ return "-08:00";
+ case "I":
+ return "-09:00";
+ case "K":
+ return "-10:00";
+ case "L":
+ return "-11:00";
+ case "M":
+ return "-12:00";
+ case "N":
+ return "+01:00";
+ case "O":
+ return "+02:00";
+ case "P":
+ return "+03:00";
+ case "Q":
+ return "+04:00";
+ case "R":
+ return "+05:00";
+ case "S":
+ return "+06:00";
+ case "T":
+ return "+07:00";
+ case "U":
+ return "+08:00";
+ case "V":
+ return "+09:00";
+ case "W":
+ return "+10:00";
+ case "X":
+ return "+11:00";
+ case "Y":
+ return "+12:00";
+ default:
+ return "";
+ }
+ }
+
+ }
+}
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Rss20FeedFormatter.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Rss20FeedFormatter.cs
index de97d03a9d..409c24b87d 100644
--- a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Rss20FeedFormatter.cs
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/Rss20FeedFormatter.cs
@@ -78,6 +78,11 @@ namespace System.ServiceModel.Syndication
_feedType = feedToWrite.GetType();
}
+ internal override Func<string, string, string, DateTimeOffset> GetDefaultDateTimeParser()
+ {
+ return DateTimeHelper.DefaultRss20DateTimeParser;
+ }
+
public bool PreserveAttributeExtensions
{
get { return _preserveAttributeExtensions; }
@@ -254,211 +259,10 @@ namespace System.ServiceModel.Syndication
this.WriteItem(writer, item, feedBaseUri);
}
}
-
- private static DateTimeOffset DateFromString(string dateTimeString, XmlReader reader)
- {
- StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim());
- if (dateTimeStringBuilder.Length < 18)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
- new XmlException(FeedUtils.AddLineInfo(reader,
- SR.ErrorParsingDateTime)));
- }
- if (dateTimeStringBuilder[3] == ',')
- {
- // There is a leading (e.g.) "Tue, ", strip it off
- dateTimeStringBuilder.Remove(0, 4);
- // There's supposed to be a space here but some implementations dont have one
- RemoveExtraWhiteSpaceAtStart(dateTimeStringBuilder);
- }
- ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(dateTimeStringBuilder);
- if (char.IsDigit(dateTimeStringBuilder[1]))
- {
- // two-digit day, we are good
- }
- else
- {
- dateTimeStringBuilder.Insert(0, '0');
- }
- if (dateTimeStringBuilder.Length < 19)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
- new XmlException(FeedUtils.AddLineInfo(reader,
- SR.ErrorParsingDateTime)));
- }
- bool thereAreSeconds = (dateTimeStringBuilder[17] == ':');
- int timeZoneStartIndex;
- if (thereAreSeconds)
- {
- timeZoneStartIndex = 21;
- }
- else
- {
- timeZoneStartIndex = 18;
- }
- string timeZoneSuffix = dateTimeStringBuilder.ToString().Substring(timeZoneStartIndex);
- dateTimeStringBuilder.Remove(timeZoneStartIndex, dateTimeStringBuilder.Length - timeZoneStartIndex);
- bool isUtc;
- dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc));
- string wellFormattedString = dateTimeStringBuilder.ToString();
-
- DateTimeOffset theTime;
- string parseFormat;
- if (thereAreSeconds)
- {
- parseFormat = "dd MMM yyyy HH:mm:ss zzz";
- }
- else
- {
- parseFormat = "dd MMM yyyy HH:mm zzz";
- }
- if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat,
- CultureInfo.InvariantCulture.DateTimeFormat,
- (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime))
- {
- return theTime;
- }
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
- new XmlException(FeedUtils.AddLineInfo(reader,
- SR.ErrorParsingDateTime)));
- }
-
- private static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc)
- {
- isUtc = false;
- // return a string in "-08:00" format
- if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-')
- {
- // the time zone is supposed to be 4 digits but some feeds omit the initial 0
- StringBuilder result = new StringBuilder(rfc822TimeZone);
- if (result.Length == 4)
- {
- // the timezone is +/-HMM. Convert to +/-HHMM
- result.Insert(1, '0');
- }
- result.Insert(3, ':');
- return result.ToString();
- }
- switch (rfc822TimeZone)
- {
- case "UT":
- case "Z":
- isUtc = true;
- return "-00:00";
- case "GMT":
- return "-00:00";
- case "A":
- return "-01:00";
- case "B":
- return "-02:00";
- case "C":
- return "-03:00";
- case "D":
- case "EDT":
- return "-04:00";
- case "E":
- case "EST":
- case "CDT":
- return "-05:00";
- case "F":
- case "CST":
- case "MDT":
- return "-06:00";
- case "G":
- case "MST":
- case "PDT":
- return "-07:00";
- case "H":
- case "PST":
- return "-08:00";
- case "I":
- return "-09:00";
- case "K":
- return "-10:00";
- case "L":
- return "-11:00";
- case "M":
- return "-12:00";
- case "N":
- return "+01:00";
- case "O":
- return "+02:00";
- case "P":
- return "+03:00";
- case "Q":
- return "+04:00";
- case "R":
- return "+05:00";
- case "S":
- return "+06:00";
- case "T":
- return "+07:00";
- case "U":
- return "+08:00";
- case "V":
- return "+09:00";
- case "W":
- return "+10:00";
- case "X":
- return "+11:00";
- case "Y":
- return "+12:00";
- default:
- return "";
- }
- }
-
- private static void RemoveExtraWhiteSpaceAtStart(StringBuilder stringBuilder)
- {
- int i = 0;
- while (i < stringBuilder.Length)
- {
- if (!char.IsWhiteSpace(stringBuilder[i]))
- {
- break;
- }
- ++i;
- }
- if (i > 0)
- {
- stringBuilder.Remove(0, i);
- }
- }
-
- private static void ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(StringBuilder builder)
- {
- int index = 0;
- int whiteSpaceStart = -1;
- while (index < builder.Length)
- {
- if (char.IsWhiteSpace(builder[index]))
- {
- if (whiteSpaceStart < 0)
- {
- whiteSpaceStart = index;
- // normalize all white spaces to be ' ' so that the date time parsing works
- builder[index] = ' ';
- }
- }
- else if (whiteSpaceStart >= 0)
- {
- if (index > whiteSpaceStart + 1)
- {
- // there are at least 2 spaces... replace by 1
- builder.Remove(whiteSpaceStart, index - whiteSpaceStart - 1);
- index = whiteSpaceStart + 1;
- }
- whiteSpaceStart = -1;
- }
- ++index;
- }
- // we have already trimmed the start and end so there cannot be a trail of white spaces in the end
- Debug.Assert(builder.Length == 0 || builder[builder.Length - 1] != ' ', "The string builder doesnt end in a white space");
- }
-
+
private string AsString(DateTimeOffset dateTime)
{
- if (dateTime.Offset == Atom10FeedFormatter.zeroOffset)
+ if (dateTime.Offset == Atom10FeedFormatter.ZeroOffset)
{
return dateTime.ToUniversalTime().ToString(Rfc822OutputUtcDateTimeFormat, CultureInfo.InvariantCulture);
}
@@ -497,8 +301,9 @@ namespace System.ServiceModel.Syndication
}
}
}
+
string uri = reader.ReadElementString();
- link.Uri = new Uri(uri, UriKind.RelativeOrAbsolute);
+ link.Uri = UriParser(uri, UriKind.RelativeOrAbsolute, Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace);
return link;
}
@@ -601,6 +406,8 @@ namespace System.ServiceModel.Syndication
if (!isEmpty)
{
string fallbackAlternateLink = null;
+ string fallbackAlternateLinkLocalName = null;
+ string fallbackAlternateLinkNamespace = null;
XmlDictionaryWriter extWriter = null;
bool readAlternateLink = false;
try
@@ -641,10 +448,13 @@ namespace System.ServiceModel.Syndication
{
isPermalink = false;
}
+
result.Id = reader.ReadElementString();
if (isPermalink)
{
fallbackAlternateLink = result.Id;
+ fallbackAlternateLinkLocalName = Rss20Constants.GuidTag;
+ fallbackAlternateLinkNamespace = Rss20Constants.Rss20Namespace;
}
}
else if (reader.IsStartElement(Rss20Constants.PubDateTag, Rss20Constants.Rss20Namespace))
@@ -656,7 +466,14 @@ namespace System.ServiceModel.Syndication
string str = reader.ReadString();
if (!string.IsNullOrEmpty(str))
{
- result.PublishDate = DateFromString(str, reader);
+ try
+ {
+ result.PublishDate = DateFromString(str, reader);
+ }
+ catch (XmlException e)
+ {
+ result.PublishDateException = e;
+ }
}
reader.ReadEndElement();
}
@@ -677,7 +494,7 @@ namespace System.ServiceModel.Syndication
string val = reader.Value;
if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
{
- feed.Links.Add(SyndicationLink.CreateSelfLink(new Uri(val, UriKind.RelativeOrAbsolute)));
+ feed.Links.Add(SyndicationLink.CreateSelfLink(UriParser(val, UriKind.RelativeOrAbsolute, Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace)));
}
else if (!FeedUtils.IsXmlns(name, ns))
{
@@ -692,6 +509,7 @@ namespace System.ServiceModel.Syndication
}
}
}
+
string feedTitle = reader.ReadElementString();
feed.Title = new TextSyndicationContent(feedTitle);
result.SourceFeed = feed;
@@ -729,7 +547,7 @@ namespace System.ServiceModel.Syndication
reader.ReadEndElement(); // item
if (!readAlternateLink && fallbackAlternateLink != null)
{
- result.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(fallbackAlternateLink, UriKind.RelativeOrAbsolute)));
+ result.Links.Add(SyndicationLink.CreateAlternateLink(UriParser(fallbackAlternateLink, UriKind.RelativeOrAbsolute, fallbackAlternateLinkLocalName, fallbackAlternateLinkNamespace)));
readAlternateLink = true;
}
@@ -775,7 +593,7 @@ namespace System.ServiceModel.Syndication
string val = reader.Value;
if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
{
- link.Uri = new Uri(val, UriKind.RelativeOrAbsolute);
+ link.Uri = UriParser(val, UriKind.RelativeOrAbsolute, Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace);
}
else if (name == Rss20Constants.TypeTag && ns == Rss20Constants.Rss20Namespace)
{
@@ -861,7 +679,11 @@ namespace System.ServiceModel.Syndication
try
{
string baseUri = null;
+ string baseUriLocalName = null;
+ string baseUriNamespace = null;
reader.MoveToContent();
+ string elementLocalName = reader.LocalName;
+ string elementNamespace = reader.NamespaceURI;
string version = reader.GetAttribute(Rss20Constants.VersionTag, Rss20Constants.Rss20Namespace);
if (version != Rss20Constants.Version)
{
@@ -873,10 +695,14 @@ namespace System.ServiceModel.Syndication
if (!string.IsNullOrEmpty(tmp))
{
baseUri = tmp;
+ baseUriLocalName = elementLocalName;
+ baseUriNamespace = elementNamespace;
}
}
reader.ReadStartElement();
reader.MoveToContent();
+ elementLocalName = reader.LocalName;
+ elementNamespace = reader.NamespaceURI;
if (reader.HasAttributes)
{
while (reader.MoveToNextAttribute())
@@ -886,6 +712,8 @@ namespace System.ServiceModel.Syndication
if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
{
baseUri = reader.Value;
+ baseUriLocalName = elementLocalName;
+ baseUriNamespace = elementNamespace;
continue;
}
if (FeedUtils.IsXmlns(name, ns) || FeedUtils.IsXmlSchemaType(name, ns))
@@ -906,10 +734,12 @@ namespace System.ServiceModel.Syndication
}
}
}
+
if (!string.IsNullOrEmpty(baseUri))
{
- result.BaseUri = new Uri(baseUri, UriKind.RelativeOrAbsolute);
+ result.BaseUri = UriParser(baseUri, UriKind.RelativeOrAbsolute, baseUriLocalName, baseUriNamespace);
}
+
bool areAllItemsRead = true;
reader.ReadStartElement(Rss20Constants.ChannelTag, Rss20Constants.Rss20Namespace);
@@ -954,7 +784,14 @@ namespace System.ServiceModel.Syndication
string str = reader.ReadString();
if (!string.IsNullOrEmpty(str))
{
- result.LastUpdatedTime = DateFromString(str, reader);
+ try
+ {
+ result.LastUpdatedTime = DateFromString(str, reader);
+ }
+ catch (XmlException e)
+ {
+ result.LastUpdatedTimeException = e;
+ }
}
reader.ReadEndElement();
}
@@ -974,7 +811,7 @@ namespace System.ServiceModel.Syndication
{
if (reader.IsStartElement(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace))
{
- result.ImageUrl = new Uri(reader.ReadElementString(), UriKind.RelativeOrAbsolute);
+ result.ImageUrl = UriParser(reader.ReadElementString(), UriKind.RelativeOrAbsolute, Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace);
}
else
{
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeed.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeed.cs
index 6df7450bd7..621c98994f 100644
--- a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeed.cs
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeed.cs
@@ -227,10 +227,24 @@ namespace System.ServiceModel.Syndication
set { _language = value; }
}
+ internal Exception LastUpdatedTimeException { get; set; }
+
public DateTimeOffset LastUpdatedTime
{
- get { return _lastUpdatedTime; }
- set { _lastUpdatedTime = value; }
+ get
+ {
+ if (LastUpdatedTimeException != null)
+ {
+ throw LastUpdatedTimeException;
+ }
+
+ return _lastUpdatedTime;
+ }
+ set
+ {
+ LastUpdatedTimeException = null;
+ _lastUpdatedTime = value;
+ }
}
public Collection<SyndicationLink> Links
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeedFormatter.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeedFormatter.cs
index fa89a8493b..750563c606 100644
--- a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeedFormatter.cs
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationFeedFormatter.cs
@@ -7,11 +7,9 @@ namespace System.ServiceModel.Syndication
using System;
using System.Diagnostics;
using System.Globalization;
- using System.Runtime;
using System.Runtime.Serialization;
using System.Xml;
using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
- using System.Runtime.CompilerServices;
[DataContract]
public abstract class SyndicationFeedFormatter
@@ -21,6 +19,7 @@ namespace System.ServiceModel.Syndication
protected SyndicationFeedFormatter()
{
_feed = null;
+ DateTimeParser = GetDefaultDateTimeParser();
}
protected SyndicationFeedFormatter(SyndicationFeed feedToWrite)
@@ -30,6 +29,7 @@ namespace System.ServiceModel.Syndication
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feedToWrite");
}
_feed = feedToWrite;
+ DateTimeParser = GetDefaultDateTimeParser();
}
public SyndicationFeed Feed
@@ -40,6 +40,21 @@ namespace System.ServiceModel.Syndication
}
}
+ public Func<string, UriKind, string, string, Uri> UriParser { get; set; } = DefaultUriParser;
+
+ // Different DateTimeParsers are needed for Atom and Rss so can't set inline
+ public Func<string, string, string, DateTimeOffset> DateTimeParser { get; set; }
+
+ internal virtual Func<string, string, string, DateTimeOffset> GetDefaultDateTimeParser()
+ {
+ return NotImplementedDateTimeParser;
+ }
+
+ private DateTimeOffset NotImplementedDateTimeParser(string dtoString, string localName, string ns)
+ {
+ throw new NotImplementedException();
+ }
+
public abstract string Version
{ get; }
@@ -376,6 +391,24 @@ namespace System.ServiceModel.Syndication
_feed = feed;
}
+ internal DateTimeOffset DateFromString(string dateTimeString, XmlReader reader)
+ {
+ try
+ {
+ return DateTimeParser(dateTimeString, reader.LocalName, reader.NamespaceURI);
+ }
+ catch (FormatException e)
+ {
+ throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
+ new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingDateTime), e));
+ }
+ }
+
+ private static Uri DefaultUriParser(string value, UriKind kind, string localName, string ns)
+ {
+ return new Uri(value, kind);
+ }
+
internal static void CloseBuffer(XmlBuffer buffer, XmlDictionaryWriter extWriter)
{
if (buffer == null)
diff --git a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationItem.cs b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationItem.cs
index 3f8648be02..08cf46b42e 100644
--- a/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationItem.cs
+++ b/src/System.ServiceModel.Syndication/src/System/ServiceModel/Syndication/SyndicationItem.cs
@@ -158,10 +158,24 @@ namespace System.ServiceModel.Syndication
set { _id = value; }
}
+ internal Exception LastUpdatedTimeException { get; set; }
+
public DateTimeOffset LastUpdatedTime
{
- get { return _lastUpdatedTime; }
- set { _lastUpdatedTime = value; }
+ get
+ {
+ if (LastUpdatedTimeException != null)
+ {
+ throw LastUpdatedTimeException;
+ }
+
+ return _lastUpdatedTime;
+ }
+ set
+ {
+ LastUpdatedTimeException = null;
+ _lastUpdatedTime = value;
+ }
}
public Collection<SyndicationLink> Links
@@ -176,10 +190,24 @@ namespace System.ServiceModel.Syndication
}
}
+ internal Exception PublishDateException { get; set; }
+
public DateTimeOffset PublishDate
{
- get { return _publishDate; }
- set { _publishDate = value; }
+ get
+ {
+ if (PublishDateException != null)
+ {
+ throw PublishDateException;
+ }
+
+ return _publishDate;
+ }
+ set
+ {
+ PublishDateException = null;
+ _publishDate = value;
+ }
}
public SyndicationFeed SourceFeed
diff --git a/src/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs b/src/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs
index ef2665785e..ee89b27219 100644
--- a/src/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs
+++ b/src/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs
@@ -9,6 +9,7 @@ using System.ServiceModel.Syndication;
using System.Xml;
using System.IO;
using Xunit;
+using System.Linq;
namespace System.ServiceModel.Syndication.Tests
{
@@ -285,19 +286,126 @@ namespace System.ServiceModel.Syndication.Tests
}
[Fact]
- [ActiveIssue(25156)]
public static void SyndicationFeed_Rss_WrongDateFormat()
{
// *** SETUP *** \\
- Rss20FeedFormatter rssformatter = new Rss20FeedFormatter();
-
XmlReader reader = XmlReader.Create(@"rssSpecExampleWrongDateFormat.xml");
// *** EXECUTE *** \\
SyndicationFeed res = SyndicationFeed.Load(reader);
// *** ASSERT *** \\
- Assert.True(!res.LastUpdatedTime.Equals(new DateTimeOffset()));
+ Assert.True(res != null, "res was null.");
+ Assert.Equal(new DateTimeOffset(2016, 8, 23, 16, 8, 0, new TimeSpan(-4, 0, 0)), res.LastUpdatedTime);
+ Assert.True(res.Items != null, "res.Items was null.");
+ Assert.True(res.Items.Count() == 4, $"res.Items.Count() was not as expected. Expected: 4; Actual: {res.Items.Count()}");
+ SyndicationItem[] items = res.Items.ToArray();
+ DateTimeOffset dateTimeOffset;
+ Assert.Throws<XmlException>(() => dateTimeOffset = items[2].PublishDate);
+ }
+
+ [Fact]
+ public static void SyndicationFeed_Rss_DateTimeParser()
+ {
+ // *** SETUP *** \\
+ // *** EXECUTE *** \\
+ SyndicationFeed feed;
+ DateTimeOffset dto = new DateTimeOffset(2017, 1, 2, 3, 4, 5, new TimeSpan(0));
+ using (XmlReader reader = XmlReader.Create(@"RssSpecCustomParser.xml"))
+ {
+ var formatter = new Rss20FeedFormatter();
+ formatter.DateTimeParser = (value, localName, ns) => dto;
+ formatter.ReadFrom(reader);
+ feed = formatter.Feed;
+ }
+
+ // *** ASSERT *** \\
+ Assert.True(feed != null, "res was null.");
+ Assert.Equal(dto, feed.LastUpdatedTime);
+ }
+
+ [Fact]
+ public static void SyndicationFeed_Rss_UriParser()
+ {
+ // *** SETUP *** \\
+ // *** EXECUTE *** \\
+ SyndicationFeed feed;
+ using (XmlReader reader = XmlReader.Create(@"RssSpecCustomParser.xml"))
+ {
+ var formatter = new Rss20FeedFormatter
+ {
+ UriParser = (value, kind, localName, ns) => new Uri($"http://value-{value}-kind-{kind}-localName-{localName}-ns-{ns}-end")
+ };
+ formatter.ReadFrom(reader);
+ feed = formatter.Feed;
+ }
+
+ // *** ASSERT *** \\
+ Assert.True(feed != null, "res was null.");
+ Assert.Equal(new Uri("http://value-ChannelBase-kind-relativeorabsolute-localName-channel-ns--end"), feed.BaseUri);
+ Assert.Equal(new Uri("http://value-ImageUrl-kind-relativeorabsolute-localName-url-ns--end"), feed.ImageUrl);
+ Assert.NotNull(feed.Links);
+ Assert.Equal(1, feed.Links.Count);
+ Assert.Equal(new Uri("http://value-FeedLink-kind-relativeorabsolute-localName-link-ns--end"), feed.Links.First().Uri);
+
+ Assert.True(feed.Items != null, "res.Items was null.");
+ Assert.Equal(1, feed.Items.Count());
+ Assert.Equal(1, feed.Items.First().Links.Count);
+ Assert.Equal(new Uri("http://value-itemlink-kind-relativeorabsolute-localName-link-ns--end"), feed.Items.First().Links.First().Uri);
+ }
+
+ [Fact]
+ public static void SyndicationFeed_Atom_DateTimeParser()
+ {
+ // *** SETUP *** \\
+ // *** EXECUTE *** \\
+ SyndicationFeed feed;
+ DateTimeOffset dto = new DateTimeOffset(2017, 1, 2, 3, 4, 5, new TimeSpan(0));
+ using (XmlReader reader = XmlReader.Create(@"SimpleAtomFeedCustomParser.xml"))
+ {
+ var formatter = new Atom10FeedFormatter
+ {
+ DateTimeParser = (value, localName, ns) => dto
+ };
+ formatter.ReadFrom(reader);
+ feed = formatter.Feed;
+ }
+
+ // *** ASSERT *** \\
+ Assert.True(feed != null, "res was null.");
+ Assert.Equal(dto, feed.LastUpdatedTime);
+
+ Assert.True(feed.Items != null, "res.Items was null.");
+ Assert.Equal(1, feed.Items.Count());
+ Assert.Equal(dto, feed.Items.First().LastUpdatedTime);
+ }
+
+ [Fact]
+ public static void SyndicationFeed_Atom_UriParser()
+ {
+ // *** SETUP *** \\
+ // *** EXECUTE *** \\
+ SyndicationFeed feed;
+ using (XmlReader reader = XmlReader.Create(@"SimpleAtomFeedCustomParser.xml"))
+ {
+ var formatter = new Atom10FeedFormatter
+ {
+ UriParser = (value, kind, localName, ns) => new Uri($"http://value-{value}-kind-{kind}-localName-{localName}-ns-{ns}-end")
+ };
+ formatter.ReadFrom(reader);
+ feed = formatter.Feed;
+ }
+
+ // *** ASSERT *** \\
+ Assert.True(feed != null, "res was null.");
+ Assert.Equal(new Uri("http://value-FeedLogo-kind-relativeorabsolute-localName-logo-ns-http//www.w3.org/2005/Atom-end"), feed.ImageUrl);
+
+ Assert.True(feed.Items != null, "res.Items was null.");
+ Assert.Equal(1, feed.Items.Count());
+ Assert.NotNull(feed.Items.First().Links);
+ Assert.Equal(1, feed.Items.First().Links.Count);
+ Assert.Equal(new Uri("http://value-EntryLinkHref-kind-relativeorabsolute-localName-link-ns-http//www.w3.org/2005/Atom-end"), feed.Items.First().Links.First().Uri);
+ Assert.Equal(new Uri("http://value-EntryContentSrc-kind-relativeorabsolute-localName-content-ns-http://www.w3.org/2005/Atom-end"), ((UrlSyndicationContent)feed.Items.First().Content).Url);
}
[Fact]
diff --git a/src/System.ServiceModel.Syndication/tests/TestFeeds/RssSpecCustomParser.xml b/src/System.ServiceModel.Syndication/tests/TestFeeds/RssSpecCustomParser.xml
new file mode 100644
index 0000000000..c90436a4f7
--- /dev/null
+++ b/src/System.ServiceModel.Syndication/tests/TestFeeds/RssSpecCustomParser.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<rss version="2.0">
+ <channel xml:base="ChannelBase" customAttribute="customAtt">
+ <title>FeedTitle</title>
+ <link>FeedLink</link>
+ <description>FeedDescription</description>
+ <language>FeedLanguage</language>
+ <lastBuildDate>Tue, 23 August 2016 16:08:00 EDT</lastBuildDate>
+ <docs>http://blogs.law.harvard.edu/tech/rss</docs>
+ <generator>FeedGenerator</generator>
+ <managingEditor>FeedEditorEmail</managingEditor>
+ <category>FeedCategory</category>
+ <copyright>FeedCopyright</copyright>
+ <ttl>60</ttl>
+ <image>
+ <url>ImageUrl</url>
+ </image>
+ <item>
+ <title>ItemTitle</title>
+ <link>ItemLink</link>
+ <description>ItemDescription</description>
+ <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
+ <guid>ItemGuid</guid>
+ </item>
+ </channel>
+</rss> \ No newline at end of file
diff --git a/src/System.ServiceModel.Syndication/tests/TestFeeds/SimpleAtomFeedCustomParser.xml b/src/System.ServiceModel.Syndication/tests/TestFeeds/SimpleAtomFeedCustomParser.xml
new file mode 100644
index 0000000000..e6237940dc
--- /dev/null
+++ b/src/System.ServiceModel.Syndication/tests/TestFeeds/SimpleAtomFeedCustomParser.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xml:base="http://mypage.com/" xmlns="http://www.w3.org/2005/Atom">
+ <title type="text">FeedTitle</title>
+ <subtitle type="text">FeedSubtitle</subtitle>
+ <id>FeedID</id>
+ <updated>2017-06-26T14:41:43-07:00</updated>
+ <logo>FeedLogo</logo>
+ <generator>FeedGenerator</generator>
+ <author>
+ <name>AuthorName</name>
+ <email>author@Contoso.com</email>
+ <uri>AuthorUri</uri>
+ </author>
+ <link rel="alternate" href="http://www.contoso.com/news" />
+ <CustomElement xmlns="">asd</CustomElement>
+ <entry>
+ <id>EntryId</id>
+ <title type="text">SyndicationFeed released for .net Core</title>
+ <updated>2017-06-26T21:41:43Z</updated>
+ <link rel="alternate" href="EntryLinkHref" />
+ <content src="EntryContentSrc" />
+ </entry>
+</feed> \ No newline at end of file