Skip to content

Commit

Permalink
#28 TW-62292 Unicode symbols were not escaped within TeamCity service…
Browse files Browse the repository at this point in the history
… messages
  • Loading branch information
NikolayPianikov authored and NikolayPianikov committed Sep 26, 2019
1 parent 09da936 commit 089836b
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 63 deletions.
80 changes: 40 additions & 40 deletions TeamCity.ServiceMessages.Tests/ServiceMessageReplacementsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,54 +21,54 @@ namespace JetBrains.TeamCity.ServiceMessages.Tests
[TestFixture]
public class ServiceMessageReplacementsTest
{
[Test]
public void TestDecode_0()
{
Assert.AreEqual("", ServiceMessageReplacements.Decode(""));
}
// Simple
[TestCase("", "")]
[TestCase("!@#$%^&*", "!@#$%^&*")]
[TestCase("a", "a")]
[TestCase("Abc", "Abc")]

[Test]
public void TestDecode_1()
{
Assert.AreEqual("a", ServiceMessageReplacements.Decode("a"));
}
// Special
[TestCase("|n", "\n")]
[TestCase("|r", "\r")]
[TestCase("|]", "]")]
[TestCase("|[", "[")]
[TestCase("|'", "'")]
[TestCase("||", "|")]
[TestCase("|x", "\u0085")]
[TestCase("|l", "\u2028")]
[TestCase("|p", "\u2029")]

[Test]
public void TestDecode_1_special()
{
Assert.AreEqual(ServiceMessageReplacements.Decode("|n"), "\n");
Assert.AreEqual(ServiceMessageReplacements.Decode("|r"), "\r");
Assert.AreEqual(ServiceMessageReplacements.Decode("|]"), "]");
Assert.AreEqual(ServiceMessageReplacements.Decode("|'"), "'");
Assert.AreEqual(ServiceMessageReplacements.Decode("||"), "|");
Assert.AreEqual(ServiceMessageReplacements.Decode("|x"), "\u0085");
Assert.AreEqual(ServiceMessageReplacements.Decode("|l"), "\u2028");
Assert.AreEqual(ServiceMessageReplacements.Decode("|p"), "\u2029");
}
[TestCase("aaa|nbbb", "aaa\nbbb")]
[TestCase("aaa|nbbb||", "aaa\nbbb|")]
[TestCase("||||", "||")]

[Test]
public void TestEncode_0()
// Unicode
[TestCase("|0x00bf", "\u00bf")]
[TestCase("|0x00bfaaa", "\u00bfaaa")]
[TestCase("bb|0x00bfaaa", "bb\u00bfaaa")]
public void ShouldDecodeAndEncodeWhenUnicode(string textFormServiceMessage, string actualText)
{
Assert.AreEqual("", ServiceMessageReplacements.Encode(""));
Assert.AreEqual(actualText, ServiceMessageReplacements.Decode(textFormServiceMessage));
Assert.AreEqual(textFormServiceMessage, ServiceMessageReplacements.Encode(actualText));
}

[Test]
public void TestEncode_1()
{
Assert.AreEqual("a", ServiceMessageReplacements.Encode("a"));
}
// Invalid special
[TestCase("|z", "?")]
[TestCase("|", "?")]

// Invalid unicode
[TestCase("|0", "?")]
[TestCase("|0x", "?")]
[TestCase("|0x0", "?")]
[TestCase("|0x0b", "?")]
[TestCase("|0x00b", "?")]
[TestCase("|0x00bg", "?")]
[TestCase("aaa|0x00b", "aaa?")]
[TestCase("aaa|0x00fkccc", "aaa?ccc")]

[Test]
public void TestEncode_1_special()
public void ShouldDecodeWhenInvalidDecodedText(string textFormServiceMessage, string actualText)
{
Assert.AreEqual("|n", ServiceMessageReplacements.Encode("\n"));
Assert.AreEqual("|r", ServiceMessageReplacements.Encode("\r"));
Assert.AreEqual("|]", ServiceMessageReplacements.Encode("]"));
Assert.AreEqual("|'", ServiceMessageReplacements.Encode("'"));
Assert.AreEqual("||", ServiceMessageReplacements.Encode("|"));
Assert.AreEqual("|x", ServiceMessageReplacements.Encode("\u0085"));
Assert.AreEqual("|l", ServiceMessageReplacements.Encode("\u2028"));
Assert.AreEqual("|p", ServiceMessageReplacements.Encode("\u2029"));
Assert.AreEqual(actualText, ServiceMessageReplacements.Decode(textFormServiceMessage));
}
}
}
115 changes: 92 additions & 23 deletions TeamCity.ServiceMessages/ServiceMessageReplacements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@
namespace JetBrains.TeamCity.ServiceMessages
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

