github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/wildcard/wildcard.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package wildcard implements a very simple wildcard matcher which supports
    21  // only the term '*', which matches any sequence of characters. The match
    22  // function, WildcardMatch, both parses the pattern and matches the target;
    23  // there is no compile stage and WildcardMatch can be a drop in replacement
    24  // anywhere a normal string comparison is done.
    25  //
    26  // This package is very similar to and inspired by github.com/ryanuber/go-
    27  // glob, but with performance optimizations; github.com/gobwas/glob offers a
    28  // much richer glob syntax and faster performance for cases where a compiled
    29  // glob state can be maintained.
    30  //
    31  // Performance comparison:
    32  //
    33  // wildcard:
    34  //
    35  // BenchmarkFixedMatch-4               100000000        14.0 ns/op
    36  // BenchmarkPrefixMatch-4               50000000        26.2 ns/op
    37  // BenchmarkSuffixMatch-4               50000000        25.8 ns/op
    38  // BenchmarkMultipleMatch-4             10000000       167 ns/op
    39  //
    40  // github.com/ryanuber/go-glob:
    41  //
    42  // BenchmarkFixedGoGlob-4              30000000         58.3 ns/op
    43  // BenchmarkPrefixGoGlob-4              20000000       106 ns/op
    44  // BenchmarkSuffixGoGlob-4              20000000       105 ns/op
    45  // BenchmarkMultipleGoGlob-4             5000000       270 ns/op
    46  //
    47  // github.com/gobwas/glob with precompile:
    48  //
    49  // BenchmarkFixedGlobPrecompile-4       100000000       14.1 ns/op
    50  // BenchmarkPrefixGlobPrecompile-4      200000000        6.66 ns/op
    51  // BenchmarkSuffixGlobPrecompile-4      200000000        7.31 ns/op
    52  // BenchmarkMultipleGlobPrecompile-4    10000000       151 ns/op
    53  //
    54  // github.com/gobwas/glob with compile-and-match:
    55  //
    56  // BenchmarkFixedGlob-4                   300000      4120 ns/op
    57  // BenchmarkPrefixGlob-4                 1000000      1502 ns/op
    58  // BenchmarkSuffixGlob-4                 1000000      1501 ns/op
    59  // BenchmarkMultipleGlob-4                300000      5203 ns/op
    60  //
    61  package wildcard
    62  
    63  import (
    64  	"strings"
    65  )
    66  
    67  func Match(pattern, target string) bool {
    68  
    69  	wildcard := "*"
    70  
    71  	// Pattern and target inputs are advanced as substring matches are found,
    72  	// and each iteration operates on the remaining pattern and target.
    73  
    74  	for n := 0; ; n++ {
    75  
    76  		if pattern == wildcard {
    77  
    78  			// Any remaining target matches the remaining "*" pattern.
    79  
    80  			return true
    81  		}
    82  
    83  		i := strings.Index(pattern, wildcard)
    84  
    85  		if n == 0 {
    86  
    87  			// First wildcard.
    88  
    89  			if i == -1 {
    90  
    91  				// No wildcard, so the target must exactly match the pattern.
    92  
    93  				return pattern == target
    94  
    95  			} else if i == 0 {
    96  
    97  				// For the pattern "*abc...", advance the pattern to search for
    98  				// "abc..."
    99  
   100  				pattern = pattern[i+1:]
   101  
   102  			} else if i > 0 {
   103  
   104  				// For the pattern "a*bc...", the target must begin with "a";
   105  				// advance the target past "a" and advance the pattern to
   106  				// search for "bc..."
   107  
   108  				if !strings.HasPrefix(target, pattern[:i]) {
   109  					return false
   110  				}
   111  				target = target[i:]
   112  				pattern = pattern[i+1:]
   113  			}
   114  
   115  		} else {
   116  
   117  			// After advancing from a previous wildcard.
   118  			//
   119  			// In the following cases, the previous wildcard may match the
   120  			// characters still at the start of the remaining target.
   121  
   122  			if i == -1 {
   123  
   124  				// No further wildcard, so the remaining target must end in
   125  				// the remaining pattern.
   126  
   127  				return strings.HasSuffix(target, pattern)
   128  
   129  			} else if i == 0 {
   130  
   131  				// If the previous iteration found "**abc...", then "*abc..."
   132  				// is found in this case; advance to search for "abc..."
   133  
   134  				pattern = pattern[i+1:]
   135  
   136  			} else if i > 0 {
   137  
   138  				// For the remaining pattern "a*bc...", the remaining target
   139  				// must contain "a"; advance the target past the first "a"
   140  				// and advance the pattern to search for "bc..."
   141  
   142  				j := strings.Index(target, pattern[:i])
   143  				if j == -1 {
   144  					return false
   145  				}
   146  				target = target[j+i:]
   147  				pattern = pattern[i+1:]
   148  			}
   149  
   150  		}
   151  	}
   152  }