< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.DiagnosticText
Assembly: AsyncResponse.Core
File(s): /_/src/AsyncResponse.Core/DiagnosticText.cs
Line coverage
81%
Covered lines: 22
Uncovered lines: 5
Coverable lines: 27
Total lines: 89
Line coverage: 81.4%
Branch coverage
84%
Covered branches: 27
Total branches: 32
Branch coverage: 84.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
EscapedExcerpt(...)85.71%151483.33%
IndexOfUnsafe(...)83.33%7671.42%
IsPairStart(...)100%44100%
IsUnsafe(...)75%88100%

File(s)

/_/src/AsyncResponse.Core/DiagnosticText.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text;
 3
 4namespace AsyncResponse;
 5
 6/// <summary>
 7/// Quotes UNTRUSTED text — an inbound correlation id, a persisted type or method name — into a log
 8/// line, an exception message, or an activity status without handing its author the line.
 9/// <para>
 10/// The ids and names quoted here are rejected precisely because they are malformed, and the
 11/// malformation is frequently the payload: a CR/LF pair inside the quoted excerpt ends the real
 12/// log entry and starts a forged one in every line-oriented sink, and an unpaired surrogate is
 13/// replaced with U+FFFD by the sink's UTF-8 encoder, so the persisted line no longer says what
 14/// was received. The excerpt therefore carries such code units as visible backslash-u escapes,
 15/// never raw.
 16/// </para>
 17/// </summary>
 18internal static class DiagnosticText
 19{
 20    private const char LineSeparator = (char)0x2028;
 21    private const char ParagraphSeparator = (char)0x2029;
 22
 23    /// <summary>
 24    /// The first <paramref name="maxLength"/> UTF-16 code units of <paramref name="value"/> (cut
 25    /// through <see cref="PortableText.TruncateWellFormed"/>, so a whole surrogate pair is never
 26    /// split), with every control character, line/paragraph separator, and unpaired surrogate
 27    /// written as a backslash-u escape, and an ellipsis appended when anything was cut. The budget
 28    /// applies to the INPUT: an escape widens one unit to six, so the result is at most six times
 29    /// the budget.
 30    /// </summary>
 31    internal static string EscapedExcerpt(string value, int maxLength = 40)
 32    {
 19133        var truncated = value.Length > maxLength;
 19134        var text = truncated ? PortableText.TruncateWellFormed(value, maxLength) : value;
 35
 19136        var firstUnsafe = IndexOfUnsafe(text);
 19137        if (firstUnsafe < 0)
 16738            return truncated ? string.Concat(text, "…") : text;
 39
 2440        var builder = new StringBuilder(text.Length + 16);
 2441        builder.Append(text, 0, firstUnsafe);
 15642        for (var index = firstUnsafe; index < text.Length; index++)
 43        {
 5444            var unit = text[index];
 5445            if (IsPairStart(text, index))
 46            {
 47                // A well-formed pair is ordinary text: keep both halves.
 048                builder.Append(unit).Append(text[++index]);
 049                continue;
 50            }
 51
 5452            if (IsUnsafe(unit))
 2453                builder.Append('\\').Append('u').Append(((int)unit).ToString("x4", CultureInfo.InvariantCulture));
 54            else
 3055                builder.Append(unit);
 56        }
 57
 2458        if (truncated)
 059            builder.Append('…');
 60
 2461        return builder.ToString();
 62    }
 63
 64    private static int IndexOfUnsafe(string text)
 65    {
 1306066        for (var index = 0; index < text.Length; index++)
 67        {
 636368            if (IsPairStart(text, index))
 69            {
 070                index++;
 071                continue;
 72            }
 73
 636374            if (IsUnsafe(text[index]))
 2475                return index;
 76        }
 77
 16778        return -1;
 79    }
 80
 81    private static bool IsPairStart(string text, int index)
 641782        => char.IsHighSurrogate(text[index]) && index + 1 < text.Length && char.IsLowSurrogate(text[index + 1]);
 83
 84    // Reached only for a unit that is NOT part of a well-formed pair, so any surrogate seen here
 85    // is unpaired. The line and paragraph separators are not control characters to char.IsControl,
 86    // but they are line breaks to the viewers and parsers that matter for log forging.
 87    private static bool IsUnsafe(char unit)
 641788        => char.IsControl(unit) || char.IsSurrogate(unit) || unit is LineSeparator or ParagraphSeparator;
 89}