package http import ( "fmt" "html/template" "io/ioutil" "net/http" "strings" "git.darknebu.la/emile/faila/src/structs" "github.com/gorilla/mux" "github.com/sirupsen/logrus" "github.com/spf13/viper" ) // Server defines and runs an HTTP server func Server() { r := mux.NewRouter() // static js / css hosting assets := r.PathPrefix("/assets/").Subrouter() fs := http.FileServer(http.Dir("./hosted/assets")) assets.PathPrefix("/").Handler(http.StripPrefix("/assets/", fs)) r.HandleFunc("/download", downloadHandler).Methods("GET") t := r.PathPrefix("/").Subrouter() t.PathPrefix("/").HandlerFunc(pathHandler) // get the ip and port from the config bindIP := viper.GetString("server.bindip") listenPort := viper.GetString("server.listenport") // define the http server httpServer := http.Server{ Addr: fmt.Sprintf("%s:%s", bindIP, listenPort), Handler: r, } logrus.Warnf("HTTP server defined listening on %s:%s", bindIP, listenPort) logrus.Fatal(httpServer.ListenAndServe()) } func downloadHandler(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() file := query["file"][0] root := viper.GetString("server.root") logrus.Info(root) strippedFile := strings.Replace(file, root, "", -1) strippedFile = strings.Replace(strippedFile, "..", "", -1) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", strippedFile)) w.Header().Set("Content-Type", r.Header.Get("Content-Type")) actualFile := fmt.Sprintf("%s%s", root, strippedFile) logrus.Infof("serving: %s", actualFile) http.ServeFile(w, r, actualFile) } func pathHandler(w http.ResponseWriter, r *http.Request) { var content map[string]interface{} content = make(map[string]interface{}) root := viper.GetString("server.root") requestURI := fmt.Sprintf("%s%s", root, r.RequestURI) logrus.Tracef("root: %s", root) logrus.Tracef("requestURI: %s", root) query := r.URL.Query() if query["download"] != nil { // strip the file before and after the request strippedFile := strings.Replace(requestURI, root, "", -1) strippedFile = strings.Replace(strippedFile, "?download", "", -1) path := fmt.Sprintf("/download?file=%s", strippedFile) http.Redirect(w, r, path, http.StatusSeeOther) return } breadcrumbsList := breadcrumbs(r) content["Breadcrumbs"] = breadcrumbsList // get all files in the request dir files, err := ioutil.ReadDir(requestURI) if err != nil { logrus.Warn(err) return } // define the items (files and dirs) var items structs.Items var dirCount int = 0 var fileCount int = 0 for _, f := range files { if filterIsValid(f.Name()) == false { continue } // get the file or dirs modtime and format it in a readable way as // described in the config modTime := f.ModTime() if viper.GetString("time.format") == "" { logrus.Fatalf("Please insert a format for the time in the config (time.format), see the README for more information.") } humanModTime := modTime.Format(viper.GetString("time.format")) // define the file or dir's url var url string if r.RequestURI != "/" { url = fmt.Sprintf("%s/%s", r.RequestURI, f.Name()) } else { url = fmt.Sprintf("/%s", f.Name()) } // define the file or dir item := structs.Item{ Name: f.Name(), HumanSize: f.Size(), URL: url, HumanModTime: humanModTime, IsSymlink: false, Size: "0", } // if it is a dir, say so if f.IsDir() == true { item.IsDir = true dirCount++ } else { item.Download = true fileCount++ } items = append(items, item) } // add the items to the content map content["Items"] = items // ad the file and dir count to the contents map content["NumDirs"] = dirCount content["NumFiles"] = fileCount logrus.Tracef("") logrus.Tracef("numDirs: %d", dirCount) logrus.Tracef("numFiles: %d", fileCount) // if there are more than one breadcrumb, define the uppath as the second // last breadcrumb // I did this, because somehow things broke when simply using ".." in // combination with hidden folders if len(breadcrumbsList) > 1 { content["UpPath"] = breadcrumbsList[len(breadcrumbsList)-2].Link } else { content["UpPath"] = ".." } // In the caddy content["ItemsLimitedTo"] = 100000000000 // define the sort order manually // TODO: handle this correctly content["Sort"] = "namedirfirst" content["Order"] = "desc" // Set the site's title to the title defined in the config content["SiteTitle"] = viper.GetString("server.name") // if we're not at the root, we can still go futher down if r.RequestURI != "/" { content["CanGoUp"] = "true" logrus.Tracef("can go up") } // define a new template to render the challenges in t := template.New("") t, err = t.ParseGlob("./hosted/tmpl/*.html") if err != nil { logrus.Warn(err) return } err = t.ExecuteTemplate(w, "index", content) logrus.Warn(err) return } // breadcrumbs get's the breadcrumbs from the request func breadcrumbs(r *http.Request) structs.Breadcrumbs { request := r.RequestURI // mitigate path traversals strippedRequest := strings.Replace(request, "..", "", -1) if request != "/" { strippedRequest = strings.TrimRight(strippedRequest, "/") } // continue without the first slash, as it produces an unused field that has // no use crumbs := strings.Split(strippedRequest[1:], "/") // build the breadcrumbs from the split RequestURI var breadcrumbs structs.Breadcrumbs for i, crumb := range crumbs { text := crumb // the link is defined as the text until the given crumb link := strings.Join(crumbs[:i+1], "/") resultCrumb := structs.Crumb{ Text: text, Link: fmt.Sprintf("/%s", link), } breadcrumbs = append(breadcrumbs, resultCrumb) } return breadcrumbs } // filter filters files returning true if they should be displayed and false if // not. The descision is made using the hide config from the config file func filterIsValid(name string) bool { // hide files starting with a dot if the "hide.file" directive is set if viper.GetBool("hide.files") == true { if name[0] == '.' { return false } } extensions := viper.GetStringSlice("hide.extensions") for _, extension := range extensions { if strings.HasSuffix(name, extension) { return false } } return true }