Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 13:15 UTC
caddyhttp: Address some Go 1.20 features (#6252)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Matt Holt committed
Apr 23, 2024 at 20:05 UTC
6d97d8d87beb788d19a4084d07ec9157e5705b13
8 files changed
+125
-16
cmd/x509rootsfallback.go
new
+33
@@ -0,0 +1,33 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddycmd
16
+
17
+import (
18
+ // For running in minimal environments, this can ease
19
+ // headaches related to establishing TLS connections.
20
+ // "Package fallback embeds a set of fallback X.509 trusted
21
+ // roots in the application by automatically invoking
22
+ // x509.SetFallbackRoots. This allows the application to
23
+ // work correctly even if the operating system does not
24
+ // provide a verifier or system roots pool. ... It's
25
+ // recommended that only binaries, and not libraries,
26
+ // import this package. This package must be kept up to
27
+ // date for security and compatibility reasons."
28
+ //
29
+ // This is in its own file only because of conflicts
30
+ // between gci and goimports when in main.go.
31
+ // See https://github.com/daixiang0/gci/issues/76
32
+ _ "golang.org/x/crypto/x509roots/fallback"
33
+)
go.mod
+1
@@ -36,6 +36,7 @@ require (
36
go.uber.org/zap v1.27.0
37
go.uber.org/zap/exp v0.2.0
38
golang.org/x/crypto v0.22.0
39
+ golang.org/x/crypto/x509roots/fallback v0.0.0-20240416174822-0da2a6a1bbc8
40
golang.org/x/net v0.24.0
41
golang.org/x/sync v0.7.0
42
golang.org/x/term v0.19.0
go.sum
+2
@@ -507,6 +507,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
507
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
508
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
509
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
510
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240416174822-0da2a6a1bbc8 h1:WsZ1vq1wEPrhLWtbblOEshIKDAbG0sYGgbYCFInT0QM=
511
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240416174822-0da2a6a1bbc8/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
512
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
513
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
514
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
modules/caddyhttp/caddyhttp.go
+11
-2
@@ -226,13 +226,22 @@ func StatusCodeMatches(actual, configured int) bool {
226
// in the implementation of http.Dir. The root is assumed to
227
// be a trusted path, but reqPath is not; and the output will
228
// never be outside of root. The resulting path can be used
229
-// with the local file system.
229
+// with the local file system. If root is empty, the current
230
+// directory is assumed. If the cleaned request path is deemed
231
+// not local according to lexical processing (i.e. ignoring links),
232
+// it will be rejected as unsafe and only the root will be returned.
233
func SanitizedPathJoin(root, reqPath string) string {
234
if root == "" {
235
root = "."
236
}
237
235
- path := filepath.Join(root, path.Clean("/"+reqPath))
238
+ relPath := path.Clean("/" + reqPath)[1:] // clean path and trim the leading /
239
+ if !filepath.IsLocal(relPath) {
240
+ // path is unsafe (see https://github.com/golang/go/issues/56336#issuecomment-1416214885)
241
+ return root
242
+ }
243
+
244
+ path := filepath.Join(root, filepath.FromSlash(relPath))
245
246
// filepath.Join also cleans the path, and cleaning strips
247
// the trailing slash, so we need to re-add it afterwards.
modules/caddyhttp/caddyhttp_test.go
+19
-7
@@ -3,6 +3,7 @@ package caddyhttp
3
import (
4
"net/url"
5
"path/filepath"
6
+ "runtime"
7
"testing"
8
)
9
@@ -12,9 +13,10 @@ func TestSanitizedPathJoin(t *testing.T) {
13
// %2f = /
14
// %5c = \
15
for i, tc := range []struct {
15
- inputRoot string
16
- inputPath string
17
- expect string
16
+ inputRoot string
17
+ inputPath string
18
+ expect string
19
+ expectWindows string
20
}{
21
{
22
inputPath: "",
@@ -63,7 +65,7 @@ func TestSanitizedPathJoin(t *testing.T) {
65
{
66
inputRoot: "/a/b",
67
inputPath: "/%2e%2e%2f%2e%2e%2f",
66
- expect: filepath.Join("/", "a", "b") + separator,
68
+ expect: "/a/b", // inputPath fails the IsLocal test so only the root is returned
69
},
70
{
71
inputRoot: "/a/b",
@@ -81,9 +83,16 @@ func TestSanitizedPathJoin(t *testing.T) {
83
expect: filepath.Join("C:\\www", "foo", "bar"),
84
},
85
{
84
- inputRoot: "C:\\www",
85
- inputPath: "/D:\\foo\\bar",
86
- expect: filepath.Join("C:\\www", "D:\\foo\\bar"),
86
+ inputRoot: "C:\\www",
87
+ inputPath: "/D:\\foo\\bar",
88
+ expect: filepath.Join("C:\\www", "D:\\foo\\bar"),
89
+ expectWindows: filepath.Join("C:\\www"), // inputPath fails IsLocal on Windows
90
+ },
91
+ {
92
+ // https://github.com/golang/go/issues/56336#issuecomment-1416214885
93
+ inputRoot: "root",
94
+ inputPath: "/a/b/../../c",
95
+ expect: filepath.Join("root", "c"),
96
},
97
} {
98
// we don't *need* to use an actual parsed URL, but it
@@ -96,6 +105,9 @@ func TestSanitizedPathJoin(t *testing.T) {
105
t.Fatalf("Test %d: invalid URL: %v", i, err)
106
}
107
actual := SanitizedPathJoin(tc.inputRoot, u.Path)
108
+ if runtime.GOOS == "windows" && tc.expectWindows != "" {
109
+ tc.expect = tc.expectWindows
110
+ }
111
if actual != tc.expect {
112
t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') => '%s' (expected '%s')",
113
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
modules/caddyhttp/requestbody/caddyfile.go
+25
-1
@@ -15,6 +15,8 @@
15
package requestbody
16
17
import (
18
+ "time"
19
+
20
"github.com/dustin/go-humanize"
21
22
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
@@ -44,8 +46,30 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
46
}
47
rb.MaxSize = int64(size)
48
49
+ case "read_timeout":
50
+ var timeoutStr string
51
+ if !h.AllArgs(&timeoutStr) {
52
+ return nil, h.ArgErr()
53
+ }
54
+ timeout, err := time.ParseDuration(timeoutStr)
55
+ if err != nil {
56
+ return nil, h.Errf("parsing read_timeout: %v", err)
57
+ }
58
+ rb.ReadTimeout = timeout
59
+
60
+ case "write_timeout":
61
+ var timeoutStr string
62
+ if !h.AllArgs(&timeoutStr) {
63
+ return nil, h.ArgErr()
64
+ }
65
+ timeout, err := time.ParseDuration(timeoutStr)
66
+ if err != nil {
67
+ return nil, h.Errf("parsing write_timeout: %v", err)
68
+ }
69
+ rb.WriteTimeout = timeout
70
+
71
default:
48
- return nil, h.Errf("unrecognized servers option '%s'", h.Val())
72
+ return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val())
73
}
74
}
75
modules/caddyhttp/requestbody/requestbody.go
+30
@@ -17,6 +17,9 @@ package requestbody
17
import (
18
"io"
19
"net/http"
20
+ "time"
21
+
22
+ "go.uber.org/zap"
23
24
"github.com/caddyserver/caddy/v2"
25
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
@@ -31,6 +34,14 @@ type RequestBody struct {
34
// The maximum number of bytes to allow reading from the body by a later handler.
35
// If more bytes are read, an error with HTTP status 413 is returned.
36
MaxSize int64 `json:"max_size,omitempty"`
37
+
38
+ // EXPERIMENTAL. Subject to change/removal.
39
+ ReadTimeout time.Duration `json:"read_timeout,omitempty"`
40
+
41
+ // EXPERIMENTAL. Subject to change/removal.
42
+ WriteTimeout time.Duration `json:"write_timeout,omitempty"`
43
+
44
+ logger *zap.Logger
45
}
46
47
// CaddyModule returns the Caddy module information.
@@ -41,6 +52,11 @@ func (RequestBody) CaddyModule() caddy.ModuleInfo {
52
}
53
}
54
55
+func (rb *RequestBody) Provision(ctx caddy.Context) error {
56
+ rb.logger = ctx.Logger()
57
+ return nil
58
+}
59
+
60
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
61
if r.Body == nil {
62
return next.ServeHTTP(w, r)
@@ -48,6 +64,20 @@ func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next cad
64
if rb.MaxSize > 0 {
65
r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)}
66
}
67
+ if rb.ReadTimeout > 0 || rb.WriteTimeout > 0 {
68
+ //nolint:bodyclose
69
+ rc := http.NewResponseController(w)
70
+ if rb.ReadTimeout > 0 {
71
+ if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil {
72
+ rb.logger.Error("could not set read deadline", zap.Error(err))
73
+ }
74
+ }
75
+ if rb.WriteTimeout > 0 {
76
+ if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil {
77
+ rb.logger.Error("could not set write deadline", zap.Error(err))
78
+ }
79
+ }
80
+ }
81
return next.ServeHTTP(w, r)
82
}
83
modules/caddyhttp/responsewriter_test.go
+4
-6
@@ -2,7 +2,6 @@ package caddyhttp
2
3
import (
4
"bytes"
5
- "fmt"
5
"io"
6
"net/http"
7
"strings"
@@ -75,20 +74,19 @@ func TestResponseWriterWrapperReadFrom(t *testing.T) {
74
// take precedence over our ReadFrom.
75
src := struct{ io.Reader }{strings.NewReader(srcData)}
76
78
- fmt.Println(name)
77
if _, err := io.Copy(wrapped, src); err != nil {
80
- t.Errorf("Copy() err = %v", err)
78
+ t.Errorf("%s: Copy() err = %v", name, err)
79
}
80
81
if got := tt.responseWriter.Written(); got != srcData {
84
- t.Errorf("data = %q, want %q", got, srcData)
82
+ t.Errorf("%s: data = %q, want %q", name, got, srcData)
83
}
84
85
if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom {
86
if tt.wantReadFrom {
89
- t.Errorf("ReadFrom() should have been called")
87
+ t.Errorf("%s: ReadFrom() should have been called", name)
88
} else {
91
- t.Errorf("ReadFrom() should not have been called")
89
+ t.Errorf("%s: ReadFrom() should not have been called", name)
90
}
91
}
92
})