Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(#1534) Escape illegal xml characters when using choco new #2175

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,62 @@ public void should_log_info_if_regular_output()
}
}

public class when_generate_file_from_template_is_called_on_xml_with_special_characters : TemplateServiceSpecsBase
{
private Action because;
private readonly ChocolateyConfiguration config = new ChocolateyConfiguration();
private readonly TemplateValues templateValues = new TemplateValues();
private readonly string template = "[[PackageName]]";
private const string fileLocation = "c:\\packages\\bob<>&'\".nuspec";
private string fileContent;

public override void Context()
{
base.Context();

fileSystem.Setup(x => x.write_file(It.Is((string fl) => fl == fileLocation), It.IsAny<string>(), Encoding.UTF8))
.Callback((string filePath, string fileContent, Encoding encoding) => this.fileContent = fileContent);

templateValues.PackageName = "Bob<>&'\"";
}

public override void Because()
{
because = () => service.generate_file_from_template(config, templateValues, template, fileLocation, Encoding.UTF8, TemplateLanguage.XML);
}

public override void BeforeEachSpec()
{
MockLogger.reset();
}

[Fact]
public void should_write_file_with_escaped_and_replaced_tokens()
{
because();

var debugs = MockLogger.MessagesFor(LogLevel.Debug);
debugs.Count.ShouldEqual(1);
debugs[0].ShouldEqual("Bob&lt;&gt;&amp;&apos;&quot;");
}

[Fact]
public void should_log_info_if_regular_output()
{
config.RegularOutput = true;

because();

var debugs = MockLogger.MessagesFor(LogLevel.Debug);
debugs.Count.ShouldEqual(1);
debugs[0].ShouldEqual("Bob&lt;&gt;&amp;&apos;&quot;");

var infos = MockLogger.MessagesFor(LogLevel.Info);
infos.Count.ShouldEqual(1);
infos[0].ShouldEqual(string.Format(@"Generating template to a file{0} at 'c:\packages\bob<>&'"".nuspec'", Environment.NewLine));
}
}

public class when_generate_is_called_with_existing_directory : TemplateServiceSpecsBase
{
private Action because;
Expand Down
37 changes: 25 additions & 12 deletions src/chocolatey/infrastructure.app/services/TemplateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ public void generate(ChocolateyConfiguration configuration)
var defaultTemplateOverride = _fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, "default");
if (string.IsNullOrWhiteSpace(configuration.NewCommand.TemplateName) && (!_fileSystem.directory_exists(defaultTemplateOverride) || configuration.NewCommand.UseOriginalTemplate))
{
generate_file_from_template(configuration, tokens, NuspecTemplate.Template, _fileSystem.combine_paths(packageLocation, "{0}.nuspec".format_with(tokens.PackageNameLower)), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyInstallTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateyinstall.ps1"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyBeforeModifyTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateybeforemodify.ps1"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyUninstallTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateyuninstall.ps1"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyLicenseFileTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "LICENSE.txt"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyVerificationFileTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "VERIFICATION.txt"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyReadMeTemplate.Template, _fileSystem.combine_paths(packageLocation, "ReadMe.md"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, ChocolateyTodoTemplate.Template, _fileSystem.combine_paths(packageLocation, "_TODO.txt"), Encoding.UTF8);
generate_file_from_template(configuration, tokens, NuspecTemplate.Template, _fileSystem.combine_paths(packageLocation, "{0}.nuspec".format_with(tokens.PackageNameLower)), Encoding.UTF8, NuspecTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyInstallTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateyinstall.ps1"), Encoding.UTF8, ChocolateyInstallTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyBeforeModifyTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateybeforemodify.ps1"), Encoding.UTF8, ChocolateyBeforeModifyTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyUninstallTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "chocolateyuninstall.ps1"), Encoding.UTF8, ChocolateyUninstallTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyLicenseFileTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "LICENSE.txt"), Encoding.UTF8, ChocolateyLicenseFileTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyVerificationFileTemplate.Template, _fileSystem.combine_paths(packageToolsLocation, "VERIFICATION.txt"), Encoding.UTF8, ChocolateyVerificationFileTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyReadMeTemplate.Template, _fileSystem.combine_paths(packageLocation, "ReadMe.md"), Encoding.UTF8, ChocolateyReadMeTemplate.Language);
generate_file_from_template(configuration, tokens, ChocolateyTodoTemplate.Template, _fileSystem.combine_paths(packageLocation, "_TODO.txt"), Encoding.UTF8, ChocolateyTodoTemplate.Language);
}
else
{
Expand All @@ -134,7 +134,7 @@ public void generate(ChocolateyConfiguration configuration)
}
else
{
generate_file_from_template(configuration, tokens, _fileSystem.read_file(file), packageFileLocation, Encoding.UTF8);
generate_file_from_template(configuration, tokens, _fileSystem.read_file(file), packageFileLocation, Encoding.UTF8, guessLanguageFromExtension(fileExtension));
}
}
}
Expand All @@ -144,10 +144,23 @@ public void generate(ChocolateyConfiguration configuration)
configuration.NewCommand.Name, configuration.NewCommand.AutomaticPackage ? " (automatic)" : string.Empty, Environment.NewLine, packageLocation));
}

