github.com/avfs/avfs@v0.33.1-0.20240303173310-c6ba67c33eb7/vfs_ostype_on.go (about)

     1  //
     2  //  Copyright 2024 The AVFS authors
     3  //
     4  //  Licensed under the Apache License, Version 2.0 (the "License");
     5  //  you may not use this file except in compliance with the License.
     6  //  You may obtain a copy of the License at
     7  //
     8  //  	http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  //  Unless required by applicable law or agreed to in writing, software
    11  //  distributed under the License is distributed on an "AS IS" BASIS,
    12  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  //  See the License for the specific language governing permissions and
    14  //  limitations under the License.
    15  //
    16  
    17  //go:build avfs_setostype
    18  
    19  package avfs
    20  
    21  import (
    22  	"errors"
    23  	"path/filepath"
    24  	"strings"
    25  	"unicode/utf8"
    26  )
    27  
    28  const buildFeatSetOSType = FeatSetOSType
    29  
    30  // Base returns the last element of path.
    31  // Trailing path separators are removed before extracting the last element.
    32  // If the path is empty, Base returns ".".
    33  // If the path consists entirely of separators, Base returns a single separator.
    34  func Base[T VFSBase](vfs T, path string) string {
    35  	if path == "" {
    36  		return "."
    37  	}
    38  
    39  	// Strip trailing slashes.
    40  	for len(path) > 0 && IsPathSeparator(vfs, path[len(path)-1]) {
    41  		path = path[0 : len(path)-1]
    42  	}
    43  
    44  	// Throw away volume name
    45  	path = path[len(VolumeName(vfs, path)):]
    46  
    47  	// Find the last element
    48  	i := len(path) - 1
    49  	for i >= 0 && !IsPathSeparator(vfs, path[i]) {
    50  		i--
    51  	}
    52  
    53  	if i >= 0 {
    54  		path = path[i+1:]
    55  	}
    56  
    57  	// If empty now, it had only slashes.
    58  	if path == "" {
    59  		return string(vfs.PathSeparator())
    60  	}
    61  
    62  	return path
    63  }
    64  
    65  // Clean returns the shortest path name equivalent to path
    66  // by purely lexical processing. It applies the following rules
    67  // iteratively until no further processing can be done:
    68  //
    69  //  1. Replace multiple Separator elements with a single one.
    70  //  2. Eliminate each . path name element (the current directory).
    71  //  3. Eliminate each inner .. path name element (the parent directory)
    72  //     along with the non-.. element that precedes it.
    73  //  4. Eliminate .. elements that begin a rooted path:
    74  //     that is, replace "/.." by "/" at the beginning of a path,
    75  //     assuming Separator is '/'.
    76  //
    77  // The returned path ends in a slash only if it represents a root directory,
    78  // such as "/" on Unix or `C:\` on Windows.
    79  //
    80  // Finally, any occurrences of slash are replaced by Separator.
    81  //
    82  // If the result of this process is an empty string, Clean
    83  // returns the string ".".
    84  //
    85  // On Windows, Clean does not modify the volume name other than to replace
    86  // occurrences of "/" with `\`.
    87  // For example, Clean("//host/share/../x") returns `\\host\share\x`.
    88  //
    89  // See also Rob Pike, “Lexical File Names in Plan 9 or
    90  // Getting Dot-Dot Right,”
    91  // https://9p.io/sys/doc/lexnames.html
    92  func Clean[T VFSBase](vfs T, path string) string {
    93  	pathSeparator := vfs.PathSeparator()
    94  	originalPath := path
    95  	volLen := VolumeNameLen(vfs, path)
    96  
    97  	path = path[volLen:]
    98  	if path == "" {
    99  		if volLen > 1 && IsPathSeparator(vfs, originalPath[0]) && IsPathSeparator(vfs, originalPath[1]) {
   100  			// should be UNC
   101  			return FromSlash(vfs, originalPath)
   102  		}
   103  
   104  		return originalPath + "."
   105  	}
   106  
   107  	rooted := IsPathSeparator(vfs, path[0])
   108  
   109  	// Invariants:
   110  	//	reading from path; r is index of next byte to process.
   111  	//	writing to buf; w is index of next byte to write.
   112  	//	dotdot is index in buf where .. must stop, either because
   113  	//		it is the leading slash or it is a leading ../../.. prefix.
   114  	n := len(path)
   115  	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
   116  	r, dotdot := 0, 0
   117  
   118  	if rooted {
   119  		out.append(pathSeparator)
   120  
   121  		r, dotdot = 1, 1
   122  	}
   123  
   124  	for r < n {
   125  		switch {
   126  		case IsPathSeparator(vfs, path[r]):
   127  			// empty path element
   128  			r++
   129  		case path[r] == '.' && (r+1 == n || IsPathSeparator(vfs, path[r+1])):
   130  			// . element
   131  			r++
   132  		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(vfs, path[r+2])):
   133  			// .. element: remove to last separator
   134  			r += 2
   135  
   136  			switch {
   137  			case out.w > dotdot:
   138  				// can backtrack
   139  				out.w--
   140  				for out.w > dotdot && !IsPathSeparator(vfs, out.index(out.w)) {
   141  					out.w--
   142  				}
   143  			case !rooted:
   144  				// cannot backtrack, but not rooted, so append .. element.
   145  				if out.w > 0 {
   146  					out.append(pathSeparator)
   147  				}
   148  
   149  				out.append('.')
   150  				out.append('.')
   151  				dotdot = out.w
   152  			}
   153  		default:
   154  			// real path element.
   155  			// add slash if needed
   156  			if rooted && out.w != 1 || !rooted && out.w != 0 {
   157  				out.append(pathSeparator)
   158  			}
   159  
   160  			// If a ':' appears in the path element at the start of a Windows path,
   161  			// insert a .\ at the beginning to avoid converting relative paths
   162  			// like a/../c: into c:.
   163  			if vfs.OSType() == OsWindows && out.w == 0 && out.volLen == 0 && r != 0 {
   164  				for i := r; i < n && !IsPathSeparator(vfs, path[i]); i++ {
   165  					if path[i] == ':' {
   166  						out.append('.')
   167  						out.append(pathSeparator)
   168  
   169  						break
   170  					}
   171  				}
   172  			}
   173  
   174  			// copy element
   175  			for ; r < n && !IsPathSeparator(vfs, path[r]); r++ {
   176  				out.append(path[r])
   177  			}
   178  		}
   179  	}
   180  
   181  	// Turn empty string into "."
   182  	if out.w == 0 {
   183  		out.append('.')
   184  	}
   185  
   186  	return FromSlash(vfs, out.string())
   187  }
   188  
   189  // Dir returns all but the last element of path, typically the path's directory.
   190  // After dropping the final element, Dir calls Clean on the path and trailing
   191  // slashes are removed.
   192  // If the path is empty, Dir returns ".".
   193  // If the path consists entirely of separators, Dir returns a single separator.
   194  // The returned path does not end in a separator unless it is the root directory.
   195  func Dir[T VFSBase](vfs T, path string) string {
   196  	vol := VolumeName(vfs, path)
   197  
   198  	i := len(path) - 1
   199  	for i >= len(vol) && !IsPathSeparator(vfs, path[i]) {
   200  		i--
   201  	}
   202  
   203  	dir := Clean(vfs, path[len(vol):i+1])
   204  	if dir == "." && len(vol) > 2 {
   205  		// must be UNC
   206  		return vol
   207  	}
   208  
   209  	return vol + dir
   210  }
   211  
   212  // FromSlash returns the result of replacing each slash ('/') character
   213  // in path with a separator character. Multiple slashes are replaced
   214  // by multiple separators.
   215  func FromSlash[T VFSBase](vfs T, path string) string {
   216  	pathSeparator := vfs.PathSeparator()
   217  
   218  	if vfs.OSType() != OsWindows {
   219  		return path
   220  	}
   221  
   222  	return strings.ReplaceAll(path, "/", string(pathSeparator))
   223  }
   224  
   225  // getEsc gets a possibly-escaped character from chunk, for a character class.
   226  func getEsc[T VFSBase](vfs T, chunk string) (r rune, nchunk string, err error) {
   227  	if chunk == "" || chunk[0] == '-' || chunk[0] == ']' {
   228  		err = filepath.ErrBadPattern
   229  
   230  		return
   231  	}
   232  
   233  	if chunk[0] == '\\' && vfs.OSType() != OsWindows {
   234  		chunk = chunk[1:]
   235  		if chunk == "" {
   236  			err = filepath.ErrBadPattern
   237  
   238  			return
   239  		}
   240  	}
   241  
   242  	r, n := utf8.DecodeRuneInString(chunk)
   243  	if r == utf8.RuneError && n == 1 {
   244  		err = filepath.ErrBadPattern
   245  	}
   246  
   247  	nchunk = chunk[n:]
   248  	if nchunk == "" {
   249  		err = filepath.ErrBadPattern
   250  	}
   251  
   252  	return
   253  }
   254  
   255  // IsAbs reports whether the path is absolute.
   256  func IsAbs[T VFSBase](vfs T, path string) bool {
   257  	if vfs.OSType() != OsWindows {
   258  		return strings.HasPrefix(path, "/")
   259  	}
   260  
   261  	l := VolumeNameLen(vfs, path)
   262  	if l == 0 {
   263  		return false
   264  	}
   265  
   266  	// If the volume name starts with a double slash, this is an absolute path.
   267  	if isSlash(path[0]) && isSlash(path[1]) {
   268  		return true
   269  	}
   270  
   271  	path = path[l:]
   272  	if path == "" {
   273  		return false
   274  	}
   275  
   276  	return isSlash(path[0])
   277  }
   278  
   279  // IsPathSeparator reports whether c is a directory separator character.
   280  func IsPathSeparator[T VFSBase](vfs T, c uint8) bool {
   281  	if vfs.OSType() != OsWindows {
   282  		return c == '/'
   283  	}
   284  
   285  	return c == '\\' || c == '/'
   286  }
   287  
   288  func isSlash(c uint8) bool {
   289  	return c == '\\' || c == '/'
   290  }
   291  
   292  // Join joins any number of path elements into a single path, adding a
   293  // separating slash if necessary. The result is Cleaned; in particular,
   294  // all empty strings are ignored.
   295  func Join[T VFSBase](vfs T, elem ...string) string {
   296  	pathSeparator := vfs.PathSeparator()
   297  
   298  	if vfs.OSType() != OsWindows {
   299  		// If there's a bug here, fix the logic in ./path_plan9.go too.
   300  		for i, e := range elem {
   301  			if e != "" {
   302  				return Clean(vfs, strings.Join(elem[i:], string(pathSeparator)))
   303  			}
   304  		}
   305  
   306  		return ""
   307  	}
   308  
   309  	return joinWindows(vfs, elem)
   310  }
   311  
   312  func joinWindows[T VFSBase](vfs T, elem []string) string {
   313  	var (
   314  		b        strings.Builder
   315  		lastChar byte
   316  	)
   317  
   318  	for _, e := range elem {
   319  		switch {
   320  		case b.Len() == 0:
   321  			// Add the first non-empty path element unchanged.
   322  		case isSlash(lastChar):
   323  			// If the path ends in a slash, strip any leading slashes from the next
   324  			// path element to avoid creating a UNC path (any path starting with "\\")
   325  			// from non-UNC elements.
   326  			//
   327  			// The correct behavior for Join when the first element is an incomplete UNC
   328  			// path (for example, "\\") is underspecified. We currently join subsequent
   329  			// elements so Join("\\", "host", "share") produces "\\host\share".
   330  			for len(e) > 0 && isSlash(e[0]) {
   331  				e = e[1:]
   332  			}
   333  		case lastChar == ':':
   334  			// If the path ends in a colon, keep the path relative to the current directory
   335  			// on a drive and don't add a separator. Preserve leading slashes in the next
   336  			// path element, which may make the path absolute.
   337  			//
   338  			// 	Join(`C:`, `f`) = `C:f`
   339  			//	Join(`C:`, `\f`) = `C:\f`
   340  		default:
   341  			// In all other cases, add a separator between elements.
   342  			b.WriteByte('\\')
   343  
   344  			lastChar = '\\'
   345  		}
   346  
   347  		if len(e) > 0 {
   348  			b.WriteString(e)
   349  			lastChar = e[len(e)-1]
   350  		}
   351  	}
   352  
   353  	if b.Len() == 0 {
   354  		return ""
   355  	}
   356  
   357  	return Clean(vfs, b.String())
   358  }
   359  
   360  // Match reports whether name matches the shell file name pattern.
   361  // The pattern syntax is:
   362  //
   363  //	pattern:
   364  //		{ term }
   365  //	term:
   366  //		'*'         matches any sequence of non-Separator characters
   367  //		'?'         matches any single non-Separator character
   368  //		'[' [ '^' ] { character-range } ']'
   369  //		            character class (must be non-empty)
   370  //		c           matches character c (c != '*', '?', '\\', '[')
   371  //		'\\' c      matches character c
   372  //
   373  //	character-range:
   374  //		c           matches character c (c != '\\', '-', ']')
   375  //		'\\' c      matches character c
   376  //		lo '-' hi   matches character c for lo <= c <= hi
   377  //
   378  // Match requires pattern to match all of name, not just a substring.
   379  // The only possible returned error is ErrBadPattern, when pattern
   380  // is malformed.
   381  //
   382  // On Windows, escaping is disabled. Instead, '\\' is treated as
   383  // path separator.
   384  func Match[T VFSBase](vfs T, pattern, name string) (matched bool, err error) {
   385  	pathSeparator := vfs.PathSeparator()
   386  
   387  Pattern:
   388  	for len(pattern) > 0 {
   389  		var star bool
   390  		var chunk string
   391  
   392  		star, chunk, pattern = scanChunk(vfs, pattern)
   393  		if star && chunk == "" {
   394  			// Trailing * matches rest of string unless it has a /.
   395  			return !strings.Contains(name, string(pathSeparator)), nil
   396  		}
   397  
   398  		// Look for match at current position.
   399  		t, ok, err := matchChunk(vfs, chunk, name)
   400  
   401  		// if we're the last chunk, make sure we've exhausted the name
   402  		// otherwise we'll give a false result even if we could still match
   403  		// using the star
   404  		if ok && (t == "" || len(pattern) > 0) {
   405  			name = t
   406  
   407  			continue
   408  		}
   409  
   410  		if err != nil {
   411  			return false, err
   412  		}
   413  
   414  		if star {
   415  			// Look for match skipping i+1 bytes.
   416  			// Cannot skip /.
   417  			for i := 0; i < len(name) && name[i] != pathSeparator; i++ {
   418  				t, ok, err := matchChunk(vfs, chunk, name[i+1:])
   419  				if ok {
   420  					// if we're the last chunk, make sure we exhausted the name
   421  					if pattern == "" && len(t) > 0 {
   422  						continue
   423  					}
   424  					name = t
   425  
   426  					continue Pattern
   427  				}
   428  				if err != nil {
   429  					return false, err
   430  				}
   431  			}
   432  		}
   433  
   434  		return false, nil
   435  	}
   436  
   437  	return name == "", nil
   438  }
   439  
   440  // matchChunk checks whether chunk matches the beginning of s.
   441  // If so, it returns the remainder of s (after the match).
   442  // Chunk is all single-character operators: literals, char classes, and ?.
   443  func matchChunk[T VFSBase](vfs T, chunk, s string) (rest string, ok bool, err error) {
   444  	pathSeparator := vfs.PathSeparator()
   445  
   446  	// failed records whether the match has failed.
   447  	// After the match fails, the loop continues on processing chunk,
   448  	// checking that the pattern is well-formed but no longer reading s.
   449  	failed := false
   450  
   451  	for len(chunk) > 0 {
   452  		if !failed && s == "" {
   453  			failed = true
   454  		}
   455  
   456  		switch chunk[0] {
   457  		case '[':
   458  			// character class
   459  			var r rune
   460  
   461  			if !failed {
   462  				var n int
   463  				r, n = utf8.DecodeRuneInString(s)
   464  				s = s[n:]
   465  			}
   466  
   467  			chunk = chunk[1:]
   468  			// possibly negated
   469  			negated := false
   470  
   471  			if len(chunk) > 0 && chunk[0] == '^' {
   472  				negated = true
   473  				chunk = chunk[1:]
   474  			}
   475  
   476  			// parse all ranges
   477  			match := false
   478  			nrange := 0
   479  
   480  			for {
   481  				if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
   482  					chunk = chunk[1:]
   483  
   484  					break
   485  				}
   486  
   487  				var lo, hi rune
   488  
   489  				if lo, chunk, err = getEsc(vfs, chunk); err != nil {
   490  					return "", false, err
   491  				}
   492  
   493  				hi = lo
   494  
   495  				if chunk[0] == '-' {
   496  					if hi, chunk, err = getEsc(vfs, chunk[1:]); err != nil {
   497  						return "", false, err
   498  					}
   499  				}
   500  
   501  				if lo <= r && r <= hi {
   502  					match = true
   503  				}
   504  
   505  				nrange++
   506  			}
   507  
   508  			if match == negated {
   509  				failed = true
   510  			}
   511  		case '?':
   512  			if !failed {
   513  				if s[0] == pathSeparator {
   514  					failed = true
   515  				}
   516  
   517  				_, n := utf8.DecodeRuneInString(s)
   518  				s = s[n:]
   519  			}
   520  
   521  			chunk = chunk[1:]
   522  		case '\\':
   523  			if vfs.OSType() != OsWindows {
   524  				chunk = chunk[1:]
   525  				if chunk == "" {
   526  					return "", false, filepath.ErrBadPattern
   527  				}
   528  			}
   529  
   530  			fallthrough
   531  		default:
   532  			if !failed {
   533  				if chunk[0] != s[0] {
   534  					failed = true
   535  				}
   536  
   537  				s = s[1:]
   538  			}
   539  
   540  			chunk = chunk[1:]
   541  		}
   542  	}
   543  
   544  	if failed {
   545  		return "", false, nil
   546  	}
   547  
   548  	return s, true, nil
   549  }
   550  
   551  // Rel returns a relative path that is lexically equivalent to targpath when
   552  // joined to basepath with an intervening separator. That is,
   553  // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
   554  // On success, the returned path will always be relative to basepath,
   555  // even if basepath and targpath share no elements.
   556  // An error is returned if targpath can't be made relative to basepath or if
   557  // knowing the current working directory would be necessary to compute it.
   558  // Rel calls Clean on the result.
   559  func Rel[T VFSBase](vfs T, basepath, targpath string) (string, error) {
   560  	pathSeparator := vfs.PathSeparator()
   561  
   562  	baseVol := VolumeName(vfs, basepath)
   563  	targVol := VolumeName(vfs, targpath)
   564  	base := Clean(vfs, basepath)
   565  	targ := Clean(vfs, targpath)
   566  
   567  	if sameWord(vfs, targ, base) {
   568  		return ".", nil
   569  	}
   570  
   571  	base = base[len(baseVol):]
   572  	targ = targ[len(targVol):]
   573  
   574  	if base == "." {
   575  		base = ""
   576  	} else if base == "" && VolumeNameLen(vfs, baseVol) > 2 /* isUNC */ {
   577  		// Treat any targetpath matching `\\host\share` basepath as absolute path.
   578  		base = string(pathSeparator)
   579  	}
   580  
   581  	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
   582  	baseSlashed := len(base) > 0 && base[0] == pathSeparator
   583  	targSlashed := len(targ) > 0 && targ[0] == pathSeparator
   584  
   585  	if baseSlashed != targSlashed || !sameWord(vfs, baseVol, targVol) {
   586  		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
   587  	}
   588  
   589  	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
   590  	bl := len(base)
   591  	tl := len(targ)
   592  
   593  	var b0, bi, t0, ti int
   594  
   595  	for {
   596  		for bi < bl && base[bi] != pathSeparator {
   597  			bi++
   598  		}
   599  
   600  		for ti < tl && targ[ti] != pathSeparator {
   601  			ti++
   602  		}
   603  
   604  		if !sameWord(vfs, targ[t0:ti], base[b0:bi]) {
   605  			break
   606  		}
   607  
   608  		if bi < bl {
   609  			bi++
   610  		}
   611  
   612  		if ti < tl {
   613  			ti++
   614  		}
   615  
   616  		b0 = bi
   617  		t0 = ti
   618  	}
   619  
   620  	if base[b0:bi] == ".." {
   621  		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
   622  	}
   623  
   624  	if b0 != bl {
   625  		// Base elements left. Must go up before going down.
   626  		seps := strings.Count(base[b0:bl], string(pathSeparator))
   627  		size := 2 + seps*3
   628  
   629  		if tl != t0 {
   630  			size += 1 + tl - t0
   631  		}
   632  
   633  		buf := make([]byte, size)
   634  		n := copy(buf, "..")
   635  
   636  		for i := 0; i < seps; i++ {
   637  			buf[n] = pathSeparator
   638  			copy(buf[n+1:], "..")
   639  			n += 3
   640  		}
   641  
   642  		if t0 != tl {
   643  			buf[n] = pathSeparator
   644  			copy(buf[n+1:], targ[t0:])
   645  		}
   646  
   647  		return string(buf), nil
   648  	}
   649  
   650  	return targ[t0:], nil
   651  }
   652  
   653  func sameWord[T VFSBase](vfs T, a, b string) bool {
   654  	if vfs.OSType() != OsWindows {
   655  		return a == b
   656  	}
   657  
   658  	return strings.EqualFold(a, b)
   659  }
   660  
   661  // scanChunk gets the next segment of pattern, which is a non-star string
   662  // possibly preceded by a star.
   663  func scanChunk[T VFSBase](vfs T, pattern string) (star bool, chunk, rest string) {
   664  	for len(pattern) > 0 && pattern[0] == '*' {
   665  		pattern = pattern[1:]
   666  		star = true
   667  	}
   668  
   669  	inrange := false
   670  
   671  	var i int
   672  
   673  Scan:
   674  	for i = 0; i < len(pattern); i++ {
   675  		switch pattern[i] {
   676  		case '\\':
   677  			if vfs.OSType() != OsWindows {
   678  				// error check handled in matchChunk: bad pattern.
   679  				if i+1 < len(pattern) {
   680  					i++
   681  				}
   682  			}
   683  		case '[':
   684  			inrange = true
   685  		case ']':
   686  			inrange = false
   687  		case '*':
   688  			if !inrange {
   689  				break Scan
   690  			}
   691  		}
   692  	}
   693  
   694  	return star, pattern[0:i], pattern[i:]
   695  }
   696  
   697  // Split splits path immediately following the final Separator,
   698  // separating it into a directory and file name component.
   699  // If there is no Separator in path, Split returns an empty dir
   700  // and file set to path.
   701  // The returned values have the property that path = dir+file.
   702  func Split[T VFSBase](vfs T, path string) (dir, file string) {
   703  	vol := VolumeName(vfs, path)
   704  
   705  	i := len(path) - 1
   706  	for i >= len(vol) && !IsPathSeparator(vfs, path[i]) {
   707  		i--
   708  	}
   709  
   710  	return path[:i+1], path[i+1:]
   711  }
   712  
   713  // ToSlash returns the result of replacing each separator character
   714  // in path with a slash ('/') character. Multiple separators are
   715  // replaced by multiple slashes.
   716  func ToSlash[T VFSBase](vfs T, path string) string {
   717  	pathSeparator := vfs.PathSeparator()
   718  
   719  	if pathSeparator == '/' {
   720  		return path
   721  	}
   722  
   723  	return strings.ReplaceAll(path, string(pathSeparator), "/")
   724  }
   725  
   726  // VolumeNameLen returns length of the leading volume name on Windows.
   727  // It returns 0 elsewhere.
   728  func VolumeNameLen[T VFSBase](vfs T, path string) int {
   729  	if vfs.OSType() != OsWindows {
   730  		return 0
   731  	}
   732  
   733  	if len(path) < 2 {
   734  		return 0
   735  	}
   736  
   737  	// with drive letter
   738  	c := path[0]
   739  	if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
   740  		return 2
   741  	}
   742  
   743  	// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
   744  	if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
   745  		!isSlash(path[2]) && path[2] != '.' {
   746  		// first, leading `\\` and next shouldn't be `\`. its server name.
   747  		for n := 3; n < l-1; n++ {
   748  			// second, next '\' shouldn't be repeated.
   749  			if isSlash(path[n]) {
   750  				n++
   751  				// third, following something characters. its share name.
   752  				if !isSlash(path[n]) {
   753  					if path[n] == '.' {
   754  						break
   755  					}
   756  
   757  					for ; n < l; n++ {
   758  						if isSlash(path[n]) {
   759  							break
   760  						}
   761  					}
   762  
   763  					return n
   764  				}
   765  
   766  				break
   767  			}
   768  		}
   769  	}
   770  
   771  	return 0
   772  }
   773  
   774  // A lazybuf is a lazily constructed path buffer.
   775  // It supports append, reading previously appended bytes,
   776  // and retrieving the final string. It does not allocate a buffer
   777  // to hold the output until that output diverges from s.
   778  type lazybuf struct {
   779  	path       string
   780  	volAndPath string
   781  	buf        []byte
   782  	w          int
   783  	volLen     int
   784  }
   785  
   786  func (b *lazybuf) index(i int) byte {
   787  	if b.buf != nil {
   788  		return b.buf[i]
   789  	}
   790  
   791  	return b.path[i]
   792  }
   793  
   794  func (b *lazybuf) append(c byte) {
   795  	if b.buf == nil {
   796  		if b.w < len(b.path) && b.path[b.w] == c {
   797  			b.w++
   798  
   799  			return
   800  		}
   801  
   802  		b.buf = make([]byte, len(b.path))
   803  		copy(b.buf, b.path[:b.w])
   804  	}
   805  
   806  	b.buf[b.w] = c
   807  	b.w++
   808  }
   809  
   810  func (b *lazybuf) string() string {
   811  	if b.buf == nil {
   812  		return b.volAndPath[:b.volLen+b.w]
   813  	}
   814  
   815  	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
   816  }