From 07697d2ed4577155a4b49a14768aa643765a4028 Mon Sep 17 00:00:00 2001 From: Matthias Kersting Date: Wed, 7 Sep 2022 11:43:09 +0200 Subject: [PATCH 1/4] Check comment values in IniKeyValueTest --- .../Stores/Formats/IniKeyValueTest.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs index 7af2131..6869d7b 100644 --- a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs +++ b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs @@ -6,17 +6,27 @@ namespace Config.Net.Tests.Stores.Formats public class IniKeyValueTest { [Theory] - [InlineData("key=value", "key", "value")] - [InlineData("key=value;123", "key", "value")] - [InlineData("key==value", "key", "=value")] - [InlineData("key=value;value;value", "key", "value;value")] - [InlineData("key=value=value;value", "key", "value=value")] - public void FromLine_ParsingInlineComments(string input, string expectedKey, string expectedValue) + [InlineData("key=value", "key", "value", null)] + [InlineData("key=value;123", "key", "value", "123")] + [InlineData("key==value", "key", "=value", null)] + [InlineData("key=value;value;value", "key", "value;value", "value")] + [InlineData("key=value=value;value", "key", "value=value", "value")] + [InlineData("key=value;rest;", "key", "value;rest", "")] + public void FromLine_ParsingInlineComments(string input, string expectedKey, string expectedValue, string expectedComment) { IniKeyValue kv = IniKeyValue.FromLine(input, true); Assert.Equal(expectedKey, kv.Key); Assert.Equal(expectedValue, kv.Value); + + if (expectedComment == null) + { + Assert.Null(kv.Comment); + } + else + { + Assert.Equal(expectedComment, kv.Comment.Value); + } } [Theory] @@ -31,7 +41,7 @@ public void FromLine_IgnoringInlineComments(string input, string expectedKey, st Assert.Equal(expectedKey, kv.Key); Assert.Equal(expectedValue, kv.Value); + Assert.Null(kv.Comment); } - } } From 0c07136861a19fd901292ecef5b40c262eafb85b Mon Sep 17 00:00:00 2001 From: Matthias Kersting Date: Wed, 7 Sep 2022 21:29:10 +0200 Subject: [PATCH 2/4] Escape \r and \n characters when writing keys and values. --- .../Stores/IniFileConfigStoreTest.cs | 18 ++++++++++++++++++ .../Stores/Formats/Ini/IniKeyValue.cs | 10 ++++++++++ .../Stores/Formats/Ini/IniSection.cs | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs b/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs index a25ce86..fd822d8 100644 --- a/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs +++ b/src/Config.Net.Tests/Stores/IniFileConfigStoreTest.cs @@ -85,5 +85,23 @@ public void Write_NewFileWithNewValues_WritesCorrectText() key0=s2value0 ", resultText, false, true); } + + [Theory] + [InlineData("key", "l1\r\nl2", @"key=l1\r\nl2")] + [InlineData("key", "l1\rl2", @"key=l1\rl2")] + [InlineData("key", "l1\nl2", @"key=l1\nl2")] + [InlineData("k1\r\nk2", "value", @"k1\r\nk2=value")] + [InlineData("k1\rk2", "value", @"k1\rk2=value")] + [InlineData("k1\nk2", "value", @"k1\nk2=value")] + public void Write_NewLine(string key, string value, string expectedOutput) + { + string fullPath = Path.Combine(TestDir.FullName, Guid.NewGuid() + ".ini"); + var ini = new IniFileConfigStore(fullPath, true, true); + + ini.Write(key, value); + + string resultText = File.ReadAllText(fullPath); + Assert.Equal(expectedOutput + "\r\n", resultText); + } } } \ No newline at end of file diff --git a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs index 9480726..afcd76a 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs @@ -18,6 +18,16 @@ public IniKeyValue(string key, string value, string? comment) public string Value { get; set; } + public string EscapedKey + { + get { return Key.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + + public string EscapedValue + { + get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + public IniComment? Comment { get; } public static IniKeyValue? FromLine(string line, bool parseInlineComments) diff --git a/src/Config.Net/Stores/Formats/Ini/IniSection.cs b/src/Config.Net/Stores/Formats/Ini/IniSection.cs index e2cb1ab..93bb706 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniSection.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniSection.cs @@ -93,7 +93,7 @@ public void WriteTo(StreamWriter writer) IniKeyValue? ikv = entity as IniKeyValue; if(ikv != null) { - writer.Write($"{ikv.Key}{IniKeyValue.KeyValueSeparator}{ikv.Value}"); + writer.Write($"{ikv.EscapedKey}{IniKeyValue.KeyValueSeparator}{ikv.EscapedValue}"); if(ikv.Comment != null) { writer.Write(" "); From b98ad8e4db2821a92fdbd7a556624691e6d6acfd Mon Sep 17 00:00:00 2001 From: Matthias Kersting Date: Wed, 7 Sep 2022 22:13:45 +0200 Subject: [PATCH 3/4] Add option to unescape new line characters. --- .../Stores/Formats/IniKeyValueTest.cs | 39 ++++++++++++------- .../Stores/Formats/Ini/IniKeyValue.cs | 14 ++++++- .../Stores/Formats/Ini/StructuredIniFile.cs | 4 +- src/Config.Net/Stores/IniFileConfigStore.cs | 14 +++---- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs index 6869d7b..339a76c 100644 --- a/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs +++ b/src/Config.Net.Tests/Stores/Formats/IniKeyValueTest.cs @@ -14,19 +14,7 @@ public class IniKeyValueTest [InlineData("key=value;rest;", "key", "value;rest", "")] public void FromLine_ParsingInlineComments(string input, string expectedKey, string expectedValue, string expectedComment) { - IniKeyValue kv = IniKeyValue.FromLine(input, true); - - Assert.Equal(expectedKey, kv.Key); - Assert.Equal(expectedValue, kv.Value); - - if (expectedComment == null) - { - Assert.Null(kv.Comment); - } - else - { - Assert.Equal(expectedComment, kv.Comment.Value); - } + FromLine_DoTest(true, false, input, expectedKey, expectedValue, expectedComment); } [Theory] @@ -37,11 +25,32 @@ public void FromLine_ParsingInlineComments(string input, string expectedKey, str [InlineData("key=value=value;value", "key", "value=value;value")] public void FromLine_IgnoringInlineComments(string input, string expectedKey, string expectedValue) { - IniKeyValue kv = IniKeyValue.FromLine(input, false); + FromLine_DoTest(false, false, input, expectedKey, expectedValue, null); + } + + [Theory] + [InlineData(true, @"k1\r\nk2=v1\r\nv2;c1\r\nc2", "k1\r\nk2", "v1\r\nv2", "c1\r\nc2")] + [InlineData(false, @"k1\r\nk2=v1\r\nv2;c1\r\nc2", @"k1\r\nk2", @"v1\r\nv2", @"c1\r\nc2")] + public void FromLine_UnescapeNewLines(bool unescapeNewLines, string input, string expectedKey, string expectedValue, string expectedComment) + { + FromLine_DoTest(true, unescapeNewLines, input, expectedKey, expectedValue, expectedComment); + } + + private static void FromLine_DoTest(bool parseInlineComments, bool unescapeNewLines, string input, string expectedKey, string expectedValue, string expectedComment) + { + IniKeyValue kv = IniKeyValue.FromLine(input, parseInlineComments, unescapeNewLines); Assert.Equal(expectedKey, kv.Key); Assert.Equal(expectedValue, kv.Value); - Assert.Null(kv.Comment); + + if (expectedComment == null) + { + Assert.Null(kv.Comment); + } + else + { + Assert.Equal(expectedComment, kv.Comment.Value); + } } } } diff --git a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs index afcd76a..c290d7c 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniKeyValue.cs @@ -30,7 +30,7 @@ public string EscapedValue public IniComment? Comment { get; } - public static IniKeyValue? FromLine(string line, bool parseInlineComments) + public static IniKeyValue? FromLine(string line, bool parseInlineComments, bool unescapeNewLines = false) { int idx = line.IndexOf(KeyValueSeparator, StringComparison.CurrentCulture); if(idx == -1) return null; @@ -49,9 +49,21 @@ public string EscapedValue } } + if(unescapeNewLines) + { + key = UnescapeString(key); + value = UnescapeString(value); + comment = (comment != null) ? UnescapeString(comment) : null; + } + return new IniKeyValue(key, value, comment); } + private static string UnescapeString(string key) + { + return key.Replace(@"\r", "\r").Replace(@"\n", "\n"); + } + public override string ToString() { return $"{Value}"; diff --git a/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs b/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs index 3bc8a80..3b72f61 100644 --- a/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs +++ b/src/Config.Net/Stores/Formats/Ini/StructuredIniFile.cs @@ -60,7 +60,7 @@ public string? this[string key] } } - public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineComments) + public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineComments, bool unescapeNewLines = false) { if(inputStream == null) throw new ArgumentNullException(nameof(inputStream)); @@ -90,7 +90,7 @@ public static StructuredIniFile ReadFrom(Stream inputStream, bool parseInlineCom } else { - IniKeyValue? ikv = IniKeyValue.FromLine(line, parseInlineComments); + IniKeyValue? ikv = IniKeyValue.FromLine(line, parseInlineComments, unescapeNewLines); if(ikv == null) continue; section.Add(ikv); diff --git a/src/Config.Net/Stores/IniFileConfigStore.cs b/src/Config.Net/Stores/IniFileConfigStore.cs index f77e8fc..8d16976 100644 --- a/src/Config.Net/Stores/IniFileConfigStore.cs +++ b/src/Config.Net/Stores/IniFileConfigStore.cs @@ -19,7 +19,7 @@ class IniFileConfigStore : IConfigStore /// r /// File does not have to exist, however it will be created as soon as you /// try to write to it - public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments) + public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments, bool unescapeNewLines = false) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -34,13 +34,13 @@ public IniFileConfigStore(string name, bool isFilePath, bool parseInlineComments Directory.CreateDirectory(parentDirPath); } - _iniFile = ReadIniFile(_fullName, parseInlineComments); + _iniFile = ReadIniFile(_fullName, parseInlineComments, unescapeNewLines); CanWrite = true; } else { - _iniFile = ReadIniContent(name, parseInlineComments); + _iniFile = ReadIniContent(name, parseInlineComments, unescapeNewLines); CanWrite = false; } @@ -78,14 +78,14 @@ public void Write(string key, string? value) WriteIniFile(); } - private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineComments) + private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineComments, bool unescapeNewLines = false) { FileInfo iniFile = new FileInfo(fullName); if(iniFile.Exists) { using(FileStream stream = iniFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - return StructuredIniFile.ReadFrom(stream, parseInlineComments); + return StructuredIniFile.ReadFrom(stream, parseInlineComments, unescapeNewLines); } } else @@ -94,11 +94,11 @@ private static StructuredIniFile ReadIniFile(string fullName, bool parseInlineCo } } - private static StructuredIniFile ReadIniContent(string content, bool parseInlineComments) + private static StructuredIniFile ReadIniContent(string content, bool parseInlineComments, bool unescapeNewLines = false) { using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content))) { - return StructuredIniFile.ReadFrom(input, parseInlineComments); + return StructuredIniFile.ReadFrom(input, parseInlineComments, unescapeNewLines); } } From 81cf5444c8e19142c83e39687f3ce847088da252 Mon Sep 17 00:00:00 2001 From: Matthias Kersting Date: Wed, 7 Sep 2022 22:31:03 +0200 Subject: [PATCH 4/4] Add escaping of new line characters for inline comments. --- .../Stores/Formats/StructuredIniFileTest.cs | 33 +++++++++++++++++++ .../Stores/Formats/Ini/IniComment.cs | 5 +++ .../Stores/Formats/Ini/IniSection.cs | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs diff --git a/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs b/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs new file mode 100644 index 0000000..d96e32c --- /dev/null +++ b/src/Config.Net.Tests/Stores/Formats/StructuredIniFileTest.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Text; +using Config.Net.Stores.Formats.Ini; +using Xunit; + +namespace Config.Net.Tests.Stores.Formats +{ + public class StructuredIniFileTest : AbstractTestFixture + { + [Fact] + public void WriteInlineCommentWithNewLine() + { + string fullPath = Path.Combine(TestDir.FullName, Guid.NewGuid() + ".ini"); + string content = @"key=value ;c1\r\nc2 +"; + + StructuredIniFile file; + using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + file = StructuredIniFile.ReadFrom(input, true, true); + } + + using (Stream output = File.OpenWrite(fullPath)) + { + file.WriteTo(output); + } + + string resultText = File.ReadAllText(fullPath); + Assert.Equal(content, resultText); + } + } +} diff --git a/src/Config.Net/Stores/Formats/Ini/IniComment.cs b/src/Config.Net/Stores/Formats/Ini/IniComment.cs index 9793ff4..0e83091 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniComment.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniComment.cs @@ -11,6 +11,11 @@ public IniComment(string value) public string Value { get; set; } + public string EscapedValue + { + get { return Value.Replace("\r", @"\r").Replace("\n", @"\n"); } + } + public override string ToString() => Value; } } diff --git a/src/Config.Net/Stores/Formats/Ini/IniSection.cs b/src/Config.Net/Stores/Formats/Ini/IniSection.cs index 93bb706..d5074ff 100644 --- a/src/Config.Net/Stores/Formats/Ini/IniSection.cs +++ b/src/Config.Net/Stores/Formats/Ini/IniSection.cs @@ -98,7 +98,7 @@ public void WriteTo(StreamWriter writer) { writer.Write(" "); writer.Write(IniComment.CommentSeparator); - writer.Write(ikv.Comment.Value); + writer.Write(ikv.Comment.EscapedValue); } writer.WriteLine(); continue;