...

Source file src/cmd/go/internal/get/path.go

Documentation: cmd/go/internal/get

     1  // Copyright 2018 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 get
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  	"unicode"
    11  	"unicode/utf8"
    12  )
    13  
    14  // The following functions are copied verbatim from cmd/go/internal/module/module.go,
    15  // with a change to additionally reject Windows short-names,
    16  // and one to accept arbitrary letters (golang.org/issue/29101).
    17  //
    18  // TODO(bcmills): After the call site for this function is backported,
    19  // consolidate this back down to a single copy.
    20  //
    21  // NOTE: DO NOT MERGE THESE UNTIL WE DECIDE ABOUT ARBITRARY LETTERS IN MODULE MODE.
    22  
    23  // CheckImportPath checks that an import path is valid.
    24  func CheckImportPath(path string) error {
    25  	if err := checkPath(path, false); err != nil {
    26  		return fmt.Errorf("malformed import path %q: %v", path, err)
    27  	}
    28  	return nil
    29  }
    30  
    31  // checkPath checks that a general path is valid.
    32  // It returns an error describing why but not mentioning path.
    33  // Because these checks apply to both module paths and import paths,
    34  // the caller is expected to add the "malformed ___ path %q: " prefix.
    35  // fileName indicates whether the final element of the path is a file name
    36  // (as opposed to a directory name).
    37  func checkPath(path string, fileName bool) error {
    38  	if !utf8.ValidString(path) {
    39  		return fmt.Errorf("invalid UTF-8")
    40  	}
    41  	if path == "" {
    42  		return fmt.Errorf("empty string")
    43  	}
    44  	if strings.Contains(path, "..") {
    45  		return fmt.Errorf("double dot")
    46  	}
    47  	if strings.Contains(path, "//") {
    48  		return fmt.Errorf("double slash")
    49  	}
    50  	if path[len(path)-1] == '/' {
    51  		return fmt.Errorf("trailing slash")
    52  	}
    53  	elemStart := 0
    54  	for i, r := range path {
    55  		if r == '/' {
    56  			if err := checkElem(path[elemStart:i], fileName); err != nil {
    57  				return err
    58  			}
    59  			elemStart = i + 1
    60  		}
    61  	}
    62  	if err := checkElem(path[elemStart:], fileName); err != nil {
    63  		return err
    64  	}
    65  	return nil
    66  }
    67  
    68  // checkElem checks whether an individual path element is valid.
    69  // fileName indicates whether the element is a file name (not a directory name).
    70  func checkElem(elem string, fileName bool) error {
    71  	if elem == "" {
    72  		return fmt.Errorf("empty path element")
    73  	}
    74  	if strings.Count(elem, ".") == len(elem) {
    75  		return fmt.Errorf("invalid path element %q", elem)
    76  	}
    77  	if elem[0] == '.' && !fileName {
    78  		return fmt.Errorf("leading dot in path element")
    79  	}
    80  	if elem[len(elem)-1] == '.' {
    81  		return fmt.Errorf("trailing dot in path element")
    82  	}
    83  
    84  	charOK := pathOK
    85  	if fileName {
    86  		charOK = fileNameOK
    87  	}
    88  	for _, r := range elem {
    89  		if !charOK(r) {
    90  			return fmt.Errorf("invalid char %q", r)
    91  		}
    92  	}
    93  
    94  	// Windows disallows a bunch of path elements, sadly.
    95  	// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
    96  	short := elem
    97  	if i := strings.Index(short, "."); i >= 0 {
    98  		short = short[:i]
    99  	}
   100  	for _, bad := range badWindowsNames {
   101  		if strings.EqualFold(bad, short) {
   102  			return fmt.Errorf("disallowed path element %q", elem)
   103  		}
   104  	}
   105  
   106  	// Reject path components that look like Windows short-names.
   107  	// Those usually end in a tilde followed by one or more ASCII digits.
   108  	if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 {
   109  		suffix := short[tilde+1:]
   110  		suffixIsDigits := true
   111  		for _, r := range suffix {
   112  			if r < '0' || r > '9' {
   113  				suffixIsDigits = false
   114  				break
   115  			}
   116  		}
   117  		if suffixIsDigits {
   118  			return fmt.Errorf("trailing tilde and digits in path element")
   119  		}
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // pathOK reports whether r can appear in an import path element.
   126  //
   127  // NOTE: This function DIVERGES from module mode pathOK by accepting Unicode letters.
   128  func pathOK(r rune) bool {
   129  	if r < utf8.RuneSelf {
   130  		return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
   131  			'0' <= r && r <= '9' ||
   132  			'A' <= r && r <= 'Z' ||
   133  			'a' <= r && r <= 'z'
   134  	}
   135  	return unicode.IsLetter(r)
   136  }
   137  
   138  // fileNameOK reports whether r can appear in a file name.
   139  // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
   140  // If we expand the set of allowed characters here, we have to
   141  // work harder at detecting potential case-folding and normalization collisions.
   142  // See note about "safe encoding" below.
   143  func fileNameOK(r rune) bool {
   144  	if r < utf8.RuneSelf {
   145  		// Entire set of ASCII punctuation, from which we remove characters:
   146  		//     ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
   147  		// We disallow some shell special characters: " ' * < > ? ` |
   148  		// (Note that some of those are disallowed by the Windows file system as well.)
   149  		// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
   150  		// We allow spaces (U+0020) in file names.
   151  		const allowed = "!#$%&()+,-.=@[]^_{}~ "
   152  		if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
   153  			return true
   154  		}
   155  		for i := 0; i < len(allowed); i++ {
   156  			if rune(allowed[i]) == r {
   157  				return true
   158  			}
   159  		}
   160  		return false
   161  	}
   162  	// It may be OK to add more ASCII punctuation here, but only carefully.
   163  	// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
   164  	return unicode.IsLetter(r)
   165  }
   166  
   167  // badWindowsNames are the reserved file path elements on Windows.
   168  // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
   169  var badWindowsNames = []string{
   170  	"CON",
   171  	"PRN",
   172  	"AUX",
   173  	"NUL",
   174  	"COM1",
   175  	"COM2",
   176  	"COM3",
   177  	"COM4",
   178  	"COM5",
   179  	"COM6",
   180  	"COM7",
   181  	"COM8",
   182  	"COM9",
   183  	"LPT1",
   184  	"LPT2",
   185  	"LPT3",
   186  	"LPT4",
   187  	"LPT5",
   188  	"LPT6",
   189  	"LPT7",
   190  	"LPT8",
   191  	"LPT9",
   192  }
   193  

View as plain text