vodkas

Simple file sharing server
Log | Files | Refs

commit a04c2bd55869354c33873865e94e0c6cb4fc4d64
parent 765e9789669f8480212f0958deadd99e5f017b0a
Author: Vetle Haflan <vetle.haflan@luxsave.com>
Date:   Tue,  2 Jun 2020 12:42:02 +0200

Implement permanent shots for 'admin'

Closes #7

Diffstat:
M.gitignore | 1+
Mvodkas.go | 50++++++++++++++++++++++++++++++++++++--------------
Mvv.sh | 55++++++++++++++++++++++++++++++++++++++-----------------
3 files changed, 75 insertions(+), 31 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ .idea/ vodkas +vodka.db diff --git a/vodkas.go b/vodkas.go @@ -24,9 +24,13 @@ import ( /**************** Handler Functions ****************/ -const FormNameFile = "file" -const FormNameText = "text" -const FormNameNumShots = "numdls" +const ( + Version = "1.0" + FormNameFile = "file" + FormNameText = "text" + FormNameNumShots = "numdls" + AdminKeyHeader = "Admin-Key" +) var ( keyTakenMessage = []byte("The requested key is taken. Try another.\n") @@ -99,6 +103,10 @@ func shot(shotKey string) (contents []byte, err error) { } return datab.Delete(bShotKey) } + // bnums must be 'remade' to avoid segmentation fault when going from eg. 0 to -1 + // I guess because the buffer used to store 0 is too small for -1 (2's complement?). + // Size of the buffer is returned by binary.Varint btw, so this is easy to check. + bnums = make([]byte, binary.MaxVarintLen64) binary.PutVarint(bnums, nums) numsb.Put(bShotKey, bnums) return nil @@ -112,8 +120,7 @@ func shot(shotKey string) (contents []byte, err error) { // fixNumShots checks that numshots is valid, i.e. between 1 and max // (unless an admin header is given), otherwise adjusts to legal values func legalNumshots(numshots int, r *http.Request) int { - xAdminKey := r.Header.Get("X-ADMIN-KEY") - if xAdminKey == adminKey { + if r.Header.Get(AdminKeyHeader) == adminKey { return numshots } if numshots < 1 { @@ -146,7 +153,7 @@ func pour(shotKey string, r *http.Request) (err error) { if storageCTRL.bytesUsed+len(contents) > storageCTRL.bytesMax { return errors.New("database is full") } - fmt.Printf("Number of shots: %v", numshots) + fmt.Printf("Number of shots: %v\n", numshots) err = db.Update(func(tx *bbolt.Tx) error { datab := tx.Bucket([]byte(dataBucketKey)) numsb := tx.Bucket([]byte(numsBucketKey)) @@ -254,6 +261,7 @@ func keyHandler(res http.ResponseWriter, r *http.Request) { key := mux.Vars(r)["shotKey"] textOnly := r.Header.Get("Simple") != "" // for forcing textOnly mode textOnly = textOnly || strings.Contains(r.Header.Get("User-Agent"), "curl") + var err error if r.Method == http.MethodGet { // Return upload page if the key is available if !smell(key) { @@ -267,26 +275,35 @@ func keyHandler(res http.ResponseWriter, r *http.Request) { res.Write(keyTakenMessage) res.WriteHeader(http.StatusInternalServerError) } - if _, err := res.Write(contents); err != nil { + if _, err = res.Write(contents); err != nil { log.Panicln("Error when trying to write response") res.WriteHeader(http.StatusInternalServerError) } } else if r.Method == http.MethodPost { + // Admin should be able to overwrite anything + if r.Header.Get(AdminKeyHeader) == adminKey { + if err = pour(key, r); err != nil { + goto commonerror + } + return + } if smell(key) { // POSTs from website to taken shouldn't happen, so use textOnly always - if _, err := res.Write(keyTakenMessage); err != nil { + if _, err = res.Write(keyTakenMessage); err != nil { res.WriteHeader(http.StatusInternalServerError) } } else { - // TODO: Notify if database is full, not just server error - if err := pour(key, r); err != nil { - log.Println(err) - res.Write(serverErrorMessage) - res.WriteHeader(http.StatusInternalServerError) - return + if err = pour(key, r); err != nil { + goto commonerror } res.Write(pourSuccessMessage) } + return + commonerror: + // TODO: Notify if database is full, not just server error for everything + res.WriteHeader(http.StatusInternalServerError) + res.Write(serverErrorMessage) + log.Println(err) } //fmt.Printf("Request from client: %v\n", r.Header.Get("User-Agent")) return @@ -353,12 +370,17 @@ func initialize(dbFile string, limitStorage, limitNums, port int, admKey string) /**************** Main ****************/ func main() { port := flag.Int("p", 8080, "Port") + version := flag.Bool("v", false, "Print version and exit") dbFile := flag.String("d", "vodka.db", "Database file") stat := flag.Bool("s", false, "View database keys and size of associated contents") storageLimit := flag.Int("l", 10000, "Storage limit in kilobytes (1000 bytes)") numsLimit := flag.Int("n", 10, "Maximum number of shots per key") admKey := flag.String("a", "vodkas", "Admin key to allow unlimited shots") flag.Parse() + if *version { + fmt.Println("vodkas " + Version) + return + } err := initialize(*dbFile, *storageLimit, *numsLimit, *port, *admKey) defer db.Close() if err != nil { diff --git a/vv.sh b/vv.sh @@ -5,8 +5,10 @@ CMD="$1" function help () { echo "$0 commands:" + echo " a: share a file as admin (password prompt)" + echo " vv a <filename> [shotKey] [numdls]" echo " s: share a file" - echo " vv s <filename> [shotKey]" + echo " vv s <filename> [shotKey] [numdls]" echo " f: fetch a file" echo " vv f <shotKey>" } @@ -18,19 +20,38 @@ function argrequired () { fi } -if [ "$CMD" == "s" ]; then - argrequired filename $2 - shotKey=$3 - curl -F file=@$2 $HOST/$shotKey -elif [ "$CMD" == "f" ]; then - argrequired shotKey $2 - curl $HOST/$2 -elif [ "$CMD" == "l" ]; then -# Link, pretty much a simple URL shortener - argrequired shotKey $2 - argrequired link $3 - contents="<html><meta http-equiv='refresh' content='0; url=$3'></html>" - curl --form-string text="$contents" $HOST/$2 -else - help -fi +case "$CMD" in + # Admin - Prompt for admin password, then run share + a) + printf "Admin key: " + read -s adminKey + echo + ;& + s) + argrequired filename $2 + shotKey=$3 + numdls=$4 + curlArgs="-H 'Content-Type: multipart/form-data' -F file=@$2" + if [ -n "$numdls" ]; then + curlArgs="$curlArgs -F 'numdls=$numdls'" + fi + if [ -n "$adminKey" ]; then + curlArgs="$curlArgs -H 'Admin-Key: $adminKey'" + fi + echo curl "$curlArgs" $HOST/$shotKey | bash + ;; + # Fetch - Get data + f) + argrequired shotKey $2 + curl $HOST/$2 + ;; + # Link, pretty much a simple URL shortener + l) + argrequired shotKey $2 + argrequired link $3 + contents="<html><meta http-equiv='refresh' content='0; url=$3'></html>" + curl --form-string text="$contents" $HOST/$2 + ;; + *) + help +esac