Skip to content

Commit f3e6215

Browse files
committed
Draft: Serve custom page when directory listing is disabled
1 parent 5d551c4 commit f3e6215

3 files changed

Lines changed: 148 additions & 34 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//necessary bits and pieces for being able to serve e.g. the custom notification html
2+
// without needing to create files in the users folders
3+
package customfilesystem
4+
5+
import (
6+
"bytes"
7+
"errors"
8+
"io/fs"
9+
"net/http"
10+
"path/filepath"
11+
)
12+
13+
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>")
14+
15+
type CustomFileSystem struct {
16+
FS http.FileSystem
17+
}
18+
19+
//the file cannot be reused since it's io.Reader can only be read from once,
20+
// so we need a reusable way to create it
21+
func writeDirectoryListingDisabledPageFile(pageFile *MyFile) {
22+
*pageFile = MyFile{
23+
Reader: bytes.NewReader(DirectoryListingDisabledPage),
24+
mif: myFileInfo{
25+
name: "customIndex.html",
26+
data: DirectoryListingDisabledPage,
27+
},
28+
}
29+
}
30+
31+
func (cfs CustomFileSystem) Open(path string) (http.File, error) {
32+
f, err := _Open(path, cfs)
33+
34+
if err != nil {
35+
var pathErrorInstance error = &fs.PathError{
36+
Err: errors.New(""),
37+
}
38+
if errors.As(err, &pathErrorInstance) {
39+
return nil, err
40+
}
41+
var pageFile *MyFile = &MyFile{}
42+
writeDirectoryListingDisabledPageFile(pageFile)
43+
return pageFile, nil
44+
}
45+
return f, nil
46+
}
47+
48+
//if there is an elegant way to integrate the following into the function above without
49+
// using labeled breaks or adding even more control structures let me know
50+
func _Open(path string, cfs CustomFileSystem) (http.File, error) {
51+
f, err := cfs.FS.Open(path)
52+
if err != nil {
53+
if path == "/" {
54+
var pageFile *MyFile = &MyFile{}
55+
writeDirectoryListingDisabledPageFile(pageFile)
56+
return pageFile, nil
57+
}
58+
return nil, err
59+
}
60+
61+
s, err := f.Stat()
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if s.IsDir() {
67+
index := filepath.Join(path, "index.html")
68+
if _, err := cfs.FS.Open(index); err != nil {
69+
var pageFile *MyFile = &MyFile{}
70+
writeDirectoryListingDisabledPageFile(pageFile)
71+
return pageFile, nil
72+
}
73+
}
74+
return f, nil
75+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//taken from https://stackoverflow.com/questions/52697277/simples-way-to-make-a-byte-into-a-virtual-file-object-in-golang
2+
package customfilesystem
3+
4+
import (
5+
"bytes"
6+
"io"
7+
"os"
8+
"time"
9+
)
10+
11+
type File interface {
12+
io.Closer
13+
io.Reader
14+
io.Seeker
15+
Readdir(count int) ([]os.FileInfo, error)
16+
Stat() (os.FileInfo, error)
17+
}
18+
19+
type myFileInfo struct {
20+
name string
21+
data []byte
22+
}
23+
24+
func (mif myFileInfo) Name() string { return mif.name }
25+
func (mif myFileInfo) Size() int64 { return int64(len(mif.data)) }
26+
func (mif myFileInfo) Mode() os.FileMode { return 0444 } // Read for all
27+
func (mif myFileInfo) ModTime() time.Time { return time.Time{} } // Return anything
28+
func (mif myFileInfo) IsDir() bool { return false }
29+
func (mif myFileInfo) Sys() interface{} { return nil }
30+
31+
type MyFile struct {
32+
*bytes.Reader
33+
mif myFileInfo
34+
}
35+
36+
func (mf *MyFile) Close() error { return nil } // Noop, nothing to do
37+
38+
func (mf *MyFile) Readdir(count int) ([]os.FileInfo, error) {
39+
return nil, nil // We are not a directory but a single file
40+
}
41+
42+
func (mf *MyFile) Stat() (os.FileInfo, error) {
43+
return mf.mif, nil
44+
}

internal/pkg/httpserver/httpserver.go

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package httpserver
22

33
import (
4+
"bytes"
45
"crypto/tls"
56
"fmt"
67
"net"
78
"net/http"
89
"net/http/httputil"
910
"net/url"
10-
"path/filepath"
11+
"time"
1112

1213
auth "github.com/abbot/go-http-auth"
1314
"github.com/loophole/cli/config"
1415
lm "github.com/loophole/cli/internal/app/loophole/models"
16+
cfs "github.com/loophole/cli/internal/pkg/customfilesystem"
1517
"github.com/loophole/cli/internal/pkg/urlmaker"
1618
"golang.org/x/crypto/bcrypt"
1719
"golang.org/x/net/webdav"
@@ -21,34 +23,6 @@ const (
2123
logoURL = "https://raw.githubusercontent.com/loophole/website/master/static/img/logo.png"
2224
)
2325

24-
type customFileSystem struct {
25-
fs http.FileSystem
26-
}
27-
28-
func (cfs customFileSystem) Open(path string) (http.File, error) {
29-
f, err := cfs.fs.Open(path)
30-
if err != nil {
31-
return nil, err
32-
}
33-
34-
s, err := f.Stat()
35-
if err != nil {
36-
return nil, err
37-
}
38-
if s.IsDir() {
39-
index := filepath.Join(path, "index.html")
40-
if _, err := cfs.fs.Open(index); err != nil {
41-
closeErr := f.Close()
42-
if closeErr != nil {
43-
return nil, err
44-
}
45-
return nil, err
46-
}
47-
}
48-
49-
return f, nil
50-
}
51-
5226
type ServerBuilder interface {
5327
WithSiteID(string) ServerBuilder
5428
WithDomain(string) ServerBuilder
@@ -218,18 +192,39 @@ func (ssb *staticServerBuilder) WithBasicAuth(username string, password string)
218192
return ssb
219193
}
220194

195+
func serveDirectoryListingNotification(w http.ResponseWriter, req *http.Request) {
196+
customPageSeeker := bytes.NewReader(cfs.DirectoryListingDisabledPage)
197+
http.ServeContent(w, req, "name", time.Now(), customPageSeeker)
198+
}
199+
200+
//this could break desktop but I haven't been able to figure out another way yet
201+
var fsHandler http.Handler = nil
202+
var fsHandlerIsCustom = false
203+
204+
//handler functions may only take these arguments, so we need variables outside of it to make it's behaviour conditional
205+
func conditionalHandler(w http.ResponseWriter, req *http.Request) {
206+
if req.URL.Path == "/" && fsHandlerIsCustom {
207+
serveDirectoryListingNotification(w, req)
208+
} else {
209+
fsHandler.ServeHTTP(w, req)
210+
}
211+
}
212+
221213
func (ssb *staticServerBuilder) Build() (*http.Server, error) {
222-
var fs http.Handler
223214
if config.Config.Display.DisableDirectoryListing {
224-
fs = http.FileServer(customFileSystem{http.Dir(ssb.directory)})
215+
fsHandler = http.FileServer(cfs.CustomFileSystem{
216+
FS: http.Dir(ssb.directory),
217+
})
218+
fsHandlerIsCustom = true
225219
} else {
226-
fs = http.FileServer(http.Dir(ssb.directory))
220+
fsHandler = http.FileServer(http.Dir(ssb.directory))
221+
fsHandlerIsCustom = false
227222
}
228223

229224
var server *http.Server
230225

231226
if ssb.basicAuthEnabled {
232-
handler, err := getBasicAuthHandler(ssb.serverBuilder.siteID, ssb.serverBuilder.domain, ssb.basicAuthUsername, ssb.basicAuthPassword, fs.ServeHTTP)
227+
handler, err := getBasicAuthHandler(ssb.serverBuilder.siteID, ssb.serverBuilder.domain, ssb.basicAuthUsername, ssb.basicAuthPassword, conditionalHandler)
233228
if err != nil {
234229
return nil, err
235230
}
@@ -240,7 +235,7 @@ func (ssb *staticServerBuilder) Build() (*http.Server, error) {
240235
}
241236
} else {
242237
server = &http.Server{
243-
Handler: fs,
238+
Handler: http.HandlerFunc(conditionalHandler),
244239
TLSConfig: getTLSConfig(ssb.serverBuilder.siteID, ssb.serverBuilder.domain, ssb.serverBuilder.disableOldCiphers),
245240
}
246241
}

0 commit comments

Comments
 (0)