Compare commits

...

27 commits
v0.1.1 ... main

Author SHA1 Message Date
56d8733a40
add some common code file extensions as text/plain 2026-03-18 09:48:45 +00:00
d40dec3566
tidy source files
separating logging and web serving into modules
2026-03-18 09:39:02 +00:00
3c38fa2f7f
update readme 2026-03-18 09:19:52 +00:00
3b64b2b978
credit contributors in footer 2026-03-18 09:18:43 +00:00
57f31343de Merge pull request 'FEAT: directory stylesheet overrides' (#9) from supitszaire/indir:main into main
Reviewed-on: https://codeberg.org/arimelody/indir/pulls/9
2026-03-17 20:40:05 +01:00
fe05bb8a8d updated readme to reflect new features <3 2026-03-17 20:38:48 +01:00
eac93e08bb FEAT: directory stylesheet overrides 2026-03-17 20:16:28 +01:00
a690b76c42 Merge pull request 'FIX: mime-type not reflecting file extension, update README' (#7) from supitszaire/indir:main into main
Reviewed-on: https://codeberg.org/arimelody/indir/pulls/7
2026-03-17 19:18:42 +01:00
523aa894d0 update: reflect MIME-TYPE fix 2026-03-17 10:52:04 +01:00
cc05d3b2d3 Merge branch 'main' of https://codeberg.org/supitszaire/indir 2026-03-17 10:49:47 +01:00
fccba73bac FIX: mime-type returning only octet-stream 2026-03-17 10:49:09 +01:00
4ee46d8f52 updated readme to reflect additions
added section: customization>readme
added TODO & checked off things that are done
2026-03-17 00:57:20 +01:00
1cd15d51e4 Merge pull request 'FIX: no longer serving of directory named 'index.html'' (#6) from supitszaire/indir:main into main
Reviewed-on: https://codeberg.org/arimelody/indir/pulls/6
2026-03-16 22:53:25 +01:00
eb7b1cf026 platform agnostic change 2026-03-16 22:52:14 +01:00
0065861eab fixed serving of directory named 'index.html' 2026-03-16 22:44:21 +01:00
9333574de2 Merge pull request 'feat: add automatic serving of index.html in directory' (#5) from supitszaire/indir:main into main
Reviewed-on: https://codeberg.org/arimelody/indir/pulls/5
Reviewed-by: ari melody <ari@arimelody.space>
2026-03-16 22:31:03 +01:00
7014763271 fixed formatting i hope :) 2026-03-16 22:21:17 +01:00
5b11318d76 serve index.html if present in file directory 2026-03-16 22:16:00 +01:00
f567a2eb83 serve index.html when present (case-sensitve) 2026-03-16 22:05:29 +01:00
2ae702adc2
Merge pull request 'feat: embed README.md in directory listings' (#4) from supitszaire/indir:main into main
Reviewed-on: https://codeberg.org/arimelody/indir/pulls/4
Reviewed-by: ari melody <ari@arimelody.space>
2026-03-16 20:11:47 +00:00
da76681c29 removed README header in html 2026-03-16 21:08:06 +01:00
ef3dc28e7c format fix 2026-03-16 21:01:53 +01:00
73fdf8bb46 format fix 2026-03-16 21:00:51 +01:00
245d6e0fa0 fixed prior merge conflic causing double'd embed, added readme parser with full markdown, added readme render to HTML, renamed filepath to fpath to use FilePath.Join for case insensitivity, fixed ident in html src (inline css), added readme css with proper accents 2026-03-16 20:41:16 +01:00
cf03c723d0
Merge branch 'main' of forge:ari/indir 2026-03-16 18:30:16 +00:00
7aef1a8012
update old domain 2026-03-16 18:29:09 +00:00
6708d9ff33
add makefile 2025-06-08 21:40:19 +01:00
10 changed files with 524 additions and 310 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
.DS_Store .DS_Store
tmp tmp
indir indir
indir-*
.zed
index.html

28
Makefile Normal file
View file

@ -0,0 +1,28 @@
GOOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
GOARCH := $(shell uname -m)
TRIPLE := $(GOOS)-$(GOARCH)
.PHONY: build
build:
go build -o indir-$(TRIPLE) .
ln -sf ./indir-$(TRIPLE) ./indir
build-multiplatform: indir-linux-amd64 indir-darwin-arm64
indir-linux-amd64:
GOOS=linux GOARCH=amd64 go build -o ./indir-linux-amd64 .
indir-darwin-arm64:
GOOS=darwin GOARCH=arm64 go build -o ./indir-darwin-arm64 .
clean:
rm -rf \
./indir \
./indir-$(TRIPLE) \
./indir-darwin-arm64 \
./indir-linux-amd64
install: build
cp ./indir-$(TRIPLE) /usr/local/bin/indir
chmod +x /usr/local/bin/indir

View file

@ -2,7 +2,7 @@
a nifty static file indexer a nifty static file indexer
indir provides a nice, simple frontend for browsing your web directories! indir provides a nice, simple web frontend for browsing your file directories!
heavily inspired by traditional file indexers provided by the likes of nginx heavily inspired by traditional file indexers provided by the likes of nginx
and apache, with some modern amenities on top :) and apache, with some modern amenities on top :)
@ -16,7 +16,11 @@ indir [--host address] [--port port] [--root http_root] directory
--root http_root hosts on the specified subdirectory, i.e. `/files/` --root http_root hosts on the specified subdirectory, i.e. `/files/`
``` ```
## to-do: ## features
- [x] use templates instead of hard-coded HTML (i was lazy)
- [ ] directory header from readme file - **index files:** if available, indir will prefer serving an `index.html` file
- [ ] directory stylesheet overrides instead of a directory listing.
- **custom CSS:** indir will append the contents of a local `index.css` file to
the default stylesheet, if applicable.
- **readme files:** adding a `readme.md` file (case insensitive) will append
its contents beneath the directory tree in rendered markdown.

4
go.mod
View file

@ -1,3 +1,5 @@
module git.arimelody.me/ari/indir module forge.arimelody.space/ari/indir
go 1.24.3 go 1.24.3
require github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab

2
go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=

57
log/main.go Normal file
View file

@ -0,0 +1,57 @@
package log
import (
"fmt"
"net/http"
"strconv"
"time"
)
type LoggingResponseWriter struct {
http.ResponseWriter
Status int
}
var COL_Reset = "\033[0m"
var COL_Red = "\033[31m"
var COL_Green = "\033[32m"
var COL_Yellow = "\033[33m"
var COL_Blue = "\033[34m"
var COL_Purple = "\033[35m"
var COL_Cyan = "\033[36m"
var COL_Gray = "\033[37m"
var COL_White = "\033[97m"
func HTTPLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lrw := LoggingResponseWriter{w, http.StatusOK}
next.ServeHTTP(&lrw, r)
after := time.Now()
difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000
elapsed := "<1"
if difference >= 1 {
elapsed = strconv.Itoa(difference)
}
statusColour := COL_Reset
if lrw.Status - 600 <= 0 { statusColour = COL_Red }
if lrw.Status - 500 <= 0 { statusColour = COL_Yellow }
if lrw.Status - 400 <= 0 { statusColour = COL_White }
if lrw.Status - 300 <= 0 { statusColour = COL_Green }
fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
after.Format(time.UnixDate),
r.Method,
r.URL.Path,
statusColour,
lrw.Status,
COL_Reset,
elapsed,
r.Header["User-Agent"][0])
})
}

