@marcel91200 / caddy Mirror · read-only
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 11, 02:15 UTC

master
go 122 lines 4.55 KB
Raw
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 //go:build windows
16
17 package fastcgi
18
19 import (
20 "context"
21 "net/http"
22 "net/http/httptest"
23 "testing"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
27 "github.com/stretchr/testify/assert"
28 "github.com/stretchr/testify/require"
29 )
30
31 // On Windows the path matchers additionally fold backslashes into separators and
32 // trim per-component trailing dots and spaces (see caddyhttp.NormalizedPath),
33 // so the transport must do the same or the dot-segment bypass simply reappears
34 // spelled with backslashes. Backslash dot segments are rejected outright because
35 // the "%" path matchers read them as "%2f..%2f", which they do not resolve.
36 func TestBuildEnvWindowsSeparatorsCannotSelectAnotherScript(t *testing.T) {
37 t.Parallel()
38
39 newRequest := func(target string) *http.Request {
40 r := httptest.NewRequest(http.MethodGet, target, nil)
41 ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
42 ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{})
43 ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *r)
44 return r.WithContext(ctx)
45 }
46
47 for _, tc := range []struct {
48 name string
49 target string
50 wantErr bool
51 expectedScriptName string
52 expectedPathInfo string
53 }{
54 {"backslash dot segments", `/admin.php\..\other.php`, true, "", ""},
55 {"backslash, two levels", `/admin.php\..\..\other.php`, true, "", ""},
56 {"mixed separators", `/admin.php\../other.php`, true, "", ""},
57 {"escaped backslash dot segments", "/admin.php%5C..%5Cother.php", true, "", ""},
58 {"backslash separator", `/dir\other.php\a`, false, "/dir/other.php", "/a"},
59 {"escaped backslash separator", "/dir%5Cother.php%5Ca", false, "/dir/other.php", "/a"},
60 // Per-component trailing dot/space folding is Windows-only and is not
61 // something CleanPath does, so this case fails unless the transport
62 // applies the same normalization the matchers do.
63 {"trailing dot in a path-info component", "/other.php/a./b", false, "/other.php", "/a/b"},
64 {"trailing space in a path-info component", "/other.php/a%20/b", false, "/other.php", "/a/b"},
65 } {
66 t.Run(tc.name, func(t *testing.T) {
67 tr := Transport{Root: `C:\srv`, SplitPath: []string{".php"}}
68 env, err := tr.buildEnv(newRequest(tc.target))
69 if tc.wantErr {
70 var herr caddyhttp.HandlerError
71 require.ErrorAs(t, err, &herr)
72 assert.Equal(t, http.StatusBadRequest, herr.StatusCode)
73 return
74 }
75 require.NoError(t, err)
76
77 assert.Equal(t, tc.expectedScriptName, env["SCRIPT_NAME"],
78 "SCRIPT_NAME must come from the normalized, cleaned path")
79 assert.Equal(t, tc.expectedPathInfo, env["PATH_INFO"],
80 "PATH_INFO must come from the normalized, cleaned path")
81 assert.NotContains(t, env["SCRIPT_FILENAME"], "admin.php",
82 "a pre-normalization path component must never become the executed script")
83 })
84 }
85 }
86
87 // An 8.3 alias ("/UPLOAD~1.PHP" for "uploadedpayload.phpx") or an alternate
88 // data stream ("a.txt:b.php") reaches a non-PHP file with a script-looking path.
89 func TestBuildEnvWindowsScriptAliases(t *testing.T) {
90 t.Parallel()
91
92 for _, tc := range []struct {
93 target string
94 wantErr bool
95 }{
96 {"/UPLOAD~1.PHP", true},
97 {"/PROTEC~1/index.php", true},
98 {"/a.txt:b.php", true},
99 {"/a.txt%3Ab.php", true},
100 {"/index.php/urn:isbn:0451450523", false},
101 {"/index.php/PROTEC~1", false},
102 {"/index.php", false},
103 } {
104 t.Run(tc.target, func(t *testing.T) {
105 r := httptest.NewRequest(http.MethodGet, tc.target, nil)
106 ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
107 ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{})
108 ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *r)
109
110 tr := Transport{Root: `C:\srv`, SplitPath: []string{".php"}}
111 _, err := tr.buildEnv(r.WithContext(ctx))
112 if !tc.wantErr {
113 require.NoError(t, err)
114 return
115 }
116
117 var herr caddyhttp.HandlerError
118 require.ErrorAs(t, err, &herr)
119 assert.Equal(t, http.StatusBadRequest, herr.StatusCode)
120 })
121 }
122 }