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
}