diff --git a/src/Microsoft.AspNetCore.Http/Internal/InplaceStringFormatter.cs b/src/Microsoft.AspNetCore.Http/Internal/InplaceStringFormatter.cs new file mode 100644 index 00000000..044c614b --- /dev/null +++ b/src/Microsoft.AspNetCore.Http/Internal/InplaceStringFormatter.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Http.Internal +{ + public struct InplaceStringFormatter + { + private int _length; + private int _offset; + private bool _writing; + private string _value; + + public InplaceStringFormatter(int length) : this() + { + _length = length; + } + + public void AppendLength(string s) + { + AppendLength(s.Length); + } + public void AppendLength(char c) + { + AppendLength(1); + } + + public void AppendLength(int length) + { + if (_writing) + { + throw new InvalidOperationException("Cannot append lenght after write started."); + } + _length += length; + } + + public unsafe void Append(string s) + { + EnsureValue(s.Length); + fixed (char* value = _value) + fixed (char* pDomainToken = s) + { + Unsafe.CopyBlock(value + _offset, pDomainToken, (uint)s.Length * 2); + _offset += s.Length; + } + } + public unsafe void Append(char c) + { + EnsureValue(1); + fixed (char* value = _value) + { + value[_offset++] = c; + } + } + + private void EnsureValue(int length) + { + if (_value == null) + { + _writing = true; + _value = new string('\0', _length); + } + if (_offset + length > _length) + { + throw new InvalidOperationException($"Not enough space to write '{length}' characters, only '{_length - _offset}' left."); + } + } + + public override string ToString() + { + if (_offset != _length) + { + throw new InvalidOperationException($"Entire reserved lenght was not used. Length: '{_length}', written '{_offset}'."); + } + return _value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Http/project.json b/src/Microsoft.AspNetCore.Http/project.json index 792c5989..80fc5466 100644 --- a/src/Microsoft.AspNetCore.Http/project.json +++ b/src/Microsoft.AspNetCore.Http/project.json @@ -29,7 +29,8 @@ "type": "build" }, "Microsoft.Net.Http.Headers": "1.1.0-*", - "System.Buffers": "4.0.0-*" + "System.Buffers": "4.0.0-*", + "System.Runtime.CompilerServices.Unsafe": "4.0.0" }, "frameworks": { "net451": {}, diff --git a/test/Microsoft.AspNetCore.Http.Tests/Internal/InplaceStringFormatterTest.cs b/test/Microsoft.AspNetCore.Http.Tests/Internal/InplaceStringFormatterTest.cs new file mode 100644 index 00000000..c48409b3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Http.Tests/Internal/InplaceStringFormatterTest.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.AspNetCore.Http.Internal; +using Xunit; + +namespace Microsoft.AspNetCore.Http.Tests.Internal +{ + public class InplaceStringFormatterTest + { + [Fact] + public void ToString_ReturnsStringWithAllAppendedValues() + { + var s1 = "123"; + var c1 = '4'; + var s2 = "56789"; + + var formatter = new InplaceStringFormatter(); + formatter.AppendLength(s1); + formatter.AppendLength(c1); + formatter.AppendLength(s2); + formatter.Append(s1); + formatter.Append(c1); + formatter.Append(s2); + Assert.Equal("123456789", formatter.ToString()); + } + + [Fact] + public void ToString_ThrowsIfNotEnoughWritten() + { + var formatter = new InplaceStringFormatter(5); + formatter.Append("123"); + var exception = Assert.Throws(() => formatter.ToString()); + Assert.Equal(exception.Message, "Entire reserved lenght was not used. Length: '5', written '3'."); + } + + [Fact] + public void AppendLength_IfAppendWasCalled() + { + var formatter = new InplaceStringFormatter(3); + formatter.Append("123"); + + var exception = Assert.Throws(() => formatter.AppendLength(1)); + Assert.Equal(exception.Message, "Cannot append lenght after write started."); + } + + [Fact] + public void Append_ThrowsIfNotEnoughSpace() + { + var formatter = new InplaceStringFormatter(1); + + var exception = Assert.Throws(() => formatter.Append("123")); + Assert.Equal(exception.Message, "Not enough space to write '3' characters, only '1' left."); + } + } +}