diff --git a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs index c903b5b622f..8afdfcd9b8c 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs @@ -480,11 +480,35 @@ public string DefaultCommandPrefix /// /// The string to quote /// The quoted string - private string QuoteName(object name) + private string QuoteName(string name) { if (name == null) return "''"; - return "'" + name.ToString().Replace("'", "''") + "'"; + return ("'" + name.ToString().Replace("'", "''") + "'"); + } + + /// + /// Return a single-quoted string using the AbsoluteUri member to ensure it is escaped correctly + /// + /// The Uri to quote + /// The quoted AbsoluteUri + private string QuoteName(Uri name) + { + if (name == null) + return "''"; + return QuoteName(name.AbsoluteUri); + } + + /// + /// Return a single-quoted string from a Version object + /// + /// The Version object to quote + /// The quoted Version string + private string QuoteName(Version name) + { + if (name == null) + return "''"; + return QuoteName(name.ToString()); } /// @@ -877,6 +901,10 @@ protected override void EndProcessing() ValidateUriParameterValue(ProjectUri, "ProjectUri"); ValidateUriParameterValue(LicenseUri, "LicenseUri"); ValidateUriParameterValue(IconUri, "IconUri"); + if (_helpInfoUri != null) + { + ValidateUriParameterValue(new Uri(_helpInfoUri), "HelpInfoUri"); + } if (CompatiblePSEditions != null && (CompatiblePSEditions.Distinct(StringComparer.OrdinalIgnoreCase).Count() != CompatiblePSEditions.Count())) { @@ -949,7 +977,7 @@ protected override void EndProcessing() BuildModuleManifest(result, "RootModule", Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); - BuildModuleManifest(result, "ModuleVersion", Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion.ToString()), streamWriter); + BuildModuleManifest(result, "ModuleVersion", Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion), streamWriter); BuildModuleManifest(result, "CompatiblePSEditions", Modules.CompatiblePSEditions, _compatiblePSEditions != null && _compatiblePSEditions.Length > 0, () => QuoteNames(_compatiblePSEditions, streamWriter), streamWriter); @@ -973,7 +1001,7 @@ protected override void EndProcessing() BuildModuleManifest(result, "CLRVersion", StringUtil.Format(Modules.CLRVersion, Modules.PrerequisiteForDesktopEditionOnly), _ClrVersion != null && !string.IsNullOrEmpty(_ClrVersion.ToString()), () => QuoteName(_ClrVersion), streamWriter); - BuildModuleManifest(result, "ProcessorArchitecture", Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture), streamWriter); + BuildModuleManifest(result, "ProcessorArchitecture", Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture.ToString()), streamWriter); BuildModuleManifest(result, "RequiredModules", Modules.RequiredModules, _requiredModules != null && _requiredModules.Length > 0, () => QuoteModules(_requiredModules, streamWriter), streamWriter); @@ -1003,7 +1031,7 @@ protected override void EndProcessing() BuildPrivateDataInModuleManifest(result, streamWriter); - BuildModuleManifest(result, "HelpInfoURI", Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName(_helpInfoUri), streamWriter); + BuildModuleManifest(result, "HelpInfoURI", Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName((_helpInfoUri != null) ? new Uri(_helpInfoUri) : null), streamWriter); BuildModuleManifest(result, "DefaultCommandPrefix", Modules.DefaultCommandPrefix, !string.IsNullOrEmpty(_defaultCommandPrefix), () => QuoteName(_defaultCommandPrefix), streamWriter); @@ -1128,7 +1156,7 @@ private void ValidateUriParameterValue(Uri uri, string parameterName) { Dbg.Assert(!String.IsNullOrWhiteSpace(parameterName), "parameterName should not be null or whitespace"); - if (uri != null && !Uri.IsWellFormedUriString(uri.ToString(), UriKind.Absolute)) + if (uri != null && !Uri.IsWellFormedUriString(uri.AbsoluteUri, UriKind.Absolute)) { var message = StringUtil.Format(Modules.InvalidParameterValue, uri); var ioe = new InvalidOperationException(message); diff --git a/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 b/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 new file mode 100644 index 00000000000..dcd750ebc32 --- /dev/null +++ b/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 @@ -0,0 +1,30 @@ +Import-Module $PSScriptRoot\..\..\Common\Test.Helpers.psm1 + +Describe "New-ModuleManifest tests" -tags "CI" { + BeforeEach { + New-Item -ItemType Directory -Path testdrive:/module + $testModulePath = "testdrive:/module/test.psd1" + } + + AfterEach { + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue testdrive:/module + } + + It "Uris with spaces are allowed and escaped correctly" { + $testUri = [Uri]"http://foo.com/hello world" + $absoluteUri = $testUri.AbsoluteUri + + New-ModuleManifest -Path $testModulePath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri -HelpInfoUri $testUri + $module = Test-ModuleManifest -Path $testModulePath + $module.HelpInfoUri | Should BeExactly $absoluteUri + $module.PrivateData.PSData.IconUri | Should BeExactly $absoluteUri + $module.PrivateData.PSData.LicenseUri | Should BeExactly $absoluteUri + $module.PrivateData.PSData.ProjectUri | Should BeExactly $absoluteUri + } + + It "Relative URIs are not allowed" { + $testUri = [Uri]"../foo" + + { New-ModuleManifest -Path $testModulePath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri } | ShouldBeErrorId "System.InvalidOperationException,Microsoft.PowerShell.Commands.NewModuleManifestCommand" + } +} diff --git a/test/powershell/engine/Module/TestModuleManifest.ps1 b/test/powershell/engine/Module/TestModuleManifest.Tests.ps1 similarity index 100% rename from test/powershell/engine/Module/TestModuleManifest.ps1 rename to test/powershell/engine/Module/TestModuleManifest.Tests.ps1