tcp.ac/txt.go

230 lines
5.9 KiB
Go

package main
import (
"errors"
"net"
"strings"
valid "github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/twharmon/gouid"
"golang.org/x/crypto/blake2b"
termbin "git.tcp.direct/kayos/putxt"
"git.tcp.direct/tcp.direct/tcp.ac/config"
)
func init() {
termbin.UseChannel = true
}
func incoming() {
var msg termbin.Message
select {
case msg = <-termbin.Msg:
switch msg.Type {
case termbin.Error:
log.Error().
Str("RemoteAddr", msg.RAddr).
Int("Size", msg.Size).
Msg(msg.Content)
case termbin.IncomingData:
log.Debug().
Str("RemoteAddr", msg.RAddr).
Int("Size", msg.Size).
Msg("termbin_data")
case termbin.Finish:
log.Debug().
Str("RemoteAddr", msg.RAddr).
Int("Size", msg.Size).
Msg(msg.Content)
case termbin.Debug:
log.Debug().
Str("RemoteAddr", msg.RAddr).
Int("Size", msg.Size).
Msg(msg.Content)
case termbin.Final:
log.Debug().
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, _ := db.With("hsh").Get(hash); ogTxt != nil {
if db.With("txt").Has(ogTxt) {
slog.Debug().Str("ogUid", string(ogTxt)).Msg("duplicate file found! returning original URL")
post := &Post{
entryType: Text,
uid: string(ogTxt),
key: "",
priv: false,
}
termbin.Reply <- termbin.Message{Type: termbin.ReturnURL, Content: post.URLString()}
return
}
}
// generate new uid and delete key
uid := gouid.String(config.UIDSize, gouid.MixedCaseAlphaNum)
key := gouid.String(config.DeleteKeySize, gouid.MixedCaseAlphaNum)
// lets make sure that we don't clash even though its highly unlikely
for uidRef, _ := db.With("txt").Get([]byte(uid)); uidRef != nil; {
slog.Info().Msg(" uid already exists! generating new...")
uid = gouid.String(config.UIDSize, gouid.MixedCaseAlphaNum)
}
for keyRef, _ := db.With("key").Get([]byte(key)); keyRef != nil; {
slog.Info().Msg(" delete key already exists! generating new...")
key = gouid.String(config.DeleteKeySize, gouid.MixedCaseAlphaNum)
}
db.With("hsh").Put(hash, []byte(uid))
uid = gouid.String(config.UIDSize, gouid.MixedCaseAlphaNum)
key = gouid.String(config.DeleteKeySize, gouid.MixedCaseAlphaNum)
for uidRef, _ := db.With("txt").Get([]byte(uid)); uidRef != nil; {
slog.Info().Msg(" uid already exists! generating new...")
uid = gouid.String(config.UIDSize, gouid.MixedCaseAlphaNum)
}
for keyRef, _ := db.With("key").Get([]byte(key)); keyRef != nil; {
slog.Info().Msg(" delete key already exists! generating new...")
key = gouid.String(config.DeleteKeySize, gouid.MixedCaseAlphaNum)
}
db.With("hsh").Put([]byte(hash), []byte(uid))
err := db.With("txt").Put([]byte(uid), b)
if err != nil {
slog.Error().Err(err).Msg("failed to save text!")
termbin.Reply <- termbin.Message{Type: termbin.ReturnURL, Content: "internal server error"}
return
}
err = db.With("key").Put([]byte(key), []byte("t."+uid))
if err != nil {
slog.Error().Msg("failed to save delete key!")
termbin.Reply <- termbin.Message{Type: termbin.ReturnError, Content: "internal server error"}
return
}
slog.Debug().Str("uid", uid).Msg("saved to database successfully, sending to Serve")
post := &Post{
entryType: Text,
uid: uid,
key: key,
priv: false,
}
termbin.Reply <- termbin.Message{Type: termbin.ReturnURL, Content: post.URLString()}
}
func txtView(c *gin.Context) {
raddr := net.ParseIP(c.RemoteIP())
if termbin.Rater.Check(&termbin.Identity{Actual: raddr}) {
errThrow(c, 429, errors.New("ratelimitted"), "too many requests")
return
}
sUid := strings.Split(c.Param("uid"), ".")
rUid := sUid[0]
fExt = ""
if len(sUid) > 1 {
fExt = strings.ToLower(sUid[1])
if fExt != "txt" {
errThrow(c, 400, errors.New("bad file extension"), "400")
return
}
}
// if it doesn't match the key size or it isn't alphanumeric - throw it out
if !valid.IsAlphanumeric(rUid) || len(rUid) != config.UIDSize {
errThrow(c, 400, errors.New("request discarded as invalid"), "400")
return
}
// query bitcask for the id
fBytes, _ := db.With("txt").Get([]byte(rUid))
if fBytes == nil {
errThrow(c, 404, errors.New("file not found"), "file not found")
return
}
file, err := termbin.Deflate(fBytes)
if err != nil {
errThrow(c, 500, err, "internal server error")
}
c.Data(200, "text/plain", file)
}
func txtDel(c *gin.Context) {
slog := log.With().
Str("caller", "txtDel").Logger()
slog.Debug().Msg("new_request")
if !validateKey(c.Param("key")) {
errThrow(c, 400, errors.New("bad key"), "400")
return
}
rKey := c.Param("key")
targetTxt, _ := db.With("key").Get([]byte(rKey))
if targetTxt == nil || !strings.Contains(string(targetTxt), "t.") {
errThrow(c, 400, errors.New("no txt delete entry found with provided key"), "400")
return
}
t := strings.Split(string(targetTxt), ".")[1]
if !db.With("txt").Has([]byte(t)) {
errThrow(c, 500, errors.New("image not found in database"), "500") // this shouldn't happen...?
return
}
if err := db.With("txt").Delete([]byte(t)); err != nil {
errThrow(c, 500, errors.New("delete failed"), "500")
return
}
if db.With("txt").Has([]byte(t)) {
slog.Error().Str("rkey", t).Msg("delete failed!?")
errThrow(c, 500, errors.New("delete failed, this shouldn't happen"), "500")
return
}
slog.Info().Str("rkey", t).Msg("Text file deleted successfully")
slog.Debug().Str("rkey", t).Msg("Removing delete key entry")
err := db.With("key").Delete([]byte(rKey))
if err != nil {
slog.Error().Str("rkey", t).Msg("Couldn't delete key")
}
c.JSON(200, "DELETE_SUCCESS")
}
func serveTermbin() {
go func() {
for {
incoming()
}
}()
split := strings.Split(config.TermbinListen, ":")
err := termbin.Listen(split[0], split[1])
if err != nil {
println(err.Error())
return
}
}