@marcel91200 / caddy Mirror · read-only / commits / 6d97d8d8
Fork 0

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 })