validPath checks whether paths are valid for the worktree filesystem abstraction. It is intentionally tolerant of .git as the final path component of a multi-component path (e.g. "submodule/.git"), so that legitimate gitlink pointer files can still be Stat'd, Read, and Removed via the wrapper during
(paths ...string)
| 191 | // https://github.com/git/git/blob/v2.54.0/read-cache.c#L987 |
| 192 | // https://github.com/git/git/blob/v2.54.0/path.c#L1419 |
| 193 | func (sfs *worktreeFilesystem) validPath(paths ...string) error { |
| 194 | for _, p := range paths { |
| 195 | for i := 0; i < len(p); i++ { |
| 196 | if p[i] < 0x20 || p[i] == 0x7f { |
| 197 | return fmt.Errorf("invalid path %q: contains control character", p) |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') }) |
| 202 | if len(parts) == 0 { |
| 203 | return fmt.Errorf("invalid path: %q", p) |
| 204 | } |
| 205 | |
| 206 | if sfs.protectNTFS { |
| 207 | // Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:. |
| 208 | if vol := filepath.VolumeName(p); vol != "" { |
| 209 | return fmt.Errorf("invalid path: %q", p) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | for i, part := range parts { |
| 214 | if part == "." || part == ".." { |
| 215 | return fmt.Errorf("invalid path %q: cannot use %q", p, part) |
| 216 | } |
| 217 | |
| 218 | // Reject .git (and equivalents) as a path component when it is |
| 219 | // either the first component (root-level .git) or a non-final |
| 220 | // component (traversal into a .git directory, e.g. "a/.git/config"). |
| 221 | // A final non-first .git component (e.g. "submodule/.git") is |
| 222 | // allowed because submodule worktrees contain a .git pointer file. |
| 223 | if isDotGitVariant(part, sfs.protectHFS) && (i == 0 || i < len(parts)-1) { |
| 224 | return fmt.Errorf("invalid path component: %q", p) |
| 225 | } |
| 226 | |
| 227 | if sfs.protectNTFS && !pathutil.WindowsValidPath(part) { |
| 228 | return fmt.Errorf("invalid path: %q", p) |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | return nil |
| 233 | } |
| 234 | |
| 235 | // validSymlinkName checks the per-component name of a symlink for |
| 236 | // dotfile names that attackers can use to trick a checkout into |