mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 11:35:35 +00:00
116 lines
3.2 KiB
Go
116 lines
3.2 KiB
Go
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
package tailfs
|
||
|
|
||
|
import (
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/tailscale/xnet/webdav"
|
||
|
"tailscale.com/tailfs/shared"
|
||
|
)
|
||
|
|
||
|
// FileServer is a standalone WebDAV server that dynamically serves up shares.
|
||
|
// It's typically used in a separate process from the actual Tailfs server to
|
||
|
// serve up files as an unprivileged user.
|
||
|
type FileServer struct {
|
||
|
l net.Listener
|
||
|
shareHandlers map[string]http.Handler
|
||
|
sharesMu sync.RWMutex
|
||
|
}
|
||
|
|
||
|
// NewFileServer constructs a FileServer.
|
||
|
//
|
||
|
// The server attempts to listen at a random address on 127.0.0.1.
|
||
|
// The listen address is available via the Addr() method.
|
||
|
//
|
||
|
// The server has to be told about shares before it can serve them. This is
|
||
|
// accomplished either by calling SetShares(), or locking the shares with
|
||
|
// LockShares(), clearing them with ClearSharesLocked(), adding them
|
||
|
// individually with AddShareLocked(), and finally unlocking them with
|
||
|
// UnlockShares().
|
||
|
//
|
||
|
// The server doesn't actually process requests until the Serve() method is
|
||
|
// called.
|
||
|
func NewFileServer() (*FileServer, error) {
|
||
|
// path := filepath.Join(os.TempDir(), fmt.Sprintf("%v.socket", uuid.New().String()))
|
||
|
// l, err := safesocket.Listen(path)
|
||
|
// if err != nil {
|
||
|
// TODO(oxtoacart): actually get safesocket working in more environments (MacOS Sandboxed, Windows, ???)
|
||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
// }
|
||
|
return &FileServer{
|
||
|
l: l,
|
||
|
shareHandlers: make(map[string]http.Handler),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// Addr returns the address at which this FileServer is listening.
|
||
|
func (s *FileServer) Addr() string {
|
||
|
return s.l.Addr().String()
|
||
|
}
|
||
|
|
||
|
// Serve() starts serving files and blocks until it encounters a fatal error.
|
||
|
func (s *FileServer) Serve() error {
|
||
|
return http.Serve(s.l, s)
|
||
|
}
|
||
|
|
||
|
// LockShares locks the map of shares in preparation for manipulating it.
|
||
|
func (s *FileServer) LockShares() {
|
||
|
s.sharesMu.Lock()
|
||
|
}
|
||
|
|
||
|
// UnlockShares unlocks the map of shares.
|
||
|
func (s *FileServer) UnlockShares() {
|
||
|
s.sharesMu.Unlock()
|
||
|
}
|
||
|
|
||
|
// ClearSharesLocked clears the map of shares, assuming that LockShares() has
|
||
|
// been called first.
|
||
|
func (s *FileServer) ClearSharesLocked() {
|
||
|
s.shareHandlers = make(map[string]http.Handler)
|
||
|
}
|
||
|
|
||
|
// AddShareLocked adds a share to the map of shares, assuming that LockShares()
|
||
|
// has been called first.
|
||
|
func (s *FileServer) AddShareLocked(share, path string) {
|
||
|
s.shareHandlers[share] = &webdav.Handler{
|
||
|
FileSystem: &birthTimingFS{webdav.Dir(path)},
|
||
|
LockSystem: webdav.NewMemLS(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetShares sets the full map of shares to the new value, mapping name->path.
|
||
|
func (s *FileServer) SetShares(shares map[string]string) {
|
||
|
s.LockShares()
|
||
|
defer s.UnlockShares()
|
||
|
s.ClearSharesLocked()
|
||
|
for name, path := range shares {
|
||
|
s.AddShareLocked(name, path)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ServeHTTP implements the http.Handler interface.
|
||
|
func (s *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
parts := shared.CleanAndSplit(r.URL.Path)
|
||
|
r.URL.Path = shared.Join(parts[1:]...)
|
||
|
share := parts[0]
|
||
|
s.sharesMu.RLock()
|
||
|
h, found := s.shareHandlers[share]
|
||
|
s.sharesMu.RUnlock()
|
||
|
if !found {
|
||
|
w.WriteHeader(http.StatusNotFound)
|
||
|
return
|
||
|
}
|
||
|
h.ServeHTTP(w, r)
|
||
|
}
|
||
|
|
||
|
func (s *FileServer) Close() error {
|
||
|
return s.l.Close()
|
||
|
}
|