Home | History | Annotate | Download | only in path
      1 // Copyright 2010 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package path
      6 
      7 import (
      8 	"errors"
      9 	"strings"
     10 	"unicode/utf8"
     11 )
     12 
     13 // ErrBadPattern indicates a globbing pattern was malformed.
     14 var ErrBadPattern = errors.New("syntax error in pattern")
     15 
     16 // Match reports whether name matches the shell file name pattern.
     17 // The pattern syntax is:
     18 //
     19 //	pattern:
     20 //		{ term }
     21 //	term:
     22 //		'*'         matches any sequence of non-/ characters
     23 //		'?'         matches any single non-/ character
     24 //		'[' [ '^' ] { character-range } ']'
     25 //		            character class (must be non-empty)
     26 //		c           matches character c (c != '*', '?', '\\', '[')
     27 //		'\\' c      matches character c
     28 //
     29 //	character-range:
     30 //		c           matches character c (c != '\\', '-', ']')
     31 //		'\\' c      matches character c
     32 //		lo '-' hi   matches character c for lo <= c <= hi
     33 //
     34 // Match requires pattern to match all of name, not just a substring.
     35 // The only possible returned error is ErrBadPattern, when pattern
     36 // is malformed.
     37 //
     38 func Match(pattern, name string) (matched bool, err error) {
     39 Pattern:
     40 	for len(pattern) > 0 {
     41 		var star bool
     42 		var chunk string
     43 		star, chunk, pattern = scanChunk(pattern)
     44 		if star && chunk == "" {
     45 			// Trailing * matches rest of string unless it has a /.
     46 			return strings.Index(name, "/") < 0, nil
     47 		}
     48 		// Look for match at current position.
     49 		t, ok, err := matchChunk(chunk, name)
     50 		// if we're the last chunk, make sure we've exhausted the name
     51 		// otherwise we'll give a false result even if we could still match
     52 		// using the star
     53 		if ok && (len(t) == 0 || len(pattern) > 0) {
     54 			name = t
     55 			continue
     56 		}
     57 		if err != nil {
     58 			return false, err
     59 		}
     60 		if star {
     61 			// Look for match skipping i+1 bytes.
     62 			// Cannot skip /.
     63 			for i := 0; i < len(name) && name[i] != '/'; i++ {
     64 				t, ok, err := matchChunk(chunk, name[i+1:])
     65 				if ok {
     66 					// if we're the last chunk, make sure we exhausted the name
     67 					if len(pattern) == 0 && len(t) > 0 {
     68 						continue
     69 					}
     70 					name = t
     71 					continue Pattern
     72 				}
     73 				if err != nil {
     74 					return false, err
     75 				}
     76 			}
     77 		}
     78 		return false, nil
     79 	}
     80 	return len(name) == 0, nil
     81 }
     82 
     83 // scanChunk gets the next segment of pattern, which is a non-star string
     84 // possibly preceded by a star.
     85 func scanChunk(pattern string) (star bool, chunk, rest string) {
     86 	for len(pattern) > 0 && pattern[0] == '*' {
     87 		pattern = pattern[1:]
     88 		star = true
     89 	}
     90 	inrange := false
     91 	var i int
     92 Scan:
     93 	for i = 0; i < len(pattern); i++ {
     94 		switch pattern[i] {
     95 		case '\\':
     96 			// error check handled in matchChunk: bad pattern.
     97 			if i+1 < len(pattern) {
     98 				i++
     99 			}
    100 		case '[':
    101 			inrange = true
    102 		case ']':
    103 			inrange = false
    104 		case '*':
    105 			if !inrange {
    106 				break Scan
    107 			}
    108 		}
    109 	}
    110 	return star, pattern[0:i], pattern[i:]
    111 }
    112 
    113 // matchChunk checks whether chunk matches the beginning of s.
    114 // If so, it returns the remainder of s (after the match).
    115 // Chunk is all single-character operators: literals, char classes, and ?.
    116 func matchChunk(chunk, s string) (rest string, ok bool, err error) {
    117 	for len(chunk) > 0 {
    118 		if len(s) == 0 {
    119 			return
    120 		}
    121 		switch chunk[0] {
    122 		case '[':
    123 			// character class
    124 			r, n := utf8.DecodeRuneInString(s)
    125 			s = s[n:]
    126 			chunk = chunk[1:]
    127 			// possibly negated
    128 			notNegated := true
    129 			if len(chunk) > 0 && chunk[0] == '^' {
    130 				notNegated = false
    131 				chunk = chunk[1:]
    132 			}
    133 			// parse all ranges
    134 			match := false
    135 			nrange := 0
    136 			for {
    137 				if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
    138 					chunk = chunk[1:]
    139 					break
    140 				}
    141 				var lo, hi rune
    142 				if lo, chunk, err = getEsc(chunk); err != nil {
    143 					return
    144 				}
    145 				hi = lo
    146 				if chunk[0] == '-' {
    147 					if hi, chunk, err = getEsc(chunk[1:]); err != nil {
    148 						return
    149 					}
    150 				}
    151 				if lo <= r && r <= hi {
    152 					match = true
    153 				}
    154 				nrange++
    155 			}
    156 			if match != notNegated {
    157 				return
    158 			}
    159 
    160 		case '?':
    161 			if s[0] == '/' {
    162 				return
    163 			}
    164 			_, n := utf8.DecodeRuneInString(s)
    165 			s = s[n:]
    166 			chunk = chunk[1:]
    167 
    168 		case '\\':
    169 			chunk = chunk[1:]
    170 			if len(chunk) == 0 {
    171 				err = ErrBadPattern
    172 				return
    173 			}
    174 			fallthrough
    175 
    176 		default:
    177 			if chunk[0] != s[0] {
    178 				return
    179 			}
    180 			s = s[1:]
    181 			chunk = chunk[1:]
    182 		}
    183 	}
    184 	return s, true, nil
    185 }
    186 
    187 // getEsc gets a possibly-escaped character from chunk, for a character class.
    188 func getEsc(chunk string) (r rune, nchunk string, err error) {
    189 	if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
    190 		err = ErrBadPattern
    191 		return
    192 	}
    193 	if chunk[0] == '\\' {
    194 		chunk = chunk[1:]
    195 		if len(chunk) == 0 {
    196 			err = ErrBadPattern
    197 			return
    198 		}
    199 	}
    200 	r, n := utf8.DecodeRuneInString(chunk)
    201 	if r == utf8.RuneError && n == 1 {
    202 		err = ErrBadPattern
    203 	}
    204 	nchunk = chunk[n:]
    205 	if len(nchunk) == 0 {
    206 		err = ErrBadPattern
    207 	}
    208 	return
    209 }
    210