caddy/modules/caddyhttp/reverseproxy/fastcgi/fastcgi_windows_test.go

75 lines
2.9 KiB
Go

// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package fastcgi
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// On Windows the path matchers additionally fold backslashes into separators and
// trim per-component trailing dots and spaces (see caddyhttp.NormalizedPath),
// so the transport must do the same or the dot-segment bypass simply reappears
// spelled with backslashes.
func TestBuildEnvWindowsSeparatorsCannotSelectAnotherScript(t *testing.T) {
t.Parallel()
newRequest := func(target string) *http.Request {
r := httptest.NewRequest(http.MethodGet, target, nil)
ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{})
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *r)
return r.WithContext(ctx)
}
for _, tc := range []struct {
name string
target string
expectedScriptName string
expectedPathInfo string
}{
{"backslash dot segments", `/admin.php\..\other.php`, "/other.php", ""},
{"backslash, two levels", `/admin.php\..\..\other.php`, "/other.php", ""},
{"mixed separators", `/admin.php\../other.php`, "/other.php", ""},
// Per-component trailing dot/space folding is Windows-only and is not
// something CleanPath does, so this case fails unless the transport
// applies the same normalization the matchers do.
{"trailing dot in a path-info component", "/other.php/a./b", "/other.php", "/a/b"},
{"trailing space in a path-info component", "/other.php/a%20/b", "/other.php", "/a/b"},
} {
t.Run(tc.name, func(t *testing.T) {
tr := Transport{Root: `C:\srv`, SplitPath: []string{".php"}}
env, err := tr.buildEnv(newRequest(tc.target))
require.NoError(t, err)
assert.Equal(t, tc.expectedScriptName, env["SCRIPT_NAME"],
"SCRIPT_NAME must come from the normalized, cleaned path")
assert.Equal(t, tc.expectedPathInfo, env["PATH_INFO"],
"PATH_INFO must come from the normalized, cleaned path")
assert.NotContains(t, env["SCRIPT_FILENAME"], "admin.php",
"a pre-normalization path component must never become the executed script")
})
}
}