diff --git a/src/Framework/Framework/CallerArgumentExpressionAttribute.cs b/src/Framework/Framework/CallerArgumentExpressionAttribute.cs new file mode 100644 index 0000000000..2c156103a3 --- /dev/null +++ b/src/Framework/Framework/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ +#if !NET5_0_OR_GREATER + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + internal sealed class CallerArgumentExpressionAttribute : Attribute + { + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } + } +#endif +} diff --git a/src/Framework/Framework/Controls/KnockoutHelper.cs b/src/Framework/Framework/Controls/KnockoutHelper.cs index 95e0340024..48416b83fd 100644 --- a/src/Framework/Framework/Controls/KnockoutHelper.cs +++ b/src/Framework/Framework/Controls/KnockoutHelper.cs @@ -428,7 +428,7 @@ private static JsExpression TransformOptionValueToExpression(DotvvmBindableObjec } else { - return $"[{handlerNameJson},{{q:{MakeStringLiteral(queueName)}}}]"; + return $"[{handlerNameJson},{{q:{MakeStringLiteral(queueName!)}}}]"; } } diff --git a/src/Framework/Framework/Utils/ThrowHelpers.cs b/src/Framework/Framework/Utils/ThrowHelpers.cs new file mode 100644 index 0000000000..875c1104d8 --- /dev/null +++ b/src/Framework/Framework/Utils/ThrowHelpers.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace DotVVM.Framework.Utils +{ + internal static class ThrowHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ArgumentNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) + { +#if NET6_OR_GREATER + ArgumentNullException.ThrowIfNull(value); +#else + if (argument is null) + { + ThrowArgumentNullException(paramName); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] + private static void ThrowArgumentNullException(string? paramName) + { + throw new ArgumentNullException(paramName); +#endif + } + } +} diff --git a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index 90088b1cb2..714b81eab2 100644 --- a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -22,6 +22,7 @@ using Microsoft.Extensions.Logging; using DotVVM.Framework.Runtime.Tracing; using Microsoft.Extensions.DependencyInjection; +using System.Text.Encodings.Web; namespace DotVVM.Framework.ViewModel.Serialization { @@ -338,16 +339,26 @@ private List SerializeResources(IDotvvmRequestContext context, Fu /// /// Serializes the redirect action. /// - public static string GenerateRedirectActionResponse(string url, bool replace, bool allowSpa, string? downloadName) + /// UTF-8 encoded JSON response + public static byte[] GenerateRedirectActionResponse(string url, bool replace, bool allowSpa, string? downloadName) { + ThrowHelpers.ArgumentNull(url); // create result object - return JsonSerializer.Serialize(new { - url, - action = "redirect", - replace, - allowSpa, - download = downloadName - }, DefaultSerializerSettingsProvider.Instance.SettingsHtmlUnsafe); + var result = new MemoryStream(); + using (var w = new Utf8JsonWriter(result, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })) + { + w.WriteStartObject(); + w.WriteString("url"u8, url); + w.WriteString("action"u8, "redirect"u8); + if (replace) + w.WriteBoolean("replace"u8, true); + if (allowSpa) + w.WriteBoolean("allowSpa"u8, true); + if (downloadName is not null) + w.WriteString("download"u8, downloadName); + w.WriteEndObject(); + } + return result.ToArray(); } /// @@ -355,8 +366,7 @@ public static string GenerateRedirectActionResponse(string url, bool replace, bo /// internal static string GenerateMissingCachedViewModelResponse() { - // create result object - return JsonSerializer.Serialize(new { action = "viewModelNotCached" }, DefaultSerializerSettingsProvider.Instance.SettingsHtmlUnsafe); + return """{"action":"viewModelNotCached"}"""; } ///