public void generate_file_from_template(ChocolateyConfiguration configuration, TemplateValues tokens, string template, string fileLocation, Encoding encoding)
TemplateLanguage guessLanguageFromExtension(string extension)
{
template = TokenReplacer.replace_tokens(tokens, template);
template = TokenReplacer.replace_tokens(tokens.AdditionalProperties, template);
switch (extension)
{
case "xml":
case "nuspec":
return TemplateLanguage.XML;
default:
return TemplateLanguage.PlainText;
}
}

public void generate_file_from_template(ChocolateyConfiguration configuration, TemplateValues tokens, string template, string fileLocation, Encoding encoding, TemplateLanguage language = TemplateLanguage.PlainText)
{

template = TokenReplacer.replace_tokens(tokens, template, targetLanguage: language);
template = TokenReplacer.replace_tokens(tokens.AdditionalProperties, template, targetLanguage: language);

if (configuration.RegularOutput) this.Log().Info(() => "Generating template to a file{0} at '{1}'".format_with(Environment.NewLine, fileLocation));
this.Log().Debug(() => "{0}".format_with(template));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyBeforeModifyTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"# This runs in 0.9.10+ before upgrade and uninstall.
# Use this file to do things like stop services prior to upgrade or uninstall.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyInstallTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"# IMPORTANT: Before releasing this package, copy/paste the next 2 lines into PowerShell to remove all comments from this file:
# $f='c:\path\to\thisFile.ps1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyLicenseFileTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"
Note: Include this file if including binaries you have the right to distribute.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyReadMeTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"## Summary
How do I create packages? See https://chocolatey.org/docs/create-packages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyTodoTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"TODO

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyUninstallTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template =
@"# IMPORTANT: Before releasing this package, copy/paste the next 2 lines into PowerShell to remove all comments from this file:
# $f='c:\path\to\thisFile.ps1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class ChocolateyVerificationFileTemplate
{
public static TemplateLanguage Language = TemplateLanguage.PlainText;
public static string Template = @"
Note: Include this file if including binaries you have the right to distribute.
Otherwise delete. this file. If you are the software author, you can change this
Expand Down
12 changes: 12 additions & 0 deletions src/chocolatey/infrastructure.app/templates/ITokenEscaper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace chocolatey.infrastructure.app.templates
{
public interface ITokenEscaper
{
string escape(string toEscape);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chocolatey.infrastructure.app.templates
{
public class NuspecTemplate
{
public static TemplateLanguage Language = TemplateLanguage.XML;
public static string Template =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<!-- Read this before creating packages: https://chocolatey.org/docs/create-packages -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace chocolatey.infrastructure.app.templates
{
// Represents the escaping strategy of not escaping anything
class PlaintextTokenEscaper : ITokenEscaper
{
public string escape(string toEscape)
{
return toEscape;
}
}
}
41 changes: 41 additions & 0 deletions src/chocolatey/infrastructure.app/templates/TemplateLanguage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

// Copyright © 2020 Chocolatey Software, Inc
// Copyright © 2020 RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace chocolatey.infrastructure.app.templates
{
public static class Extensions
{
// get the escaper used for a given language
public static ITokenEscaper GetTokenEscaper(this TemplateLanguage language)
{
switch (language) {
case TemplateLanguage.XML:
return new XMLTokenEscaper();
default:
return new PlaintextTokenEscaper();
}
}
}
public enum TemplateLanguage
{
XML,
PlainText
}
}
17 changes: 17 additions & 0 deletions src/chocolatey/infrastructure.app/templates/XMLTokenEscaper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;

namespace chocolatey.infrastructure.app.templates
{
//escape strings so that
class XMLTokenEscaper : ITokenEscaper
{
public string escape(string toEscape)
{
return SecurityElement.Escape(toEscape);
}
}
}
7 changes: 5 additions & 2 deletions src/chocolatey/infrastructure/tokens/TokenReplacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@

namespace chocolatey.infrastructure.tokens
{
using chocolatey.infrastructure.app.templates;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;

public sealed class TokenReplacer
{
public static string replace_tokens<TConfig>(TConfig configuration, string textToReplace, string tokenPrefix = "[[", string tokenSuffix = "]]")
public static string replace_tokens<TConfig>(TConfig configuration, string textToReplace, string tokenPrefix = "[[", string tokenSuffix = "]]", TemplateLanguage targetLanguage = TemplateLanguage.PlainText)
{
if (string.IsNullOrEmpty(textToReplace)) return string.Empty;

IDictionary<string, string> dictionary = create_dictionary_from_configuration(configuration);
if (dictionary.Count == 0) return textToReplace;

ITokenEscaper tokenEscaper = targetLanguage.GetTokenEscaper();

var regex = new Regex("{0}(?<key>\\w+){1}".format_with(Regex.Escape(tokenPrefix), Regex.Escape(tokenSuffix)));

string output = regex.Replace(textToReplace, m =>
Expand All @@ -43,7 +46,7 @@ public static string replace_tokens<TConfig>(TConfig configuration, string textT
}

string value = dictionary[key];
return value;
return tokenEscaper.escape(value);
});

return output;
Expand Down