[PATCH 0/4] MR10443: windows.web: Rework string parsing
Separated from !10263 Adds support for parsing escaped string literals in JsonValue.Parse, as well as some cleanup changes. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
From: Olivia Ryan <olivia.r.dev@gmail.com> --- dlls/windows.web/json_value.c | 63 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/dlls/windows.web/json_value.c b/dlls/windows.web/json_value.c index f1930beaad3..7470d60ff81 100644 --- a/dlls/windows.web/json_value.c +++ b/dlls/windows.web/json_value.c @@ -121,10 +121,12 @@ struct json_value LONG ref; JsonValueType json_value_type; - HSTRING parsed_string; - double parsed_number; - boolean parsed_boolean; - HSTRING string_value; + union + { + boolean boolean_value; + HSTRING string_value; + double number_value; + }; }; static inline struct json_value *impl_from_IJsonValue( IJsonValue *iface ) @@ -170,8 +172,9 @@ static ULONG WINAPI json_value_Release( IJsonValue *iface ) if (!ref) { - WindowsDeleteString( impl->parsed_string ); - WindowsDeleteString( impl->string_value ); + if (impl->json_value_type == JsonValueType_String) + WindowsDeleteString( impl->string_value ); + free( impl ); } return ref; @@ -222,7 +225,7 @@ static HRESULT WINAPI json_value_GetString( IJsonValue *iface, HSTRING *value ) if (impl->json_value_type != JsonValueType_String) return E_ILLEGAL_METHOD_CALL; if (!value) return E_POINTER; - return WindowsDuplicateString( impl->parsed_string, value ); + return WindowsDuplicateString( impl->string_value, value ); } static HRESULT WINAPI json_value_GetNumber( IJsonValue *iface, DOUBLE *value ) @@ -234,7 +237,7 @@ static HRESULT WINAPI json_value_GetNumber( IJsonValue *iface, DOUBLE *value ) if (impl->json_value_type != JsonValueType_Number) return E_ILLEGAL_METHOD_CALL; if (!value) return E_POINTER; - *value = impl->parsed_number; + *value = impl->number_value; return S_OK; } @@ -247,7 +250,7 @@ static HRESULT WINAPI json_value_GetBoolean( IJsonValue *iface, boolean *value ) if (impl->json_value_type != JsonValueType_Boolean) return E_ILLEGAL_METHOD_CALL; if (!value) return E_POINTER; - *value = impl->parsed_boolean; + *value = impl->boolean_value; return S_OK; } @@ -324,15 +327,19 @@ static HRESULT trim_string( HSTRING input, HSTRING *output ) return WindowsCreateString( json + start, end - start, output ); } -static HRESULT parse_json_value( HSTRING input, struct json_value *impl ) +static HRESULT parse_json_value( HSTRING input, IJsonValue **value ) { UINT32 len; const WCHAR *json = WindowsGetStringRawBuffer( input, &len ); + struct json_value *impl; HRESULT hr = S_OK; /* FIXME: Handle all JSON edge cases */ if (!len) return WEB_E_INVALID_JSON_STRING; + if (!(impl = calloc( 1, sizeof( *impl ) ))) return E_OUTOFMEMORY; + impl->IJsonValue_iface.lpVtbl = &json_value_vtbl; + impl->ref = 1; if (len == 4 && !wcsncmp( L"null", json, 4 )) { @@ -340,7 +347,7 @@ static HRESULT parse_json_value( HSTRING input, struct json_value *impl ) } else if ((len == 4 && !wcsncmp( L"true", json, 4 )) || (len == 5 && !wcsncmp( L"false", json, 5 ))) { - impl->parsed_boolean = len == 4; + impl->boolean_value = len == 4; impl->json_value_type = JsonValueType_Boolean; } else if (json[0] == '\"' && json[len - 1] == '\"') @@ -348,9 +355,8 @@ static HRESULT parse_json_value( HSTRING input, struct json_value *impl ) json++; len -= 2; - if (len <= 2) return WEB_E_INVALID_JSON_STRING; - if (FAILED(hr = unescape_string( json, &impl->parsed_string ))) return hr; - + if (len <= 2) hr = WEB_E_INVALID_JSON_STRING; + else hr = unescape_string( json, &impl->string_value ); impl->json_value_type = JsonValueType_String; } else if (json[0] == '[' && json[len - 1] == ']') @@ -371,22 +377,24 @@ static HRESULT parse_json_value( HSTRING input, struct json_value *impl ) errno = 0; result = wcstold( json, &end ); - if (errno || errno == ERANGE || end != json + len) return WEB_E_INVALID_JSON_NUMBER; + if (errno || errno == ERANGE || end != json + len) hr = WEB_E_INVALID_JSON_NUMBER; - impl->parsed_number = result; + impl->number_value = result; impl->json_value_type = JsonValueType_Number; } + if (FAILED(hr)) free( impl ); + else *value = &impl->IJsonValue_iface; return hr; } -static HRESULT parse_json( HSTRING json, struct json_value *impl ) +static HRESULT parse_json( HSTRING json, IJsonValue **value ) { HSTRING trimmed_json = NULL; HRESULT hr = trim_string( json, &trimmed_json ); if (SUCCEEDED(hr) && WindowsIsStringEmpty( trimmed_json )) hr = WEB_E_INVALID_JSON_STRING; - if (SUCCEEDED(hr)) hr = parse_json_value( trimmed_json, impl ); + if (SUCCEEDED(hr)) hr = parse_json_value( trimmed_json, value ); WindowsDeleteString( trimmed_json ); return hr; @@ -394,26 +402,17 @@ static HRESULT parse_json( HSTRING json, struct json_value *impl ) static HRESULT WINAPI json_value_statics_Parse( IJsonValueStatics *iface, HSTRING input, IJsonValue **value ) { - struct json_value *impl; HRESULT hr; FIXME( "iface %p, input %s, value %p semi-stub\n", iface, debugstr_hstring( input ), value ); if (!value) return E_POINTER; if (!input) return WEB_E_INVALID_JSON_STRING; - if (!(impl = calloc( 1, sizeof( *impl ) ))) return E_OUTOFMEMORY; - if (FAILED(hr = parse_json( input, impl ))) - { - free( impl ); - return hr; - } - impl->IJsonValue_iface.lpVtbl = &json_value_vtbl; - impl->ref = 1; + if (SUCCEEDED(hr = parse_json( input, value ))) + TRACE( "created IJsonValue %p.\n", *value ); - *value = &impl->IJsonValue_iface; - TRACE( "created IJsonValue %p.\n", *value ); - return S_OK; + return hr; } static HRESULT WINAPI json_value_statics_TryParse( IJsonValueStatics *iface, HSTRING input, IJsonValue **result, boolean *succeeded ) @@ -449,8 +448,8 @@ static HRESULT WINAPI json_value_statics_CreateStringValue( IJsonValueStatics *i impl->json_value_type = JsonValueType_String; if (FAILED(hr = WindowsDuplicateString( input, &impl->string_value ))) { - free( impl ); - return hr; + free( impl ); + return hr; } *value = &impl->IJsonValue_iface; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
From: Olivia Ryan <olivia.r.dev@gmail.com> --- dlls/windows.web/json_value.c | 88 +++++++++++++++++++++++------------ dlls/windows.web/tests/web.c | 5 +- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/dlls/windows.web/json_value.c b/dlls/windows.web/json_value.c index 7470d60ff81..fcca2334aac 100644 --- a/dlls/windows.web/json_value.c +++ b/dlls/windows.web/json_value.c @@ -1,6 +1,7 @@ /* WinRT Windows.Data.Json.JsonValue Implementation * * Copyright (C) 2024 Mohamad Al-Jaf + * Copyright (C) 2026 Olivia Ryan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -314,60 +315,81 @@ static HRESULT unescape_string( const WCHAR *src, HSTRING *output ) return WindowsPromoteStringBuffer( buf, output ); } -static HRESULT trim_string( HSTRING input, HSTRING *output ) +struct json_buffer +{ + const WCHAR *str; + UINT32 len; +}; + +static void json_buffer_trim( struct json_buffer *json ) { static const WCHAR valid_whitespace[] = L" \t\n\r"; - UINT32 len, start = 0, end; - const WCHAR *json = WindowsGetStringRawBuffer( input, &len ); + UINT32 start = 0, end = json->len; - end = len; - while (start < end && wcschr( valid_whitespace, json[start] )) start++; - while (end > start && wcschr( valid_whitespace, json[end - 1] )) end--; + while (start < end && wcschr( valid_whitespace, json->str[start] )) start++; + while (end > start && wcschr( valid_whitespace, json->str[end - 1] )) end--; - return WindowsCreateString( json + start, end - start, output ); + json->str += start; + json->len = end - start; } -static HRESULT parse_json_value( HSTRING input, IJsonValue **value ) +static BOOL json_buffer_take( struct json_buffer *json, const WCHAR *str ) +{ + UINT32 len = wcslen( str ); + + if (json->len < len || wcsncmp( json->str, str, len )) return FALSE; + json->str += len; + json->len -= len; + + return TRUE; +} + +static HRESULT parse_json_value( struct json_buffer *json, IJsonValue **value ) { - UINT32 len; - const WCHAR *json = WindowsGetStringRawBuffer( input, &len ); struct json_value *impl; HRESULT hr = S_OK; /* FIXME: Handle all JSON edge cases */ - if (!len) return WEB_E_INVALID_JSON_STRING; + if (!json->len) return WEB_E_INVALID_JSON_STRING; if (!(impl = calloc( 1, sizeof( *impl ) ))) return E_OUTOFMEMORY; impl->IJsonValue_iface.lpVtbl = &json_value_vtbl; impl->ref = 1; - if (len == 4 && !wcsncmp( L"null", json, 4 )) + if (json_buffer_take( json, L"null" )) { impl->json_value_type = JsonValueType_Null; } - else if ((len == 4 && !wcsncmp( L"true", json, 4 )) || (len == 5 && !wcsncmp( L"false", json, 5 ))) + else if (json_buffer_take( json, L"true" )) { - impl->boolean_value = len == 4; + impl->boolean_value = TRUE; impl->json_value_type = JsonValueType_Boolean; } - else if (json[0] == '\"' && json[len - 1] == '\"') + else if (json_buffer_take( json, L"false" )) { - json++; - len -= 2; + impl->boolean_value = FALSE; + impl->json_value_type = JsonValueType_Boolean; + } + else if (*json->str == '"' && json->str[json->len - 1] == '"') + { + json->str++; + json->len -= 2; - if (len <= 2) hr = WEB_E_INVALID_JSON_STRING; - else hr = unescape_string( json, &impl->string_value ); + if (json->len <= 2) hr = WEB_E_INVALID_JSON_STRING; + else hr = unescape_string( json->str, &impl->string_value ); impl->json_value_type = JsonValueType_String; } - else if (json[0] == '[' && json[len - 1] == ']') + else if (*json->str == '[') { FIXME( "Array parsing not implemented!\n" ); impl->json_value_type = JsonValueType_Array; + hr = WEB_E_INVALID_JSON_STRING; } - else if (json[0] == '{' && json[len - 1] == '}') + else if (*json->str == '{') { FIXME( "Object parsing not implemented!\n" ); impl->json_value_type = JsonValueType_Object; + hr = WEB_E_INVALID_JSON_STRING; } else { @@ -375,9 +397,12 @@ static HRESULT parse_json_value( HSTRING input, IJsonValue **value ) WCHAR *end; errno = 0; - result = wcstold( json, &end ); + result = wcstold( json->str, &end ); + + json->len -= end - json->str; + json->str = end; - if (errno || errno == ERANGE || end != json + len) hr = WEB_E_INVALID_JSON_NUMBER; + if (errno || errno == ERANGE) hr = WEB_E_INVALID_JSON_NUMBER; impl->number_value = result; impl->json_value_type = JsonValueType_Number; @@ -388,16 +413,17 @@ static HRESULT parse_json_value( HSTRING input, IJsonValue **value ) return hr; } -static HRESULT parse_json( HSTRING json, IJsonValue **value ) +static HRESULT parse_json( HSTRING string, IJsonValue **value ) { - HSTRING trimmed_json = NULL; - HRESULT hr = trim_string( json, &trimmed_json ); - - if (SUCCEEDED(hr) && WindowsIsStringEmpty( trimmed_json )) hr = WEB_E_INVALID_JSON_STRING; - if (SUCCEEDED(hr)) hr = parse_json_value( trimmed_json, value ); + HRESULT hr; + struct json_buffer json; + json.str = WindowsGetStringRawBuffer( string, &json.len ); - WindowsDeleteString( trimmed_json ); - return hr; + json_buffer_trim( &json ); + if (!json.len) return WEB_E_INVALID_JSON_STRING; + if (FAILED(hr = parse_json_value( &json, value ))) return hr; + if (json.len) return WEB_E_INVALID_JSON_STRING; + return S_OK; } static HRESULT WINAPI json_value_statics_Parse( IJsonValueStatics *iface, HSTRING input, IJsonValue **value ) diff --git a/dlls/windows.web/tests/web.c b/dlls/windows.web/tests/web.c index b0f38edfb3b..9af1b872846 100644 --- a/dlls/windows.web/tests/web.c +++ b/dlls/windows.web/tests/web.c @@ -112,13 +112,15 @@ static void check_json_( unsigned int line, IJsonValueStatics *json_value_static if (expected_json_value_type == JsonValueType_Number) ok_(__FILE__, line)( hr == WEB_E_INVALID_JSON_NUMBER, "got hr %#lx.\n", hr ); else - todo_wine ok_(__FILE__, line)( hr == WEB_E_INVALID_JSON_STRING, "got hr %#lx.\n", hr ); WindowsDeleteString( str ); return; } + + todo_wine_if(expected_json_value_type == JsonValueType_Array || expected_json_value_type == JsonValueType_Object) ok_(__FILE__, line)( hr == S_OK, "got hr %#lx.\n", hr ); + if (FAILED(hr)) return; hr = IJsonValue_get_ValueType( json_value, &json_value_type ); ok_(__FILE__, line)( hr == S_OK, "got hr %#lx.\n", hr ); ok_(__FILE__, line)( json_value_type == expected_json_value_type, "got json_value_type %d.\n", json_value_type ); @@ -289,7 +291,6 @@ static void test_JsonValueStatics(void) hr = IJsonValueStatics_Parse( json_value_statics, str, NULL ); ok( hr == E_POINTER, "got hr %#lx.\n", hr ); hr = IJsonValueStatics_Parse( json_value_statics, str, &json_value ); - todo_wine ok( hr == WEB_E_INVALID_JSON_STRING, "got hr %#lx.\n", hr ); WindowsDeleteString( str ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
From: Olivia Ryan <olivia.r.dev@gmail.com> --- dlls/windows.web/json_value.c | 98 +++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 21 deletions(-) diff --git a/dlls/windows.web/json_value.c b/dlls/windows.web/json_value.c index fcca2334aac..f34ce5b73d9 100644 --- a/dlls/windows.web/json_value.c +++ b/dlls/windows.web/json_value.c @@ -300,21 +300,6 @@ static const struct IJsonValueVtbl json_value_vtbl = DEFINE_IINSPECTABLE( json_value_statics, IJsonValueStatics, struct json_value_statics, IActivationFactory_iface ) -static HRESULT unescape_string( const WCHAR *src, HSTRING *output ) -{ - UINT32 len = wcslen( src ) - 1, n; - const WCHAR *end = src + len; - HSTRING_BUFFER buf; - HRESULT hr; - WCHAR *dst; - - for (len = n = 0; len + n < end - src; len++) { if (src[len + n] == '\\') n++; } - if (FAILED(hr = WindowsPreallocateStringBuffer( len, &dst, &buf ))) return hr; - while (src != end) { if (*src == '\\' && ++src == end) break; *dst++ = *src++; } - - return WindowsPromoteStringBuffer( buf, output ); -} - struct json_buffer { const WCHAR *str; @@ -344,6 +329,81 @@ static BOOL json_buffer_take( struct json_buffer *json, const WCHAR *str ) return TRUE; } +static WCHAR json_buffer_next( struct json_buffer *json, const WCHAR *valid ) +{ + const WCHAR chr = *json->str; + + if (!json->len) return 0; + if (valid && !wcschr( valid, chr )) return 0; + json->str++; + json->len--; + + return chr; +} + +static HRESULT parse_json_string( struct json_buffer *json, HSTRING *output ) +{ + const WCHAR valid_hex_chars[] = L"abcdefABCDEF0123456789"; + WCHAR chr, *buf, *dst; + HRESULT hr; + + /* validate and escape string, assuming string occupies remainder of buffer */ + + if (!json_buffer_take( json, L"\"" )) return WEB_E_INVALID_JSON_STRING; + if (!json->len) return WEB_E_INVALID_JSON_STRING; + if (!(buf = calloc( json->len, sizeof( WCHAR )))) return E_OUTOFMEMORY; + dst = buf; + + while (json->len) + { + if (*json->str == '"') break; + if (json_buffer_take( json, L"\\\"" )) *(dst++) = '"'; + else if (json_buffer_take( json, L"\\\\" )) *(dst++) = '\\'; + else if (json_buffer_take( json, L"\\/" )) *(dst++) = '/'; + else if (json_buffer_take( json, L"\\b" )) *(dst++) = '\b'; + else if (json_buffer_take( json, L"\\f" )) *(dst++) = '\f'; + else if (json_buffer_take( json, L"\\n" )) *(dst++) = '\n'; + else if (json_buffer_take( json, L"\\r" )) *(dst++) = '\r'; + else if (json_buffer_take( json, L"\\t" )) *(dst++) = '\t'; + else if (json_buffer_take( json, L"\\u" )) + { + for (int i = 0; i < 4; i++) + { + if (!(chr = json_buffer_next( json, valid_hex_chars ))) + { + free( buf ); + return WEB_E_INVALID_JSON_STRING; + } + + *dst <<= 4; + if (chr >= 'A') *dst |= (chr & 0x7) + 9; + else *dst |= chr & 0xf; + } + dst++; + } + else if (*json->str >= ' ') + { + *(dst++) = *(json->str++); + json->len--; + } + else + { + free( buf ); + return WEB_E_INVALID_JSON_STRING; + } + } + + if (!json_buffer_take( json, L"\"" )) + { + free( buf ); + return WEB_E_INVALID_JSON_STRING; + } + + hr = WindowsCreateString( buf, dst - buf, output ); + free( buf ); + return hr; +} + static HRESULT parse_json_value( struct json_buffer *json, IJsonValue **value ) { struct json_value *impl; @@ -370,13 +430,9 @@ static HRESULT parse_json_value( struct json_buffer *json, IJsonValue **value ) impl->boolean_value = FALSE; impl->json_value_type = JsonValueType_Boolean; } - else if (*json->str == '"' && json->str[json->len - 1] == '"') + else if (*json->str == '"') { - json->str++; - json->len -= 2; - - if (json->len <= 2) hr = WEB_E_INVALID_JSON_STRING; - else hr = unescape_string( json->str, &impl->string_value ); + hr = parse_json_string( json, &impl->string_value ); impl->json_value_type = JsonValueType_String; } else if (*json->str == '[') -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
From: Olivia Ryan <olivia.r.dev@gmail.com> --- dlls/windows.web/tests/web.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/dlls/windows.web/tests/web.c b/dlls/windows.web/tests/web.c index 9af1b872846..b0a4fc6cd92 100644 --- a/dlls/windows.web/tests/web.c +++ b/dlls/windows.web/tests/web.c @@ -321,6 +321,31 @@ static void test_JsonValueStatics(void) IJsonValue_Release( json_value ); } + json = L"\"\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0000\\u0057\\u0069\\u006e\\u0065\\udAbC\\uDcEf\""; + hr = WindowsCreateString( json, wcslen( json ), &str ); + ok( hr == S_OK, "got hr %#lx.\n", hr ); + hr = IJsonValueStatics_Parse( json_value_statics, str, &json_value ); + ok( hr == S_OK, "got hr %#lx.\n", hr ); + WindowsDeleteString( str ); + if (SUCCEEDED(hr)) + { + HSTRING parsed_str = NULL; + int res; + + json = L"\"\\/\b\f\n\r\t\0Wine\U000BF0EF"; + hr = WindowsCreateString( json, 15, &str ); + ok( hr == S_OK, "got hr %#lx.\n", hr ); + hr = IJsonValue_GetString( json_value, &parsed_str ); + ok( hr == S_OK, "got hr %#lx.\n", hr ); + hr = WindowsCompareStringOrdinal( str, parsed_str, &res ); + ok( hr == S_OK, "got hr %#lx.\n", hr ); + ok( res == 0, "got different HSTRINGS str = %s, parsed_str = %s.\n", wine_dbgstr_hstring( str ), wine_dbgstr_hstring( parsed_str ) ); + + WindowsDeleteString( parsed_str ); + WindowsDeleteString( str ); + IJsonValue_Release( json_value ); + } + json = L"null"; check_json( json_value_statics, json, JsonValueType_Null, TRUE ); json = L"false"; @@ -384,6 +409,10 @@ static void test_JsonValueStatics(void) check_json( json_value_statics, json, JsonValueType_String, FALSE ); json = L"\v \"The Wine Project\""; check_json( json_value_statics, json, JsonValueType_String, FALSE ); + json = L"\"\\\""; + check_json( json_value_statics, json, JsonValueType_String, FALSE ); + json = L"\"\\u123\""; + check_json( json_value_statics, json, JsonValueType_String, FALSE ); json = L"[\"Wine\" \"Linux\"]"; check_json( json_value_statics, json, JsonValueType_Array, FALSE ); json = L"{" -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
Perfect, thank you! :) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10443#note_133690
This merge request was approved by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10443
participants (3)
-
Olivia Ryan -
Olivia Ryan (@olivi-r) -
Rémi Bernon (@rbernon)