package fileserver import ( "crypto/sha256" "crypto/subtle" "encoding/base64" "fmt" "github.com/pquerna/otp/totp" "html/template" "io" "net/http" "os" "path/filepath" "sophuwu.site/cdn/config" "strings" ) type DirEntry struct { Name string FullName string Size int IsDir bool } func (d DirEntry) Si() string { if d.IsDir { return "" } f := float64(d.Size) i := 0 for f > 1024 { f /= 1024 i++ } s := fmt.Sprintf("%.2f", f) s = strings.TrimRight(s, ".0") return fmt.Sprintf("%s %cB", s, " KMGTPEZY"[i]) } type TemplateData struct { Path string Dirs []DirEntry Items []DirEntry } var Temp *template.Template func FillTemp(w io.Writer, path string, items []DirEntry) error { var data = TemplateData{Path: path, Dirs: []DirEntry{}, Items: []DirEntry{}} for _, item := range items { item.FullName = filepath.Join(path, item.Name) if item.IsDir { data.Dirs = append(data.Dirs, item) } else { data.Items = append(data.Items, item) } } return Temp.ExecuteTemplate(w, "index", data) } func FillUpload(w io.Writer, path string) error { return Temp.ExecuteTemplate(w, "index", map[string]string{ "Upload": path, }) } var HttpCodes = map[int]string{ 404: "Not Found", 500: "Internal Server Error", 403: "Forbidden", 401: "Unauthorized", 400: "Bad Request", 200: "OK", } func FillError(w io.Writer, err error, code int) bool { if err == nil { return false } _ = Temp.ExecuteTemplate(w, "index", map[string]string{ "Error": fmt.Sprintf("%d: %s", code, HttpCodes[code]), }) return true } func init() { Temp = template.New("index") Temp.Parse(`{{ define "index" }} {{ if .Path }} {{ .Path }} {{ else }} {{ if .Upload }} Upload {{ else }} Error {{ end }} {{ end }} {{ if .Path }}

Index of: {{ .Path }}

{{ range .Dirs }}
{{ .Name }}{{ .Si }}
{{ end }} {{ range .Items }}
{{ .Name }}{{ .Si }}
{{ end }}
{{ else }} {{ if .Error }}

{{ .Error }}

{{ else }} {{ if .Upload }}

Upload

Path:
File:
Username:
Password:
OTP:
{{ end }} {{ end }} {{ end }} {{ end }}`) } func Handler(prefix string, dir func(string) ([]byte, []DirEntry, error)) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { data, items, err := dir(strings.TrimPrefix(r.URL.Path, prefix)) if FillError(w, err, 404) { return } w.WriteHeader(http.StatusOK) if data != nil { w.Write(data) return } items = append([]DirEntry{{ Name: "../", FullName: filepath.Dir(strings.TrimSuffix(r.URL.Path, "/")), Size: 0, IsDir: true, }}, items...) err = FillTemp(w, r.URL.Path, items) if FillError(w, err, 500) { return } }) } func Handle(prefix string, dir func(string) ([]byte, []DirEntry, error)) { http.Handle(prefix, Handler(prefix, dir)) } func VerifyOtp(p, o string) bool { b, err := os.ReadFile(config.OtpPath) if err != nil { return false } s := string(b) s = strings.Split(s, "\n")[0] b, err = base64.StdEncoding.DecodeString(strings.TrimSpace(s)) if err != nil { return false } b = b[:32] bb := sha256.Sum256([]byte(p)) for i := 0; i < len(b); i++ { b[i] = b[i] ^ bb[i] } s = string(b) return totp.Validate(o, s) } func VerifyBasicAuth(u, p string) bool { b, err := os.ReadFile(config.OtpPath) if err != nil { return false } ss := strings.Split(string(b), "\n") if len(ss) < 2 { return false } b, err = base64.StdEncoding.DecodeString(strings.TrimSpace(ss[1])) if err != nil { return false } bb := sha256.Sum256([]byte(u + ";" + p)) return subtle.ConstantTimeCompare(b, bb[:]) == 1 } func Authenticate(next http.HandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, apass, authOK := r.BasicAuth() if !authOK || !VerifyBasicAuth(user, apass) { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized.", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } func UpHandler(prefix string, save func(string, []byte) error) http.Handler { return Authenticate(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { err := FillUpload(w, prefix) if FillError(w, err, 500) { return } return } if r.Method == "POST" { r.ParseMultipartForm(256 * 1024 * 1024) path := r.Form.Get("path") username := r.Form.Get("username") password := r.Form.Get("password") otp := r.Form.Get("otp") if !VerifyOtp(username+";"+password, otp) { FillError(w, fmt.Errorf("unauthorized"), 401) return } file, _, err := r.FormFile("myFile") if FillError(w, err, 400) { return } defer file.Close() data, err := io.ReadAll(file) if FillError(w, err, 500) { return } err = save(path, data) if FillError(w, err, 500) { return } http.Redirect(w, r, strings.ToLower(prefix)+path, http.StatusFound) return } FillError(w, fmt.Errorf("method not allowed"), 405) })) } func UpHandle(prefix string, save func(string, []byte) error) { http.Handle(prefix, UpHandler(prefix, save)) }