///<summary>Creates a new Minimatcher instance, parsing the pattern into a regex.</summary> public Minimatcher(string pattern, Options options = null) { if (pattern == null) throw new ArgumentNullException("pattern"); this.options = options ?? new Options(); this.pattern = pattern.Trim(); if (this.options.AllowWindowsPaths) this.pattern = this.pattern.Replace('\\', '/'); this.Make(); }
/// <inheritdoc/> public override async Task<FtpResponse> Process(FtpCommand command, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(command.Argument)) { var taskStates = Server.GetBackgroundTaskStates(); var statusMessage = new StringBuilder(); statusMessage.AppendFormat("Server functional, {0} open connections", Server.Statistics.ActiveConnections); if (taskStates.Count != 0) statusMessage.AppendFormat(", {0} active background transfers", taskStates.Count); return new FtpResponse(211, statusMessage.ToString()); } var mask = command.Argument; if (!mask.EndsWith("*")) mask += "*"; var mmOptions = new Options() { IgnoreCase = Data.FileSystem.FileSystemEntryComparer.Equals("a", "A"), NoGlobStar = true, Dot = true, }; var mm = new Minimatcher(mask, mmOptions); var formatter = new LongListFormatter(); await Connection.WriteAsync($"211-STAT {command.Argument}", cancellationToken); foreach (var entry in (await Data.FileSystem.GetEntriesAsync(Data.CurrentDirectory, cancellationToken)).Where(x => mm.IsMatch(x.Name))) { var line = formatter.Format(entry); Connection.Log?.Debug(line); await Connection.WriteAsync($" {line}", cancellationToken); } return new FtpResponse(211, "STAT"); }
public static IEnumerable <string> ExpandFileGlobs(IEnumerable <string> potentialGlobs, IEnumerable <string> libraryFiles) { var finalSetOfFiles = new HashSet <string>(); var negatedOptions = new Minimatch.Options { FlipNegate = true }; foreach (string potentialGlob in potentialGlobs) { // only process globs where we find them, otherwise it can get expensive if (potentialGlob.StartsWith("!", StringComparison.Ordinal)) { // Remove matches from the files list var filesToRemove = finalSetOfFiles.Where(f => Minimatcher.Check(f, potentialGlob, negatedOptions)).ToList(); foreach (string file in filesToRemove) { finalSetOfFiles.Remove(file); } } else if (potentialGlob.IndexOfAny(GlobIndicatorCharacters) >= 0) { IEnumerable <string> filterResult = libraryFiles.Where(f => Minimatcher.Check(f, potentialGlob)); if (filterResult.Any()) { finalSetOfFiles.UnionWith(filterResult); } } else { // not a glob pattern, so just include the file literally finalSetOfFiles.Add(potentialGlob); } } return(finalSetOfFiles); }
// Brace expansion: // a{b,c}d -> abd acd // a{b,}c -> abc ac // a{0..3}d -> a0d a1d a2d a3d // a{b,c{d,e}f}g -> abg acdfg acefg // a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg // // Invalid sets are not expanded. // a{2..}b -> a{2..}b // a{b}c -> a{b}c ///<summary>Expands all brace ranges in a pattern, returning a sequence containing every possible combination.</summary> public static IEnumerable<string> BraceExpand(string pattern, Options options) { if (options.NoBrace || !hasBraces.IsMatch(pattern)) { // shortcut. no need to expand. return new[] { pattern }; } bool escaping = false; int i; // examples and comments refer to this crazy pattern: // a{b,c{d,e},{f,g}h}x{y,z} // expected: // abxy // abxz // acdxy // acdxz // acexy // acexz // afhxy // afhxz // aghxy // aghxz // everything before the first \{ is just a prefix. // So, we pluck that off, and work with the rest, // and then prepend it to everything we find. if (pattern[0] != '{') { // console.error(pattern) string prefix = null; for (i = 0; i < pattern.Length; i++) { var c = pattern[i]; // console.error(i, c) if (c == '\\') { escaping = !escaping; } else if (c == '{' && !escaping) { prefix = pattern.Substring(0, i); break; } } // actually no sets, all { were escaped. if (prefix == null) { // console.error("no sets") return new[] { pattern }; } return BraceExpand(pattern.Substring(i), options).Select(t => prefix + t); } // now we have something like: // {b,c{d,e},{f,g}h}x{y,z} // walk through the set, expanding each part, until // the set ends. then, we'll expand the suffix. // If the set only has a single member, then'll put the {} back // first, handle numeric sets, since they're easier var numset = numericSet.Match(pattern); if (numset.Success) { // console.error("numset", numset[1], numset[2]) var suf = BraceExpand(pattern.Substring(numset.Length), options).ToList(); int start = int.Parse(numset.Groups[1].Value), end = int.Parse(numset.Groups[2].Value), inc = start > end ? -1 : 1; var retVal = new List<string>(); for (var w = start; w != (end + inc); w += inc) { // append all the suffixes for (var ii = 0; ii < suf.Count; ii++) { retVal.Add(w.ToString() + suf[ii]); } } return retVal; } // ok, walk through the set // We hope, somewhat optimistically, that there // will be a } at the end. // If the closing brace isn't found, then the pattern is // interpreted as braceExpand("\\" + pattern) so that // the leading \{ will be interpreted literally. i = 1; // skip the \{ int depth = 1; var set = new List<string>(); string member = ""; for (i = 1; i < pattern.Length && depth > 0; i++) { var c = pattern[i]; // console.error("", i, c) if (escaping) { escaping = false; member += "\\" + c; } else { switch (c) { case '\\': escaping = true; continue; case '{': depth++; member += "{"; continue; case '}': depth--; // if this closes the actual set, then we're done if (depth == 0) { set.Add(member); member = ""; // pluck off the close-brace break; } else { member += c; continue; } case ',': if (depth == 1) { set.Add(member); member = ""; } else { member += c; } continue; default: member += c; continue; } // switch } // else } // for // now we've either finished the set, and the suffix is // pattern.substr(i), or we have *not* closed the set, // and need to escape the leading brace if (depth != 0) { // console.error("didn't close", pattern) return BraceExpand("\\" + pattern, options); } // ["b", "c{d,e}","{f,g}h"] -> // ["b", "cd", "ce", "fh", "gh"] var addBraces = set.Count == 1; set = set.SelectMany(p => BraceExpand(p, options)).ToList(); if (addBraces) set = set.Select(s => "{" + s + "}").ToList(); // now attach the suffixes. // x{y,z} -> ["xy", "xz"] // console.error("set", set) // console.error("suffix", pattern.substr(i)) return BraceExpand(pattern.Substring(i), options).SelectMany(s1 => set.Select(s2 => s2 + s1)); }
///<summary>Filters a list of inputs against a single pattern.</summary> ///<remarks>This function reparses this input on each invocation. For performance, avoid this function and reuse a Minimatcher instance instead.</remarks> public static IEnumerable<string> Filter(IEnumerable<string> list, string pattern, Options options = null) { var mm = new Minimatcher(pattern, options); list = list.Where(mm.IsMatch); if (options != null && options.NoNull) list = list.DefaultIfEmpty(pattern); return list; }
///<summary>Compiles a pattern into a single regular expression.</summary> public static Regex CreateRegex(string pattern, Options options = null) { return new Minimatcher(pattern, options).MakeRegex(); }
///<summary>Creates a filter function that tests input against a pattern.</summary> public static Func<string, bool> CreateFilter(string pattern, Options options = null) { if (pattern == null) throw new ArgumentNullException("pattern"); // "" only matches "" if (String.IsNullOrWhiteSpace(pattern)) return String.IsNullOrEmpty; var m = new Minimatcher(pattern, options); return m.IsMatch; }
/// <inheritdoc/> public override async Task<FtpResponse> Process(FtpCommand command, CancellationToken cancellationToken) { await Connection.WriteAsync(new FtpResponse(150, "Opening data connection."), cancellationToken); ITcpSocketClient responseSocket; try { responseSocket = await Connection.CreateResponseSocket(); } catch (Exception) { return new FtpResponse(425, "Can't open data connection."); } try { var argument = command.Argument; IListFormatter formatter; if (string.Equals(command.Name, "NLST", StringComparison.OrdinalIgnoreCase)) { formatter = new ShortListFormatter(); } else { formatter = new LongListFormatter(); } var mask = (string.IsNullOrEmpty(argument) || argument.StartsWith("-")) ? "*" : argument; var encoding = Data.NlstEncoding ?? Connection.Encoding; using (var stream = await Connection.CreateEncryptedStream(responseSocket.WriteStream)) { using (var writer = new StreamWriter(stream, encoding, 4096, true) { NewLine = "\r\n", }) { foreach (var line in formatter.GetPrefix(Data.CurrentDirectory)) { Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } var mmOptions = new Options() { IgnoreCase = Data.FileSystem.FileSystemEntryComparer.Equals("a", "A"), NoGlobStar = true, Dot = true, }; var mm = new Minimatcher(mask, mmOptions); foreach (var entry in (await Data.FileSystem.GetEntriesAsync(Data.CurrentDirectory, cancellationToken)).Where(x => mm.IsMatch(x.Name))) { var line = formatter.Format(entry); Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } foreach (var line in formatter.GetSuffix(Data.CurrentDirectory)) { Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } } } } finally { responseSocket.Dispose(); } // Use 250 when the connection stays open. return new FtpResponse(250, "Closing data connection."); }
public abstract bool Match(string input, Options options);
public override string RegexSource(Options options) { return Source; }
public override bool Match(string input, Options options) { return regex.Value.IsMatch(input); }
public MagicItem(string source, Options options) { Source = source; regex = new Lazy<Regex>(() => new Regex("^" + source + "$", options.RegexOptions)); }
public override string RegexSource(Options options) { return Regex.Escape(Source); }
public override bool Match(string input, Options options) { return input.Equals(Source, options.NoCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); }
public override bool Match(string input, Options options) { throw new NotSupportedException(); }
public abstract string RegexSource(Options options);
public override string RegexSource(Options options) { return options.NoGlobStar ? star : options.Dot ? twoStarDot : twoStarNoDot; }
///<summary>Tests a single input against a pattern.</summary> ///<remarks>This function reparses this input on each invocation. For performance, avoid this function and reuse a Minimatcher instance instead.</remarks> public static bool Check(string input, string pattern, Options options = null) { if (input == null) throw new ArgumentNullException("input"); if (pattern == null) throw new ArgumentNullException("pattern"); // shortcut: comments match nothing. if (options != null && !options.NoComment && pattern[0] == '#') { return false; } // "" only matches "" if (String.IsNullOrWhiteSpace(pattern)) return input == ""; return new Minimatcher(pattern, options).IsMatch(input); }
/// <inheritdoc/> public override async Task<FtpResponse> Process(FtpCommand command, CancellationToken cancellationToken) { await Connection.WriteAsync(new FtpResponse(150, "Opening data connection."), cancellationToken); ITcpSocketClient responseSocket; try { responseSocket = await Connection.CreateResponseSocket(); } catch (Exception) { return new FtpResponse(425, "Can't open data connection."); } try { // Parse arguments in a way that's compatible with broken FTP clients var argument = new ListArguments(command.Argument); var showHidden = argument.All; // Instantiate the formatter IListFormatter formatter; if (string.Equals(command.Name, "NLST", StringComparison.OrdinalIgnoreCase)) { formatter = new ShortListFormatter(); } else if (string.Equals(command.Name, "LS", StringComparison.OrdinalIgnoreCase)) { formatter = new LongListFormatter(); } else { formatter = new LongListFormatter(); } // Parse the given path to determine the mask (e.g. when information about a file was requested) var directoriesToProcess = new Queue<DirectoryQueueItem>(); // Use braces to avoid the definition of mask and path in the following parts // of this function. { var mask = "*"; var path = Data.Path.Clone(); if (!string.IsNullOrEmpty(argument.Path)) { var foundEntry = await Data.FileSystem.SearchEntryAsync(path, argument.Path, cancellationToken); if (foundEntry?.Directory == null) return new FtpResponse(550, "File system entry not found."); var dirEntry = foundEntry.Entry as IUnixDirectoryEntry; if (dirEntry == null) { mask = foundEntry.FileName; } else if (!dirEntry.IsRoot) { path.Push(dirEntry); } } directoriesToProcess.Enqueue(new DirectoryQueueItem(path, mask)); } var encoding = Data.NlstEncoding ?? Connection.Encoding; using (var stream = await Connection.CreateEncryptedStream(responseSocket.WriteStream)) { using (var writer = new StreamWriter(stream, encoding, 4096, true) { NewLine = "\r\n", }) { while (directoriesToProcess.Count != 0) { var queueItem = directoriesToProcess.Dequeue(); var currentPath = queueItem.Path; var mask = queueItem.Mask; var currentDirEntry = currentPath.Count != 0 ? currentPath.Peek() : Data.FileSystem.Root; if (argument.Recursive) { var line = currentPath.ToDisplayString() + ":"; Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } var mmOptions = new Options() { IgnoreCase = Data.FileSystem.FileSystemEntryComparer.Equals("a", "A"), NoGlobStar = true, Dot = true, }; var mm = new Minimatcher(mask, mmOptions); var entries = await Data.FileSystem.GetEntriesAsync(currentDirEntry, cancellationToken); var enumerator = new DirectoryListingEnumerator(entries, Data.FileSystem, currentPath, true); while (enumerator.MoveNext()) { var name = enumerator.Name; if (!enumerator.IsDotEntry) { if (!mm.IsMatch(name)) continue; if (name.StartsWith(".") && !showHidden) continue; } var entry = enumerator.Entry; if (argument.Recursive && !enumerator.IsDotEntry) { var dirEntry = entry as IUnixDirectoryEntry; if (dirEntry != null) { var subDirPath = currentPath.Clone(); subDirPath.Push(dirEntry); directoriesToProcess.Enqueue(new DirectoryQueueItem(subDirPath, "*")); } } var line = formatter.Format(entry, name); Connection.Log?.Debug(line); await writer.WriteLineAsync(line); } } } } } finally { responseSocket.Dispose(); } // Use 250 when the connection stays open. return new FtpResponse(250, "Closing data connection."); }
static void watcher_Changed(object sender, FileSystemEventArgs e) { var matchOptions = new Options { AllowWindowsPaths = true, MatchBase = true, Dot = true, NoCase = true, NoNull = true }; foreach (var ignoredGlob in IgnoredGlobs) { if (new Minimatcher(ignoredGlob, matchOptions).IsMatch(e.FullPath) || e.FullPath.StartsWith(ignoredGlob)) { return; } } var hasKey = EventThrottles.ContainsKey(e.FullPath); if (hasKey && EventThrottles[e.FullPath] > DateTime.Now.AddMilliseconds(-30)) { return; } EventThrottles.AddOrUpdate(e.FullPath, DateTime.Now, (k, v) => DateTime.Now); PvcWatcher.EventQueue.Enqueue(e); }