From 255fd58ea690f780f167089b599dc93ce288215f Mon Sep 17 00:00:00 2001 From: Michael Ganss Date: Wed, 12 Apr 2017 16:01:42 +0200 Subject: [PATCH] Update to VS 2017 Use System.Uri only for resolving relative URLs (fixes #118, fixes #106, fixes #66) --- HtmlSanitizer.dotnet.sln | 35 ------ HtmlSanitizer.sln | 4 +- global.json | 6 - src/HtmlSanitizer/HtmlSanitizer.cs | 60 +++++---- src/HtmlSanitizer/HtmlSanitizer.csproj | 116 +++++++---------- src/HtmlSanitizer/HtmlSanitizer.project.json | 11 -- src/HtmlSanitizer/HtmlSanitizer.xproj | 18 --- src/HtmlSanitizer/Iri.cs | 14 +++ src/HtmlSanitizer/app.net40.config | 15 +++ src/HtmlSanitizer/project.json | 54 -------- .../HtmlSanitizer.Tests.csproj | 119 +++++------------- .../HtmlSanitizer.Tests.project.json | 12 -- .../HtmlSanitizer.Tests.xproj | 21 ---- .../Properties/AssemblyInfo.cs | 12 +- test/HtmlSanitizer.Tests/Tests.cs | 47 +++++-- test/HtmlSanitizer.Tests/project.json | 31 ----- 16 files changed, 179 insertions(+), 396 deletions(-) delete mode 100644 HtmlSanitizer.dotnet.sln delete mode 100644 global.json delete mode 100644 src/HtmlSanitizer/HtmlSanitizer.project.json delete mode 100644 src/HtmlSanitizer/HtmlSanitizer.xproj create mode 100644 src/HtmlSanitizer/Iri.cs create mode 100644 src/HtmlSanitizer/app.net40.config delete mode 100644 src/HtmlSanitizer/project.json delete mode 100644 test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.project.json delete mode 100644 test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.xproj delete mode 100644 test/HtmlSanitizer.Tests/project.json diff --git a/HtmlSanitizer.dotnet.sln b/HtmlSanitizer.dotnet.sln deleted file mode 100644 index 68c0cbf..0000000 --- a/HtmlSanitizer.dotnet.sln +++ /dev/null @@ -1,35 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{422273A8-89FB-489C-9CEE-378B39D48C45}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EEC64896-0470-41A0-BCE9-118DE051CB4C}" - ProjectSection(SolutionItems) = preProject - global.json = global.json - EndProjectSection -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HtmlSanitizer", "src\HtmlSanitizer\HtmlSanitizer.xproj", "{CCDB0C26-D683-4943-B5D8-AC07116461E5}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HtmlSanitizer.Tests", "test\HtmlSanitizer.Tests\HtmlSanitizer.Tests.xproj", "{55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CCDB0C26-D683-4943-B5D8-AC07116461E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCDB0C26-D683-4943-B5D8-AC07116461E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCDB0C26-D683-4943-B5D8-AC07116461E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCDB0C26-D683-4943-B5D8-AC07116461E5}.Release|Any CPU.Build.0 = Release|Any CPU - {55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/HtmlSanitizer.sln b/HtmlSanitizer.sln index 92e6391..251cfa3 100644 --- a/HtmlSanitizer.sln +++ b/HtmlSanitizer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlSanitizer", "src\HtmlSanitizer\HtmlSanitizer.csproj", "{CCDB0C26-D683-4943-B5D8-AC07116461E5}" EndProject diff --git a/global.json b/global.json deleted file mode 100644 index 7ee23dc..0000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "projects": [ "src", "test" ], - "sdk": { - "version": "1.0.0-preview1-002702" - } -} diff --git a/src/HtmlSanitizer/HtmlSanitizer.cs b/src/HtmlSanitizer/HtmlSanitizer.cs index f5bbf97..0cfcf68 100644 --- a/src/HtmlSanitizer/HtmlSanitizer.cs +++ b/src/HtmlSanitizer/HtmlSanitizer.cs @@ -684,7 +684,7 @@ namespace Ganss.XSS if (urls.Count > 0) { - if (urls.Cast().Any(m => GetSafeUri(m.Groups[2].Value) == null || SanitizeUrl(m.Groups[2].Value, baseUrl) == null)) + if (urls.Cast().Any(m => SanitizeUrl(m.Groups[2].Value, baseUrl) == null)) removeStyles.Add(new Tuple(style, RemoveReason.NotAllowedUrlValue)); else { @@ -732,29 +732,24 @@ namespace Ganss.XSS return r; } + private static readonly Regex SchemeRegex = new Regex(@"^\s*([^\/#]*?)(?:\:|�*58|�*3a)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + /// - /// Tries to create a safe object from a string. + /// Tries to create a safe object from a string. /// /// The URL. - /// The object or null if no safe can be created. - protected Uri GetSafeUri(string url) + /// The object or null if no safe can be created. + protected Iri GetSafeIri(string url) { - Uri uri; - if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri) - || !uri.IsAbsoluteUri && !IsWellFormedRelativeUri(uri) - || uri.IsAbsoluteUri && !AllowedSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase)) - return null; + var schemeMatch = SchemeRegex.Match(url); - return uri; - } + if (schemeMatch.Success) + { + var scheme = schemeMatch.Groups[1].Value; + return AllowedSchemes.Contains(scheme, StringComparer.OrdinalIgnoreCase) ? new Iri { Value = url, Scheme = scheme } : null; + } - private static readonly Uri _exampleUri = new Uri("http://www.example.com/"); - private static bool IsWellFormedRelativeUri(Uri uri) - { - if (uri.IsAbsoluteUri) return false; - - Uri absoluteUri; - return Uri.TryCreate(_exampleUri, uri, out absoluteUri) && !uri.OriginalString.Contains(":"); + return new Iri { Value = url }; } /// @@ -765,27 +760,28 @@ namespace Ganss.XSS /// The sanitized URL or null if no safe URL can be created. protected string SanitizeUrl(string url, string baseUrl) { - var uri = GetSafeUri(url); + var iri = GetSafeIri(url); - if (uri == null) return null; + if (iri == null) return null; - if (!uri.IsAbsoluteUri && !string.IsNullOrEmpty(baseUrl)) + if (!iri.IsAbsolute && !string.IsNullOrEmpty(baseUrl)) { // resolve relative uri - Uri baseUri; - if (Uri.TryCreate(baseUrl, UriKind.Absolute, out baseUri)) - uri = new Uri(baseUri, uri.ToString()); + if (Uri.TryCreate(baseUrl, UriKind.Absolute, out Uri baseUri)) + { + try + { + return new Uri(baseUri, iri.Value).AbsoluteUri; + } + catch (UriFormatException) + { + return null; + } + } else return null; } - try - { - return uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); - } - catch (UriFormatException) - { - return null; - } + return iri.Value; } /// diff --git a/src/HtmlSanitizer/HtmlSanitizer.csproj b/src/HtmlSanitizer/HtmlSanitizer.csproj index b978d72..5ed1693 100644 --- a/src/HtmlSanitizer/HtmlSanitizer.csproj +++ b/src/HtmlSanitizer/HtmlSanitizer.csproj @@ -1,82 +1,54 @@ - - - + + - Release - AnyCPU - {CCDB0C26-D683-4943-B5D8-AC07116461E5} - Library - Properties - Ganss.XSS + Cleans HTML from constructs that can be used for cross site scripting (XSS) + Copyright 2013-2016 Michael Ganss + HtmlSanitizer + 1.0.0-VERSION + Michael Ganss + net40;net45;netstandard1.3 HtmlSanitizer - v4.5 - 512 - - ..\ - - - AnyCPU - true - full - false - obj\Debug\net45\ - bin\Debug\net45\ - DEBUG;TRACE - prompt - 4 - false - MinimumRecommendedRules.ruleset - bin\Debug\net45\HtmlSanitizer.XML - false - - - AnyCPU - pdbonly - true - obj\Release\net45\ - bin\Release\net45\ - TRACE - prompt - 4 - bin\Release\net45\HtmlSanitizer.XML - false - - - - - - true - - HtmlSanitizer.snk + true + true + HtmlSanitizer + xss;anti;antixss;html;security + https://github.com/mganss/HtmlSanitizer + https://raw.github.com/mganss/HtmlSanitizer/master/LICENSE.md + git + git://github.com/mganss/HtmlSanitizer + $(PackageTargetFallback);dotnet + false + app.net40.config + false + Ganss.XSS + + + + + + - - - - - - - - - - - - - + + + + + + + - - + + + $(DefineConstants);NETSTANDARD + + + + + - - - \ No newline at end of file + + diff --git a/src/HtmlSanitizer/HtmlSanitizer.project.json b/src/HtmlSanitizer/HtmlSanitizer.project.json deleted file mode 100644 index 6d6f7e6..0000000 --- a/src/HtmlSanitizer/HtmlSanitizer.project.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": { - "AngleSharp": "[0.9.9]" - }, - "frameworks": { - "net45": {} - }, - "runtimes": { - "win": {} - } -} \ No newline at end of file diff --git a/src/HtmlSanitizer/HtmlSanitizer.xproj b/src/HtmlSanitizer/HtmlSanitizer.xproj deleted file mode 100644 index 2cb5b5c..0000000 --- a/src/HtmlSanitizer/HtmlSanitizer.xproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - ccdb0c26-d683-4943-b5d8-ac07116461e5 - Ganss.XSS - .\obj - .\bin\ - - - 2.0 - - - \ No newline at end of file diff --git a/src/HtmlSanitizer/Iri.cs b/src/HtmlSanitizer/Iri.cs new file mode 100644 index 0000000..5df4e55 --- /dev/null +++ b/src/HtmlSanitizer/Iri.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Ganss.XSS +{ + public class Iri + { + public string Value { get; set; } + public bool IsAbsolute => !string.IsNullOrEmpty(Scheme); + public string Scheme { get; set; } + } +} diff --git a/src/HtmlSanitizer/app.net40.config b/src/HtmlSanitizer/app.net40.config new file mode 100644 index 0000000..cc8ff34 --- /dev/null +++ b/src/HtmlSanitizer/app.net40.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/HtmlSanitizer/project.json b/src/HtmlSanitizer/project.json deleted file mode 100644 index 3cadb1f..0000000 --- a/src/HtmlSanitizer/project.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "version": "1.0.0-VERSION", - "title": "HtmlSanitizer", - "description": "Cleans HTML from constructs that can be used for cross site scripting (XSS)", - "authors": [ "Michael Ganss" ], - "copyright": "Copyright 2013-2016 Michael Ganss", - "packOptions": { - "projectUrl": "https://github.com/mganss/HtmlSanitizer", - "tags": [ "xss", "anti", "antixss", "html", "security" ], - "licenseUrl": "https://raw.github.com/mganss/HtmlSanitizer/master/LICENSE.md", - "repository": { - "type": "git", - "url": "git://github.com/mganss/HtmlSanitizer" - } - }, - "buildOptions": { - "keyFile": "HtmlSanitizer.snk" - }, - "dependencies": { - "AngleSharp": "[0.9.9]" - }, - "frameworks": { - "net40": { - "frameworkAssemblies": { - } - }, - "net45": { - "frameworkAssemblies": { - "System.Globalization": { "type": "build" }, - "System.IO": { "type": "build" }, - "System.Runtime": { "type": "build" } - } - }, - "netstandard1.3": { - "imports": "dotnet", - "buildOptions": { - "define": [ - "NETSTANDARD" - ] - }, - "dependencies": { - "System.Collections": "4.0.11", - "System.ComponentModel": "4.0.1", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Text.RegularExpressions": "4.1.0", - "System.Threading": "4.0.11" - } - } - } -} diff --git a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.csproj b/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.csproj index 977c948..4dd4f74 100644 --- a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.csproj +++ b/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.csproj @@ -1,93 +1,42 @@ - - - + + - Debug - AnyCPU - {55D772A0-8D8C-4CF7-A876-E6DAB8ED42C0} - Library - Properties - Ganss.XSS.Tests + netcoreapp1.0;net452 HtmlSanitizer.Tests - v4.5.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - ..\ - - - - - - true - full - false - obj\Debug\net451\ - bin\Debug\net451 - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - obj\Release\net451\ - bin\Release\net451 - TRACE - prompt - 4 + HtmlSanitizer.Tests + true + $(PackageTargetFallback);dnxcore50;portable-net45+win8 + 1.0.4 + false + false + false + false + false + false + false + + + + + + + + + + + + + + + - + - - - - - - - - + - - + - - - {ccdb0c26-d683-4943-b5d8-ac07116461e5} - HtmlSanitizer - - - - - - - False - - - False - - - False - - - False - - - - - - - - \ No newline at end of file + + diff --git a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.project.json b/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.project.json deleted file mode 100644 index 70dca7b..0000000 --- a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.project.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dependencies": { - "xunit": "2.1.0", - "xunit.runner.visualstudio": "2.1.0" - }, - "frameworks": { - "net451": { } - }, - "runtimes": { - "win": { } - } -} diff --git a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.xproj b/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.xproj deleted file mode 100644 index 3d2861a..0000000 --- a/test/HtmlSanitizer.Tests/HtmlSanitizer.Tests.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 55d772a0-8d8c-4cf7-a876-e6dab8ed42c0 - Ganss.XSS.Tests - .\obj - .\bin\ - - - 2.0 - - - - - - \ No newline at end of file diff --git a/test/HtmlSanitizer.Tests/Properties/AssemblyInfo.cs b/test/HtmlSanitizer.Tests/Properties/AssemblyInfo.cs index bdddbdc..7b2b2c2 100644 --- a/test/HtmlSanitizer.Tests/Properties/AssemblyInfo.cs +++ b/test/HtmlSanitizer.Tests/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("HtmlSanitizer.Tests")] @@ -14,8 +14,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -25,11 +25,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/test/HtmlSanitizer.Tests/Tests.cs b/test/HtmlSanitizer.Tests/Tests.cs index 657fe38..b573934 100644 --- a/test/HtmlSanitizer.Tests/Tests.cs +++ b/test/HtmlSanitizer.Tests/Tests.cs @@ -1150,7 +1150,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @""">XSS"; + string expected = "\">XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1250,7 +1250,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @""">XSS"; + string expected = "\">XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1349,7 +1349,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @"XSS"; + string expected = "XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1409,7 +1409,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @"XSS"; + string expected = "XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1469,7 +1469,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @"XSS"; + string expected = "XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1488,7 +1488,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @"XSS"; + string expected = "XSS"; try { @@ -1542,7 +1542,7 @@ S string actual = sanitizer.Sanitize(htmlFragment); // Assert - string expected = @""" SRC=""http://ha.ckers.org/xss.js"">"">XSS"; + string expected = "\" SRC=\"http://ha.ckers.org/xss.js\">\">XSS"; Assert.Equal(expected, actual, ignoreCase: true); } @@ -1744,7 +1744,7 @@ S html = @"test"; actual = sanitizer.Sanitize(html); - expected = @"test"; + expected = @"test"; Assert.Equal(expected, actual, ignoreCase: true); html = @"test"; @@ -1796,7 +1796,7 @@ S Assert.Equal(@"fo
o
", sanitizer.Sanitize(html), ignoreCase: true); html = @"foo"; - Assert.Equal(@"foo", sanitizer.Sanitize(html), ignoreCase: true); + Assert.Equal(html, sanitizer.Sanitize(html), ignoreCase: true); } [Fact] @@ -2291,7 +2291,7 @@ rl(javascript:alert(""foo""))'>"; var actual = s.Sanitize(htmlFragment); // Assert - var expected = @"Bang Bang"; + var expected = htmlFragment; Assert.Equal(expected, actual, ignoreCase: true); } @@ -2623,7 +2623,6 @@ rl(javascript:alert(""foo""))'>"; var sanitizer = new HtmlSanitizer(); sanitizer.AllowDataAttributes = true; sanitizer.AllowedSchemes.Add("data"); - sanitizer.RemovingAttribute += (s, e) => e.Cancel = e.Reason == RemoveReason.NotAllowedUrlValue && e.Attribute.Value.Length >= 0xfff0 && e.Attribute.Value.StartsWith("data:", StringComparison.OrdinalIgnoreCase); var html = @"

Assert.Equal("", actual); } + + [Fact] + public void TrailingSlashTest() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedSchemes.Add("resources"); + + var html = ""; + + var actual = sanitizer.Sanitize(html); + + Assert.Equal(html, actual, ignoreCase: true); + } + + [Fact] + public void FileUrlTest() + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedSchemes.Add("file"); + + var html = @"test"; + + var actual = sanitizer.Sanitize(html); + + Assert.Equal(html, actual); + } } } diff --git a/test/HtmlSanitizer.Tests/project.json b/test/HtmlSanitizer.Tests/project.json deleted file mode 100644 index c86b8e2..0000000 --- a/test/HtmlSanitizer.Tests/project.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": "1.0.0-*", - "testRunner": "xunit", - "dependencies": { - "HtmlSanitizer": { - "target": "project" - }, - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-rc2-build10025" - }, - "frameworks": { - "netcoreapp1.0": { - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.0" - }, - "System.Reflection": "4.1.0" - }, - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - }, - "net451": { - "dependencies": { - "xunit.runner.visualstudio": "2.1.0" - } - } - } -}