diff --git a/dlls/webservices/reader.c b/dlls/webservices/reader.c index e30864d520d..d4d4dccbec6 100644 --- a/dlls/webservices/reader.c +++ b/dlls/webservices/reader.c @@ -1881,6 +1881,148 @@ static HRESULT str_to_uint64( const unsigned char *str, ULONG len, UINT64 max, U return S_OK; } +#define TICKS_PER_SEC 10000000 +#define TICKS_PER_MIN (60 * (ULONGLONG)TICKS_PER_SEC) +#define TICKS_PER_HOUR (3600 * (ULONGLONG)TICKS_PER_SEC) +#define TICKS_PER_DAY (86400 * (ULONGLONG)TICKS_PER_SEC) +#define TICKS_MAX 3155378975999999999 + +static const int month_offsets[2][12] = +{ + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} +}; + +static const int month_days[2][12] = +{ + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +static inline int is_leap_year( int year ) +{ + return !(year % 4) && (year % 100 || !(year % 400)); +} + +static inline int valid_day( int year, int month, int day ) +{ + return day > 0 && day <= month_days[is_leap_year( year )][month - 1]; +} + +static inline int leap_days_before( int year ) +{ + return (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400; +} + +static HRESULT str_to_datetime( const unsigned char *bytes, ULONG len, WS_DATETIME *ret ) +{ + const unsigned char *p = bytes, *q; + int year, month, day, hour, min, sec, sec_frac = 0, tz_hour, tz_min, tz_neg; + + while (len && read_isspace( *p )) { p++; len--; } + while (len && read_isspace( p[len - 1] )) { len--; } + + q = p; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 4 || !len || *q != '-') return WS_E_INVALID_FORMAT; + year = (p[0] - '0') * 1000 + (p[1] - '0') * 100 + (p[2] - '0') * 10 + p[3] - '0'; + if (year < 1) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len || *q != '-') return WS_E_INVALID_FORMAT; + month = (p[0] - '0') * 10 + p[1] - '0'; + if (month < 1 || month > 12) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len || *q != 'T') return WS_E_INVALID_FORMAT; + day = (p[0] - '0') * 10 + p[1] - '0'; + if (!valid_day( year, month, day )) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT; + hour = (p[0] - '0') * 10 + p[1] - '0'; + if (hour > 24) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT; + min = (p[0] - '0') * 10 + p[1] - '0'; + if (min > 59 || (min > 0 && hour == 24)) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len) return WS_E_INVALID_FORMAT; + sec = (p[0] - '0') * 10 + p[1] - '0'; + if (sec > 59 || (sec > 0 && hour == 24)) return WS_E_INVALID_FORMAT; + + if (*q == '.') + { + unsigned int i, nb_digits, mul = TICKS_PER_SEC / 10; + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + nb_digits = q - p; + if (nb_digits < 1 || nb_digits > 7) return WS_E_INVALID_FORMAT; + for (i = 0; i < nb_digits; i++) + { + sec_frac += (p[i] - '0') * mul; + mul /= 10; + } + } + if (*q == 'Z') + { + if (--len) return WS_E_INVALID_FORMAT; + tz_hour = tz_min = tz_neg = 0; + ret->format = WS_DATETIME_FORMAT_UTC; + } + else if (*q == '+' || *q == '-') + { + tz_neg = (*q == '-') ? 1 : 0; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || !len || *q != ':') return WS_E_INVALID_FORMAT; + tz_hour = (p[0] - '0') * 10 + p[1] - '0'; + if (tz_hour > 14) return WS_E_INVALID_FORMAT; + + p = ++q; len--; + while (len && isdigit( *q )) { q++; len--; }; + if (q - p != 2 || len) return WS_E_INVALID_FORMAT; + tz_min = (p[0] - '0') * 10 + p[1] - '0'; + if (tz_min > 59 || (tz_min > 0 && tz_hour == 14)) return WS_E_INVALID_FORMAT; + + ret->format = WS_DATETIME_FORMAT_LOCAL; + } + else return WS_E_INVALID_FORMAT; + + ret->ticks = ((year - 1) * 365 + leap_days_before( year )) * TICKS_PER_DAY; + ret->ticks += month_offsets[is_leap_year( year )][month - 1] * TICKS_PER_DAY; + ret->ticks += (day - 1) * TICKS_PER_DAY; + ret->ticks += hour * TICKS_PER_HOUR; + ret->ticks += min * TICKS_PER_MIN; + ret->ticks += sec * TICKS_PER_SEC; + ret->ticks += sec_frac; + + if (tz_neg) + { + if (tz_hour * TICKS_PER_HOUR + tz_min * TICKS_PER_MIN + ret->ticks > TICKS_MAX) + return WS_E_INVALID_FORMAT; + ret->ticks += tz_hour * TICKS_PER_HOUR; + ret->ticks += tz_min * TICKS_PER_MIN; + } + else + { + if (tz_hour * TICKS_PER_HOUR + tz_min * TICKS_PER_MIN > ret->ticks) + return WS_E_INVALID_FORMAT; + ret->ticks -= tz_hour * TICKS_PER_HOUR; + ret->ticks -= tz_min * TICKS_PER_MIN; + } + + return S_OK; +} + static HRESULT read_get_node_text( struct reader *reader, WS_XML_UTF8_TEXT **ret ) { WS_XML_TEXT_NODE *text; @@ -2570,6 +2712,53 @@ static HRESULT read_type_enum( struct reader *reader, WS_TYPE_MAPPING mapping, return S_OK; } +static HRESULT read_type_datetime( struct reader *reader, WS_TYPE_MAPPING mapping, + const WS_XML_STRING *localname, const WS_XML_STRING *ns, + const WS_DATETIME_DESCRIPTION *desc, WS_READ_OPTION option, + WS_HEAP *heap, void *ret, ULONG size ) +{ + WS_XML_UTF8_TEXT *utf8; + HRESULT hr; + WS_DATETIME val = {0, WS_DATETIME_FORMAT_UTC}; + BOOL found; + + if (desc) FIXME( "ignoring description\n" ); + + if ((hr = read_get_text( reader, mapping, localname, ns, &utf8, &found )) != S_OK) return hr; + if (found && (hr = str_to_datetime( utf8->value.bytes, utf8->value.length, &val )) != S_OK) return hr; + + switch (option) + { + case WS_READ_REQUIRED_VALUE: + if (!found) return WS_E_INVALID_FORMAT; + if (size != sizeof(WS_DATETIME)) return E_INVALIDARG; + *(WS_DATETIME *)ret = val; + break; + + case WS_READ_REQUIRED_POINTER: + if (!found) return WS_E_INVALID_FORMAT; + /* fall through */ + + case WS_READ_OPTIONAL_POINTER: + { + WS_DATETIME *heap_val = NULL; + if (size != sizeof(heap_val)) return E_INVALIDARG; + if (found) + { + if (!(heap_val = ws_alloc( heap, sizeof(*heap_val) ))) return WS_E_QUOTA_EXCEEDED; + *heap_val = val; + } + *(WS_DATETIME **)ret = heap_val; + break; + } + default: + FIXME( "read option %u not supported\n", option ); + return E_NOTIMPL; + } + + return S_OK; +} + static BOOL is_empty_text_node( const struct node *node ) { const WS_XML_TEXT_NODE *text = (const WS_XML_TEXT_NODE *)node; @@ -2643,6 +2832,9 @@ static ULONG get_type_size( WS_TYPE type, const WS_STRUCT_DESCRIPTION *desc ) case WS_UINT64_TYPE: return sizeof(INT64); + case WS_DATETIME_TYPE: + return sizeof(WS_DATETIME); + case WS_WSZ_TYPE: return sizeof(WCHAR *); @@ -2746,6 +2938,7 @@ static WS_READ_OPTION map_field_options( WS_TYPE type, ULONG options ) case WS_UINT32_TYPE: case WS_UINT64_TYPE: case WS_ENUM_TYPE: + case WS_DATETIME_TYPE: return WS_READ_REQUIRED_VALUE; case WS_WSZ_TYPE: @@ -2979,6 +3172,11 @@ static HRESULT read_type( struct reader *reader, WS_TYPE_MAPPING mapping, WS_TYP return hr; break; + case WS_DATETIME_TYPE: + if ((hr = read_type_datetime( reader, mapping, localname, ns, desc, option, heap, value, size )) != S_OK) + return hr; + break; + default: FIXME( "type %u not supported\n", type ); return E_NOTIMPL; diff --git a/dlls/webservices/tests/reader.c b/dlls/webservices/tests/reader.c index 5c94fef77f6..ced8a0fddd9 100644 --- a/dlls/webservices/tests/reader.c +++ b/dlls/webservices/tests/reader.c @@ -2924,6 +2924,91 @@ static void test_WsResetHeap(void) WsFreeHeap( heap ); } +static void test_datetime(void) +{ + static const struct + { + const char *str; + HRESULT hr; + __int64 ticks; + WS_DATETIME_FORMAT format; + } + tests[] = + { + {"0000-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00Z", S_OK, 0, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00.0Z", S_OK, 0, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.1Z", S_OK, 0x0000f4240, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.01Z", S_OK, 0x0000186a0, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.0000001Z", S_OK, 1, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.9999999Z", S_OK, 0x00098967f, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.0000000Z", S_OK, 0, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T00:00:00.00000001Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00Z-", WS_E_INVALID_FORMAT, 0}, + {"-0001-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-00-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-13-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-12-01T00:00:00Z", S_OK, 0x1067555f88000, WS_DATETIME_FORMAT_UTC}, + {"0001-01-00T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"2001-01-32T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"2001-01-31T00:00:00Z", S_OK, 0x8c2592fe3794000, WS_DATETIME_FORMAT_UTC}, + {"1900-02-29T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"2000-02-29T00:00:00Z", S_OK, 0x8c1505f0e438000, 0}, + {"2001-02-29T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"2001-02-28T00:00:00Z", S_OK, 0x8c26f30870a4000, WS_DATETIME_FORMAT_UTC}, + {"0001-00-01U00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T24:00:00Z", S_OK, 0xc92a69c000, WS_DATETIME_FORMAT_UTC}, + {"0001-01-01T24:00:01Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:60:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:60Z", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00Y", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00+00:01", WS_E_INVALID_FORMAT, 0, 0}, + {"0001-01-01T00:00:00-00:01", S_OK, 0x023c34600, WS_DATETIME_FORMAT_LOCAL}, + {"9999-12-31T24:00:00+00:01", S_OK, 0x2bca2875d073fa00, WS_DATETIME_FORMAT_LOCAL}, + {"9999-12-31T24:00:00-00:01", WS_E_INVALID_FORMAT, 0, 0}, + {"0002-01-01T00:00:00+14:01", WS_E_INVALID_FORMAT, 0, 0}, + {"0002-01-01T00:00:00+15:00", WS_E_INVALID_FORMAT, 0, 0}, + {"0002-01-01T00:00:00+13:60", WS_E_INVALID_FORMAT, 0, 0}, + {"0002-01-01T00:00:00+13:59", S_OK, 0x11e5c43cc5600, WS_DATETIME_FORMAT_LOCAL}, + {"0002-01-01T00:00:00+01:00", S_OK, 0x11ec917025800, WS_DATETIME_FORMAT_LOCAL}, + {"2016-01-01T00:00:00-01:00", S_OK, 0x8d31246dfbba800, WS_DATETIME_FORMAT_LOCAL}, + {"2016-01-01T00:00:00Z", S_OK, 0x8d3123e7df74000, WS_DATETIME_FORMAT_UTC}, + {" 2016-01-02T03:04:05Z ", S_OK, 0x8d313215fb64080, WS_DATETIME_FORMAT_UTC}, + {"+2016-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"", WS_E_INVALID_FORMAT, 0, 0}, + {"01-01-01T00:00:00Z", WS_E_INVALID_FORMAT, 0, 0}, + {"1601-01-01T00:00:00Z", S_OK, 0x701ce1722770000, WS_DATETIME_FORMAT_UTC}, + }; + HRESULT hr; + WS_XML_READER *reader; + WS_HEAP *heap; + WS_DATETIME date; + ULONG i; + + hr = WsCreateHeap( 1 << 16, 0, NULL, 0, &heap, NULL ); + ok( hr == S_OK, "got %08x\n", hr ); + + hr = WsCreateReader( NULL, 0, &reader, NULL ) ; + ok( hr == S_OK, "got %08x\n", hr ); + for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) + { + memset( &date, 0, sizeof(date) ); + prepare_type_test( reader, tests[i].str, strlen(tests[i].str) ); + hr = WsReadType( reader, WS_ELEMENT_CONTENT_TYPE_MAPPING, WS_DATETIME_TYPE, NULL, + WS_READ_REQUIRED_VALUE, heap, &date, sizeof(date), NULL ); + ok( hr == tests[i].hr, "%u: got %08x\n", i, hr ); + if (hr == S_OK) + { + ok( date.ticks == tests[i].ticks, "%u: got %x%08x\n", i, (ULONG)(date.ticks >> 32), (ULONG)date.ticks ); + ok( date.format == tests[i].format, "%u: got %u\n", i, date.format ); + } + } + + WsFreeReader( reader ); + WsFreeHeap( heap ); +} + START_TEST(reader) { test_WsCreateError(); @@ -2949,4 +3034,5 @@ START_TEST(reader) test_complex_struct_type(); test_repeating_element(); test_WsResetHeap(); + test_datetime(); } diff --git a/include/webservices.h b/include/webservices.h index 6e30f501b1e..a55072fb5d8 100644 --- a/include/webservices.h +++ b/include/webservices.h @@ -57,6 +57,8 @@ typedef struct _WS_PARAMETER_DESCRIPTION WS_PARAMETER_DESCRIPTION; typedef struct _WS_OPERATION_CONTEXT WS_OPERATION_CONTEXT; typedef struct _WS_CALL_PROPERTY WS_CALL_PROPERTY; typedef struct _WS_DOUBLE_DESCRIPTION WS_DOUBLE_DESCRIPTION; +typedef struct _WS_DATETIME WS_DATETIME; +typedef struct _WS_DATETIME_DESCRIPTION WS_DATETIME_DESCRIPTION; struct _WS_STRUCT_DESCRIPTION; struct _WS_XML_STRING; @@ -989,6 +991,22 @@ struct _WS_CALL_PROPERTY { ULONG valueSize; }; +typedef enum { + WS_DATETIME_FORMAT_UTC, + WS_DATETIME_FORMAT_LOCAL, + WS_DATETIME_FORMAT_NONE +} WS_DATETIME_FORMAT; + +struct _WS_DATETIME { + unsigned __int64 ticks; + WS_DATETIME_FORMAT format; +}; + +struct _WS_DATETIME_DESCRIPTION { + WS_DATETIME minValue; + WS_DATETIME maxValue; +}; + HRESULT WINAPI WsAlloc(WS_HEAP*, SIZE_T, void**, WS_ERROR*); HRESULT WINAPI WsCall(WS_SERVICE_PROXY*, const WS_OPERATION_DESCRIPTION*, const void**, WS_HEAP*, const WS_CALL_PROPERTY*, const ULONG, const WS_ASYNC_CONTEXT*, @@ -1015,6 +1033,8 @@ HRESULT WINAPI WsCreateServiceProxyFromTemplate(WS_CHANNEL_TYPE, const WS_PROXY_ HRESULT WINAPI WsCreateWriter(const WS_XML_WRITER_PROPERTY*, ULONG, WS_XML_WRITER**, WS_ERROR*); HRESULT WINAPI WsCreateXmlBuffer(WS_HEAP*, const WS_XML_BUFFER_PROPERTY*, ULONG, WS_XML_BUFFER**, WS_ERROR*); +HRESULT WINAPI WsDateTimeToFileTime(const WS_DATETIME*, FILETIME*, WS_ERROR*); +HRESULT WINAPI WsFileTimeToDateTime(const FILETIME*, WS_DATETIME*, WS_ERROR*); HRESULT WINAPI WsFillReader(WS_XML_READER*, ULONG, const WS_ASYNC_CONTEXT*, WS_ERROR*); HRESULT WINAPI WsFindAttribute(WS_XML_READER*, const WS_XML_STRING*, const WS_XML_STRING*, BOOL, ULONG*, WS_ERROR*);