210
main.go
View file

@ -2,38 +2,15 @@ package main
import ( import (
"fmt" "fmt"
"html/template"
"io/fs"
"mime"
"net/http" "net/http"
"os" "os"
"path"
"slices"
"strconv" "strconv"
"strings" "strings"
"time"
_ "embed" "forge.arimelody.space/ari/indir/log"
"forge.arimelody.space/ari/indir/web"
) )
type (
Directory struct {
Name string
Root bool
Files []*File
}
File struct {
Name string
URI string
IsDir bool
Size string
ModifiedDate string
}
)
//go:embed templates/dir.html
var dirHTML string
func main() { func main() {
if len(os.Args) < 2 { printHelp() } if len(os.Args) < 2 { printHelp() }
@ -101,191 +78,30 @@ func main() {
".DS_Store", ".DS_Store",
} }
dirTemplate, err := template.New("dir").Parse(dirHTML)
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: failed to parse directory template: %v\n", err)
os.Exit(1)
}
fmt.Printf("Now hosting \"%s\" at http://%s:%d", filesDir, host, port) fmt.Printf("Now hosting \"%s\" at http://%s:%d", filesDir, host, port)
if root != "/" { fmt.Printf("%s", root) } if root != "/" { fmt.Printf("%s", root) }
fmt.Println(".") fmt.Println(".")
http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), HTTPLog(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { app := web.AppState{
if !strings.HasPrefix(r.URL.Path, root) { Root: root,
http.NotFound(w, r) FilesDir: filesDir,
return IgnoredFiles: ignoredFiles,
} }
isRoot := r.URL.Path == root
filepath := path.Join(filesDir, strings.TrimPrefix(r.URL.Path, root)) http.ListenAndServe(
info, err := os.Stat(filepath) fmt.Sprintf("%s:%d", host, port),
if err != nil { log.HTTPLog(web.Handler(&app)),
http.NotFound(w, r) )
return
}
// downloading file
if !info.IsDir() {
if strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusFound)
return
}
file, err := os.Open(filepath)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
defer func() {
err := file.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to close file %s: %v\n", filepath, err)
}
}()
mimeType := "application/octet-stream"
extPos := strings.LastIndex(info.Name(), ".")
if extPos != -1 {
mimeType = mime.TypeByExtension(info.Name()[:extPos])
}
w.Header().Set("Content-Type", mimeType)
w.WriteHeader(http.StatusOK)
_, err = file.WriteTo(w)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send file %s: %v\n", filepath, err)
}
return
}
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.Path + "/", http.StatusFound)
return
}
data := Directory{
Root: isRoot,
Name: r.URL.Path,
Files: []*File{},
}
fsDir := os.DirFS(filepath)
directories, err := fs.ReadDir(fsDir, ".")
for _, dir := range directories {
name := dir.Name()
if slices.Contains(ignoredFiles, name) { continue }
info, err := dir.Info()
if err != nil { continue }
var uri string
if isRoot {
uri = root + name
} else {
uri = r.URL.Path + name
}
sizeStr := "&mdash;"
if !info.IsDir() {
size := info.Size()
sizeDenom := "B"
if size > 1000 {
size /= 1000
sizeDenom = "KB"
}
if size > 1000 {
size /= 1000
sizeDenom = "MB"
}
if size > 1000 {
size /= 1000
sizeDenom = "GB"
}
sizeStr = fmt.Sprintf("%d%s", size, sizeDenom)
}
dateStr := info.ModTime().Format("02-Jan-2006 15:04")
data.Files = append(data.Files, &File{
Name: name,
URI: uri,
IsDir: info.IsDir(),
Size: sizeStr,
ModifiedDate: dateStr,
})
}
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open directory: %v\n", err)
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Server", "indir")
w.WriteHeader(http.StatusOK)
dirTemplate.Execute(w, data)
})))
}
type LoggingResponseWriter struct {
http.ResponseWriter
Status int
}
var COL_Reset = "\033[0m"
var COL_Red = "\033[31m"
var COL_Green = "\033[32m"
var COL_Yellow = "\033[33m"
var COL_Blue = "\033[34m"
var COL_Purple = "\033[35m"
var COL_Cyan = "\033[36m"
var COL_Gray = "\033[37m"
var COL_White = "\033[97m"
func HTTPLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lrw := LoggingResponseWriter{w, http.StatusOK}
next.ServeHTTP(&lrw, r)
after := time.Now()
difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000
elapsed := "<1"
if difference >= 1 {
elapsed = strconv.Itoa(difference)
}
statusColour := COL_Reset
if lrw.Status - 600 <= 0 { statusColour = COL_Red }
if lrw.Status - 500 <= 0 { statusColour = COL_Yellow }
if lrw.Status - 400 <= 0 { statusColour = COL_White }
if lrw.Status - 300 <= 0 { statusColour = COL_Green }
fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
after.Format(time.UnixDate),
r.Method,
r.URL.Path,
statusColour,
lrw.Status,
COL_Reset,
elapsed,
r.Header["User-Agent"][0])
})
} }
func printHelp() { func printHelp() {
fmt.Printf( fmt.Printf(
`%s [--host address] [--port port] [--root http_root] directory `indir [--host address] [--port port] [--root http_root] directory
--help shows this help message --help shows this help message
--host address hosts on the specified address --host address hosts on the specified address
--port port hosts on the specified port --port port hosts on the specified port
--root http_root hosts on the specified subdirectory, i.e. `+"`/files/`\n", --root http_root hosts on the specified subdirectory, i.e. `+"`/files/`\n",
os.Args[0],
) )
os.Exit(0) os.Exit(0)
} }

