From f48750eafed5f522a2e11622c8b20f4a7685bad6 Mon Sep 17 00:00:00 2001
From: Christian Pointner <equinox@helsinki.at>
Date: Thu, 7 Apr 2016 12:10:12 +0200
Subject: parse multipart form as a stream to reject wrong authentication
 before the whole file got uploaded


diff --git a/src/rhimportd/uploadWeb.go b/src/rhimportd/uploadWeb.go
index 1c53fbc..ee86e0f 100644
--- a/src/rhimportd/uploadWeb.go
+++ b/src/rhimportd/uploadWeb.go
@@ -25,11 +25,14 @@
 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"
 	"strings"
@@ -62,6 +65,50 @@ 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)
@@ -81,14 +128,11 @@ func webUploadHandler(conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport
 		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())
+
+	username, password, srcfile, src, ok := webUploadParseForm(w, r)
+	if !ok {
 		return
 	}
-
-	username := r.FormValue("LOGIN_NAME")
-	password := r.FormValue("PASSWORD")
 	if username == "" {
 		webUploadErrorResponse(w, http.StatusBadRequest, "missing field LOGIN_NAME")
 		return
@@ -108,15 +152,7 @@ func webUploadHandler(conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport
 		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)
+	rhdl.Printf("WebUploadHandler: got request from user '%s', filename='%s'", username, srcfile)
 
 	dstpath, err := ioutil.TempDir(conf.TempDir, "webupload-"+username+"-")
 	if err != nil {
@@ -125,7 +161,7 @@ func webUploadHandler(conf *rhimport.Config, db *rddb.DBChan, sessions *rhimport
 		return
 	}
 
-	dstfile := dstpath + "/" + hdr.Filename
+	dstfile := dstpath + "/" + 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)
-- 
cgit v0.10.2