pkg/desktop/desktop_windows.go
Rama main · réplica de lectura del repositorio original.
//go:build windows
package desktop
import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"github.com/cockroachdb/errors"
"golang.org/x/sys/windows"
)
// open launches the path with its registered handler. For a directory this opens
// an Explorer window on the directory itself.
func open(path string) error {
target, err := shellPath(path)
if err != nil {
return err
}
file, err := windows.UTF16PtrFromString(target)
if err != nil {
return err
}
// A nil verb means the file type's default verb, falling back to "open" and
// then to whatever the registry lists first.
err = windows.ShellExecute(0, nil, file, nil, nil, windows.SW_SHOWNORMAL)
runtime.KeepAlive(file)
if err != nil {
return errors.Wrapf(err, "open %s", target)
}
return nil
}
// reveal opens Explorer on the directory holding the path, with the item selected.
func reveal(path string) error {
target, err := shellPath(path)
if err != nil {
return err
}
// The command line is built by hand below, because Explorer doesn't accept the
// quoting Go applies. A double quote is the one character that could break out
// of it, and an existing file can't contain one, so this check is dead code kept
// as a backstop for a path that never passed Reveal's stat.
if strings.Contains(target, `"`) {
return errors.Newf("cannot reveal a path containing a quote: %s", target)
}
// Resolve explorer.exe absolutely rather than off PATH.
windowsDir, err := windows.GetWindowsDirectory()
if err != nil {
return errors.Wrap(err, "locate the Windows directory")
}
explorer := filepath.Join(windowsDir, "explorer.exe")
// nosemgrep
cmd := exec.Command(explorer)
cmd.SysProcAttr = &syscall.SysProcAttr{
CmdLine: `"` + explorer + `" /select,"` + target + `"`,
}
if err := cmd.Start(); err != nil {
return errors.Wrapf(err, "reveal %s", target)
}
// Explorer's exit status is not meaningful, so it is only reaped.
go func() { _ = cmd.Wait() }()
return nil
}
func supported() bool {
return true
}
// shellPath prepares a path for Explorer, which is not the Win32 file API. The \\?\
// verbatim prefix has to go, because shell entry points reject such a path even
// when it is well under MAX_PATH, so the path must never come from a canonicalizing
// step. And a bare drive designator needs its trailing separator: "C:\" resolves
// where "C:" doesn't.
func shellPath(path string) (string, error) {
if path == "" {
return "", errors.New("empty path")
}
switch {
case strings.HasPrefix(path, `\\?\UNC\`), strings.HasPrefix(path, `\\.\UNC\`):
path = `\\` + path[len(`\\?\UNC\`):] // \\?\UNC\srv\share -> \\srv\share
case strings.HasPrefix(path, `\\?\`), strings.HasPrefix(path, `\\.\`):
path = path[len(`\\?\`):] // \\?\C:\dir -> C:\dir
}
abs, err := filepath.Abs(path)
if err != nil {
return "", errors.Wrapf(err, "resolve %s", path)
}
abs = filepath.Clean(abs)
if len(abs) == 2 && abs[1] == ':' {
// Clean("C:\\") keeps the separator, but Clean("C:") drops it.
abs += `\`
}
// The shell namespace is bound by MAX_PATH with no \\?\ escape hatch, and deep
// object keys reach that in practice. Say so rather than opening a window on
// the wrong thing.
if len(abs) >= 260 {
return "", errors.Newf("path is %d characters, which exceeds the Windows shell's limit of 260: %s", len(abs), abs)
}
return abs, nil
}