commit 362e8cd06673d46b3308554bf62c6056f25bb57e
parent e35d00f025f12cdd0d35fd563cf7d82c098082fd
Author: Vetle Haflan <vetle@haflan.dev>
Date: Sun, 1 Mar 2020 17:26:03 +0100
Support multiline form data and continue refactoring
Diffstat:
M | readme.md | | | 8 | ++------ |
A | templates.go | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | vodkas.go | | | 86 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
3 files changed, 121 insertions(+), 26 deletions(-)
diff --git a/readme.md b/readme.md
@@ -1,8 +1,4 @@
# vodka server
-One time text dumps with an optional password.
-No database needed for low-scale use, so this can be written entirely in Go and
-probably be compiled statically.
-
-To run it in Docker anyway you can simply copy the binary into an alpine
-container and run it.
+Short time text and file dumps with an optional password.
+Uses a simple `bbolt` database to store data.
diff --git a/templates.go b/templates.go
@@ -0,0 +1,53 @@
+// TODO: A better alternative to this inline HTML stuff:
+// https://odino.org/bundling-static-files-within-your-golang-app/
+package main
+
+import "html/template"
+
+
+// I think template data variables must be exported (upper case) in order to be usable
+var uploadPageTemplate = template.Must(template.New("uploadPage").Parse(`
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ textarea { width: 100%; }
+ </style>
+
+</head>
+<body>
+<h1>Upload to /{{.ShotKey}}</h1>
+
+<form action="/{{.ShotKey}}" method="POST" onreset="formReset()" enctype="multipart/form-data">
+ <label for="input-numdls">Number of downloads</label><br>
+ <input value="1" type="number" id="input-numdls" name="numdls"><br>
+ <p>
+ <label for="input-file">File dump</label><br>
+ <input type="file" id="input-file" name="file" style="width: 100%;"
+ oninput="inputChanged(this, '#input-text')"><br>
+ <b>or</b><br>
+ <label for="input-text">Text dump</label><br>
+ <textarea id="input-text" name="text" rows="15"
+ oninput="inputChanged(this, '#input-file')"></textarea><br>
+ </p>
+ <input type="reset" value="Reset">
+ <input type="submit" value="Submit">
+</form>
+
+<script>
+ function formReset() {
+ document.querySelector("#input-text").removeAttribute("disabled");
+ document.querySelector("#input-file").removeAttribute("disabled");
+ }
+ function inputChanged(changedInput, otherInputId) {
+ const otherInput = document.querySelector(otherInputId);
+ if (changedInput.value) {
+ otherInput.setAttribute("disabled", true);
+ } else {
+ otherInput.removeAttribute("disabled");
+ }
+ }
+</script>
+</body>
+</html>
+`))
diff --git a/vodkas.go b/vodkas.go
@@ -9,17 +9,23 @@ import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"go.etcd.io/bbolt"
+ "io"
"io/ioutil"
"log"
- //"mime/multipart"
+ "mime/multipart"
"net/http"
"strings"
+ "strconv"
"time"
)
/**************** Handler Functions ****************/
+const FormNameFile = "file"
+const FormNameText = "text"
+const FormNameNumShots = "numdls"
+
const InfoMessage = `Usage:
- POUR: curl vetle.vodka[/<requested-shot-key>] -d <data>
- SHOT: curl vetle.vodka/<shot-key>
@@ -73,14 +79,35 @@ func pour(key string, formdata []byte) {
fmt.Println(string(formdata))
}
-func ShotHandler() {
-// TODO: Generate random key if no request given (https://flaviocopes.com/go-random/)
- /*if !found {
- res.WriteHeader(http.StatusNotFound)
- if _, err := res.Write([]byte(fmt.Sprint("404 no shot here\n"))); err != nil {
- log.Panicln("Error when trying to write response")
- }
- return*/
+func extractMultipart(mr *multipart.Reader) (contents []byte, num int, err error) {
+ for {
+ var part *multipart.Part
+ part, err = mr.NextPart()
+ if err == io.EOF {
+ err = nil
+ break
+ }
+ formName := part.FormName()
+ if err != nil {
+ log.Fatal(err)
+ }
+ if formName == FormNameText || formName == FormNameFile {
+ contents, err = ioutil.ReadAll(part)
+ if err != nil {
+ return
+ }
+ continue
+ }
+ if formName == FormNameNumShots {
+ var numShotsRaw []byte
+ numShotsRaw, err = ioutil.ReadAll(part)
+ if err != nil { return }
+ num, err = strconv.Atoi(string(numShotsRaw))
+ if err != nil { return }
+ }
+ }
+ err = nil
+ return
}
@@ -88,33 +115,52 @@ func RootHandler(res http.ResponseWriter, r *http.Request) {
// Detect whether Simple mode (text only) is active
textOnly := r.Header.Get("Simple") != "" // for forcing textOnly mode
textOnly = textOnly || strings.Contains(r.Header.Get("User-Agent"), "curl")
- var responseText string
+ var response string
if r.Method == http.MethodGet {
if textOnly {
- responseText = InfoMessage
+ response = InfoMessage
+ if _, err := res.Write([]byte(response)); err != nil {
+ log.Panicln("Error when trying to write response body")
+ }
} else {
- responseText = "This will be replaced by a template"
+ templateData := struct { ShotKey string }{ "" }
+ uploadPageTemplate.Execute(res, templateData)
}
+ return
} else if r.Method == http.MethodPost {
- b, err := ioutil.ReadAll(r.Body)
+ var err error
+ var numshots int
+ var contents []byte
+ // Dumps can be both x-www-urlencoded and multipart/form-data.
+ // Try multipart first, then x-www-urlencoded
+ mpReader, _ := r.MultipartReader()
+ if mpReader != nil {
+ contents, numshots, err = extractMultipart(mpReader)
+ } else {
+ numshots = 1
+ contents, err = ioutil.ReadAll(r.Body)
+ }
if err != nil {
res.WriteHeader(http.StatusInternalServerError)
return
}
+ fmt.Printf("Number of shots: %v\n", numshots)
+ fmt.Printf("Bytes in contents: %v\n", len(contents))
// Generate random shot key
random := make([]byte, 16)
rand.Read(random)
randhex := hex.EncodeToString(random)
- push(randhex, b)
- if textOnly {
- responseText = r.Host + "/" + randhex
+ push(randhex, contents)
+ if /*textOnly*/ true {
+ response = r.Host + "/" + randhex
+ if _, err := res.Write([]byte(response)); err != nil {
+ log.Panicln("Error when trying to write response body")
+ }
}
+ return
}
- if _, err := res.Write([]byte(responseText)); err != nil {
- log.Panicln("Error when trying to write response body")
- }
- return
}
+
func KeyHandler(res http.ResponseWriter, r *http.Request) {
key := mux.Vars(r)["shotKey"]
contents, found := pop(key)