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 }