From f52e54d69ca1b62c9c855f360881ca8e4886ed1a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 13 Aug 2022 01:39:24 +0200 Subject: [PATCH 1/5] 2 small fixes --- libquickjs-sys/embed/quickjs/quickjs.c | 20 ++++++++++++-------- src/tests.rs | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index a3325b3..8ff9f4e 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -48322,7 +48322,7 @@ static void string_skip_spaces_and_comments(JSString *sp, int *pp) { int nxt = *pp + 1; // interpret - before a number as a sign rather than a comment char - if (ch == '-' && nxt < sp->len && is_digit(string_get(sp, nxt))) { + if (ch == '-' && nesting == 0 && nxt < sp->len && is_digit(string_get(sp, nxt))) { break; } if (!is_space_like(ch)) { @@ -48496,6 +48496,8 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, sp = JS_VALUE_GET_STRING(s); p = 0; + string_skip_spaces_and_comments(sp, &p); + if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) { /* ISO format */ /* year field can be negative */ @@ -48579,7 +48581,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (p - word_start >= 3) { month = find_abbrev(sp, word_start, month_names, 12); } - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); word_start = p; } else { p++; @@ -48593,7 +48595,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, month = find_abbrev(sp, word_start, month_names, 12); } - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); if (sp->len <= p) goto done; @@ -48640,7 +48642,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (string_get(sp, p) == '-') { p++; } - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); if (string_get(sp, p) == ',') { p++; @@ -48669,6 +48671,8 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, } } + string_skip_spaces_and_comments(sp, &p); + if (year <= 0) { if (string_get_digits(sp, &p, &year)) goto done; @@ -48689,7 +48693,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, year = -1; } else if (is_space_like(string_get(sp, p))) { p++; - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); } else { goto done; } @@ -48736,7 +48740,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, goto done; } - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); if (string_eq_ignorecase(sp, p, "AM", 2)) { if (hour > 12) { @@ -48746,7 +48750,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, hour = 0; } p += 2; - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); } else if (string_eq_ignorecase(sp, p, "PM", 2)) { if (hour > 12) { goto done; @@ -48755,7 +48759,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, hour += 12; } p += 2; - string_skip_spaces_and_comments(sp, &p); // and comments + string_skip_spaces_and_comments(sp, &p); } } } diff --git a/src/tests.rs b/src/tests.rs index a6a4773..8908421 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -628,7 +628,21 @@ fn test_global_setter() { #[cfg(feature = "chrono")] #[test] -fn test_ut() { +fn test_date_manual() { + let case = " ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT "; + + let ctx = Context::new().unwrap(); + let res = ctx + // ' Sat, 01 Jan 2000 08:00:00 UT ' + .eval(&format!(r#"new Date("{}")"#, case)) + .unwrap(); + + dbg!(res); +} + +#[cfg(feature = "chrono")] +#[test] +fn test_date_ut() { let test_cases_ut = vec![ "Sat, 01-Jan-2000 08:00:00 UT", "Sat, 01 Jan 2000 08:00:00 UT", From ad17c000b6664c6f9ef2ecedbcaade19fc41c1eb Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 13 Aug 2022 10:11:06 +0200 Subject: [PATCH 2/5] fix y2k issue --- libquickjs-sys/embed/quickjs/quickjs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index 8ff9f4e..e7c71b0 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -48604,7 +48604,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (string_get_digits(sp, &p, &day)) goto done; - int64_t year = 0; + int64_t year = -1; if (day > 31) { if (string_get(sp, p) != '/') goto done; @@ -48673,10 +48673,10 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, string_skip_spaces_and_comments(sp, &p); - if (year <= 0) { + if (year < 0) { if (string_get_digits(sp, &p, &year)) goto done; - if (year <= 0) { + if (year < 0) { goto done; } } From 333283c377422f62a5e72605814c34b74f0f875d Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 13 Aug 2022 15:02:04 +0200 Subject: [PATCH 3/5] fix more date formats, now passes all tests --- Cargo.toml | 3 + libquickjs-sys/embed/quickjs/quickjs.c | 84 ++++++++----- src/tests.rs | 167 +++++++++++++++++++++++-- 3 files changed, 218 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ba2515..812552d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ num-traits = { version = "0.2.0", optional = true } log = { version = "0.4.8", optional = true } once_cell = "1.2.0" +[dev-dependencies] +rstest = "0.15.0" + [workspace] members = [ "libquickjs-sys", diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index e7c71b0..91b34c7 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -1436,7 +1436,7 @@ static inline int is_digit(int c) { } static inline int is_space_like(char c) { - return c == ' ' || c == ',' || c == ':' || c == '-'; + return c == ' ' || c == ',' || c == ':' || c == '-' || c == '/'; } typedef struct JSClassShortDef { @@ -48339,12 +48339,10 @@ static void string_skip_spaces_and_comments(JSString *sp, int *pp) { } } -static BOOL char_eq_ignorecase(char c1, char c2) { - if (c1 == c2) return TRUE; - - if (c1 >= 'A' && c1 <= 'Z' && c2 == c1 + 32) return TRUE; - if (c1 >= 'a' && c1 <= 'z' && c2 == c1 - 32) return TRUE; - return FALSE; +static inline BOOL char_eq_ignorecase(char c1, char c2) { + if ((c1 >= 'A' && c1 <= 'Z') || (c1 >= 'a' && c1 <= 'z')) + return (c1 | 0x20) == (c2 | 0x20); + return c1 == c2; } static BOOL string_eq_ignorecase(JSString *s1, int p, const char *s2, int len) { @@ -48498,7 +48496,16 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, p = 0; string_skip_spaces_and_comments(sp, &p); - if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) { + int end_of_digits = p; + if (string_get(sp, end_of_digits) == '+' || string_get(sp, end_of_digits) == '-') { + p++; + } + while (end_of_digits < sp->len && is_digit(string_get(sp, end_of_digits))) { + end_of_digits++; + } + + if ((end_of_digits - p) > 0 && + (string_get(sp, end_of_digits) == '-' || string_get(sp, end_of_digits) == 'T')) { /* ISO format */ /* year field can be negative */ if (string_get_signed_digits(sp, &p, &fields[0])) @@ -48523,8 +48530,13 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, c = '.'; break; } - if (string_get(sp, p) != c) - break; + if (string_get(sp, p) != c) { + // 2000T08:00Z + if (i < 3 && string_get(sp, p) == 'T') { + i = 3; + } + else break; + } p++; if (i == 6) { if (string_get_milliseconds(sp, &p, &fields[i])) @@ -48633,7 +48645,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (string_get_digits(sp, &p, &day)) goto done; - if (string_get(sp, p) != '/') + if (!is_space_like(string_get(sp, p))) goto done; p++; if (sp->len <= p) @@ -48644,25 +48656,17 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, } string_skip_spaces_and_comments(sp, &p); - if (string_get(sp, p) == ',') { - p++; - } - + // Jan,2000,08:00:00 UT if (month == -1) { month = find_abbrev(sp, p, month_names, 12); if (month == -1) goto done; - while (p < sp->len && string_get(sp, p) != '-' && string_get(sp, p) != ' ') { + while (p < sp->len && !is_space_like(string_get(sp, p))) { p++; } if (sp->len <= p) goto done; - - // '-99 23:12:40 GMT' - if (string_get(sp, p) != '-' && string_get(sp, p) != '/' && string_get(sp, p) != ' ') { - goto done; - } p++; } @@ -48674,10 +48678,11 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, string_skip_spaces_and_comments(sp, &p); if (year < 0) { - if (string_get_digits(sp, &p, &year)) - goto done; - if (year < 0) { - goto done; + // Year following, e.g. 01 Jan 2000 08:00:00 UT + // Time following, e.g. Jan 01 08:00:00 UT 2000 + if (sp->len <= p + 2 || string_get(sp, p + 2) != ':') { + if (string_get_digits(sp, &p, &year)) + goto done; } } @@ -48694,8 +48699,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, } else if (is_space_like(string_get(sp, p))) { p++; string_skip_spaces_and_comments(sp, &p); - } else { - goto done; } // Read a number? If not, this might be a timezone name. @@ -48769,7 +48772,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (sp->len > p) { if (string_get(sp, p) == '+' || string_get(sp, p) == '-') { int64_t o; - if (string_get_digits(sp, &p, &o)) + if (string_get_signed_digits(sp, &p, &o)) goto done; if (o < -9959 || o > 9959) { @@ -48795,6 +48798,31 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, tz = known_zones[i].tzOffset; p += strlen(known_zones[i].tzName); is_local = FALSE; + + // TZ offset (GMT+0) + if (string_get(sp, p) == '+' || string_get(sp, p) == '-') { + int64_t o; + if (string_get_signed_digits(sp, &p, &o)) + goto done; + + if (o < -9959 || o > 9959) { + goto done; + } + + int sgn = (o < 0) ? -1 : 1; + o = abs((int32_t) o); + + if (string_get(sp, p) != ':') { + tz += ((o / 100) * 60 + (o % 100)) * sgn; + } else { + p++; + int64_t o2; + if (string_get_digits(sp, &p, &o2)) + goto done; + tz += (o * 60 + o2) * sgn; + } + } + break; } } diff --git a/src/tests.rs b/src/tests.rs index 8908421..65a27ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,6 +3,8 @@ use std::{collections::HashMap, convert::TryInto}; #[cfg(feature = "chrono")] use chrono::{Date, DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; +use rstest::rstest; + use super::*; // #[test] @@ -629,17 +631,168 @@ fn test_global_setter() { #[cfg(feature = "chrono")] #[test] fn test_date_manual() { - let case = " ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT "; - + let case = "2000-01-01TZ"; + let ctx = Context::new().unwrap(); let res = ctx - // ' Sat, 01 Jan 2000 08:00:00 UT ' + // ' Sat, 01 Jan 2000 08:00:00 UT ' .eval(&format!(r#"new Date("{}")"#, case)) .unwrap(); dbg!(res); } +#[cfg(feature = "chrono")] +#[rstest] +// testCasesES5Misc +#[case("2000-01-01T08:00:00.000Z", 946713600000)] +#[case("2000-01-01T08:00:00Z", 946713600000)] +#[case("2000-01-01T08:00Z", 946713600000)] +#[case("2000-01T08:00:00.000Z", 946713600000)] +#[case("2000T08:00:00.000Z", 946713600000)] +#[case("2000T08:00Z", 946713600000)] +#[case("2000-01T00:00:00.000-08:00", 946713600000)] +#[case("2000-01T08:00:00.001Z", 946713600001)] +#[case("2000-01T08:00:00.099Z", 946713600099)] +#[case("2000-01T08:00:00.999Z", 946713600999)] +#[case("2000-01T00:00:00.001-08:00", 946713600001)] +#[case("2000-01-01T24:00Z", 946771200000)] +#[case("2000-01-01T24:00:00Z", 946771200000)] +#[case("2000-01-01T24:00:00.000Z", 946771200000)] +fn test_date_iso(#[case] input: &str, #[case] expect: i64) { + let ctx = Context::new().unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, input)) + .unwrap(); + + let n: f64 = res.try_into().unwrap(); + assert_eq!(n, expect as f64); +} + +#[cfg(feature = "chrono")] +#[rstest] +#[case("Sat, 01-Jan-2000 08:00:00 GMT", 946713600000)] +#[case("Sat, 01-Jan-2000 08:00:00 GMT+0", 946713600000)] +#[case("Sat, 01-Jan-2000 08:00:00 GMT+00", 946713600000)] +#[case("Sat, 01-Jan-2000 08:00:00 GMT+000", 946713600000)] +#[case("Sat, 01-Jan-2000 08:00:00 GMT+0000", 946713600000)] +#[case("Sat, 01-Jan-2000 08:00:00 GMT+00:00", 946713600000)] +#[case("Sat, 01 Jan 2000 08:00:00 GMT", 946713600000)] +#[case("Saturday, 01-Jan-00 08:00:00 GMT", 946713600000)] +#[case("01 Jan 00 08:00 -0000", 946713600000)] +#[case("01 Jan 00 08:00 +0000", 946713600000)] +fn test_date_gmt(#[case] input: &str, #[case] expect: i64) { + let ctx = Context::new().unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, input)) + .unwrap(); + + let n: f64 = res.try_into().unwrap(); + assert_eq!(n, expect as f64); +} + +#[cfg(feature = "chrono")] +#[rstest] +// EST (-5:00) +#[case("Sat, 01-Jan-2000 03:00:00 UTC-0500", 946713600000)] +#[case("Sat, 01-Jan-2000 03:00:00 UTC-05:00", 946713600000)] +#[case("Sat, 01-Jan-2000 03:00:00 EST", 946713600000)] +#[case("Sat, 01 Jan 2000 03:00:00 EST", 946713600000)] +#[case("Saturday, 01-Jan-00 03:00:00 EST", 946713600000)] +#[case("01 Jan 00 03:00 -0500", 946713600000)] +// EDT (-4:00) +#[case("Sat, 01-Jan-2000 04:00:00 EDT", 946713600000)] +#[case("Sat, 01 Jan 2000 04:00:00 EDT", 946713600000)] +#[case("Saturday, 01-Jan-00 04:00:00 EDT", 946713600000)] +#[case("01 Jan 00 04:00 -0400", 946713600000)] +// CST (-6:00) +#[case("Sat, 01-Jan-2000 02:00:00 CST", 946713600000)] +#[case("Sat, 01 Jan 2000 02:00:00 CST", 946713600000)] +#[case("Saturday, 01-Jan-00 02:00:00 CST", 946713600000)] +#[case("01 Jan 00 02:00 -0600", 946713600000)] +// CDT (-5:00) +#[case("Sat, 01-Jan-2000 03:00:00 CDT", 946713600000)] +#[case("Sat, 01 Jan 2000 03:00:00 CDT", 946713600000)] +#[case("Saturday, 01-Jan-00 03:00:00 CDT", 946713600000)] +#[case("01 Jan 00 03:00 -0500", 946713600000)] +// MST (-7:00) +#[case("Sat, 01-Jan-2000 01:00:00 MST", 946713600000)] +#[case("Sat, 01 Jan 2000 01:00:00 MST", 946713600000)] +#[case("Saturday, 01-Jan-00 01:00:00 MST", 946713600000)] +#[case("01 Jan 00 01:00 -0700", 946713600000)] +// MDT (-6:00) +#[case("Sat, 01-Jan-2000 02:00:00 MDT", 946713600000)] +#[case("Sat, 01 Jan 2000 02:00:00 MDT", 946713600000)] +#[case("Saturday, 01-Jan-00 02:00:00 MDT", 946713600000)] +#[case("01 Jan 00 02:00 -0600", 946713600000)] +// PST (-8:00) +#[case("Sat, 01-Jan-2000 00:00:00 PST", 946713600000)] +#[case("Sat, 01 Jan 2000 00:00:00 PST", 946713600000)] +#[case("Saturday, 01-Jan-00 00:00:00 PST", 946713600000)] +#[case("01 Jan 00 00:00 -0800", 946713600000)] +#[case("Sat, 01-Jan-2000 PST", 946713600000)] +// PDT (-7:00) +#[case("Sat, 01-Jan-2000 01:00:00 PDT", 946713600000)] +#[case("Sat, 01 Jan 2000 01:00:00 PDT", 946713600000)] +#[case("Saturday, 01-Jan-00 01:00:00 PDT", 946713600000)] +#[case("01 Jan 00 01:00 -0700", 946713600000)] +fn test_date_tz(#[case] input: &str, #[case] expect: i64) { + let ctx = Context::new().unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, input)) + .unwrap(); + + let n: f64 = res.try_into().unwrap(); + assert_eq!(n, expect as f64); +} + +#[cfg(feature = "chrono")] +#[rstest] +// Special handling for years in the [0, 100) range. +#[case("Sat, 01 Jan 0 08:00:00 UT", 946713600000)] // year 2000 +#[case("Sat, 01 Jan 49 08:00:00 UT", 2493100800000)] // year 2049 +#[case("Sat, 01 Jan 50 08:00:00 UT", -631123200000)] // year 1950 +#[case("Sat, 01 Jan 99 08:00:00 UT", 915177600000)] // year 1999 +#[case("Sat, 01 Jan 100 08:00:00 UT", -59011430400000)] // year 100 +// Test PM after time. +#[case("Sat, 01-Jan-2000 08:00 PM UT", 946756800000)] +#[case("Sat, 01 Jan 2000 08:00 PM UT", 946756800000)] +#[case("Jan 01 2000 08:00 PM UT", 946756800000)] +#[case("Jan 01 08:00 PM UT 2000", 946756800000)] +#[case("Saturday, 01-Jan-00 08:00 PM UT", 946756800000)] +#[case("01 Jan 00 08:00 PM +0000", 946756800000)] +fn test_date_special(#[case] input: &str, #[case] expect: i64) { + let ctx = Context::new().unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, input)) + .unwrap(); + + let n: f64 = res.try_into().unwrap(); + assert_eq!(n, expect as f64); +} + +/* +#[rstest] +#[case("2000-01-01TZ")] +#[case("2000-01-01T60Z")] +#[case("2000-01-01T60:60Z")] +#[case("2000-01-0108:00Z")] +#[case("2000-01-01T08Z")] +#[case("2000-01-01T24:01")] +#[case("2000-01-01T24:00:01")] +#[case("2000-01-01T24:00:00.001")] +#[case("2000-01-01T24:00:00.999Z")] +fn test_date_invalid(#[case] input: &str) { + let ctx = Context::new().unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, input)) + .unwrap(); + + let n: f64 = res.try_into().unwrap(); + assert!(n.is_nan(), "got: {}", n); +} +*/ + #[cfg(feature = "chrono")] #[test] fn test_date_ut() { @@ -722,13 +875,11 @@ fn test_date_ut() { let mut passed = 0; let mut failed = 0; - + for case in test_cases_ut { let ctx = Context::new().unwrap(); - let res = ctx - .eval(&format!(r#"new Date("{}")"#, case)) - .unwrap(); - + let res = ctx.eval(&format!(r#"new Date("{}")"#, case)).unwrap(); + if res == expected { passed += 1; } else { From 749be29936abc5c9d1d1dedf863a70d7bafaa795 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 13 Aug 2022 18:07:09 +0200 Subject: [PATCH 4/5] added date validation --- libquickjs-sys/embed/quickjs/quickjs.c | 105 ++++++++++++------------- src/tests.rs | 66 ++++++++-------- 2 files changed, 82 insertions(+), 89 deletions(-) diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index 91b34c7..68f0120 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -48440,6 +48440,34 @@ static int string_get_milliseconds(JSString *sp, int *pp, int64_t *pval) { return 0; } +static int string_get_num_timezone(JSString *sp, int *pp, int64_t *pval) +{ + int p = *pp; + + int64_t o; + if (string_get_signed_digits(sp, &p, &o)) + return -1; + + if (o < -9959 || o > 9959) { + return -1; + } + + int sgn = (o < 0) ? -1 : 1; + o = abs((int32_t) o); + + if (string_get(sp, p) != ':') { + *pval = ((o / 100) * 60 + (o % 100)) * sgn; + } else { + p++; + int64_t o2; + if (string_get_digits(sp, &p, &o2)) + return -1; + *pval = (o * 60 + o2) * sgn; + } + + *pp = p; + return 0; +} static int find_abbrev(JSString *sp, int p, const char *list, int count) { int n, i; @@ -48515,18 +48543,18 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (p >= sp->len) break; switch(i) { - case 1: - case 2: + case 1: // Year + case 2: // Month c = '-'; break; - case 3: + case 3: // Day c = 'T'; break; - case 4: - case 5: + case 4: // Hour + case 5: // Minute c = ':'; break; - case 6: + case 6: // Second c = '.'; break; } @@ -48546,6 +48574,9 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, goto done; } } + // Hour only is invalid + if (i == 4) goto done; + /* no time: UTC by default */ is_local = (i > 3); fields[1] -= 1; @@ -48669,10 +48700,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, goto done; p++; } - - if (month < 0 || month > 11) { - goto done; - } } string_skip_spaces_and_comments(sp, &p); @@ -48718,10 +48745,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (string_get_digits(sp, &p, &minute)) goto done; - - if (minute < 0 || minute > 59) { - goto done; - } // ':40 GMT' // seconds are optional in rfc822 + rfc2822 @@ -48730,10 +48753,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, if (string_get_digits(sp, &p, &second)) goto done; - - if (second < 0 || second > 59) { - goto done; - } // disallow trailing colon seconds if (string_get(sp, p) == ':') { @@ -48771,26 +48790,8 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, // Some websites omit the time zone if (sp->len > p) { if (string_get(sp, p) == '+' || string_get(sp, p) == '-') { - int64_t o; - if (string_get_signed_digits(sp, &p, &o)) + if (string_get_num_timezone(sp, &p, &tz)) goto done; - - if (o < -9959 || o > 9959) { - goto done; - } - - int sgn = (o < 0) ? -1 : 1; - o = abs((int32_t) o); - - if (string_get(sp, p) != ':') { - tz = ((o / 100) * 60 + (o % 100)) * sgn; - } else { - p++; - int64_t o2; - if (string_get_digits(sp, &p, &o2)) - goto done; - tz = (o * 60 + o2) * sgn; - } is_local = FALSE; } else { for (int i = 0; i < sizeof(known_zones) / sizeof(struct KnownZone); i++) { @@ -48802,27 +48803,11 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, // TZ offset (GMT+0) if (string_get(sp, p) == '+' || string_get(sp, p) == '-') { int64_t o; - if (string_get_signed_digits(sp, &p, &o)) + if (string_get_num_timezone(sp, &p, &o)) goto done; - if (o < -9959 || o > 9959) { - goto done; - } - - int sgn = (o < 0) ? -1 : 1; - o = abs((int32_t) o); - - if (string_get(sp, p) != ':') { - tz += ((o / 100) * 60 + (o % 100)) * sgn; - } else { - p++; - int64_t o2; - if (string_get_digits(sp, &p, &o2)) - goto done; - tz += (o * 60 + o2) * sgn; - } + tz += o; } - break; } } @@ -48860,6 +48845,16 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, fields[5] = second; } + // Validate fields + if (fields[1] < 0 || fields[1] > 11 || + fields[2] < 1 || fields[2] > 31 || + fields[3] < 0 || fields[3] > 24 || + fields[4] < 0 || fields[4] > 59 || + fields[5] < 0 || fields[5] > 59 || + fields[6] < 0 || fields[6] > 999 || + fields[3] * 3600 * 1000 + fields[4] * 60000 + fields[5] * 1000 + fields[6] > 24 * 3600 * 1000 + ) goto done; + for(i = 0; i < 7; i++) fields1[i] = fields[i]; d = set_date_fields(fields1, is_local) - (tz * 60000); diff --git a/src/tests.rs b/src/tests.rs index 65a27ff..87d2dae 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,8 +1,5 @@ use std::{collections::HashMap, convert::TryInto}; -#[cfg(feature = "chrono")] -use chrono::{Date, DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; - use rstest::rstest; use super::*; @@ -628,21 +625,9 @@ fn test_global_setter() { ctx.eval("a + 1").unwrap(); } -#[cfg(feature = "chrono")] -#[test] -fn test_date_manual() { - let case = "2000-01-01TZ"; +// Test suite was taken from V8 +// Source: https://github.com/v8/v8/blob/9433ad119aadfe10d60935029195c31f25ab8624/test/mjsunit/date-parse.js - let ctx = Context::new().unwrap(); - let res = ctx - // ' Sat, 01 Jan 2000 08:00:00 UT ' - .eval(&format!(r#"new Date("{}")"#, case)) - .unwrap(); - - dbg!(res); -} - -#[cfg(feature = "chrono")] #[rstest] // testCasesES5Misc #[case("2000-01-01T08:00:00.000Z", 946713600000)] @@ -669,7 +654,6 @@ fn test_date_iso(#[case] input: &str, #[case] expect: i64) { assert_eq!(n, expect as f64); } -#[cfg(feature = "chrono")] #[rstest] #[case("Sat, 01-Jan-2000 08:00:00 GMT", 946713600000)] #[case("Sat, 01-Jan-2000 08:00:00 GMT+0", 946713600000)] @@ -691,7 +675,6 @@ fn test_date_gmt(#[case] input: &str, #[case] expect: i64) { assert_eq!(n, expect as f64); } -#[cfg(feature = "chrono")] #[rstest] // EST (-5:00) #[case("Sat, 01-Jan-2000 03:00:00 UTC-0500", 946713600000)] @@ -746,7 +729,6 @@ fn test_date_tz(#[case] input: &str, #[case] expect: i64) { assert_eq!(n, expect as f64); } -#[cfg(feature = "chrono")] #[rstest] // Special handling for years in the [0, 100) range. #[case("Sat, 01 Jan 0 08:00:00 UT", 946713600000)] // year 2000 @@ -771,7 +753,6 @@ fn test_date_special(#[case] input: &str, #[case] expect: i64) { assert_eq!(n, expect as f64); } -/* #[rstest] #[case("2000-01-01TZ")] #[case("2000-01-01T60Z")] @@ -782,6 +763,12 @@ fn test_date_special(#[case] input: &str, #[case] expect: i64) { #[case("2000-01-01T24:00:01")] #[case("2000-01-01T24:00:00.001")] #[case("2000-01-01T24:00:00.999Z")] +#[case("May 25 2008 1:30 (PM)) UTC")] +#[case("May 25 2008 1:30( )AM (PM)")] +#[case("a1")] +#[case("nasfdjklsfjoaifg1")] +#[case("x_2")] +#[case("May 25 2008 AAA (GMT)")] fn test_date_invalid(#[case] input: &str) { let ctx = Context::new().unwrap(); let res = ctx @@ -791,9 +778,7 @@ fn test_date_invalid(#[case] input: &str) { let n: f64 = res.try_into().unwrap(); assert!(n.is_nan(), "got: {}", n); } -*/ -#[cfg(feature = "chrono")] #[test] fn test_date_ut() { let test_cases_ut = vec![ @@ -865,25 +850,18 @@ fn test_date_ut() { " 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) ", ]; - let expected = JsValue::Date(DateTime::from_utc( - NaiveDateTime::new( - NaiveDate::from_ymd(2000, 1, 1), - NaiveTime::from_hms(8, 0, 0), - ), - Utc, - )); - let mut passed = 0; let mut failed = 0; for case in test_cases_ut { let ctx = Context::new().unwrap(); - let res = ctx.eval(&format!(r#"new Date("{}")"#, case)).unwrap(); + let res = ctx.eval(&format!(r#"new Date("{}").getTime()"#, case)).unwrap(); + let n: f64 = res.try_into().unwrap(); - if res == expected { + if n == 946713600000.0 { passed += 1; } else { - println!("FAIL: `{}` - {:?}", case, res); + println!("FAIL: `{}` - {}", case, n); failed += 1; } } @@ -891,3 +869,23 @@ fn test_date_ut() { println!("{}/{} Passed", passed, passed + failed); assert_eq!(failed, 0); } + +#[test] +// Test if the JS interpreter can parse its own date format +// (Dates from 1970 to ~2070 with 150h steps.) +fn test_date_selfparse() { + let ctx = Context::new().unwrap(); + let res = ctx.eval(r#" + test = () => { + for (var i = 0; i < 24 * 365 * 100; i += 150) { + var ms = i * (3600 * 1000); + var s = (new Date(ms)).toString(); + if(ms != Date.parse(s)) return false; + } + return true; + } + test(); + "#).unwrap(); + let res: bool = res.try_into().unwrap(); + assert!(res); +} From 6eeb0f70b9d70a3728baa7d4bbdc3fe81d520206 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 3 Oct 2022 00:37:26 +0200 Subject: [PATCH 5/5] fix: cargo warnings --- src/lib.rs | 1 + src/tests.rs | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aeaeab5..9c5bb82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ //! "#).unwrap(); //! ``` +#![allow(dead_code)] #![deny(missing_docs)] mod bindings; diff --git a/src/tests.rs b/src/tests.rs index 87d2dae..b35900b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -855,7 +855,9 @@ fn test_date_ut() { for case in test_cases_ut { let ctx = Context::new().unwrap(); - let res = ctx.eval(&format!(r#"new Date("{}").getTime()"#, case)).unwrap(); + let res = ctx + .eval(&format!(r#"new Date("{}").getTime()"#, case)) + .unwrap(); let n: f64 = res.try_into().unwrap(); if n == 946713600000.0 { @@ -875,7 +877,9 @@ fn test_date_ut() { // (Dates from 1970 to ~2070 with 150h steps.) fn test_date_selfparse() { let ctx = Context::new().unwrap(); - let res = ctx.eval(r#" + let res = ctx + .eval( + r#" test = () => { for (var i = 0; i < 24 * 365 * 100; i += 150) { var ms = i * (3600 * 1000); @@ -885,7 +889,9 @@ fn test_date_selfparse() { return true; } test(); - "#).unwrap(); - let res: bool = res.try_into().unwrap(); - assert!(res); + "#, + ) + .unwrap(); + let res: bool = res.try_into().unwrap(); + assert!(res); }