From 022073c9a746bae206d61d303853a6f24ff1c3d8 Mon Sep 17 00:00:00 2001 From: kayos Date: Wed, 28 Jul 2021 00:31:34 -0700 Subject: [PATCH] implement termbin functionality --- config.go | 65 ++++---- config.toml | 17 ++- globals.go | 29 ---- img.go | 112 ++++++-------- main.go | 1 + router.go | 29 ++-- termbin/main.go | 10 ++ termbin/ratelimit.go | 4 +- txt.go | 346 +++++++++++++++++++++++-------------------- 9 files changed, 311 insertions(+), 302 deletions(-) delete mode 100644 globals.go diff --git a/config.go b/config.go index 50e3b58..d9eb453 100644 --- a/config.go +++ b/config.go @@ -2,12 +2,42 @@ package main import ( "fmt" + "github.com/prologic/bitcask" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" + "os" "strconv" ) +////////////// global declarations +var ( + // datastores + imgDB *bitcask.Bitcask + hashDB *bitcask.Bitcask + keyDB *bitcask.Bitcask + urlDB *bitcask.Bitcask + txtDB *bitcask.Bitcask + + // config directives + debugBool bool + baseUrl string + webPort string + webIP string + dbDir string + logDir string + uidSize int + keySize int + txtPort string + + // utilitarian globals + err error + fn string + s string + i int + f *os.File +) + func configRead() { // name of the file // and the extension @@ -34,34 +64,17 @@ func configRead() { zerolog.SetGlobalLevel(zerolog.InfoLevel) } - // base URL of site - s = "http.baseurl" - baseUrl = viper.GetString(s) + baseUrl = viper.GetString("http.baseurl") - // bind port - s = "http.port" - i := viper.GetInt(s) - webPort = strconv.Itoa(i) // int looks cleaner in config + i := viper.GetInt("http.port") + webPort = strconv.Itoa(i) - // bind IP - s = "http.bindip" - webIP = viper.GetString(s) - - // database location (main storage) - s = "files.data" - dbDir = viper.GetString(s) - - // logfile location - s = "files.logs" - logDir = viper.GetString(s) - - // character count of unique IDs for posts - s = "img.uidsize" - uidSize = viper.GetInt(s) - - // size of generated unique delete keys - s = "img.delkeysize" - keySize = viper.GetInt(s) + webIP = viper.GetString("http.bindip") + dbDir = viper.GetString("files.data") + logDir = viper.GetString("files.logs") + uidSize = viper.GetInt("global.uidsize") + keySize = viper.GetInt("global.delkeysize") + txtPort = viper.GetString("txt.port") log.Debug().Str("baseUrl", baseUrl).Str("webIP", webIP).Str("webPort", webPort).Msg("Web") log.Debug().Str("logDir", logDir).Str("dbDir", dbDir).Msg("Filesystem") diff --git a/config.toml b/config.toml index 0837045..16b1ed6 100644 --- a/config.toml +++ b/config.toml @@ -1,16 +1,19 @@ title = "tcp.ac config" [global] -debug = false +debug = true +uidsize = 5 +delkeysize = 12 [http] -baseurl = "http://192.168.69.1/" -bindip = "192.168.69.1" -port = 80 +baseurl = "http://127.0.0.1:8080/" +basehost = "127.0.0.1" +bindip = "127.0.0.1" +port = 8080 -[img] -uidsize = 4 -delkeysize = 12 +[txt] +# string: port to listen on for termbin clone +port = "9999" [files] data = "/etc/tcpac/data/" diff --git a/globals.go b/globals.go deleted file mode 100644 index ad1ed17..0000000 --- a/globals.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import "github.com/prologic/bitcask" -import "os" - -////////////// global declarations -// datastores -var imgDB *bitcask.Bitcask -var hashDB *bitcask.Bitcask -var keyDB *bitcask.Bitcask -var urlDB *bitcask.Bitcask -var txtDB *bitcask.Bitcask - -// config directives -var debugBool bool -var baseUrl string -var webPort string -var webIP string -var dbDir string -var logDir string -var uidSize int -var keySize int - -// utilitarian globals -var err error -var fn string -var s string -var i int -var f *os.File diff --git a/img.go b/img.go index afd63ee..31808f8 100644 --- a/img.go +++ b/img.go @@ -18,46 +18,21 @@ import ( var fExt string -type Post struct { - Type string - Uid string - Key string - Priv bool -} - -func (p Post) Serve(c *gin.Context) { - url := baseUrl + p.Type + "/" + string(p.Uid) - priv := "no" - keyurl := "" - if p.Key != "" { - keyurl = baseUrl + "d/" + p.Type + "/" + p.Key - } - - if p.Priv == true { - priv = "yes" - } - - log.Info().Str("type", p.Type).Str("uid", p.Uid).Str("key", p.Key).Str("private", priv).Msg("success") - c.JSON(201, gin.H{"Imgurl": url, "ToDelete": keyurl}) - return -} - func imgDel(c *gin.Context) { - fn = "imgDel" + slog := log.With().Str("caller", "imgView").Logger() - log.Debug().Str("func", fn).Msg("Request received!") // received request + slog.Debug().Msg("Request received!") // received request rKey := c.Param("key") - // if it doesn't match the key size or it isn't alphanumeric - throw it out - if len(rKey) != keySize || !valid.IsAlphanumeric(rKey) { - log.Error().Str("func", fn).Msg("delete request failed sanity check!") + if !validateKey(rKey) { + slog.Error().Msg("delete request failed sanity check!") errThrow(c, 400, "400", "400") return } targetImg, _ := keyDB.Get([]byte(rKey)) if targetImg == nil || !strings.Contains(string(targetImg), "i.") { - log.Error().Str("func", fn).Str("rkey", rKey).Msg("no img delete entry found with provided key") + slog.Error().Str("rkey", rKey).Msg("no img delete entry found with provided key") errThrow(c, 400, "400", "400") return } @@ -65,28 +40,28 @@ func imgDel(c *gin.Context) { finalTarget := strings.Split(string(targetImg), ".") if !imgDB.Has([]byte(finalTarget[1])) { - log.Error().Str("func", fn).Str("rkey", rKey).Msg("corresponding image not found in database!") + slog.Error().Str("rkey", rKey).Msg("corresponding image not found in database!") errThrow(c, 500, "500", "500") // this shouldn't happen...? return } err := imgDB.Delete([]byte(finalTarget[1])) if err != nil { - log.Error().Str("func", fn).Str("rkey", finalTarget[1]).Msg("delete failed!") + slog.Error().Str("rkey", finalTarget[1]).Msg("delete failed!") errThrow(c, 500, "500", "500") return } if imgDB.Has([]byte(finalTarget[1])) { - log.Error().Str("func", fn).Str("rkey", finalTarget[1]).Msg("delete failed!?") + slog.Error().Str("rkey", finalTarget[1]).Msg("delete failed!?") errThrow(c, 500, "500", "500") return } - log.Info().Str("func", fn).Str("rkey", finalTarget[1]).Msg("Image file deleted successfully") - log.Debug().Str("func", fn).Str("rkey", finalTarget[1]).Msg("Removing delete key entry") + slog.Info().Str("rkey", finalTarget[1]).Msg("Image file deleted successfully") + slog.Debug().Str("rkey", finalTarget[1]).Msg("Removing delete key entry") err = keyDB.Delete([]byte(rKey)) if err != nil { - log.Error().Str("func", fn).Str("rkey", finalTarget[1]).Msg("Couldn't delete key") + slog.Error().Str("rkey", finalTarget[1]).Msg("Couldn't delete key") // it would be insane to try and delete the hash here } // if someone is uploading this image again after del c.JSON(200, "DELETE_SUCCESS") // and the file corresponding to the hash no longer exists @@ -94,18 +69,18 @@ func imgDel(c *gin.Context) { } func imgView(c *gin.Context) { - fn = "imgView" + slog := log.With().Str("caller", "imgView").Logger() // the user can access their image with or without a file extension in URI - log.Debug().Str("func", fn).Msg("request received") // however it must be a valid extension (more checks further down) + slog.Debug().Msg("request received") // however it must be a valid extension (more checks further down) sUid := strings.Split(c.Param("uid"), ".") rUid := sUid[0] if len(sUid) > 1 { fExt = strings.ToLower(sUid[1]) - log.Debug().Str("func", fn).Str("ext", fExt).Msg("detected file extension") + slog.Debug().Str("ext", fExt).Msg("detected file extension") if fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif" { - log.Error().Str("func", fn).Msg("Bad file extension!") + slog.Error().Msg("Bad file extension!") errThrow(c, 400, "400", "400") return } @@ -115,18 +90,18 @@ func imgView(c *gin.Context) { // if it doesn't match the key size or it isn't alphanumeric - throw it out if !valid.IsAlphanumeric(rUid) || len(rUid) != uidSize { - log.Error().Str("func", fn).Msg("request discarded as invalid") // these limits should be variables eventually + slog.Error().Msg("request discarded as invalid") // these limits should be variables eventually errThrow(c, 400, "400", "400") return } // now that we think its a valid request we will query - log.Debug().Str("func", fn).Str("rUid", rUid).Msg("request validated") + slog.Debug().Str("rUid", rUid).Msg("request validated") // query bitcask for the id fBytes, _ := imgDB.Get([]byte(rUid)) if fBytes == nil { - log.Error().Str("func", fn).Str("rUid", rUid).Msg("no corresponding file for this id") + slog.Error().Str("rUid", rUid).Msg("no corresponding file for this id") errThrow(c, 404, "404", "File not found") return } @@ -137,16 +112,16 @@ func imgView(c *gin.Context) { if !ok { // extra sanity check to make sure we don't serve non-image data that somehow got into the database errThrow(c, http.StatusBadRequest, "400", "400") - log.Error().Str("func", fn).Str("rUid", rUid).Msg("the file we grabbed is not an image!?") + slog.Error().Str("rUid", rUid).Msg("the file we grabbed is not an image!?") return } else { // seems like its an image, we will proceed - log.Debug().Str("func", fn).Str("rUid", rUid).Str("imageFormat", imageFormat).Msg("Image format detected") + slog.Debug().Str("rUid", rUid).Str("imageFormat", imageFormat).Msg("Image format detected") } // additional extension sanity check - if they're gonna use an extension it needs to be the right one if fExt != "nil" && fExt != imageFormat { - log.Error().Str("func", fn).Str("rUid", rUid).Msg("requested file extension does not match filetype") + slog.Error().Str("rUid", rUid).Msg("requested file extension does not match filetype") errThrow(c, 400, "400", "400") return } @@ -156,14 +131,16 @@ func imgView(c *gin.Context) { contentType := "image/" + imageFormat c.Data(200, contentType, fBytes) - log.Info().Str("func", fn).Str("rUid", rUid).Msg("Successful upload") + slog.Info().Str("rUid", rUid).Msg("Successful upload") } func imgPost(c *gin.Context) { - fn = "imgPost" - var Scrubbed []byte - var priv bool = false - var key string = "" + slog := log.With().Str("caller", "imgPost").Logger() + var ( + Scrubbed []byte + priv bool = false + key string = "" + ) // check if incoming POST data is invalid f, err := c.FormFile("upload") @@ -171,7 +148,7 @@ func imgPost(c *gin.Context) { errThrow(c, http.StatusBadRequest, err.Error(), "400") } - log.Debug().Str("func", fn).Str("filename", f.Filename).Msg("[+] New upload") + slog.Debug().Str("filename", f.Filename).Msg("[+] New upload") // read the incoming file into an io file reader file, err := f.Open() @@ -180,38 +157,38 @@ func imgPost(c *gin.Context) { } // verify the incoming file is an image - log.Debug().Str("func", fn).Msg("verifying file is an image") + slog.Debug().Msg("verifying file is an image") imageFormat, ok := checkImage(file) if !ok { errThrow(c, http.StatusBadRequest, "400", "input does not appear to be an image") return } else { - log.Debug().Str("func", fn).Msg("image file type detected") + slog.Debug().Msg("image file type detected") } // dump this into a byte object and scrub it // TO-DO: Write our own function for scrubbing exif - log.Debug().Str("func", fn).Msg("dumping byte form of file") + slog.Debug().Msg("dumping byte form of file") fbytes, err := ioutil.ReadAll(file) if imageFormat != "gif" { - log.Debug().Str("func", fn).Err(err).Msg("error scrubbing exif") + slog.Debug().Err(err).Msg("error scrubbing exif") Scrubbed, err = exifremove.Remove(fbytes) if err != nil { errThrow(c, http.StatusInternalServerError, err.Error(), "error scrubbing exif") return } } else { - log.Debug().Str("func", fn).Msg("skipping exif scrub for gif image") + slog.Debug().Msg("skipping exif scrub for gif image") Scrubbed = fbytes } // time to start checking for duplicates - log.Debug().Str("func", fn).Msg("calculating blake2b checksum") + slog.Debug().Msg("calculating blake2b checksum") Hashr, _ := blake2b.New(64, nil) Hashr.Write(Scrubbed) hash := Hashr.Sum(nil) - log.Debug().Str("func", fn).Msg("checking for duplicate files") + slog.Debug().Msg("checking for duplicate files") // the keys (stored in memory) for hashDb are hashes // making it quick to find duplicates. the value is the uid @@ -220,9 +197,9 @@ func imgPost(c *gin.Context) { // if we get a hit we redirect them to the original image file with no delete key if imgRef != nil { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("duplicate checksum in hash database, checking if file still exists...") + slog.Debug().Str("ogUid", ogUid).Msg("duplicate checksum in hash database, checking if file still exists...") if imgDB.Has(imgRef) { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("duplicate file found! returning original URL") + slog.Debug().Str("ogUid", ogUid).Msg("duplicate file found! returning original URL") post := &Post{ Type: "i", @@ -235,12 +212,12 @@ func imgPost(c *gin.Context) { return } else { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("stale hash found, deleting entry...") + slog.Debug().Str("ogUid", ogUid).Msg("stale hash found, deleting entry...") hashDB.Delete(hash) } } - log.Info().Str("func", fn).Msg("no duplicate images found, generating uid and delete key") + slog.Info().Msg("no duplicate images found, generating uid and delete key") // generate new uid and delete key uid := gouid.String(uidSize, gouid.MixedCaseAlphaNum) @@ -248,11 +225,11 @@ func imgPost(c *gin.Context) { // lets make sure that we don't clash even though its highly unlikely for uidRef, _ := imgDB.Get([]byte(uid)); uidRef != nil; { - log.Info().Str("func", fn).Msg(" uid already exists! generating new...") + slog.Info().Msg(" uid already exists! generating new...") uid = gouid.String(uidSize, gouid.MixedCaseAlphaNum) } for keyRef, _ := keyDB.Get([]byte(key)); keyRef != nil; { - log.Info().Str("func", fn).Msg(" delete key already exists! generating new...") + slog.Info().Msg(" delete key already exists! generating new...") key = gouid.String(keySize, gouid.MixedCaseAlphaNum) } @@ -260,7 +237,7 @@ func imgPost(c *gin.Context) { hashDB.Put([]byte(hash), []byte(uid)) // insert actual file to database - log.Debug().Str("func", fn).Str("uid", uid).Msg("saving file to database") + slog.Debug().Str("uid", uid).Msg("saving file to database") err = imgDB.Put([]byte(uid), []byte(Scrubbed)) if err != nil { errThrow(c, 500, err.Error(), "upload failed") @@ -276,7 +253,7 @@ func imgPost(c *gin.Context) { } // good to go, send them to the finisher function - log.Debug().Str("func", fn).Str("uid", uid).Msg("saved to database successfully, sending to Serve") + slog.Debug().Str("uid", uid).Msg("saved to database successfully, sending to Serve") post := &Post{ Type: "i", @@ -285,6 +262,7 @@ func imgPost(c *gin.Context) { Priv: priv, } + post.Serve(c) } diff --git a/main.go b/main.go index 16a12f8..054eae3 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func init() { } func main() { + go serveTermbin() // see router.go httpRouter() } diff --git a/router.go b/router.go index 6747ebb..2a2ffd2 100644 --- a/router.go +++ b/router.go @@ -3,7 +3,7 @@ package main import ( "encoding/base64" "github.com/gin-contrib/gzip" - "github.com/gin-contrib/logger" +// "github.com/gin-contrib/logger" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" ) @@ -41,7 +41,7 @@ func httpRouter() { router := gin.New() - router.Use(logger.SetLogger()) + //router.Use(logger.SetLogger()) router.MaxMultipartMemory = 16 << 20 // crude POST limit (fix this) @@ -57,26 +57,33 @@ func httpRouter() { imgR := router.Group("/i") { - imgR.GET("/", func(c *gin.Context) { c.String(200, "") }) + imgR.GET("/", placeHolder) // put looks nicer even though its actually POST imgR.POST("/put", imgPost) imgR.GET("/:uid", imgView) } + txtR := router.Group("/t") + { + txtR.GET("/", placeHolder) + txtR.GET("/:uid", txtView) + } + delR := router.Group("/d") { delR.GET("/i/:key", imgDel) + delR.GET("/t/:key", txtDel) } -// txtR := router.Group("/t") -// { -// txtR.POST("/put", txtPost) -// } + // txtR := router.Group("/t") + // { + // txtR.POST("/put", txtPost) + // } -// urlR := router.Group("/u") -// { -// urlR.POST("/put", urlPost) -// } + // urlR := router.Group("/u") + // { + // urlR.POST("/put", urlPost) + // } log.Info().Str("webIP", webIP).Str("webPort", webPort).Msg("done; tcp.ac is live.") router.Run(webIP + ":" + webPort) diff --git a/termbin/main.go b/termbin/main.go index 5de6a04..070fe11 100644 --- a/termbin/main.go +++ b/termbin/main.go @@ -10,6 +10,8 @@ import ( var ( // Msg is a channel for status information during concurrent server operations Msg chan Message + // Reply is a channel to receive messages to send our client upon completion + Reply chan Message // Timeout is the read timeout for incoming data in seconds Timeout int = 4 // Max size for incoming data (default: 3 << 30 or 3 MiB) @@ -31,6 +33,7 @@ type Message struct { func init() { Msg = make(chan Message) + Reply = make(chan Message) } func termStatus(m Message) { @@ -113,6 +116,13 @@ func serve(c net.Conn) { } termStatus(Message{Type: "FINAL", RAddr: c.RemoteAddr().String(), Size: len(final), Bytes: final, Content: "SUCCESS"}) + url := <- Reply + switch url.Type { + case "URL": + c.Write([]byte(url.Content)) + case "ERROR": + c.Write([]byte("ERROR: " + url.Content)) + } c.Close() } diff --git a/termbin/ratelimit.go b/termbin/ratelimit.go index fe21a27..ffe6f4c 100644 --- a/termbin/ratelimit.go +++ b/termbin/ratelimit.go @@ -10,11 +10,11 @@ var ( // Users contains all remote addresses currently being ratelimited Users *cache.Cache // Rate is the amount of seconds between each post from an IP address - Rate time.Duration = 30 + Rate time.Duration = 8 ) func init() { - Users = cache.New(Rate*time.Second, 30*time.Second) + Users = cache.New(Rate*time.Second, 10*time.Second) } func isThrottled(addr string) bool { diff --git a/txt.go b/txt.go index 75fdb37..890b3e2 100644 --- a/txt.go +++ b/txt.go @@ -3,226 +3,252 @@ package main import ( valid "github.com/asaskevich/govalidator" "github.com/gin-gonic/gin" + cache "github.com/patrickmn/go-cache" "github.com/rs/zerolog/log" "github.com/twharmon/gouid" "golang.org/x/crypto/blake2b" - "net/http" "strings" + "tcp.ac/termbin" + "time" ) -var hash []byte -var uid string -var key string +var ( + Users *cache.Cache +) -func txtFin(c *gin.Context, id string, key string) { - txturl := baseUrl + "t/" + string(id) - keyurl := "duplicate" - if key != "nil" { - keyurl = baseUrl + "d/t/" + string(key) - } - - log.Info().Str("func", "txtPost").Str("id", id).Str("status", "201").Str("txturl", txturl).Str("keyurl", keyurl).Msg("success") - c.JSON(201, gin.H{"delkey": keyurl, "txturl": txturl}) - return +func init() { + termbin.UseChannel = true + Users = cache.New(termbin.Rate*time.Second, 30*time.Second) } -func txtDel(c *gin.Context) { - fn = "txtDel" +func txtThrottled(addr string) bool { + if _, ok := Users.Get(addr); !ok { + Users.Set(addr, 1, 0) + return false + } else { + return true + } +} - // make sure its the proper amount of characters and is alphanumeric - log.Info().Str("func", fn).Msg("delete request received") - rKey := c.Param("key") +func incoming() { + var msg termbin.Message - if len(rKey) != keySize || !valid.IsAlphanumeric(rKey) { - log.Error().Err(err).Str("func", fn).Msg("delete request failed sanity check!") - errThrow(c, 400, "400", "400") - return + select { + case msg = <-termbin.Msg: + switch msg.Type { + case "ERROR": + log.Error(). + Str("RemoteAddr", msg.RAddr). + Int("Size", msg.Size). + Msg(msg.Content) + case "INCOMING_DATA": + log.Debug(). + Str("RemoteAddr", msg.RAddr). + Int("Size", msg.Size). + Msg("termbin_data") + case "FINISH": + log.Debug(). + Str("RemoteAddr", msg.RAddr). + Int("Size", msg.Size). + Msg(msg.Content) + + case "DEBUG": + log.Debug(). + Str("RemoteAddr", msg.RAddr). + Int("Size", msg.Size). + Msg(msg.Content) + + case "FINAL": + log.Info(). + Str("RemoteAddr", msg.RAddr). + Int("Size", msg.Size). + Msg(msg.Content) + + termPost(msg.Bytes) + } + } +} + +func termPost(b []byte) { + slog := log.With().Str("caller", "termPost").Logger() + Hashr, _ := blake2b.New(64, nil) + Hashr.Write(b) + hash := Hashr.Sum(nil) + if ogTxt, _ := hashDB.Get(hash); ogTxt != nil { + if txtDB.Has(ogTxt) { + slog.Debug().Str("ogUid", string(ogTxt)).Msg("duplicate file found! returning original URL") + post := &Post{ + Type: "t", + Uid: string(ogTxt), + Key: "", + Priv: false, + } + termbin.Reply <- termbin.Message{Type: "URL", Content: post.URLString()} + return + } } - // first we see if the key even exists, and if it does, is it a txt key (vs txt or url) - targettxt, _ := keyDB.Get([]byte(rKey)) - if targettxt == nil || !strings.Contains(string(targettxt), "t.") { - log.Error().Err(err).Str("func", fn).Str("rkey", rKey).Msg("no txt delete entry found with provided key") - errThrow(c, 400, "400", "400") - return + // generate new uid and delete key + uid := gouid.String(uidSize, gouid.MixedCaseAlphaNum) + key := gouid.String(keySize, gouid.MixedCaseAlphaNum) + + // lets make sure that we don't clash even though its highly unlikely + for uidRef, _ := txtDB.Get([]byte(uid)); uidRef != nil; { + slog.Info().Msg(" uid already exists! generating new...") + uid = gouid.String(uidSize, gouid.MixedCaseAlphaNum) + } + for keyRef, _ := keyDB.Get([]byte(key)); keyRef != nil; { + slog.Info().Msg(" delete key already exists! generating new...") + key = gouid.String(keySize, gouid.MixedCaseAlphaNum) } - // seperate the key from the indicator to get the actual txt delete key - finalTarget := strings.Split(string(targettxt), ".") + hashDB.Put([]byte(hash), []byte(uid)) - // somehow we have a key that doesn't correspond with with an actual entry - if !txtDB.Has([]byte(finalTarget[1])) { - log.Error().Err(err).Str("func", fn).Str("rkey", rKey).Msg("corresponding text entry not found in database!") - errThrow(c, 500, "500", "500") - return + uid = gouid.String(uidSize, gouid.MixedCaseAlphaNum) + key = gouid.String(keySize, gouid.MixedCaseAlphaNum) + + for uidRef, _ := txtDB.Get([]byte(uid)); uidRef != nil; { + slog.Info().Msg(" uid already exists! generating new...") + uid = gouid.String(uidSize, gouid.MixedCaseAlphaNum) + } + for keyRef, _ := keyDB.Get([]byte(key)); keyRef != nil; { + slog.Info().Msg(" delete key already exists! generating new...") + key = gouid.String(keySize, gouid.MixedCaseAlphaNum) } - // if we passed all those checks, delete the txt entry from the database - err := txtDB.Delete([]byte(finalTarget[1])) + hashDB.Put([]byte(hash), []byte(uid)) - // failed to delete it? shouldn't happen + err = txtDB.Put([]byte(uid), b) if err != nil { - log.Error().Err(err).Str("func", fn).Str("rkey", finalTarget[1]).Msg("delete failed!") - errThrow(c, 500, "500", "500") + slog.Error().Msg("failed to save text!") + termbin.Reply <- termbin.Message{Type: "ERROR", Content: "internal server error"} return } - - // make sure its actually gone - if txtDB.Has([]byte(finalTarget[1])) { - log.Error().Err(err).Str("func", fn).Str("rkey", finalTarget[1]).Msg("delete failed!?") - errThrow(c, 500, "500", "500") - return - } - - // remove the delete key entry but not the hash (see below) - log.Info().Str("func", fn).Str("rkey", finalTarget[1]).Msg("text deleted successfully") - log.Debug().Str("func", fn).Str("rkey", finalTarget[1]).Msg("Removing delete key entry") - err = keyDB.Delete([]byte(rKey)) + err = keyDB.Put([]byte(key), []byte("t."+uid)) if err != nil { - log.Error().Err(err).Str("func", fn).Str("rkey", finalTarget[1]).Msg("Couldn't delete key") + slog.Error().Msg("failed to save delete key!") + termbin.Reply <- termbin.Message{Type: "ERROR", Content: "internal server error"} + return } - c.JSON(200, "DELETE_SUCCESS") - // it would be insane to try and delete the hash here - // if someone is uploading this text again after del - // and the file corresponding to the hash no longer exists - // we will delete the hash entry then and re-add then + slog.Debug().Str("uid", uid).Msg("saved to database successfully, sending to Serve") + + post := &Post{ + Type: "t", + Uid: uid, + Key: key, + Priv: false, + } + + termbin.Reply <- termbin.Message{Type: "URL", Content: post.URLString()} } func txtView(c *gin.Context) { - fn = "txtView" - // the user can access their text with or without a file extension in URI - // however it must be a valid extension (more checks further down) - log.Info().Str("func", fn).Msg("request received") + slog := log.With().Str("caller", "txtView").Logger() + + raddr, _ := c.RemoteIP() + if txtThrottled(raddr.String()) { + errThrow(c, 429, "ratelimited", "too many requests") + return + } + + // the user can access their image with or without a file extension in URI + slog.Debug().Msg("request received") // however it must be a valid extension (more checks further down) + sUid := strings.Split(c.Param("uid"), ".") rUid := sUid[0] + fExt = "" + if len(sUid) > 1 { fExt = strings.ToLower(sUid[1]) - log.Debug().Str("func", fn).Str("ext", fExt).Msg("detected file extension") if fExt != "txt" { - log.Error().Err(err).Str("func", fn).Msg("Bad file extension!") + slog.Error().Msg("bad file extension") errThrow(c, 400, "400", "400") return } - } else { - fExt = "nil" } + // if it doesn't match the key size or it isn't alphanumeric - throw it out if !valid.IsAlphanumeric(rUid) || len(rUid) != uidSize { - log.Error().Err(err).Str("func", fn).Msg("request discarded as invalid") + slog.Error().Msg("request discarded as invalid") // these limits should be variables eventually errThrow(c, 400, "400", "400") return } - // now that we think its a valid request we will query - log.Debug().Str("func", fn).Str("rUid", rUid).Msg("request validated") + // query bitcask for the id fBytes, _ := txtDB.Get([]byte(rUid)) if fBytes == nil { - log.Error().Err(err).Str("func", fn).Str("rUid", rUid).Msg("no corresponding file for this id") - errThrow(c, 404, "404", "File not found") + slog.Error().Str("rUid", rUid).Msg("no corresponding file for this id") + errThrow(c, 404, "404", "file not found") return } - c.Data(200, "text/plain", fBytes) + file, _ := termbin.Deflate(fBytes) + c.Data(200, "text/plain", file) - log.Info().Str("func", fn).Str("rUid", rUid).Msg("Success") + slog.Info().Str("rUid", rUid).Msg("success") } -func txtPost(c *gin.Context) { - fn = "txtPost" +func txtDel(c *gin.Context) { + slog := log.With(). + Str("caller", "txtDel").Logger() - t := c.PostForm("txt") - priv := c.PostForm("priv") + slog.Debug().Msg("new_request") // received request - tbyte := []byte(t) + if !validateKey(c.Param("key")) { + errThrow(c, 400, "400", "400") + return + } - if err != nil { - log.Error().Err(err).Str("fn", fn).Msg("Oh?") + rKey := c.Param("key") + + targetTxt, _ := keyDB.Get([]byte(rKey)) + if targetTxt == nil || !strings.Contains(string(targetTxt), "t.") { + slog.Warn().Str("rkey", rKey).Msg("no txt delete entry found with provided key") + errThrow(c, 400, "400", "400") + return + } + + t := strings.Split(string(targetTxt), ".")[1] + + if !txtDB.Has([]byte(t)) { + slog.Warn().Str("rkey", rKey).Msg("corresponding image not found in database!") + errThrow(c, 500, "500", "500") // this shouldn't happen...? + return + } + if err := txtDB.Delete([]byte(t)); err != nil { + slog.Error().Str("rkey", t).Msg("delete failed!") errThrow(c, 500, "500", "500") return } - if len(t) == 0 { - log.Warn().Str("fn", fn).Msg("received an empty request") - errThrow(c, 400, "400", "400") + if txtDB.Has([]byte(t)) { + slog.Error().Str("rkey", t).Msg("delete failed!?") + errThrow(c, 500, "500", "500") return } - if c.ContentType() != "text/plain" { - log.Warn().Str("fn", fn).Str("ContentType", c.ContentType()).Msg("received a non-text content-type") - errThrow(c, 400, "400", "400") - return - } - - // an optional switch for a privnote style burn after read - // priv := c.GetBool("private") - + slog.Info().Str("rkey", t).Msg("Image file deleted successfully") + slog.Debug().Str("rkey", t).Msg("Removing delete key entry") + err = keyDB.Delete([]byte(rKey)) if err != nil { - // incoming POST data is invalid - errThrow(c, http.StatusBadRequest, err.Error(), "400") // 400 bad request - } - - log.Debug().Str("func", fn).Msg("New paste") - - if priv == "on" { - // check for dupes - log.Debug().Str("func", fn).Msg("calculating blake2b checksum") - Hashr, _ := blake2b.New(64, nil) - Hashr.Write(tbyte) - hash := Hashr.Sum(nil) - log.Debug().Str("func", fn).Msg("Checking for duplicate's in database") - txtRef, _ := txtDB.Get(hash) - ogUid := string(txtRef) - - if txtRef != nil { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("duplicate checksum in hash database, checking if file still exists...") - if txtDB.Has(txtRef) { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("duplicate file found! returning original URL") - // they weren't the original uploader so they don't get a delete key - txtFin(c, ogUid, "nil") - return - } else { - log.Debug().Str("func", fn).Str("ogUid", ogUid).Msg("stale hash found, deleting entry...") - hashDB.Delete(hash) - } - } - log.Info().Str("func", fn).Msg("no duplicate txts found, generating uid and delete key") - - // generate identifier and delete key based on configured sizes - uid := gouid.String(uidSize, gouid.MixedCaseAlphaNum) - key := gouid.String(keySize, gouid.MixedCaseAlphaNum) - - // lets make sure that we don't clash even though its highly unlikely - for uidRef, _ := txtDB.Get([]byte(uid)); uidRef != nil; { - log.Info().Str("func", fn).Msg("uid already exists! generating another...") - uid = gouid.String(uidSize, gouid.MixedCaseAlphaNum) - } - for keyRef, _ := keyDB.Get([]byte(key)); keyRef != nil; { - log.Info().Str("func", fn).Msg(" delete key already exists! generating another...") - key = gouid.String(keySize, gouid.MixedCaseAlphaNum) - } - - // save checksum to db to prevent dupes in the future - hashDB.Put([]byte(hash), []byte(uid)) - - log.Debug().Str("func", fn).Str("uid", uid).Msg("saving file to database") - - err = txtDB.Put([]byte(uid), []byte(tbyte)) - if err != nil { - errThrow(c, 500, err.Error(), "upload failed") - return - } - - // add delete key to database with txt prefix - err = keyDB.Put([]byte(key), []byte("t."+uid)) - if err != nil { - errThrow(c, http.StatusInternalServerError, err.Error(), "internal error") - return - } - } - - log.Debug().Str("func", fn).Str("uid", uid).Msg("saved to database successfully, sending to txtFin") - - txtFin(c, uid, key) - + slog.Error().Str("rkey", t).Msg("Couldn't delete key") + // it would be insane to try and delete the hash here + } // if someone is uploading this image again after del + c.JSON(200, "DELETE_SUCCESS") // and the file corresponding to the hash no longer exists + // we will delete the hash entry then and re-add then +} + +//func serveTermbin() { +func serveTermbin() { + go func() { + for { + incoming() + } + }() + err := termbin.Listen("", txtPort) + if err != nil { + println(err.Error()) + return + } }