github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/rc/rcserver/rcserver.go (about)

     1  // Package rcserver implements the HTTP endpoint to serve the remote control
     2  package rcserver
     3  
     4  import (
     5  	"encoding/json"
     6  	"mime"
     7  	"net/http"
     8  	"net/url"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/ncw/rclone/cmd/serve/httplib"
    14  	"github.com/ncw/rclone/cmd/serve/httplib/serve"
    15  	"github.com/ncw/rclone/fs"
    16  	"github.com/ncw/rclone/fs/cache"
    17  	"github.com/ncw/rclone/fs/config"
    18  	"github.com/ncw/rclone/fs/list"
    19  	"github.com/ncw/rclone/fs/rc"
    20  	"github.com/pkg/errors"
    21  	"github.com/skratchdot/open-golang/open"
    22  )
    23  
    24  // Start the remote control server if configured
    25  //
    26  // If the server wasn't configured the *Server returned may be nil
    27  func Start(opt *rc.Options) (*Server, error) {
    28  	if opt.Enabled {
    29  		// Serve on the DefaultServeMux so can have global registrations appear
    30  		s := newServer(opt, http.DefaultServeMux)
    31  		return s, s.Serve()
    32  	}
    33  	return nil, nil
    34  }
    35  
    36  // Server contains everything to run the rc server
    37  type Server struct {
    38  	*httplib.Server
    39  	files http.Handler
    40  	opt   *rc.Options
    41  }
    42  
    43  func newServer(opt *rc.Options, mux *http.ServeMux) *Server {
    44  	s := &Server{
    45  		Server: httplib.NewServer(mux, &opt.HTTPOptions),
    46  		opt:    opt,
    47  	}
    48  	mux.HandleFunc("/", s.handler)
    49  
    50  	// Add some more mime types which are often missing
    51  	_ = mime.AddExtensionType(".wasm", "application/wasm")
    52  	_ = mime.AddExtensionType(".js", "application/javascript")
    53  
    54  	// File handling
    55  	if opt.Files != "" {
    56  		fs.Logf(nil, "Serving files from %q", opt.Files)
    57  		s.files = http.FileServer(http.Dir(opt.Files))
    58  	}
    59  	return s
    60  }
    61  
    62  // Serve runs the http server in the background.
    63  //
    64  // Use s.Close() and s.Wait() to shutdown server
    65  func (s *Server) Serve() error {
    66  	err := s.Server.Serve()
    67  	if err != nil {
    68  		return err
    69  	}
    70  	fs.Logf(nil, "Serving remote control on %s", s.URL())
    71  	// Open the files in the browser if set
    72  	if s.files != nil {
    73  		openURL, err := url.Parse(s.URL())
    74  		if err != nil {
    75  			return errors.Wrap(err, "invalid serving URL")
    76  		}
    77  		// Add username, password into the URL if they are set
    78  		user, pass := s.opt.HTTPOptions.BasicUser, s.opt.HTTPOptions.BasicPass
    79  		if user != "" || pass != "" {
    80  			openURL.User = url.UserPassword(user, pass)
    81  		}
    82  		_ = open.Start(openURL.String())
    83  	}
    84  	return nil
    85  }
    86  
    87  // writeError writes a formatted error to the output
    88  func writeError(path string, in rc.Params, w http.ResponseWriter, err error, status int) {
    89  	fs.Errorf(nil, "rc: %q: error: %v", path, err)
    90  	// Adjust the error return for some well known errors
    91  	errOrig := errors.Cause(err)
    92  	switch {
    93  	case errOrig == fs.ErrorDirNotFound || errOrig == fs.ErrorObjectNotFound:
    94  		status = http.StatusNotFound
    95  	case rc.IsErrParamInvalid(err) || rc.IsErrParamNotFound(err):
    96  		status = http.StatusBadRequest
    97  	}
    98  	w.WriteHeader(status)
    99  	err = rc.WriteJSON(w, rc.Params{
   100  		"status": status,
   101  		"error":  err.Error(),
   102  		"input":  in,
   103  		"path":   path,
   104  	})
   105  	if err != nil {
   106  		// can't return the error at this point
   107  		fs.Errorf(nil, "rc: failed to write JSON output: %v", err)
   108  	}
   109  }
   110  
   111  // handler reads incoming requests and dispatches them
   112  func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
   113  	path := strings.TrimLeft(r.URL.Path, "/")
   114  
   115  	w.Header().Add("Access-Control-Allow-Origin", "*")
   116  
   117  	// echo back access control headers client needs
   118  	reqAccessHeaders := r.Header.Get("Access-Control-Request-Headers")
   119  	w.Header().Add("Access-Control-Allow-Headers", reqAccessHeaders)
   120  
   121  	switch r.Method {
   122  	case "POST":
   123  		s.handlePost(w, r, path)
   124  	case "OPTIONS":
   125  		s.handleOptions(w, r, path)
   126  	case "GET", "HEAD":
   127  		s.handleGet(w, r, path)
   128  	default:
   129  		writeError(path, nil, w, errors.Errorf("method %q not allowed", r.Method), http.StatusMethodNotAllowed)
   130  		return
   131  	}
   132  }
   133  
   134  func (s *Server) handlePost(w http.ResponseWriter, r *http.Request, path string) {
   135  	contentType := r.Header.Get("Content-Type")
   136  
   137  	values := r.URL.Query()
   138  	if contentType == "application/x-www-form-urlencoded" {
   139  		// Parse the POST and URL parameters into r.Form, for others r.Form will be empty value
   140  		err := r.ParseForm()
   141  		if err != nil {
   142  			writeError(path, nil, w, errors.Wrap(err, "failed to parse form/URL parameters"), http.StatusBadRequest)
   143  			return
   144  		}
   145  		values = r.Form
   146  	}
   147  
   148  	// Read the POST and URL parameters into in
   149  	in := make(rc.Params)
   150  	for k, vs := range values {
   151  		if len(vs) > 0 {
   152  			in[k] = vs[len(vs)-1]
   153  		}
   154  	}
   155  
   156  	// Parse a JSON blob from the input
   157  	if contentType == "application/json" {
   158  		err := json.NewDecoder(r.Body).Decode(&in)
   159  		if err != nil {
   160  			writeError(path, in, w, errors.Wrap(err, "failed to read input JSON"), http.StatusBadRequest)
   161  			return
   162  		}
   163  	}
   164  
   165  	// Find the call
   166  	call := rc.Calls.Get(path)
   167  	if call == nil {
   168  		writeError(path, in, w, errors.Errorf("couldn't find method %q", path), http.StatusNotFound)
   169  		return
   170  	}
   171  
   172  	// Check to see if it requires authorisation
   173  	if !s.opt.NoAuth && call.AuthRequired && !s.UsingAuth() {
   174  		writeError(path, in, w, errors.Errorf("authentication must be set up on the rc server to use %q or the --rc-no-auth flag must be in use", path), http.StatusForbidden)
   175  		return
   176  	}
   177  
   178  	// Check to see if it is async or not
   179  	isAsync, err := in.GetBool("_async")
   180  	if rc.NotErrParamNotFound(err) {
   181  		writeError(path, in, w, err, http.StatusBadRequest)
   182  		return
   183  	}
   184  
   185  	delete(in, "_async") // remove the async parameter after parsing so vfs operations don't get confused
   186  
   187  	fs.Debugf(nil, "rc: %q: with parameters %+v", path, in)
   188  	var out rc.Params
   189  	if isAsync {
   190  		out, err = rc.StartJob(call.Fn, in)
   191  	} else {
   192  		out, err = call.Fn(r.Context(), in)
   193  	}
   194  	if err != nil {
   195  		writeError(path, in, w, err, http.StatusInternalServerError)
   196  		return
   197  	}
   198  	if out == nil {
   199  		out = make(rc.Params)
   200  	}
   201  
   202  	fs.Debugf(nil, "rc: %q: reply %+v: %v", path, out, err)
   203  	err = rc.WriteJSON(w, out)
   204  	if err != nil {
   205  		// can't return the error at this point
   206  		fs.Errorf(nil, "rc: failed to write JSON output: %v", err)
   207  	}
   208  }
   209  
   210  func (s *Server) handleOptions(w http.ResponseWriter, r *http.Request, path string) {
   211  	w.WriteHeader(http.StatusOK)
   212  }
   213  
   214  func (s *Server) serveRoot(w http.ResponseWriter, r *http.Request) {
   215  	remotes := config.FileSections()
   216  	sort.Strings(remotes)
   217  	directory := serve.NewDirectory("", s.HTMLTemplate)
   218  	directory.Title = "List of all rclone remotes."
   219  	q := url.Values{}
   220  	for _, remote := range remotes {
   221  		q.Set("fs", remote)
   222  		directory.AddEntry("["+remote+":]", true)
   223  	}
   224  	directory.Serve(w, r)
   225  }
   226  
   227  func (s *Server) serveRemote(w http.ResponseWriter, r *http.Request, path string, fsName string) {
   228  	f, err := cache.Get(fsName)
   229  	if err != nil {
   230  		writeError(path, nil, w, errors.Wrap(err, "failed to make Fs"), http.StatusInternalServerError)
   231  		return
   232  	}
   233  	if path == "" || strings.HasSuffix(path, "/") {
   234  		path = strings.Trim(path, "/")
   235  		entries, err := list.DirSorted(r.Context(), f, false, path)
   236  		if err != nil {
   237  			writeError(path, nil, w, errors.Wrap(err, "failed to list directory"), http.StatusInternalServerError)
   238  			return
   239  		}
   240  		// Make the entries for display
   241  		directory := serve.NewDirectory(path, s.HTMLTemplate)
   242  		for _, entry := range entries {
   243  			_, isDir := entry.(fs.Directory)
   244  			directory.AddEntry(entry.Remote(), isDir)
   245  		}
   246  		directory.Serve(w, r)
   247  	} else {
   248  		path = strings.Trim(path, "/")
   249  		o, err := f.NewObject(r.Context(), path)
   250  		if err != nil {
   251  			writeError(path, nil, w, errors.Wrap(err, "failed to find object"), http.StatusInternalServerError)
   252  			return
   253  		}
   254  		serve.Object(w, r, o)
   255  	}
   256  }
   257  
   258  // Match URLS of the form [fs]/remote
   259  var fsMatch = regexp.MustCompile(`^\[(.*?)\](.*)$`)
   260  
   261  func (s *Server) handleGet(w http.ResponseWriter, r *http.Request, path string) {
   262  	// Look to see if this has an fs in the path
   263  	match := fsMatch.FindStringSubmatch(path)
   264  	switch {
   265  	case match != nil && s.opt.Serve:
   266  		// Serve /[fs]/remote files
   267  		s.serveRemote(w, r, match[2], match[1])
   268  		return
   269  	case path == "*" && s.opt.Serve:
   270  		// Serve /* as the remote listing
   271  		s.serveRoot(w, r)
   272  		return
   273  	case s.files != nil:
   274  		// Serve the files
   275  		s.files.ServeHTTP(w, r)
   276  		return
   277  	case path == "" && s.opt.Serve:
   278  		// Serve the root as a remote listing
   279  		s.serveRoot(w, r)
   280  		return
   281  	}
   282  	http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
   283  }