Skip to content

%z/%Z tags the remaining input as US-ASCII, so one non-ASCII byte raises ArgumentError instead of returning nil #179

Description

@sh4d0byss

Date._strptime normally returns nil when the input does not match the format. With a format containing %z or %Z, a single byte >= 0x80 anywhere after the point the zone directive starts scanning makes it raise ArgumentError: invalid byte sequence in US-ASCII instead.

DateTime.strptime/DateTime._strptime use "%FT%T%z" as their default format, so this is reachable without the caller choosing a %z format at all.

require'date's="2011-10-05T22:26:12é"# a valid timestamp plus one non-ASCII byteDateTime._strptime(s)# default format "%FT%T%z"Date._strptime(s,"%Y-%m-%dT%H:%M:%S%z")Time.strptime(s,"%Y-%m-%dT%H:%M:%S%z")# same path via dateDate._strptime("2011-10-05T22:26:12@@@","%Y-%m-%dT%H:%M:%S%z")# control, ASCII garbage

Actual:

ArgumentError: invalid byte sequence in US-ASCII
ArgumentError: invalid byte sequence in US-ASCII
ArgumentError: invalid byte sequence in US-ASCII
nil # control behaves as documented

Expected: nil in all four cases, as for any other non-matching input.

Realistic inputs are not exotic: a trailing NBSP ("2011-10-05T22:26:12+00:00 ") or a typographic quote pasted into a form field is enough.

Cause: ext/date/date_strptime.c:600 re-tags the remainder of the caller's string as US-ASCII before matching the zone pattern:

m=f_match(pat, rb_usascii_str_new(&str[si], slen-si));

The rest of the file works on the caller's encoding, and date_core.c deliberately accepts any ASCII-compatible encoding (date_core.c:4530) and copies the caller's encoding back onto the zone and leftover values (:4557, :4561), so the US-ASCII tag here looks unintended. Since the zone pattern is ASCII-only, matching against the original encoding, or against a binary string, would give the same matches without the exception.

Note that Date::Error < ArgumentError, but this is a plain ArgumentError, so rescue Date::Error does not catch it while rescue ArgumentError does.

Tested: ruby 3.4.5 / date 3.5.1, and ruby 4.1.0dev (master) / date 3.5.1. Same result on both.

Filing this publicly as a correctness bug: it produces an unexpected exception class, not a memory-safety or resource-exhaustion problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions