git » gofer » next » tree

[next] / server / dir.go

package server

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"blitiri.com.ar/go/gofer/config"
)

type FileSystem struct {
	fs http.FileSystem

	opts config.DirOpts
}

func NewFS(fs http.FileSystem, opts config.DirOpts) *FileSystem {
	return &FileSystem{
		fs:   fs,
		opts: opts,
	}
}

func ListingEnabled(opts *config.DirOpts, name string) bool {
	if name == "" {
		name = "/"
	}
	name = filepath.Clean(name)

	longestP := ""
	value := false
	for p, val := range opts.Listing {
		p = filepath.Clean(p)
		if strings.HasPrefix(name, p) && len(p) > len(longestP) {
			longestP = p
			value = val
		}
	}

	return value
}

func (fs *FileSystem) Open(name string) (http.File, error) {
	for _, re := range fs.opts.Exclude {
		if re.MatchString(name) {
			return nil, os.ErrNotExist
		}
	}

	f, err := fs.fs.Open(name)
	if err != nil {
		return nil, err
	}

	// If it's not a directory, let it be.
	if s, _ := f.Stat(); s == nil || !s.IsDir() {
		return f, nil
	}

	f = wrappedFile{File: f, name: name, opts: &fs.opts}

	if ListingEnabled(&fs.opts, name) {
		return f, nil
	}

	// It's a directory, and listing not allowed.
	// However, if there is an index.html, we let it be served.
	index := filepath.Join(name, "index.html")
	if idxf, err := fs.fs.Open(index); err == nil {
		idxf.Close()
		return f, err
	}

	f.Close()
	return nil, os.ErrNotExist
}

type wrappedFile struct {
	http.File
	name string
	opts *config.DirOpts
}

func (f wrappedFile) Readdir(count int) ([]os.FileInfo, error) {
	if !ListingEnabled(f.opts, f.name) {
		return nil, os.ErrNotExist
	}

	// Exclude files from listings too.
	all, err := f.File.Readdir(count)
	var fis []os.FileInfo
outer:
	for _, fi := range all {
		for _, re := range f.opts.Exclude {
			name := filepath.Join(f.name, fi.Name())
			if re.MatchString(name) {
				continue outer
			}
		}
		fis = append(fis, fi)
	}
	return fis, err
}