// // rhimportd // // The Radio Helsinki Rivendell Import Daemon // // // Copyright (C) 2015-2016 Christian Pointner // // This file is part of rhimportd. // // rhimportd is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhimportd is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhimportd. If not, see . // package main import ( "bytes" "code.helsinki.at/rhrd-go/rddb" "code.helsinki.at/rhrd-go/rhimport" "encoding/json" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "os" "path" "path/filepath" "strings" "time" ) type webUploadResponseData struct { ResponseCode int `json:"RESPONSE_CODE"` ErrorString string `json:"ERROR_STRING"` SourceFile string `json:"SOURCE_FILE"` } func webUploadErrorResponse(w http.ResponseWriter, code int, errStr string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) encoder := json.NewEncoder(w) respdata := webUploadResponseData{code, errStr, ""} encoder.Encode(respdata) } func webUploadResponse(w http.ResponseWriter, file string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) respdata := webUploadResponseData{http.StatusOK, "success", file} encoder.Encode(respdata) } const ( webUploadMaxRequestSize = (2 << 30) - 1 // 2GB, (2 << 30) overflows int on 32-bit systems therefore we use 2GB - 1 Byte ) func webUploadParseForm(w http.ResponseWriter, r *http.Request) (username, password, srcfile string, src *multipart.Part, ok bool) { mpr, err := r.MultipartReader() if err != nil { rhl.Printf("WebUploadHandler: error while parsing multi-part-form: %v", err) webUploadErrorResponse(w, http.StatusBadRequest, err.Error()) return } for { p, err := mpr.NextPart() if err != nil { rhl.Printf("WebUploadHandler: error while parsing multi-part-form: %v", err) webUploadErrorResponse(w, http.StatusBadRequest, err.Error()) return } var dstfield *string switch p.FormName() { case "LOGIN_NAME": dstfield = &username case "PASSWORD": dstfield = &password case "FILENAME": srcfile = p.FileName() src = p ok = true return default: rhl.Printf("WebUploadHandler: unknown form field: '%s'", p.FormName()) webUploadErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("unknown field %s", p.FormName())) return } var buf bytes.Buffer if _, err := io.CopyN(&buf, p, 1<<10); err != nil && err != io.EOF { // 1kB should be enough rhl.Printf("WebUploadHandler: error while extracting form field: %v", err) webUploadErrorResponse(w, http.StatusBadRequest, err.Error()) return } *dstfield = buf.String() p.Close() } return } func webUploadHandler(conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport.SessionStoreChan, trusted bool, w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { http.Redirect(w, r, "/static/upload-form.html", http.StatusTemporaryRedirect) return } if r.Method != "POST" { rhl.Printf("WebUploadHandler: got invalid request method") webUploadErrorResponse(w, http.StatusMethodNotAllowed, "only POST and GET method are allowed") return } // This is from: stackoverflow.com/questions/26392196 if r.ContentLength > webUploadMaxRequestSize { rhl.Printf("WebUploadHandler: request ist to large: %d > %d", r.ContentLength, webUploadMaxRequestSize) webUploadErrorResponse(w, http.StatusExpectationFailed, "request too large") return } r.Body = http.MaxBytesReader(w, r.Body, webUploadMaxRequestSize) username, password, srcfile, src, ok := webUploadParseForm(w, r) if !ok { return } if username == "" { webUploadErrorResponse(w, http.StatusBadRequest, "missing field LOGIN_NAME") return } if password == "" { webUploadErrorResponse(w, http.StatusBadRequest, "missing field PASSWORD") return } if authenticated, err := db.CheckPassword(username, password); err != nil { rhl.Printf("WebUploadHandler: error checking username/password: %v", err) webUploadErrorResponse(w, http.StatusUnauthorized, err.Error()) return } else if !authenticated { rhl.Printf("WebUploadHandler: invalid username/password") webUploadErrorResponse(w, http.StatusUnauthorized, "invalid username/password") return } rhdl.Printf("WebUploadHandler: got request from user '%s', filename='%s'", username, srcfile) dstpath, err := ioutil.TempDir(conf.TempDir, "webupload-"+username+"-") if err != nil { rhl.Printf("WebUploadHandler: error creating temporary directory: %v", err) webUploadErrorResponse(w, http.StatusInternalServerError, err.Error()) return } dstfile := filepath.Join(dstpath, path.Clean("/"+srcfile)) dst, err := os.OpenFile(dstfile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) if err != nil { rhl.Printf("WebUploadHandler: Unable to create file '%s': %v", dstfile, err) webUploadErrorResponse(w, http.StatusInternalServerError, err.Error()) return } defer dst.Close() size, err := io.Copy(dst, src) if err != nil { rhl.Printf("WebUploadHandler: error storing uploaded file '%s': %v", dstfile, err) webUploadErrorResponse(w, http.StatusInternalServerError, err.Error()) return } rhl.Printf("WebUploadHandler: stored file '%s' (size: %d Bytes)", dstfile, size) webUploadResponse(w, "tmp://"+strings.TrimPrefix(dstfile, conf.TempDir)) } func webUploadCleanerRun(dir *os.File, conf *rhimport.Config, maxAge time.Duration) { t := time.NewTicker(1 * time.Minute) defer t.Stop() for now := range t.C { var err error if _, err = dir.Seek(0, 0); err != nil { rhl.Printf("webUploadCleaner: reading directory contents failed: %s", err) return } var entries []os.FileInfo if entries, err = dir.Readdir(0); err != nil { rhl.Printf("webUploadCleaner: reading directory contents failed: %s", err) return } for _, entry := range entries { if !strings.HasPrefix(entry.Name(), "webupload-") { continue } age := now.Sub(entry.ModTime()) if age <= maxAge { continue } if err := os.RemoveAll(conf.TempDir + "/" + entry.Name()); err != nil { rhl.Printf("webUploadCleaner: deleting dir '%s' failed: %v", entry.Name(), err) continue } rhl.Printf("webUploadCleaner: successfully deleted dir '%s', Age: %v", entry.Name(), age) } } } func webUploadCleaner(conf *rhimport.Config, maxAge time.Duration) { first := true for { if !first { time.Sleep(5 * time.Second) } first = false dir, err := os.Open(conf.TempDir) if err != nil { rhl.Printf("webUploadCleaner: %s", err) continue } if i, err := dir.Stat(); err != nil { rhl.Printf("webUploadCleaner: %s", err) dir.Close() continue } else { if !i.IsDir() { rhl.Printf("webUploadCleaner: %s is not a directory", conf.TempDir) dir.Close() continue } } rhdl.Printf("webUploadCleaner: running with max age: %v", maxAge) webUploadCleanerRun(dir, conf, maxAge) rhdl.Printf("webUploadCleanerRun: returned - restring in 5 sec...") dir.Close() } }