Skip to content

Commit 71dd550

Browse files
committed
Clean-up, format listing as table, add sizes
1 parent f97be82 commit 71dd550

File tree

5 files changed

+127
-44
lines changed

5 files changed

+127
-44
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = 1.2.2
1+
VERSION = 1.3.0
22

33
APP := http-file-server
44
PACKAGES := $(shell go list -f {{.Dir}} ./...)

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# http-file-server
22

3-
`http-file-server` is a dependency-free HTTP file server.
3+
`http-file-server` is a dependency-free HTTP file server. Beyond directory listings and file downloads, it lets you download a whole directory as as `.zip` or `.tar.gz` (generated on-the-fly).
44

55
![screenshot](doc/screenshot.gif)
66

@@ -60,14 +60,14 @@ Or [download a binary](https://github.com/sgreben/http-file-server/releases/late
6060

6161
```sh
6262
# Linux
63-
curl -L https://github.com/sgreben/http-file-server/releases/download/1.2.2/http-file-server_1.2.2_linux_x86_64.tar.gz | tar xz
63+
curl -L https://github.com/sgreben/http-file-server/releases/download/1.3.0/http-file-server_1.3.0_linux_x86_64.tar.gz | tar xz
6464

6565
# OS X
66-
curl -L https://github.com/sgreben/http-file-server/releases/download/1.2.2/http-file-server_1.2.2_osx_x86_64.tar.gz | tar xz
66+
curl -L https://github.com/sgreben/http-file-server/releases/download/1.3.0/http-file-server_1.3.0_osx_x86_64.tar.gz | tar xz
6767

6868
# Windows
69-
curl -LO https://github.com/sgreben/http-file-server/releases/download/1.2.2/http-file-server_1.2.2_windows_x86_64.zip
70-
unzip versions_1.2.2_windows_x86_64.zip
69+
curl -LO https://github.com/sgreben/http-file-server/releases/download/1.3.0/http-file-server_1.3.0_windows_x86_64.zip
70+
unzip versions_1.3.0_windows_x86_64.zip
7171
```
7272

7373
## Use it

README.template.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# http-file-server
22

3-
`http-file-server` is a dependency-free HTTP file server.
3+
`http-file-server` is a dependency-free HTTP file server. Beyond directory listings and file downloads, it lets you download a whole directory as as `.zip` or `.tar.gz` (generated on-the-fly).
44

55
![screenshot](doc/screenshot.gif)
66

doc/screenshot.gif

-6.29 KB
Loading

server.go

+120-37
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package main
22

33
import (
44
"fmt"
5+
"html/template"
56
"net/http"
67
"net/url"
78
"os"
8-
"path"
99
"path/filepath"
1010
"sort"
1111
"strings"
@@ -21,17 +21,86 @@ const (
2121
zipContentType = "application/zip"
2222
)
2323

24+
const directoryListingTemplateText = `
25+
<html>
26+
<head>
27+
<title>{{ .Title }}</title>
28+
<style>
29+
.number { text-align: right; }
30+
.text { text-align: left; }
31+
</style>
32+
</head>
33+
<body>
34+
<h1>{{ .Title }}</h1>
35+
{{ if .Files }}
36+
<ul>
37+
<li><a href="{{ .TarGzURL }}">Entire directory as .tar.gz</a></li>
38+
<li><a href="{{ .ZipURL }}">Entire directory as .zip</a></li>
39+
</ul>
40+
{{ end }}
41+
<table>
42+
<thead>
43+
<th class=text>Name</th>
44+
<th class=number>Size</th>
45+
<th class=number>Size (bytes)</th>
46+
</thead>
47+
<tbody>
48+
{{ range .Files }}
49+
<tr>
50+
<td class=text><a href="{{ .URL.String }}">{{ .Name }}</td>
51+
<td class=number>{{ if (not .IsDir) }}<pre>{{.Size.String }}</pre>{{ end }}</td>
52+
<td class=number>{{ if (not .IsDir) }}<pre>{{ .Size | printf "%d" }}</pre>{{ end }}</td>
53+
</tr>
54+
{{ end }}
55+
</tbody>
56+
</table>
57+
</body>
58+
</html>
59+
`
60+
61+
type fileSizeBytes int64
62+
63+
func (f fileSizeBytes) String() string {
64+
const (
65+
KB = 1024
66+
MB = 1024 * KB
67+
GB = 1024 * MB
68+
)
69+
switch {
70+
case f < KB:
71+
return fmt.Sprintf("%d B", f)
72+
case f < MB:
73+
return fmt.Sprintf("%d KB", f/KB)
74+
case f < GB:
75+
return fmt.Sprintf("%d MB", f/MB)
76+
case f >= GB:
77+
fallthrough
78+
default:
79+
return fmt.Sprintf("%d GB", f/GB)
80+
}
81+
}
82+
83+
type directoryListingFileData struct {
84+
Name string
85+
Size fileSizeBytes
86+
IsDir bool
87+
URL *url.URL
88+
}
89+
90+
type directoryListingData struct {
91+
Title string
92+
ZipURL *url.URL
93+
TarGzURL *url.URL
94+
Files []directoryListingFileData
95+
}
96+
2497
type fileHandler struct {
2598
route string
2699
path string
27100
}
28101

29-
var htmlReplacer = strings.NewReplacer(
30-
"&", "&amp;",
31-
"<", "&lt;",
32-
">", "&gt;",
33-
`"`, "&#34;",
34-
"'", "&#39;",
102+
var (
103+
directoryListingTemplate = template.Must(template.New("").Parse(directoryListingTemplateText))
35104
)
36105

37106
func (f *fileHandler) serveStatus(w http.ResponseWriter, r *http.Request, status int) {
@@ -65,37 +134,51 @@ func (f *fileHandler) serveDir(w http.ResponseWriter, r *http.Request, dirPath s
65134
return
66135
}
67136
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
68-
69137
w.Header().Set("Content-Type", "text/html; charset=utf-8")
70-
title, _ := filepath.Rel(f.path, dirPath)
71-
title = filepath.Join(filepath.Base(f.path), title)
72-
fmt.Fprintf(w, "<h1>%s</h1>\n", htmlReplacer.Replace(title))
73-
fmt.Fprintf(w, "<ul>\n")
74-
for _, d := range files {
75-
name := d.Name()
76-
if d.IsDir() {
77-
name += "/"
78-
}
79-
url := url.URL{Path: path.Join(r.URL.Path, name)}
80-
fmt.Fprintf(w, "<li><a href=\"%s\">%s</a></li>\n", url.String(), htmlReplacer.Replace(name))
81-
}
82-
fmt.Fprintf(w, "</ul>\n")
83-
if len(files) > 0 {
84-
url := url.URL{Path: r.URL.Path}
85-
q := url.Query()
86-
q.Set(tarGzKey, tarGzValue)
87-
url.RawQuery = q.Encode()
88-
fmt.Fprintf(w, "<p>\n")
89-
fmt.Fprintf(w, "<a href=\"%s\">Entire directory as .tar.gz</a>\n", url.String())
90-
fmt.Fprintf(w, "</p>\n")
91-
url.RawQuery = ""
92-
q = url.Query()
93-
q.Set(zipKey, zipValue)
94-
url.RawQuery = q.Encode()
95-
fmt.Fprintf(w, "<p>\n")
96-
fmt.Fprintf(w, "<a href=\"%s\">Entire directory as .zip</a>\n", url.String())
97-
fmt.Fprintf(w, "</p>\n")
98-
}
138+
directoryListingTemplate.Execute(w, directoryListingData{
139+
Title: func() string {
140+
relPath, _ := filepath.Rel(f.path, dirPath)
141+
return filepath.Join(filepath.Base(f.path), relPath)
142+
}(),
143+
TarGzURL: func() *url.URL {
144+
url := *r.URL
145+
q := url.Query()
146+
q.Set(tarGzKey, tarGzValue)
147+
url.RawQuery = q.Encode()
148+
return &url
149+
}(),
150+
ZipURL: func() *url.URL {
151+
url := *r.URL
152+
q := url.Query()
153+
q.Set(zipKey, zipValue)
154+
url.RawQuery = q.Encode()
155+
return &url
156+
}(),
157+
Files: func() (out []directoryListingFileData) {
158+
for _, d := range files {
159+
name := d.Name()
160+
if d.IsDir() {
161+
name += "/"
162+
}
163+
fileData := directoryListingFileData{
164+
Name: name,
165+
IsDir: d.IsDir(),
166+
Size: fileSizeBytes(d.Size()),
167+
URL: func() *url.URL {
168+
url := *r.URL
169+
path := filepath.Join(url.Path, name)
170+
if d.IsDir() {
171+
path += "/"
172+
}
173+
url.Path = path
174+
return &url
175+
}(),
176+
}
177+
out = append(out, fileData)
178+
}
179+
return out
180+
}(),
181+
})
99182
}
100183

101184
// ServeHTTP is http.Handler.ServeHTTP

0 commit comments

Comments
 (0)