// // 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" "encoding/json" "io" "mime/multipart" "net/http" "code.helsinki.at/rhrd-go/rddb" "code.helsinki.at/rhrd-go/rhimport" ) type webUploadResponseData struct { ResponseCode int `json:"RESPONSE_CODE"` ErrorString string `json:"ERROR_STRING"` } 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 webUploadSuccessResponse(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) respdata := webUploadResponseData{http.StatusOK, "success"} 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, sessionid, srcfile string, src *multipart.Part) { mpr, err := r.MultipartReader() if err != nil { rhl.Printf("WebUploadHandler: error while parsing multipart-form: %v", err) webUploadErrorResponse(w, http.StatusBadRequest, err.Error()) return } for { p, err := mpr.NextPart() if err == io.EOF { return } if err != nil { rhl.Printf("WebUploadHandler: error while parsing multipart-form: %v", err) webUploadErrorResponse(w, http.StatusBadRequest, err.Error()) return } var dstfield *string switch p.FormName() { case "LOGIN_NAME": dstfield = &username case "SESSION_ID": dstfield = &sessionid case "FILENAME": srcfile = p.FileName() src = p return // don't read any fileds beyond this point because we would need to load the whole file in order to continue parsing default: rhdl.Printf("WebUploadHandler: ingoring unknown form field: '%s'", p.FormName()) continue } 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() } } 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 is too large: %d > %d", r.ContentLength, webUploadMaxRequestSize) webUploadErrorResponse(w, http.StatusExpectationFailed, "request too large") return } r.Body = http.MaxBytesReader(w, r.Body, webUploadMaxRequestSize) username, sessionid, srcfile, src := webUploadParseForm(w, r) if username == "" { webUploadErrorResponse(w, http.StatusBadRequest, "missing field LOGIN_NAME") return } if sessionid == "" { webUploadErrorResponse(w, http.StatusBadRequest, "missing field SESSION_ID") return } if srcfile == "" || src == nil { webUploadErrorResponse(w, http.StatusBadRequest, "missing field FILENAME") return } s, _, code, _ := sessions.Get(username, sessionid) if code != http.StatusOK { webUploadErrorResponse(w, http.StatusUnauthorized, "session not found") return } cancel, attachmentChan := s.AttachUploader() if attachmentChan == nil || cancel == nil { webUploadErrorResponse(w, http.StatusForbidden, "upload already in progress") return } defer close(attachmentChan) rhl.Printf("WebUploadHandler: starting upload for file '%s'", srcfile) for { chunk := rhimport.AttachmentChunk{} var data [128 * 1024]byte n, err := src.Read(data[:]) if n > 0 { chunk.Data = data[:n] if err == io.EOF { err = nil } } chunk.Error = err if err == io.EOF { webUploadSuccessResponse(w) return } select { case <-cancel: rhl.Printf("WebUploadHandler: upload for file '%s' got canceld", srcfile) webUploadErrorResponse(w, http.StatusNoContent, "canceled") return case attachmentChan <- chunk: } if err != nil { webUploadErrorResponse(w, http.StatusInternalServerError, err.Error()) return } } }