github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/includes_normalize.go (about) 1 // Copyright 2012 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nin 16 17 import ( 18 "errors" 19 "strings" 20 ) 21 22 // Utility functions for normalizing include paths on Windows. 23 // TODO: this likely duplicates functionality of CanonicalizePath; refactor. 24 type includesNormalize struct { 25 relativeTo string 26 splitRelativeTo []string 27 } 28 29 func newIncludesNormalize(relativeTo string) (includesNormalize, error) { 30 relativeTo, err := absPath(relativeTo) 31 return includesNormalize{ 32 relativeTo: relativeTo, 33 splitRelativeTo: strings.Split(relativeTo, "/"), 34 }, err 35 } 36 37 // Return true if paths a and b are on the same windows drive. 38 // Return false if this function cannot check 39 // whether or not on the same windows drive. 40 func sameDriveFast(a string, b string) bool { 41 if len(a) < 3 || len(b) < 3 { 42 return false 43 } 44 45 if !islatinalpha(a[0]) || !islatinalpha(b[0]) { 46 return false 47 } 48 49 if toLowerASCII(a[0]) != toLowerASCII(b[0]) { 50 return false 51 } 52 53 if a[1] != ':' || b[1] != ':' { 54 return false 55 } 56 57 return isPathSeparator(a[2]) && isPathSeparator(b[2]) 58 } 59 60 // Return true if paths a and b are on the same Windows drive. 61 func sameDrive(a, b string) (bool, error) { 62 if sameDriveFast(a, b) { 63 return true, nil 64 } 65 66 aAbsolute, err := internalGetFullPathName(a) 67 if err != nil { 68 return false, err 69 } 70 bAbsolute, err := internalGetFullPathName(b) 71 if err != nil { 72 return false, err 73 } 74 return getDrive(aAbsolute) == getDrive(bAbsolute), nil 75 } 76 77 func getDrive(s string) string { 78 s = strings.TrimPrefix(s, "\\\\?\\") 79 if len(s) >= 2 && islatinalpha(s[0]) && s[1] == ':' { 80 return s[:2] 81 } 82 return "" 83 } 84 85 // Check path |s| is FullPath style returned by GetFullPathName. 86 // This ignores difference of path separator. 87 // This is used not to call very slow GetFullPathName API. 88 func isFullPathName(s string) bool { 89 if len(s) < 3 || !islatinalpha(s[0]) || s[1] != ':' || !isPathSeparator(s[2]) { 90 return false 91 } 92 93 // Check "." or ".." is contained in path. 94 for i := 2; i < len(s); i++ { 95 if !isPathSeparator(s[i]) { 96 continue 97 } 98 99 // Check ".". 100 if i+1 < len(s) && s[i+1] == '.' && (i+2 >= len(s) || isPathSeparator(s[i+2])) { 101 return false 102 } 103 104 // Check "..". 105 if i+2 < len(s) && s[i+1] == '.' && s[i+2] == '.' && (i+3 >= len(s) || isPathSeparator(s[i+3])) { 106 return false 107 } 108 } 109 110 return true 111 } 112 113 // Internal utilities made available for testing, maybe useful otherwise. 114 func absPath(s string) (string, error) { 115 if isFullPathName(s) { 116 return strings.ReplaceAll(s, "\\", "/"), nil 117 } 118 result, err := internalGetFullPathName(s) 119 return strings.ReplaceAll(result, "\\", "/"), err 120 } 121 122 func relativize(path string, startList []string) (string, error) { 123 absPath, err := absPath(path) 124 if err != nil { 125 return "", err 126 } 127 pathList := strings.Split(absPath, "/") 128 i := 0 129 end := len(startList) 130 if end2 := len(pathList); end2 < end { 131 end = end2 132 } 133 for i = 0; i < end; i++ { 134 if !equalsCaseInsensitiveASCII(startList[i], pathList[i]) { 135 break 136 } 137 } 138 139 relList := make([]string, 0, len(pathList)-i) 140 for j := 0; j < len(startList)-i; j++ { 141 relList = append(relList, "..") 142 } 143 for j := i; j < len(pathList); j++ { 144 relList = append(relList, pathList[j]) 145 } 146 if len(relList) == 0 { 147 return ".", nil 148 } 149 return strings.Join(relList, "/"), nil 150 } 151 152 // Normalize by fixing slashes style, fixing redundant .. and . and makes the 153 // path input relative to relativeTo. 154 func (i *includesNormalize) Normalize(input string) (string, error) { 155 len2 := len(input) 156 if len2 >= maxPath { 157 return "", errors.New("path too long") 158 } 159 cp := CanonicalizePath(input) 160 absInput, err := absPath(cp) 161 if err != nil { 162 return "", err 163 } 164 165 same, err := sameDrive(absInput, i.relativeTo) 166 if err != nil { 167 return "", err 168 } 169 if !same { 170 return cp, nil 171 } 172 return relativize(absInput, i.splitRelativeTo) 173 }