1// Package httplib provides common functionality for http servers
2//
3// Deprecated: httplib has been replaced with lib/http
4package httplib
5
6import (
7	"context"
8	"crypto/tls"
9	"crypto/x509"
10	"encoding/base64"
11	"fmt"
12	"html/template"
13	"io/ioutil"
14	"log"
15	"net"
16	"net/http"
17	"strings"
18	"time"
19
20	auth "github.com/abbot/go-http-auth"
21	"github.com/pkg/errors"
22	"github.com/rclone/rclone/cmd/serve/http/data"
23	"github.com/rclone/rclone/fs"
24)
25
26// Globals
27var ()
28
29// Help contains text describing the http server to add to the command
30// help.
31var Help = `
32### Server options
33
34Use --addr to specify which IP address and port the server should
35listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all
36IPs.  By default it only listens on localhost.  You can use port
37:0 to let the OS choose an available port.
38
39If you set --addr to listen on a public or LAN accessible IP address
40then using Authentication is advised - see the next section for info.
41
42--server-read-timeout and --server-write-timeout can be used to
43control the timeouts on the server.  Note that this is the total time
44for a transfer.
45
46--max-header-bytes controls the maximum number of bytes the server will
47accept in the HTTP header.
48
49--baseurl controls the URL prefix that rclone serves from.  By default
50rclone will serve from the root.  If you used --baseurl "/rclone" then
51rclone would serve from a URL starting with "/rclone/".  This is
52useful if you wish to proxy rclone serve.  Rclone automatically
53inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
54--baseurl "/rclone" and --baseurl "/rclone/" are all treated
55identically.
56
57--template allows a user to specify a custom markup template for http
58and webdav serve functions.  The server exports the following markup
59to be used within the template to server pages:
60
61| Parameter   | Description |
62| :---------- | :---------- |
63| .Name       | The full path of a file/directory. |
64| .Title      | Directory listing of .Name |
65| .Sort       | The current sort used.  This is changeable via ?sort= parameter |
66|             | Sort Options: namedirfirst,name,size,time (default namedirfirst) |
67| .Order      | The current ordering used.  This is changeable via ?order= parameter |
68|             | Order Options: asc,desc (default asc) |
69| .Query      | Currently unused. |
70| .Breadcrumb | Allows for creating a relative navigation |
71|-- .Link     | The relative to the root link of the Text. |
72|-- .Text     | The Name of the directory. |
73| .Entries    | Information about a specific file/directory. |
74|-- .URL      | The 'url' of an entry.  |
75|-- .Leaf     | Currently same as 'URL' but intended to be 'just' the name. |
76|-- .IsDir    | Boolean for if an entry is a directory or not. |
77|-- .Size     | Size in Bytes of the entry. |
78|-- .ModTime  | The UTC timestamp of an entry. |
79
80#### Authentication
81
82By default this will serve files without needing a login.
83
84You can either use an htpasswd file which can take lots of users, or
85set a single username and password with the --user and --pass flags.
86
87Use --htpasswd /path/to/htpasswd to provide an htpasswd file.  This is
88in standard apache format and supports MD5, SHA1 and BCrypt for basic
89authentication.  Bcrypt is recommended.
90
91To create an htpasswd file:
92
93    touch htpasswd
94    htpasswd -B htpasswd user
95    htpasswd -B htpasswd anotherUser
96
97The password file can be updated while rclone is running.
98
99Use --realm to set the authentication realm.
100
101#### SSL/TLS
102
103By default this will serve over http.  If you want you can serve over
104https.  You will need to supply the --cert and --key flags.  If you
105wish to do client side certificate validation then you will need to
106supply --client-ca also.
107
108--cert should be either a PEM encoded certificate or a concatenation
109of that with the CA certificate.  --key should be the PEM encoded
110private key and --client-ca should be the PEM encoded client
111certificate authority certificate.
112`
113
114// Options contains options for the http Server
115type Options struct {
116	ListenAddr         string        // Port to listen on
117	BaseURL            string        // prefix to strip from URLs
118	ServerReadTimeout  time.Duration // Timeout for server reading data
119	ServerWriteTimeout time.Duration // Timeout for server writing data
120	MaxHeaderBytes     int           // Maximum size of request header
121	SslCert            string        // SSL PEM key (concatenation of certificate and CA certificate)
122	SslKey             string        // SSL PEM Private key
123	ClientCA           string        // Client certificate authority to verify clients with
124	HtPasswd           string        // htpasswd file - if not provided no authentication is done
125	Realm              string        // realm for authentication
126	BasicUser          string        // single username for basic auth if not using Htpasswd
127	BasicPass          string        // password for BasicUser
128	Auth               AuthFn        `json:"-"` // custom Auth (not set by command line flags)
129	Template           string        // User specified template
130}
131
132// AuthFn if used will be used to authenticate user, pass. If an error
133// is returned then the user is not authenticated.
134//
135// If a non nil value is returned then it is added to the context under the key
136type AuthFn func(user, pass string) (value interface{}, err error)
137
138// DefaultOpt is the default values used for Options
139var DefaultOpt = Options{
140	ListenAddr:         "localhost:8080",
141	Realm:              "rclone",
142	ServerReadTimeout:  1 * time.Hour,
143	ServerWriteTimeout: 1 * time.Hour,
144	MaxHeaderBytes:     4096,
145}
146
147// Server contains info about the running http server
148type Server struct {
149	Opt             Options
150	handler         http.Handler // original handler
151	listener        net.Listener
152	waitChan        chan struct{} // for waiting on the listener to close
153	httpServer      *http.Server
154	basicPassHashed string
155	useSSL          bool               // if server is configured for SSL/TLS
156	usingAuth       bool               // set if authentication is configured
157	HTMLTemplate    *template.Template // HTML template for web interface
158}
159
160type contextUserType struct{}
161
162// ContextUserKey is a simple context key for storing the username of the request
163var ContextUserKey = &contextUserType{}
164
165type contextAuthType struct{}
166
167// ContextAuthKey is a simple context key for storing info returned by AuthFn
168var ContextAuthKey = &contextAuthType{}
169
170// singleUserProvider provides the encrypted password for a single user
171func (s *Server) singleUserProvider(user, realm string) string {
172	if user == s.Opt.BasicUser {
173		return s.basicPassHashed
174	}
175	return ""
176}
177
178// parseAuthorization parses the Authorization header into user, pass
179// it returns a boolean as to whether the parse was successful
180func parseAuthorization(r *http.Request) (user, pass string, ok bool) {
181	authHeader := r.Header.Get("Authorization")
182	if authHeader != "" {
183		s := strings.SplitN(authHeader, " ", 2)
184		if len(s) == 2 && s[0] == "Basic" {
185			b, err := base64.StdEncoding.DecodeString(s[1])
186			if err == nil {
187				parts := strings.SplitN(string(b), ":", 2)
188				user = parts[0]
189				if len(parts) > 1 {
190					pass = parts[1]
191					ok = true
192				}
193			}
194		}
195	}
196	return
197}
198
199// NewServer creates an http server.  The opt can be nil in which case
200// the default options will be used.
201func NewServer(handler http.Handler, opt *Options) *Server {
202	s := &Server{
203		handler: handler,
204	}
205
206	// Make a copy of the options
207	if opt != nil {
208		s.Opt = *opt
209	} else {
210		s.Opt = DefaultOpt
211	}
212
213	// Use htpasswd if required on everything
214	if s.Opt.HtPasswd != "" || s.Opt.BasicUser != "" || s.Opt.Auth != nil {
215		var authenticator *auth.BasicAuth
216		if s.Opt.Auth == nil {
217			var secretProvider auth.SecretProvider
218			if s.Opt.HtPasswd != "" {
219				fs.Infof(nil, "Using %q as htpasswd storage", s.Opt.HtPasswd)
220				secretProvider = auth.HtpasswdFileProvider(s.Opt.HtPasswd)
221			} else {
222				fs.Infof(nil, "Using --user %s --pass XXXX as authenticated user", s.Opt.BasicUser)
223				s.basicPassHashed = string(auth.MD5Crypt([]byte(s.Opt.BasicPass), []byte("dlPL2MqE"), []byte("$1$")))
224				secretProvider = s.singleUserProvider
225			}
226			authenticator = auth.NewBasicAuthenticator(s.Opt.Realm, secretProvider)
227		}
228		oldHandler := handler
229		handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
230			// No auth wanted for OPTIONS method
231			if r.Method == "OPTIONS" {
232				oldHandler.ServeHTTP(w, r)
233				return
234			}
235			unauthorized := func() {
236				w.Header().Set("Content-Type", "text/plain")
237				w.Header().Set("WWW-Authenticate", `Basic realm="`+s.Opt.Realm+`"`)
238				http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
239			}
240			user, pass, authValid := parseAuthorization(r)
241			if !authValid {
242				unauthorized()
243				return
244			}
245			if s.Opt.Auth == nil {
246				if username := authenticator.CheckAuth(r); username == "" {
247					fs.Infof(r.URL.Path, "%s: Unauthorized request from %s", r.RemoteAddr, user)
248					unauthorized()
249					return
250				}
251			} else {
252				// Custom Auth
253				value, err := s.Opt.Auth(user, pass)
254				if err != nil {
255					fs.Infof(r.URL.Path, "%s: Auth failed from %s: %v", r.RemoteAddr, user, err)
256					unauthorized()
257					return
258				}
259				if value != nil {
260					r = r.WithContext(context.WithValue(r.Context(), ContextAuthKey, value))
261				}
262			}
263			r = r.WithContext(context.WithValue(r.Context(), ContextUserKey, user))
264			oldHandler.ServeHTTP(w, r)
265		})
266		s.usingAuth = true
267	}
268
269	s.useSSL = s.Opt.SslKey != ""
270	if (s.Opt.SslCert != "") != s.useSSL {
271		log.Fatalf("Need both -cert and -key to use SSL")
272	}
273
274	// If a Base URL is set then serve from there
275	s.Opt.BaseURL = strings.Trim(s.Opt.BaseURL, "/")
276	if s.Opt.BaseURL != "" {
277		s.Opt.BaseURL = "/" + s.Opt.BaseURL
278	}
279
280	// FIXME make a transport?
281	s.httpServer = &http.Server{
282		Addr:              s.Opt.ListenAddr,
283		Handler:           handler,
284		ReadTimeout:       s.Opt.ServerReadTimeout,
285		WriteTimeout:      s.Opt.ServerWriteTimeout,
286		MaxHeaderBytes:    s.Opt.MaxHeaderBytes,
287		ReadHeaderTimeout: 10 * time.Second, // time to send the headers
288		IdleTimeout:       60 * time.Second, // time to keep idle connections open
289		TLSConfig: &tls.Config{
290			MinVersion: tls.VersionTLS10, // disable SSL v3.0 and earlier
291		},
292	}
293
294	if s.Opt.ClientCA != "" {
295		if !s.useSSL {
296			log.Fatalf("Can't use --client-ca without --cert and --key")
297		}
298		certpool := x509.NewCertPool()
299		pem, err := ioutil.ReadFile(s.Opt.ClientCA)
300		if err != nil {
301			log.Fatalf("Failed to read client certificate authority: %v", err)
302		}
303		if !certpool.AppendCertsFromPEM(pem) {
304			log.Fatalf("Can't parse client certificate authority")
305		}
306		s.httpServer.TLSConfig.ClientCAs = certpool
307		s.httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
308	}
309
310	htmlTemplate, templateErr := data.GetTemplate(s.Opt.Template)
311	if templateErr != nil {
312		log.Fatalf(templateErr.Error())
313	}
314	s.HTMLTemplate = htmlTemplate
315
316	return s
317}
318
319// Serve runs the server - returns an error only if
320// the listener was not started; does not block, so
321// use s.Wait() to block on the listener indefinitely.
322func (s *Server) Serve() error {
323	ln, err := net.Listen("tcp", s.httpServer.Addr)
324	if err != nil {
325		return errors.Wrapf(err, "start server failed")
326	}
327	s.listener = ln
328	s.waitChan = make(chan struct{})
329	go func() {
330		var err error
331		if s.useSSL {
332			// hacky hack to get this to work with old Go versions, which
333			// don't have ServeTLS on http.Server; see PR #2194.
334			type tlsServer interface {
335				ServeTLS(ln net.Listener, cert, key string) error
336			}
337			srvIface := interface{}(s.httpServer)
338			if tlsSrv, ok := srvIface.(tlsServer); ok {
339				// yay -- we get easy TLS support with HTTP/2
340				err = tlsSrv.ServeTLS(s.listener, s.Opt.SslCert, s.Opt.SslKey)
341			} else {
342				// oh well -- we can still do TLS but might not have HTTP/2
343				tlsConfig := new(tls.Config)
344				tlsConfig.Certificates = make([]tls.Certificate, 1)
345				tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(s.Opt.SslCert, s.Opt.SslKey)
346				if err != nil {
347					log.Printf("Error loading key pair: %v", err)
348				}
349				tlsLn := tls.NewListener(s.listener, tlsConfig)
350				err = s.httpServer.Serve(tlsLn)
351			}
352		} else {
353			err = s.httpServer.Serve(s.listener)
354		}
355		if err != nil {
356			log.Printf("Error on serving HTTP server: %v", err)
357		}
358	}()
359	return nil
360}
361
362// Wait blocks while the listener is open.
363func (s *Server) Wait() {
364	<-s.waitChan
365}
366
367// Close shuts the running server down
368func (s *Server) Close() {
369	err := s.httpServer.Close()
370	if err != nil {
371		log.Printf("Error on closing HTTP server: %v", err)
372		return
373	}
374	close(s.waitChan)
375}
376
377// URL returns the serving address of this server
378func (s *Server) URL() string {
379	proto := "http"
380	if s.useSSL {
381		proto = "https"
382	}
383	addr := s.Opt.ListenAddr
384	// prefer actual listener address if using ":port" or "addr:0"
385	useActualAddress := addr == "" || addr[0] == ':' || addr[len(addr)-1] == ':' || strings.HasSuffix(addr, ":0")
386	if s.listener != nil && useActualAddress {
387		// use actual listener address; required if using 0-port
388		// (i.e. port assigned by operating system)
389		addr = s.listener.Addr().String()
390	}
391	return fmt.Sprintf("%s://%s%s/", proto, addr, s.Opt.BaseURL)
392}
393
394// UsingAuth returns true if authentication is required
395func (s *Server) UsingAuth() bool {
396	return s.usingAuth
397}
398
399// Path returns the current path with the Prefix stripped
400//
401// If it returns false, then the path was invalid and the handler
402// should exit as the error response has already been sent
403func (s *Server) Path(w http.ResponseWriter, r *http.Request) (Path string, ok bool) {
404	Path = r.URL.Path
405	if s.Opt.BaseURL == "" {
406		return Path, true
407	}
408	if !strings.HasPrefix(Path, s.Opt.BaseURL+"/") {
409		// Send a redirect if the BaseURL was requested without a /
410		if Path == s.Opt.BaseURL {
411			http.Redirect(w, r, s.Opt.BaseURL+"/", http.StatusPermanentRedirect)
412			return Path, false
413		}
414		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
415		return Path, false
416	}
417	Path = Path[len(s.Opt.BaseURL):]
418	return Path, true
419}
420