summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Pointner <equinox@helsinki.at>2016-04-02 16:53:16 (GMT)
committerChristian Pointner <equinox@helsinki.at>2016-04-02 16:53:16 (GMT)
commit374cabeb4d2d59b588e9d4a30e3c09e2dbc0abdd (patch)
tree06bd6587085c6a572bee5077c440b445a4479b6a
parent27eaf84de6c5900c739f73725bd07270d43d49b7 (diff)
parent761d6d0824c0cc92fc746b77499ac563f4e6e579 (diff)
Merge branch 'upload-feature'
-rw-r--r--src/rhimportd/ctrlWatchDir.go5
-rw-r--r--src/rhimportd/ctrlWeb.go9
-rw-r--r--src/rhimportd/main.go35
-rw-r--r--src/rhimportd/uploadWeb.go211
-rw-r--r--web-static/upload-form.html25
5 files changed, 279 insertions, 6 deletions
diff --git a/src/rhimportd/ctrlWatchDir.go b/src/rhimportd/ctrlWatchDir.go
index 1b08e62..14d1c1f 100644
--- a/src/rhimportd/ctrlWatchDir.go
+++ b/src/rhimportd/ctrlWatchDir.go
@@ -165,6 +165,8 @@ func watchDirHandler(conf *rhimport.Config, db *rddb.DBChan, ctx *rhimport.Conte
func watchDirRun(dir *os.File, conf *rhimport.Config, db *rddb.DBChan) {
rhl.Printf("watch-dir-ctrl: watching for files in %s", dir.Name())
+ t := time.NewTicker(1 * time.Second)
+ defer t.Stop()
for {
var err error
if _, err = dir.Seek(0, 0); err != nil {
@@ -199,8 +201,7 @@ func watchDirRun(dir *os.File, conf *rhimport.Config, db *rddb.DBChan) {
}
}
}
-
- time.Sleep(1 * time.Second)
+ <-t.C
}
}
diff --git a/src/rhimportd/ctrlWeb.go b/src/rhimportd/ctrlWeb.go
index 40075d0..2b52ef0 100644
--- a/src/rhimportd/ctrlWeb.go
+++ b/src/rhimportd/ctrlWeb.go
@@ -44,11 +44,14 @@ func (self webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
self.H(self.Config, self.DBChan, self.SessionStoreChan, self.trusted, w, r)
}
-func StartControlWeb(addr string, conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport.SessionStoreChan) {
- http.Handle("/public/simple", webHandler{conf, db, sessions, false, webSimpleHandler})
- // http.Handle("/trusted/simple", webHandler{conf, db, sessions, true, webSimpleHandler})
+func StartControlWeb(addr, staticDir string, uploadMaxAge time.Duration, conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport.SessionStoreChan) {
+ http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
+ // http.Handle("/trusted/simple", webHandler{conf, db, sessions, true, webSimpleHandler})
+ http.Handle("/public/simple", webHandler{conf, db, sessions, false, webSimpleHandler})
http.Handle("/public/socket", webHandler{conf, db, sessions, false, webSocketHandler})
+ http.Handle("/public/upload", webHandler{conf, db, sessions, false, webUploadHandler})
+ go webUploadCleaner(conf, uploadMaxAge)
rhl.Println("web-ctrl: listening on", addr)
server := &http.Server{Addr: addr, ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second}
diff --git a/src/rhimportd/main.go b/src/rhimportd/main.go
index 4b0514b..97a12ec 100644
--- a/src/rhimportd/main.go
+++ b/src/rhimportd/main.go
@@ -34,6 +34,7 @@ import (
"os"
"os/signal"
"sync"
+ "time"
)
var (
@@ -66,9 +67,33 @@ func (s *envStringValue) Get() interface{} { return string(*s) }
func (s *envStringValue) String() string { return fmt.Sprintf("%s", *s) }
+type envDurationValue time.Duration
+
+func newEnvDurationValue(key string, dflt time.Duration) (*envDurationValue, error) {
+ if envval, exists := os.LookupEnv(key); exists {
+ var val envDurationValue
+ err := (&val).Set(envval)
+ return &val, err
+ } else {
+ return (*envDurationValue)(&dflt), nil
+ }
+}
+
+func (d *envDurationValue) Set(val string) error {
+ dval, err := time.ParseDuration(val)
+ *d = envDurationValue(dval)
+ return err
+}
+
+func (d *envDurationValue) Get() interface{} { return time.Duration(*d) }
+
+func (d *envDurationValue) String() string { return fmt.Sprintf("%v", *d) }
+
func main() {
webAddr := newEnvStringValue("RHIMPORTD_WEB_ADDR", "localhost:4080")
flag.Var(webAddr, "web-addr", "addr:port to listen on (environment: RHIMPORTD_WEB_ADDR)")
+ webStaticDir := newEnvStringValue("RHIMPORTD_WEB_STATIC_DIR", "/usr/share/rhimportd/web-static/")
+ flag.Var(webStaticDir, "web-static-dir", "path to static html files (environment: RHIMPORTD_WEB_STATIC_DIR)")
telnetAddr := newEnvStringValue("RHIMPORTD_TELNET_ADDR", "localhost:4023")
flag.Var(telnetAddr, "telnet-addr", "addr:port to listen on (environment: RHIMPORTD_TELNET_ADDR)")
watchDir := newEnvStringValue("RHIMPORTD_WATCH_DIR", "")
@@ -81,6 +106,14 @@ func main() {
flag.Var(tempDir, "tmp-dir", "path to temporary files (environment: RHIMPORTD_TEMP_DIR)")
localFetchDir := newEnvStringValue("RHIMPORTD_LOCAL_FETCH_DIR", os.TempDir())
flag.Var(localFetchDir, "local-fetch-dir", "base path for local:// urls (environment: RHIMPORTD_LOCAL_FETCH_DIR)")
+
+ uploadMaxAge, err := newEnvDurationValue("RHIMPORTD_UPLOAD_MAX_AGE", 30*time.Minute)
+ if err != nil {
+ rhl.Println("Error parsing RHIMPORTD_UPLOAD_MAX_AGE from environment:", err)
+ return
+ }
+ flag.Var(uploadMaxAge, "upload-max-age", "maximum age of uploaded files before the get deleted (environment: RHIMPORTD_UPLOAD_MAX_AGE)")
+
help := flag.Bool("help", false, "show usage")
flag.Parse()
@@ -112,7 +145,7 @@ func main() {
go func() {
defer wg.Done()
rhl.Println("starting web-ctrl")
- StartControlWeb(webAddr.Get().(string), conf, db.GetInterface(), sessions.GetInterface())
+ StartControlWeb(webAddr.Get().(string), webStaticDir.Get().(string), uploadMaxAge.Get().(time.Duration), conf, db.GetInterface(), sessions.GetInterface())
rhl.Println("web-ctrl finished")
}()
}
diff --git a/src/rhimportd/uploadWeb.go b/src/rhimportd/uploadWeb.go
new file mode 100644
index 0000000..4b56a43
--- /dev/null
+++ b/src/rhimportd/uploadWeb.go
@@ -0,0 +1,211 @@
+//
+// rhimportd
+//
+// The Radio Helsinki Rivendell Import Daemon
+//
+//
+// Copyright (C) 2015-2016 Christian Pointner <equinox@helsinki.at>
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+package main
+
+import (
+ "code.helsinki.at/rhrd-go/rddb"
+ "code.helsinki.at/rhrd-go/rhimport"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "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 // 2GB
+)
+
+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)
+ if err := r.ParseMultipartForm(32 << 10); err != nil {
+ rhl.Printf("WebUploadHandler: error while parsing multi-part-form: %v", err)
+ webUploadErrorResponse(w, http.StatusBadRequest, err.Error())
+ return
+ }
+
+ username := r.FormValue("LOGIN_NAME")
+ password := r.FormValue("PASSWORD")
+ if username == "" {
+ webUploadErrorResponse(w, http.StatusBadRequest, "missing field LOGIN_NAME")
+ return
+ }
+ if password == "" {
+ webUploadErrorResponse(w, http.StatusBadRequest, "missing field LOGIN_NAME")
+ 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
+ }
+
+ src, hdr, err := r.FormFile("FILENAME")
+ if err != nil {
+ rhdl.Printf("WebUploadHandler: error looking for upload file in form: %s", err)
+ webUploadErrorResponse(w, http.StatusBadRequest, err.Error())
+ return
+ }
+ defer src.Close()
+
+ rhdl.Printf("WebUploadHandler: got request from user '%s', filename='%s'", username, hdr.Filename)
+
+ 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 := dstpath + "/" + hdr.Filename
+ 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()
+ }
+}
diff --git a/web-static/upload-form.html b/web-static/upload-form.html
new file mode 100644
index 0000000..bbb397c
--- /dev/null
+++ b/web-static/upload-form.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>rhimportd File Upload</title>
+ </head>
+ <body>
+ <div class="container">
+ <h1>rhimportd File Upload</h1>
+ <form class="form-upload" method="post" action="/public/upload" enctype="multipart/form-data">
+ <fieldset>
+ <label for="LOGIN_NAME">Username:</label>
+ <input type="text" name="LOGIN_NAME">
+ </br>
+ <label for="LOGIN_NAME">Password:</label>
+ <input type="password" name="PASSWORD">
+ </br>
+ <label for="FILENAME">File:</label>
+ <input type="file" name="FILENAME">
+ </br>
+ <input type="submit" name="submit" value="Submit">
+ </fieldset>
+ </form>
+ </div>
+ </body>
+</html>