// An empty Dir is treated as ".".
type Dir string
+// mapDirOpenError maps the provided non-nil error from opening name
+// to a possibly better non-nil error. In particular, it turns OS-specific errors
+// about opening files in non-directories into os.ErrNotExist. See Issue 18984.
+func mapDirOpenError(originalErr error, name string) error {
+ if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
+ return originalErr
+ }
+
+ parts := strings.Split(name, string(filepath.Separator))
+ for i := range parts {
+ fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
+ if err != nil {
+ return originalErr
+ }
+ if !fi.IsDir() {
+ return os.ErrNotExist
+ }
+ }
+ return originalErr
+}
+
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
if dir == "" {
dir = "."
}
- f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
+ fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
+ f, err := os.Open(fullName)
if err != nil {
- return nil, err
+ return nil, mapDirOpenError(err, fullName)
}
return f, nil
}
}
}
+// Issue 18984: tests that requests for paths beyond files return not-found errors
+func TestFileServerNotDirError(t *testing.T) {
+ defer afterTest(t)
+ ts := httptest.NewServer(FileServer(Dir("testdata")))
+ defer ts.Close()
+
+ res, err := Get(ts.URL + "/index.html/not-a-file")
+ if err != nil {
+ t.Fatal(err)
+ }
+ res.Body.Close()
+ if res.StatusCode != 404 {
+ t.Errorf("StatusCode = %v; want 404", res.StatusCode)
+ }
+
+ dir := Dir("testdata")
+ _, err = dir.Open("/index.html/not-a-file")
+ if err == nil {
+ t.Fatal("err == nil; want != nil")
+ }
+ if !os.IsNotExist(err) {
+ t.Errorf("err = %v; os.IsNotExist(err) = %v; want true", err, os.IsNotExist(err))
+ }
+
+ _, err = dir.Open("/index.html/not-a-dir/not-a-file")
+ if err == nil {
+ t.Fatal("err == nil; want != nil")
+ }
+ if !os.IsNotExist(err) {
+ t.Errorf("err = %v; os.IsNotExist(err) = %v; want true", err, os.IsNotExist(err))
+ }
+}
+
func TestFileServerCleanPath(t *testing.T) {
tests := []struct {
path string