2018-02-14 20:39:11 +00:00
|
|
|
// Package httplib provides common functionality for http servers
|
|
|
|
package httplib
|
|
|
|
|
|
|
|
import (
|
2019-06-07 17:47:46 +00:00
|
|
|
"context"
|
2018-02-15 15:01:19 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2018-12-13 12:15:05 +00:00
|
|
|
"encoding/base64"
|
2018-02-14 20:39:11 +00:00
|
|
|
"fmt"
|
2018-12-23 00:16:50 +00:00
|
|
|
"html/template"
|
2018-02-15 15:01:19 +00:00
|
|
|
"io/ioutil"
|
2018-02-14 20:39:11 +00:00
|
|
|
"log"
|
2018-04-04 13:56:26 +00:00
|
|
|
"net"
|
2018-02-14 20:39:11 +00:00
|
|
|
"net/http"
|
2018-12-13 12:15:05 +00:00
|
|
|
"strings"
|
2018-02-15 15:01:19 +00:00
|
|
|
"time"
|
2018-02-14 20:39:11 +00:00
|
|
|
|
2018-02-14 21:06:34 +00:00
|
|
|
auth "github.com/abbot/go-http-auth"
|
2018-11-01 17:16:31 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/cmd/serve/httplib/serve/data"
|
|
|
|
"github.com/rclone/rclone/fs"
|
2018-02-14 20:39:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Globals
|
2018-02-15 15:01:19 +00:00
|
|
|
var ()
|
2018-02-14 20:39:11 +00:00
|
|
|
|
|
|
|
// Help contains text describing the http server to add to the command
|
|
|
|
// help.
|
|
|
|
var Help = `
|
|
|
|
### Server options
|
|
|
|
|
|
|
|
Use --addr to specify which IP address and port the server should
|
2020-10-13 21:49:58 +00:00
|
|
|
listen on, e.g. --addr 1.2.3.4:8000 or --addr :8080 to listen to all
|
2018-04-04 13:56:26 +00:00
|
|
|
IPs. By default it only listens on localhost. You can use port
|
|
|
|
:0 to let the OS choose an available port.
|
2018-02-14 21:06:34 +00:00
|
|
|
|
2018-03-05 18:25:51 +00:00
|
|
|
If you set --addr to listen on a public or LAN accessible IP address
|
2018-04-04 13:56:26 +00:00
|
|
|
then using Authentication is advised - see the next section for info.
|
2018-03-05 18:25:51 +00:00
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
--server-read-timeout and --server-write-timeout can be used to
|
|
|
|
control the timeouts on the server. Note that this is the total time
|
|
|
|
for a transfer.
|
|
|
|
|
|
|
|
--max-header-bytes controls the maximum number of bytes the server will
|
|
|
|
accept in the HTTP header.
|
|
|
|
|
2019-08-24 08:10:50 +00:00
|
|
|
--baseurl controls the URL prefix that rclone serves from. By default
|
|
|
|
rclone will serve from the root. If you used --baseurl "/rclone" then
|
2019-08-04 09:56:38 +00:00
|
|
|
rclone would serve from a URL starting with "/rclone/". This is
|
2019-08-24 08:10:50 +00:00
|
|
|
useful if you wish to proxy rclone serve. Rclone automatically
|
|
|
|
inserts leading and trailing "/" on --baseurl, so --baseurl "rclone",
|
|
|
|
--baseurl "/rclone" and --baseurl "/rclone/" are all treated
|
|
|
|
identically.
|
2019-08-04 09:56:38 +00:00
|
|
|
|
2020-04-30 18:24:11 +00:00
|
|
|
--template allows a user to specify a custom markup template for http
|
|
|
|
and webdav serve functions. The server exports the following markup
|
|
|
|
to be used within the template to server pages:
|
|
|
|
|
2020-05-04 17:31:22 +00:00
|
|
|
| Parameter | Description |
|
|
|
|
| :---------- | :---------- |
|
|
|
|
| .Name | The full path of a file/directory. |
|
|
|
|
| .Title | Directory listing of .Name |
|
|
|
|
| .Sort | The current sort used. This is changeable via ?sort= parameter |
|
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-09 00:17:24 +00:00
|
|
|
| | Sort Options: namedirfirst,name,size,time (default namedirfirst) |
|
2020-05-04 17:31:22 +00:00
|
|
|
| .Order | The current ordering used. This is changeable via ?order= parameter |
|
|
|
|
| | Order Options: asc,desc (default asc) |
|
|
|
|
| .Query | Currently unused. |
|
|
|
|
| .Breadcrumb | Allows for creating a relative navigation |
|
|
|
|
|-- .Link | The relative to the root link of the Text. |
|
|
|
|
|-- .Text | The Name of the directory. |
|
|
|
|
| .Entries | Information about a specific file/directory. |
|
|
|
|
|-- .URL | The 'url' of an entry. |
|
|
|
|
|-- .Leaf | Currently same as 'URL' but intended to be 'just' the name. |
|
|
|
|
|-- .IsDir | Boolean for if an entry is a directory or not. |
|
|
|
|
|-- .Size | Size in Bytes of the entry. |
|
|
|
|
|-- .ModTime | The UTC timestamp of an entry. |
|
2020-04-30 18:24:11 +00:00
|
|
|
|
2018-02-14 23:14:14 +00:00
|
|
|
#### Authentication
|
|
|
|
|
|
|
|
By default this will serve files without needing a login.
|
|
|
|
|
|
|
|
You can either use an htpasswd file which can take lots of users, or
|
|
|
|
set a single username and password with the --user and --pass flags.
|
|
|
|
|
2018-02-14 21:06:34 +00:00
|
|
|
Use --htpasswd /path/to/htpasswd to provide an htpasswd file. This is
|
|
|
|
in standard apache format and supports MD5, SHA1 and BCrypt for basic
|
|
|
|
authentication. Bcrypt is recommended.
|
|
|
|
|
|
|
|
To create an htpasswd file:
|
|
|
|
|
|
|
|
touch htpasswd
|
|
|
|
htpasswd -B htpasswd user
|
|
|
|
htpasswd -B htpasswd anotherUser
|
2018-02-14 23:14:14 +00:00
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
The password file can be updated while rclone is running.
|
|
|
|
|
|
|
|
Use --realm to set the authentication realm.
|
|
|
|
|
|
|
|
#### SSL/TLS
|
|
|
|
|
|
|
|
By default this will serve over http. If you want you can serve over
|
2018-02-17 11:06:33 +00:00
|
|
|
https. You will need to supply the --cert and --key flags. If you
|
|
|
|
wish to do client side certificate validation then you will need to
|
|
|
|
supply --client-ca also.
|
|
|
|
|
2020-05-19 11:02:44 +00:00
|
|
|
--cert should be either a PEM encoded certificate or a concatenation
|
2018-02-17 11:06:33 +00:00
|
|
|
of that with the CA certificate. --key should be the PEM encoded
|
|
|
|
private key and --client-ca should be the PEM encoded client
|
|
|
|
certificate authority certificate.
|
2018-02-15 15:01:19 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
// Options contains options for the http Server
|
|
|
|
type Options struct {
|
|
|
|
ListenAddr string // Port to listen on
|
2019-08-24 08:10:50 +00:00
|
|
|
BaseURL string // prefix to strip from URLs
|
2018-02-15 15:01:19 +00:00
|
|
|
ServerReadTimeout time.Duration // Timeout for server reading data
|
|
|
|
ServerWriteTimeout time.Duration // Timeout for server writing data
|
|
|
|
MaxHeaderBytes int // Maximum size of request header
|
|
|
|
SslCert string // SSL PEM key (concatenation of certificate and CA certificate)
|
|
|
|
SslKey string // SSL PEM Private key
|
|
|
|
ClientCA string // Client certificate authority to verify clients with
|
|
|
|
HtPasswd string // htpasswd file - if not provided no authentication is done
|
|
|
|
Realm string // realm for authentication
|
|
|
|
BasicUser string // single username for basic auth if not using Htpasswd
|
|
|
|
BasicPass string // password for BasicUser
|
2019-08-10 15:22:17 +00:00
|
|
|
Auth AuthFn `json:"-"` // custom Auth (not set by command line flags)
|
2020-04-30 18:24:11 +00:00
|
|
|
Template string // User specified template
|
2018-02-15 15:01:19 +00:00
|
|
|
}
|
|
|
|
|
2019-08-01 16:18:44 +00:00
|
|
|
// AuthFn if used will be used to authenticate user, pass. If an error
|
|
|
|
// is returned then the user is not authenticated.
|
|
|
|
//
|
|
|
|
// If a non nil value is returned then it is added to the context under the key
|
|
|
|
type AuthFn func(user, pass string) (value interface{}, err error)
|
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
// DefaultOpt is the default values used for Options
|
|
|
|
var DefaultOpt = Options{
|
|
|
|
ListenAddr: "localhost:8080",
|
|
|
|
Realm: "rclone",
|
|
|
|
ServerReadTimeout: 1 * time.Hour,
|
|
|
|
ServerWriteTimeout: 1 * time.Hour,
|
|
|
|
MaxHeaderBytes: 4096,
|
|
|
|
}
|
2018-02-14 20:39:11 +00:00
|
|
|
|
|
|
|
// Server contains info about the running http server
|
|
|
|
type Server struct {
|
2018-02-15 15:01:19 +00:00
|
|
|
Opt Options
|
|
|
|
handler http.Handler // original handler
|
2018-04-04 13:56:26 +00:00
|
|
|
listener net.Listener
|
|
|
|
waitChan chan struct{} // for waiting on the listener to close
|
2018-02-14 23:14:14 +00:00
|
|
|
httpServer *http.Server
|
|
|
|
basicPassHashed string
|
2018-12-23 00:16:50 +00:00
|
|
|
useSSL bool // if server is configured for SSL/TLS
|
|
|
|
usingAuth bool // set if authentication is configured
|
|
|
|
HTMLTemplate *template.Template // HTML template for web interface
|
2018-02-14 23:14:14 +00:00
|
|
|
}
|
|
|
|
|
2019-06-07 17:47:46 +00:00
|
|
|
type contextUserType struct{}
|
|
|
|
|
2019-08-01 16:18:44 +00:00
|
|
|
// ContextUserKey is a simple context key for storing the username of the request
|
2019-06-07 17:47:46 +00:00
|
|
|
var ContextUserKey = &contextUserType{}
|
|
|
|
|
2019-08-01 16:18:44 +00:00
|
|
|
type contextAuthType struct{}
|
|
|
|
|
|
|
|
// ContextAuthKey is a simple context key for storing info returned by AuthFn
|
|
|
|
var ContextAuthKey = &contextAuthType{}
|
|
|
|
|
2018-02-14 23:14:14 +00:00
|
|
|
// singleUserProvider provides the encrypted password for a single user
|
|
|
|
func (s *Server) singleUserProvider(user, realm string) string {
|
2018-02-15 15:01:19 +00:00
|
|
|
if user == s.Opt.BasicUser {
|
2018-02-14 23:14:14 +00:00
|
|
|
return s.basicPassHashed
|
|
|
|
}
|
|
|
|
return ""
|
2018-02-14 20:39:11 +00:00
|
|
|
}
|
|
|
|
|
2019-08-01 16:18:44 +00:00
|
|
|
// parseAuthorization parses the Authorization header into user, pass
|
|
|
|
// it returns a boolean as to whether the parse was successful
|
|
|
|
func parseAuthorization(r *http.Request) (user, pass string, ok bool) {
|
|
|
|
authHeader := r.Header.Get("Authorization")
|
|
|
|
if authHeader != "" {
|
|
|
|
s := strings.SplitN(authHeader, " ", 2)
|
|
|
|
if len(s) == 2 && s[0] == "Basic" {
|
|
|
|
b, err := base64.StdEncoding.DecodeString(s[1])
|
|
|
|
if err == nil {
|
|
|
|
parts := strings.SplitN(string(b), ":", 2)
|
|
|
|
user = parts[0]
|
|
|
|
if len(parts) > 1 {
|
|
|
|
pass = parts[1]
|
|
|
|
ok = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
// NewServer creates an http server. The opt can be nil in which case
|
|
|
|
// the default options will be used.
|
|
|
|
func NewServer(handler http.Handler, opt *Options) *Server {
|
2018-02-14 23:14:14 +00:00
|
|
|
s := &Server{
|
2018-02-15 15:01:19 +00:00
|
|
|
handler: handler,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a copy of the options
|
|
|
|
if opt != nil {
|
|
|
|
s.Opt = *opt
|
|
|
|
} else {
|
|
|
|
s.Opt = DefaultOpt
|
2018-02-14 23:14:14 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 21:06:34 +00:00
|
|
|
// Use htpasswd if required on everything
|
2019-08-01 16:18:44 +00:00
|
|
|
if s.Opt.HtPasswd != "" || s.Opt.BasicUser != "" || s.Opt.Auth != nil {
|
|
|
|
var authenticator *auth.BasicAuth
|
|
|
|
if s.Opt.Auth == nil {
|
|
|
|
var secretProvider auth.SecretProvider
|
|
|
|
if s.Opt.HtPasswd != "" {
|
|
|
|
fs.Infof(nil, "Using %q as htpasswd storage", s.Opt.HtPasswd)
|
|
|
|
secretProvider = auth.HtpasswdFileProvider(s.Opt.HtPasswd)
|
|
|
|
} else {
|
|
|
|
fs.Infof(nil, "Using --user %s --pass XXXX as authenticated user", s.Opt.BasicUser)
|
|
|
|
s.basicPassHashed = string(auth.MD5Crypt([]byte(s.Opt.BasicPass), []byte("dlPL2MqE"), []byte("$1$")))
|
|
|
|
secretProvider = s.singleUserProvider
|
|
|
|
}
|
|
|
|
authenticator = auth.NewBasicAuthenticator(s.Opt.Realm, secretProvider)
|
2018-02-14 23:14:14 +00:00
|
|
|
}
|
2018-12-13 12:15:05 +00:00
|
|
|
oldHandler := handler
|
|
|
|
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2019-06-10 15:38:58 +00:00
|
|
|
// No auth wanted for OPTIONS method
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
oldHandler.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
2019-08-01 16:18:44 +00:00
|
|
|
unauthorized := func() {
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="`+s.Opt.Realm+`"`)
|
|
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
user, pass, authValid := parseAuthorization(r)
|
|
|
|
if !authValid {
|
|
|
|
unauthorized()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.Opt.Auth == nil {
|
|
|
|
if username := authenticator.CheckAuth(r); username == "" {
|
|
|
|
fs.Infof(r.URL.Path, "%s: Unauthorized request from %s", r.RemoteAddr, user)
|
|
|
|
unauthorized()
|
|
|
|
return
|
2018-12-13 12:15:05 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-08-01 16:18:44 +00:00
|
|
|
// Custom Auth
|
|
|
|
value, err := s.Opt.Auth(user, pass)
|
|
|
|
if err != nil {
|
|
|
|
fs.Infof(r.URL.Path, "%s: Auth failed from %s: %v", r.RemoteAddr, user, err)
|
|
|
|
unauthorized()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if value != nil {
|
|
|
|
r = r.WithContext(context.WithValue(r.Context(), ContextAuthKey, value))
|
|
|
|
}
|
2018-12-13 12:15:05 +00:00
|
|
|
}
|
2019-08-01 16:18:44 +00:00
|
|
|
r = r.WithContext(context.WithValue(r.Context(), ContextUserKey, user))
|
|
|
|
oldHandler.ServeHTTP(w, r)
|
2018-12-13 12:15:05 +00:00
|
|
|
})
|
2018-11-03 16:37:09 +00:00
|
|
|
s.usingAuth = true
|
2018-02-14 21:06:34 +00:00
|
|
|
}
|
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
s.useSSL = s.Opt.SslKey != ""
|
|
|
|
if (s.Opt.SslCert != "") != s.useSSL {
|
|
|
|
log.Fatalf("Need both -cert and -key to use SSL")
|
|
|
|
}
|
|
|
|
|
2019-08-24 08:10:50 +00:00
|
|
|
// If a Base URL is set then serve from there
|
|
|
|
s.Opt.BaseURL = strings.Trim(s.Opt.BaseURL, "/")
|
|
|
|
if s.Opt.BaseURL != "" {
|
|
|
|
s.Opt.BaseURL = "/" + s.Opt.BaseURL
|
2019-08-04 09:56:38 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 20:39:11 +00:00
|
|
|
// FIXME make a transport?
|
|
|
|
s.httpServer = &http.Server{
|
2019-04-15 19:03:33 +00:00
|
|
|
Addr: s.Opt.ListenAddr,
|
|
|
|
Handler: handler,
|
|
|
|
ReadTimeout: s.Opt.ServerReadTimeout,
|
|
|
|
WriteTimeout: s.Opt.ServerWriteTimeout,
|
|
|
|
MaxHeaderBytes: s.Opt.MaxHeaderBytes,
|
|
|
|
ReadHeaderTimeout: 10 * time.Second, // time to send the headers
|
|
|
|
IdleTimeout: 60 * time.Second, // time to keep idle connections open
|
2018-02-15 15:01:19 +00:00
|
|
|
TLSConfig: &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS10, // disable SSL v3.0 and earlier
|
|
|
|
},
|
2018-02-14 20:39:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
if s.Opt.ClientCA != "" {
|
|
|
|
if !s.useSSL {
|
|
|
|
log.Fatalf("Can't use --client-ca without --cert and --key")
|
|
|
|
}
|
|
|
|
certpool := x509.NewCertPool()
|
|
|
|
pem, err := ioutil.ReadFile(s.Opt.ClientCA)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to read client certificate authority: %v", err)
|
|
|
|
}
|
|
|
|
if !certpool.AppendCertsFromPEM(pem) {
|
|
|
|
log.Fatalf("Can't parse client certificate authority")
|
|
|
|
}
|
|
|
|
s.httpServer.TLSConfig.ClientCAs = certpool
|
|
|
|
s.httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
|
|
}
|
|
|
|
|
2020-04-30 18:24:11 +00:00
|
|
|
htmlTemplate, templateErr := data.GetTemplate(s.Opt.Template)
|
2018-12-23 00:16:50 +00:00
|
|
|
if templateErr != nil {
|
|
|
|
log.Fatalf(templateErr.Error())
|
|
|
|
}
|
|
|
|
s.HTMLTemplate = htmlTemplate
|
|
|
|
|
2018-02-15 15:01:19 +00:00
|
|
|
return s
|
2018-02-14 20:39:11 +00:00
|
|
|
}
|
|
|
|
|
2018-04-04 13:56:26 +00:00
|
|
|
// Serve runs the server - returns an error only if
|
|
|
|
// the listener was not started; does not block, so
|
|
|
|
// use s.Wait() to block on the listener indefinitely.
|
|
|
|
func (s *Server) Serve() error {
|
|
|
|
ln, err := net.Listen("tcp", s.httpServer.Addr)
|
|
|
|
if err != nil {
|
2018-11-01 17:16:31 +00:00
|
|
|
return errors.Wrapf(err, "start server failed")
|
2018-02-15 15:01:19 +00:00
|
|
|
}
|
2018-04-04 13:56:26 +00:00
|
|
|
s.listener = ln
|
|
|
|
s.waitChan = make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
if s.useSSL {
|
|
|
|
// hacky hack to get this to work with old Go versions, which
|
|
|
|
// don't have ServeTLS on http.Server; see PR #2194.
|
|
|
|
type tlsServer interface {
|
|
|
|
ServeTLS(ln net.Listener, cert, key string) error
|
|
|
|
}
|
|
|
|
srvIface := interface{}(s.httpServer)
|
|
|
|
if tlsSrv, ok := srvIface.(tlsServer); ok {
|
|
|
|
// yay -- we get easy TLS support with HTTP/2
|
|
|
|
err = tlsSrv.ServeTLS(s.listener, s.Opt.SslCert, s.Opt.SslKey)
|
|
|
|
} else {
|
|
|
|
// oh well -- we can still do TLS but might not have HTTP/2
|
|
|
|
tlsConfig := new(tls.Config)
|
|
|
|
tlsConfig.Certificates = make([]tls.Certificate, 1)
|
|
|
|
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(s.Opt.SslCert, s.Opt.SslKey)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error loading key pair: %v", err)
|
|
|
|
}
|
|
|
|
tlsLn := tls.NewListener(s.listener, tlsConfig)
|
|
|
|
err = s.httpServer.Serve(tlsLn)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = s.httpServer.Serve(s.listener)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error on serving HTTP server: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait blocks while the listener is open.
|
|
|
|
func (s *Server) Wait() {
|
|
|
|
<-s.waitChan
|
2018-02-19 14:02:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close shuts the running server down
|
|
|
|
func (s *Server) Close() {
|
2019-04-15 19:03:33 +00:00
|
|
|
err := s.httpServer.Close()
|
2018-02-19 14:02:19 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error on closing HTTP server: %v", err)
|
2018-04-04 13:56:26 +00:00
|
|
|
return
|
2018-02-19 14:02:19 +00:00
|
|
|
}
|
2018-04-04 13:56:26 +00:00
|
|
|
close(s.waitChan)
|
2018-02-14 20:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// URL returns the serving address of this server
|
|
|
|
func (s *Server) URL() string {
|
2018-02-15 15:01:19 +00:00
|
|
|
proto := "http"
|
|
|
|
if s.useSSL {
|
|
|
|
proto = "https"
|
|
|
|
}
|
2018-04-04 13:56:26 +00:00
|
|
|
addr := s.Opt.ListenAddr
|
2020-12-18 12:55:12 +00:00
|
|
|
// prefer actual listener address if using ":port" or "addr:0"
|
|
|
|
useActualAddress := addr == "" || addr[0] == ':' || addr[len(addr)-1] == ':' || strings.HasSuffix(addr, ":0")
|
|
|
|
if s.listener != nil && useActualAddress {
|
|
|
|
// use actual listener address; required if using 0-port
|
2018-04-04 13:56:26 +00:00
|
|
|
// (i.e. port assigned by operating system)
|
|
|
|
addr = s.listener.Addr().String()
|
|
|
|
}
|
2019-08-24 08:10:50 +00:00
|
|
|
return fmt.Sprintf("%s://%s%s/", proto, addr, s.Opt.BaseURL)
|
2018-02-14 20:39:11 +00:00
|
|
|
}
|
2018-11-03 16:37:09 +00:00
|
|
|
|
|
|
|
// UsingAuth returns true if authentication is required
|
|
|
|
func (s *Server) UsingAuth() bool {
|
|
|
|
return s.usingAuth
|
|
|
|
}
|
2019-08-04 09:56:38 +00:00
|
|
|
|
|
|
|
// Path returns the current path with the Prefix stripped
|
|
|
|
//
|
|
|
|
// If it returns false, then the path was invalid and the handler
|
|
|
|
// should exit as the error response has already been sent
|
|
|
|
func (s *Server) Path(w http.ResponseWriter, r *http.Request) (Path string, ok bool) {
|
|
|
|
Path = r.URL.Path
|
2019-08-24 08:10:50 +00:00
|
|
|
if s.Opt.BaseURL == "" {
|
2019-08-04 09:56:38 +00:00
|
|
|
return Path, true
|
|
|
|
}
|
2019-08-24 08:10:50 +00:00
|
|
|
if !strings.HasPrefix(Path, s.Opt.BaseURL+"/") {
|
2020-11-30 10:19:48 +00:00
|
|
|
// Send a redirect if the BaseURL was requested without a /
|
|
|
|
if Path == s.Opt.BaseURL {
|
|
|
|
http.Redirect(w, r, s.Opt.BaseURL+"/", http.StatusPermanentRedirect)
|
|
|
|
return Path, false
|
|
|
|
}
|
2019-08-04 09:56:38 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
|
|
return Path, false
|
|
|
|
}
|
2019-08-24 08:10:50 +00:00
|
|
|
Path = Path[len(s.Opt.BaseURL):]
|
2019-08-04 09:56:38 +00:00
|
|
|
return Path, true
|
|
|
|
}
|