예제 #1
0
        ///<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();
        }
예제 #2
0
        /// <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");
        }
예제 #3
0
        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);
        }
예제 #4
0
        // 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));
        }
예제 #5
0
 ///<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;
 }
예제 #6
0
 ///<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();
 }
예제 #7
0
        ///<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;
        }
예제 #8
0
        /// <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.");
        }
예제 #9
0
 public abstract bool Match(string input, Options options);
예제 #10
0
 public override string RegexSource(Options options)
 {
     return Source;
 }
예제 #11
0
 public override bool Match(string input, Options options)
 {
     return regex.Value.IsMatch(input);
 }
예제 #12
0
 public MagicItem(string source, Options options)
 {
     Source = source;
     regex = new Lazy<Regex>(() => new Regex("^" + source + "$", options.RegexOptions));
 }
예제 #13
0
 public override string RegexSource(Options options)
 {
     return Regex.Escape(Source);
 }
예제 #14
0
 public override bool Match(string input, Options options)
 {
     return input.Equals(Source, options.NoCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 }
예제 #15
0
 public override bool Match(string input, Options options)
 {
     throw new NotSupportedException();
 }
예제 #16
0
 public abstract string RegexSource(Options options);
예제 #17
0
 public override string RegexSource(Options options)
 {
     return options.NoGlobStar ? star
     : options.Dot ? twoStarDot
     : twoStarNoDot;
 }
예제 #18
0
        ///<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.");
        }
예제 #20
0
        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);
        }