package workspace import ( "errors" "io/fs" "os" "path" "strings" ) var ( errInvalidPath = errors.New("workspace path is invalid") errReservedPath = errors.New("workspace path is reserved") errUnsafePath = errors.New("workspace path is unsafe") errNotFound = errors.New("workspace path not found") ) type targetIdentity struct { exists bool device uint64 inode uint64 mode fs.FileMode } // userPath accepts a canonical relative path only. .iop is private runtime // state: no caller-facing operation can name it or a child beneath it. func userPath(value string) (string, error) { if value == "" || strings.Contains(value, "\\") || path.IsAbs(value) || path.Clean(value) != value { return "", errInvalidPath } if value == "." { return value, nil } if strings.HasPrefix(value, "../") || value == ".." { return "", errInvalidPath } first := strings.Split(value, "/")[0] if first == ".iop" { return "", errReservedPath } return value, nil } // internalPath is deliberately unexported. It is available only to future // request-owned runtime artifacts and cannot name sibling request namespaces. func (r *Request) internalPath(value string) (string, error) { if value == "" || path.IsAbs(value) || path.Clean(value) != value || value == "." || strings.HasPrefix(value, "../") || value == ".." { return "", errInvalidPath } prefix := r.internalPrefix + "/" if value != r.internalPrefix && !strings.HasPrefix(value, prefix) { return "", errReservedPath } return value, nil } func checkedExisting(entry *catalogEntry, name string, wantDirectory bool, allowSymlinkTarget bool) (fs.FileInfo, error) { if name != "." { parts := strings.Split(name, "/") for index := range parts { partial := strings.Join(parts[:index+1], "/") info, err := entry.root.Lstat(partial) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil, errNotFound } return nil, errUnsafePath } if info.Mode()&os.ModeSymlink != 0 && (!allowSymlinkTarget || index != len(parts)-1) { return nil, errUnsafePath } if index != len(parts)-1 && !info.IsDir() { return nil, errUnsafePath } if info.Mode()&os.ModeSymlink == 0 { if err := sameFilesystem(entry, partial); err != nil { return nil, err } } } } info, err := entry.root.Lstat(name) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil, errNotFound } return nil, errUnsafePath } if info.Mode()&os.ModeSymlink != 0 && !allowSymlinkTarget { return nil, errUnsafePath } if !allowSymlinkTarget || info.Mode()&os.ModeSymlink == 0 { if err := sameFilesystem(entry, name); err != nil { return nil, err } } if wantDirectory && !info.IsDir() { return nil, errUnsafePath } return info, nil } func sameFilesystem(entry *catalogEntry, name string) error { info, err := entry.root.Stat(name) if err != nil { return errUnsafePath } device, _, ok := fileIdentity(info) if !ok || device != entry.device { return errUnsafePath } return nil }