From 0cebc7862312f7e0182e6dbda5148ba533673e40 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Tue, 21 Mar 2023 12:39:02 +0100 Subject: [PATCH 01/18] Add EncodingHelper --- .../utility/WebCmdlet/EncodingHelper.cs | 96 +++++++++++++++++++ .../utility/WebCmdlet/StreamHelper.cs | 20 +++- 2 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs new file mode 100644 index 00000000000..6c978cac602 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Microsoft.PowerShell.Commands +{ + internal static class EncodingHelper + { + private const int UTF8CodePage = 65001; + private const int UTF8PreambleLength = 3; + private const byte UTF8PreambleByte0 = 0xEF; + private const byte UTF8PreambleByte1 = 0xBB; + private const byte UTF8PreambleByte2 = 0xBF; + private const int UTF8PreambleFirst2Bytes = 0xEFBB; + + private const int UTF32CodePage = 12000; + private const int UTF32PreambleLength = 4; + private const byte UTF32PreambleByte0 = 0xFF; + private const byte UTF32PreambleByte1 = 0xFE; + private const byte UTF32PreambleByte2 = 0x00; + private const byte UTF32PreambleByte3 = 0x00; + private const int UTF32OrUnicodePreambleFirst2Bytes = 0xFFFE; + + private const int BigEndianUTF32CodePage = 12001; + private const int BigEndianUTF32PreambleLength = 4; + private const byte BigEndianUTF32PreambleByte0 = 0x00; + private const byte BigEndianUTF32PreambleByte1 = 0x00; + private const byte BigEndianUTF32PreambleByte2 = 0xFE; + private const byte BigEndianUTF32PreambleByte3 = 0xFF; + private const int BigEndianUTF32PreambleFirst2Bytes = 0x0000; + + private const int UnicodeCodePage = 1200; + private const int UnicodePreambleLength = 2; + private const byte UnicodePreambleByte0 = 0xFF; + private const byte UnicodePreambleByte1 = 0xFE; + + private const int BigEndianUnicodeCodePage = 1201; + private const int BigEndianUnicodePreambleLength = 2; + private const byte BigEndianUnicodePreambleByte0 = 0xFE; + private const byte BigEndianUnicodePreambleByte1 = 0xFF; + private const int BigEndianUnicodePreambleFirst2Bytes = 0xFEFF; + + internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] out Encoding? encoding, out int preambleLength) + { + int first2Bytes = buffer[0] << 8 | buffer[1]; + + switch (first2Bytes) + { + case UTF8PreambleFirst2Bytes: + if (buffer[2] == UTF8PreambleByte2) + { + encoding = Encoding.UTF8; + preambleLength = UTF8PreambleLength; + return true; + } + break; + + case UTF32OrUnicodePreambleFirst2Bytes: + // UTF32 not supported on Phone + if (buffer[2] == UTF32PreambleByte2 && buffer[3] == UTF32PreambleByte3) + { + encoding = Encoding.UTF32; + preambleLength = UTF32PreambleLength; + } + else + { + encoding = Encoding.Unicode; + preambleLength = UnicodePreambleLength; + } + return true; + + case BigEndianUnicodePreambleFirst2Bytes: + encoding = Encoding.BigEndianUnicode; + preambleLength = BigEndianUnicodePreambleLength; + return true; + + case BigEndianUTF32PreambleFirst2Bytes: + if (buffer[2] == BigEndianUTF32PreambleByte2 && buffer[3] == BigEndianUTF32PreambleByte3) + { + encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); + preambleLength = BigEndianUTF32PreambleLength; + return true; + } + break; + } + + encoding = null; + preambleLength = 0; + return false; + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 49cc7d43402..6ce4c477edf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -383,9 +383,21 @@ private static string StreamToString(Stream stream, Encoding encoding) internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) { - bool isDefaultEncoding = !TryGetEncoding(characterSet, out encoding); + byte[] buffer = new byte[4]; + stream.ReadExactly(buffer, 0, 4); + EncodingHelper.TryDetectEncodingFromBom(buffer, out encoding, out int preambleLength); + + bool isDefaultEncoding = false; + + if (encoding is null) + { + isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); + } + + stream.Seek(preambleLength, SeekOrigin.Begin); string content = StreamToString(stream, encoding); + if (isDefaultEncoding) { // We only look within the first 1k characters as the meta element and @@ -405,9 +417,9 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco { characterSet = match.Groups["charset"].Value; - if (TryGetEncoding(characterSet, out Encoding localEncoding)) + if (TryGetEncodingFromCharset(characterSet, out Encoding localEncoding)) { - stream.Seek(0, SeekOrigin.Begin); + stream.Seek(preambleLength, SeekOrigin.Begin); content = StreamToString(stream, localEncoding); encoding = localEncoding; } @@ -417,7 +429,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco return content; } - internal static bool TryGetEncoding(string characterSet, out Encoding encoding) + internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding encoding) { bool result = false; try From dd9811b53ec5836e84730557659ddc0023dbc70a Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Tue, 21 Mar 2023 12:44:14 +0100 Subject: [PATCH 02/18] remove const --- .../utility/WebCmdlet/EncodingHelper.cs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs index 6c978cac602..767d2409d8b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs @@ -10,38 +10,21 @@ namespace Microsoft.PowerShell.Commands { internal static class EncodingHelper { - private const int UTF8CodePage = 65001; private const int UTF8PreambleLength = 3; - private const byte UTF8PreambleByte0 = 0xEF; - private const byte UTF8PreambleByte1 = 0xBB; private const byte UTF8PreambleByte2 = 0xBF; private const int UTF8PreambleFirst2Bytes = 0xEFBB; - private const int UTF32CodePage = 12000; private const int UTF32PreambleLength = 4; - private const byte UTF32PreambleByte0 = 0xFF; - private const byte UTF32PreambleByte1 = 0xFE; private const byte UTF32PreambleByte2 = 0x00; private const byte UTF32PreambleByte3 = 0x00; private const int UTF32OrUnicodePreambleFirst2Bytes = 0xFFFE; - private const int BigEndianUTF32CodePage = 12001; - private const int BigEndianUTF32PreambleLength = 4; - private const byte BigEndianUTF32PreambleByte0 = 0x00; - private const byte BigEndianUTF32PreambleByte1 = 0x00; private const byte BigEndianUTF32PreambleByte2 = 0xFE; private const byte BigEndianUTF32PreambleByte3 = 0xFF; private const int BigEndianUTF32PreambleFirst2Bytes = 0x0000; - private const int UnicodeCodePage = 1200; private const int UnicodePreambleLength = 2; - private const byte UnicodePreambleByte0 = 0xFF; - private const byte UnicodePreambleByte1 = 0xFE; - private const int BigEndianUnicodeCodePage = 1201; - private const int BigEndianUnicodePreambleLength = 2; - private const byte BigEndianUnicodePreambleByte0 = 0xFE; - private const byte BigEndianUnicodePreambleByte1 = 0xFF; private const int BigEndianUnicodePreambleFirst2Bytes = 0xFEFF; internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] out Encoding? encoding, out int preambleLength) @@ -75,14 +58,14 @@ internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] case BigEndianUnicodePreambleFirst2Bytes: encoding = Encoding.BigEndianUnicode; - preambleLength = BigEndianUnicodePreambleLength; + preambleLength = UnicodePreambleLength; return true; case BigEndianUTF32PreambleFirst2Bytes: if (buffer[2] == BigEndianUTF32PreambleByte2 && buffer[3] == BigEndianUTF32PreambleByte3) { encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); - preambleLength = BigEndianUTF32PreambleLength; + preambleLength = UTF32PreambleLength; return true; } break; From efe8e0aa8d12e6eb15a1013d04ac9a0816dfb364 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Tue, 21 Mar 2023 13:12:53 +0100 Subject: [PATCH 03/18] add stream.length chack --- .../commands/utility/WebCmdlet/StreamHelper.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 6ce4c477edf..89cd13accbe 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -383,13 +383,16 @@ private static string StreamToString(Stream stream, Encoding encoding) internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) { + bool isDefaultEncoding = false; byte[] buffer = new byte[4]; - stream.ReadExactly(buffer, 0, 4); + + if (stream.Length >= 4) + { + stream.ReadExactly(buffer, 0, 4); + } EncodingHelper.TryDetectEncodingFromBom(buffer, out encoding, out int preambleLength); - bool isDefaultEncoding = false; - if (encoding is null) { isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); From 3d42ae55dce6a789bd59060b76127b9bf88603d6 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Tue, 21 Mar 2023 16:53:54 +0100 Subject: [PATCH 04/18] use when --- .../utility/WebCmdlet/EncodingHelper.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs index 767d2409d8b..945daac490c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs @@ -33,17 +33,12 @@ internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] switch (first2Bytes) { - case UTF8PreambleFirst2Bytes: - if (buffer[2] == UTF8PreambleByte2) - { - encoding = Encoding.UTF8; - preambleLength = UTF8PreambleLength; - return true; - } - break; + case UTF8PreambleFirst2Bytes when buffer[2] == UTF8PreambleByte2: + encoding = Encoding.UTF8; + preambleLength = UTF8PreambleLength; + return true; case UTF32OrUnicodePreambleFirst2Bytes: - // UTF32 not supported on Phone if (buffer[2] == UTF32PreambleByte2 && buffer[3] == UTF32PreambleByte3) { encoding = Encoding.UTF32; @@ -61,16 +56,12 @@ internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] preambleLength = UnicodePreambleLength; return true; - case BigEndianUTF32PreambleFirst2Bytes: - if (buffer[2] == BigEndianUTF32PreambleByte2 && buffer[3] == BigEndianUTF32PreambleByte3) - { - encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); - preambleLength = UTF32PreambleLength; - return true; - } - break; + case BigEndianUTF32PreambleFirst2Bytes when buffer[2] == BigEndianUTF32PreambleByte2 && buffer[3] == BigEndianUTF32PreambleByte3: + encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); + preambleLength = UTF32PreambleLength; + return true; } - + encoding = null; preambleLength = 0; return false; From b43ef898c1d54aa6387fce915396cd4898434966 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 23 Mar 2023 08:50:47 +0100 Subject: [PATCH 05/18] use StreamReader detectEncodingFromByteOrderMarks --- .../utility/WebCmdlet/EncodingHelper.cs | 70 ---------------- .../utility/WebCmdlet/StreamHelper.cs | 82 +++---------------- 2 files changed, 11 insertions(+), 141 deletions(-) delete mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs deleted file mode 100644 index 945daac490c..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/EncodingHelper.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#nullable enable - -using System.Diagnostics.CodeAnalysis; -using System.Text; - -namespace Microsoft.PowerShell.Commands -{ - internal static class EncodingHelper - { - private const int UTF8PreambleLength = 3; - private const byte UTF8PreambleByte2 = 0xBF; - private const int UTF8PreambleFirst2Bytes = 0xEFBB; - - private const int UTF32PreambleLength = 4; - private const byte UTF32PreambleByte2 = 0x00; - private const byte UTF32PreambleByte3 = 0x00; - private const int UTF32OrUnicodePreambleFirst2Bytes = 0xFFFE; - - private const byte BigEndianUTF32PreambleByte2 = 0xFE; - private const byte BigEndianUTF32PreambleByte3 = 0xFF; - private const int BigEndianUTF32PreambleFirst2Bytes = 0x0000; - - private const int UnicodePreambleLength = 2; - - private const int BigEndianUnicodePreambleFirst2Bytes = 0xFEFF; - - internal static bool TryDetectEncodingFromBom(byte[] buffer, [NotNullWhen(true)] out Encoding? encoding, out int preambleLength) - { - int first2Bytes = buffer[0] << 8 | buffer[1]; - - switch (first2Bytes) - { - case UTF8PreambleFirst2Bytes when buffer[2] == UTF8PreambleByte2: - encoding = Encoding.UTF8; - preambleLength = UTF8PreambleLength; - return true; - - case UTF32OrUnicodePreambleFirst2Bytes: - if (buffer[2] == UTF32PreambleByte2 && buffer[3] == UTF32PreambleByte3) - { - encoding = Encoding.UTF32; - preambleLength = UTF32PreambleLength; - } - else - { - encoding = Encoding.Unicode; - preambleLength = UnicodePreambleLength; - } - return true; - - case BigEndianUnicodePreambleFirst2Bytes: - encoding = Encoding.BigEndianUnicode; - preambleLength = UnicodePreambleLength; - return true; - - case BigEndianUTF32PreambleFirst2Bytes when buffer[2] == BigEndianUTF32PreambleByte2 && buffer[3] == BigEndianUTF32PreambleByte3: - encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); - preambleLength = UTF32PreambleLength; - return true; - } - - encoding = null; - preambleLength = 0; - return false; - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 89cd13accbe..cf77222dac0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -329,83 +329,25 @@ internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet c WriteToStream(stream, output, cmdlet, contentLength, cancellationToken); } - private static string StreamToString(Stream stream, Encoding encoding) - { - StringBuilder result = new(capacity: ChunkSize); - Decoder decoder = encoding.GetDecoder(); - - int useBufferSize = 64; - if (useBufferSize < encoding.GetMaxCharCount(10)) - { - useBufferSize = encoding.GetMaxCharCount(10); - } - - char[] chars = new char[useBufferSize]; - byte[] bytes = new byte[useBufferSize * 4]; - int bytesRead = 0; - do - { - // Read at most the number of bytes that will fit in the input buffer. The - // return value is the actual number of bytes read, or zero if no bytes remain. - bytesRead = stream.Read(bytes, 0, useBufferSize * 4); - - bool completed = false; - int byteIndex = 0; - - while (!completed) - { - // If this is the last input data, flush the decoder's internal buffer and state. - bool flush = (bytesRead == 0); - decoder.Convert(bytes, byteIndex, bytesRead - byteIndex, - chars, 0, useBufferSize, flush, - out int bytesUsed, out int charsUsed, out completed); - - // The conversion produced the number of characters indicated by charsUsed. Write that number - // of characters to our result buffer - result.Append(chars, 0, charsUsed); - - // Increment byteIndex to the next block of bytes in the input buffer, if any, to convert. - byteIndex += bytesUsed; - - // The behavior of decoder.Convert changed start .NET 3.1-preview2. - // The change was made in https://github.com/dotnet/coreclr/pull/27229 - // The recommendation from .NET team is to not check for 'completed' if 'flush' is false. - // Break out of the loop if all bytes have been read. - if (!flush && bytesRead == byteIndex) - { - break; - } - } - } while (bytesRead != 0); - - return result.ToString(); - } - internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) { - bool isDefaultEncoding = false; - byte[] buffer = new byte[4]; + bool isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); - if (stream.Length >= 4) - { - stream.ReadExactly(buffer, 0, 4); - } + using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true); - EncodingHelper.TryDetectEncodingFromBom(buffer, out encoding, out int preambleLength); - - if (encoding is null) - { - isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); - } - - stream.Seek(preambleLength, SeekOrigin.Begin); - string content = StreamToString(stream, encoding); + encoding = reader.CurrentEncoding; if (isDefaultEncoding) { // We only look within the first 1k characters as the meta element and // the xml declaration are at the start of the document - string substring = content.Substring(0, Math.Min(content.Length, 1024)); + int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); + + char[] buffer = new char[bufferLength]; + reader.ReadBlock(buffer, 0, bufferLength); + stream.Seek(0, SeekOrigin.Begin); + + string substring = new(buffer); // Check for a charset attribute on the meta element to override the default Match match = s_metaRegex.Match(substring); @@ -422,14 +364,12 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco if (TryGetEncodingFromCharset(characterSet, out Encoding localEncoding)) { - stream.Seek(preambleLength, SeekOrigin.Begin); - content = StreamToString(stream, localEncoding); encoding = localEncoding; } } } - return content; + return reader.ReadToEnd(); } internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding encoding) From 1141be9442a590dd3236c256ea962216f5928aec Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 23 Mar 2023 09:24:16 +0100 Subject: [PATCH 06/18] fix return --- .../commands/utility/WebCmdlet/StreamHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index cf77222dac0..89f4d0c8b78 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -369,7 +369,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco } } - return reader.ReadToEnd(); + return new StreamReader(stream, encoding).ReadToEnd(); } internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding encoding) From 4a90872659a734cc22f586be47ff4af1f86fc024 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 23 Mar 2023 09:46:55 +0100 Subject: [PATCH 07/18] stream leaveOpen: true --- .../commands/utility/WebCmdlet/StreamHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 89f4d0c8b78..ccb6f4bb623 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -333,7 +333,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco { bool isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); - using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true); + using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); encoding = reader.CurrentEncoding; @@ -369,7 +369,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco } } - return new StreamReader(stream, encoding).ReadToEnd(); + return new StreamReader(stream, encoding, leaveOpen: true).ReadToEnd(); } internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding encoding) From a2233fc7c77cfa9ca5f154af463b3eacaeeae8fc Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 25 Mar 2023 00:54:39 +0100 Subject: [PATCH 08/18] add tests --- .../WebCmdlets.Tests.ps1 | 33 +++++++++++++ .../Controllers/EncodingController.cs | 48 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 5f1e6e4bae3..d0c9b3f0688 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -608,6 +608,24 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $Result.Output.Content | Should -Match '测试123' } + It "Invoke-WebRequest detects encoding from BOM." -TestCases @( + @{ Name = "Utf8BOM"; CodePage = 65001 } + @{ Name = "Unicode"; CodePage = 1200 } + @{ Name = "BigEndianUnicode"; CodePage = 1201 } + @{ Name = "Utf32"; CodePage = 12000 } + @{ Name = "Utf32BE"; CodePage = 12001 } + ) { + param ($name, $codepage) + $uri = Get-WebListenerUrl -Test 'Encoding' -TestValue $name + $command = "Invoke-WebRequest -Uri '$uri'" + + $result = ExecuteWebCommand -command $command + ValidateResponse -response $result + + $Result.Output.Encoding.CodePage | Should -Be $codepage + $Result.Output.Content | Should -Match 'hello' + } + It "Invoke-WebRequest validate timeout option" { $uri = Get-WebListenerUrl -Test 'Delay' -TestValue '5' $command = "Invoke-WebRequest -Uri '$uri' -TimeoutSec 2" @@ -2587,6 +2605,21 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $Result.Output | Should -Match '测试123' } + It "Invoke-RestMethod detects encoding from BOM." -TestCases @( + @{ Name = "Utf8BOM"; CodePage = 65001 } + @{ Name = "Unicode"; CodePage = 1200 } + @{ Name = "BigEndianUnicode"; CodePage = 1201 } + @{ Name = "Utf32"; CodePage = 12000 } + @{ Name = "Utf32BE"; CodePage = 12001 } + ) { + param ($name, $codepage) + $uri = Get-WebListenerUrl -Test 'Encoding' -TestValue $name + $command = "Invoke-RestMethod -Uri '$uri'" + + $result = ExecuteWebCommand -command $command + $Result.Output | Should -Match 'hello' + } + It "Invoke-RestMethod validate timeout option" { $uri = Get-WebListenerUrl -Test 'Delay' -TestValue '5' $command = "Invoke-RestMethod -Uri '$uri' -TimeoutSec 2" diff --git a/test/tools/WebListener/Controllers/EncodingController.cs b/test/tools/WebListener/Controllers/EncodingController.cs index 7f245d4544b..8b8e573a945 100644 --- a/test/tools/WebListener/Controllers/EncodingController.cs +++ b/test/tools/WebListener/Controllers/EncodingController.cs @@ -51,6 +51,54 @@ public async void Cp936() }; await Response.Body.WriteAsync(body, 0, body.Length); } + public async void Utf8BOM() + { + MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); + Response.ContentType = mediaType.ToString(); + byte[] body = Encoding.UTF8.GetPreamble() + Encoding.UTF8.GetBytes("hello"); + + await Response.Body.WriteAsync(body, 0, body.Length); + } + + public async void Unicode() + { + MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); + Response.ContentType = mediaType.ToString(); + byte[] body = Encoding.Unicode.GetPreamble() + Encoding.Unicode.GetBytes("hello"); + + await Response.Body.WriteAsync(body, 0, body.Length); + } + + public async void BigEndianUnicode() + { + MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); + Response.ContentType = mediaType.ToString(); + byte[] body = Encoding.BigEndianUnicode.GetPreamble() + Encoding.BigEndianUnicode.GetBytes("hello"); + + await Response.Body.WriteAsync(body, 0, body.Length); + } + + public async void Utf32() + { + MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); + Response.ContentType = mediaType.ToString(); + byte[] body = Encoding.UTF32.GetPreamble() + Encoding.UTF32.GetBytes("hello"); + + await Response.Body.WriteAsync(body, 0, body.Length); + } + + public async void Utf32BE() + { + MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); + Response.ContentType = mediaType.ToString(); + + UTF32Encoding Utf32BE = new(bigEndian: true, byteOrderMark: true); + byte[] body = Utf32BE.GetPreamble() + Utf32BE.GetBytes("hello"); + + await Response.Body.WriteAsync(body, 0, body.Length); + } + + public IActionResult Error() { From af44f3175a1043207f25383a2a3764679bcdcb4b Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 25 Mar 2023 00:58:03 +0100 Subject: [PATCH 09/18] fix codefactor --- test/tools/WebListener/Controllers/EncodingController.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/tools/WebListener/Controllers/EncodingController.cs b/test/tools/WebListener/Controllers/EncodingController.cs index 8b8e573a945..dde41acc2f1 100644 --- a/test/tools/WebListener/Controllers/EncodingController.cs +++ b/test/tools/WebListener/Controllers/EncodingController.cs @@ -51,6 +51,7 @@ public async void Cp936() }; await Response.Body.WriteAsync(body, 0, body.Length); } + public async void Utf8BOM() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); @@ -92,14 +93,12 @@ public async void Utf32BE() MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); Response.ContentType = mediaType.ToString(); - UTF32Encoding Utf32BE = new(bigEndian: true, byteOrderMark: true); - byte[] body = Utf32BE.GetPreamble() + Utf32BE.GetBytes("hello"); + UTF32Encoding utf32BE = new(bigEndian: true, byteOrderMark: true); + byte[] body = utf32BE.GetPreamble() + utf32BE.GetBytes("hello"); await Response.Body.WriteAsync(body, 0, body.Length); } - - public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); From 314c97964701f3c1e6033af48e9e6990540d5f0d Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 25 Mar 2023 01:25:16 +0100 Subject: [PATCH 10/18] fix tests --- .../WebListener/Controllers/EncodingController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/tools/WebListener/Controllers/EncodingController.cs b/test/tools/WebListener/Controllers/EncodingController.cs index dde41acc2f1..ef3d51f7805 100644 --- a/test/tools/WebListener/Controllers/EncodingController.cs +++ b/test/tools/WebListener/Controllers/EncodingController.cs @@ -56,7 +56,7 @@ public async void Utf8BOM() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); Response.ContentType = mediaType.ToString(); - byte[] body = Encoding.UTF8.GetPreamble() + Encoding.UTF8.GetBytes("hello"); + byte[] body = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes("hello")).ToArray(); await Response.Body.WriteAsync(body, 0, body.Length); } @@ -65,7 +65,7 @@ public async void Unicode() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); Response.ContentType = mediaType.ToString(); - byte[] body = Encoding.Unicode.GetPreamble() + Encoding.Unicode.GetBytes("hello"); + byte[] body = Encoding.Unicode.GetPreamble().Concat(Encoding.Unicode.GetBytes("hello")).ToArray(); await Response.Body.WriteAsync(body, 0, body.Length); } @@ -74,7 +74,7 @@ public async void BigEndianUnicode() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); Response.ContentType = mediaType.ToString(); - byte[] body = Encoding.BigEndianUnicode.GetPreamble() + Encoding.BigEndianUnicode.GetBytes("hello"); + byte[] body = Encoding.BigEndianUnicode.GetPreamble().Concat(Encoding.BigEndianUnicode.GetBytes("hello")).ToArray(); await Response.Body.WriteAsync(body, 0, body.Length); } @@ -83,7 +83,7 @@ public async void Utf32() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/html"); Response.ContentType = mediaType.ToString(); - byte[] body = Encoding.UTF32.GetPreamble() + Encoding.UTF32.GetBytes("hello"); + byte[] body = Encoding.UTF32.GetPreamble().Concat(Encoding.UTF32.GetBytes("hello")).ToArray(); await Response.Body.WriteAsync(body, 0, body.Length); } @@ -94,7 +94,7 @@ public async void Utf32BE() Response.ContentType = mediaType.ToString(); UTF32Encoding utf32BE = new(bigEndian: true, byteOrderMark: true); - byte[] body = utf32BE.GetPreamble() + utf32BE.GetBytes("hello"); + byte[] body = utf32BE.GetPreamble().Concat(utf32BE.GetBytes("hello")).ToArray(); await Response.Body.WriteAsync(body, 0, body.Length); } From 2d1107b015f5638fceb05f24c8099d9e913abc15 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 25 Mar 2023 10:11:23 +0100 Subject: [PATCH 11/18] reader.Peek() --- .../commands/utility/WebCmdlet/StreamHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index ccb6f4bb623..1beb811c72b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -335,6 +335,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); + reader.Peek(); encoding = reader.CurrentEncoding; if (isDefaultEncoding) From c64591a5f227b1b89824c9527632bb1b84a72805 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sun, 26 Mar 2023 14:19:28 +0200 Subject: [PATCH 12/18] comment out reader.Peek() --- .../commands/utility/WebCmdlet/StreamHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 1beb811c72b..0d9cf7bd81e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -335,7 +335,7 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); - reader.Peek(); + // reader.Peek(); encoding = reader.CurrentEncoding; if (isDefaultEncoding) From 79698f35926e90527168f2bad7477d7f3873b900 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sun, 26 Mar 2023 23:35:23 +0200 Subject: [PATCH 13/18] try fix --- .../commands/utility/WebCmdlet/StreamHelper.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 0d9cf7bd81e..ed5c9eae64f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -335,19 +335,17 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); - // reader.Peek(); + // We only look within the first 1k characters as the meta element and + // the xml declaration are at the start of the document + int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); + + char[] buffer = new char[bufferLength]; + reader.ReadBlock(buffer, 0, bufferLength); + stream.Seek(0, SeekOrigin.Begin); encoding = reader.CurrentEncoding; if (isDefaultEncoding) { - // We only look within the first 1k characters as the meta element and - // the xml declaration are at the start of the document - int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); - - char[] buffer = new char[bufferLength]; - reader.ReadBlock(buffer, 0, bufferLength); - stream.Seek(0, SeekOrigin.Begin); - string substring = new(buffer); // Check for a charset attribute on the meta element to override the default From 1a6707d38d65f85fc96920f66a0905207a194768 Mon Sep 17 00:00:00 2001 From: stevenebutler Date: Sat, 8 Apr 2023 17:59:25 +1000 Subject: [PATCH 14/18] Pr/19379 (#1) * Remove FormObject.cs and FormObjectCollection.cs (#19383) * Exclude redundant parameter aliases from completion results (#19382) * Revert "Remove FormObject.cs and FormObjectCollection.cs (#19383)" (#19387) This reverts commit 190c99a416b6f3c7e48066eadf3943d4ddccb2d8. * Add the parameter `-RelativeBasePath` to `Resolve-Path` (#19358) * Fix a crash in the type inference code (#19400) * Remove GetResponseObject (#19380) * Add `-Environment` parameter to `Start-Process` (#19374) * Add `-Environment` parameter to `Start-Process` * address codefactor * fix test for Windows * handle case where value is $null to remove env var * change variables to make it more clear what the test is doing * Add PoolNames variable group to compliance pipeline (#19408) * Improve package management acceptance tests by not going to the gallery (#19412) * Skip VT100 tests on Windows Server 2012R2 as console does not support it (#19413) * Update the `ICommandPredictor` interface to reduce boilerplate code from predictor implementation (#19414) * Enable type conversion of `AutomationNull` to `$null` for assignment (#19415) * Remove code related to `#requires -pssnapin` (#19320) * Support CTRL-C when reading data and connection hangs for `Invoke-RestMethod` and `Invoke-WebRequest` (#19330) * Update to the latest NOTICES file (#19332) * Update the cgmanifest (#19459) * WIP: Harden default command test. (#19416) --- ThirdPartyNotices.txt | 96 ++++++++++ .../commands/management/Process.cs | 48 ++++- .../commands/management/ResolvePathCommand.cs | 94 +++++++++- .../BasicHtmlWebResponseObject.Common.cs | 20 ++- .../Common/InvokeRestMethodCommand.Common.cs | 18 +- .../Common/WebRequestPSCmdlet.Common.cs | 7 +- .../Common/WebResponseObject.Common.cs | 29 ++-- .../InvokeWebRequestCommand.CoreClr.cs | 7 +- .../WebResponseObjectFactory.CoreClr.cs | 19 -- .../utility/WebCmdlet/StreamHelper.cs | 64 ++++--- .../CommandCompletion/CompletionCompleters.cs | 41 +++-- .../engine/CommandDiscovery.cs | 109 +----------- .../engine/ExternalScriptInfo.cs | 9 - .../engine/LanguagePrimitives.cs | 6 + .../FeedbackSubsystem/IFeedbackProvider.cs | 164 ------------------ .../PredictionSubsystem/CommandPrediction.cs | 1 - .../PredictionSubsystem/ICommandPredictor.cs | 16 +- .../engine/hostifaces/Connection.cs | 4 +- .../engine/hostifaces/LocalConnection.cs | 2 +- .../engine/parser/TypeInferenceVisitor.cs | 24 ++- .../engine/parser/ast.cs | 8 - .../engine/parser/tokenizer.cs | 3 - .../Host/TabCompletion/BugFix.Tests.ps1 | 3 +- .../TabCompletion/TabCompletion.Tests.ps1 | 20 ++- .../Scripting/Scripting.Followup.Tests.ps1 | 12 ++ .../SuppressAnsiEscapeSequence.Tests.ps1 | 7 + .../Resolve-Path.Tests.ps1 | 4 + .../Start-Process.Tests.ps1 | 31 +++- .../Get-Error.Tests.ps1 | 5 + .../WebCmdlets.Tests.ps1 | 117 +++++++++++++ .../PackageManagement.Tests.ps1 | 72 ++++---- .../assets/PowerShell.TestPackage.3.2.1.nupkg | Bin 0 -> 3225 bytes .../engine/Api/TypeInference.Tests.ps1 | 8 + .../engine/Basic/DefaultCommands.Tests.ps1 | 113 ++++++------ .../Formatting/OutputRendering.Tests.ps1 | 5 +- .../Modules/WebListener/WebListener.psm1 | 4 + .../Controllers/DelayController.cs | 119 ++++++++++++- test/tools/WebListener/Startup.cs | 16 ++ test/xUnit/csharp/test_Subsystem.cs | 10 -- tools/cgmanifest.json | 16 +- tools/releaseBuild/azureDevOps/compliance.yml | 1 + 41 files changed, 821 insertions(+), 531 deletions(-) delete mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs create mode 100644 test/powershell/Modules/PackageManagement/assets/PowerShell.TestPackage.3.2.1.nupkg diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index b945bd44d07..a03f292a93d 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -135,6 +135,24 @@ limitations under the License. --------------------------------------------------------- +Markdig.Signed 0.31.0 - BSD-2-Clause + + + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------- + +--------------------------------------------------------- + Microsoft.ApplicationInsights 2.21.0 - MIT @@ -218,6 +236,48 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.Common 4.5.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.CSharp 4.5.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) Microsoft Corporation. Alle Rechte + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + --------------------------------------------------------- --------------------------------------------------------- @@ -3682,6 +3742,42 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.NetFramingBase 6.0.0-preview1.23060.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index b731ce905cc..047aada0edd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -1823,6 +1823,26 @@ public SwitchParameter UseNewEnvironment private SwitchParameter _UseNewEnvironment; + /// + /// Gets or sets the environment variables for the process. + /// + [Parameter] + public Hashtable Environment + { + get + { + return _environment; + } + + set + { + _environment = value; + _isDefaultSetParameterSpecified = true; + } + } + + private Hashtable _environment; + #endregion #region overrides @@ -1929,8 +1949,13 @@ protected override void BeginProcessing() if (_UseNewEnvironment) { startInfo.EnvironmentVariables.Clear(); - LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)); - LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)); + LoadEnvironmentVariable(startInfo, System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)); + LoadEnvironmentVariable(startInfo, System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)); + } + + if (_environment != null) + { + LoadEnvironmentVariable(startInfo, _environment); } startInfo.WindowStyle = _windowstyle; @@ -2176,13 +2201,20 @@ private static void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDiction processEnvironment.Remove(entry.Key.ToString()); } - if (entry.Key.ToString().Equals("PATH")) + if (entry.Value != null) { - processEnvironment.Add(entry.Key.ToString(), Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.Machine) + ";" + Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.User)); - } - else - { - processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); + if (entry.Key.ToString().Equals("PATH")) + { +#if UNIX + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); +#else + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString() + Path.PathSeparator + System.Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.Machine) + Path.PathSeparator + System.Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.User)); +#endif + } + else + { + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); + } } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index 905a82c8e86..06e8b3f7c69 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -22,6 +22,8 @@ public class ResolvePathCommand : CoreCommandWithCredentialsBase /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Position = 0, ParameterSetName = "PathWithRelativeBase", + Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { get @@ -40,6 +42,8 @@ public string[] Path /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = "LiteralPathWithRelativeBase", + Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] [Alias("PSPath", "LP")] public string[] LiteralPath { @@ -59,7 +63,8 @@ public string[] LiteralPath /// Gets or sets the value that determines if the resolved path should /// be resolved to its relative version. /// - [Parameter()] + [Parameter(ParameterSetName = "Path")] + [Parameter(ParameterSetName = "LiteralPath")] public SwitchParameter Relative { get @@ -75,6 +80,25 @@ public SwitchParameter Relative private SwitchParameter _relative; + /// + /// Gets or sets the path the resolved relative path should be based off. + /// + [Parameter(Mandatory = true, ParameterSetName = "PathWithRelativeBase")] + [Parameter(Mandatory = true, ParameterSetName = "LiteralPathWithRelativeBase")] + public string RelativeBasePath + { + get + { + return _relativeBasePath; + } + + set + { + _relative = true; + _relativeBasePath = value; + } + } + #endregion Parameters #region parameter data @@ -84,10 +108,68 @@ public SwitchParameter Relative /// private string[] _paths; + private PSDriveInfo _relativeDrive; + private string _relativeBasePath; + #endregion parameter data #region Command code + /// + /// Finds the path and drive that should be used for relative path resolution + /// represents. + /// + protected override void BeginProcessing() + { + if (_relative) + { + if (!string.IsNullOrEmpty(RelativeBasePath)) + { + try + { + _relativeBasePath = SessionState.Internal.Globber.GetProviderPath(RelativeBasePath, CmdletProviderContext, out _, out _relativeDrive); + } + catch (ProviderNotFoundException providerNotFound) + { + ThrowTerminatingError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + } + catch (DriveNotFoundException driveNotFound) + { + ThrowTerminatingError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + } + catch (ProviderInvocationException providerInvocation) + { + ThrowTerminatingError( + new ErrorRecord( + providerInvocation.ErrorRecord, + providerInvocation)); + } + catch (NotSupportedException notSupported) + { + ThrowTerminatingError( + new ErrorRecord(notSupported, "ProviderIsNotNavigationCmdletProvider", ErrorCategory.InvalidArgument, RelativeBasePath)); + } + catch (InvalidOperationException invalidOperation) + { + ThrowTerminatingError( + new ErrorRecord(invalidOperation, "InvalidHomeLocation", ErrorCategory.InvalidOperation, RelativeBasePath)); + } + + return; + } + + _relativeDrive = SessionState.Path.CurrentLocation.Drive; + _relativeBasePath = SessionState.Path.CurrentLocation.ProviderPath; + return; + } + } + /// /// Resolves the path containing glob characters to the PowerShell paths that it /// represents. @@ -109,10 +191,9 @@ protected override void ProcessRecord() { // When result path and base path is on different PSDrive // (../)*path should not go beyond the root of base path - if (currentPath.Drive != SessionState.Path.CurrentLocation.Drive && - SessionState.Path.CurrentLocation.Drive != null && - !currentPath.ProviderPath.StartsWith( - SessionState.Path.CurrentLocation.Drive.Root, StringComparison.OrdinalIgnoreCase)) + if (currentPath.Drive != _relativeDrive && + _relativeDrive != null && + !currentPath.ProviderPath.StartsWith(_relativeDrive.Root, StringComparison.OrdinalIgnoreCase)) { WriteObject(currentPath.Path, enumerateCollection: false); continue; @@ -127,8 +208,7 @@ protected override void ProcessRecord() } baseCache = basePath; - string adjustedPath = SessionState.Path.NormalizeRelativePath(currentPath.Path, - SessionState.Path.CurrentLocation.ProviderPath); + string adjustedPath = SessionState.Path.NormalizeRelativePath(currentPath.Path, _relativeBasePath); // Do not insert './' if result path is not relative if (!adjustedPath.StartsWith( diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs index 8c15ac96d97..8eaa95c224a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs @@ -8,6 +8,7 @@ using System.Net.Http; using System.Text; using System.Text.RegularExpressions; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -21,18 +22,20 @@ public class BasicHtmlWebResponseObject : WebResponseObject /// /// Initializes a new instance of the class. /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response) : this(response, null) { } + /// The response. + /// Cancellation token. + public BasicHtmlWebResponseObject(HttpResponseMessage response, CancellationToken cancellationToken) : this(response, null, cancellationToken) { } /// /// Initializes a new instance of the class /// with the specified . /// - /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentStream) : base(response, contentStream) + /// The response. + /// The content stream associated with the response. + /// Cancellation token. + public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentStream, CancellationToken cancellationToken) : base(response, contentStream, cancellationToken) { - InitializeContent(); + InitializeContent(cancellationToken); InitializeRawContent(response); } @@ -141,7 +144,8 @@ public WebCmdletElementCollection Images /// /// Reads the response content from the web response. /// - protected void InitializeContent() + /// The cancellation token. + protected void InitializeContent(CancellationToken cancellationToken) { string contentType = ContentHelper.GetContentType(BaseResponse); if (ContentHelper.IsText(contentType)) @@ -149,7 +153,7 @@ protected void InitializeContent() // Fill the Content buffer string characterSet = WebResponseHelper.GetCharacterSet(BaseResponse); - Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out Encoding encoding); + Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out Encoding encoding, cancellationToken); Encoding = encoding; } else diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs index adfb4f1a610..3fe6e2a91ad 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs @@ -6,6 +6,7 @@ using System.Management.Automation; using System.Net.Http; using System.Text; +using System.Threading; using System.Xml; using Newtonsoft.Json; @@ -74,12 +75,13 @@ public int MaximumFollowRelLink internal override void ProcessResponse(HttpResponseMessage response) { ArgumentNullException.ThrowIfNull(response); + ArgumentNullException.ThrowIfNull(_cancelToken); - Stream baseResponseStream = StreamHelper.GetResponseStream(response); + Stream baseResponseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token); if (ShouldWriteToPipeline) { - using var responseStream = new BufferingStreamReader(baseResponseStream); + using var responseStream = new BufferingStreamReader(baseResponseStream, _cancelToken.Token); // First see if it is an RSS / ATOM feed, in which case we can // stream it - unless the user has overridden it with a return type of "XML" @@ -95,7 +97,7 @@ internal override void ProcessResponse(HttpResponseMessage response) // Try to get the response encoding from the ContentType header. string charSet = WebResponseHelper.GetCharacterSet(response); - string str = StreamHelper.DecodeStream(responseStream, charSet, out Encoding encoding); + string str = StreamHelper.DecodeStream(responseStream, charSet, out Encoding encoding, _cancelToken.Token); object obj = null; Exception ex = null; @@ -112,7 +114,7 @@ internal override void ProcessResponse(HttpResponseMessage response) // NOTE: Tests use this verbose output to verify the encoding. WriteVerbose(string.Create(System.Globalization.CultureInfo.InvariantCulture, $"Content encoding: {encodingVerboseName}")); - + bool convertSuccess = false; if (returnType == RestReturnType.Json) @@ -231,7 +233,7 @@ private bool TryProcessFeedStream(Stream responseStream) } } } - catch (XmlException) + catch (XmlException) { // Catch XmlException } @@ -349,17 +351,19 @@ public enum RestReturnType internal class BufferingStreamReader : Stream { - internal BufferingStreamReader(Stream baseStream) + internal BufferingStreamReader(Stream baseStream, CancellationToken cancellationToken) { _baseStream = baseStream; _streamBuffer = new MemoryStream(); _length = long.MaxValue; _copyBuffer = new byte[4096]; + _cancellationToken = cancellationToken; } private readonly Stream _baseStream; private readonly MemoryStream _streamBuffer; private readonly byte[] _copyBuffer; + private readonly CancellationToken _cancellationToken; public override bool CanRead => true; @@ -393,7 +397,7 @@ public override int Read(byte[] buffer, int offset, int count) // If we don't have enough data to fill this from memory, cache more. // We try to read 4096 bytes from base stream every time, so at most we // may cache 4095 bytes more than what is required by the Read operation. - int bytesRead = _baseStream.Read(_copyBuffer, 0, _copyBuffer.Length); + int bytesRead = _baseStream.ReadAsync(_copyBuffer, 0, _copyBuffer.Length, _cancellationToken).GetAwaiter().GetResult(); if (_streamBuffer.Position < _streamBuffer.Length) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index c078faaed5c..f979a763da8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -613,7 +613,7 @@ protected override void ProcessRecord() string detailMsg = string.Empty; try { - string error = StreamHelper.GetResponseString(response); + string error = StreamHelper.GetResponseString(response, _cancelToken.Token); detailMsg = FormatErrorMessage(error, contentType); } catch @@ -658,6 +658,11 @@ protected override void ProcessRecord() ThrowTerminatingError(er); } + finally + { + _cancelToken?.Dispose(); + _cancelToken = null; + } if (_followRelLink) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs index 5a55ccc2e73..8bfdeed2da4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs @@ -7,6 +7,7 @@ using System.IO; using System.Net.Http; using System.Text; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -74,19 +75,21 @@ public class WebResponseObject /// /// Initializes a new instance of the class. /// - /// - public WebResponseObject(HttpResponseMessage response) : this(response, null) + /// The Http response. + /// The cancellation token. + public WebResponseObject(HttpResponseMessage response, CancellationToken cancellationToken) : this(response, null, cancellationToken) { } /// /// Initializes a new instance of the class /// with the specified . /// - /// - /// - public WebResponseObject(HttpResponseMessage response, Stream contentStream) + /// Http response. + /// The http content stream. + /// The cancellation token. + public WebResponseObject(HttpResponseMessage response, Stream contentStream, CancellationToken cancellationToken) { - SetResponse(response, contentStream); + SetResponse(response, contentStream, cancellationToken); InitializeContent(); InitializeRawContent(response); } @@ -116,13 +119,13 @@ private void InitializeRawContent(HttpResponseMessage baseResponse) RawContent = raw.ToString(); } - private static bool IsPrintable(char c) => char.IsLetterOrDigit(c) - || char.IsPunctuation(c) - || char.IsSeparator(c) - || char.IsSymbol(c) + private static bool IsPrintable(char c) => char.IsLetterOrDigit(c) + || char.IsPunctuation(c) + || char.IsSeparator(c) + || char.IsSymbol(c) || char.IsWhiteSpace(c); - private void SetResponse(HttpResponseMessage response, Stream contentStream) + private void SetResponse(HttpResponseMessage response, Stream contentStream, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(response); @@ -138,7 +141,7 @@ private void SetResponse(HttpResponseMessage response, Stream contentStream) Stream st = contentStream; if (contentStream is null) { - st = StreamHelper.GetResponseStream(response); + st = StreamHelper.GetResponseStream(response, cancellationToken); } long contentLength = response.Content.Headers.ContentLength.Value; @@ -148,7 +151,7 @@ private void SetResponse(HttpResponseMessage response, Stream contentStream) } int initialCapacity = (int)Math.Min(contentLength, StreamHelper.DefaultReadBuffer); - RawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault()); + RawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault(), cancellationToken); } // Set the position of the content stream to the beginning diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs index 94245548e80..19ebc294c10 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs @@ -34,7 +34,7 @@ internal override void ProcessResponse(HttpResponseMessage response) { ArgumentNullException.ThrowIfNull(response); - Stream responseStream = StreamHelper.GetResponseStream(response); + Stream responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token); if (ShouldWriteToPipeline) { // creating a MemoryStream wrapper to response stream here to support IsStopping. @@ -42,8 +42,9 @@ internal override void ProcessResponse(HttpResponseMessage response) responseStream, StreamHelper.ChunkSize, this, - response.Content.Headers.ContentLength.GetValueOrDefault()); - WebResponseObject ro = WebResponseObjectFactory.GetResponseObject(response, responseStream, this.Context); + response.Content.Headers.ContentLength.GetValueOrDefault(), + _cancelToken.Token); + WebResponseObject ro = WebResponseHelper.IsText(response) ? new BasicHtmlWebResponseObject(response, responseStream, _cancelToken.Token) : new WebResponseObject(response, responseStream, _cancelToken.Token); ro.RelationLink = _relationLink; WriteObject(ro); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs deleted file mode 100644 index 4c8f1c40321..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.IO; -using System.Management.Automation; -using System.Net.Http; - -namespace Microsoft.PowerShell.Commands -{ - internal static class WebResponseObjectFactory - { - internal static WebResponseObject GetResponseObject(HttpResponseMessage response, Stream responseStream, ExecutionContext executionContext) - { - WebResponseObject output = WebResponseHelper.IsText(response) ? new BasicHtmlWebResponseObject(response, responseStream) : new WebResponseObject(response, responseStream); - - return output; - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index ed5c9eae64f..ffcb89d0dcd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -25,8 +25,9 @@ internal class WebResponseContentMemoryStream : MemoryStream private readonly long? _contentLength; private readonly Stream _originalStreamToProxy; - private bool _isInitialized = false; private readonly Cmdlet _ownerCmdlet; + private readonly CancellationToken _cancellationToken; + private bool _isInitialized = false; #endregion Data @@ -34,15 +35,17 @@ internal class WebResponseContentMemoryStream : MemoryStream /// /// Initializes a new instance of the class. /// - /// - /// + /// Response stream. + /// Presize the memory stream. /// Owner cmdlet if any. /// Expected download size in Bytes. - internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet, long? contentLength) : base(initialCapacity) + /// Cancellation token. + internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet, long? contentLength, CancellationToken cancellationToken) : base(initialCapacity) { this._contentLength = contentLength; _originalStreamToProxy = stream; _ownerCmdlet = cmdlet; + _cancellationToken = cancellationToken; } #endregion Constructors @@ -77,7 +80,7 @@ public override long Length /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.CopyToAsync(destination, bufferSize, cancellationToken); } @@ -102,7 +105,7 @@ public override int Read(byte[] buffer, int offset, int count) /// public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.ReadAsync(buffer, offset, count, cancellationToken); } @@ -153,7 +156,7 @@ public override void Write(byte[] buffer, int offset, int count) /// public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.WriteAsync(buffer, offset, count, cancellationToken); } @@ -182,15 +185,18 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - /// - /// - private void Initialize() + private void Initialize(CancellationToken cancellationToken = default) { - if (_isInitialized) + if (_isInitialized) { return; } + if (cancellationToken == default) + { + cancellationToken = _cancellationToken; + } + _isInitialized = true; try { @@ -220,7 +226,7 @@ private void Initialize() } } - read = _originalStreamToProxy.Read(buffer, 0, buffer.Length); + read = _originalStreamToProxy.ReadAsync(buffer, 0, buffer.Length, cancellationToken).GetAwaiter().GetResult(); if (read > 0) { @@ -237,11 +243,11 @@ private void Initialize() // Make sure the length is set appropriately base.SetLength(totalRead); - base.Seek(0, SeekOrigin.Begin); + Seek(0, SeekOrigin.Begin); } catch (Exception) { - base.Dispose(); + Dispose(); throw; } } @@ -329,10 +335,9 @@ internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet c WriteToStream(stream, output, cmdlet, contentLength, cancellationToken); } - internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) + private static async Task GetStreamEncodingAsync(Stream stream, string characterSet, CancellationToken cancellationToken) { - bool isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out encoding); - + bool isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out Encoding encoding); using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); // We only look within the first 1k characters as the meta element and @@ -340,23 +345,22 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); char[] buffer = new char[bufferLength]; - reader.ReadBlock(buffer, 0, bufferLength); - stream.Seek(0, SeekOrigin.Begin); + await reader.ReadBlockAsync(buffer.AsMemory(), cancellationToken); encoding = reader.CurrentEncoding; - + stream.Seek(0, SeekOrigin.Begin); if (isDefaultEncoding) { string substring = new(buffer); // Check for a charset attribute on the meta element to override the default Match match = s_metaRegex.Match(substring); - + // Check for a encoding attribute on the xml declaration to override the default if (!match.Success) { match = s_xmlRegex.Match(substring); } - + if (match.Success) { characterSet = match.Groups["charset"].Value; @@ -367,8 +371,14 @@ internal static string DecodeStream(Stream stream, string characterSet, out Enco } } } + return encoding; + } + + internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding, CancellationToken cancellationToken) + { + encoding = GetStreamEncodingAsync(stream, characterSet, cancellationToken).GetAwaiter().GetResult(); - return new StreamReader(stream, encoding, leaveOpen: true).ReadToEnd(); + return new StreamReader(stream, encoding, leaveOpen: true).ReadToEndAsync(cancellationToken).GetAwaiter().GetResult(); } internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding encoding) @@ -392,11 +402,11 @@ internal static bool TryGetEncodingFromCharset(string characterSet, out Encoding @"<]*charset\s*=\s*[""'\n]?(?[A-Za-z].[^\s""'\n<>]*)[\s""'\n>]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking ); - + private static readonly Regex s_xmlRegex = new( @"<\?xml\s.*[^.><]*encoding\s*=\s*[""'\n]?(?[A-Za-z].[^\s""'\n<>]*)[\s""'\n>]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking - ); + ); internal static byte[] EncodeToBytes(string str, Encoding encoding) { @@ -406,9 +416,9 @@ internal static byte[] EncodeToBytes(string str, Encoding encoding) return encoding.GetBytes(str); } - internal static string GetResponseString(HttpResponseMessage response) => response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + internal static string GetResponseString(HttpResponseMessage response, CancellationToken cancellationToken) => response.Content.ReadAsStringAsync(cancellationToken).GetAwaiter().GetResult(); - internal static Stream GetResponseStream(HttpResponseMessage response) => response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + internal static Stream GetResponseStream(HttpResponseMessage response, CancellationToken cancellationToken) => response.Content.ReadAsStreamAsync(cancellationToken).GetAwaiter().GetResult(); #endregion Static Methods } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 9b10aa31eab..7d7ecf9ab91 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -719,13 +719,21 @@ private static List GetParameterCompletionResults(string param string tooltip = parameterType + matchedParameterName; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } - - // Process alias when there is partial input - result.AddRange(from alias in param.Parameter.Aliases - where pattern.IsMatch(alias) - select - new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName, - parameterType + alias)); + else + { + // Process alias when there is partial input + foreach (var alias in param.Parameter.Aliases) + { + if (pattern.IsMatch(alias)) + { + result.Add(new CompletionResult( + $"-{alias}{colonSuffix}", + alias, + CompletionResultType.ParameterName, + parameterType + alias)); + } + } + } return result; } @@ -791,15 +799,20 @@ private static List GetParameterCompletionResults( tooltip)); } } - - if (parameterName != string.Empty) + else if (parameterName != string.Empty) { // Process alias when there is partial input - listInUse.AddRange(from alias in param.Parameter.Aliases - where pattern.IsMatch(alias) - select - new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName, - type + alias)); + foreach (var alias in param.Parameter.Aliases) + { + if (pattern.IsMatch(alias)) + { + listInUse.Add(new CompletionResult( + $"-{alias}{colonSuffix}", + alias, + CompletionResultType.ParameterName, + type + alias)); + } + } } } diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index 8efaff4d0a9..d0e2d568ab4 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -316,92 +316,25 @@ internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, Execut } } - private static Collection GetPSSnapinNames(IEnumerable PSSnapins) - { - Collection result = new Collection(); - - foreach (var PSSnapin in PSSnapins) - { - result.Add(BuildPSSnapInDisplayName(PSSnapin)); - } - - return result; - } - private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState) { VerifyScriptRequirements(scriptInfo, Context); - IEnumerable requiresPSSnapIns = scriptInfo.RequiresPSSnapIns; - if (requiresPSSnapIns != null && requiresPSSnapIns.Any()) - { - Collection requiresMissingPSSnapIns = null; - VerifyRequiredSnapins(requiresPSSnapIns, context, out requiresMissingPSSnapIns); - if (requiresMissingPSSnapIns != null) - { - ScriptRequiresException scriptRequiresException = - new ScriptRequiresException( - scriptInfo.Name, - requiresMissingPSSnapIns, - "ScriptRequiresMissingPSSnapIns", - true); - throw scriptRequiresException; - } - } - else + if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) { - // If there were no PSSnapins required but there is a shellID required, then we need - // to error + ScriptRequiresException sre = + new ScriptRequiresException( + scriptInfo.Name, + string.Empty, + string.Empty, + "RequiresShellIDInvalidForSingleShell"); - if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) - { - ScriptRequiresException sre = - new ScriptRequiresException( - scriptInfo.Name, - string.Empty, - string.Empty, - "RequiresShellIDInvalidForSingleShell"); - - throw sre; - } + throw sre; } return CreateCommandProcessorForScript(scriptInfo, Context, useLocalScope, sessionState); } - private static void VerifyRequiredSnapins(IEnumerable requiresPSSnapIns, ExecutionContext context, out Collection requiresMissingPSSnapIns) - { - requiresMissingPSSnapIns = null; - Dbg.Assert(context.InitialSessionState != null, "PowerShell should be hosted with InitialSessionState"); - - foreach (var requiresPSSnapIn in requiresPSSnapIns) - { - var loadedPSSnapIn = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name); - if (loadedPSSnapIn is null) - { - requiresMissingPSSnapIns ??= new Collection(); - requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn)); - } - else - { - // the requires PSSnapin is loaded. now check the PSSnapin version - Dbg.Assert(loadedPSSnapIn.Version != null, - string.Format( - CultureInfo.InvariantCulture, - "Version is null for loaded PSSnapin {0}.", loadedPSSnapIn)); - if (requiresPSSnapIn.Version != null) - { - if (!AreInstalledRequiresVersionsCompatible( - requiresPSSnapIn.Version, loadedPSSnapIn.Version)) - { - requiresMissingPSSnapIns ??= new Collection(); - requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn)); - } - } - } - } - } - // This method verifies the following 3 elements of #Requires statement // #Requires -RunAsAdministrator // #Requires -PSVersion @@ -481,32 +414,6 @@ internal static void VerifyElevatedPrivileges(ExternalScriptInfo scriptInfo) } } - /// - /// Used to determine compatibility between the versions in the requires statement and - /// the installed version. The version can be PSSnapin or msh. - /// - /// Versions in the requires statement. - /// Version installed. - /// - /// true if requires and installed's major version match and requires' minor version - /// is smaller than or equal to installed's - /// - /// - /// In PowerShell V2, script requiring PowerShell 1.0 will fail. - /// - private static bool AreInstalledRequiresVersionsCompatible(Version requires, Version installed) - { - return requires.Major == installed.Major && requires.Minor <= installed.Minor; - } - - private static string BuildPSSnapInDisplayName(PSSnapInSpecification PSSnapin) - { - return PSSnapin.Version == null ? - PSSnapin.Name : - StringUtil.Format(DiscoveryExceptions.PSSnapInNameVersion, - PSSnapin.Name, PSSnapin.Version); - } - /// /// Look up a command using a CommandInfo object and return its CommandProcessorBase. /// diff --git a/src/System.Management.Automation/engine/ExternalScriptInfo.cs b/src/System.Management.Automation/engine/ExternalScriptInfo.cs index 31a520d6070..8e0938605ac 100644 --- a/src/System.Management.Automation/engine/ExternalScriptInfo.cs +++ b/src/System.Management.Automation/engine/ExternalScriptInfo.cs @@ -458,15 +458,6 @@ internal uint PSVersionLineNumber get { return 0; } } - internal IEnumerable RequiresPSSnapIns - { - get - { - var data = GetRequiresData(); - return data?.RequiresPSSnapIns; - } - } - /// /// Gets the original contents of the script. /// diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index b1c7cab0ace..ac3c1c1a70c 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -4691,6 +4691,12 @@ internal static PSObject SetObjectProperties(object o, IDictionary properties, T } } + // treat AutomationNull.Value as null for consistency + if (propValue == AutomationNull.Value) + { + propValue = null; + } + property.Value = propValue; } else diff --git a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs index 0e126f9f27a..4a680f30db6 100644 --- a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs @@ -188,168 +188,4 @@ internal GeneralCommandErrorFeedback() return null; } } - - internal sealed class UnixCommandNotFound : IFeedbackProvider, ICommandPredictor - { - private readonly Guid _guid; - private List? _candidates; - - internal UnixCommandNotFound() - { - _guid = new Guid("47013747-CB9D-4EBC-9F02-F32B8AB19D48"); - } - - Dictionary? ISubsystem.FunctionsToDefine => null; - - public Guid Id => _guid; - - public string Name => "cmd-not-found"; - - public string Description => "The built-in feedback/prediction source for the Unix command utility."; - - #region IFeedbackProvider - - private static string? GetUtilityPath() - { - string cmd_not_found = "/usr/lib/command-not-found"; - bool exist = IsFileExecutable(cmd_not_found); - - if (!exist) - { - cmd_not_found = "/usr/share/command-not-found/command-not-found"; - exist = IsFileExecutable(cmd_not_found); - } - - return exist ? cmd_not_found : null; - - static bool IsFileExecutable(string path) - { - var file = new FileInfo(path); - return file.Exists && file.UnixFileMode.HasFlag(UnixFileMode.OtherExecute); - } - } - - public FeedbackItem? GetFeedback(string commandLine, ErrorRecord lastError, CancellationToken token) - { - if (Platform.IsWindows || lastError.FullyQualifiedErrorId != "CommandNotFoundException") - { - return null; - } - - var target = (string)lastError.TargetObject; - if (target is null) - { - return null; - } - - if (target.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - string? cmd_not_found = GetUtilityPath(); - if (cmd_not_found is not null) - { - var startInfo = new ProcessStartInfo(cmd_not_found); - startInfo.ArgumentList.Add(target); - startInfo.RedirectStandardError = true; - startInfo.RedirectStandardOutput = true; - - using var process = Process.Start(startInfo); - if (process is not null) - { - string? header = null; - List? actions = null; - - while (true) - { - string? line = process.StandardError.ReadLine(); - if (line is null) - { - break; - } - - if (line == string.Empty) - { - continue; - } - - if (line.StartsWith("sudo ", StringComparison.Ordinal)) - { - actions ??= new List(); - actions.Add(line.TrimEnd()); - } - else if (actions is null) - { - header = line; - } - } - - if (actions is not null && header is not null) - { - _candidates = actions; - - var footer = process.StandardOutput.ReadToEnd().Trim(); - return string.IsNullOrEmpty(footer) - ? new FeedbackItem(header, actions) - : new FeedbackItem(header, actions, footer, FeedbackDisplayLayout.Portrait); - } - } - } - - return null; - } - - #endregion - - #region ICommandPredictor - - public bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback) - { - return feedback switch - { - PredictorFeedbackKind.CommandLineAccepted => true, - _ => false, - }; - } - - public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken) - { - if (_candidates is not null) - { - string input = context.InputAst.Extent.Text; - List? result = null; - - foreach (string c in _candidates) - { - if (c.StartsWith(input, StringComparison.OrdinalIgnoreCase)) - { - result ??= new List(_candidates.Count); - result.Add(new PredictiveSuggestion(c)); - } - } - - if (result is not null) - { - return new SuggestionPackage(result); - } - } - - return default; - } - - public void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) - { - // Reset the candidate state. - _candidates = null; - } - - public void OnSuggestionDisplayed(PredictionClient client, uint session, int countOrIndex) { } - - public void OnSuggestionAccepted(PredictionClient client, uint session, string acceptedSuggestion) { } - - public void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { } - - #endregion; - } } diff --git a/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs index 3e06d046481..edb46960612 100644 --- a/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Threading; using System.Threading.Tasks; diff --git a/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs index 1c632c541c4..275dc1733b6 100644 --- a/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs @@ -36,7 +36,7 @@ public interface ICommandPredictor : ISubsystem /// Represents the client that initiates the call. /// A specific type of feedback. /// True or false, to indicate whether the specific feedback is accepted. - bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback); + bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback) => false; /// /// One or more suggestions provided by the predictor were displayed to the user. @@ -47,7 +47,7 @@ public interface ICommandPredictor : ISubsystem /// When the value is greater than 0, it's the number of displayed suggestions from the list returned in , starting from the index 0. /// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value. /// - void OnSuggestionDisplayed(PredictionClient client, uint session, int countOrIndex); + void OnSuggestionDisplayed(PredictionClient client, uint session, int countOrIndex) { } /// /// The suggestion provided by the predictor was accepted. @@ -55,7 +55,7 @@ public interface ICommandPredictor : ISubsystem /// Represents the client that initiates the call. /// Represents the mini-session where the accepted suggestion came from. /// The accepted suggestion text. - void OnSuggestionAccepted(PredictionClient client, uint session, string acceptedSuggestion); + void OnSuggestionAccepted(PredictionClient client, uint session, string acceptedSuggestion) { } /// /// A command line was accepted to execute. @@ -63,7 +63,7 @@ public interface ICommandPredictor : ISubsystem /// /// Represents the client that initiates the call. /// History command lines provided as references for prediction. - void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history); + void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) { } /// /// A command line was done execution. @@ -71,7 +71,7 @@ public interface ICommandPredictor : ISubsystem /// Represents the client that initiates the call. /// The last accepted command line. /// Shows whether the execution was successful. - void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success); + void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { } } /// @@ -131,6 +131,12 @@ public sealed class PredictionClient /// public PredictionClientKind Kind { get; } + /// + /// Gets the current location of the default session. + /// It returns null if there is no default Runspace or if the default is a remote Runspace. + /// + public PathInfo? CurrentLocation { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index a637655aeeb..50c7e49f92d 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -703,7 +703,7 @@ public Guid InstanceId /// /// Runspace is not opened. /// - internal System.Management.Automation.ExecutionContext ExecutionContext + internal ExecutionContext ExecutionContext { get { @@ -1577,7 +1577,7 @@ protected virtual void Dispose(bool disposing) /// /// Gets the execution context. /// - internal abstract System.Management.Automation.ExecutionContext GetExecutionContext + internal abstract ExecutionContext GetExecutionContext { get; } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 64c9e19516f..b88a466623d 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -237,7 +237,7 @@ protected override Pipeline CoreCreatePipeline(string command, bool addToHistory /// /// Gets the execution context. /// - internal override System.Management.Automation.ExecutionContext GetExecutionContext + internal override ExecutionContext GetExecutionContext { get { diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 5f2288e0106..afa33d0e918 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -739,19 +739,28 @@ object ICustomAstVisitor.VisitSubExpression(SubExpressionAst subExpressionAst) object ICustomAstVisitor.VisitErrorStatement(ErrorStatementAst errorStatementAst) { var inferredTypes = new List(); - foreach (var ast in errorStatementAst.Conditions) + if (errorStatementAst.Conditions is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.Conditions) + { + inferredTypes.AddRange(InferTypes(ast)); + } } - foreach (var ast in errorStatementAst.Bodies) + if (errorStatementAst.Bodies is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.Bodies) + { + inferredTypes.AddRange(InferTypes(ast)); + } } - foreach (var ast in errorStatementAst.NestedAst) + if (errorStatementAst.NestedAst is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.NestedAst) + { + inferredTypes.AddRange(InferTypes(ast)); + } } return inferredTypes; @@ -1944,7 +1953,8 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List public ReadOnlyCollection RequiredModules { get; internal set; } - /// - /// The snapins this script requires, specified like: - /// #requires -PSSnapin Snapin - /// #requires -PSSnapin Snapin -Version 2 - /// If no snapins are required, this property is an empty collection. - /// - public ReadOnlyCollection RequiresPSSnapIns { get; internal set; } - /// /// The assemblies this script requires, specified like: /// #requires -Assembly path\to\foo.dll diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 8b19c7dc385..3d0aa3a18aa 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -1976,9 +1976,6 @@ internal ScriptRequirements GetScriptRequirements() RequiredPSEditions = requiredEditions != null ? new ReadOnlyCollection(requiredEditions) : ScriptRequirements.EmptyEditionCollection, - RequiresPSSnapIns = requiredSnapins != null - ? new ReadOnlyCollection(requiredSnapins) - : ScriptRequirements.EmptySnapinCollection, RequiredAssemblies = requiredAssemblies != null ? new ReadOnlyCollection(requiredAssemblies) : ScriptRequirements.EmptyAssemblyCollection, diff --git a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 index 3cc44f3c0e1..dabb215cb16 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -28,10 +28,9 @@ Describe "Tab completion bug fix" -Tags "CI" { It "Issue#1345 - 'Import-Module -n' should work" { $cmd = "Import-Module -n" $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches | Should -HaveCount 3 + $result.CompletionMatches | Should -HaveCount 2 $result.CompletionMatches[0].CompletionText | Should -BeExactly "-Name" $result.CompletionMatches[1].CompletionText | Should -BeExactly "-NoClobber" - $result.CompletionMatches[2].CompletionText | Should -BeExactly "-NoOverwrite" } It "Issue#11227 - [CompletionCompleters]::CompleteVariable and [CompletionCompleters]::CompleteType should work" { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index bf3195b7aa0..d7c98b1589d 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -871,6 +871,18 @@ class InheritedClassTest : System.Attribute $res.CompletionMatches.CompletionText | Should -Contain 'TypeId' } + it 'Should not complete parameter aliases if the real parameter is in the completion results' { + $res = TabExpansion2 -inputScript 'Get-ChildItem -p' + $res.CompletionMatches.CompletionText | Should -Not -Contain '-proga' + $res.CompletionMatches.CompletionText | Should -Contain '-ProgressAction' + } + + it 'Should not complete parameter aliases if the real parameter is in the completion results (Non ambiguous parameters)' { + $res = TabExpansion2 -inputScript 'Get-ChildItem -prog' + $res.CompletionMatches.CompletionText | Should -Not -Contain '-proga' + $res.CompletionMatches.CompletionText | Should -Contain '-ProgressAction' + } + Context "Script name completion" { BeforeAll { Setup -f 'install-powershell.ps1' -Content "" @@ -1646,15 +1658,15 @@ dir -Recurse ` It "Test completion with exact match" { $inputStr = 'get-content -wa' $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches | Should -HaveCount 4 - [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-wa,-Wait,-WarningAction,-WarningVariable" + $res.CompletionMatches | Should -HaveCount 3 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-Wait,-WarningAction,-WarningVariable" } It "Test completion with splatted variable" { $inputStr = 'Get-Content @Splat -P' $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches | Should -HaveCount 6 - [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-Path,-PipelineVariable,-proga,-ProgressAction,-PSPath,-pv" + $res.CompletionMatches | Should -HaveCount 4 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-Path,-PipelineVariable,-ProgressAction,-PSPath" } It "Test completion for HttpVersion parameter name" { diff --git a/test/powershell/Language/Scripting/Scripting.Followup.Tests.ps1 b/test/powershell/Language/Scripting/Scripting.Followup.Tests.ps1 index 888c43b3578..fff4993ffec 100644 --- a/test/powershell/Language/Scripting/Scripting.Followup.Tests.ps1 +++ b/test/powershell/Language/Scripting/Scripting.Followup.Tests.ps1 @@ -30,6 +30,18 @@ Describe "Scripting.Followup.Tests" -Tags "CI" { $arraylist.Count | Should -Be 0 } + It 'AutomationNull should be same as null for type conversion' { + $result = pwsh -noprofile -command { + class Example { + [string[]]$LogMessage + } + + [Example] @{ LogMessage = [System.Management.Automation.Internal.AutomationNull]::Value } + } + + $result.LogMessage | Should -BeNullOrEmpty + } + ## fix https://github.com/PowerShell/PowerShell/issues/17165 It "([bool] `$var = 42) should return the varaible value" { ([bool]$var = 42).GetType().FullName | Should -Be "System.Boolean" diff --git a/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 b/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 index 30a82309a70..0530aea93ee 100644 --- a/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 +++ b/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 @@ -3,12 +3,19 @@ Describe '$env:__SuppressAnsiEscapeSequences tests' -Tag CI { BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + + if (-not $host.ui.SupportsVirtualTerminal) { + $global:PSDefaultParameterValues["it:skip"] = $true + } + $originalSuppressPref = $env:__SuppressAnsiEscapeSequences $originalRendering = $PSStyle.OutputRendering $PSStyle.OutputRendering = 'Ansi' } AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues $env:__SuppressAnsiEscapeSequences = $originalSuppressPref $PSStyle.OutputRendering = $originalRendering } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 index 87195c26352..91a16eb507b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 @@ -48,4 +48,8 @@ Describe "Resolve-Path returns proper path" -Tag "CI" { Pop-Location } } + It 'Resolve-Path should support user specified base paths' { + $Expected = Join-Path -Path .\ -ChildPath fakeroot + Resolve-Path -Path $fakeRoot -RelativeBasePath $testRoot | Should -BeExactly $Expected + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 index db89dd681fc..30bc1e676dc 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 @@ -181,7 +181,7 @@ Describe "Start-Process tests requiring admin" -Tags "Feature","RequireAdminOnWi } } -Describe "Start-Process" -Tags "Feature" { +Describe "Environment Tests" -Tags "Feature" { It "UseNewEnvironment parameter should reset environment variables for child process" { @@ -206,4 +206,33 @@ Describe "Start-Process" -Tags "Feature" { $env:TestEnvVariable = $null } } + + It '-Environment adds or replaces environment variables to child process' { + $outputfile = Join-Path -Path $TestDrive -ChildPath output.txt + Start-Process pwsh -ArgumentList '-NoProfile','-Nologo','-OutputFormat xml','-Command get-childitem env:' -Wait -Environment @{ a = 1; B = 'hello'; TERM = 'dumb'; PATH = 'mine' } -RedirectStandardOutput $outputfile + $out = Import-Clixml $outputfile + ($out | Where-Object { $_.Name -eq 'a' }).Value | Should -Be 1 + ($out | Where-Object { $_.Name -eq 'B' }).Value | Should -BeExactly 'hello' + ($out | Where-Object { $_.Name -eq 'TERM' }).Value | Should -BeExactly 'dumb' + $pathSeparator = [System.IO.Path]::PathSeparator + if ($IsWindows) { + ($out | Where-Object { $_.Name -eq 'PATH' }).Value | Should -BeLike "*${pathSeparator}mine${pathSeparator}*" + } else { + ($out | Where-Object { $_.Name -eq 'PATH' }).Value | Should -BeLike "*${pathSeparator}mine" + } + } + + It '-Environment can remove an environment variable from child process' { + try { + $env:existing = 1 # set a variable that we will remove + $env:nonexisting = $null # validate that removing a non-existing variable is a no-op + $outputfile = Join-Path -Path $TestDrive -ChildPath output.txt + Start-Process pwsh -ArgumentList '-NoProfile','-Nologo','-OutputFormat xml','-Command get-childitem env:' -Wait -Environment @{ existing = $null; nonexisting = $null } -RedirectStandardOutput $outputfile + $out = Import-Clixml $outputfile + $out | Where-Object { $_.Name -eq 'existing' } | Should -BeNullOrEmpty + $out | Where-Object { $_.Name -eq 'nonexisting' } | Should -BeNullOrEmpty + } finally { + $env:existing = $null + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 index 1d89bf6c1fa..4f86ee561cb 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 @@ -119,6 +119,11 @@ Describe 'Get-Error tests' -Tag CI { } It 'Get-Error uses Error color for Message and PositionMessage members' { + + if (-not $host.ui.SupportsVirtualTerminal) { + Set-ItResult -Skipped -Because 'Windows Server 2012 R2 does not support VT100 escape sequences' + } + $suppressVT = $false if (Test-Path env:/__SuppressAnsiEscapeSequences) { $suppressVT = $true diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index d0c9b3f0688..9f89dafddde 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -4216,3 +4216,120 @@ Describe "Web cmdlets tests using the cmdlet's aliases" -Tags "CI", "RequireAdmi { Invoke-RestMethod -Uri $uri -ContentType $null } | Should -Not -Throw } } + +Describe 'Invoke-WebRequest and Invoke-RestMethod support Cancellation through CTRL-C' -Tags "CI", "RequireAdminOnWindows" { + BeforeAll { + $oldProgress = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + $WebListener = Start-WebListener + } + + AfterAll { + $ProgressPreference = $oldProgress + } + + function RunWithCancellation { + param( + [string]$Command = 'Invoke-WebRequest', + [string]$Arguments = '', + [uri]$Uri, + [int]$DelayMs = 100, + [switch]$WillComplete + ) + + $pwsh = [PowerShell]::Create() + $invoke = "`$result = $Command -Uri `"$Uri`" $Arguments" + $task = $pwsh.AddScript($invoke).InvokeAsync() + Start-Sleep -Milliseconds $DelayMs + $task.IsCompleted | Should -Be $WillComplete.ToBool() + $pwsh.Stop() + Wait-UntilTrue { [bool]($Task.IsCompleted) } | Should -BeTrue + $result = $pwsh.Runspace.SessionStateProxy.GetVariable('result') + $pwsh.Dispose() + + return $result + } + + It 'Invoke-WebRequest: CTRL-C Cancels request before request headers received' { + $uri = Get-WebListenerUrl -test Delay -TestValue 30 + RunWithCancellation -Uri $uri -DelayMs 0 + } + + It 'Invoke-WebRequest: CTRL-C Cancels request after request headers received' { + $uri = Get-WebListenerUrl -test Stall -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri + } + + It 'Invoke-WebRequest: HTTPS CTRL-C Cancels request after request headers' { + $uri = Get-WebListenerUrl -Https -Test Stall -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri -Arguments "-SkipCertificateCheck" + } + + It 'Invoke-WebRequest: Compression CTRL-C Cancels request after request headers' { + $uri = Get-WebListenerUrl -Test StallBrotli -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri + $uri = Get-WebListenerUrl -Test StallGzip -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri + $uri = Get-WebListenerUrl -Test StallDeflate -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri + } + + It 'Invoke-WebRequest: HTTPS with compression CTRL-C Cancels request after request headers' { + $uri = Get-WebListenerUrl -Https -Test StallBrotli -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri -Arguments '-SkipCertificateCheck' + $uri = Get-WebListenerUrl -Https -Test StallGzip -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri -Arguments '-SkipCertificateCheck' + $uri = Get-WebListenerUrl -Https -Test StallDeflate -TestValue '30/application%2fjson' + RunWithCancellation -Uri $uri -Arguments '-SkipCertificateCheck' + } + + It 'Invoke-WebRequest: CTRL-C Cancels file download request after request headers received' { + $uri = Get-WebListenerUrl -Test Stall -TestValue '30' + $outFile = Join-Path $TestDrive "output.txt" + RunWithCancellation -Uri $uri -Arguments "-OutFile $outFile" -DelayMs 300 + # No guarantee the file will be present since the D/L is interrupted + if (Test-Path -Path $outFile) { + Remove-Item -Path $outFile + } + } + + It 'Invoke-WebRequest: CTRL-C after stalled file download completes gives entire file' { + $uri = Get-WebListenerUrl -test Stall -TestValue '1' + $outFile = Join-Path $TestDrive "output.txt" + RunWithCancellation -Uri $uri -Arguments "-OutFile $outFile" -DelayMs 1200 -WillComplete + Get-content -Path $outFile | should -be 'Hello worldHello world' + Remove-Item -Path $outFile + } + + It 'Invoke-RestMethod: CTRL-C Cancels request before request headers received' { + $uri = Get-WebListenerUrl -test Delay -TestValue 30 + RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri -DelayMs 0 + } + + It 'Invoke-RestMethod: CTRL-C Cancels request after JSON request headers received' { + $uri = Get-WebListenerUrl -test Stall -TestValue '30/application%2fjson' + RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri + } + + It 'Invoke-RestMethod: CTRL-C after stalled JSON download processes JSON response' { + $uri = Get-WebListenerUrl -test Stall -TestValue '1/application%2fjson' + $result = RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri -DelayMs 1200 -WillComplete + $result.name3 | should -be 'value3' + } + + It 'Invoke-RestMethod: CTRL-C Cancels request after plain request headers received' { + $uri = Get-WebListenerUrl -test Stall -TestValue '30' + RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri + } + + It 'Invoke-RestMethod: CTRL-C after stalled atom feed download processes atom response' { + $uri = Get-WebListenerUrl -test Stall -TestValue '1/application%2fxml' + $result = RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri -DelayMs 1200 -WillComplete + $result.title | should -be 'Atom-Powered Robots Run Amok' + } + + It 'Invoke-RestMethod: CTRL-C Cancels request in XML atom processing' { + $uri = Get-WebListenerUrl -test Stall -TestValue '30/application%2fxml' + RunWithCancellation -Command 'Invoke-RestMethod' -Uri $uri + } +} diff --git a/test/powershell/Modules/PackageManagement/PackageManagement.Tests.ps1 b/test/powershell/Modules/PackageManagement/PackageManagement.Tests.ps1 index 3fea57db36f..bd4d0218f5d 100644 --- a/test/powershell/Modules/PackageManagement/PackageManagement.Tests.ps1 +++ b/test/powershell/Modules/PackageManagement/PackageManagement.Tests.ps1 @@ -13,34 +13,36 @@ # # ------------------ PackageManagement Test ----------------------------------- -$gallery = "https://www.powershellgallery.com/api/v2" -$source = 'OneGetTestSource' - Describe "PackageManagement Acceptance Test" -Tags "Feature" { - BeforeAll{ - Register-PackageSource -Name Nugettest -provider NuGet -Location https://www.nuget.org/api/v2 -Force + BeforeAll { + # the package name for testing + $packageName = "PowerShell.TestPackage" + + # register the asset directory + $localSourceName = [Guid]::NewGuid().ToString("n") + $localSourceLocation = Join-Path $PSScriptRoot assets + Register-PackageSource -Name $localSourceName -provider NuGet -Location $localSourceLocation -Force -Trusted + + # register the gallery location + $galleryLocation = "https://www.powershellgallery.com/api/v2" + $gallerySourceName = [Guid]::newGuid().ToString("n") + Register-PackageSource -Name $gallerySourceName -Location $galleryLocation -ProviderName 'PowerShellGet' -Trusted -ErrorAction SilentlyContinue + + $SavedProgressPreference = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + } - $packageSource = Get-PackageSource -Location $gallery -ErrorAction SilentlyContinue - if ($packageSource) { - $source = $packageSource.Name - Set-PackageSource -Name $source -Trusted - } else { - Register-PackageSource -Name $source -Location $gallery -ProviderName 'PowerShellGet' -Trusted -ErrorAction SilentlyContinue + AfterAll { + $ProgressPreference = $SavedProgressPreference + Unregister-PackageSource -Source $localSourceName -ErrorAction Ignore + Unregister-PackageSource -Name $gallerySourceName -ErrorAction Ignore + Uninstall-Module NanoServerPackage -ErrorAction Ignore -WarningAction SilentlyContinue } - $SavedProgressPreference = $ProgressPreference - $ProgressPreference = "SilentlyContinue" - } - AfterAll { - $ProgressPreference = $SavedProgressPreference - } It "get-packageprovider" { - $gpp = Get-PackageProvider - $gpp.Name | Should -Contain 'NuGet' - $gpp.Name | Should -Contain 'PowerShellGet' } @@ -50,32 +52,40 @@ Describe "PackageManagement Acceptance Test" -Tags "Feature" { } It "install-packageprovider, Expect succeed" { - $ipp = (Install-PackageProvider -Name NanoServerPackage -Force -Source $source -Scope CurrentUser).name + Set-ItResult -Pending -Because "local test package provider not installable" + $ippArgs = @{ + Name = "NanoServerPackage" + Force = $true + Source = $galleryLocation + Scope = "CurrentUser" + WarningAction = "SilentlyContinue" + } + $ipp = (Install-PackageProvider @ippArgs).name $ipp | Should -Contain "NanoServerPackage" } It "Find-package" { - $f = Find-Package -ProviderName NuGet -Name jquery -Source Nugettest - $f.Name | Should -Contain "jquery" + $f = Find-Package -ProviderName NuGet -Name $packageName -Source $localSourceName + $f.Name | Should -Contain "$packageName" } It "Install-package" { - $i = Install-Package -ProviderName NuGet -Name jquery -Force -Source Nugettest -Scope CurrentUser - $i.Name | Should -Contain "jquery" + $i = Install-Package -ProviderName NuGet -Name $packageName -Force -Source $localSourceName -Scope CurrentUser + $i.Name | Should -Contain "$packageName" } It "Get-package" { - $g = Get-Package -ProviderName NuGet -Name jquery - $g.Name | Should -Contain "jquery" + $g = Get-Package -ProviderName NuGet -Name $packageName + $g.Name | Should -Contain "$packageName" } It "save-package" { - $s = Save-Package -ProviderName NuGet -Name jquery -Path $TestDrive -Force -Source Nugettest - $s.Name | Should -Contain "jquery" + $s = Save-Package -ProviderName NuGet -Name $packageName -Path $TestDrive -Force -Source $localSourceName + $s.Name | Should -Contain "$packageName" } It "uninstall-package" { - $u = Uninstall-Package -ProviderName NuGet -Name jquery - $u.Name | Should -Contain "jquery" + $u = Uninstall-Package -ProviderName NuGet -Name $packageName + $u.Name | Should -Contain "$packageName" } } diff --git a/test/powershell/Modules/PackageManagement/assets/PowerShell.TestPackage.3.2.1.nupkg b/test/powershell/Modules/PackageManagement/assets/PowerShell.TestPackage.3.2.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..bc3a6d9b094ff479b0acef2b1e54415e559c339d GIT binary patch literal 3225 zcmb_ecU03$8Vy}Rib(Y-hy|1qLMMWvB1ixUH7cTzgh&ku5K10B5I{sB5E1DmAWe9R zG=b1jKp@ha5D^fh3ZW=%;}OPy%i-QxuE{WeVYXO~9FDJpd^KrOxNN-$ey%!- zH3Sp7h?IU#-7+t?w=i&xG zlPCCL*gB$my5C|Hadqhsqa@*~u25Qp@S*PLDoD&Gmf(MT)2#f8r@9?E)VVO?EK5Z{8>^YVoiy zf-Gr%^FcdgD4Ihh(d#8?re&Q~Ys^7VA_RRVOT*%n$ROEiC2?5uHY=LChVI*888>G?N zV*DHQ%R99iO-q+5UQ{?waxMEUZ?938H)}T&V}?s5E#e}rLF(#}1Lr78-yN)*X26S5T^VRtg0&M73oM$Uf zEP`j7v|gIb2yspT@I+$L_zSy#eqtg)TR6TyxP(3NiFNktk)xGJsjkAbWpN=moBl0F z=R(OoWWgKs;DHz1@016Mw|81An%?n-t&zui4DqLHi8c#xXV)vAT8?~@A5rMB)h@c% zM|$^`mPdOPr|^YPwR}8_|EA{ms1OHmuA=tF=_bMLnFkD3XZQj}@eZVlC;REa8Lt1B3Mzb3nOLKSSQ$}Z1&UcK=^0)cTY5GNvBd!EJ=%Llf z;bE&9XX>QBdD1Ooq@B^vTy4_4^eh4$pb33Da;8>$KEXvjhe;9yGS-MW^v%mws2Elh*_8zF+K*6IQi=TC3aA(=b(CyRA+R@7HCm0onI-XfBOj zA_}E5kh*Yu<8w=e+p;`k1F)&~x*)%C-81?$dAFPba3Qa5<_tTGXcS;Kmw@I_k}I(c zwd&8edeXUNtuU%Cd}BBGMjxx~t12mMtTTIYMSl0Kq`AsKJMq8|YVUHwt*|9;Yab*u z?9N7i&2?uPo2Yptrvs}ypSdwZoi*6|zB`BE>B|Bo7|J$VznEPTK{$MgsPI#FqptMk z3q9ED+Ptp;Gi0P~y!rwe1eYUL3W9G~uZ|gA47K&$P4&HBn((oTS{(`P0P1UDb8Xh} zXQT6)zQjg#J9A~j&Iv4{`jUMVS-uNapv%Hy%P? zH=BIipFjWly}5d!L9DrwXY@njb<;r@iMz$-Yx`%FYK&whsdnT2Z`p%gH99~9<#T{` z4uj))o#N3MEN>{aCKQzMIR@#A^tjE^APot-g&OTXzrWWKh1j!uBvX?4|&ZcCrs=K zx(lw>#kTG^3teLn&}foJ4>Ck`Rp8AP=*P%8+R3V(aYdiUcKsRa@WHde={)Bs^9mMO z?f?sbBG2!o5K7=&eE$A1={qSbGkmi$bc@V_cQ2x%R1IwO)AdfXx!;-cK5WGJri9al zb(5M_a$<-F++A&24+)qNbHmq_pRO)gyx8RtlQ^lm@vuHf40+(xsWq<#+de@9Tdu7I zvGy6frEHT?&1mS$H}C4Z$y$14R=zyV(h6-!Lio*--8PhpEbU~?)QB#hxU{P&HQHs$ z3!MI+9FijT;)*Dd3pPvU?r)CmDg|3LJwjnsR=YD|FfkRoeMcSiah7Qq5tuEa|8S77 z@I_y!Z`VHFym7y`{hEYY?Eah?ZQ^J3x;#;C*2{c;JDBRllsCX_U5pzJ<%YAh#A8vO zcYwZV*A&tPwgjLlx#&$l!vzy66L9gM&aQ0a2#9_iSa-wU_CHc zln2gg} zN8fR4u}8D)#x>1cino*z{);1}&u=+F{9+d;cp^0)V$gNByU)~u6O#Oc*V>chBOS#A zK#hhd?^~vyyuHQ8rs6Gca>+T!Ve{(aVfH)bQSw8lU`DM4BTmjA9zhx|=^o^~@W8Q| zKzcwo+%L(vra$w(kkqfri=Ir=;BShotnk&a~CI{tMs;1uFx-ndI)QD&Gpj5t)(Z}FyuW#+@^Z`Fwl2CmIITg-kMG!Ty z8Y&0b1`uPn(<|xmN?lhj_E%$-8tB!o$;Qq~I3RTZiY@F}yYoiBc7 zZ-sd!2*t`Suo#P6OGf)w8B|8c^F)dcGS)Ls%zFdlv>-F8#>43=koLe5^Sy)rx?~ns zA-4Z)c$qi-&yN!FBmA5FH8uKq;ja$w&t(7rCB%T4HUHJ`{RH(luIDePAZGIYZx8en w9y4`+E#JTJI1l`7G5-qiYwrIAz|Exb|6U2EMjV_!w%}kkGLyVz?jOB>0Jg-hYybcN literal 0 HcmV?d00001 diff --git a/test/powershell/engine/Api/TypeInference.Tests.ps1 b/test/powershell/engine/Api/TypeInference.Tests.ps1 index 36293030da7..14e3a3b0868 100644 --- a/test/powershell/engine/Api/TypeInference.Tests.ps1 +++ b/test/powershell/engine/Api/TypeInference.Tests.ps1 @@ -1406,6 +1406,14 @@ Describe "Type inference Tests" -tags "CI" { $res = [AstTypeInference]::InferTypeOf( { pwsh }.Ast) $res.Name | Should -Be 'System.String' } + + It 'Should not throw when inferring $_ in switch condition' { + $FoundAst = { switch($_){default{}} }.Ast.Find( + {param($Ast) $Ast -is [Language.VariableExpressionAst]}, + $true + ) + $null = [AstTypeInference]::InferTypeOf($FoundAst) + } } Describe "AstTypeInference tests" -Tags CI { diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index f5fc66748a7..9d1b030a6a5 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -2,6 +2,19 @@ # Licensed under the MIT License. Describe "Verify approved aliases list" -Tags "CI" { BeforeAll { + function ConvertTo-Hashtable { + [CmdletBinding()] + param ([Parameter(ValueFromPipeline=$true)][psobject]$o) + PROCESS { + $pNames = $o.psobject.properties.name + $ht = @{} + foreach($pName in $pNames) { + $ht[$pName] = $o.$pName + } + $ht + } + } + $FullCLR = !$IsCoreCLR $CoreWindows = $IsCoreCLR -and $IsWindows $CoreUnix = $IsCoreCLR -and !$IsWindows @@ -368,8 +381,8 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "New-FileCatalog", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" "Cmdlet", "New-GUID", "", $( $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "New-Item", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" -"Cmdlet", "New-ItemProperty", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" -"Cmdlet", "New-Module", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" +"Cmdlet", "New-ItemProperty", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" +"Cmdlet", "New-Module", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "New-ModuleManifest", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Low" "Cmdlet", "New-Object", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "New-PSDrive", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Low" @@ -387,7 +400,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "New-WSManInstance", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "New-WSManSessionOption", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Out-Default", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" -"Cmdlet", "Out-File", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" +"Cmdlet", "Out-File", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Out-GridView", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Out-Host", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Out-LineOutput", "", $($FullCLR ), "", "", "" @@ -458,7 +471,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Set-Service", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" "Cmdlet", "Set-StrictMode", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Set-TimeZone", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" -"Cmdlet", "Set-TraceSource", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" +"Cmdlet", "Set-TraceSource", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Set-Variable", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Set-WmiInstance", "", $($FullCLR ), "", "", "" "Cmdlet", "Set-WSManInstance", "", $($FullCLR -or $CoreWindows ), "", "", "None" @@ -474,7 +487,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Start-Service", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" "Cmdlet", "Start-Sleep", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Start-Transaction", "", $($FullCLR ), "", "", "" -"Cmdlet", "Start-Transcript", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" +"Cmdlet", "Start-Transcript", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Stop-Computer", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Stop-Job", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Stop-Process", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" @@ -483,7 +496,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Suspend-Job", "", $($FullCLR ), "", "", "" "Cmdlet", "Suspend-Service", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" "Cmdlet", "Switch-Process", "", $( $CoreUnix), "", "", "None" -"Cmdlet", "Tee-Object", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" +"Cmdlet", "Tee-Object", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Test-Connection", "", $( $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Test-ComputerSecureChannel", "", $($FullCLR ), "", "", "" "Cmdlet", "Test-FileCatalog", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" @@ -519,9 +532,18 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Write-Warning", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "@ - # We control only default engine aliases (Source -eq "") and aliases from following default loaded modules + # We control only default engine aliases (Source -eq "") and aliases from following default loaded modules " # We control only default engine Cmdlets (Source -eq "") and Cmdlets from following default loaded modules - $moduleList = @("Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Management", "Microsoft.PowerShell.Security", "Microsoft.PowerShell.Host", "Microsoft.PowerShell.Diagnostics", "Microsoft.WSMan.Management", "Microsoft.PowerShell.Core", "CimCmdlets") + $moduleList = @( + "Microsoft.PowerShell.Utility", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.Security", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.WSMan.Management", + "Microsoft.PowerShell.Core", + "CimCmdlets" + ) $getAliases = { param($moduleList) @@ -557,7 +579,12 @@ Describe "Verify approved aliases list" -Tags "CI" { } $commandList = $commandString | ConvertFrom-Csv -Delimiter "," + $commandHashTableList = $commandList.Where({$_.Present -eq "True" -and $_.CommandType -eq "Cmdlet"}) | ConvertTo-Hashtable + $aliasFullList = $commandList | Where-Object { $_.Present -eq "True" -and $_.CommandType -eq "Alias" } + + $AllScopeOption = [System.Management.Automation.ScopedItemOptions]::AllScope + $ReadOnlyOption = [System.Management.Automation.ScopedItemOptions]::ReadOnly } AfterAll { @@ -567,68 +594,36 @@ Describe "Verify approved aliases list" -Tags "CI" { } It "All approved aliases present (no new aliases added, no aliases removed)" { - $currentDisplayNameAliasList = $currentAliasList | Select-Object -ExpandProperty DisplayName - $aliasDisplayNameAliasList = $aliasFullList | ForEach-Object { "{0} -> {1}" -f $_.Name, $_.Definition} - - $result = Compare-Object -ReferenceObject $currentDisplayNameAliasList -DifferenceObject $aliasDisplayNameAliasList - - # Below 'Should Be' don't show full list wrong aliases so we output them explicitly - # if all aliases is Ok we output nothing - $result | Write-Host - $result | Should -BeNullOrEmpty + $observedAliases = $currentAliasList.ForEach({"{0}:{1}" -f $_.Name,$_.Definition}) | Sort-Object + $expectedAliases = $aliasFullList.ForEach({"{0}:{1}" -f $_.Name, $_.Definition}) | Sort-Object + $observedAliases | Should -Be $expectedAliases } It "All approved aliases have the correct 'AllScope' option" { - $aliasAllScopeOptionList = $aliasFullList | Where-Object { $_.AllScopeOption -eq "AllScope"} | ForEach-Object { "{0} -> {1}" -f $_.Name, $_.Definition} - $currentAllScopeOptionList = $currentAliasList | Where-Object { $_.Options -band [System.Management.Automation.ScopedItemOptions]::AllScope } | Select-Object -ExpandProperty DisplayName - - $result = Compare-Object -ReferenceObject $currentAllScopeOptionList -DifferenceObject $aliasAllScopeOptionList - - # Below 'Should Be' don't show full list wrong aliases so we output them explicitly - # if all aliases is Ok we output nothing - $result | Write-Host - $result | Should -BeNullOrEmpty + $expectedAllScopeAliases = $aliasFullList.Where({$_.AllScopeOption -eq "AllScope"}).ForEach({"{0}:{1}" -f $_.Name, $_.Definition}) | Sort-Object + $observedAllScopeAliases = $currentAliasList.Where({($_.Options -as [System.Management.Automation.ScopedItemOptions]) -band $AllScopeOption}).Foreach({"{0}:{1}" -f $_.Name, $_.Definition}) | Sort-Object + $observedAllScopeAliases | Should -Be $expectedAllScopeAliases } It "All approved aliases have the correct 'ReadOnly' option" { - $aliasReadOnlyOptionList = $aliasFullList | Where-Object { $_.ReadOnlyOption -eq "ReadOnly"} | ForEach-Object { "{0} -> {1}" -f $_.Name, $_.Definition} - $currentReadOnlyOptionList = $currentAliasList | Where-Object { $_.Options -band [System.Management.Automation.ScopedItemOptions]::ReadOnly } | Select-Object -ExpandProperty DisplayName - - $result = Compare-Object -ReferenceObject $currentReadOnlyOptionList -DifferenceObject $aliasReadOnlyOptionList - - # Below 'Should Be' don't show full list wrong aliases so we output them explicitly - # if all aliases is Ok we output nothing - $result | Write-Host - $result | Should -BeNullOrEmpty + $expectedReadOnlyAliases = $aliasFullList.Where({$_.ReadOnlyOption -eq "ReadOnly"}).ForEach({"{0}:{1}" -f $_.Name, $_.Definition}) | Sort-Object + $observedReadOnlyAliases = $currentAliasList.Where({($_.Options -as [System.Management.Automation.ScopedItemOptions]) -band $ReadOnlyOption}).ForEach({"{0}:{1}" -f $_.Name, $_.Definition}) | Sort-Object + $observedReadOnlyAliases | Should -Be $expectedReadOnlyAliases } It "All approved Cmdlets present (no new Cmdlets added, no Cmdlets removed)" { - $cmdletList = $commandList | Where-Object { $_.Present -eq "True" -and $_.CommandType -eq "Cmdlet" } | Select-Object -ExpandProperty Name - - $result = (Compare-Object -ReferenceObject $currentCmdletList.Name -DifferenceObject $cmdletList).InputObject - $result | Should -BeNullOrEmpty + $expectedCmdletList = $commandList.Where({$_.Present -eq "True" -and $_.CommandType -eq "Cmdlet"}).Name | Sort-Object + $observedCmdletList = $currentCmdletList.Name | Sort-Object + $observedCmdletList | Should -Be $expectedCmdletList } - It "All present Cmdlets should have the correct ConfirmImpact" { - $CmdletList = $commandList | - Where-Object { $_.Present -eq "True" -and $_.CommandType -eq "Cmdlet"} | - Select-Object -Property Name, ConfirmImpact - - # if Preview, $currentCmdletList is deserialized, so we re-hydrate them so comparison succeeds - $currentCmdletList = $currentCmdletList | ForEach-Object { Get-Command $_.Name } | - Where-Object { $moduleList -contains $_.Source -and $null -ne $_.ImplementingType } | - Select-Object -Property Name, @{ - Name = 'ConfirmImpact' - Expression = { - if (($t = $_.ImplementingType)) { - $t.GetCustomAttributes($true).Where{$_.TypeId.Name -eq 'CmdletAttribute'}.ConfirmImpact - } - } - } - - # -PassThru is provided to give meaningful output when differences arise - $result = Compare-Object -ReferenceObject $currentCmdletList -DifferenceObject $CmdletList -Property ConfirmImpact -PassThru - $result | Should -BeNullOrEmpty + It "'' Cmdlet should have the correct ConfirmImpact ''" -TestCases $commandHashtableList { + param ( $Name, $ConfirmImpact ) + # retrieve again because we may have serialized the commandinfo + $cmdlet = Get-Command $Name + $cmdletAttribute = $cmdlet.ImplementingType.GetCustomAttributes($true).Where({$_ -is [System.Management.Automation.CmdletAttribute]}) + $impact = $cmdletAttribute.ConfirmImpact + $impact | Should -Be $ConfirmImpact } } diff --git a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 index 18adcf6a8d8..dda4bdbd1eb 100644 --- a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 +++ b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 @@ -5,8 +5,9 @@ Describe 'OutputRendering tests' -Tag 'CI' { BeforeAll { $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() # Console host does not support VT100 escape sequences on Windows 2012R2 or earlier - if ($IsWindows -and [System.Environment]::OSVersion.Version -le [version]::new(6, 3)) { - $PSDefaultParameterValues["it:skip"] = $true + + if (-not $host.ui.SupportsVirtualTerminal) { + $global:PSDefaultParameterValues["it:skip"] = $true } } diff --git a/test/tools/Modules/WebListener/WebListener.psm1 b/test/tools/Modules/WebListener/WebListener.psm1 index e065363e673..15ce1d8fe38 100644 --- a/test/tools/Modules/WebListener/WebListener.psm1 +++ b/test/tools/Modules/WebListener/WebListener.psm1 @@ -235,6 +235,10 @@ function Get-WebListenerUrl { 'ResponseHeaders', 'Resume', 'Retry', + 'Stall', + 'StallGZip', + 'StallBrotli', + 'StallDeflate', '/' )] [String]$Test, diff --git a/test/tools/WebListener/Controllers/DelayController.cs b/test/tools/WebListener/Controllers/DelayController.cs index f5e877cfe2e..5ab081d580d 100644 --- a/test/tools/WebListener/Controllers/DelayController.cs +++ b/test/tools/WebListener/Controllers/DelayController.cs @@ -1,24 +1,50 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; +using System.IO; +using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Http.Extensions; using mvc.Models; +using System.IO.Compression; namespace mvc.Controllers { public class DelayController : Controller { + private static readonly byte[] GenericBytes = "Hello worldHello world"u8.ToArray(); + + private static readonly byte[] JsonBytes = """ + {"name1":"value1","name2":"value2","name3":"value3"} + """u8.ToArray(); + + private static readonly byte[] AtomFeed = """ + + + Example Feed + + 2003-12-13T18:30:02Z + + John Doe + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + + Atom-Powered Robots Run Amok + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-12-13T18:30:02Z + Some text. + + + """u8.ToArray(); + public JsonResult Index(int seconds) { - if (seconds > 0){ + if (seconds > 0) + { int milliseconds = seconds * 1000; Thread.Sleep(milliseconds); } @@ -28,9 +54,90 @@ public JsonResult Index(int seconds) return getController.Index(); } + public async Task Stall(int seconds, string contentType, CancellationToken cancellationToken) + { + await WriteStallResponse(seconds, contentType, null, null, cancellationToken); + } + + public async Task StallBrotli(int seconds, string contentType, CancellationToken cancellationToken) + { + using var memStream = new MemoryStream(); + using var compressedStream = new BrotliStream(memStream, CompressionLevel.Fastest); + Response.Headers.ContentEncoding = "br"; + await WriteStallResponse(seconds, contentType, compressedStream, memStream, cancellationToken); + } + + public async Task StallDeflate(int seconds, string contentType, CancellationToken cancellationToken) + { + using var memStream = new MemoryStream(); + using var compressedStream = new DeflateStream(memStream, CompressionLevel.Fastest); + Response.Headers.ContentEncoding = "deflate"; + await WriteStallResponse(seconds, contentType, compressedStream, memStream, cancellationToken); + } + + public async Task StallGZip(int seconds, string contentType, CancellationToken cancellationToken) + { + using var memStream = new MemoryStream(); + using var compressedStream = new GZipStream(memStream, CompressionLevel.Fastest); + Response.Headers.ContentEncoding = "gzip"; + await WriteStallResponse(seconds, contentType, compressedStream, memStream, cancellationToken); + } + public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } + + private async Task WriteStallResponse(int seconds, string contentType, Stream stream, MemoryStream memStream, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(contentType)) + { + contentType = "text/plain"; + } + else + { + contentType = WebUtility.UrlDecode(contentType); + } + + Response.ContentType = contentType; + Response.StatusCode = StatusCodes.Status200OK; + byte[] response; + + if (contentType.Contains("json")) + { + response = JsonBytes; + } + else if (contentType.Contains("xml")) + { + response = AtomFeed; + } + else + { + response = GenericBytes; + } + + if (stream is not null && memStream is not null) + { + // Generate the compressed data for sending on the response stream + stream.Write(response); + stream.Flush(); + stream.Close(); + response = memStream.ToArray(); + } + int midPoint = response.Length / 2; + + // Start writing approx half the content, including headers and then delay before writing the rest. + await Response.Body.WriteAsync(response, 0, midPoint, cancellationToken); + await Response.Body.FlushAsync(cancellationToken); + + if (seconds > 0) + { + int milliseconds = seconds * 1000; + await Task.Delay(milliseconds); + } + + await Response.Body.WriteAsync(response, midPoint, response.Length - midPoint, cancellationToken); + await Response.Body.FlushAsync(cancellationToken); + } } } diff --git a/test/tools/WebListener/Startup.cs b/test/tools/WebListener/Startup.cs index 4bceaa0bf71..0136decb5f5 100644 --- a/test/tools/WebListener/Startup.cs +++ b/test/tools/WebListener/Startup.cs @@ -65,6 +65,22 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) name: "delay", template: "Delay/{seconds?}", defaults: new { controller = "Delay", action = "Index" }); + routes.MapRoute( + name: "stall", + template: "Stall/{seconds?}/{contentType?}", + defaults: new { controller = "Delay", action = "Stall" }); + routes.MapRoute( + name: $"stallbrotli", + template: "StallBrotli/{seconds?}/{contentType?}", + defaults: new { controller = "Delay", action = $"StallBrotli" }); + routes.MapRoute( + name: $"stalldeflate", + template: "StallDeflate/{seconds?}/{contentType?}", + defaults: new { controller = "Delay", action = $"StallDeflate" }); + routes.MapRoute( + name: $"stallgzip", + template: "StallGZip/{seconds?}/{contentType?}", + defaults: new { controller = "Delay", action = $"StallGZip" }); routes.MapRoute( name: "post", template: "Post", diff --git a/test/xUnit/csharp/test_Subsystem.cs b/test/xUnit/csharp/test_Subsystem.cs index 1318f13139d..2889bab378b 100644 --- a/test/xUnit/csharp/test_Subsystem.cs +++ b/test/xUnit/csharp/test_Subsystem.cs @@ -71,18 +71,8 @@ private MyCompositeSubsystem(Guid id) #region ICommandPredictor - public bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback) => false; - public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken) => default; - public void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) { } - - public void OnSuggestionDisplayed(PredictionClient client, uint session, int countOrIndex) { } - - public void OnSuggestionAccepted(PredictionClient client, uint session, string acceptedSuggestion) { } - - public void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { } - #endregion } diff --git a/tools/cgmanifest.json b/tools/cgmanifest.json index bb6dcb5f360..a0559eaa6b6 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest.json @@ -55,7 +55,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Analyzers", - "Version": "3.3.3" + "Version": "3.3.4" } }, "DevelopmentDependency": true @@ -235,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "Newtonsoft.Json", - "Version": "13.0.2" + "Version": "13.0.3" } }, "DevelopmentDependency": false @@ -775,7 +775,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.DiagnosticSource", - "Version": "7.0.1" + "Version": "7.0.2" } }, "DevelopmentDependency": false @@ -1215,7 +1215,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Duplex", - "Version": "4.10.0" + "Version": "4.10.2" } }, "DevelopmentDependency": false @@ -1225,7 +1225,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Http", - "Version": "4.10.0" + "Version": "4.10.2" } }, "DevelopmentDependency": false @@ -1245,7 +1245,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetTcp", - "Version": "4.10.0" + "Version": "4.10.2" } }, "DevelopmentDependency": false @@ -1255,7 +1255,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Primitives", - "Version": "4.10.0" + "Version": "4.10.2" } }, "DevelopmentDependency": false @@ -1265,7 +1265,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Security", - "Version": "4.10.0" + "Version": "4.10.2" } }, "DevelopmentDependency": false diff --git a/tools/releaseBuild/azureDevOps/compliance.yml b/tools/releaseBuild/azureDevOps/compliance.yml index 19935237216..43ef005402b 100644 --- a/tools/releaseBuild/azureDevOps/compliance.yml +++ b/tools/releaseBuild/azureDevOps/compliance.yml @@ -30,6 +30,7 @@ variables: - group: 'Azure Blob variable group' # Defines the variables CgPat, CgOrganization, and CgProject - group: 'ComponentGovernance' + - group: 'PoolNames' stages: - stage: compliance From 3ecad716c36f16d33d1227dd33efdf971484e982 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Wed, 3 May 2023 10:14:34 +0200 Subject: [PATCH 15/18] fix nullable --- .../commands/utility/WebCmdlet/StreamHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index bfdd6f93d2a..c1e7e19c77f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -378,7 +378,7 @@ private static async Task GetStreamEncodingAsync(Stream stream, string return encoding; } - internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding, CancellationToken cancellationToken) + internal static string DecodeStream(Stream stream, string? characterSet, out Encoding encoding, CancellationToken cancellationToken) { encoding = GetStreamEncodingAsync(stream, characterSet, cancellationToken).GetAwaiter().GetResult(); From 61b23d3e55c2eb1ff8062217269b61fbc3382b5b Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Tue, 12 Dec 2023 00:31:20 +0100 Subject: [PATCH 16/18] Add comments to code; better name for variable; encoding precedence to BOM --- .../commands/utility/WebCmdlet/StreamHelper.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index c1e7e19c77f..bd50814e753 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -337,9 +337,12 @@ internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet c WriteToStream(stream, output, cmdlet, contentLength, cancellationToken); } + // Precedence: BOM, charset, meta element, XML declaration private static async Task GetStreamEncodingAsync(Stream stream, string? characterSet, CancellationToken cancellationToken) { - bool isDefaultEncoding = !TryGetEncodingFromCharset(characterSet, out Encoding encoding); + bool encodingSearchFailed = !TryGetEncodingFromCharset(characterSet, out Encoding encoding); + + // Tries to detect encoding from BOM, if it fails fall back to encoding using StreamReader reader = new(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true); // We only look within the first 1k characters as the meta element and @@ -348,17 +351,18 @@ private static async Task GetStreamEncodingAsync(Stream stream, string char[] buffer = new char[bufferLength]; await reader.ReadBlockAsync(buffer.AsMemory(), cancellationToken); - encoding = reader.CurrentEncoding; stream.Seek(0, SeekOrigin.Begin); - if (isDefaultEncoding) + // Only try to parse meta element and XML declaration if getting encoding from charset + // fails and detectEncodingFromByteOrderMarks doesn't change the encoding. + if (encodingSearchFailed && encoding == reader.CurrentEncoding) { string substring = new(buffer); - // Check for a charset attribute on the meta element to override the default + // Check for a charset attribute on the meta element to override the default. Match match = s_metaRegex.Match(substring); - // Check for a encoding attribute on the xml declaration to override the default + // Check for a encoding attribute on the xml declaration to override the default. if (!match.Success) { match = s_xmlRegex.Match(substring); @@ -374,6 +378,10 @@ private static async Task GetStreamEncodingAsync(Stream stream, string } } } + else + { + encoding = reader.CurrentEncoding; + } return encoding; } From 2edb659544b29bca6cfbd72f2a5b95f49533256c Mon Sep 17 00:00:00 2001 From: Steven Butler Date: Fri, 5 Jan 2024 07:44:59 +1000 Subject: [PATCH 17/18] Make encoding changes handle network stall timeouts Added extension methods to StreamReader that will add a timeout to each stream read if a timeout property is set in IWR/IRM. --- .../utility/WebCmdlet/StreamHelper.cs | 119 ++++++++++++++++-- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 5babef67048..a10e389e82e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -204,10 +204,12 @@ private void Initialize(CancellationToken cancellationToken = default) } _isInitialized = true; + byte[]? buffer = null; try { long totalRead = 0; - byte[] buffer = new byte[StreamHelper.ChunkSize]; + buffer = ArrayPool.Shared.Rent(StreamHelper.ChunkSize); + var mem = buffer.AsMemory(); ProgressRecord record = new(StreamHelper.ActivityId, WebCmdletStrings.ReadResponseProgressActivity, "statusDescriptionPlaceholder"); string totalDownloadSize = _contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)_contentLength); for (int read = 1; read > 0; totalRead += read) @@ -232,7 +234,7 @@ private void Initialize(CancellationToken cancellationToken = default) } } - read = _originalStreamToProxy.ReadAsync(buffer.AsMemory(), _perReadTimeout, cancellationToken).GetAwaiter().GetResult(); + read = _originalStreamToProxy.ReadAsync(mem, _perReadTimeout, cancellationToken).GetAwaiter().GetResult(); if (read > 0) { @@ -256,7 +258,109 @@ private void Initialize(CancellationToken cancellationToken = default) Dispose(); throw; } + finally + { + if (buffer != null) + { + ArrayPool.Shared.Return(buffer); + } + } + } + } + + internal static class StreamReaderTimeoutExtensions + { + private static async ValueTask ReadBlockAsyncInternal(this StreamReader reader, Memory buffer, TimeSpan perReadTimeout, CancellationToken cancellationToken) + { + int n = 0, i; + do + { + i = await reader.ReadAsync(buffer.Slice(n), perReadTimeout, cancellationToken).ConfigureAwait(false); + n += i; + } while (i > 0 && n < buffer.Length); + + return n; + } + + internal static async Task ReadBlockAsync(this StreamReader reader, Memory buffer, TimeSpan readTimeout, CancellationToken cancellationToken) + { + if (readTimeout == Timeout.InfiniteTimeSpan) + { + return await reader.ReadBlockAsync(buffer, cancellationToken).ConfigureAwait(false); + } + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + cts.CancelAfter(readTimeout); + return await reader.ReadBlockAsyncInternal(buffer, readTimeout, cts.Token).ConfigureAwait(false); + } + catch (TaskCanceledException ex) + { + if (cts.IsCancellationRequested) + { + throw new TimeoutException($"The request was canceled due to the configured OperationTimeout of {readTimeout.TotalSeconds} seconds elapsing", ex); + } + else + { + throw; + } + } + } + + internal static async Task ReadAsync(this StreamReader reader, Memory buffer, TimeSpan readTimeout, CancellationToken cancellationToken) + { + if (readTimeout == Timeout.InfiniteTimeSpan) + { + return await reader.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + cts.CancelAfter(readTimeout); + return await reader.ReadAsync(buffer, cts.Token).ConfigureAwait(false); + } + catch (TaskCanceledException ex) + { + if (cts.IsCancellationRequested) + { + throw new TimeoutException($"The request was canceled due to the configured OperationTimeout of {readTimeout.TotalSeconds} seconds elapsing", ex); + } + else + { + throw; + } + } } + + internal static async Task ReadToEndAsync(this StreamReader reader, TimeSpan perReadTimeout, CancellationToken cancellationToken) + { + if (perReadTimeout == Timeout.InfiniteTimeSpan) + { + return await reader.ReadToEndAsync(cancellationToken); + } + + int useBufferSize = 4096; + char[] chars = ArrayPool.Shared.Rent(useBufferSize); + try + { + Memory buffer = chars.AsMemory(); + StringBuilder sb = new StringBuilder(useBufferSize); + int charsRead = 0; + while ((charsRead = await reader.ReadAsync(buffer, perReadTimeout, cancellationToken)) != 0) + { + sb.Append(chars, 0, charsRead); + } + + return sb.ToString(); + } + finally + { + ArrayPool.Shared.Return(chars); + } + } + } internal static class StreamTimeoutExtensions @@ -421,7 +525,7 @@ internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet c } // Precedence: BOM, charset, meta element, XML declaration - private static async Task GetStreamEncodingAsync(Stream stream, string? characterSet, CancellationToken cancellationToken) + private static async Task GetStreamEncodingAsync(Stream stream, string? characterSet, TimeSpan perReadTimeout, CancellationToken cancellationToken) { bool encodingSearchFailed = !TryGetEncodingFromCharset(characterSet, out Encoding encoding); @@ -433,7 +537,7 @@ private static async Task GetStreamEncodingAsync(Stream stream, string int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); char[] buffer = new char[bufferLength]; - await reader.ReadBlockAsync(buffer.AsMemory(), cancellationToken); + await reader.ReadBlockAsync(buffer.AsMemory(), perReadTimeout, cancellationToken); stream.Seek(0, SeekOrigin.Begin); // Only try to parse meta element and XML declaration if getting encoding from charset @@ -469,11 +573,10 @@ private static async Task GetStreamEncodingAsync(Stream stream, string return encoding; } - internal static string DecodeStream(Stream stream, string? characterSet, out Encoding encoding, CancellationToken cancellationToken) + internal static string DecodeStream(Stream stream, string? characterSet, out Encoding encoding, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - encoding = GetStreamEncodingAsync(stream, characterSet, cancellationToken).GetAwaiter().GetResult(); - - return new StreamReader(stream, encoding, leaveOpen: true).ReadToEndAsync(cancellationToken).GetAwaiter().GetResult(); + encoding = GetStreamEncodingAsync(stream, characterSet, perReadTimeout, cancellationToken).GetAwaiter().GetResult(); + return new StreamReader(stream, encoding, leaveOpen: true).ReadToEndAsync(perReadTimeout, cancellationToken).GetAwaiter().GetResult(); } internal static bool TryGetEncodingFromCharset(string? characterSet, out Encoding encoding) From 4abf7139dffcbc7aa104a72fd81ed1de1784abbb Mon Sep 17 00:00:00 2001 From: stevenebutler Date: Sun, 7 Jan 2024 20:54:22 +1000 Subject: [PATCH 18/18] Add ConfigureAwait(false) to awaits (#3) This is needed to stop web methods from deadlocking when windows forms are loaded from within the PowerShell process. --- .../commands/utility/WebCmdlet/StreamHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index a10e389e82e..0e97f9f1cb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -338,7 +338,7 @@ internal static async Task ReadToEndAsync(this StreamReader reader, Time { if (perReadTimeout == Timeout.InfiniteTimeSpan) { - return await reader.ReadToEndAsync(cancellationToken); + return await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); } int useBufferSize = 4096; @@ -348,7 +348,7 @@ internal static async Task ReadToEndAsync(this StreamReader reader, Time Memory buffer = chars.AsMemory(); StringBuilder sb = new StringBuilder(useBufferSize); int charsRead = 0; - while ((charsRead = await reader.ReadAsync(buffer, perReadTimeout, cancellationToken)) != 0) + while ((charsRead = await reader.ReadAsync(buffer, perReadTimeout, cancellationToken).ConfigureAwait(false)) != 0) { sb.Append(chars, 0, charsRead); } @@ -537,7 +537,7 @@ private static async Task GetStreamEncodingAsync(Stream stream, string int bufferLength = (int)Math.Min(reader.BaseStream.Length, 1024); char[] buffer = new char[bufferLength]; - await reader.ReadBlockAsync(buffer.AsMemory(), perReadTimeout, cancellationToken); + await reader.ReadBlockAsync(buffer.AsMemory(), perReadTimeout, cancellationToken).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); // Only try to parse meta element and XML declaration if getting encoding from charset