NTFileSystemHelper.Find.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using Utilities;
  11. namespace SMBLibrary.Server
  12. {
  13. public partial class NTFileSystemHelper
  14. {
  15. // Filename pattern examples:
  16. // '\Directory' - Get the directory entry
  17. // '\Directory\*' - List the directory files
  18. // '\Directory\s*' - List the directory files starting with s (cmd.exe will use this syntax when entering 's' and hitting tab for autocomplete)
  19. // '\Directory\<.inf' (Update driver will use this syntax)
  20. // '\Directory\exefile"*' (cmd.exe will use this syntax when entering an exe without its extension, explorer will use this opening a directory from the run menu)
  21. /// <param name="fileNamePattern">The filename pattern to search for. This field MAY contain wildcard characters</param>
  22. /// <returns>null if the path does not exist</returns>
  23. /// <exception cref="System.UnauthorizedAccessException"></exception>
  24. public static NTStatus FindEntries(out List<FileSystemEntry> entries, IFileSystem fileSystem, string fileNamePattern)
  25. {
  26. int separatorIndex = fileNamePattern.LastIndexOf('\\');
  27. if (separatorIndex >= 0)
  28. {
  29. string path = fileNamePattern.Substring(0, separatorIndex + 1);
  30. string expression = fileNamePattern.Substring(separatorIndex + 1);
  31. return FindEntries(out entries, fileSystem, path, expression);
  32. }
  33. else
  34. {
  35. entries = null;
  36. return NTStatus.STATUS_INVALID_PARAMETER;
  37. }
  38. }
  39. /// <param name="expression">Expression as described in [MS-FSA] 2.1.4.4</param>
  40. /// <returns>null if the path does not exist</returns>
  41. public static NTStatus FindEntries(out List<FileSystemEntry> entries, IFileSystem fileSystem, string path, string expression)
  42. {
  43. entries = null;
  44. FileSystemEntry entry = fileSystem.GetEntry(path);
  45. if (entry == null)
  46. {
  47. return NTStatus.STATUS_NO_SUCH_FILE;
  48. }
  49. if (expression == String.Empty)
  50. {
  51. return NTStatus.STATUS_INVALID_PARAMETER;
  52. }
  53. bool findExactName = !ContainsWildcardCharacters(expression);
  54. if (!findExactName)
  55. {
  56. try
  57. {
  58. entries = fileSystem.ListEntriesInDirectory(path);
  59. }
  60. catch (UnauthorizedAccessException)
  61. {
  62. return NTStatus.STATUS_ACCESS_DENIED;
  63. }
  64. entries = GetFiltered(entries, expression);
  65. // Windows will return "." and ".." when enumerating directory files.
  66. // The SMB1 / SMB2 specifications mandate that when zero entries are found, the server SHOULD / MUST return STATUS_NO_SUCH_FILE.
  67. // For this reason, we MUST include the current directory and/or parent directory when enumerating a directory
  68. // in order to diffrentiate between a directory that does not exist and a directory with no entries.
  69. FileSystemEntry currentDirectory = fileSystem.GetEntry(path);
  70. currentDirectory.Name = ".";
  71. FileSystemEntry parentDirectory = fileSystem.GetEntry(FileSystem.GetParentDirectory(path));
  72. parentDirectory.Name = "..";
  73. entries.Insert(0, parentDirectory);
  74. entries.Insert(0, currentDirectory);
  75. }
  76. else
  77. {
  78. entry = fileSystem.GetEntry(path + expression);
  79. if (entry == null)
  80. {
  81. return NTStatus.STATUS_NO_SUCH_FILE;
  82. }
  83. entries = new List<FileSystemEntry>();
  84. entries.Add(entry);
  85. }
  86. return NTStatus.STATUS_SUCCESS;
  87. }
  88. /// <param name="expression">Expression as described in [MS-FSA] 2.1.4.4</param>
  89. private static List<FileSystemEntry> GetFiltered(List<FileSystemEntry> entries, string expression)
  90. {
  91. if (expression == "*")
  92. {
  93. return entries;
  94. }
  95. List<FileSystemEntry> result = new List<FileSystemEntry>();
  96. foreach (FileSystemEntry entry in entries)
  97. {
  98. if (IsFileNameInExpression(entry.Name, expression))
  99. {
  100. result.Add(entry);
  101. }
  102. }
  103. return result;
  104. }
  105. private static bool ContainsWildcardCharacters(string expression)
  106. {
  107. return (expression.Contains("?") || expression.Contains("*") || expression.Contains("\"") || expression.Contains(">") || expression.Contains("<"));
  108. }
  109. // [MS-FSA] 2.1.4.4
  110. // The FileName is string compared with Expression using the following wildcard rules:
  111. // * (asterisk) Matches zero or more characters.
  112. // ? (question mark) Matches a single character.
  113. // DOS_DOT (" quotation mark) Matches either a period or zero characters beyond the name string.
  114. // DOS_QM (> greater than) Matches any single character or, upon encountering a period or end of name string, advances the expression to the end of the set of contiguous DOS_QMs.
  115. // DOS_STAR (< less than) Matches zero or more characters until encountering and matching the final . in the name.
  116. private static bool IsFileNameInExpression(string fileName, string expression)
  117. {
  118. if (expression == "*")
  119. {
  120. return true;
  121. }
  122. else if (expression.EndsWith("*")) // expression.Length > 1
  123. {
  124. string desiredFileNameStart = expression.Substring(0, expression.Length - 1);
  125. bool findExactNameWithoutExtension = false;
  126. if (desiredFileNameStart.EndsWith("\""))
  127. {
  128. findExactNameWithoutExtension = true;
  129. desiredFileNameStart = desiredFileNameStart.Substring(0, desiredFileNameStart.Length - 1);
  130. }
  131. if (!findExactNameWithoutExtension)
  132. {
  133. if (fileName.StartsWith(desiredFileNameStart, StringComparison.InvariantCultureIgnoreCase))
  134. {
  135. return true;
  136. }
  137. }
  138. else
  139. {
  140. if (fileName.StartsWith(desiredFileNameStart + ".", StringComparison.InvariantCultureIgnoreCase) ||
  141. fileName.Equals(desiredFileNameStart, StringComparison.InvariantCultureIgnoreCase))
  142. {
  143. return true;
  144. }
  145. }
  146. }
  147. else if (expression.StartsWith("<"))
  148. {
  149. string desiredFileNameEnd = expression.Substring(1);
  150. if (fileName.EndsWith(desiredFileNameEnd, StringComparison.InvariantCultureIgnoreCase))
  151. {
  152. return true;
  153. }
  154. }
  155. else if (String.Equals(fileName, expression, StringComparison.CurrentCultureIgnoreCase))
  156. {
  157. return true;
  158. }
  159. return false;
  160. }
  161. }
  162. }