Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c5f23d3ac | ||
|
|
c6109e23af | ||
|
|
6ae09962ad | ||
|
|
17e3e21e56 | ||
|
|
334b64ca6d | ||
|
|
9dc505af17 | ||
|
|
0c36391bb2 | ||
|
|
7c67df0f1d |
214
main.go
214
main.go
@@ -9,15 +9,32 @@ listing file.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"local/args"
|
||||
"local/gziphttp"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ENDPOINT_UPLOAD = "__upload__"
|
||||
ENDPOINT_DELETE = "__delete__"
|
||||
)
|
||||
|
||||
var (
|
||||
fs *args.ArgSet
|
||||
)
|
||||
|
||||
func main() {
|
||||
fs := args.NewArgSet()
|
||||
fs = args.NewArgSet()
|
||||
fs.Append(args.STRING, "p", "port to serve", "8100")
|
||||
fs.Append(args.STRING, "d", "static path to serve", "./public")
|
||||
if err := fs.Parse(); err != nil {
|
||||
@@ -34,14 +51,203 @@ func main() {
|
||||
log.Fatal(http.ListenAndServe(":"+p, nil))
|
||||
}
|
||||
|
||||
func handler(d string) func(http.ResponseWriter, *http.Request) {
|
||||
h := http.FileServer(http.Dir(d))
|
||||
func handler(d string) http.HandlerFunc {
|
||||
return gzip(endpoints(withDel(fserve(d))))
|
||||
}
|
||||
|
||||
func writeMeta(w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, `
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
`)
|
||||
}
|
||||
|
||||
func writeForm(w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, `
|
||||
<form enctype="multipart/form-data" action="./%s" method="post">
|
||||
<input type="file" name="file" required/>
|
||||
<input type="submit" value="upload"/>
|
||||
</form>
|
||||
`, ENDPOINT_UPLOAD)
|
||||
}
|
||||
|
||||
func gzip(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if gziphttp.Can(r) {
|
||||
gz := gziphttp.New(w)
|
||||
defer gz.Close()
|
||||
w = gz
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func endpoints(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if isDir(r) {
|
||||
writeMeta(w)
|
||||
writeForm(w)
|
||||
}
|
||||
if isUploaded(r) {
|
||||
if err := upload(w, r); err != nil {
|
||||
fmt.Fprintln(w, err.Error())
|
||||
}
|
||||
} else if isDeleted(r) {
|
||||
if err := del(w, r); err != nil {
|
||||
fmt.Fprintln(w, err.Error())
|
||||
}
|
||||
} else {
|
||||
setContentTypeIfMedia(w, r)
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isUploaded(r *http.Request) bool {
|
||||
return path.Base(r.URL.Path) == ENDPOINT_UPLOAD
|
||||
}
|
||||
|
||||
func isDeleted(r *http.Request) bool {
|
||||
return path.Base(r.URL.Path) == ENDPOINT_DELETE
|
||||
}
|
||||
|
||||
func isDir(r *http.Request) bool {
|
||||
d := toRealPath(r.URL.Path)
|
||||
fi, err := os.Stat(d)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return false
|
||||
}
|
||||
if _, err := os.Stat(path.Join(d, "index.html")); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func withDel(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !isDir(r) {
|
||||
foo(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w, `<a href=".."><input type="button" style="padding: .15em 4em .35em 4em" value=".."/></a>`+"\n")
|
||||
w2 := httptest.NewRecorder()
|
||||
foo(w2, r)
|
||||
b := bytes.Split(w2.Body.Bytes(), []byte("\n"))
|
||||
buff := bytes.NewBuffer(nil)
|
||||
for i := range b {
|
||||
if bytes.Contains(b[i], []byte("<a href=")) {
|
||||
re := regexp.MustCompile(`href="[^"]*"`)
|
||||
match := re.Find(b[i])
|
||||
if len(match) > 0 {
|
||||
match = bytes.Split(match, []byte(`href="`))[1]
|
||||
match = match[:len(match)-1]
|
||||
b[i] = []byte(fmt.Sprintf(`<a href="%s/%s"><input type="button" value="❌" style="padding: .40em 1em .10em 1em; margin-right: .5em" onclick='return confirm("Delete "+%q+"?");'></input></a> %s`, match, ENDPOINT_DELETE, match, b[i]))
|
||||
}
|
||||
}
|
||||
buff.Write(b[i])
|
||||
buff.Write([]byte("\n"))
|
||||
}
|
||||
io.Copy(w, buff)
|
||||
}
|
||||
}
|
||||
|
||||
func fserve(d string) http.HandlerFunc {
|
||||
h := http.FileServer(http.Dir(d))
|
||||
return h.ServeHTTP
|
||||
}
|
||||
|
||||
func upload(w http.ResponseWriter, r *http.Request) error {
|
||||
r.ParseMultipartForm(100 << 20)
|
||||
file, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
p := toRealPath(path.Join(path.Dir(r.URL.Path), handler.Filename))
|
||||
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
|
||||
return errors.New("already exists")
|
||||
}
|
||||
f, err := os.Create(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, file); err != nil {
|
||||
return err
|
||||
}
|
||||
http.Redirect(w, r, path.Dir(r.URL.Path)+"/", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func del(w http.ResponseWriter, r *http.Request) error {
|
||||
p := toRealPath(path.Dir(r.URL.Path))
|
||||
_, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.RemoveAll(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.Redirect(w, r, path.Dir(path.Dir(r.URL.Path))+"/", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func toRealPath(p string) string {
|
||||
d := path.Join(fs.Get("d").GetString())
|
||||
return path.Join(d, p)
|
||||
}
|
||||
|
||||
func setContentTypeIfMedia(w http.ResponseWriter, r *http.Request) {
|
||||
ext := strings.ToLower(path.Ext(r.URL.Path))
|
||||
if i := strings.LastIndex(ext, "."); i != -1 {
|
||||
ext = ext[i:]
|
||||
}
|
||||
switch ext {
|
||||
case ".mp4":
|
||||
w.Header().Set("Content-Type", "video/mp4")
|
||||
case ".mkv":
|
||||
w.Header().Set("Content-Type", "video/x-matroska")
|
||||
case ".mp3":
|
||||
w.Header().Set("Content-Type", "audio/mpeg3")
|
||||
case ".epub", ".mobi":
|
||||
w.Header().Set("Content-Disposition", "attachment")
|
||||
case ".jpg", ".jpeg":
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
case ".gif":
|
||||
w.Header().Set("Content-Type", "image/gif")
|
||||
case ".png":
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
case ".ico":
|
||||
w.Header().Set("Content-Type", "image/x-icon")
|
||||
case ".svg":
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
case ".css":
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
case ".js":
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
case ".json":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
case ".html", ".htm":
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
case ".pdf":
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
case ".webm":
|
||||
w.Header().Set("Content-Type", "video/webm")
|
||||
case ".weba":
|
||||
w.Header().Set("Content-Type", "audio/webm")
|
||||
case ".webp":
|
||||
w.Header().Set("Content-Type", "image/webp")
|
||||
case ".zip":
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
case ".7z":
|
||||
w.Header().Set("Content-Type", "application/x-7z-compressed")
|
||||
case ".tar":
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
}
|
||||
}
|
||||
|
||||
34
main_test.go
Normal file
34
main_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetContentType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]struct {
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
"css with multi .": {
|
||||
path: "/static/css/main.2145ce41.chunk.css",
|
||||
want: "text/css",
|
||||
},
|
||||
}
|
||||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := &http.Request{URL: &url.URL{Path: c.path}}
|
||||
w := httptest.NewRecorder()
|
||||
setContentTypeIfMedia(w, r)
|
||||
if ct := w.Header().Get("Content-Type"); ct != c.want {
|
||||
t.Errorf("wrong content type: want %q, got %q", c.want, ct)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
0
public/DIR2/e.md
Executable file
0
public/DIR2/e.md
Executable file
@@ -1 +0,0 @@
|
||||
hi
|
||||
0
public/b.md
Executable file
0
public/b.md
Executable file
Reference in New Issue
Block a user