diff --git a/EppLib.UnitTests/LocalTest.cs b/EppLib.UnitTests/LocalTest.cs index be05c14..2ffefdf 100644 --- a/EppLib.UnitTests/LocalTest.cs +++ b/EppLib.UnitTests/LocalTest.cs @@ -17,13 +17,13 @@ public class LocalTest public TestContext TestContext { get; set; } - [TestInitialize] - public void TestSetup() - { - var testName = TestContext.TestName; - var method = new StackFrame().GetMethod().DeclaringType.GetMethod(testName); - var attributes = method.GetCustomAttributes(typeof(DeploymentItemAttribute), false); - DeploymentUtility.CopyDeploymentItems(attributes); + [TestInitialize] + public void TestSetup() + { + var testName = TestContext.TestName; + var method = new StackFrame().GetMethod().DeclaringType.GetMethod(testName); + var attributes = method.GetCustomAttributes(typeof(DeploymentItemAttribute), false); + DeploymentUtility.CopyDeploymentItems(attributes); } public LocalTest() @@ -1072,6 +1072,21 @@ public void TestDomainRenewCommand1() Assert.AreEqual(expected, command.ToXml().InnerXml); } + /// + /// Domain renew command given a full UTC date-time, as returned in exDate. The date must not shift with the local timezone. + /// + [TestMethod] + [TestCategory("LocalCommand")] + [DeploymentItem("TestData/DomainRenewCommand1.xml")] + public void TestDomainRenewCommandWithUtcDateTime() + { + string expected = File.ReadAllText("DomainRenewCommand1.xml"); + + var command = new DomainRenew("example.com", "2000-04-03T22:00:00.0Z", new DomainPeriod(5, "y")); + command.TransactionId = "ABC-12345"; + Assert.AreEqual(expected, command.ToXml().InnerXml); + } + /// /// Domain renew response, example RFC5731 /// @@ -1080,7 +1095,12 @@ public void TestDomainRenewCommand1() [DeploymentItem("TestData/DomainRenewResponse1.xml")] public void TestDomainRenewResponse1() { - Assert.Inconclusive("Not implemented"); + var response = new DomainRenewResponse(File.ReadAllBytes("DomainRenewResponse1.xml")); + + Assert.AreEqual("1000", response.Code); + Assert.IsNotNull(response.ExDate); + Assert.AreEqual(new DateTime(2005, 4, 3, 22, 0, 0, DateTimeKind.Utc), response.ExDate.Value); + Assert.AreEqual(DateTimeKind.Utc, response.ExDate.Value.Kind); } #endregion diff --git a/EppLib.UnitTests/NominetExtensionLocalTest.cs b/EppLib.UnitTests/NominetExtensionLocalTest.cs index 986f1de..b5be7dc 100644 --- a/EppLib.UnitTests/NominetExtensionLocalTest.cs +++ b/EppLib.UnitTests/NominetExtensionLocalTest.cs @@ -65,9 +65,11 @@ public void TestNominetContactInfoResponse1() Assert.AreEqual("invalid", response.DataQuality.Status); Assert.AreEqual("Incorrect Address", response.DataQuality.Reason); Assert.IsTrue(response.DataQuality.DateCommenced.HasValue); - Assert.AreEqual(new DateTime(2015,5,7,13,20,4).ToString(), response.DataQuality.DateCommenced.Value.ToString()); + Assert.AreEqual(new DateTime(2015, 5, 7, 13, 20, 4, DateTimeKind.Utc), response.DataQuality.DateCommenced.Value); + Assert.AreEqual(DateTimeKind.Utc, response.DataQuality.DateCommenced.Value.Kind); Assert.IsTrue(response.DataQuality.DateToSuspend.HasValue); - Assert.AreEqual(new DateTime(2015, 6, 6, 13, 20, 4).ToString(), response.DataQuality.DateToSuspend.Value.ToString()); + Assert.AreEqual(new DateTime(2015, 6, 6, 13, 20, 4, DateTimeKind.Utc), response.DataQuality.DateToSuspend.Value); + Assert.AreEqual(DateTimeKind.Utc, response.DataQuality.DateToSuspend.Value.Kind); Assert.IsTrue(response.DataQuality.LockApplied.HasValue); Assert.IsTrue(response.DataQuality.LockApplied.Value); Assert.IsNotNull(response.DataQuality.DomainList); @@ -314,7 +316,8 @@ public void TestAbuseNotification() Assert.AreEqual("www.youve.been.phished.co.uk", notification.HostName); Assert.AreEqual("http://www.youve.been.phished.co.uk/give/us/your/money.htm", notification.Url); Assert.IsNotNull(notification.Date); - Assert.AreEqual(new DateTime(2011, 3, 1, 11, 44, 1), notification.Date.Value); + Assert.AreEqual(new DateTime(2011, 3, 1, 11, 44, 1, DateTimeKind.Utc), notification.Date.Value); + Assert.AreEqual(DateTimeKind.Utc, notification.Date.Value.Kind); Assert.AreEqual("213.135.134.24", notification.Ip); Assert.AreEqual("ns0.crooked.dealings.net", notification.Nameserver); Assert.AreEqual("hostmaster@crooked.dealings.net", notification.DnsAdmin); @@ -350,7 +353,8 @@ public void TestDomainsSuspendedNotification() Assert.AreEqual("Data Quality", notification.SuspendedReason); Assert.IsNotNull(notification.CancelDate); - Assert.AreEqual(new DateTime(2009, 12, 12, 0, 0, 13, DateTimeKind.Utc), notification.CancelDate.Value.ToUniversalTime()); + Assert.AreEqual(new DateTime(2009, 12, 12, 0, 0, 13, DateTimeKind.Utc), notification.CancelDate.Value); + Assert.AreEqual(DateTimeKind.Utc, notification.CancelDate.Value.Kind); Assert.IsNotNull(notification.SuspendedDomains); Assert.AreEqual(2, notification.SuspendedDomains.Count); Assert.AreEqual("epp-example1.co.uk", notification.SuspendedDomains.First()); diff --git a/EppLib/Entities/Domain/DomainRenew.cs b/EppLib/Entities/Domain/DomainRenew.cs index 57e544e..a2ca87e 100755 --- a/EppLib/Entities/Domain/DomainRenew.cs +++ b/EppLib/Entities/Domain/DomainRenew.cs @@ -35,7 +35,7 @@ protected override XmlElement BuildCommandElement(XmlDocument doc, XmlElement co var domainRenew = BuildCommandElement(doc, "renew", commandRootElement); AddXmlElement(doc, domainRenew, "domain:name", DomainName, namespaceUri); - AddXmlElement(doc, domainRenew, "domain:curExpDate", DateTime.Parse(CurrentExpirationDate, CultureInfo.InvariantCulture).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), namespaceUri); + AddXmlElement(doc, domainRenew, "domain:curExpDate", EppDateTime.Parse(CurrentExpirationDate).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), namespaceUri); if (m_period != null) { diff --git a/EppLib/Entities/Domain/DomainRenewResponse.cs b/EppLib/Entities/Domain/DomainRenewResponse.cs index 09f0331..32b9852 100755 --- a/EppLib/Entities/Domain/DomainRenewResponse.cs +++ b/EppLib/Entities/Domain/DomainRenewResponse.cs @@ -41,7 +41,7 @@ protected override void ProcessDataNode(XmlDocument doc, XmlNamespaceManager nam if (exDateNode != null) { DateTime exDate; - if (DateTime.TryParse(exDateNode.InnerText, out exDate)) + if (EppDateTime.TryParse(exDateNode.InnerText, out exDate)) { this._exDate = exDate; } diff --git a/EppLib/EppDateTime.cs b/EppLib/EppDateTime.cs new file mode 100644 index 0000000..0bdc8c9 --- /dev/null +++ b/EppLib/EppDateTime.cs @@ -0,0 +1,38 @@ +// Copyright 2012 Code Maker Inc. (http://codemaker.net) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.Globalization; + +namespace EppLib +{ + /// + /// Parses EPP date-time values. RFC 5730-5733 require every date-time to be UTC, so values + /// are returned as UTC (DateTimeKind.Utc). Values with an offset are converted to UTC, and + /// values without a zone designator, which some registries send, are taken to be UTC. + /// + internal static class EppDateTime + { + private const DateTimeStyles Styles = DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; + + public static bool TryParse(string value, out DateTime result) + { + return DateTime.TryParse(value, CultureInfo.InvariantCulture, Styles, out result); + } + + public static DateTime Parse(string value) + { + return DateTime.Parse(value, CultureInfo.InvariantCulture, Styles); + } + } +} diff --git a/EppLib/EppLib.csproj b/EppLib/EppLib.csproj index e4f40cb..186de71 100644 --- a/EppLib/EppLib.csproj +++ b/EppLib/EppLib.csproj @@ -4,7 +4,7 @@ netstandard2.0;net10.0 EppLib EppLib - 1.5.0 + 1.6.0 Code Maker Inc. and Contributors EppLib.NET provides a library that makes easy for registrars to interact with registries implementing the Extensible Provisioning Protocol (EPP). Includes extensions for CIRA (.CA), Nominet (.UK), IIS (.SE), Verisign Namestore, LaunchPhase and SecDNS. epp;registrar;registry;domain;rfc5730 @@ -13,7 +13,7 @@ https://github.com/CodeMakerInc/EppLib.NET.git git README.md - Breaking: TcpTransport now validates the registry's server certificate (trusted chain and matching host name) instead of accepting any certificate. For test environments with self-signed certificates, set TcpTransport.ServerCertificateValidationCallback. + Breaking: parsed EPP dates (DomainRenewResponse.ExDate and the Nominet dates) are now returned in UTC (DateTimeKind.Utc) as RFC 5730-5733 require, instead of being converted to the machine's local time. Values without a zone designator are taken as UTC. Fixes DomainRenew sending the wrong curExpDate east of UTC when given a full date-time, and a culture-dependent Smallregistry birthDate. true false false @@ -179,6 +179,7 @@ + diff --git a/EppLib/Extensions/Nominet/ContactInfo/NominetContactInfoResponse.cs b/EppLib/Extensions/Nominet/ContactInfo/NominetContactInfoResponse.cs index b85752a..b175a48 100644 --- a/EppLib/Extensions/Nominet/ContactInfo/NominetContactInfoResponse.cs +++ b/EppLib/Extensions/Nominet/ContactInfo/NominetContactInfoResponse.cs @@ -94,7 +94,7 @@ protected override void ProcessExtensionNode(XmlDocument doc, XmlNamespaceManage if (dateCommenced != null) { DateTime date; - if (DateTime.TryParse(dateCommenced.InnerText, out date)) + if (EppDateTime.TryParse(dateCommenced.InnerText, out date)) { _nomDataQuality.DateCommenced = date; } @@ -103,7 +103,7 @@ protected override void ProcessExtensionNode(XmlDocument doc, XmlNamespaceManage if (dateToSuspend != null) { DateTime date; - if (DateTime.TryParse(dateToSuspend.InnerText, out date)) + if (EppDateTime.TryParse(dateToSuspend.InnerText, out date)) { _nomDataQuality.DateToSuspend = date; } diff --git a/EppLib/Extensions/Nominet/Notifications/AbuseNotification.cs b/EppLib/Extensions/Nominet/Notifications/AbuseNotification.cs index 71c115d..da3a378 100644 --- a/EppLib/Extensions/Nominet/Notifications/AbuseNotification.cs +++ b/EppLib/Extensions/Nominet/Notifications/AbuseNotification.cs @@ -57,7 +57,7 @@ protected override void ProcessDataNode(XmlDocument doc, XmlNamespaceManager nam if (dateNode != null) { DateTime parsedDate; - if (DateTime.TryParse(dateNode.InnerText, out parsedDate)) + if (EppDateTime.TryParse(dateNode.InnerText, out parsedDate)) { Date = parsedDate; } diff --git a/EppLib/Extensions/Nominet/Notifications/DomainsSuspendedNotification.cs b/EppLib/Extensions/Nominet/Notifications/DomainsSuspendedNotification.cs index 1b04779..f425109 100644 --- a/EppLib/Extensions/Nominet/Notifications/DomainsSuspendedNotification.cs +++ b/EppLib/Extensions/Nominet/Notifications/DomainsSuspendedNotification.cs @@ -32,7 +32,7 @@ protected override void ProcessDataNode(XmlDocument doc, XmlNamespaceManager nam if (cancelDateNode != null) { DateTime parsedDate; - if (DateTime.TryParse(cancelDateNode.InnerText, out parsedDate)) + if (EppDateTime.TryParse(cancelDateNode.InnerText, out parsedDate)) { CancelDate = parsedDate; } diff --git a/EppLib/Extensions/Smallregistry/ContactCreate/SmallregistryContactPPCreateExtension.cs b/EppLib/Extensions/Smallregistry/ContactCreate/SmallregistryContactPPCreateExtension.cs index 990090e..1a225a6 100644 --- a/EppLib/Extensions/Smallregistry/ContactCreate/SmallregistryContactPPCreateExtension.cs +++ b/EppLib/Extensions/Smallregistry/ContactCreate/SmallregistryContactPPCreateExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Xml; namespace EppLib.Extensions.Smallregistry @@ -19,7 +20,7 @@ public override XmlNode ToXml(XmlDocument doc) if (BirthDate != null) { - AddXmlElement(doc, person, "birthDate", BirthDate.ToString("yyyy-MM-dd")); + AddXmlElement(doc, person, "birthDate", BirthDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } if (BirthPlace != null)