Skip to content

Avoid private time parsing APIs - #259

Closed
joshka wants to merge 3 commits into
rwf2:masterfrom
joshka:joshka/avoid-private-time-parsing
Closed

Avoid private time parsing APIs#259
joshka wants to merge 3 commits into
rwf2:masterfrom
joshka:joshka/avoid-private-time-parsing

Conversation

@joshka

@joshka joshka commented Jul 15, 2026

Copy link
Copy Markdown

Use documented time parsing entry points for cookie expiration dates so the crate does not rely on internal parsed-field methods from the time crate.

This replaces #256 with an approach that tests, documents and uses the public method instead of the private one.

Comment thread src/parse.rs Outdated
Comment on lines +44 to +45
pub static FMT2: StaticFormatDescription = format_description!(version = 2,
"[optional [[weekday], ]][day]-[month repr:short]-[year repr:last_two] [hour]:[minute]:[second] GMT"
"[optional [[weekday], ]][day]-[month repr:short]-[year padding:none] [hour]:[minute]:[second] GMT"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the format specified in https://datatracker.ietf.org/doc/html/rfc2616#section-3.3.1. If we are going to deviate, we must only do so to maintain compatibility with the ecosystem at large. This would mean presenting evidence (via code) from some such project such as a popular web browser or web framework.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two options to avoid this, as I believe it was only done to allow parsing to succeed. One is to increase the version of time (and probably your MSRV as well) and use parse_with_defaults. The other is to do it a bit more manually, constructing Parsed with the "year century" as zero before parsing with Parsed::parse_item and calling try_into to create the PrimitiveDateTime from the now-mutated Parsed struct.

If you go this way, you can also map the year before converting to the final type, though there is likely no difference in performance between that and using replace_year.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the intended behavior? I see that we get an InsufficientInformation for two digit years now, but isn't this what the format description is saying is acceptable?

I see that the docs say:

Users can choose between three representations: the full year (the default), the century and the last two digits of the year. This should be relatively straightforward. Note that when parsing, if only the century or last two digits of the year are present, the value returned may not be what was expected — if the return is successful at all (it’s not guaranteed).

Which is...surprisingly vague.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been working on a specification for increased clarity, but it's not yet public. The insufficient information is because only the last two digits are known, not the century. That's where the default needs to be specified somewhere.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially there are two phases: textually parsing and then converting into the final type. With only the last two digits, the second phase fails.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by keeping FMT2 as [year repr:last_two]. This PR no longer broadens the accepted long-weekday dash format to full years, so it does not need browser/framework evidence for that behavior change.

I also added a characterization test showing that Sunday, 06-Nov-1994 08:49:37 GMT remains unsupported.

Comment thread src/parse.rs Outdated
};

Ok(PrimitiveDateTime::try_from(date)?.assume_utc())
Ok(date_time.replace_year(year)?.assume_utc())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doe this always result in the same behavior as the code had prior?

@joshka joshka Jul 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is now covered more directly. The first commit is now only characterization tests and passes before the implementation change. The implementation commit then passes the same tests.

The year cases cover the RFC 6265 cutoff edges (00/01/68/69/70/71/99), full-year guard cases, and the cutoff behavior across each supported cookie-rs date format.

@SergioBenitez

Copy link
Copy Markdown
Member

Out of curiosity, was this AI assisted? The documentation feels excessive, and the added tests are not idiomatic to the present style. Otherwise, seems okay, except for the 1) definite and 2) possible deviations in behavior, with other minor tweaks for style, and a significant reduction (and humanification) of the docs.

@jhpratt jhpratt left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without looking at the tests or checking for spec compliance (re. the change to how year is handled), I can confirm that this avoids invoking private APIs.

@joshka

joshka commented Jul 30, 2026

Copy link
Copy Markdown
Author

I think the current diff makes the tests/docs look more like churn than intended. The goal is to make this behavior-preserving, so I’m going to split the branch into:

  1. characterization tests for the existing accepted/rejected date behavior;
  2. the internal implementation change away from the private time API;
  3. focused docs explaining the parser boundary and the short-year rule.

The docs are intentionally more explicit than the previous comment because the parser is not a full RFC6265 tokenizer, while parts of the behavior are RFC6265-aligned. Without spelling that out near the code, future changes can easily “simplify” this back into either private time APIs or accidental behavior drift.

I’ll also re-check the FMT2 full-year case separately. If that is new behavior, I’ll either remove it from this PR or keep it isolated with specific rationale/evidence rather than presenting it as preservation.

Also - yes this was AI assisted with human judgment in the loop and guided intentionally to add characterization tests and significant documentation around this to clarify the rationale and edge cases here.

@jhpratt

jhpratt commented Jul 30, 2026

Copy link
Copy Markdown

future changes can easily “simplify” this back into either private time APIs

The private APIs have been locked down using technical measures, and once this usage is fixed the final one will be removed. Even now, there is a blindingly obvious deprecation warning.

@joshka
joshka force-pushed the joshka/avoid-private-time-parsing branch from 63524a9 to 0821e3e Compare July 30, 2026 02:56
@joshka
joshka force-pushed the joshka/avoid-private-time-parsing branch from 0821e3e to fbc617e Compare July 30, 2026 03:05
@SergioBenitez

Copy link
Copy Markdown
Member

Since this did not preserve MSRV, I cannot merge it. Fixed in d4472e3.

@joshka

joshka commented Aug 9, 2026

Copy link
Copy Markdown
Author

Since this did not preserve MSRV, I cannot merge it. Fixed in d4472e3.

The MSRV check https://github.com/rwf2/cookie-rs/actions/runs/30510223790/job/90861072938?pr=259 passed on this PR - is that broken?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants