diff --git a/HtmlAbstractions.sln b/HtmlAbstractions.sln index 805c4daa7a..a45f34a265 100644 --- a/HtmlAbstractions.sln +++ b/HtmlAbstractions.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.24711.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5A15F1C-885A-452A-A731-B0173DDBD913}" EndProject @@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution NuGetPackageVerifier.json = NuGetPackageVerifier.json EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.WebEncoders", "src\Microsoft.Extensions.WebEncoders\Microsoft.Extensions.WebEncoders.xproj", "{DD2CE416-765E-4000-A03E-C2FF165DA1B6}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.WebEncoders.Tests", "test\Microsoft.Extensions.WebEncoders.Tests\Microsoft.Extensions.WebEncoders.Tests.xproj", "{7AE2731D-43CD-4CF8-850A-4914DE2CE930}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +54,30 @@ Global {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|Mixed Platforms.Build.0 = Release|Any CPU {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|x86.ActiveCfg = Release|Any CPU {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|x86.Build.0 = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|x86.Build.0 = Debug|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Any CPU.Build.0 = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|x86.ActiveCfg = Release|Any CPU + {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|x86.Build.0 = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|x86.ActiveCfg = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|x86.Build.0 = Debug|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Any CPU.Build.0 = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|x86.ActiveCfg = Release|Any CPU + {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -57,5 +85,7 @@ Global GlobalSection(NestedProjects) = preSolution {68A28E4A-3ADE-4187-9625-4FF185887CB3} = {A5A15F1C-885A-452A-A731-B0173DDBD913} {2D187B88-94BD-4A39-AC97-F8F8B9363301} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21} + {DD2CE416-765E-4000-A03E-C2FF165DA1B6} = {A5A15F1C-885A-452A-A731-B0173DDBD913} + {7AE2731D-43CD-4CF8-850A-4914DE2CE930} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21} EndGlobalSection EndGlobal diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index c57b2afb17..31f9437602 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -9,7 +9,8 @@ "StrictSemanticVersionValidationRule" ], "packages": { - "Microsoft.AspNet.Html.Abstractions": { } + "Microsoft.AspNet.Html.Abstractions": { }, + "Microsoft.Extensions.WebEncoders": { } } }, "Default": { // Rules to run for packages not listed in any other set. diff --git a/src/Microsoft.Extensions.WebEncoders/EncoderServiceCollectionExtensions.cs b/src/Microsoft.Extensions.WebEncoders/EncoderServiceCollectionExtensions.cs new file mode 100644 index 0000000000..80f1987c17 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/EncoderServiceCollectionExtensions.cs @@ -0,0 +1,64 @@ +// 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.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.WebEncoders; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class EncoderServiceCollectionExtensions + { + public static IServiceCollection AddWebEncoders(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + return AddWebEncoders(services, configureOptions: null); + } + + public static IServiceCollection AddWebEncoders(this IServiceCollection services, Action configureOptions) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions(); + + // Register the default encoders + // We want to call the 'Default' property getters lazily since they perform static caching + services.TryAddSingleton( + CreateFactory(() => HtmlEncoder.Default, settings => HtmlEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => JavaScriptEncoder.Default, settings => JavaScriptEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => UrlEncoder.Default, settings => UrlEncoder.Create(settings))); + + if (configureOptions != null) + { + services.Configure(configureOptions); + } + + return services; + } + + private static Func CreateFactory( + Func defaultFactory, + Func customSettingsFactory) + { + return serviceProvider => + { + var settings = serviceProvider + ?.GetService>() + ?.Value + ?.TextEncoderSettings; + return (settings != null) ? customSettingsFactory(settings) : defaultFactory(); + }; + } + } +} diff --git a/src/Microsoft.Extensions.WebEncoders/EncoderServiceProviderExtensions.cs b/src/Microsoft.Extensions.WebEncoders/EncoderServiceProviderExtensions.cs new file mode 100644 index 0000000000..8d9d2da4e8 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/EncoderServiceProviderExtensions.cs @@ -0,0 +1,50 @@ +// 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.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders +{ + /// + /// Contains extension methods for fetching encoders from an . + /// + public static class EncoderServiceProviderExtensions + { + /// + /// Retrieves an from an . + /// + /// + /// This method is guaranteed never to return null. + /// It will return a default encoder instance if does not contain one or is null. + /// + public static HtmlEncoder GetHtmlEncoder(this IServiceProvider serviceProvider) + { + return (HtmlEncoder)serviceProvider?.GetService(typeof(HtmlEncoder)) ?? HtmlEncoder.Default; + } + + /// + /// Retrieves an from an . + /// + /// + /// This method is guaranteed never to return null. + /// It will return a default encoder instance if does not contain one or is null. + /// + public static JavaScriptEncoder GetJavaScriptEncoder(this IServiceProvider serviceProvider) + { + return (JavaScriptEncoder)serviceProvider?.GetService(typeof(JavaScriptEncoder)) ?? JavaScriptEncoder.Default; + } + + /// + /// Retrieves an from an . + /// + /// + /// This method is guaranteed never to return null. + /// It will return a default encoder instance if does not contain one or is null. + /// + public static UrlEncoder GetUrlEncoder(this IServiceProvider serviceProvider) + { + return (UrlEncoder)serviceProvider?.GetService(typeof(UrlEncoder)) ?? UrlEncoder.Default; + } + } +} diff --git a/src/Microsoft.Extensions.WebEncoders/Microsoft.Extensions.WebEncoders.xproj b/src/Microsoft.Extensions.WebEncoders/Microsoft.Extensions.WebEncoders.xproj new file mode 100644 index 0000000000..084e7901ea --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/Microsoft.Extensions.WebEncoders.xproj @@ -0,0 +1,17 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + dd2ce416-765e-4000-a03e-c2ff165da1b6 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.Extensions.WebEncoders/Properties/AssemblyInfo.cs b/src/Microsoft.Extensions.WebEncoders/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b2437d9ad6 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// 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.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] \ No newline at end of file diff --git a/src/Microsoft.Extensions.WebEncoders/Testing/HtmlTestEncoder.cs b/src/Microsoft.Extensions.WebEncoders/Testing/HtmlTestEncoder.cs new file mode 100644 index 0000000000..162ce4f6c1 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/Testing/HtmlTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public sealed class HtmlTestEncoder : HtmlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"HtmlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.WebEncoders/Testing/JavaScriptTestEncoder.cs b/src/Microsoft.Extensions.WebEncoders/Testing/JavaScriptTestEncoder.cs new file mode 100644 index 0000000000..bef4461676 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/Testing/JavaScriptTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class JavaScriptTestEncoder : JavaScriptEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"JavaScriptEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.WebEncoders/Testing/UrlTestEncoder.cs b/src/Microsoft.Extensions.WebEncoders/Testing/UrlTestEncoder.cs new file mode 100644 index 0000000000..295bda63e8 --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/Testing/UrlTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class UrlTestEncoder : UrlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"UrlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.WebEncoders/WebEncoderOptions.cs b/src/Microsoft.Extensions.WebEncoders/WebEncoderOptions.cs new file mode 100644 index 0000000000..2f5e770a0c --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/WebEncoderOptions.cs @@ -0,0 +1,21 @@ +// 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.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders +{ + /// + /// Specifies options common to all three encoders (HtmlEncode, JavaScriptEncode, UrlEncode). + /// + public sealed class WebEncoderOptions + { + /// + /// Specifies which code points are allowed to be represented unescaped by the encoders. + /// + /// + /// If this property is null, then the encoders will use their default allow lists. + /// + public TextEncoderSettings TextEncoderSettings { get; set; } + } +} diff --git a/src/Microsoft.Extensions.WebEncoders/project.json b/src/Microsoft.Extensions.WebEncoders/project.json new file mode 100644 index 0000000000..0facfeeb8c --- /dev/null +++ b/src/Microsoft.Extensions.WebEncoders/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + "description": "Contains registration and configuration APIs for the core framework encoders.", + "repository": { + "type": "git", + "url": "git://github.com/aspnet/httpabstractions" + }, + "compilationOptions": { + "warningsAsErrors": true, + "allowUnsafe": true, + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0-*", + "Microsoft.Extensions.Options": "1.0.0-*", + "System.Text.Encodings.Web": "4.0.0-*" + }, + "frameworks": { + "net451": { + "frameworkAssemblies": { + "System.IO": "", + "System.Runtime": "" + } + }, + "dotnet5.4": {} + } +} diff --git a/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs b/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..8e060102bb --- /dev/null +++ b/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs @@ -0,0 +1,90 @@ +// 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.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.WebEncoders.Testing; +using Xunit; + +namespace Microsoft.Extensions.WebEncoders +{ + public class EncoderServiceCollectionExtensionsTests + { + [Fact] + public void AddWebEncoders_WithoutOptions_RegistersDefaultEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_WithOptions_RegistersEncodersWithCustomCodeFilter() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("abcde", htmlEncoder.Encode("abcde")); + Assert.Same(htmlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var javaScriptEncoder = serviceProvider.GetRequiredService(); + Assert.Equal(@"a\u0062c\u0064e", javaScriptEncoder.Encode("abcde")); + Assert.Same(javaScriptEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + Assert.Same(urlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_DoesNotOverrideExistingRegisteredEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + // we don't register an existing URL encoder + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetHtmlEncoder(); + Assert.Equal("HtmlEncode[[abcde]]", htmlEncoder.Encode("abcde")); + + var javaScriptEncoder = serviceProvider.GetJavaScriptEncoder(); + Assert.Equal("JavaScriptEncode[[abcde]]", javaScriptEncoder.Encode("abcde")); + + var urlEncoder = serviceProvider.GetUrlEncoder(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + } + } +} diff --git a/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs b/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs new file mode 100644 index 0000000000..01492e759e --- /dev/null +++ b/test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceProviderExtensionsTests.cs @@ -0,0 +1,103 @@ +// 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.Text.Encodings.Web; +using Xunit; + +namespace Microsoft.Extensions.WebEncoders +{ + public class EncoderServiceProviderExtensionsTests + { + [Fact] + public void GetHtmlEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new TestServiceProvider(); + + // Act + var retVal = serviceProvider.GetHtmlEncoder(); + + // Assert + Assert.Same(HtmlEncoder.Default, retVal); + } + + [Fact] + public void GetHtmlEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = HtmlEncoder.Default; + var serviceProvider = new TestServiceProvider() { Service = expectedEncoder }; + + // Act + var retVal = serviceProvider.GetHtmlEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + + [Fact] + public void GetJavaScriptEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new TestServiceProvider(); + + // Act + var retVal = serviceProvider.GetJavaScriptEncoder(); + + // Assert + Assert.Same(JavaScriptEncoder.Default, retVal); + } + + [Fact] + public void GetJavaScriptEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = JavaScriptEncoder.Default; + var serviceProvider = new TestServiceProvider() { Service = expectedEncoder }; + + // Act + var retVal = serviceProvider.GetJavaScriptEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + + [Fact] + public void GetUrlEncoder_ServiceProviderDoesNotHaveEncoder_UsesDefault() + { + // Arrange + var serviceProvider = new TestServiceProvider(); + + // Act + var retVal = serviceProvider.GetUrlEncoder(); + + // Assert + Assert.Same(UrlEncoder.Default, retVal); + } + + [Fact] + public void GetUrlEncoder_ServiceProviderHasEncoder_ReturnsRegisteredInstance() + { + // Arrange + var expectedEncoder = UrlEncoder.Default; + var serviceProvider = new TestServiceProvider() { Service = expectedEncoder }; + + // Act + var retVal = serviceProvider.GetUrlEncoder(); + + // Assert + Assert.Same(expectedEncoder, retVal); + } + + private class TestServiceProvider : IServiceProvider + { + public object Service { get; set; } + + public object GetService(Type serviceType) + { + return Service; + } + } + } +} diff --git a/test/Microsoft.Extensions.WebEncoders.Tests/HtmlTestEncoderTest.cs b/test/Microsoft.Extensions.WebEncoders.Tests/HtmlTestEncoderTest.cs new file mode 100644 index 0000000000..baafedc4de --- /dev/null +++ b/test/Microsoft.Extensions.WebEncoders.Tests/HtmlTestEncoderTest.cs @@ -0,0 +1,26 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public class HtmlTestEncoderTest + { + [Theory] + [InlineData("", "")] + [InlineData("abcd", "HtmlEncode[[abcd]]")] + [InlineData("<<''\"\">>", "HtmlEncode[[<<''\"\">>]]")] + public void StringEncode_EncodesAsExpected(string input, string expectedOutput) + { + // Arrange + var encoder = new HtmlTestEncoder(); + + // Act + var output = encoder.Encode(input); + + // Assert + Assert.Equal(expectedOutput, output); + } + } +} diff --git a/test/Microsoft.Extensions.WebEncoders.Tests/Microsoft.Extensions.WebEncoders.Tests.xproj b/test/Microsoft.Extensions.WebEncoders.Tests/Microsoft.Extensions.WebEncoders.Tests.xproj new file mode 100644 index 0000000000..9b0698e3cc --- /dev/null +++ b/test/Microsoft.Extensions.WebEncoders.Tests/Microsoft.Extensions.WebEncoders.Tests.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 7ae2731d-43cd-4cf8-850a-4914de2ce930 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.Extensions.WebEncoders.Tests/project.json b/test/Microsoft.Extensions.WebEncoders.Tests/project.json new file mode 100644 index 0000000000..4d81779a7c --- /dev/null +++ b/test/Microsoft.Extensions.WebEncoders.Tests/project.json @@ -0,0 +1,24 @@ +{ + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "1.0.0-*", + "Microsoft.Extensions.WebEncoders": "1.0.0-*", + "Newtonsoft.Json": "6.0.6", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "commands": { + "test": "xunit.runner.aspnet" + }, + "compilationOptions": { + "allowUnsafe": true, + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "System.Text.Encoding.Extensions": "4.0.11-*" + } + } + } +}