View file

@ -1,107 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Files in {{.Name}}</title>
<style>
html {
background: #101010;
color: #f0f0f0;
font-family: 'Monaspace Argon', monospace;
font-size: 16px;
}
body {
width: min(calc(100% - 1em), 1000px);
margin: 0 auto;
}
table {
width: 100%;
border-collapse: collapse;
}
tr:hover {
background-color: #80808040;
}
th {
text-align: left;
}
td {
width: 1%;
max-width: 500px;
padding: .2em 0;
}
table a {
display: block;
line-break: anywhere;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
a {
color: #b7fd49;
text-decoration: none;
}
a:hover {
color: white;
text-decoration: underline;
}
footer {
padding: 1em 0;
}
@media screen and (max-width: 700px) {
body {
font-size: 12px;
}
td {
width: auto;
}
td:last-of-type,
th:last-of-type {
display: none;
}
}
</style>
</head>
<body>
<main>
<h1>Files in {{.Name}}</h1>
<hr>
<table>
<tr>
<th>Name</th>
<th>Size</th>
<th>Modified</th>
</tr>
{{if not .Root}}
<tr>
<td><a href="./..">../</a></td>
<td>&mdash;</td>
<td>&mdash;</td>
</tr>
{{end}}
{{range .Files}}
<tr>
<td><a href="{{.URI}}">{{.Name}}</a></td>
<td>{{if .IsDir}}&mdash;{{else}}{{.Size}}{{end}}</td>
<td>{{.ModifiedDate}}</td>
</tr>
{{end}}
</table>
<hr>
</main>
<footer>
<em>made with <span aria-label="love"></span> by ari, 2025 <a href="https://git.arimelody.me/ari/indir" target="_blank">[source]</a></em>
</footer>
</body>
</html>

191
web/dir.html Normal file
View file

@ -0,0 +1,191 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Files in {{.Name}}</title>
<style>
html {
background: #101010;
color: #f0f0f0;
font-family: "Monaspace Argon", monospace;
font-size: 16px;
}
body {
width: min(calc(100% - 1em), 1000px);
margin: 0 auto;
}
table {
width: 100%;
border-collapse: collapse;
}
tr:hover {
background-color: #80808040;
}
th {
text-align: left;
}
td {
width: 1%;
max-width: 500px;
padding: 0.2em 0;
}
table a {
display: block;
line-break: anywhere;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
a {
color: #b7fd49;
text-decoration: none;
}
a:hover {
color: white;
text-decoration: underline;
}
.readme {
margin-top: 2rem;
}
.readme h1, .readme h2, .readme h3,
.readme h4, .readme h5, .readme h6 {
color: #f0f0f0;
margin-top: 1.2rem;
}
.readme p, .readme li {
line-height: 1.7;
color: #d0d0d0;
}
.readme a {
color: #b7fd49;
}
.readme a:hover {
color: white;
text-decoration: underline;
}
.readme code {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 3px;
padding: .1em, .4em;
font-family: 'Monaspace Argon', monospace;
font-size: .9em;
color: #b7fd79;
}
.readme pre {
background: #1e1e1e;
border: 3px solid #333;
border-radius: 6px;
padding: 1em;
overflow-x: auto;
}
.readme pre code {
border: none;
padding: 0;
background: transparent;
}
.readme blockquote {
border-left: 3px solid #b7fd49;
margin: 0;
padding-left: 1em;
color: #a0a0a0;
}
.readme hr {
border-color: #333;
}
.readme table {
width: auto;
}
.readme table td,
.readme table th {
border: 1px solid #333;
padding: .3em .6em;
width: auto;
}
footer {
padding: 1em 0;
}
@media screen and (max-width: 700px) {
body {
font-size: 12px;
}
td {
width: auto;
}
td:last-of-type,
th:last-of-type {
display: none;
}
}
</style>
{{if .CSS}}
<style>{{.CSS}}</style>
{{end}}
</head>
<body>
<main>
<h1>Files in {{.Name}}</h1>
<hr />
<table>
<tr>
<th>Name</th>
<th>Size</th>
<th>Modified</th>
</tr>
{{if not .Root}}
<tr>
<td><a href="./..">../</a></td>
<td>&mdash;</td>
<td>&mdash;</td>
</tr>
{{end}} {{range .Files}}
<tr>
<td><a href="{{.URI}}">{{.Name}}</a></td>
<td>{{if .IsDir}}&mdash;{{else}}{{.Size}}{{end}}</td>
<td>{{.ModifiedDate}}</td>
</tr>
{{end}}
</table>
<hr />
{{if .Readme}}
<article class="readme">
{{.Readme}}
</article>
{{end}}
</main>
<footer>
<em
>made with <span aria-label="love"></span> by ari melody and contributors, 2026
<a
href="https://codeberg.org/arimelody/indir"
target="_blank"
>[source]</a
></em
>
</footer>
</body>
</html>

218
web/main.go Normal file
View file

@ -0,0 +1,218 @@
package web
import (
_ "embed"
"fmt"
"html/template"
"io/fs"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"slices"
"strings"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
type (
AppState struct {
Root string
FilesDir string
IgnoredFiles []string
}
Directory struct {
Name string
Root bool
Files []*File
Readme template.HTML
CSS template.CSS
}
File struct {
Name string
URI string
IsDir bool
Size string
ModifiedDate string
}
)
var mimeTypes = map[string]string {
".go": "text/plain",
".rs": "text/plain",
".c": "text/plain",
".h": "text/plain",
".cpp": "text/plain",
".hpp": "text/plain",
".java": "text/plain",
}
//go:embed dir.html
var dirTemplateSrc string
var dirTemplate = template.Must(template.New("dir").Parse(dirTemplateSrc))
func Handler(app *AppState) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, app.Root) {
http.NotFound(w, r)
return
}
isRoot := r.URL.Path == app.Root
fpath := path.Join(app.FilesDir, strings.TrimPrefix(r.URL.Path, app.Root))
info, err := os.Stat(fpath)
if err != nil {
http.NotFound(w, r)
return
}
// downloading file
if !info.IsDir() {
if strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusFound)
return
}
file, err := os.Open(fpath)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
defer func() {
err := file.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to close file %s: %v\n", fpath, err)
}
}()
mimeType := "application/octet-stream"
extPos := strings.LastIndex(info.Name(), ".")
ext := string(info.Name()[strings.LastIndex(info.Name(), "."):])
fmt.Println(ext)
if extPos != -1 {
if m := mime.TypeByExtension(ext); m != "" {
mimeType = m
} else if m, ok := mimeTypes[ext]; ok {
mimeType = m
}
}
w.Header().Set("Content-Type", mimeType)
w.WriteHeader(http.StatusOK)
_, err = file.WriteTo(w)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send file %s: %v\n", fpath, err)
}
return
}
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.Path + "/", http.StatusFound)
return
}
// serve index.html if present (case-sensitive)
indexPath := filepath.Join(fpath, "index.html")
if indexInfo, err := os.Stat(indexPath); err == nil && !indexInfo.IsDir() {
http.ServeFile(w, r, indexPath)
return
}
// load index.css if present
var customCSS template.CSS
cssPath := filepath.Join(fpath, "index.css")
if cssInfo, err := os.Stat(cssPath); err == nil && !cssInfo.IsDir() {
if src, err := os.ReadFile(cssPath); err == nil {
customCSS = template.CSS(src)
}
}
// embeded readme
var readmeHTML template.HTML
entries, err := os.ReadDir(fpath)
if err == nil {
for _, entry := range entries {
if strings.EqualFold(entry.Name(), "readme.md") {
src, err := os.ReadFile(filepath.Join(fpath, entry.Name()))
if err == nil {
mdFlags := html.CommonFlags | html.HrefTargetBlank
mdRenderer := html.NewRenderer(html.RendererOptions{Flags: mdFlags})
mdParser := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs)
md := mdParser.Parse(src)
readmeHTML = template.HTML(markdown.Render(md, mdRenderer))
}
break
}
}
}
data := Directory{
Root: isRoot,
Name: r.URL.Path,
Files: []*File{},
Readme: readmeHTML,
CSS: customCSS,
}
fsDir := os.DirFS(fpath)
directories, err := fs.ReadDir(fsDir, ".")
for _, dir := range directories {
name := dir.Name()
if slices.Contains(app.IgnoredFiles, name) { continue }
info, err := dir.Info()
if err != nil { continue }
var uri string
if isRoot {
uri = app.Root + name
} else {
uri = r.URL.Path + name
}
sizeStr := "&mdash;"
if !info.IsDir() {
size := info.Size()
sizeDenom := "B"
if size > 1000 {
size /= 1000
sizeDenom = "KB"
}
if size > 1000 {
size /= 1000
sizeDenom = "MB"
}
if size > 1000 {
size /= 1000
sizeDenom = "GB"
}
sizeStr = fmt.Sprintf("%d%s", size, sizeDenom)
}
dateStr := info.ModTime().Format("02-Jan-2006 15:04")
data.Files = append(data.Files, &File{
Name: name,
URI: uri,
IsDir: info.IsDir(),
Size: sizeStr,
ModifiedDate: dateStr,
})
}
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open directory: %v\n", err)
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Server", "indir")
w.WriteHeader(http.StatusOK)
dirTemplate.Execute(w, data)
}
}