From 7a2c1c747225b021892b44af90cff1c694a7b0f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Mar 2026 03:55:10 +0000 Subject: [PATCH 1/2] fix: defensive DateTime parsing and improve Range header error message - TextConversions.AsDateTime / AsDateTimeOffset: use Double.TryParse with InvariantCulture instead of Double.Parse, and guard against values that would overflow DateTime/DateTimeOffset.AddMilliseconds (e.g. a /Date(9...9)/ string with hundreds of digits now returns None instead of throwing ArgumentOutOfRangeException). - Http.fs setHeaders Range case: fix error message to show the original header value (the 'value' variable) instead of the internal bytes array representation, which printed 'System.String[]' rather than the actual invalid value. - Add tests for overflow-safe ms-Date parsing in TextConversions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 5 +++ src/FSharp.Data.Http/Http.fs | 2 +- .../TextConversions.fs | 34 ++++++++++++++----- .../FSharp.Data.Core.Tests/TextConversions.fs | 7 ++++ 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3c7b3733e..121d622b9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,10 @@ # Release Notes +## 8.1.2 - Mar 17 2026 + +- Fix `TextConversions.AsDateTime` and `TextConversions.AsDateTimeOffset` to return `None` (instead of throwing `ArgumentOutOfRangeException`) when a `/Date(...)/` value overflows the valid `DateTime`/`DateTimeOffset` range; use `InvariantCulture` explicitly when parsing the milliseconds value +- Fix error message in Range header validation: show the original header value (not the internal byte array representation) when the range format is invalid + ## 8.1.1 - Mar 14 2026 - Fix `CombinedStream` to throw `NotSupportedException` (instead of `Exception`) for unsupported operations (write, seek, set length); fixes a typo in the length error message diff --git a/src/FSharp.Data.Http/Http.fs b/src/FSharp.Data.Http/Http.fs index 52c67d53c..10056cbaf 100644 --- a/src/FSharp.Data.Http/Http.fs +++ b/src/FSharp.Data.Http/Http.fs @@ -1745,7 +1745,7 @@ module internal HttpHelpers = let bytes = value.Substring("bytes=".Length).Split('-') if bytes.Length <> 2 then - failwithf "Invalid value for the Range header (%O)" bytes + failwithf "Invalid value for the Range header (%s)" value req.AddRange(int64 bytes.[0], int64 bytes.[1]) | "proxy-authorization" -> req.Headers.[HeaderEnum.ProxyAuthorization] <- value diff --git a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs index 0226a0cce..2170532f3 100644 --- a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs +++ b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs @@ -171,10 +171,19 @@ type TextConversions private () = let matchesMS = msDateRegex.Value.Match(text.Trim()) if matchesMS.Success then - matchesMS.Groups.[1].Value - |> Double.Parse - |> DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds - |> Some + match + Double.TryParse( + matchesMS.Groups.[1].Value, + Globalization.NumberStyles.Integer, + Globalization.CultureInfo.InvariantCulture + ) + with + | true, ms when not (Double.IsInfinity ms) -> + try + DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds ms |> Some + with :? ArgumentOutOfRangeException -> + None + | _ -> None else // Parse ISO 8601 format, fixing time zone if needed match ParseISO8601FormattedDateTime text cultureInfo with @@ -208,10 +217,19 @@ type TextConversions private () = // e.g. 1231456+1000, 123123+0000, 123123-0500, etc. match offset matchesMS.Groups.[2].Value with | ValueSome ofst -> - matchesMS.Groups.[1].Value - |> Double.Parse - |> DateTimeOffset(1970, 1, 1, 0, 0, 0, ofst).AddMilliseconds - |> Some + match + Double.TryParse( + matchesMS.Groups.[1].Value, + Globalization.NumberStyles.Integer, + Globalization.CultureInfo.InvariantCulture + ) + with + | true, ms when not (Double.IsInfinity ms) -> + try + DateTimeOffset(1970, 1, 1, 0, 0, 0, ofst).AddMilliseconds ms |> Some + with :? ArgumentOutOfRangeException -> + None + | _ -> None | ValueNone -> None else match ParseISO8601FormattedDateTime text cultureInfo with diff --git a/tests/FSharp.Data.Core.Tests/TextConversions.fs b/tests/FSharp.Data.Core.Tests/TextConversions.fs index 8ae3d0041..4e34501cf 100644 --- a/tests/FSharp.Data.Core.Tests/TextConversions.fs +++ b/tests/FSharp.Data.Core.Tests/TextConversions.fs @@ -163,6 +163,10 @@ let ``DateTime conversions with various formats``() = TextConversions.AsDateTime culture "/Date(1577836800000)/" |> Option.isSome |> should equal true TextConversions.AsDateTime culture "/Date(1577836800000+0000)/" |> Option.isSome |> should equal true + // Overflow-safe: astronomically large ms-Date values should return None rather than throwing + TextConversions.AsDateTime culture ("/Date(" + System.String('9', 500) + ")/") |> should equal None + TextConversions.AsDateTime culture ("/Date(-" + System.String('9', 500) + ")/") |> should equal None + // Invalid values TextConversions.AsDateTime culture "not-a-date" |> should equal None TextConversions.AsDateTime culture "" |> should equal None @@ -190,6 +194,9 @@ let ``DateTimeOffset conversions with additional formats``() = TextConversions.AsDateTimeOffset culture "2020-01-01T12:00:00+05:30" |> Option.isSome |> should equal true TextConversions.AsDateTimeOffset culture "2020-01-01T12:00:00-08:00" |> Option.isSome |> should equal true + // Overflow-safe: astronomically large ms-Date+offset values should return None rather than throwing + TextConversions.AsDateTimeOffset culture ("/Date(" + System.String('9', 500) + "+0000)/") |> should equal None + // Invalid values TextConversions.AsDateTimeOffset culture "2020-01-01" |> should equal None TextConversions.AsDateTimeOffset culture "invalid" |> should equal None From 70dad85c0101d474f827f439da33e4249355e8d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Mar 2026 04:00:58 +0000 Subject: [PATCH 2/2] ci: trigger checks