// 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. package fileserver import ( "net/url" "path/filepath" "testing" ) func TestSanitizedPathJoin(t *testing.T) { // For easy reference: // %2e = . // %2f = / // %5c = \ for i, tc := range []struct { inputRoot string inputPath string expect string }{ { inputPath: "", expect: ".", }, { inputPath: "/", expect: ".", }, { inputPath: "/foo", expect: "foo", }, { inputPath: "/foo/", expect: "foo" + string(filepath.Separator), }, { inputPath: "/foo/bar", expect: filepath.Join("foo", "bar"), }, { inputRoot: "/a", inputPath: "/foo/bar", expect: filepath.Join("/", "a", "foo", "bar"), }, { inputPath: "/foo/../bar", expect: "bar", }, { inputRoot: "/a/b", inputPath: "/foo/../bar", expect: filepath.Join("/", "a", "b", "bar"), }, { inputRoot: "/a/b", inputPath: "/..%2fbar", expect: filepath.Join("/", "a", "b", "bar"), }, { inputRoot: "/a/b", inputPath: "/%2e%2e%2fbar", expect: filepath.Join("/", "a", "b", "bar"), }, { inputRoot: "/a/b", inputPath: "/%2e%2e%2f%2e%2e%2f", expect: filepath.Join("/", "a", "b") + string(filepath.Separator), }, { inputRoot: "C:\\www", inputPath: "/foo/bar", expect: filepath.Join("C:\\www", "foo", "bar"), }, // TODO: test more windows paths... on windows... sigh. } { // we don't *need* to use an actual parsed URL, but it // adds some authenticity to the tests since real-world // values will be coming in from URLs; thus, the test // corpus can contain paths as encoded by clients, which // more closely emulates the actual attack vector u, err := url.Parse("http://test:9999" + tc.inputPath) if err != nil { t.Fatalf("Test %d: invalid URL: %v", i, err) } actual := sanitizedPathJoin(tc.inputRoot, u.Path) if actual != tc.expect { t.Errorf("Test %d: [%s %s] => %s (expected %s)", i, tc.inputRoot, tc.inputPath, actual, tc.expect) } } } func TestFileHidden(t *testing.T) { for i, tc := range []struct { inputHide []string inputPath string expect bool }{ { inputHide: nil, inputPath: "", expect: false, }, { inputHide: []string{".gitignore"}, inputPath: "/.gitignore", expect: true, }, { inputHide: []string{".git"}, inputPath: "/.gitignore", expect: false, }, { inputHide: []string{"/.git"}, inputPath: "/.gitignore", expect: false, }, { inputHide: []string{".git"}, inputPath: "/.git", expect: true, }, { inputHide: []string{".git"}, inputPath: "/.git/foo", expect: true, }, { inputHide: []string{".git"}, inputPath: "/foo/.git/bar", expect: true, }, { inputHide: []string{"/prefix"}, inputPath: "/prefix/foo", expect: true, }, { inputHide: []string{"/foo/*/bar"}, inputPath: "/foo/asdf/bar", expect: true, }, { inputHide: []string{"/foo"}, inputPath: "/foo", expect: true, }, { inputHide: []string{"/foo"}, inputPath: "/foobar", expect: false, }, } { // for Windows' sake tc.inputPath = filepath.FromSlash(tc.inputPath) for i := range tc.inputHide { tc.inputHide[i] = filepath.FromSlash(tc.inputHide[i]) } actual := fileHidden(tc.inputPath, tc.inputHide) if actual != tc.expect { t.Errorf("Test %d: Is %s hidden in %v? Got %t but expected %t", i, tc.inputPath, tc.inputHide, actual, tc.expect) } } }