runtime: System.Net.Http.Headers.DateHeaderParser in FW 4.7.2 doesn't support numerous date/time formats (UTC specifically)

In the current version, DateHeaderParser delegates to System.Net.HttpDateParser which supports the following date/time formats:

        private static readonly string[] s_dateFormats = new string[] {
            // "r", // RFC 1123, required output format but too strict for input
            "ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
            "ddd, d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC
            "ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
            "d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
            "d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week
            "d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
            "ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
            "ddd, d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, short year
            "ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
            "d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
            "d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week, short year
            "d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone

            "dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
            "dddd, d'-'MMM'-'yy H:m:s 'UTC'", // RFC 850, UTC
            "dddd, d'-'MMM'-'yy H:m:s zzz", // RFC 850, offset
            "dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
            "ddd MMM d H:m:s yyyy", // ANSI C's asctime() format

            "ddd, d MMM yyyy H:m:s zzz", // RFC 5322
            "ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
            "d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
            "d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
        };

In Framework 4.7.2 and NuGet 4.3.4, DateHeaderParser delegates to System.Net.Http.HttpRuleParser which supports these date/time formats (disassembled from System.Net.Http.dll since the 4.7.2 sources aren’t available from symbol servers):

    private static readonly string[] dateFormats = new string[15]
    {
      "ddd, d MMM yyyy H:m:s 'GMT'",
      "ddd, d MMM yyyy H:m:s",
      "d MMM yyyy H:m:s 'GMT'",
      "d MMM yyyy H:m:s",
      "ddd, d MMM yy H:m:s 'GMT'",
      "ddd, d MMM yy H:m:s",
      "d MMM yy H:m:s 'GMT'",
      "d MMM yy H:m:s",
      "dddd, d'-'MMM'-'yy H:m:s 'GMT'",
      "dddd, d'-'MMM'-'yy H:m:s",
      "ddd MMM d H:m:s yyyy",
      "ddd, d MMM yyyy H:m:s zzz",
      "ddd, d MMM yyyy H:m:s",
      "d MMM yyyy H:m:s zzz",
      "d MMM yyyy H:m:s"
    };

As a result, DateHeaderParser in 4.7.2 supports fewer date/time formats - in particular UTC formats are missing. What’s (impressively) sad is that ye olde System.Net.WebClient does handle these formats correctly!

Is this a known issue, does Microsoft care if so, and will there be a fix in Framework 4.8?

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

Threw together the below monkey patch for this issue in about 15 minutes - which is still more time than Microsoft took to test how well HttpClient parses headers. It’s ugly, it’s dirty, but it works and it solves the issue and best of all: it doesn’t require Microsoft to do anything more than the nothing they were already doing. It also works on both Framework and Core, since this fix is only in Core 3.x.

using System;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Versioning;

namespace MonkeyPatch
{
    public static class HttpClientDateHeaderParser
    {
        // taken from https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Net/HttpDateParser.cs#L11
        private static readonly string[] s_dateFormats = new string[] {
            // "r", // RFC 1123, required output format but too strict for input
            "ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
            "ddd, d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC
            "ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
            "d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
            "d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week
            "d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
            "ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
            "ddd, d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, short year
            "ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
            "d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
            "d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week, short year
            "d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone

            "dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
            "dddd, d'-'MMM'-'yy H:m:s 'UTC'", // RFC 850, UTC
            "dddd, d'-'MMM'-'yy H:m:s zzz", // RFC 850, offset
            "dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
            "ddd MMM d H:m:s yyyy", // ANSI C's asctime() format

            "ddd, d MMM yyyy H:m:s zzz", // RFC 5322
            "ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
            "d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
            "d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
        };

        public static bool DoMicrosoftsJob()
        {
            var framework = Assembly
                .GetEntryAssembly()?
                .GetCustomAttribute<TargetFrameworkAttribute>()?
                .FrameworkName;

            string dateFormatsFieldName;
            if (string.IsNullOrWhiteSpace(framework) || framework.IndexOf("framework", StringComparison.InvariantCultureIgnoreCase) != -1) dateFormatsFieldName = "dateFormats";
            else dateFormatsFieldName = "s_dateFormats";

            var httpClientType = typeof(HttpClient);
            var systemNetHttpAssembly = httpClientType.Assembly;

            var httpRuleParserType = systemNetHttpAssembly.GetType("System.Net.Http.HttpRuleParser");
            if (httpRuleParserType == null) return false;

            var dateFormatsField = httpRuleParserType.GetField(dateFormatsFieldName, BindingFlags.NonPublic | BindingFlags.Static);
            if (dateFormatsField == null) return false;

            dateFormatsField.SetValue(null, s_dateFormats);
            return true;
        }
    }
}

@IanKemp

For reference, the change that added the extra date/time formats was made in dotnet/corefx#28843 - about a year ago. This comment indicates that the change probably should be considered for backporting to the Framework - not sure if that got lost and forgotten along the way, or was decided against, or…

The change was not ported back to .NET Framework. There are no plans to do so. .NET Framework is much more sensitive to changes which could cause applications to break since it is installed on the machine and used globally by all .NET Framework applications.