|
|
@@ -0,0 +1,256 @@
|
|
|
+package main
|
|
|
+
|
|
|
+
|
|
|
+import (
|
|
|
+ "os"
|
|
|
+ "log"
|
|
|
+ "path"
|
|
|
+ "net/url"
|
|
|
+ "net/http"
|
|
|
+ "io/ioutil"
|
|
|
+ "strings"
|
|
|
+ "encoding/json"
|
|
|
+ "path/filepath"
|
|
|
+
|
|
|
+ "compress/gzip"
|
|
|
+ "bytes"
|
|
|
+ "archive/tar"
|
|
|
+ "io"
|
|
|
+ "time"
|
|
|
+ "math"
|
|
|
+ "fmt"
|
|
|
+ "html/template"
|
|
|
+)
|
|
|
+
|
|
|
+type Item struct {
|
|
|
+ Name string
|
|
|
+ Size int64
|
|
|
+ URL string
|
|
|
+ Date time.Time
|
|
|
+ Valid bool
|
|
|
+}
|
|
|
+
|
|
|
+func (i Item) FDate() string {
|
|
|
+ if i.Date.IsZero() {
|
|
|
+ return "-"
|
|
|
+ }
|
|
|
+ return i.Date.Format(time.RFC822)
|
|
|
+}
|
|
|
+
|
|
|
+func (i Item) FSize() string {
|
|
|
+ if i.Size == 0 {
|
|
|
+ return "-"
|
|
|
+ }
|
|
|
+ if i.Size < 1024 {
|
|
|
+ return fmt.Sprintf("%d B", i.Size)
|
|
|
+ }
|
|
|
+ exp := int(math.Log(float64(i.Size)) / math.Log(1024))
|
|
|
+ pre := string("KMGTPE"[(exp-1)])
|
|
|
+ fsize := float64(i.Size) / math.Pow(1024, float64(exp))
|
|
|
+ return fmt.Sprintf("%.1f %siB", fsize, pre)
|
|
|
+}
|
|
|
+
|
|
|
+type Page struct {
|
|
|
+ Title string
|
|
|
+ Items []Item
|
|
|
+ Error int
|
|
|
+}
|
|
|
+
|
|
|
+type Config struct {
|
|
|
+ Directories map[string]Directory `json:"data"`
|
|
|
+ Address string `json:"address"`
|
|
|
+}
|
|
|
+
|
|
|
+type Directory struct {
|
|
|
+ Path string `json:"path"`
|
|
|
+ Extension string `json:"ext"`
|
|
|
+ Compress bool `json:"compress"`
|
|
|
+}
|
|
|
+var config = Config{}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ defer func() {
|
|
|
+ if r := recover(); r != nil {
|
|
|
+ log.Fatalln("Exiting due to error:", r)
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ var err error
|
|
|
+
|
|
|
+ // Processing template
|
|
|
+ tmpl, err = template.New("index").Parse(index)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Reading configuration
|
|
|
+ logFile, err := ioutil.ReadFile("config.json")
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+ err = json.Unmarshal(logFile, &config)
|
|
|
+ if err !=nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Serving HTTP
|
|
|
+ http.HandleFunc("/", router)
|
|
|
+
|
|
|
+ err = http.ListenAndServe(config.Address, nil)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+ log.Println("Listening on", config.Address)
|
|
|
+}
|
|
|
+
|
|
|
+func router(w http.ResponseWriter, r *http.Request) {
|
|
|
+ defer func() {
|
|
|
+ if rec := recover(); rec != nil {
|
|
|
+ handleError(w, r, rec, "Internal Server Error", http.StatusInternalServerError)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ if r.URL.Path == "/" {
|
|
|
+ serveIndex(w, r)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ location, _ := path.Split(r.URL.Path)
|
|
|
+ if location == "/" {
|
|
|
+ serveDirectory(w, r)
|
|
|
+ } else {
|
|
|
+ serveFile(w, r)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func handleError(w http.ResponseWriter, r *http.Request, err interface{}, message string, status int) {
|
|
|
+ log.Println("Request error [", r.URL.Path, "]:", message + ":", err)
|
|
|
+ w.WriteHeader(status)
|
|
|
+ tmpl.ExecuteTemplate(w, "index", &Page{Title: message, Error: status})
|
|
|
+}
|
|
|
+
|
|
|
+func serveDirectory(w http.ResponseWriter, r *http.Request) {
|
|
|
+ var page Page
|
|
|
+
|
|
|
+ urlPath, err := url.QueryUnescape(r.URL.Path)
|
|
|
+ if err !=nil {
|
|
|
+ handleError(w,r, nil, "Invalid URL", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ urlPath = strings.TrimLeft(urlPath, "/")
|
|
|
+
|
|
|
+ configDir, exists := config.Directories[urlPath]
|
|
|
+ if !exists {
|
|
|
+ handleError(w,r, nil, "Directory do not exist", http.StatusNotFound)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ files, err := ioutil.ReadDir(configDir.Path)
|
|
|
+ if err != nil {
|
|
|
+ handleError(w,r, err, "Unable to read server directory", http.StatusForbidden)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ page = Page{Title: urlPath, Items: make([]Item, len(files))}
|
|
|
+ for i, file := range files {
|
|
|
+ if file.IsDir() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ext := strings.TrimLeft(filepath.Ext(file.Name()), ".")
|
|
|
+ if strings.ToLower(ext) != strings.ToLower(configDir.Extension) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ page.Items[i] = Item{
|
|
|
+ Name: file.Name(),
|
|
|
+ URL: url.QueryEscape(urlPath + "/" + file.Name()),
|
|
|
+ Size: file.Size(),
|
|
|
+ Date: file.ModTime(),
|
|
|
+ Valid: true,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ tmpl.ExecuteTemplate(w, "index", &page)
|
|
|
+}
|
|
|
+
|
|
|
+func serveFile(w http.ResponseWriter, r *http.Request) {
|
|
|
+ urlPath, err := url.QueryUnescape(r.URL.Path)
|
|
|
+ if err !=nil {
|
|
|
+ handleError(w,r, nil, "Invalid URL", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ urlPath = strings.TrimLeft(urlPath, "/")
|
|
|
+
|
|
|
+ location, urlFile := path.Split(urlPath)
|
|
|
+ configDir, exists := config.Directories[strings.TrimRight(location, "/")]
|
|
|
+ if !exists {
|
|
|
+ handleError(w,r, nil, "Directory do not exist", http.StatusNotFound)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ servedFile := path.Join(configDir.Path, urlFile)
|
|
|
+ if _, err := os.Stat(servedFile); os.IsNotExist(err) {
|
|
|
+ handleError(w,r, err, "File does not exist", http.StatusNotFound)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if configDir.Compress {
|
|
|
+ buf := new(bytes.Buffer)
|
|
|
+ err = compress(servedFile, buf)
|
|
|
+ if err != nil {
|
|
|
+ handleError(w, r, err, "Unable to compress file", http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ w.Header().Set("Content-Disposition", "attachment; filename=\""+urlFile+".tar.gz\"")
|
|
|
+ w.Write(buf.Bytes())
|
|
|
+ } else {
|
|
|
+ file, err := ioutil.ReadFile(servedFile)
|
|
|
+ if err != nil {
|
|
|
+ handleError(w, r, err, "Unable to read file", http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ w.Header().Set("Content-Disposition", "attachment; filename=\""+urlFile+"\"")
|
|
|
+ w.Write(file)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func compress(filePath string, buf *bytes.Buffer) error {
|
|
|
+ gw := gzip.NewWriter(buf)
|
|
|
+ defer gw.Close()
|
|
|
+ tw := tar.NewWriter(gw)
|
|
|
+ defer tw.Close()
|
|
|
+
|
|
|
+ file, err := os.Open(filePath)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+ if stat, err := file.Stat(); err == nil {
|
|
|
+ header := new(tar.Header)
|
|
|
+ header.Name = path.Base(filePath)
|
|
|
+ header.Size = stat.Size()
|
|
|
+ header.Mode = int64(stat.Mode())
|
|
|
+ header.ModTime = stat.ModTime()
|
|
|
+ if err := tw.WriteHeader(header); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if _, err := io.Copy(tw, file); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func serveIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
+ page := Page{Title: "Index", Items: make([]Item, len(config.Directories))}
|
|
|
+ i := 0
|
|
|
+ for name, dir := range config.Directories {
|
|
|
+ stat, err := os.Stat(dir.Path)
|
|
|
+ if err == nil {
|
|
|
+ page.Items[i].Date = stat.ModTime()
|
|
|
+ }
|
|
|
+
|
|
|
+ page.Items[i].Name = name
|
|
|
+ page.Items[i].URL = url.QueryEscape(name)
|
|
|
+ page.Items[i].Valid = true
|
|
|
+ i++
|
|
|
+ }
|
|
|
+ tmpl.ExecuteTemplate(w, "index", &page)
|
|
|
+
|
|
|
+}
|