package main import ( "fmt" "log" "net/http" "os" "io" "html/template" "sync" "flag" "path" "net/url" "errors" "strconv" "strings" "davy/config" ) var tmpl *template.Template; var tmplXML *template.Template; func verifyEmptyBody( w http.ResponseWriter, r *http.Request) error { // 8.4 RFC4918 - Must return 415 (Unsupported Media Type) if body and should not. if r.Body != nil { p := make([]byte, 1) _,er := r.Body.Read(p) if er == nil { r.Body.Close() w.WriteHeader(http.StatusUnsupportedMediaType) return errors.New("Body not empty") } } return nil } func encodeURL( urlStr string ) string { url, err := url.Parse("http://localhost" + urlStr) if err == nil { return url.RequestURI() } return urlStr } func normaliseName( name string ) string { if len(name) > 22 { name = name[:20] + "..." } return name } func normaliseSize( size int64) string { unit := 0 nb := float64(size) for nb > 1023. { unit++ nb /= 1024. } unitStr := " KMGTPEZY" if unit > 0 { return fmt.Sprintf("%.2f%cio", nb, unitStr[unit]); } return fmt.Sprintf("%do", size); } func hasHidden( path string ) bool { if path == "." { return false } for _, sub_node := range strings.Split(path, "/") { if strings.HasPrefix(sub_node, ".") { return true } } return false } func getPath( r *http.Request ) string { if r.URL.Path == "/" { return "." } return r.URL.Path[1:] } func writeNotFound( w http.ResponseWriter, writeMessage bool) { w.WriteHeader(http.StatusNotFound) if writeMessage { fmt.Fprintln(w, "File not found") fmt.Fprintln(w, "") fmt.Fprintln(w, "Sry bro") } } func handleGet( w http.ResponseWriter, r *http.Request, headOnly bool) { filename := getPath(r) fmt.Printf("Trying to get [%s]\n", filename) if !config.Config.AllowHidden && hasHidden(filename) { writeNotFound(w, !headOnly) return } stat, err := os.Stat(filename) switch { case err != nil || stat == nil : if os.IsNotExist(err) { writeNotFound(w, !headOnly) return } case stat.IsDir() : // If it's a collection list return the list of the collection type Entry struct { Name string Path string Size int64 SizePrint string } var files []Entry var cols []Entry f, err := os.Open(filename) if err != nil { writeNotFound(w, !headOnly) return } fileInfo, err := f.Readdir(-1) f.Close() if err != nil { writeNotFound(w, !headOnly) } initPath := r.URL.Path; cols = append(cols, Entry{".", initPath, 0, ""}) if initPath != "/" { cols = append(cols, Entry{"..", path.Dir(initPath), 0, ""}) initPath += "/" } for _, file := range fileInfo { if file.IsDir() { if !config.Config.HideHidden || file.Name()[0] != '.' { cols = append(cols, Entry{file.Name(), initPath + file.Name(), 0, ""}) } } else { if !config.Config.HideHidden || file.Name()[0] != '.' { files = append(files, Entry{file.Name(), initPath + file.Name(), file.Size(), normaliseSize(file.Size())}) } } } type Collection struct { Name string ListCols []Entry ListFiles []Entry } name := "davy - " + filename err = tmpl.Execute(w, Collection{ name, cols, files}) if err != nil { fmt.Println(err) } case stat.Mode().IsRegular(): if !headOnly { http.ServeFile(w, r, filename) } default: } } func handleDelete( w http.ResponseWriter, r *http.Request) { filename := getPath(r) fmt.Printf("Trying to delete [%s]\n", filename) if verifyEmptyBody(w, r) != nil { return } _, err := os.Stat(filename) if err != nil { if os.IsNotExist(err) { writeNotFound(w, true) } else { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Shit happen") } } err = os.Remove(filename) if err != nil { w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "Error : %s\n", err.Error()) return } w.WriteHeader(http.StatusNoContent) // Default success code ¯\_(ツ)_/¯ cf : 9.6.1.8 } func handlePut( w http.ResponseWriter, r *http.Request) { filename := r.URL.Path[1:] if filename == "" { filename = "." } fmt.Printf("Trying to create [%s]\n", filename) stat, err := os.Stat(path.Dir(filename)) if os.IsNotExist(err) { w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "Can't find [%s]\n)", path.Dir(filename)) fmt.Fprintf(w, "Trying to create it") err := os.MkdirAll(path.Dir(filename), os.ModePerm) if err != nil && os.IsNotExist(err) { return } } else if err != nil{ w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, err.Error()) fmt.Printf("Error Abording on [%s] about %s\n", filename, err) return } else if !stat.IsDir(){ w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "¯\\_(ツ)_/¯ [Error: %s]\n", err) return } stat, err = os.Stat(filename) if err != nil { if !os.IsNotExist(err) { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, err.Error()) return } } else if stat.IsDir() { w.WriteHeader(http.StatusMethodNotAllowed) fmt.Fprintln(w, "Cannot write a file over a existing collection") return } file, err := os.Create(filename) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, err.Error()) return } defer file.Close() s, err := io.Copy(file, r.Body) if err == io.EOF { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, err.Error()) return } w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, "%d bytes written\n", s) } func handleMkcol( w http.ResponseWriter, r *http.Request) { filename := r.URL.Path[1:] if filename == "" { filename = "." } fmt.Printf("Trying to Mkcol [%s]\n", filename) // 8.4 RFC4918 - Must return 415 (Unsupported Media Type) if body and should not. if verifyEmptyBody(w, r) != nil { return } fmt.Printf(" Base [%s]\n", path.Base(filename)) fmt.Printf(" Dir [%s]\n", path.Dir(filename)) err := os.Mkdir(filename, 0755) if err != nil { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "Sry for the error : %s", err.Error()) } w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, "Collection created") } func handleOptions( w http.ResponseWriter, r *http.Request) { if verifyEmptyBody(w, r) != nil { fmt.Println("The body of the request isn't empty. It should be.") return; } w.Header().Add("Allow", "GET,OPTIONS,HEAD,MKCOL,PUT,PROPFIND,PROPPATCH,DELETE,MOVE,COPY"); w.Header().Add("Server","Davy/1.0 Fun server"); w.Header().Add("DAV","1"); w.WriteHeader(http.StatusOK); } func handleCopy( w http.ResponseWriter, r *http.Request) { fmt.Println(w, "The body of the request isn't empty. It should be.") } func handleMove( w http.ResponseWriter, r *http.Request) { filename := r.URL.Path[1:] dest := r.Header.Get("Destination") overwrite := "T" if r.Header.Get("Overwrite") != "" { overwrite = r.Header.Get("Overwrite") } if verifyEmptyBody(w, r) != nil { return } fmt.Println("Moving ", filename, " => ", dest) fmt.Println(dest) return /* 5 cases src doesn't exist => error src is a directory, dest a file => error src is a directory, dest doesn't exist => rename src is a file, dest doesn't exist or is a file => overwrite or not dest is a directory => move src to that directory */ if filename == "" || filename == "." { w.WriteHeader(http.StatusBadRequest) fmt.Fprintln(w, "Cannot move root directory") return } tmpStatDest, errDest := os.Stat( dest ) // Case 1 if tmpStat, err := os.Stat( filename ) ; err != nil { if os.IsNotExist( err ) { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "%s doesn't exist bro\n", filename) } else { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "¯\\_(ツ)_/¯ with [%s]\n", filename) } return } else if tmpStat.IsDir() { // Case 2 & 3 if errDest != nil { if !os.IsNotExist( errDest ) { // Case 3 just rename w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "Error %s\n", err.Error()) return } } else if tmpStatDest.Mode().IsRegular() { w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "Can't write a collection over a file\n") return } } if errDest == nil { if tmpStatDest.IsDir() { // Case 5 if dest[len(dest) - 1] != '/' { dest += "/" } dest += path.Base(filename) } else if tmpStatDest.Mode().IsRegular() && overwrite == "F" { os.Remove(dest) } } err := os.Rename( filename, dest ) if err != nil { w.WriteHeader(http.StatusConflict) fmt.Fprintf(w, "Error : %s\n", err.Error()) } else { w.WriteHeader(http.StatusCreated) } } func handlePropfind( w http.ResponseWriter, r *http.Request) { } func handleMethod( w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: handleGet(w, r, false) case http.MethodHead: handleGet(w, r, true) case http.MethodPut: handlePut(w, r) case http.MethodDelete: handleDelete(w, r) case http.MethodOptions: fmt.Println("🦊"); handleOptions(w, r) case "MOVE": handleDelete(w, r) case "MKCOL": handleMkcol(w, r) case "LOCK": fallthrough case "UNLOCK": fallthrough case "PROPFIND": handlePropfind(w, r) case http.MethodPost: w.WriteHeader(http.StatusTeapot) fmt.Fprintln(w, "Sry, will be implemented \"soon\"") fmt.Fprintln(w, "") fmt.Fprintln(w, "Here's a unicorn. 🦄") default: w.WriteHeader(http.StatusTeapot) fmt.Fprintln(w, "Sry, i'm a teapot >///<") fmt.Fprintln(w, "") fmt.Fprintln(w, "Here's some tea. 🍵") } } func main() { var dirRoot string var help, version bool var port = -1 var host string helpString := `This is the help` versionString := "0.0.0" const ( defaultRoot = "." defaultPort = 8080 portUsage = "port to listen" usageDir = "the directory to serve" usageHelp = "show some help" usageVersion = "show the version" usageHost = "select the host adress" ) flag.StringVar(&dirRoot, "directory", "", usageDir) flag.StringVar(&dirRoot, "d", "localhost", usageHost + " (shorthand)") flag.StringVar(&host, "hostname", "", usageHost) flag.StringVar(&host, "H", "", usageHost + " (shorthand)") flag.IntVar(&port, "port", 0, portUsage) flag.IntVar(&port, "p", 0, portUsage + " (shorthand)") flag.BoolVar(&help, "help", false, usageHelp) flag.BoolVar(&help, "h", false, usageHelp+" (shorthand)") flag.BoolVar(&version, "version", false, usageVersion) flag.BoolVar(&version, "v", false, usageVersion+" (shorthand)") flag.Parse() if help { fmt.Println(helpString) return } if version { fmt.Printf("davy %s\n", versionString) return } if config.ReadConfig("") != nil { if config.ReadConfig("config/config.json") != nil { fmt.Println("Config file error.") fmt.Println("Charging default value") config.SetDefaultValue() } } if port == 0 { port = config.Config.Port if port == 0 { port = defaultPort } } if dirRoot == "" { dirRoot = config.Config.RootPath if dirRoot == "" { dirRoot = defaultRoot } } if host != "" { config.Config.Hostnames = []string{host} } fmap := template.FuncMap{ "formatURL": encodeURL, "formatSize": normaliseSize, "formatName": normaliseName, } tmpl = template.Must(template.New("dir.tmpl").Funcs(fmap).ParseFiles("src/dir.tmpl")) tmplXML = template.Must(template.New("request.tmpl").Funcs(fmap).ParseFiles("src/request.tmpl")) baseWd,_ := os.Getwd(); http.HandleFunc("/pink_davy.svg", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, baseWd + "/static/pink.svg") }); http.HandleFunc("/ban_davy.svg", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, baseWd + "/static/ban.svg") }) if err := os.Chdir(dirRoot) ; err != nil { fmt.Println("Error with directory [" + dirRoot + "]") return } http.HandleFunc("/", handleMethod) if len(config.Config.Hostnames) == 0 { fmt.Println("Error: No hostnames precised\nEmpty will be set", port) config.Config.Hostnames = []string{""} } var wg sync.WaitGroup wg.Add(len(config.Config.Hostnames)) for _, host := range config.Config.Hostnames { go func(host string) { defer wg.Done() fmt.Println("Launch server listen on " + host + ":" + strconv.Itoa(port)) log.Fatal(http.ListenAndServe(host + ":" + strconv.Itoa(port), nil)) }(host) } wg.Wait() fmt.Println("Bye bye") }