diff options
author | Julian T <julian@jtle.dk> | 2020-09-10 22:39:42 +0200 |
---|---|---|
committer | Julian T <julian@jtle.dk> | 2020-09-10 22:39:42 +0200 |
commit | f400c4c9b01e572ed65f133cb3c489c2a150e4ee (patch) | |
tree | 73242d0958bf96e06d66711c58cc41913aeecbfa | |
parent | 0c2623543f324e70132ae12595814cd183255135 (diff) |
Added uploading
-rw-r--r-- | config.json | 3 | ||||
-rw-r--r-- | main.go | 135 | ||||
-rw-r--r-- | root.template | 2 |
3 files changed, 129 insertions, 11 deletions
diff --git a/config.json b/config.json index ec59916..60269dd 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { "listen": "localhost:8080", "db": "user=julian dbname=noteman sslmode=disable", - "tmpl": "" + "tmpl": "", + "data": "data" } @@ -1,31 +1,40 @@ package main import ( - "net/http" - "flag" - "fmt" - "strings" "crypto/md5" + "mime" + "io/ioutil" + "io" "encoding/json" - "os" + "sync" + "flag" + "fmt" "html/template" "log" + "strconv" + "net/http" + "os" "path" + "strings" - _"github.com/lib/pq" "github.com/jmoiron/sqlx" + _"github.com/lib/pq" ) type Config struct { Listen string `json:"listen"` DBStr string `json:"db"` TmplPath string `json:"tmpl"` + DataPath string `json:"data"` } type Server struct { mux *http.ServeMux conf *Config db *sqlx.DB + + fslock sync.RWMutex + filestore map[string][]string } type note struct { @@ -43,6 +52,13 @@ const scheme = ` ) ` +var validMIME = map[string]bool { + "text/plain": true, + "image/jpeg": true, + "image/png": true, + "application/pdf": true, +} + func main() { confFlag := flag.String("c", "config.json", "config file") flag.Parse() @@ -75,13 +91,27 @@ func NewServer(conf *Config) *Server { s := &Server{} s.mux = http.NewServeMux() s.mux.HandleFunc("/", s.httpRoot) + s.mux.HandleFunc("/upload", s.httpUpload) s.conf = conf + s.filestore = map[string][]string{} + return s } func (s *Server) Connect() error { - var err error + files, err := ioutil.ReadDir(s.conf.DataPath) + if err != nil { + return err + } + + for _, file := range files { + name := file.Name() + + hash := name[0:4] + s.saveFile(hash, name) + } + s.db, err = sqlx.Connect("postgres", s.conf.DBStr) if err != nil { return err @@ -111,6 +141,21 @@ func (s *Server) renderTemplate(w http.ResponseWriter, data interface{}, pathnam return err } +func (s *Server) httpLog(r *http.Request, format string, args... interface{}) { + args = append([]interface{}{r.URL.Path, r.RemoteAddr}, args...) + log.Printf("(url: %s, ip: %s) " + format, args...) +} + +func (s *Server) Error(w http.ResponseWriter, r *http.Request, err string, code int) { + if code < 500 { + http.Error(w, err, code) + } else { + w.WriteHeader(code) + } + + s.httpLog(r, "ERR: %s", err) +} + func (s *Server) httpCreateNode(w http.ResponseWriter, r *http.Request) { var ( name = r.FormValue("name") @@ -123,10 +168,12 @@ func (s *Server) httpCreateNode(w http.ResponseWriter, r *http.Request) { INSERT INTO notes (hash, name, location) VALUES ($1, $2, $3)`, hash, name, location) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + s.Error(w, r, err.Error(), http.StatusInternalServerError) return } + s.httpLog(r, "Created node %s", hash) + var ref strings.Builder fmt.Fprintf(&ref, "#%s", hash) if location != "" { @@ -161,9 +208,79 @@ func (s *Server) httpRoot(w http.ResponseWriter, r *http.Request) { err := s.db.SelectContext(r.Context(), &page.Notes, ` SELECT hash, name, location FROM notes WHERE available = True`) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + s.Error(w, r, err.Error(), http.StatusInternalServerError) return } s.renderTemplate(w, &page, "root.template") } + +func (s *Server) httpUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Head hash + hash := r.FormValue("h") + _, err := strconv.ParseUint(hash, 16, 16) + if err != nil || len(hash) != 4 { + s.Error(w, r, "Invalid hash", http.StatusBadRequest) + return + } + + // Load file + inf, header, err := r.FormFile("f") + if err != nil { + s.Error(w, r, err.Error(), http.StatusInternalServerError) + return + } + defer inf.Close() + + // Check if valid type + mtype := header.Header.Get("Content-Type") + ok := validMIME[mtype] + if !ok { + s.Error(w, r, "Invalid file type", http.StatusBadRequest) + return + } + + // Create file + fname := s.allocFile(hash, mtype) + f, err := os.Create(path.Join(s.conf.DataPath, fname)) + if err != nil { + s.Error(w, r, err.Error(), http.StatusInternalServerError) + return + } + defer f.Close() + + _, err = io.Copy(f, inf) + if err != nil { + s.Error(w, r, err.Error(), http.StatusInternalServerError) + return + } + + s.saveFile(hash, fname) + s.httpLog(r, "Uploaded file %s", fname) +} + +func (s *Server) allocFile(hash string, t string) string { + s.fslock.RLock() + existing := s.filestore[hash] + s.fslock.RUnlock() + + var ext string + extarr, _ := mime.ExtensionsByType(t) + if len(extarr) > 0 { + ext = extarr[0] + } + + return fmt.Sprintf("%s.%d%s", hash, len(existing), ext) +} + +func (s *Server) saveFile(hash string, fname string) { + s.fslock.Lock() + s.filestore[hash] = append(s.filestore[hash], fname) + + s.fslock.Unlock() +} diff --git a/root.template b/root.template index 93741f9..e4f5104 100644 --- a/root.template +++ b/root.template @@ -47,8 +47,8 @@ <input type="checkbox" id="doupload"> <div id="upload-box"> <form method="POST" action="/upload" enctype="multipart/form-data"> + <input type="input" name="h"> <input type="file" name="f" accept="image/*,.pdf,.txt,.md" capture> - <input type="input" name="hash"> <input type="submit" value="go"> </form> </div> |