Skip to content

Commit

Permalink
Draft: Serve custom page when directory listing is disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
denny241 committed Feb 24, 2022
1 parent b5d9ea4 commit 9b5738e
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 35 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/loophole/cli

go 1.14
go 1.16

require (
github.com/abbot/go-http-auth v0.4.0
Expand Down
75 changes: 75 additions & 0 deletions internal/pkg/customfilesystem/customfilesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//necessary bits and pieces for being able to serve e.g. the custom notification html
// without needing to create files in the users folders
package customfilesystem

import (
"bytes"
"errors"
"io/fs"
"net/http"
"path/filepath"
)

var DirectoryListingDisabledPage = []byte("<!DOCTYPE html><html><body><img src=\"https://raw.githubusercontent.com/loophole/website/master/static/img/logo.png\" alt=\"https://raw.githubusercontent.com/loophole/website/master/static/img/logo.png\" class=\"transparent shrinkToFit\" width=\"400\" height=\"88\"><p>Directory index listing has been disabled. Please enter the path of a file.</p></body></html>")

type CustomFileSystem struct {
FS http.FileSystem
}

//the file cannot be reused since it's io.Reader can only be read from once,
// so we need a reusable way to create it
func writeDirectoryListingDisabledPageFile(pageFile *MyFile) {
*pageFile = MyFile{
Reader: bytes.NewReader(DirectoryListingDisabledPage),
mif: myFileInfo{
name: "customIndex.html",
data: DirectoryListingDisabledPage,
},
}
}

func (cfs CustomFileSystem) Open(path string) (http.File, error) {
f, err := _Open(path, cfs)

if err != nil {
var pathErrorInstance error = &fs.PathError{
Err: errors.New(""),
}
if errors.As(err, &pathErrorInstance) {
return nil, err
}
var pageFile *MyFile = &MyFile{}
writeDirectoryListingDisabledPageFile(pageFile)
return pageFile, nil
}
return f, nil
}

//if there is an elegant way to integrate the following into the function above without
// using labeled breaks or adding even more control structures let me know
func _Open(path string, cfs CustomFileSystem) (http.File, error) {
f, err := cfs.FS.Open(path)
if err != nil {
if path == "/" {
var pageFile *MyFile = &MyFile{}
writeDirectoryListingDisabledPageFile(pageFile)
return pageFile, nil
}
return nil, err
}

s, err := f.Stat()
if err != nil {
return nil, err
}

if s.IsDir() {
index := filepath.Join(path, "index.html")
if _, err := cfs.FS.Open(index); err != nil {
var pageFile *MyFile = &MyFile{}
writeDirectoryListingDisabledPageFile(pageFile)
return pageFile, nil
}
}
return f, nil
}
44 changes: 44 additions & 0 deletions internal/pkg/customfilesystem/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//taken from https://stackoverflow.com/questions/52697277/simples-way-to-make-a-byte-into-a-virtual-file-object-in-golang
package customfilesystem

import (
"bytes"
"io"
"os"
"time"
)

type File interface {
io.Closer
io.Reader
io.Seeker
Readdir(count int) ([]os.FileInfo, error)
Stat() (os.FileInfo, error)
}

type myFileInfo struct {
name string
data []byte
}

func (mif myFileInfo) Name() string { return mif.name }
func (mif myFileInfo) Size() int64 { return int64(len(mif.data)) }
func (mif myFileInfo) Mode() os.FileMode { return 0444 } // Read for all
func (mif myFileInfo) ModTime() time.Time { return time.Time{} } // Return anything
func (mif myFileInfo) IsDir() bool { return false }
func (mif myFileInfo) Sys() interface{} { return nil }

type MyFile struct {
*bytes.Reader
mif myFileInfo
}

func (mf *MyFile) Close() error { return nil } // Noop, nothing to do

func (mf *MyFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil // We are not a directory but a single file
}

func (mf *MyFile) Stat() (os.FileInfo, error) {
return mf.mif, nil
}
63 changes: 29 additions & 34 deletions internal/pkg/httpserver/httpserver.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package httpserver

import (
"bytes"
"crypto/tls"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"path/filepath"
"time"

auth "github.com/abbot/go-http-auth"
"github.com/loophole/cli/config"
lm "github.com/loophole/cli/internal/app/loophole/models"
cfs "github.com/loophole/cli/internal/pkg/customfilesystem"
"github.com/loophole/cli/internal/pkg/urlmaker"
"golang.org/x/crypto/bcrypt"
"golang.org/x/net/webdav"
Expand All @@ -20,34 +22,6 @@ const (
logoURL = "https://raw.githubusercontent.com/loophole/website/master/static/img/logo.png"
)

type customFileSystem struct {
fs http.FileSystem
}

func (cfs customFileSystem) Open(path string) (http.File, error) {
f, err := cfs.fs.Open(path)
if err != nil {
return nil, err
}

s, err := f.Stat()
if err != nil {
return nil, err
}
if s.IsDir() {
index := filepath.Join(path, "index.html")
if _, err := cfs.fs.Open(index); err != nil {
closeErr := f.Close()
if closeErr != nil {
return nil, err
}
return nil, err
}
}

return f, nil
}

type ServerBuilder interface {
WithSiteID(string) ServerBuilder
WithDomain(string) ServerBuilder
Expand Down Expand Up @@ -206,18 +180,39 @@ func (ssb *staticServerBuilder) WithBasicAuth(username string, password string)
return ssb
}

func serveDirectoryListingNotification(w http.ResponseWriter, req *http.Request) {
customPageSeeker := bytes.NewReader(cfs.DirectoryListingDisabledPage)
http.ServeContent(w, req, "name", time.Now(), customPageSeeker)
}

//this could break desktop but I haven't been able to figure out another way yet
var fsHandler http.Handler = nil
var fsHandlerIsCustom = false

//handler functions may only take these arguments, so we need variables outside of it to make it's behaviour conditional
func conditionalHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/" && fsHandlerIsCustom {
serveDirectoryListingNotification(w, req)
} else {
fsHandler.ServeHTTP(w, req)
}
}

func (ssb *staticServerBuilder) Build() (*http.Server, error) {
var fs http.Handler
if config.Config.Display.DisableDirectoryListing {
fs = http.FileServer(customFileSystem{http.Dir(ssb.directory)})
fsHandler = http.FileServer(cfs.CustomFileSystem{
FS: http.Dir(ssb.directory),
})
fsHandlerIsCustom = true
} else {
fs = http.FileServer(http.Dir(ssb.directory))
fsHandler = http.FileServer(http.Dir(ssb.directory))
fsHandlerIsCustom = false
}

var server *http.Server

if ssb.basicAuthEnabled {
handler, err := getBasicAuthHandler(ssb.serverBuilder.siteID, ssb.serverBuilder.domain, ssb.basicAuthUsername, ssb.basicAuthPassword, fs.ServeHTTP)
handler, err := getBasicAuthHandler(ssb.serverBuilder.siteID, ssb.serverBuilder.domain, ssb.basicAuthUsername, ssb.basicAuthPassword, conditionalHandler)
if err != nil {
return nil, err
}
Expand All @@ -228,7 +223,7 @@ func (ssb *staticServerBuilder) Build() (*http.Server, error) {
}
} else {
server = &http.Server{
Handler: fs,
Handler: http.HandlerFunc(conditionalHandler),
TLSConfig: getTLSConfig(ssb.serverBuilder.siteID, ssb.serverBuilder.domain),
}
}
Expand Down

0 comments on commit 9b5738e

Please sign in to comment.