Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/Seq.Syntax/Expressions/Runtime/RuntimeOperators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using Seq.Syntax.Expressions.Compilation.Linq;
Expand All @@ -25,6 +26,13 @@ namespace Seq.Syntax.Expressions.Runtime;

static class RuntimeOperators
{
static readonly JsonSerializerOptions ToJsonSerializerOptions = new()
{
// Avoids defense-in-depth encoding of HTML content chars/non-ASCII data, which renders the results unreadable
// for many non-Latin languages.
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

internal static EvaluationResult ScalarBoolean(bool value)
{
return JsonValue.Create(value);
Expand Down Expand Up @@ -552,12 +560,10 @@ public static EvaluationResult UriEncode(string value)
}

public static EvaluationResult ToJson(JsonNode? value)
{
// Serializes over the *inserted* form of a typed scalar: `Values.Clone` degrades a level
// to its string moniker and rejects pre-encoded `unsafe()` output. Nodes nested within
// containers were already degraded when they were inserted.
{
// `Values.Clone` handles the level wrapper type and rejects pre-encoded `unsafe()` output.
var node = value is JsonValue ? Values.Clone(value) : value;
return JsonValue.Create(node?.ToJsonString() ?? "null");
return JsonValue.Create(node?.ToJsonString(ToJsonSerializerOptions) ?? "null");
}

public static EvaluationResult FromJson(string json)
Expand Down
8 changes: 1 addition & 7 deletions src/Seq.Syntax/Templates/Rendering/JsonWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ public JsonWriter(TemplateOutputEncoder encoder)
_null = encoder.GetRun(TemplateThemeStyle.Null);
_scalar = encoder.GetRun(TemplateThemeStyle.Scalar);
}

public void Format(JsonNode? value, TextWriter output)
{
var invisibleCharacterCount = 0;
Format(value, output, ref invisibleCharacterCount);
}


public void Format(JsonNode? value, TextWriter output, ref int invisibleCharacterCount)
{
try
Expand Down
6 changes: 6 additions & 0 deletions test/Seq.Syntax.Tests/Cases/expression-evaluation-cases.asv
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@ tojson(undefined()) ⇶ undefined()
tojson([1, 'b', null]) ⇶ '[1,"b",null]'
tojson({a: 1}) ⇶ '{"a":1}'
tojson(@Level) ⇶ '"Information"'
// HTML-sensitive and non-ASCII characters are emitted literally rather than as `\uXXXX` escapes
tojson('<a href="x">&</a>') ⇶ '"<a href=\"x\">&</a>"'
tojson('it''s 1+1') ⇶ '"it''s 1+1"'
tojson('日本語 Ñandú') ⇶ '"日本語 Ñandú"'
tojson({'名前': '値'}) ⇶ '{"名前":"値"}'
tojson(['<', '>', '&', '''', '+']) ⇶ '["<",">","&","''","+"]'

// JSON deserialization
fromjson('{"a": [1, null, "x"]}') ⇶ {a: [1, null, 'x']}
Expand Down
Loading