package main import ( "os" "log" "path" "net/url" "net/http" "io/ioutil" "strings" "encoding/json" "bytes" "html/template" ) 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 configFile := "config.json" // Reading configuration if len(os.Args) >= 2 { configFile = os.Args[1] } log.Println("Reading config", configFile) logFile, err := ioutil.ReadFile(configFile) if err != nil { panic(err) } err = json.Unmarshal(logFile, &config) if err !=nil { panic(err) } // Processing template tmpl, err = template.New("index").Parse(index) 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 { // TODO: Support directories if file.IsDir() { continue } if !strings.HasSuffix(strings.ToLower(file.Name()), 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, Directory: false, } } 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 !strings.HasSuffix(strings.ToLower(urlFile), strings.ToLower(configDir.Extension)) { handleError(w,r, err, "File does not exist", http.StatusNotFound) return } q := r.URL.Query() if q.Get("tarball") == "1" { buf := new(bytes.Buffer) file, err := os.Open(servedFile) if err != nil { handleError(w, r, err, "Unable to serve file", http.StatusInternalServerError) return } err = compress(file, 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 } // If request is not tarball - send raw file to browser not as attachment. //w.Header().Set("Content-Disposition", "attachment; filename=\""+urlFile+"\"") w.Write(file) } } 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 page.Items[i].Directory = true i++ } tmpl.ExecuteTemplate(w, "index", &page) }