forked from tcp.direct/tcp.ac
1
0
Fork 0

implement termbin functionality

This commit is contained in:
kayos 2021-07-28 00:31:34 -07:00
parent d79575721a
commit 022073c9a7
9 changed files with 311 additions and 302 deletions

View File

@ -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")

View File

@ -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/"

View File

@ -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

112
img.go
View File

@ -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)
}

View File

@ -62,6 +62,7 @@ func init() {
}
func main() {
go serveTermbin()
// see router.go
httpRouter()
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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 {

346
txt.go
View File

@ -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
}
}