diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 25eadffae..ed60d54f5 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -269,7 +269,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { foreach (var (name, value) in resourcesFile) { - string fileName = CleanUpFileName(name) + string fileName = SanitizeFileName(name) .Replace('/', Path.DirectorySeparatorChar); string dirName = Path.GetDirectoryName(fileName); if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) @@ -571,14 +571,25 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// public static string CleanUpFileName(string text) { - return CleanUpFileName(text, separateAtDots: false); + return CleanUpName(text, separateAtDots: false, treatAsFileName: false); + } + + /// + /// Removes invalid characters from file names and reduces their length, + /// but keeps file extensions intact. + /// + public static string SanitizeFileName(string fileName) + { + return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true); } /// /// Cleans up a node name for use as a file system name. If is active, - /// dots are seen as segment separators. Each segment is limited to 255 characters. + /// dots are seen as segment separators. Each segment is limited to maxSegmentLength characters. + /// (see ) If is active, + /// we check for file a extension and try to preserve it, if it's valid. /// - static string CleanUpFileName(string text, bool separateAtDots) + static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName) { int pos = text.IndexOf(':'); if (pos > 0) @@ -587,23 +598,49 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (pos > 0) text = text.Substring(0, pos); text = text.Trim(); - // Whitelist allowed characters, replace everything else: - StringBuilder b = new StringBuilder(text.Length); + string extension = null; int currentSegmentLength = 0; var (supportsLongPaths, maxPathLength, maxSegmentLength) = longPathSupport.Value; + if (treatAsFileName) + { + // Check if input is a file name, i.e., has a valid extension + // If yes, preserve extension and append it at the end. + // But only, if the extension length does not exceed maxSegmentLength, + // if that's the case we just give up and treat the extension no different + // from the file name. + int lastDot = text.LastIndexOf('.'); + if (lastDot >= 0 && text.Length - lastDot < maxSegmentLength) + { + string originalText = text; + extension = text.Substring(lastDot); + text = text.Remove(lastDot); + foreach (var c in extension) + { + if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.')) + { + // extension contains an invalid character, therefore cannot be a valid extension. + extension = null; + text = originalText; + break; + } + } + } + } + // Whitelist allowed characters, replace everything else: + StringBuilder b = new StringBuilder(text.Length + (extension?.Length ?? 0)); foreach (var c in text) { currentSegmentLength++; if (char.IsLetterOrDigit(c) || c == '-' || c == '_') { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (currentSegmentLength <= maxSegmentLength) b.Append(c); } else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.') { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (separateAtDots || currentSegmentLength <= maxSegmentLength) b.Append('.'); // allow dot, but never two in a row @@ -614,7 +651,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } else { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (currentSegmentLength <= maxSegmentLength) b.Append('-'); @@ -625,6 +662,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (b.Length == 0) b.Append('-'); string name = b.ToString(); + if (extension != null) + name += extension; if (IsReservedFileSystemName(name)) return name + "_"; else if (name == ".") @@ -638,7 +677,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// public static string CleanUpDirectoryName(string text) { - return CleanUpFileName(text, separateAtDots: true).Replace('.', Path.DirectorySeparatorChar); + return CleanUpName(text, separateAtDots: true, treatAsFileName: false) + .Replace('.', Path.DirectorySeparatorChar); } static bool IsReservedFileSystemName(string name) diff --git a/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs b/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs index e41d055bd..79974c89b 100644 --- a/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs +++ b/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Threading.Tasks; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; @@ -46,7 +47,7 @@ namespace ICSharpCode.ILSpy return; var assembly = selectedNodes[0].PackageEntry; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(assembly.Name)); + dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(assembly.Name)); dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources.AllFiles; dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.LoadedAssembly.FileName); if (dlg.ShowDialog() != true) @@ -70,7 +71,7 @@ namespace ICSharpCode.ILSpy { foreach (var node in selectedNodes) { - var fileName = Path.GetFileName(DecompilerTextView.CleanUpName(node.PackageEntry.Name)); + var fileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(node.PackageEntry.Name)); SaveEntry(output, node.PackageEntry, Path.Combine(outputFolderOrFileName, fileName)); } } diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 97101ece0..2577206e6 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -25,6 +25,7 @@ using System.Windows; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; @@ -63,7 +64,7 @@ namespace ICSharpCode.ILSpy return; } SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index be9597b75..113122953 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -19,8 +19,8 @@ using System.IO; using System.Linq; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using Microsoft.Win32; @@ -35,7 +35,7 @@ namespace ICSharpCode.ILSpy if (assembly == null) return; OpenFileDialog dlg = new OpenFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index ffc9ef303..2c70a15bf 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -1034,7 +1034,7 @@ namespace ICSharpCode.ILSpy.TextView SaveFileDialog dlg = new SaveFileDialog(); dlg.DefaultExt = language.FileExtension; dlg.Filter = language.Name + "|*" + language.FileExtension + Properties.Resources.AllFiles; - dlg.FileName = CleanUpName(treeNodes.First().ToString()) + language.FileExtension; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString()) + language.FileExtension; if (dlg.ShowDialog() == true) { SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName); @@ -1149,14 +1149,6 @@ namespace ICSharpCode.ILSpy.TextView thread.Start(); return tcs.Task; } - - /// - /// Cleans up a node name for use as a file name. - /// - internal static string CleanUpName(string text) - { - return WholeProjectDecompiler.CleanUpFileName(text); - } #endregion internal ReferenceSegment? GetReferenceSegmentAtMousePosition() diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index da10af18c..a4e9f1c0d 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -26,10 +26,10 @@ using System.Windows.Controls; using System.Windows.Documents; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.TreeView; @@ -384,7 +384,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (string.IsNullOrEmpty(language.ProjectFileExtension)) return false; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(LoadedAssembly.ShortName) + language.ProjectFileExtension; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName) + language.ProjectFileExtension; dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; if (dlg.ShowDialog() == true) { diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs index 659f72639..52271e845 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourceEntryNode.cs @@ -20,8 +20,8 @@ using System; using System.IO; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.ILSpy.TextView; using Microsoft.Win32; @@ -80,7 +80,7 @@ namespace ICSharpCode.ILSpy.TreeNodes public override bool Save(ViewModels.TabPageModel tabPage) { SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(key)); + dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(key)); if (dlg.ShowDialog() == true) { using var data = OpenStream(); diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs index 37630289f..ce862a523 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourceTreeNode.cs @@ -24,6 +24,7 @@ using System.Text; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; @@ -91,7 +92,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (type == FileType.Xml) ext = ".xml"; else - ext = Path.GetExtension(DecompilerTextView.CleanUpName(Resource.Name)); + ext = Path.GetExtension(WholeProjectDecompiler.SanitizeFileName(Resource.Name)); tabPage.ShowTextView(textView => textView.ShowNode(output, this, HighlightingManager.Instance.GetDefinitionByExtension(ext))); tabPage.SupportsLanguageSwitching = false; return true; @@ -106,7 +107,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (s == null) return false; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(Resource.Name); + dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(Resource.Name)); if (dlg.ShowDialog() == true) { s.Position = 0; diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs index de6145d68..bdef592c7 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs @@ -24,6 +24,7 @@ using System.IO; using System.Linq; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.Controls; @@ -129,7 +130,7 @@ namespace ICSharpCode.ILSpy.TreeNodes if (s == null) return false; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(Resource.Name); + dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(Resource.Name)); dlg.Filter = Resources.ResourcesFileFilter; if (dlg.ShowDialog() == true) {