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) }