public class ServiceMessageReplacements
{
private static char InvalidChar = '?';

/// <summary>
/// Performs TeamCity-format escaping of a string.
/// </summary>
Expand Down Expand Up @@ -60,7 +65,14 @@ public static string Encode([NotNull] string value)
sb.Append("|p");
break; //
default:
sb.Append(ch);
if (ch > 127)
{
sb.Append($"|0x{(ulong)ch:x4}");
}
else
{
sb.Append(ch);
}
break;
}
return sb.ToString();
Expand All @@ -76,57 +88,114 @@ public static string Decode([NotNull] string value)
return Decode(value.ToCharArray());
}

[NotNull]
public static string Decode([NotNull] char[] value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
var i = 0;
var sb = value;
var escape = false;
return new string(DecodeChars(value).ToArray());
}

private static IEnumerable<char> DecodeChars([NotNull] IEnumerable<char> value)
{
var isEscaping = false;
var unicodeCounter = 0;
var unicodeSb = new StringBuilder();
foreach (var ch in value)
if (!escape)
{
if (unicodeCounter > 0)
{
if (unicodeCounter-- == 5)
{
if (ch != 'x')
{
unicodeCounter = 0;
}
}
else
{
unicodeSb.Append(ch);
}
}

if (unicodeCounter != 0)
{
if (ch == '|') escape = true;
else sb[i++] = ch;
continue;
}
else

if (unicodeSb.Length == 4)
{
var unicodeStr = "" + InvalidChar;
try
{
unicodeStr = char.ConvertFromUtf32(int.Parse(unicodeSb.ToString(), NumberStyles.HexNumber));
}
catch (FormatException)
{
}

unicodeSb.Length = 0;
foreach (var c in unicodeStr)
{
yield return c;
}

continue;
}

if (isEscaping)
{
isEscaping = false;
switch (ch)
{
case '|':
sb[i++] = '|';
yield return '|';
break; //
case '\'':
sb[i++] = '\'';
yield return '\'';
break; //
case 'n':
sb[i++] = '\n';
yield return '\n';
break; //
case 'r':
sb[i++] = '\r';
yield return '\r';
break; //
case '[':
sb[i++] = '[';
yield return '[';
break; //
case ']':
sb[i++] = ']';
yield return ']';
break; //
case 'x':
sb[i++] = '\u0085';
yield return '\u0085';
break; //\u0085 (next line)=>|x
case 'l':
sb[i++] = '\u2028';
yield return '\u2028';
break; //\u2028 (line separator)=>|l
case 'p':
sb[i++] = '\u2029';
yield return '\u2029';
break; //
case '0':
unicodeCounter = 5;
break;
default:
sb[i++] = '?';
break; // do not thow any exception to make it faster //TODO: no exception on illegal format
yield return InvalidChar;
break; // do not throw any exception to make it faster //TODO: no exception on illegal format
}
escape = false;

continue;
}
return new string(sb, 0, i);

if (ch == '|')
{
isEscaping = true;
continue;
}

yield return ch;
}

if (isEscaping || unicodeCounter > 0)
{
yield return InvalidChar;
}
}
}
}

0 comments on commit 089836b

Please sign in to comment.