v1: Stabilize overhaul
This commit is contained in:
parent
25b8e048fb
commit
c3cd9e9f04
81
common.go
81
common.go
|
@ -7,9 +7,13 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/json-iterator/go"
|
||||
|
||||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
type EntryType uint8
|
||||
|
||||
const (
|
||||
|
@ -22,6 +26,7 @@ const (
|
|||
|
||||
type UserID uint64
|
||||
|
||||
// Entry FIXME: not currently used
|
||||
type Entry interface {
|
||||
TypeCode() string
|
||||
UID() string
|
||||
|
@ -48,9 +53,9 @@ func (p *Post) setLogger() {
|
|||
p.log = &pl
|
||||
}
|
||||
|
||||
func NewImg(data []byte, priv bool) *Post {
|
||||
func newPost(entryType EntryType, data []byte, priv bool) *Post {
|
||||
p := &Post{
|
||||
entryType: Image,
|
||||
entryType: entryType,
|
||||
priv: priv,
|
||||
data: data,
|
||||
}
|
||||
|
@ -58,37 +63,35 @@ func NewImg(data []byte, priv bool) *Post {
|
|||
return p
|
||||
}
|
||||
|
||||
func NewTxt(data []byte, priv bool) *Post {
|
||||
p := &Post{
|
||||
entryType: Text,
|
||||
priv: priv,
|
||||
data: data,
|
||||
func typeToString(t EntryType, long bool) string {
|
||||
switch t {
|
||||
case Image:
|
||||
if long {
|
||||
return "img"
|
||||
}
|
||||
return "i"
|
||||
case Text:
|
||||
if long {
|
||||
return "txt"
|
||||
}
|
||||
return "t"
|
||||
case URL:
|
||||
if long {
|
||||
return "url"
|
||||
}
|
||||
return "u"
|
||||
case Custom:
|
||||
if long {
|
||||
return "custom"
|
||||
}
|
||||
return "c"
|
||||
default:
|
||||
panic("unknown entry type")
|
||||
}
|
||||
p.setLogger()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Post) TypeCode(long bool) (code string) {
|
||||
switch p.entryType {
|
||||
case Image:
|
||||
code = "i"
|
||||
if long {
|
||||
code += "mg"
|
||||
}
|
||||
case Text:
|
||||
code = "t"
|
||||
if long {
|
||||
code += "xt"
|
||||
}
|
||||
case URL:
|
||||
code = "u"
|
||||
if long {
|
||||
code += "rl"
|
||||
}
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
return
|
||||
return typeToString(p.entryType, long)
|
||||
}
|
||||
|
||||
func (p *Post) UID() string {
|
||||
|
@ -112,18 +115,15 @@ func (p *Post) Log() *zerolog.Logger {
|
|||
}
|
||||
|
||||
func validateKey(rKey string) bool {
|
||||
// if it doesn't match the key size or it isn't alphanumeric - throw it out
|
||||
if len(rKey) != config.DeleteKeySize || !valid.IsAlphanumeric(rKey) {
|
||||
log.Warn().Str("rKey", rKey).
|
||||
Msg("delete request failed sanity check!")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Post) URLString() string {
|
||||
var keyurl string = ""
|
||||
url := config.BaseURL + p.TypeCode(false) + "/" + string(p.UID())
|
||||
var keyurl = ""
|
||||
url := config.BaseURL + p.TypeCode(false) + "/" + p.UID()
|
||||
if p.DelKey() != "" {
|
||||
keyurl = config.BaseURL + "d/" + p.TypeCode(false) + "/" + p.DelKey()
|
||||
}
|
||||
|
@ -137,8 +137,8 @@ func (p *Post) URLString() string {
|
|||
}
|
||||
|
||||
func (p *Post) NewPostResponse(responder any) {
|
||||
var keyurl string = ""
|
||||
url := config.BaseURL + p.TypeCode(false) + "/" + string(p.UID())
|
||||
var keyurl = ""
|
||||
url := config.BaseURL + p.TypeCode(false) + "/" + p.UID()
|
||||
if p.DelKey() != "" {
|
||||
keyurl = config.BaseURL + "d/" + p.TypeCode(false) + "/" + p.DelKey()
|
||||
}
|
||||
|
@ -159,6 +159,15 @@ func (p *Post) NewPostResponse(responder any) {
|
|||
if cg, ginok := responder.(*gin.Context); ginok {
|
||||
cg.JSON(201, gin.H{urlString: url, delString: keyurl})
|
||||
}
|
||||
if ct, tdok := responder.(*textValidator); tdok {
|
||||
js, err := json.Marshal(gin.H{urlString: url, delString: keyurl})
|
||||
if err != nil {
|
||||
log.Error().Interface("post", p).
|
||||
Err(err).Msg("json marshal failed")
|
||||
ct.out = []byte("{\"error\":\"json marshal failed\"}")
|
||||
}
|
||||
ct.out = js
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func init() {
|
|||
|
||||
var (
|
||||
BaseURL, HTTPPort, HTTPBind, DBDir, LogDir,
|
||||
TermbinListen, UnixSocketPath string
|
||||
TermbinListen, UnixSocketPath, AdminKey string
|
||||
UIDSize, DeleteKeySize, KVMaxKeySizeMB,
|
||||
KVMaxValueSizeMB int
|
||||
UnixSocketPermissions uint32
|
||||
|
@ -220,6 +220,7 @@ func getConfigPaths() (paths []string) {
|
|||
return
|
||||
}
|
||||
|
||||
// TODO: use this?
|
||||
func loadCustomConfig(path string) {
|
||||
/* #nosec */
|
||||
f, err := os.Open(path)
|
||||
|
@ -267,6 +268,7 @@ func processOpts() {
|
|||
"logger.directory": &LogDir,
|
||||
"other.termbin_listen": &TermbinListen,
|
||||
"other.base_url": &BaseURL,
|
||||
"admin.key": &AdminKey,
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(BaseURL, "/") {
|
||||
|
|
|
@ -5,11 +5,12 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
|
||||
"git.tcp.direct/kayos/common/entropy"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
configSections = []string{"logger", "http", "data", "other"}
|
||||
configSections = []string{"logger", "http", "data", "other", "admin"}
|
||||
defNoColor = false
|
||||
)
|
||||
|
||||
|
@ -42,6 +43,9 @@ func initDefaults() {
|
|||
"termbin_listen": "127.0.0.1:9999",
|
||||
"base_url": "http://localhost:8080/",
|
||||
},
|
||||
"admin": {
|
||||
"key": entropy.RandStrWithUpper(24),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
func adminBypass(c *gin.Context, adminKey string, t EntryType) {
|
||||
slog := log.With().Str("caller", "admin_del").Str("type", typeToString(t, true)).Logger()
|
||||
slog.Trace().Msg("admin key attempt")
|
||||
if config.AdminKey == "" {
|
||||
errThrow(c, 404, errors.New("admin key not configured"), message404)
|
||||
return
|
||||
}
|
||||
rKey := c.Param("key")
|
||||
if adminKey != config.AdminKey {
|
||||
if zerolog.GlobalLevel() == zerolog.TraceLevel {
|
||||
slog.Warn().Str("wanted", config.AdminKey).Str("got", adminKey).Msg("bad admin key!")
|
||||
} else {
|
||||
slog.Warn().Msg("bad admin key!")
|
||||
}
|
||||
errThrow(c, 404, errors.New("bad key"), message404)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Trace().Msg("admin key accepted")
|
||||
if !db.With(typeToString(t, true)).Has([]byte(rKey)) {
|
||||
errThrow(c, 404, errors.New("failed to delete entry"), messageAdmin404)
|
||||
return
|
||||
}
|
||||
err := db.With(typeToString(t, true)).Delete([]byte(rKey))
|
||||
if err != nil {
|
||||
errThrow(c, 500, err, mustJson(map[string]string{"error": err.Error()}))
|
||||
return
|
||||
}
|
||||
slog.Info().Msg("admin deleted entry")
|
||||
c.JSON(200, "DELETE_SUCCESS")
|
||||
}
|
||||
|
||||
func del(c *gin.Context, t EntryType) {
|
||||
slog := log.With().Str("caller", "del").Str("type", typeToString(t, true)).Logger()
|
||||
rKey := c.Param("key")
|
||||
adminKey, adminAttempt := c.GetQuery("admin")
|
||||
if adminAttempt {
|
||||
adminBypass(c, adminKey, t)
|
||||
return
|
||||
}
|
||||
|
||||
if !validateKey(rKey) {
|
||||
errThrow(c, 400, errors.New("failed to validate delete key"), message404)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := db.With("key").Get([]byte(rKey))
|
||||
if err != nil {
|
||||
errThrow(c, 400, err, message400)
|
||||
return
|
||||
}
|
||||
if target == nil || !strings.HasPrefix(string(target), typeToString(t, false)+".") {
|
||||
errThrow(c, 400, errors.New("no delete entry found with provided key"), message404)
|
||||
return
|
||||
}
|
||||
finalTarget := strings.Split(string(target), ".")
|
||||
if !db.With(typeToString(t, true)).Has([]byte(finalTarget[1])) {
|
||||
// this shouldn't happen...?
|
||||
errThrow(c, 500, errors.New("corresponding image to delete not found in database"), message500)
|
||||
return
|
||||
}
|
||||
err = db.With(typeToString(t, true)).Delete([]byte(finalTarget[1]))
|
||||
if err != nil {
|
||||
errThrow(c, 500, err, message500)
|
||||
return
|
||||
}
|
||||
if db.With(typeToString(t, true)).Has([]byte(finalTarget[1])) {
|
||||
errThrow(c, 500, errors.New("failed to delete entry"), message500)
|
||||
return
|
||||
}
|
||||
slog.Info().Str("rkey", finalTarget[1]).Msg("Image file deleted successfully")
|
||||
slog.Trace().Str("rkey", finalTarget[1]).Msg("Removing delete key entry")
|
||||
err = db.With("key").Delete([]byte(rKey))
|
||||
if err != nil {
|
||||
slog.Error().Str("rkey", finalTarget[1]).Msg("Couldn't delete key")
|
||||
}
|
||||
c.JSON(200, "DELETE_SUCCESS")
|
||||
}
|
9
go.mod
9
go.mod
|
@ -4,19 +4,18 @@ go 1.18
|
|||
|
||||
require (
|
||||
git.tcp.direct/kayos/common v0.7.0
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220707194005-5bc828145cc4
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220718092256-8851c78611cd
|
||||
git.tcp.direct/tcp.direct/database v0.0.0-20220610180603-058d36edd7f0
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-contrib/logger v0.2.2
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/muesli/termenv v0.12.0
|
||||
github.com/rs/zerolog v1.27.0
|
||||
github.com/scottleedavis/go-exif-remove v0.0.0-20190908021517-58bdbaac8636
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/afero v1.9.0
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/twharmon/gouid v0.5.2
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -36,7 +35,6 @@ require (
|
|||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20190812012225-f41920e961ce // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
|
@ -60,6 +58,7 @@ require (
|
|||
github.com/yunginnanet/Rate5 v1.0.1 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 // indirect
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -44,8 +44,8 @@ git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34 h1:tvoL
|
|||
git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34/go.mod h1:NX/Gqm/iy6Tg2CfcmmB/kW/qzKKrGR6o2koOKA5KWnc=
|
||||
git.tcp.direct/kayos/common v0.7.0 h1:KZDwoCzUiwQaYSWESr080N8wUVyLD27QYgzXgc7LiAQ=
|
||||
git.tcp.direct/kayos/common v0.7.0/go.mod h1:7tMZBVNPLFSZk+JXTA6pgXWpf/XHqYRfT7Q3OziI++Y=
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220707194005-5bc828145cc4 h1:HhXghmJMzXSE/3clQRECP21OIcVv0za9dyzRlryaXno=
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220707194005-5bc828145cc4/go.mod h1:WInY1F5uGGRQ6Bzq36OFrB240FvP9EVCDn0vqv4mEBM=
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220718092256-8851c78611cd h1:s4dGya6D1KG7rOc6s7Jy7iZYkUVblbEuPAXtadoGDtA=
|
||||
git.tcp.direct/kayos/putxt v0.0.0-20220718092256-8851c78611cd/go.mod h1:vtJNXcTDS9dn1nFEAG+Aprg/HLTgcXgyPvYs8Gp4FxY=
|
||||
git.tcp.direct/tcp.direct/database v0.0.0-20220610180603-058d36edd7f0 h1:p0DGzX6vm1xvj3OtmroTJ4eAX51FAcnYwpWmhkx6UA0=
|
||||
git.tcp.direct/tcp.direct/database v0.0.0-20220610180603-058d36edd7f0/go.mod h1:g5XsBf1G8T/ssqfYq65EBloB5NdRCTQBEC5dyqGHbQY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
|
@ -376,8 +376,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
|||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
|
||||
github.com/spf13/afero v1.9.0/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
|
@ -414,8 +414,6 @@ github.com/tidwall/btree v0.4.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQF
|
|||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/redcon v1.4.1/go.mod h1:XwNPFbJ4ShWNNSA2Jazhbdje6jegTCcwFR6mfaADvHA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/twharmon/gouid v0.5.2 h1:fqFUx700Ishb4dZaXpRv95CGGGR1CBuCkjM0t62XAxw=
|
||||
github.com/twharmon/gouid v0.5.2/go.mod h1:m1SyQo0sYYbukI1yNZ1WRk980fV2XWBuYGAtMo/AmQ8=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
|
|
304
img.go
304
img.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
@ -11,296 +12,109 @@ import (
|
|||
"strings"
|
||||
|
||||
_ "git.tcp.direct/kayos/common"
|
||||
"git.tcp.direct/kayos/common/entropy"
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
exifremove "github.com/scottleedavis/go-exif-remove"
|
||||
|
||||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
var fExt string
|
||||
type imageValidator struct{}
|
||||
|
||||
func imgDel(c *gin.Context) {
|
||||
slog := log.With().Str("caller", "imgView").Logger()
|
||||
|
||||
rKey := c.Param("key")
|
||||
|
||||
if !validateKey(rKey) {
|
||||
errThrow(c, 400, errors.New("failed to validate delete key"), "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
targetImg, err := db.With("key").Get([]byte(rKey))
|
||||
if err != nil {
|
||||
errThrow(c, 400, err, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
if targetImg == nil || !strings.Contains(string(targetImg), "i.") {
|
||||
errThrow(c, 400, errors.New("no img delete entry found with provided key"), "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
finalTarget := strings.Split(string(targetImg), ".")
|
||||
|
||||
if !db.With("img").Has([]byte(finalTarget[1])) {
|
||||
// this shouldn't happen...?
|
||||
errThrow(c, 500, errors.New("corresponding image todelete not found in database"), "internal server error")
|
||||
return
|
||||
}
|
||||
err = db.With("img").Delete([]byte(finalTarget[1]))
|
||||
if err != nil {
|
||||
errThrow(c, 500, err, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
if db.With("img").Has([]byte(finalTarget[1])) {
|
||||
slog.Error().Str("rkey", finalTarget[1]).Msg("delete failed!?")
|
||||
errThrow(c, 500, errors.New("failed to delete entry"), "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info().Str("rkey", finalTarget[1]).Msg("Image file deleted successfully")
|
||||
slog.Trace().Str("rkey", finalTarget[1]).Msg("Removing delete key entry")
|
||||
err = db.With("key").Delete([]byte(rKey))
|
||||
if err != nil {
|
||||
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
|
||||
// we will delete the hash entry then and re-add then
|
||||
func (i imageValidator) finalize(data []byte) ([]byte, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func imgView(c *gin.Context) {
|
||||
slog := log.With().Str("caller", "imgView").Logger()
|
||||
sUID := strings.Split(c.Param("uid"), ".")
|
||||
rUID := sUID[0]
|
||||
|
||||
if len(sUID) > 1 {
|
||||
fExt = strings.ToLower(sUID[1])
|
||||
slog.Trace().Str("ext", fExt).Msg("detected file extension")
|
||||
if fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif" {
|
||||
errThrow(c, 400, errors.New("bad file extension"), "invalid request")
|
||||
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) != config.UIDSize {
|
||||
slog.Warn().
|
||||
Str("remoteaddr", c.ClientIP()).
|
||||
Msg("request discarded as invalid")
|
||||
|
||||
errThrow(c, 400, errors.New("invalid request"), "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// now that we think its a valid request we will query
|
||||
slog.Trace().Str("rUid", rUID).Msg("request validated")
|
||||
|
||||
// query bitcask for the id
|
||||
fBytes, _ := db.With("img").Get([]byte(rUID))
|
||||
if fBytes == nil {
|
||||
slog.Error().Str("rUid", rUID).Msg("no corresponding file for this id")
|
||||
errThrow(c, 404, errors.New("entry not found"), "File not found")
|
||||
return
|
||||
}
|
||||
|
||||
// read the data from bitcask into a reader
|
||||
file := bytes.NewReader(fBytes)
|
||||
imageFormat, err := checkImage(file)
|
||||
if err != nil {
|
||||
// extra sanity check to make sure we don't serve non-image data that somehow got into the database
|
||||
errThrow(c, http.StatusBadRequest, errors.New("entry in datbase is not an image: "+err.Error()), "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// additional extension sanity check - if they're gonna use an extension it needs to be the right one
|
||||
if fExt != "nil" && fExt != imageFormat {
|
||||
errThrow(c, 400, errors.New("requested file extension does not match filetype"), "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// extension or not (they are optional)
|
||||
// we give them the proper content type
|
||||
contentType := "image/" + imageFormat
|
||||
c.Data(200, contentType, fBytes)
|
||||
|
||||
slog.Info().Str("rUid", rUID).Msg("Successful upload")
|
||||
}
|
||||
|
||||
func instantiateWithIDs(p *Post) *Post {
|
||||
slog := log.With().Str("caller", "instantiateWithIDs").Logger()
|
||||
// generate new uid and delete key
|
||||
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
||||
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
||||
// lets make sure that we don't clash even though its highly unlikely
|
||||
for db.With(p.TypeCode(true)).Has([]byte(p.UID())) {
|
||||
slog.Warn().Msg(" uid already exists! generating new...")
|
||||
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
||||
}
|
||||
for db.With("key").Has([]byte(p.DelKey())) {
|
||||
slog.Warn().Msg(" delete key already exists! generating new...")
|
||||
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
||||
}
|
||||
// save checksum to db to prevent dupes in the future
|
||||
err := db.With("hsh").Put(p.Sum(), []byte(p.UID()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func savePost(p *Post) error {
|
||||
// insert actual file to database
|
||||
p.Log().Trace().Msg("saving file to database")
|
||||
err := db.With(p.TypeCode(true)).Put([]byte(p.UID()), p.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.
|
||||
With("key").
|
||||
Put(
|
||||
[]byte(p.DelKey()),
|
||||
[]byte(p.TypeCode(false)+"."+p.UID()),
|
||||
)
|
||||
}
|
||||
|
||||
func readAndScrubImage(file io.ReadSeeker) (scrubbed []byte, err error) {
|
||||
imageFormat, err := checkImage(file)
|
||||
func readAndScrubImage(c any, readHead io.ReadSeeker) (scrubbed []byte, err error) {
|
||||
cg := c.(*gin.Context)
|
||||
imageFormat, err := checkImage(readHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cg.Set("real.extension", imageFormat)
|
||||
// dump this into a byte object and scrub it
|
||||
// TO-DO: Write our own function for scrubbing exif
|
||||
fbytes, err := io.ReadAll(file)
|
||||
fbytes, err := io.ReadAll(readHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
scrubbed = fbytes
|
||||
|
||||
if imageFormat == "gif" {
|
||||
return
|
||||
}
|
||||
|
||||
scrubbed, err = exifremove.Remove(fbytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type validatingScrubber func(c *gin.Context) ([]byte, error)
|
||||
func (i imageValidator) getContentType(c *gin.Context) (string, error) {
|
||||
imageType, ok := c.Get("real.extension")
|
||||
if !ok {
|
||||
return "", errors.New("no filetype in context")
|
||||
}
|
||||
return "image/" + imageType.(string), nil
|
||||
}
|
||||
|
||||
func imgValidateAndScrub(c *gin.Context) ([]byte, error) {
|
||||
func (i imageValidator) checkURL(c *gin.Context) error {
|
||||
sUID := strings.Split(c.Param("uid"), ".")
|
||||
var fExt string
|
||||
if len(sUID) > 1 {
|
||||
fExt = strings.ToLower(sUID[1])
|
||||
log.Trace().Str("caller", c.Request.RequestURI).Str("ext", fExt).Msg("detected file extension")
|
||||
if fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif" && fExt != "webm" {
|
||||
return errors.New("bad file extension")
|
||||
}
|
||||
c.Set("url.extension", fExt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i imageValidator) checkContent(c *gin.Context, data []byte) error {
|
||||
readHead := bytes.NewReader(data)
|
||||
var err error
|
||||
_, err = readAndScrubImage(c, readHead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
urlExt, uExists := c.Get("url.extension")
|
||||
bytExt, bExists := c.Get("real.extension")
|
||||
if uExists && bExists && urlExt != bytExt {
|
||||
return errors.New("bad file extension")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i imageValidator) checkAndScrubPost(c any) ([]byte, error) {
|
||||
cg := c.(*gin.Context)
|
||||
slog := log.With().Str("caller", "imgPost").
|
||||
Str("User-Agent", c.GetHeader("User-Agent")).
|
||||
Str("RemoteAddr", c.ClientIP()).Logger()
|
||||
Str("User-Agent", cg.GetHeader("User-Agent")).
|
||||
Str("RemoteAddr", cg.ClientIP()).Logger()
|
||||
// check if incoming POST data is invalid
|
||||
f, err := c.FormFile("upload")
|
||||
f, err := cg.FormFile("upload")
|
||||
if err != nil || f == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Debug().Str("filename", f.Filename).Msg("[+] New upload")
|
||||
|
||||
// read the incoming file into an io file reader
|
||||
file, err := f.Open()
|
||||
if err != nil {
|
||||
errThrow(c, http.StatusInternalServerError, err, "error processing file\n")
|
||||
errThrow(cg, http.StatusInternalServerError, err, message500)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scrubbed, err := readAndScrubImage(file)
|
||||
scrubbed, err := readAndScrubImage(c, file)
|
||||
if err != nil {
|
||||
errThrow(c, http.StatusBadRequest, err, "invalid request")
|
||||
errThrow(cg, http.StatusBadRequest, err, message400)
|
||||
return nil, err
|
||||
}
|
||||
return scrubbed, nil
|
||||
}
|
||||
|
||||
func getOldRef(p *Post) (*Post, error) {
|
||||
var oldRef []byte
|
||||
oldRef, err := db.With("hsh").Get(p.Sum())
|
||||
func checkImage(r io.ReadSeeker) (fmt string, err error) {
|
||||
// in theory this makes sure the file is an image via magic bytes
|
||||
_, fmt, err = image.Decode(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
p.Log().Trace().Caller().Msg("duplicate checksum in hash database, checking if file still exists...")
|
||||
if db.With(p.TypeCode(true)).Has(oldRef) {
|
||||
p.Log().Debug().Str("ogUid", string(oldRef)).
|
||||
Msg("duplicate file found! returning original URL")
|
||||
p.uid = string(oldRef)
|
||||
p.key = ""
|
||||
p.priv = false
|
||||
return p, nil
|
||||
}
|
||||
p.Log().Trace().
|
||||
Str("ogUid", string(oldRef)).
|
||||
Msg("stale hash found, deleting entry...")
|
||||
err = db.With("hsh").Delete(p.Sum())
|
||||
if err != nil {
|
||||
p.Log().Error().Err(err).Msg("failed to delete stale hash")
|
||||
p = nil
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func post(c *gin.Context, vas validatingScrubber, t EntryType) error {
|
||||
scrubbed, err := vas(c)
|
||||
if err != nil {
|
||||
if c != nil {
|
||||
return errThrow(c, http.StatusBadRequest, err, "invalid request")
|
||||
}
|
||||
return err
|
||||
}
|
||||
var p *Post
|
||||
switch t {
|
||||
case Image:
|
||||
p = NewImg(scrubbed, false)
|
||||
case Text:
|
||||
p = NewTxt(scrubbed, false)
|
||||
default:
|
||||
return errors.New("invalid entry type")
|
||||
}
|
||||
|
||||
// the keys (stored in memory) for db.With("hsh") are hashes
|
||||
// making it quick to find duplicates. the value is the uid
|
||||
if db.With("hsh").Has(p.Sum()) {
|
||||
p, err = getOldRef(p)
|
||||
if err != nil {
|
||||
if c != nil {
|
||||
return errThrow(c, http.StatusInternalServerError, err, "internal server error")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p = instantiateWithIDs(p)
|
||||
if p == nil {
|
||||
if c != nil {
|
||||
return errThrow(c, 500, err, "upload failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = savePost(p)
|
||||
if err != nil {
|
||||
if c != nil {
|
||||
return errThrow(c, http.StatusInternalServerError, err, "internal error")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// good to go, send them to the finisher function
|
||||
p.Log().Trace().Msg("saved to database successfully, sending to NewPostResponse")
|
||||
|
||||
p.NewPostResponse(c)
|
||||
return nil
|
||||
_, err = r.Seek(0, 0)
|
||||
return
|
||||
}
|
||||
|
|
8
main.go
8
main.go
|
@ -67,6 +67,12 @@ func main() {
|
|||
log.Warn().Err(err).Msg("sync failure!")
|
||||
}
|
||||
}()
|
||||
go serveTermbin()
|
||||
go func() {
|
||||
err := serveTermbin()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to start termbin")
|
||||
}
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
wait(httpRouter())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"git.tcp.direct/kayos/common/entropy"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
func mustJson(v any) []byte {
|
||||
js, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return js
|
||||
}
|
||||
|
||||
var (
|
||||
message500 = mustJson(map[string]string{"error": "internal server error"})
|
||||
message400 = mustJson(map[string]string{"error": "bad request"})
|
||||
message404 = mustJson(map[string]string{"error": "file not found"})
|
||||
messageAdmin404 = mustJson(map[string]string{"error": "post id does not exist"})
|
||||
)
|
||||
|
||||
type validator interface {
|
||||
checkURL(c *gin.Context) error
|
||||
checkContent(c *gin.Context, data []byte) error
|
||||
checkAndScrubPost(c any) ([]byte, error)
|
||||
getContentType(c *gin.Context) (string, error)
|
||||
finalize(data []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
func post(c any, vas validator, t EntryType, priv bool) error {
|
||||
scrubbed, err := vas.checkAndScrubPost(c)
|
||||
if err != nil {
|
||||
switch c.(type) {
|
||||
case *gin.Context:
|
||||
return errThrow(c.(*gin.Context), http.StatusBadRequest, err, message400)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p := newPost(t, scrubbed, priv)
|
||||
|
||||
var exists bool
|
||||
// the keyspace (stored in memory) for db.With("hsh") are hashes
|
||||
// making it quick to find duplicates. the value is the uid
|
||||
if db.With("hsh").Has(p.Sum()) {
|
||||
p, err, exists = getOldRef(p)
|
||||
if err != nil {
|
||||
switch c.(type) {
|
||||
case *gin.Context:
|
||||
return errThrow(c.(*gin.Context), http.StatusInternalServerError, err, message500)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if exists {
|
||||
p.NewPostResponse(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
p = instantiateWithIDs(p)
|
||||
if p == nil {
|
||||
switch c.(type) {
|
||||
case *gin.Context:
|
||||
return errThrow(c.(*gin.Context), 500, err, message500)
|
||||
default:
|
||||
return errors.New("upload failed")
|
||||
}
|
||||
}
|
||||
|
||||
err = savePost(p)
|
||||
if err != nil {
|
||||
switch c.(type) {
|
||||
case *gin.Context:
|
||||
return errThrow(c.(*gin.Context), http.StatusInternalServerError, err, message500)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// good to go, send them to the finisher function
|
||||
p.Log().Trace().Msg("saved to database successfully, sending to NewPostResponse")
|
||||
|
||||
p.NewPostResponse(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func savePost(p *Post) error {
|
||||
// insert actual file to database
|
||||
p.Log().Trace().Msg("saving file to database")
|
||||
err := db.With(p.TypeCode(true)).Put([]byte(p.UID()), p.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.
|
||||
With("key").
|
||||
Put(
|
||||
[]byte(p.DelKey()),
|
||||
[]byte(p.TypeCode(false)+"."+p.UID()),
|
||||
)
|
||||
}
|
||||
|
||||
func instantiateWithIDs(p *Post) *Post {
|
||||
slog := log.With().Str("caller", "instantiateWithIDs").Logger()
|
||||
// generate new uid and delete key
|
||||
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
||||
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
||||
// lets make sure that we don't clash even though its highly unlikely
|
||||
for db.With(p.TypeCode(true)).Has([]byte(p.UID())) {
|
||||
slog.Warn().Msg(" uid already exists! generating new...")
|
||||
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
||||
}
|
||||
for db.With("key").Has([]byte(p.DelKey())) {
|
||||
slog.Warn().Msg(" delete key already exists! generating new...")
|
||||
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
||||
}
|
||||
// save checksum to db to prevent dupes in the future
|
||||
err := db.With("hsh").Put(p.Sum(), []byte(p.UID()))
|
||||
if err != nil {
|
||||
slog.Error().Err(err).Msg("failed to save checksum to db")
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
34
router.go
34
router.go
|
@ -15,12 +15,12 @@ import (
|
|||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
var favicon string = "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"
|
||||
var staticIndex string = "PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KCTxtZXRhIGNoYXJzZXQ9IlVURi04Ij4KCTxtZXRhIG5hbWU9ImRlc2NyaXB0aW9uIiBjb250ZW50PSJ0Y3AuYWMgLSB1bmRlciBjb25zdHJ1Y3Rpb24gLSBpbWFnZSB1cGxvYWRpbmcsIHVybCBzaG9ydGVuaW5nLCB0ZXh0IGJpbiI+Cgk8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9InRjcC5kaXJlY3QiPgoJPHRpdGxlPnRjcC5hYzwvdGl0bGU+CgoJPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCQlib2R5IHsKCQkJYmFja2dyb3VuZC1jb2xvcjojMTAxMDEwOwoJCQljb2xvcjojOTQ4REI4OwoJCQlmb250LWZhbWlseTptb25vc3BhY2U7CgkJCXRleHQtYWxpZ246Y2VudGVyOwoJCX0KCQkuaGVsbG8gewoJCQlwb3NpdGlvbjogZml4ZWQ7CgkJCXRvcDogNTAlOwoJCQlsZWZ0OiA1MCU7CgkJCXRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpOwoJCX0KCQkuZmFkaW5nIHsKCQkJYW5pbWF0aW9uOmZhZGluZyA1cyBpbmZpbml0ZQoJCX0KCQlAa2V5ZnJhbWVzIGZhZGluZ3sKCQkJMCV7b3BhY2l0eTowfQoJCQk1MCV7b3BhY2l0eToxfQoJCQkxMDAle29wYWNpdHk6MH0KCQl9Cgk8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5Pgo8ZGl2IGNsYXNzPSJoZWxsbyI+CjxwcmUgY2xhc3M9ImZhZGluZyI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAsZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgODggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApNTTg4TU1NICxhZFBQWWJhLCA4YixkUFBZYmEsICAgICAgLGFkUFBZWWJhLCAgLGFkUFBZYmEsICAKICA4OCAgIGE4IiAgICAgIiIgODhQJyAgICAiOGEgICAgICIiICAgICBgWTggYTgiICAgICAiIiAgCiAgODggICA4YiAgICAgICAgIDg4ICAgICAgIGQ4ICAgICAsYWRQUFBQUDg4IDhiICAgICAgICAgIAogIDg4LCAgIjhhLCAgICxhYSA4OGIsICAgLGE4IiA4ODggODgsICAgICw4OCAiOGEsICAgLGFhICAKICAiWTg4OCBgIlliYmQ4IicgODhgWWJiZFAiJyAgODg4IGAiOGJiZFAiWTggIGAiWWJiZDgiJyAgCiAgICAgICAgICAgICAgICAgIDg4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA4OCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKCjwvcHJlPgo8L2Rpdj4KCjxhIGhyZWY9Imh0dHA6Ly9hZG1pbi50Y3AuYWMvIiBzdHlsZT0iZGlzcGxheTpub25lOyI+YWRtaW4gbG9naW48L2E+Cgo8L2JvZHk+Cgo8L2h0bWw+Cg=="
|
||||
const favicon = "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"
|
||||
const staticIndex = "PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KCTxtZXRhIGNoYXJzZXQ9IlVURi04Ij4KCTxtZXRhIG5hbWU9ImRlc2NyaXB0aW9uIiBjb250ZW50PSJ0Y3AuYWMgLSB1bmRlciBjb25zdHJ1Y3Rpb24gLSBpbWFnZSB1cGxvYWRpbmcsIHVybCBzaG9ydGVuaW5nLCB0ZXh0IGJpbiI+Cgk8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9InRjcC5kaXJlY3QiPgoJPHRpdGxlPnRjcC5hYzwvdGl0bGU+CgoJPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCQlib2R5IHsKCQkJYmFja2dyb3VuZC1jb2xvcjojMTAxMDEwOwoJCQljb2xvcjojOTQ4REI4OwoJCQlmb250LWZhbWlseTptb25vc3BhY2U7CgkJCXRleHQtYWxpZ246Y2VudGVyOwoJCX0KCQkuaGVsbG8gewoJCQlwb3NpdGlvbjogZml4ZWQ7CgkJCXRvcDogNTAlOwoJCQlsZWZ0OiA1MCU7CgkJCXRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpOwoJCX0KCQkuZmFkaW5nIHsKCQkJYW5pbWF0aW9uOmZhZGluZyA1cyBpbmZpbml0ZQoJCX0KCQlAa2V5ZnJhbWVzIGZhZGluZ3sKCQkJMCV7b3BhY2l0eTowfQoJCQk1MCV7b3BhY2l0eToxfQoJCQkxMDAle29wYWNpdHk6MH0KCQl9Cgk8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5Pgo8ZGl2IGNsYXNzPSJoZWxsbyI+CjxwcmUgY2xhc3M9ImZhZGluZyI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAsZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgODggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApNTTg4TU1NICxhZFBQWWJhLCA4YixkUFBZYmEsICAgICAgLGFkUFBZWWJhLCAgLGFkUFBZYmEsICAKICA4OCAgIGE4IiAgICAgIiIgODhQJyAgICAiOGEgICAgICIiICAgICBgWTggYTgiICAgICAiIiAgCiAgODggICA4YiAgICAgICAgIDg4ICAgICAgIGQ4ICAgICAsYWRQUFBQUDg4IDhiICAgICAgICAgIAogIDg4LCAgIjhhLCAgICxhYSA4OGIsICAgLGE4IiA4ODggODgsICAgICw4OCAiOGEsICAgLGFhICAKICAiWTg4OCBgIlliYmQ4IicgODhgWWJiZFAiJyAgODg4IGAiOGJiZFAiWTggIGAiWWJiZDgiJyAgCiAgICAgICAgICAgICAgICAgIDg4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICA4OCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKCjwvcHJlPgo8L2Rpdj4KCjxhIGhyZWY9Imh0dHA6Ly9hZG1pbi50Y3AuYWMvIiBzdHlsZT0iZGlzcGxheTpub25lOyI+YWRtaW4gbG9naW48L2E+Cgo8L2JvZHk+Cgo8L2h0bWw+Cg=="
|
||||
|
||||
func favIcon(c *gin.Context) {
|
||||
ico, _ := base64.StdEncoding.DecodeString(favicon)
|
||||
c.Data(200, "image/ico", []byte(ico))
|
||||
c.Data(200, "image/ico", ico)
|
||||
}
|
||||
|
||||
func placeHolder(c *gin.Context) {
|
||||
|
@ -28,10 +28,6 @@ func placeHolder(c *gin.Context) {
|
|||
c.Data(200, "text/html", html)
|
||||
}
|
||||
|
||||
func urlPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
func httpRouter() *http.Server {
|
||||
if !config.Trace {
|
||||
log.Debug().Caller().Msg("running gin in release mode, enable trace to run gin in debug mode")
|
||||
|
@ -45,6 +41,12 @@ func httpRouter() *http.Server {
|
|||
router.Use(logger.SetLogger(
|
||||
logger.WithLogger(
|
||||
func(c *gin.Context, w io.Writer, d time.Duration) zerolog.Logger {
|
||||
if zerolog.GlobalLevel() > zerolog.DebugLevel {
|
||||
// because this spams the logs
|
||||
if c.Request.URL.String() == "/ip" {
|
||||
return zerolog.Nop()
|
||||
}
|
||||
}
|
||||
return log.With().
|
||||
Str("caller", c.ClientIP()).
|
||||
Str("url", c.Request.URL.String()).
|
||||
|
@ -59,29 +61,23 @@ func httpRouter() *http.Server {
|
|||
// use gzip compression unless someone requests something with an explicit extension
|
||||
router.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPathsRegexs([]string{".*"})))
|
||||
|
||||
// static html and such
|
||||
// workaround the issue where the router tries to handle /*
|
||||
// router.Static("/h", "../public")
|
||||
|
||||
router.GET("/favicon.ico", favIcon)
|
||||
router.GET("/", placeHolder)
|
||||
|
||||
router.GET("/ip", func(c *gin.Context) {
|
||||
c.String(200, c.ClientIP())
|
||||
})
|
||||
router.GET("/ip", func(c *gin.Context) { c.String(200, c.ClientIP()) })
|
||||
|
||||
imgR := router.Group("/i")
|
||||
imgR.GET("/", placeHolder)
|
||||
imgR.POST("/put", imgPost)
|
||||
imgR.GET("/:uid", imgView)
|
||||
imgR.POST("/put", func(c *gin.Context) { post(c, imageValidator{}, Image, false) })
|
||||
imgR.GET("/:uid", func(c *gin.Context) { view(c, imageValidator{}, Image) })
|
||||
|
||||
txtR := router.Group("/t")
|
||||
txtR.GET("/", placeHolder)
|
||||
txtR.GET("/:uid", txtView)
|
||||
txtR.GET("/:uid", func(c *gin.Context) { view(c, textValidator{}, Text) })
|
||||
|
||||
delR := router.Group("/d")
|
||||
delR.GET("/i/:key", imgDel)
|
||||
delR.GET("/t/:key", txtDel)
|
||||
delR.GET("/i/:key", func(c *gin.Context) { del(c, Image) })
|
||||
delR.GET("/t/:key", func(c *gin.Context) { del(c, Text) })
|
||||
|
||||
log.Info().Str("Host", config.HTTPBind).
|
||||
Str("Port", config.HTTPPort).
|
||||
|
|
242
txt.go
242
txt.go
|
@ -2,231 +2,73 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
"git.tcp.direct/kayos/common/squish"
|
||||
termdumpster "git.tcp.direct/kayos/putxt"
|
||||
"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
|
||||
type textValidator struct {
|
||||
in []byte
|
||||
out []byte
|
||||
}
|
||||
|
||||
func incoming() {
|
||||
var msg termbin.Message
|
||||
select {
|
||||
case msg = <-termbin.Msg:
|
||||
switch msg.Type {
|
||||
case termbin.Error:
|
||||
log.Warn().
|
||||
Str("RemoteAddr", msg.RAddr).
|
||||
Int("Size", msg.Size).
|
||||
Msg(msg.Content)
|
||||
case termbin.IncomingData:
|
||||
log.Trace().
|
||||
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.Trace().
|
||||
Str("RemoteAddr", msg.RAddr).
|
||||
Int("Size", msg.Size).
|
||||
Msg(msg.Content)
|
||||
case termbin.Final:
|
||||
log.Trace().
|
||||
Str("RemoteAddr", msg.RAddr).
|
||||
Int("Size", msg.Size).
|
||||
Msg(msg.Content)
|
||||
type textIngestor struct{}
|
||||
|
||||
termPost(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func termPost(msg) {
|
||||
slog := log.With().Str("caller", "imgPost").
|
||||
Str("User-Agent", c.GetHeader("User-Agent")).
|
||||
Str("RemoteAddr", c.ClientIP()).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)
|
||||
func (i textIngestor) Ingest(data []byte) ([]byte, error) {
|
||||
tp := &textValidator{in: data}
|
||||
err := post(tp, tp, Text, false)
|
||||
if err != nil {
|
||||
slog.Error().Err(err).Msg("failed to save text!")
|
||||
termbin.Reply <- termbin.Message{Type: termbin.ReturnURL, Content: "internal server error"}
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
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 NewPostResponse")
|
||||
|
||||
post := &Post{
|
||||
entryType: Text,
|
||||
uid: uid,
|
||||
key: key,
|
||||
priv: false,
|
||||
}
|
||||
|
||||
termbin.Reply <- termbin.Message{Type: termbin.ReturnURL, Content: post.URLString()}
|
||||
return tp.out, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func (i textValidator) finalize(data []byte) ([]byte, error) {
|
||||
return squish.Gunzip(data)
|
||||
}
|
||||
|
||||
sUid := strings.Split(c.Param("uid"), ".")
|
||||
rUid := sUid[0]
|
||||
fExt = ""
|
||||
func (i textValidator) getContentType(c *gin.Context) (string, error) {
|
||||
return "text/plain", nil
|
||||
}
|
||||
|
||||
if len(sUid) > 1 {
|
||||
fExt = strings.ToLower(sUid[1])
|
||||
func (i textValidator) checkURL(c *gin.Context) error {
|
||||
sUID := strings.Split(c.Param("uid"), ".")
|
||||
var fExt string
|
||||
if len(sUID) > 1 {
|
||||
fExt = strings.ToLower(sUID[1])
|
||||
log.Trace().Str("caller", c.Request.RequestURI).Str("ext", fExt).Msg("detected file extension")
|
||||
if fExt != "txt" {
|
||||
errThrow(c, 400, errors.New("bad file extension"), "400")
|
||||
return
|
||||
return errors.New("bad file extension")
|
||||
}
|
||||
c.Set("url.extension", fExt)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 (i textValidator) checkContent(c *gin.Context, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func serveTermbin() {
|
||||
go func() {
|
||||
for {
|
||||
incoming()
|
||||
}
|
||||
}()
|
||||
func (i textValidator) checkAndScrubPost(c any) ([]byte, error) {
|
||||
if i.in == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
return i.in, nil
|
||||
}
|
||||
|
||||
func serveTermbin() error {
|
||||
td := termdumpster.NewTermDumpster(textIngestor{}).WithGzip().WithLogger(&log.Logger).
|
||||
WithMaxSize(int64(config.KVMaxValueSizeMB * 1024 * 1024))
|
||||
split := strings.Split(config.TermbinListen, ":")
|
||||
err := termbin.Listen(split[0], split[1])
|
||||
log.Info().Str("listen", config.TermbinListen).Msg("starting termbin")
|
||||
err := td.Listen(split[0], split[1])
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
38
util.go
38
util.go
|
@ -2,26 +2,26 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func errThrow(c *gin.Context, respcode int, thrown error, msg string) error {
|
||||
func errThrow(c *gin.Context, respcode int, thrown error, msg []byte) error {
|
||||
log.Error().
|
||||
Str("IP", c.ClientIP()).
|
||||
Str("User-Agent", c.GetHeader("User-Agent")).
|
||||
Err(thrown).Msg(msg)
|
||||
c.String(respcode, msg)
|
||||
Err(thrown).Msg(string(msg))
|
||||
c.Data(respcode, "application/json", msg)
|
||||
var err error
|
||||
if thrown != nil {
|
||||
err = fmt.Errorf("%s: %s", msg, thrown)
|
||||
err = fmt.Errorf("%s: %w", msg, thrown)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: do we need this?
|
||||
func getSize(s io.Seeker) (size int64, err error) {
|
||||
// get size of file
|
||||
if _, err = s.Seek(0, 0); err != nil {
|
||||
|
@ -35,12 +35,28 @@ func getSize(s io.Seeker) (size int64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func checkImage(r io.ReadSeeker) (fmt string, err error) {
|
||||
// in theory this makes sure the file is an image via magic bytes
|
||||
_, fmt, err = image.Decode(r)
|
||||
func getOldRef(p *Post) (*Post, error, bool) {
|
||||
var oldRef []byte
|
||||
oldRef, err := db.With("hsh").Get(p.Sum())
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err, false
|
||||
}
|
||||
_, err = r.Seek(0, 0)
|
||||
return
|
||||
p.Log().Trace().Caller().Msg("duplicate checksum in hash database, checking if file still exists...")
|
||||
if db.With(p.TypeCode(true)).Has(oldRef) {
|
||||
p.Log().Debug().Str("ogUid", string(oldRef)).
|
||||
Msg("duplicate file found! returning original URL")
|
||||
p.uid = string(oldRef)
|
||||
p.key = ""
|
||||
p.priv = false
|
||||
return p, nil, true
|
||||
}
|
||||
p.Log().Trace().
|
||||
Str("ogUid", string(oldRef)).
|
||||
Msg("stale hash found, deleting entry...")
|
||||
err = db.With("hsh").Delete(p.Sum())
|
||||
if err != nil {
|
||||
p.Log().Error().Err(err).Msg("failed to delete stale hash")
|
||||
p = nil
|
||||
}
|
||||
return p, err, false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
||||
)
|
||||
|
||||
func view(c *gin.Context, validate validator, t EntryType) {
|
||||
slog := log.With().Str("caller", "view").Logger()
|
||||
if err := validate.checkURL(c); err != nil {
|
||||
errThrow(c, 400, err, message400)
|
||||
return
|
||||
}
|
||||
sUID := strings.Split(c.Param("uid"), ".")
|
||||
rUID := sUID[0]
|
||||
// if it doesn't match the key size or it isn't alphanumeric - throw it out
|
||||
if !govalidator.IsAlphanumeric(rUID) || len(rUID) != config.UIDSize {
|
||||
slog.Warn().
|
||||
Str("remoteaddr", c.ClientIP()).
|
||||
Msg("request discarded as invalid")
|
||||
errThrow(c, 400, errors.New(string(message400)), message400)
|
||||
return
|
||||
}
|
||||
slog.Trace().Str("rUid", rUID).Msg("request validated")
|
||||
// query bitcask for the id
|
||||
fBytes, err := db.With(typeToString(t, true)).Get([]byte(rUID))
|
||||
if fBytes == nil || err != nil {
|
||||
slog.Error().Str("rUid", rUID).Msg("no corresponding file for this id")
|
||||
realErr := err
|
||||
if err == nil {
|
||||
realErr = errors.New("no corresponding file for this id")
|
||||
}
|
||||
errThrow(c, 404, realErr, message404)
|
||||
return
|
||||
}
|
||||
err = validate.checkContent(c, fBytes)
|
||||
if err != nil {
|
||||
errThrow(c, http.StatusBadRequest, err, message400)
|
||||
return
|
||||
}
|
||||
var contentType string
|
||||
contentType, err = validate.getContentType(c)
|
||||
if err != nil {
|
||||
errThrow(c, http.StatusBadRequest, err, message400)
|
||||
return
|
||||
}
|
||||
|
||||
fBytes, err = validate.finalize(fBytes)
|
||||
if err != nil {
|
||||
errThrow(c, http.StatusInternalServerError, err, message500)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(200, contentType, fBytes)
|
||||
}
|
Loading…
Reference in New Issue