77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Spec is a parsed source/dest location: [user@]host:path, or a bare local
|
|
// path (Host == "").
|
|
type Spec struct {
|
|
Raw string
|
|
User string
|
|
Host string
|
|
Path string
|
|
}
|
|
|
|
func (s Spec) IsLocal() bool { return s.Host == "" }
|
|
|
|
func (s Spec) String() string {
|
|
if s.IsLocal() {
|
|
return s.Path
|
|
}
|
|
if s.User != "" {
|
|
return fmt.Sprintf("%s@%s:%s", s.User, s.Host, s.Path)
|
|
}
|
|
return fmt.Sprintf("%s:%s", s.Host, s.Path)
|
|
}
|
|
|
|
// parseSpec parses "[user@]host:path" or a local "path". A leading "/",
|
|
// "./" or "../", or the absence of any colon, is treated as a local path so
|
|
// that ordinary absolute/relative paths are never mistaken for a host spec.
|
|
func parseSpec(raw string) (Spec, error) {
|
|
if raw == "" {
|
|
return Spec{}, fmt.Errorf("empty location")
|
|
}
|
|
if strings.HasPrefix(raw, "/") || strings.HasPrefix(raw, "./") || strings.HasPrefix(raw, "../") || !strings.Contains(raw, ":") {
|
|
return Spec{Raw: raw, Path: raw}, nil
|
|
}
|
|
idx := strings.Index(raw, ":")
|
|
hostpart := raw[:idx]
|
|
path := raw[idx+1:]
|
|
if hostpart == "" || path == "" || strings.ContainsAny(hostpart, "/ ") {
|
|
return Spec{}, fmt.Errorf("cannot parse location %q (expected [user@]host:path or a local path)", raw)
|
|
}
|
|
user := ""
|
|
host := hostpart
|
|
if at := strings.Index(hostpart, "@"); at >= 0 {
|
|
user = hostpart[:at]
|
|
host = hostpart[at+1:]
|
|
}
|
|
if host == "" {
|
|
return Spec{}, fmt.Errorf("cannot parse location %q: empty host", raw)
|
|
}
|
|
return Spec{Raw: raw, User: user, Host: host, Path: path}, nil
|
|
}
|
|
|
|
// checkNotSame does a best-effort local check that source and dest don't
|
|
// refer to the exact same path, to avoid an obviously destructive mistake.
|
|
// It cannot resolve whether two different remote hostnames are actually the
|
|
// same machine.
|
|
func checkNotSame(src, dst Spec) error {
|
|
if src.IsLocal() != dst.IsLocal() {
|
|
return nil
|
|
}
|
|
if !src.IsLocal() && !strings.EqualFold(src.Host, dst.Host) {
|
|
return nil
|
|
}
|
|
if !src.IsLocal() && src.User != dst.User {
|
|
return nil
|
|
}
|
|
if filepath.Clean(src.Path) == filepath.Clean(dst.Path) {
|
|
return fmt.Errorf("source and destination resolve to the same path (%s)", src.Path)
|
|
}
|
|
return nil
|
|
}
|