From 5ded580444e9258cb35a9c94192d3c1d63e7b74f Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Thu, 16 Feb 2023 11:14:07 -0500 Subject: cmd: Adjust documentation for commands (#5377) --- modules/caddyhttp/reverseproxy/command.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go index 44f4c22..04fb9b4 100644 --- a/modules/caddyhttp/reverseproxy/command.go +++ b/modules/caddyhttp/reverseproxy/command.go @@ -35,7 +35,7 @@ func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "reverse-proxy", Func: cmdReverseProxy, - Usage: "[--from ] [--to ] [--change-host-header]", + Usage: "[--from ] [--to ] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects]", Short: "A quick and production-ready reverse proxy", Long: ` A simple but production-ready reverse proxy. Useful for quick deployments, @@ -52,16 +52,23 @@ If the --from address has a host or IP, Caddy will attempt to serve the proxy over HTTPS with a certificate (unless overridden by the HTTP scheme or port). -If --change-host-header is set, the Host header on the request will be modified -from its original incoming value to the address of the upstream. (Otherwise, by -default, all incoming headers are passed through unmodified.) +If serving HTTPS: + --disable-redirects can be used to avoid binding to the HTTP port. + --internal-certs can be used to force issuance certs using the internal + CA instead of attempting to issue a public certificate. + +For proxying: + --change-host-header sets the Host header on the request to the address + of the upstream, instead of defaulting to the incoming Host header. + --insecure disables TLS verification with the upstream. WARNING: THIS + DISABLES SECURITY BY NOT VERIFYING THE UPSTREAM'S CERTIFICATE. `, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError) fs.String("from", "localhost", "Address on which to receive traffic") fs.Var(&reverseProxyCmdTo, "to", "Upstream address(es) to which traffic should be sent") fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream") - fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING SSL CERTIFICATES!)") + fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)") fs.Bool("internal-certs", false, "Use internal CA for issuing certs") fs.Bool("debug", false, "Enable verbose debug logs") fs.Bool("disable-redirects", false, "Disable HTTP->HTTPS redirects") -- cgit v1.2.3 From be53e432fcac0a9b9accbc36885304639e8ca70b Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 22 Feb 2023 13:41:01 -0500 Subject: caddytls: Relax the warning for on-demand (#5384) --- modules/caddytls/tls.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'modules') diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 8051653..92004b8 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -22,6 +22,7 @@ import ( "log" "net/http" "runtime/debug" + "strings" "sync" "time" @@ -259,7 +260,17 @@ func (t *TLS) Start() error { if t.Automation.OnDemand == nil || (t.Automation.OnDemand.Ask == "" && t.Automation.OnDemand.RateLimit == nil) { for _, ap := range t.Automation.Policies { - if ap.OnDemand { + isWildcardOrDefault := false + if len(ap.Subjects) == 0 { + isWildcardOrDefault = true + } + for _, sub := range ap.Subjects { + if strings.HasPrefix(sub, "*") { + isWildcardOrDefault = true + break + } + } + if ap.OnDemand && isWildcardOrDefault { t.logger.Warn("YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place", zap.String("docs", "https://caddyserver.com/docs/automatic-https#on-demand-tls")) break -- cgit v1.2.3 From e3909cc385f68a636cc8675dc12f6cd5185d722d Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Fri, 24 Feb 2023 22:54:04 +0300 Subject: reverseproxy: refactor HTTP transport layer (#5369) Co-authored-by: Francis Lavoie Co-authored-by: Weidi Deng --- modules/caddyhttp/reverseproxy/httptransport.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index ec5d2f2..71d06d5 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -172,12 +172,19 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e } } - // Set up the dialer to pull the correct information from the context dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { - // the proper dialing information should be embedded into the request's context + // For unix socket upstreams, we need to recover the dial info from + // the request's context, because the Host on the request's URL + // will have been modified by directing the request, overwriting + // the unix socket filename. + // Also, we need to avoid overwriting the address at this point + // when not necessary, because http.ProxyFromEnvironment may have + // modified the address according to the user's env proxy config. if dialInfo, ok := GetDialInfo(ctx); ok { - network = dialInfo.Network - address = dialInfo.Address + if strings.HasPrefix(dialInfo.Network, "unix") { + network = dialInfo.Network + address = dialInfo.Address + } } conn, err := dialer.DialContext(ctx, network, address) @@ -188,8 +195,8 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e return nil, DialError{err} } - // if read/write timeouts are configured and this is a TCP connection, enforce the timeouts - // by wrapping the connection with our own type + // if read/write timeouts are configured and this is a TCP connection, + // enforce the timeouts by wrapping the connection with our own type if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) { conn = &tcpRWTimeoutConn{ TCPConn: tcpConn, @@ -203,6 +210,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e } rt := &http.Transport{ + Proxy: http.ProxyFromEnvironment, DialContext: dialContext, MaxConnsPerHost: h.MaxConnsPerHost, ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout), -- cgit v1.2.3 From 9e6919550be5689628d0020ec14e90ea6f527716 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Fri, 24 Feb 2023 18:09:12 -0500 Subject: cmd: Expand cobra support, add short flags (#5379) * cmd: Expand cobra support * Convert commands to cobra, add short flags * Fix version command typo Co-authored-by: Emily Lange * Apply suggestions from code review Co-authored-by: Matt Holt --------- Co-authored-by: Emily Lange Co-authored-by: Matt Holt --- modules/caddyhttp/caddyauth/command.go | 16 +++++------ modules/caddyhttp/fileserver/command.go | 24 ++++++++-------- modules/caddyhttp/reverseproxy/command.go | 46 +++++++++++++++++-------------- modules/caddyhttp/staticresp.go | 31 ++++++++++----------- modules/caddypki/command.go | 36 +++++++++++------------- 5 files changed, 74 insertions(+), 79 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/caddyauth/command.go b/modules/caddyhttp/caddyauth/command.go index 609de4e..915bcd3 100644 --- a/modules/caddyhttp/caddyauth/command.go +++ b/modules/caddyhttp/caddyauth/command.go @@ -18,20 +18,19 @@ import ( "bufio" "bytes" "encoding/base64" - "flag" "fmt" "os" "os/signal" "github.com/caddyserver/caddy/v2" caddycmd "github.com/caddyserver/caddy/v2/cmd" + "github.com/spf13/cobra" "golang.org/x/term" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "hash-password", - Func: cmdHashPassword, Usage: "[--algorithm ] [--salt ] [--plaintext ]", Short: "Hashes a password and writes base64", Long: ` @@ -50,13 +49,12 @@ be provided (scrypt). Note that scrypt is deprecated. Please use 'bcrypt' instead. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("hash-password", flag.ExitOnError) - fs.String("algorithm", "bcrypt", "Name of the hash algorithm") - fs.String("plaintext", "", "The plaintext password") - fs.String("salt", "", "The password salt") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("plaintext", "p", "", "The plaintext password") + cmd.Flags().StringP("salt", "s", "", "The password salt") + cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword) + }, }) } diff --git a/modules/caddyhttp/fileserver/command.go b/modules/caddyhttp/fileserver/command.go index bc7f981..697ec34 100644 --- a/modules/caddyhttp/fileserver/command.go +++ b/modules/caddyhttp/fileserver/command.go @@ -16,7 +16,6 @@ package fileserver import ( "encoding/json" - "flag" "log" "strconv" "time" @@ -27,13 +26,13 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" "github.com/caddyserver/certmagic" + "github.com/spf13/cobra" "go.uber.org/zap" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "file-server", - Func: cmdFileServer, Usage: "[--domain ] [--root ] [--listen ] [--browse] [--access-log]", Short: "Spins up a production-ready file server", Long: ` @@ -49,17 +48,16 @@ using this option. If --browse is enabled, requests for folders without an index file will respond with a file listing.`, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("file-server", flag.ExitOnError) - fs.String("domain", "", "Domain name at which to serve the files") - fs.String("root", "", "The path to the root of the site") - fs.String("listen", "", "The address to which to bind the listener") - fs.Bool("browse", false, "Enable directory browsing") - fs.Bool("templates", false, "Enable template rendering") - fs.Bool("access-log", false, "Enable the access log") - fs.Bool("debug", false, "Enable verbose debug logs") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files") + cmd.Flags().StringP("root", "r", "", "The path to the root of the site") + cmd.Flags().StringP("listen", "", "", "The address to which to bind the listener") + cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing") + cmd.Flags().BoolP("templates", "t", false, "Enable template rendering") + cmd.Flags().BoolP("access-log", "", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer) + }, }) } diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go index 04fb9b4..8c171ec 100644 --- a/modules/caddyhttp/reverseproxy/command.go +++ b/modules/caddyhttp/reverseproxy/command.go @@ -16,7 +16,6 @@ package reverseproxy import ( "encoding/json" - "flag" "fmt" "net/http" "strconv" @@ -28,14 +27,14 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" "github.com/caddyserver/caddy/v2/modules/caddytls" + "github.com/spf13/cobra" "go.uber.org/zap" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "reverse-proxy", - Func: cmdReverseProxy, - Usage: "[--from ] [--to ] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects]", + Usage: "[--from ] [--to ] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects] [--access-log]", Short: "A quick and production-ready reverse proxy", Long: ` A simple but production-ready reverse proxy. Useful for quick deployments, @@ -63,17 +62,17 @@ For proxying: --insecure disables TLS verification with the upstream. WARNING: THIS DISABLES SECURITY BY NOT VERIFYING THE UPSTREAM'S CERTIFICATE. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError) - fs.String("from", "localhost", "Address on which to receive traffic") - fs.Var(&reverseProxyCmdTo, "to", "Upstream address(es) to which traffic should be sent") - fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream") - fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)") - fs.Bool("internal-certs", false, "Use internal CA for issuing certs") - fs.Bool("debug", false, "Enable verbose debug logs") - fs.Bool("disable-redirects", false, "Disable HTTP->HTTPS redirects") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("from", "f", "localhost", "Address on which to receive traffic") + cmd.Flags().StringSliceP("to", "t", []string{}, "Upstream address(es) to which traffic should be sent") + cmd.Flags().BoolP("change-host-header", "c", false, "Set upstream Host header to address of upstream") + cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)") + cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects") + cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs") + cmd.Flags().BoolP("access-log", "", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy) + }, }) } @@ -83,14 +82,19 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { from := fs.String("from") changeHost := fs.Bool("change-host-header") insecure := fs.Bool("insecure") + disableRedir := fs.Bool("disable-redirects") internalCerts := fs.Bool("internal-certs") + accessLog := fs.Bool("access-log") debug := fs.Bool("debug") - disableRedir := fs.Bool("disable-redirects") httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort) - if len(reverseProxyCmdTo) == 0 { + to, err := fs.GetStringSlice("to") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid to flag: %v", err) + } + if len(to) == 0 { return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required") } @@ -119,9 +123,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { // set up the upstream address; assume missing information from given parts // mixing schemes isn't supported, so use first defined (if available) - toAddresses := make([]string, len(reverseProxyCmdTo)) + toAddresses := make([]string, len(to)) var toScheme string - for i, toLoc := range reverseProxyCmdTo { + for i, toLoc := range to { addr, scheme, err := parseUpstreamDialAddress(toLoc) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err) @@ -180,6 +184,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { Routes: caddyhttp.RouteList{route}, Listen: []string{":" + fromAddr.Port}, } + if accessLog { + server.Logs = &caddyhttp.ServerLogConfig{} + } if fromAddr.Scheme == "http" { server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{Disabled: true} @@ -238,6 +245,3 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { select {} } - -// reverseProxyCmdTo holds the parsed values from repeated use of the --to flag. -var reverseProxyCmdTo caddycmd.StringSlice diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index add5b12..67614c2 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -17,7 +17,6 @@ package caddyhttp import ( "bytes" "encoding/json" - "flag" "fmt" "io" "net/http" @@ -32,6 +31,7 @@ import ( "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" caddycmd "github.com/caddyserver/caddy/v2/cmd" + "github.com/spf13/cobra" "go.uber.org/zap" ) @@ -39,7 +39,6 @@ func init() { caddy.RegisterModule(StaticResponse{}) caddycmd.RegisterCommand(caddycmd.Command{ Name: "respond", - Func: cmdRespond, Usage: `[--status ] [--body ] [--listen ] [--access-log] [--debug] [--header "Field: value"] `, Short: "Simple, hard-coded HTTP responses for development and testing", Long: ` @@ -71,16 +70,15 @@ Access/request logging and more verbose debug logging can also be enabled. Response headers may be added using the --header flag for each header field. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("respond", flag.ExitOnError) - fs.String("listen", ":0", "The address to which to bind the listener") - fs.Int("status", http.StatusOK, "The response status code") - fs.String("body", "", "The body of the HTTP response") - fs.Bool("access-log", false, "Enable the access log") - fs.Bool("debug", false, "Enable more verbose debug-level logging") - fs.Var(&respondCmdHeaders, "header", "Set a header on the response (format: \"Field: value\"") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("listen", "l", ":0", "The address to which to bind the listener") + cmd.Flags().IntP("status", "s", http.StatusOK, "The response status code") + cmd.Flags().StringP("body", "b", "", "The body of the HTTP response") + cmd.Flags().BoolP("access-log", "", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable more verbose debug-level logging") + cmd.Flags().StringSliceP("header", "H", []string{}, "Set a header on the response (format: \"Field: value\")") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdRespond) + }, }) } @@ -318,8 +316,12 @@ func cmdRespond(fl caddycmd.Flags) (int, error) { } // build headers map + headers, err := fl.GetStringSlice("header") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) + } hdr := make(http.Header) - for i, h := range respondCmdHeaders { + for i, h := range headers { key, val, found := strings.Cut(h, ":") key, val = strings.TrimSpace(key), strings.TrimSpace(val) if !found || key == "" || val == "" { @@ -432,9 +434,6 @@ func cmdRespond(fl caddycmd.Flags) (int, error) { select {} } -// respondCmdHeaders holds the parsed values from repeated use of the --header flag. -var respondCmdHeaders caddycmd.StringSlice - // Interface guards var ( _ MiddlewareHandler = (*StaticResponse)(nil) diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go index cb86c93..e78f35c 100644 --- a/modules/caddypki/command.go +++ b/modules/caddypki/command.go @@ -18,7 +18,6 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" - "flag" "fmt" "net/http" "os" @@ -27,12 +26,12 @@ import ( "github.com/caddyserver/caddy/v2" caddycmd "github.com/caddyserver/caddy/v2/cmd" "github.com/smallstep/truststore" + "github.com/spf13/cobra" ) func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "trust", - Func: cmdTrust, Usage: "[--ca ] [--address ] [--config [--adapter ]]", Short: "Installs a CA certificate into local trust stores", Long: ` @@ -53,19 +52,17 @@ This command will attempt to connect to Caddy's admin API running at '` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may explicitly specify the --address, or use the --config flag to load the admin address from your config, if not using the default.`, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("trust", flag.ExitOnError) - fs.String("ca", "", "The ID of the CA to trust (defaults to 'local')") - fs.String("address", "", "Address of the administration API listener (if --config is not used)") - fs.String("config", "", "Configuration file (if --address is not used)") - fs.String("adapter", "", "Name of config adapter to apply (if --config is used)") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')") + cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)") + cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust) + }, }) caddycmd.RegisterCommand(caddycmd.Command{ Name: "untrust", - Func: cmdUntrust, Usage: "[--cert ] | [[--ca ] [--address ] [--config [--adapter ]]]", Short: "Untrusts a locally-trusted CA certificate", Long: ` @@ -89,15 +86,14 @@ will attempt to connect to the Caddy's admin API running at '` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may explicitly specify the --address, or use the --config flag to load the admin address from your config, if not using the default.`, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("untrust", flag.ExitOnError) - fs.String("cert", "", "The path to the CA certificate to untrust") - fs.String("ca", "", "The ID of the CA to untrust (defaults to 'local')") - fs.String("address", "", "Address of the administration API listener (if --config is not used)") - fs.String("config", "", "Configuration file (if --address is not used)") - fs.String("adapter", "", "Name of config adapter to apply (if --config is used)") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust") + cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')") + cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)") + cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust) + }, }) } -- cgit v1.2.3 From 960150bb034dc9a549ee7289b1a4eb4abafeb30a Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Sat, 25 Feb 2023 19:34:27 -0500 Subject: caddyfile: Implement heredoc support (#5385) --- modules/caddyhttp/celmatcher.go | 6 ++-- .../caddyhttp/reverseproxy/fastcgi/caddyfile.go | 42 ++++++---------------- .../reverseproxy/forwardauth/caddyfile.go | 13 +++---- 3 files changed, 19 insertions(+), 42 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index 60ca00b..c4635e2 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -191,15 +191,17 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val { celReq, ok := lhs.(celHTTPRequest) if !ok { return types.NewErr( - "invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)", + "invalid request of type '%v' to %s(request, placeholderVarName)", lhs.Type(), + placeholderFuncName, ) } phStr, ok := rhs.(types.String) if !ok { return types.NewErr( - "invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)", + "invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)", rhs.Type(), + placeholderFuncName, ) } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index 799050e..4687e68 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -217,25 +217,18 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, dispenser.ArgErr() } fcgiTransport.Root = dispenser.Val() - dispenser.Delete() - dispenser.Delete() + dispenser.DeleteN(2) case "split": extensions = dispenser.RemainingArgs() - dispenser.Delete() - for range extensions { - dispenser.Delete() - } + dispenser.DeleteN(len(extensions) + 1) if len(extensions) == 0 { return nil, dispenser.ArgErr() } case "env": args := dispenser.RemainingArgs() - dispenser.Delete() - for range args { - dispenser.Delete() - } + dispenser.DeleteN(len(args) + 1) if len(args) != 2 { return nil, dispenser.ArgErr() } @@ -246,10 +239,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error case "index": args := dispenser.RemainingArgs() - dispenser.Delete() - for range args { - dispenser.Delete() - } + dispenser.DeleteN(len(args) + 1) if len(args) != 1 { return nil, dispenser.ArgErr() } @@ -257,10 +247,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error case "try_files": args := dispenser.RemainingArgs() - dispenser.Delete() - for range args { - dispenser.Delete() - } + dispenser.DeleteN(len(args) + 1) if len(args) < 1 { return nil, dispenser.ArgErr() } @@ -268,10 +255,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error case "resolve_root_symlink": args := dispenser.RemainingArgs() - dispenser.Delete() - for range args { - dispenser.Delete() - } + dispenser.DeleteN(len(args) + 1) fcgiTransport.ResolveRootSymlink = true case "dial_timeout": @@ -283,8 +267,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) } fcgiTransport.DialTimeout = caddy.Duration(dur) - dispenser.Delete() - dispenser.Delete() + dispenser.DeleteN(2) case "read_timeout": if !dispenser.NextArg() { @@ -295,8 +278,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) } fcgiTransport.ReadTimeout = caddy.Duration(dur) - dispenser.Delete() - dispenser.Delete() + dispenser.DeleteN(2) case "write_timeout": if !dispenser.NextArg() { @@ -307,15 +289,11 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) } fcgiTransport.WriteTimeout = caddy.Duration(dur) - dispenser.Delete() - dispenser.Delete() + dispenser.DeleteN(2) case "capture_stderr": args := dispenser.RemainingArgs() - dispenser.Delete() - for range args { - dispenser.Delete() - } + dispenser.DeleteN(len(args) + 1) fcgiTransport.CaptureStderr = true } } diff --git a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go index cecc000..dbd2a29 100644 --- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go @@ -129,8 +129,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) return nil, dispenser.ArgErr() } rpHandler.Rewrite.URI = dispenser.Val() - dispenser.Delete() - dispenser.Delete() + dispenser.DeleteN(2) case "copy_headers": args := dispenser.RemainingArgs() @@ -140,13 +139,11 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) args = append(args, dispenser.Val()) } - dispenser.Delete() // directive name + // directive name + args + dispenser.DeleteN(len(args) + 1) if hadBlock { - dispenser.Delete() // opening brace - dispenser.Delete() // closing brace - } - for range args { - dispenser.Delete() + // opening & closing brace + dispenser.DeleteN(2) } for _, headerField := range args { -- cgit v1.2.3 From 941eae5f615aeaf038f62002e673a7bf4886f1c7 Mon Sep 17 00:00:00 2001 From: Emily Lange Date: Mon, 27 Feb 2023 18:23:09 +0100 Subject: reverseproxy: allow specifying ip version for dynamic `a` upstream (#5401) Co-authored-by: Francis Lavoie --- modules/caddyhttp/reverseproxy/caddyfile.go | 25 +++++++++++++++++++- modules/caddyhttp/reverseproxy/upstreams.go | 36 +++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 1211188..cf84ba7 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -1324,6 +1324,7 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // resolvers // dial_timeout // dial_fallback_delay +// versions ipv4|ipv6 // } func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { for d.Next() { @@ -1397,8 +1398,30 @@ func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } u.FallbackDelay = caddy.Duration(dur) + case "versions": + args := d.RemainingArgs() + if len(args) == 0 { + return d.Errf("must specify at least one version") + } + + if u.Versions == nil { + u.Versions = &ipVersions{} + } + + trueBool := true + for _, arg := range args { + switch arg { + case "ipv4": + u.Versions.IPv4 = &trueBool + case "ipv6": + u.Versions.IPv6 = &trueBool + default: + return d.Errf("unsupported version: '%s'", arg) + } + } + default: - return d.Errf("unrecognized srv option '%s'", d.Val()) + return d.Errf("unrecognized a option '%s'", d.Val()) } } } diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go index 7a90016..30bd7b5 100644 --- a/modules/caddyhttp/reverseproxy/upstreams.go +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -213,6 +213,11 @@ func (sl srvLookup) isFresh() bool { return time.Since(sl.freshness) < time.Duration(sl.srvUpstreams.Refresh) } +type ipVersions struct { + IPv4 *bool `json:"ipv4,omitempty"` + IPv6 *bool `json:"ipv6,omitempty"` +} + // AUpstreams provides upstreams from A/AAAA lookups. // Results are cached and refreshed at the configured // refresh interval. @@ -240,6 +245,11 @@ type AUpstreams struct { // A negative value disables this. FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"` + // The IP versions to resolve for. By default, both + // "ipv4" and "ipv6" will be enabled, which + // correspond to A and AAAA records respectively. + Versions *ipVersions `json:"versions,omitempty"` + resolver *net.Resolver } @@ -286,7 +296,29 @@ func (au *AUpstreams) Provision(_ caddy.Context) error { func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - auStr := repl.ReplaceAll(au.String(), "") + + resolveIpv4 := au.Versions.IPv4 == nil || *au.Versions.IPv4 + resolveIpv6 := au.Versions.IPv6 == nil || *au.Versions.IPv6 + + // Map ipVersion early, so we can use it as part of the cache-key. + // This should be fairly inexpensive and comes and the upside of + // allowing the same dynamic upstream (name + port combination) + // to be used multiple times with different ip versions. + // + // It also forced a cache-miss if a previously cached dynamic + // upstream changes its ip version, e.g. after a config reload, + // while keeping the cache-invalidation as simple as it currently is. + var ipVersion string + switch { + case resolveIpv4 && !resolveIpv6: + ipVersion = "ip4" + case !resolveIpv4 && resolveIpv6: + ipVersion = "ip6" + default: + ipVersion = "ip" + } + + auStr := repl.ReplaceAll(au.String()+ipVersion, "") // first, use a cheap read-lock to return a cached result quickly aAaaaMu.RLock() @@ -311,7 +343,7 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { name := repl.ReplaceAll(au.Name, "") port := repl.ReplaceAll(au.Port, "") - ips, err := au.resolver.LookupIPAddr(r.Context(), name) + ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name) if err != nil { return nil, err } -- cgit v1.2.3 From f6bab8ba85b231ea0930282e684c0040001059e6 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Mon, 27 Feb 2023 13:58:27 -0500 Subject: context: Rename func to `AppIfConfigured` (#5397) --- modules/caddypki/adminapi.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'modules') diff --git a/modules/caddypki/adminapi.go b/modules/caddypki/adminapi.go index f03c6b6..cab7c70 100644 --- a/modules/caddypki/adminapi.go +++ b/modules/caddypki/adminapi.go @@ -49,20 +49,14 @@ func (a *adminAPI) Provision(ctx caddy.Context) error { a.ctx = ctx a.log = ctx.Logger(a) // TODO: passing in 'a' is a hack until the admin API is officially extensible (see #5032) - // First check if the PKI app was configured, because - // a.ctx.App() has the side effect of instantiating - // and provisioning an app even if it wasn't configured. - pkiAppConfigured := a.ctx.AppIsConfigured("pki") - if !pkiAppConfigured { - return nil - } - - // Load the PKI app, so we can query it for information. - appModule, err := a.ctx.App("pki") + // Avoid initializing PKI if it wasn't configured + pkiApp, err := a.ctx.AppIfConfigured("pki") if err != nil { return err } - a.pkiApp = appModule.(*PKI) + if pkiApp != nil { + a.pkiApp = pkiApp.(*PKI) + } return nil } -- cgit v1.2.3 From 85375861f6a903da9af8aa665552a6546d075c9f Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Thu, 2 Mar 2023 11:01:54 -0500 Subject: caddyhttp: Fix `vars_regexp` matcher with placeholders (#5408) Changed to match the `vars` matcher's logic for handling placeholders --- modules/caddyhttp/vars.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index b4e1d89..d2d7f62 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -255,9 +255,18 @@ func (m MatchVarsRE) Provision(ctx caddy.Context) error { func (m MatchVarsRE) Match(r *http.Request) bool { vars := r.Context().Value(VarsCtxKey).(map[string]any) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - for k, rm := range m { + for key, val := range m { + var varValue any + if strings.HasPrefix(key, "{") && + strings.HasSuffix(key, "}") && + strings.Count(key, "{") == 1 { + varValue, _ = repl.Get(strings.Trim(key, "{}")) + } else { + varValue = vars[key] + } + var varStr string - switch vv := vars[k].(type) { + switch vv := varValue.(type) { case string: varStr = vv case fmt.Stringer: @@ -267,13 +276,9 @@ func (m MatchVarsRE) Match(r *http.Request) bool { default: varStr = fmt.Sprintf("%v", vv) } - valExpanded := repl.ReplaceAll(varStr, "") - if match := rm.Match(valExpanded, repl); match { - return match - } - replacedVal := repl.ReplaceAll(k, "") - if match := rm.Match(replacedVal, repl); match { + valExpanded := repl.ReplaceAll(varStr, "") + if match := val.Match(valExpanded, repl); match { return match } } -- cgit v1.2.3 From 99d47050e97a8ccac2aad3bda46be46d4fec85ed Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Thu, 2 Mar 2023 21:00:18 -0700 Subject: core: Eliminate unnecessary shutdown delay on Unix (#5413) * core: Eliminate unnecessary shutdown delay on Unix Fix #5393, alternate to #5405 * Comments, cleanup, adjust logs * Fix build constraint --- modules/caddyhttp/app.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 36a4011..670185a 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -517,7 +517,7 @@ func (app *App) Stop() error { // honor scheduled/delayed shutdown time if delay { - app.logger.Debug("shutdown scheduled", + app.logger.Info("shutdown scheduled", zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)), zap.Time("time", scheduledTime)) time.Sleep(time.Duration(app.ShutdownDelay)) @@ -528,9 +528,9 @@ func (app *App) Stop() error { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod)) defer cancel() - app.logger.Debug("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod))) + app.logger.Info("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod))) } else { - app.logger.Debug("servers shutting down with eternal grace period") + app.logger.Info("servers shutting down with eternal grace period") } // goroutines aren't guaranteed to be scheduled right away, -- cgit v1.2.3 From 94d41a9d86d6ca2d2d001183a62d762ee045094f Mon Sep 17 00:00:00 2001 From: esell Date: Fri, 3 Mar 2023 14:45:17 -0700 Subject: fileserver: Remove trailing slash on fs filenames (#5417) --- modules/caddyhttp/fileserver/staticfiles.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'modules') diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index c0fde66..0459b3a 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -250,7 +250,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c root := repl.ReplaceAll(fsrv.Root, ".") - filename := caddyhttp.SanitizedPathJoin(root, r.URL.Path) + // remove any trailing `/` as it breaks fs.ValidPath() in the stdlib + filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/") fsrv.logger.Debug("sanitized path join", zap.String("site_root", root), -- cgit v1.2.3 From b3f0cea2c31f6137cd1604ba31afffdbfc8b70ee Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Tue, 7 Mar 2023 00:13:48 +0800 Subject: encode: flush status code when hijacked. (#5419) --- modules/caddyhttp/encode/encode.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'modules') diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index e3a6267..8a6fc10 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -20,9 +20,11 @@ package encode import ( + "bufio" "fmt" "io" "math" + "net" "net/http" "sort" "strconv" @@ -212,6 +214,18 @@ func (rw *responseWriter) Flush() { rw.HTTPInterfaces.Flush() } +// Hijack implements http.Hijacker. It will flush status code if set. We don't track actual hijacked +// status assuming http middlewares will track its status. +func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if !rw.wroteHeader { + if rw.statusCode != 0 { + rw.HTTPInterfaces.WriteHeader(rw.statusCode) + } + rw.wroteHeader = true + } + return rw.HTTPInterfaces.Hijack() +} + // Write writes to the response. If the response qualifies, // it is encoded using the encoder, which is initialized // if not done so already. -- cgit v1.2.3 From b420561737e4589affbfe08c0acf7dcb593381d0 Mon Sep 17 00:00:00 2001 From: Chris Reeves Date: Thu, 9 Mar 2023 16:02:35 +0000 Subject: tracing: Support autoprop from OTEL_PROPAGATORS (#5147) Co-authored-by: Matt Holt --- modules/caddyhttp/tracing/tracer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go index d113a56..cb72743 100644 --- a/modules/caddyhttp/tracing/tracer.go +++ b/modules/caddyhttp/tracing/tracer.go @@ -6,9 +6,9 @@ import ( "net/http" "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" @@ -62,7 +62,7 @@ func newOpenTelemetryWrapper( return ot, fmt.Errorf("creating trace exporter error: %w", err) } - ot.propagators = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + ot.propagators = autoprop.NewTextMapPropagator() tracerProvider := globalTracerProvider.getTracerProvider( sdktrace.WithBatcher(traceExporter), -- cgit v1.2.3 From 6cc3cbbc697d80ae7112c589ce34032f821b4b47 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Fri, 10 Mar 2023 11:19:31 -0700 Subject: fileserver: New file browse template (#5427) * fileserver: New file browse template * Redo extension/icon logic; minor color tweaks * Fine-tune image display --- modules/caddyhttp/fileserver/browse.go | 10 +- modules/caddyhttp/fileserver/browse.html | 826 +++++++++++++++++------ modules/caddyhttp/fileserver/browsetplcontext.go | 13 + 3 files changed, 659 insertions(+), 190 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go index a8f5e8a..e1a0894 100644 --- a/modules/caddyhttp/fileserver/browse.go +++ b/modules/caddyhttp/fileserver/browse.go @@ -152,12 +152,20 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, dir fs.ReadDi // browseApplyQueryParams applies query parameters to the listing. // It mutates the listing and may set cookies. func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) { + layoutParam := r.URL.Query().Get("layout") sortParam := r.URL.Query().Get("sort") orderParam := r.URL.Query().Get("order") limitParam := r.URL.Query().Get("limit") offsetParam := r.URL.Query().Get("offset") - // first figure out what to sort by + switch layoutParam { + case "list", "grid", "": + listing.Layout = layoutParam + default: + listing.Layout = "list" + } + + // figure out what to sort by switch sortParam { case "": sortParam = sortByNameDirFirst diff --git a/modules/caddyhttp/fileserver/browse.html b/modules/caddyhttp/fileserver/browse.html index 01537fc..479ead2 100644 --- a/modules/caddyhttp/fileserver/browse.html +++ b/modules/caddyhttp/fileserver/browse.html @@ -1,3 +1,94 @@ +{{- define "icon"}} + {{- if .IsDir}} + + + + + {{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp"}} + + {{- else if .HasExt ".mp4" ".mov" ".mpeg" ".avi" ".ogg" ".webm"}} + + + + + + + + + + + + {{- else if .HasExt ".mp3" ".flac" ".wav" ".wma"}} + + + + + + + + {{- else if .HasExt ".pdf"}} + + + + + + + + {{- else if .HasExt ".txt"}} + + + + + + + + + {{- else if .HasExt ".zip" ".gz" ".xz" ".tar"}} + + + + + + + + + + + + {{- else if .HasExt ".md" ".mdown" ".markdown"}} + + + + + + + {{- else if .HasExt ".go"}} + + + + + + + + + {{- else}} + {{- if .IsSymlink}} + + + + + + + + {{- else}} + + + + + + {{- end}} + {{- end}} +{{- end}} @@ -5,38 +96,72 @@ +{{- if eq .Layout "grid"}} + +{{- end}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
-

- {{range $i, $crumb := .Breadcrumbs}}{{html $crumb.Text}}{{if ne $i 0}}/{{end}}{{end}} -

+
+ +

+ {{range $i, $crumb := .Breadcrumbs}}{{html $crumb.Text}}{{if ne $i 0}}/{{end}}{{end}} +

+
-
-
-
- {{.NumDirs}} director{{if eq 1 .NumDirs}}y{{else}}ies{{end}} - {{.NumFiles}} file{{if ne 1 .NumFiles}}s{{end}} - {{- if ne 0 .Limit}} - (of which only {{.Limit}} are displayed) - {{- end}} - +
+
+
+
+ + {{.NumDirs}} director{{if eq 1 .NumDirs}}y{{else}}ies{{end}} + + + {{.NumFiles}} file{{if ne 1 .NumFiles}}s{{end}} + + {{- if ne 0 .Limit}} + + (of which only {{.Limit}} are displayed) + + {{- end}} +
+ + + + + + + List + + + + + + + + + + Grid +
-
-
+
+ {{- if eq .Layout "grid"}} + {{- range .Items}} +
+ + {{template "icon" .}} +
{{html .Name}}
+
{{.HumanSize}}
+
+
+ {{- end}} + {{- else}} @@ -387,11 +725,15 @@ footer { - - + + {{- end}} @@ -400,54 +742,145 @@ footer { {{- if .IsDir}} - + {{- else}} - + {{- end}} - + {{- end}}
{{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}} - + + + + + + {{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}} - + + + + + + {{- else}} - + + + + + + {{- end}} {{- if and (eq .Sort "name") (ne .Order "desc")}} - Name + + Name + + + + + {{- else if and (eq .Sort "name") (ne .Order "asc")}} - Name + + Name + + + + + {{- else}} - Name + + Name + {{- end}} + +
+ + + + + + +
{{- if and (eq .Sort "size") (ne .Order "desc")}} - Size + + Size + + + + + {{- else if and (eq .Sort "size") (ne .Order "asc")}} - Size + + Size + + + + + {{- else}} - Size + + Size + {{- end}} {{- if and (eq .Sort "time") (ne .Order "desc")}} - Modified + + Modified + + + + + {{- else if and (eq .Sort "time") (ne .Order "asc")}} - Modified + + Modified + + + + + {{- else}} - Modified + + Modified + {{- end}} - Go up + + + + + Up
- {{- if .IsDir}} - - {{- else}} - - {{- end}} + {{template "icon" .}} {{html .Name}} {{.HumanSize}} +
+
+
+ {{.HumanSize}} +
+
+
+ +
+ {{- end}}
-
+ + +