diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs
deleted file mode 100644
index aba6043d2bb..00000000000
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Collections.ObjectModel;
-
-namespace Microsoft.PowerShell.Commands
-{
- ///
- /// This class is used to parse CSV text.
- ///
- internal class CSVHelper
- {
- internal CSVHelper(char delimiter)
- {
- Delimiter = delimiter;
- }
-
- ///
- /// Gets or sets the delimiter that separates the values.
- ///
- internal char Delimiter { get; } = ',';
-
- ///
- /// Parse a CSV string.
- ///
- ///
- /// String to be parsed.
- ///
- internal Collection ParseCsv(string csv)
- {
- Collection result = new Collection();
- string tempString = string.Empty;
- csv = csv.Trim();
- if (csv.Length == 0 || csv[0] == '#')
- {
- return result;
- }
-
- bool inQuote = false;
- for (int i = 0; i < csv.Length; i++)
- {
- char c = csv[i];
- if (c == Delimiter)
- {
- if (!inQuote)
- {
- result.Add(tempString);
- tempString = string.Empty;
- }
- else
- {
- tempString += c;
- }
- }
- else
- {
- switch (c)
- {
- case '"':
- if (inQuote)
- {
- // If we are at the end of the string or the end of the segment, create a new value
- // Otherwise we have an error
- if (i == csv.Length - 1)
- {
- result.Add(tempString);
- tempString = string.Empty;
- inQuote = false;
- break;
- }
-
- if (csv[i + 1] == Delimiter)
- {
- result.Add(tempString);
- tempString = string.Empty;
- inQuote = false;
- i++;
- }
- else if (csv[i + 1] == '"')
- {
- tempString += '"';
- i++;
- }
- else
- {
- inQuote = false;
- }
- }
- else
- {
- inQuote = true;
- }
-
- break;
-
- default:
- tempString += c;
- break;
- }
- }
- }
-
- if (tempString.Length > 0)
- {
- result.Add(tempString);
- }
-
- return result;
- }
- }
-}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs
index 8ccd9a3a834..c401f5311a1 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs
@@ -8,6 +8,7 @@
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Security;
+using System.Text;
namespace Microsoft.PowerShell.Commands
{
@@ -99,6 +100,93 @@ public SwitchParameter Force
#endregion Parameters
#region Command code
+
+ private static bool OnlyContainsWhitespace(string line)
+ {
+ bool result = true;
+
+ foreach (char c in line)
+ {
+ if (char.IsWhiteSpace(c))
+ {
+ continue;
+ }
+
+ result = false;
+ break;
+ }
+
+ return result;
+ }
+
+ private static Collection ParseCsvLine(string csv)
+ {
+ ReadOnlySpan csvTrimmed = csv.Trim();
+ Collection result = new Collection();
+ StringBuilder wordBuffer = new StringBuilder();
+
+ for (int i = 0; i < csvTrimmed.Length; i++)
+ {
+ char nextChar = csvTrimmed[i];
+
+ // if next character was delimiter or we are at the end, add string to result and clear wordBuffer
+ // else if next character was quote, perform reading until next quote and add it to wordBuffer
+ // else read and add it to wordBuffer
+ if (nextChar == ',')
+ {
+ result.Add(wordBuffer.ToString());
+ wordBuffer.Clear();
+ }
+ else if (nextChar == '"')
+ {
+ bool inQuotes = true;
+
+ // if we are within a quote section, read and append to wordBuffer until we find a next quote that is not followed by another quote
+ // if it is a single quote, escape the quote section
+ // if the quote is followed by an other quote, do not escape and add a quote character to wordBuffer
+ while (i + 1 < csvTrimmed.Length && inQuotes)
+ {
+ i++;
+ nextChar = csvTrimmed[i];
+
+ if (nextChar == '"')
+ {
+ if (i + 1 < csvTrimmed.Length && csvTrimmed[i + 1] == '"')
+ {
+ wordBuffer.Append(nextChar);
+ i++;
+ }
+ else
+ {
+ inQuotes = false;
+ }
+ }
+ else
+ {
+ wordBuffer.Append(nextChar);
+ }
+ }
+ }
+ else
+ {
+ wordBuffer.Append(nextChar);
+ }
+ }
+
+ string lastWord = wordBuffer.ToString();
+ if (lastWord != string.Empty)
+ {
+ result.Add(lastWord);
+ }
+
+ return result;
+ }
+
+ private static bool LineShouldBeSkipped(string line)
+ {
+ // if line is empty or a comment, return true
+ return line.Length == 0 || line[0] == '#' || OnlyContainsWhitespace(line);
+ }
///
/// The main processing loop of the command.
@@ -289,104 +377,100 @@ private bool VerifyShadowingExistingCommandsAndWriteError(string aliasName)
private Collection GetAliasesFromFile(bool isLiteralPath)
{
Collection result = new Collection();
-
string filePath = null;
using (StreamReader reader = OpenFile(out filePath, isLiteralPath))
{
- CSVHelper csvHelper = new CSVHelper(',');
-
- Int64 lineNumber = 0;
+ long lineNumber = 0;
string line = null;
while ((line = reader.ReadLine()) != null)
{
++lineNumber;
- // Ignore blank lines
- if (line.Length == 0)
+ if (LineShouldBeSkipped(line))
{
continue;
}
- // Ignore lines that only contain whitespace
- if (OnlyContainsWhitespace(line))
- {
- continue;
- }
+ Collection parsedLine = ParseCsvLine(line);
- // Ignore comment lines
- if (line[0] == '#')
+ if (IsValidParsedLine(parsedLine, lineNumber, filePath))
{
- continue;
+ ScopedItemOptions options = CreateItemOptions(parsedLine, filePath, lineNumber);
+ result.Add(ConstructAlias(parsedLine, options));
}
+ }
+ }
- Collection values = csvHelper.ParseCsv(line);
-
- if (values.Count != 4)
- {
- string message = StringUtil.Format(AliasCommandStrings.ImportAliasFileInvalidFormat, filePath, lineNumber);
-
- FormatException formatException =
- new FormatException(message);
-
- ErrorRecord errorRecord =
- new ErrorRecord(
- formatException,
- "ImportAliasFileFormatError",
- ErrorCategory.ReadError,
- filePath);
-
- errorRecord.ErrorDetails = new ErrorDetails(message);
+ return result;
+ }
- ThrowTerminatingError(errorRecord);
- }
+ private ScopedItemOptions CreateItemOptions(Collection parsedLine, string filePath, long lineNumber)
+ {
+ ScopedItemOptions options;
+ if (!Enum.TryParse(parsedLine[3], out options))
+ {
+ // if parsing is no succes
+ string message = StringUtil.Format(AliasCommandStrings.ImportAliasOptionsError, filePath, lineNumber);
+ ErrorRecord errorRecord =
+ new ErrorRecord(
+ new ArgumentException(),
+ "ImportAliasOptionsError",
+ ErrorCategory.ReadError,
+ filePath);
+
+ errorRecord.ErrorDetails = new ErrorDetails(message);
+ WriteError(errorRecord);
+ }
- ScopedItemOptions options = ScopedItemOptions.None;
+ return options;
+ }
- try
- {
- options = (ScopedItemOptions)Enum.Parse(typeof(ScopedItemOptions), values[3], true);
- }
- catch (ArgumentException argException)
- {
- string message = StringUtil.Format(AliasCommandStrings.ImportAliasOptionsError, filePath, lineNumber);
+ private AliasInfo ConstructAlias(Collection parsedLine, ScopedItemOptions options)
+ {
+ AliasInfo newAlias =
+ new AliasInfo(
+ parsedLine[0],
+ parsedLine[1],
+ this.Context,
+ options);
+ string aliasDescription = parsedLine[2];
+ if (!string.IsNullOrEmpty(aliasDescription))
+ {
+ newAlias.Description = aliasDescription;
+ }
- ErrorRecord errorRecord =
- new ErrorRecord(
- argException,
- "ImportAliasOptionsError",
- ErrorCategory.ReadError,
- filePath);
+ return newAlias;
+ }
- errorRecord.ErrorDetails = new ErrorDetails(message);
- WriteError(errorRecord);
- continue;
- }
+ private bool IsValidParsedLine(Collection parsedLine, long lineNumber, string filePath)
+ {
+ if (parsedLine.Count != 4)
+ {
+ // if not four values, do ThrowTerminatingError(errorRecord) with ImportAliasFileFormatError, just like old implementation
+ string message = StringUtil.Format(AliasCommandStrings.ImportAliasFileInvalidFormat, filePath, lineNumber);
- AliasInfo newAlias =
- new AliasInfo(
- values[0],
- values[1],
- Context,
- options);
+ FormatException formatException =
+ new FormatException(message);
- if (!string.IsNullOrEmpty(values[2]))
- {
- newAlias.Description = values[2];
- }
+ ErrorRecord errorRecord =
+ new ErrorRecord(
+ formatException,
+ "ImportAliasFileFormatError",
+ ErrorCategory.ReadError,
+ filePath);
- result.Add(newAlias);
- }
+ errorRecord.ErrorDetails = new ErrorDetails(message);
- reader.Dispose();
+ ThrowTerminatingError(errorRecord);
+ return false;
}
- return result;
+ return true;
}
private StreamReader OpenFile(out string filePath, bool isLiteralPath)
{
StreamReader result = null;
-
filePath = null;
ProviderInfo provider = null;
Collection paths = null;
@@ -459,24 +543,6 @@ private void ThrowFileOpenError(Exception e, string pathWithError)
errorRecord.ErrorDetails = new ErrorDetails(message);
this.ThrowTerminatingError(errorRecord);
}
-
- private static bool OnlyContainsWhitespace(string line)
- {
- bool result = true;
-
- foreach (char c in line)
- {
- if (char.IsWhiteSpace(c) && c != '\n' && c != '\r')
- {
- continue;
- }
-
- result = false;
- break;
- }
-
- return result;
- }
#endregion Command code
}
}