|
|
|
|
@ -41,6 +41,17 @@ type peerAPIServer struct { |
|
|
|
|
|
|
|
|
|
const partialSuffix = ".tspartial" |
|
|
|
|
|
|
|
|
|
func (s *peerAPIServer) diskPath(baseName string) (fullPath string, ok bool) { |
|
|
|
|
clean := path.Clean(baseName) |
|
|
|
|
if clean != baseName || |
|
|
|
|
clean == "." || |
|
|
|
|
strings.ContainsAny(clean, `/\`) || |
|
|
|
|
strings.HasSuffix(clean, partialSuffix) { |
|
|
|
|
return "", false |
|
|
|
|
} |
|
|
|
|
return filepath.Join(s.rootDir, strings.ReplaceAll(url.PathEscape(baseName), ":", "%3a")), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// hasFilesWaiting reports whether any files are buffered in the
|
|
|
|
|
// tailscaled daemon storage.
|
|
|
|
|
func (s *peerAPIServer) hasFilesWaiting() bool { |
|
|
|
|
@ -80,6 +91,85 @@ func (s *peerAPIServer) hasFilesWaiting() bool { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WaitingFile is a JSON-marshaled struct sent by the localapi to pick
|
|
|
|
|
// up queued files.
|
|
|
|
|
type WaitingFile struct { |
|
|
|
|
Name string |
|
|
|
|
Size int64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *peerAPIServer) WaitingFiles() (ret []WaitingFile, err error) { |
|
|
|
|
if s.rootDir == "" { |
|
|
|
|
return nil, errors.New("peerapi disabled; no storage configured") |
|
|
|
|
} |
|
|
|
|
f, err := os.Open(s.rootDir) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
defer f.Close() |
|
|
|
|
for { |
|
|
|
|
des, err := f.ReadDir(10) |
|
|
|
|
for _, de := range des { |
|
|
|
|
name := de.Name() |
|
|
|
|
if strings.HasSuffix(name, partialSuffix) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
if de.Type().IsRegular() { |
|
|
|
|
fi, err := de.Info() |
|
|
|
|
if err != nil { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
ret = append(ret, WaitingFile{ |
|
|
|
|
Name: filepath.Base(name), |
|
|
|
|
Size: fi.Size(), |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if err == io.EOF { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return ret, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *peerAPIServer) DeleteFile(baseName string) error { |
|
|
|
|
if s.rootDir == "" { |
|
|
|
|
return errors.New("peerapi disabled; no storage configured") |
|
|
|
|
} |
|
|
|
|
path, ok := s.diskPath(baseName) |
|
|
|
|
if !ok { |
|
|
|
|
return errors.New("bad filename") |
|
|
|
|
} |
|
|
|
|
err := os.Remove(path) |
|
|
|
|
if err != nil && !os.IsNotExist(err) { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *peerAPIServer) OpenFile(baseName string) (rc io.ReadCloser, size int64, err error) { |
|
|
|
|
if s.rootDir == "" { |
|
|
|
|
return nil, 0, errors.New("peerapi disabled; no storage configured") |
|
|
|
|
} |
|
|
|
|
path, ok := s.diskPath(baseName) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, 0, errors.New("bad filename") |
|
|
|
|
} |
|
|
|
|
f, err := os.Open(path) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, 0, err |
|
|
|
|
} |
|
|
|
|
fi, err := f.Stat() |
|
|
|
|
if err != nil { |
|
|
|
|
f.Close() |
|
|
|
|
return nil, 0, err |
|
|
|
|
} |
|
|
|
|
return f, fi.Size(), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) { |
|
|
|
|
ipStr := ip.String() |
|
|
|
|
|
|
|
|
|
@ -264,13 +354,12 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
http.Error(w, "no rootdir", http.StatusInternalServerError) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
name := path.Base(r.URL.Path) |
|
|
|
|
if name == "." || name == "/" || strings.HasSuffix(name, partialSuffix) { |
|
|
|
|
http.Error(w, "bad filename", http.StatusForbidden) |
|
|
|
|
baseName := path.Base(r.URL.Path) |
|
|
|
|
dstFile, ok := h.ps.diskPath(baseName) |
|
|
|
|
if !ok { |
|
|
|
|
http.Error(w, "bad filename", 400) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
fileBase := strings.ReplaceAll(url.PathEscape(name), ":", "%3a") |
|
|
|
|
dstFile := filepath.Join(h.ps.rootDir, fileBase) |
|
|
|
|
f, err := os.Create(dstFile) |
|
|
|
|
if err != nil { |
|
|
|
|
h.logf("put Create error: %v", err) |
|
|
|
|
@ -296,7 +385,7 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
h.logf("put(%q): %d bytes from %v/%v", name, n, h.remoteAddr.IP, h.peerNode.ComputedName) |
|
|
|
|
h.logf("put of %s from %v/%v", baseName, approxSize(n), h.remoteAddr.IP, h.peerNode.ComputedName) |
|
|
|
|
|
|
|
|
|
// TODO: set modtime
|
|
|
|
|
// TODO: some real response
|
|
|
|
|
@ -305,3 +394,13 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
h.ps.knownEmpty.Set(false) |
|
|
|
|
h.ps.b.send(ipn.Notify{}) // it will set FilesWaiting
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func approxSize(n int64) string { |
|
|
|
|
if n <= 1<<10 { |
|
|
|
|
return "<=1KB" |
|
|
|
|
} |
|
|
|
|
if n <= 1<<20 { |
|
|
|
|
return "<=1MB" |
|
|
|
|
} |
|
|
|
|
return fmt.Sprintf("~%dMB", n/1<<20) |
|
|
|
|
} |
|
|
|
|
|