2021-02-15 20:52:35 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2022-01-21 12:58:22 +00:00
|
|
|
"errors"
|
|
|
|
_ "image/gif"
|
|
|
|
_ "image/jpeg"
|
|
|
|
_ "image/png"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
_ "git.tcp.direct/kayos/common"
|
|
|
|
"git.tcp.direct/kayos/common/entropy"
|
2021-02-15 20:52:35 +00:00
|
|
|
valid "github.com/asaskevich/govalidator"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
exifremove "github.com/scottleedavis/go-exif-remove"
|
2022-07-08 20:23:20 +00:00
|
|
|
|
|
|
|
"git.tcp.direct/tcp.direct/tcp.ac/config"
|
2021-02-15 20:52:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var fExt string
|
|
|
|
|
|
|
|
func imgDel(c *gin.Context) {
|
2021-07-28 07:31:34 +00:00
|
|
|
slog := log.With().Str("caller", "imgView").Logger()
|
2021-02-15 20:52:35 +00:00
|
|
|
|
|
|
|
rKey := c.Param("key")
|
|
|
|
|
2021-07-28 07:31:34 +00:00
|
|
|
if !validateKey(rKey) {
|
2022-01-21 12:58:22 +00:00
|
|
|
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")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if targetImg == nil || !strings.Contains(string(targetImg), "i.") {
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 400, errors.New("no img delete entry found with provided key"), "invalid request")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
finalTarget := strings.Split(string(targetImg), ".")
|
|
|
|
|
2022-01-21 12:58:22 +00:00
|
|
|
if !db.With("img").Has([]byte(finalTarget[1])) {
|
2021-09-01 07:19:10 +00:00
|
|
|
// this shouldn't happen...?
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 500, errors.New("corresponding image todelete not found in database"), "internal server error")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
2022-01-21 12:58:22 +00:00
|
|
|
err = db.With("img").Delete([]byte(finalTarget[1]))
|
2021-02-15 20:52:35 +00:00
|
|
|
if err != nil {
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 500, err, "internal server error")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-21 12:58:22 +00:00
|
|
|
if db.With("img").Has([]byte(finalTarget[1])) {
|
2021-07-28 07:31:34 +00:00
|
|
|
slog.Error().Str("rkey", finalTarget[1]).Msg("delete failed!?")
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 500, errors.New("failed to delete entry"), "internal server error")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-28 07:31:34 +00:00
|
|
|
slog.Info().Str("rkey", finalTarget[1]).Msg("Image file deleted successfully")
|
2022-07-09 19:39:29 +00:00
|
|
|
slog.Trace().Str("rkey", finalTarget[1]).Msg("Removing delete key entry")
|
2022-01-21 12:58:22 +00:00
|
|
|
err = db.With("key").Delete([]byte(rKey))
|
2021-02-15 20:52:35 +00:00
|
|
|
if err != nil {
|
2021-07-28 07:31:34 +00:00
|
|
|
slog.Error().Str("rkey", finalTarget[1]).Msg("Couldn't delete key")
|
2021-02-15 20:52:35 +00:00
|
|
|
// 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 imgView(c *gin.Context) {
|
2021-07-28 07:31:34 +00:00
|
|
|
slog := log.With().Str("caller", "imgView").Logger()
|
2022-07-09 19:39:29 +00:00
|
|
|
sUID := strings.Split(c.Param("uid"), ".")
|
|
|
|
rUID := sUID[0]
|
2021-02-15 20:52:35 +00:00
|
|
|
|
2022-07-09 19:39:29 +00:00
|
|
|
if len(sUID) > 1 {
|
|
|
|
fExt = strings.ToLower(sUID[1])
|
|
|
|
slog.Trace().Str("ext", fExt).Msg("detected file extension")
|
2021-02-15 20:52:35 +00:00
|
|
|
if fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif" {
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 400, errors.New("bad file extension"), "invalid request")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fExt = "nil"
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it doesn't match the key size or it isn't alphanumeric - throw it out
|
2022-07-09 19:39:29 +00:00
|
|
|
if !valid.IsAlphanumeric(rUID) || len(rUID) != config.UIDSize {
|
2021-07-29 19:40:53 +00:00
|
|
|
slog.Warn().
|
2021-08-24 09:56:10 +00:00
|
|
|
Str("remoteaddr", c.ClientIP()).
|
|
|
|
Msg("request discarded as invalid")
|
2021-07-29 19:40:53 +00:00
|
|
|
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 400, errors.New("invalid request"), "invalid request")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// now that we think its a valid request we will query
|
2022-07-09 19:39:29 +00:00
|
|
|
slog.Trace().Str("rUid", rUID).Msg("request validated")
|
2021-02-15 20:52:35 +00:00
|
|
|
|
|
|
|
// query bitcask for the id
|
2022-07-09 19:39:29 +00:00
|
|
|
fBytes, _ := db.With("img").Get([]byte(rUID))
|
2021-02-15 20:52:35 +00:00
|
|
|
if fBytes == nil {
|
2022-07-09 19:39:29 +00:00
|
|
|
slog.Error().Str("rUid", rUID).Msg("no corresponding file for this id")
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 404, errors.New("entry not found"), "File not found")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// read the data from bitcask into a reader
|
|
|
|
file := bytes.NewReader(fBytes)
|
2022-01-21 12:58:22 +00:00
|
|
|
imageFormat, err := checkImage(file)
|
|
|
|
if err != nil {
|
2021-02-15 20:52:35 +00:00
|
|
|
// extra sanity check to make sure we don't serve non-image data that somehow got into the database
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, http.StatusBadRequest, errors.New("entry in datbase is not an image: "+err.Error()), "invalid request")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// additional extension sanity check - if they're gonna use an extension it needs to be the right one
|
|
|
|
if fExt != "nil" && fExt != imageFormat {
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, 400, errors.New("requested file extension does not match filetype"), "invalid request")
|
2021-02-15 20:52:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// extension or not (they are optional)
|
|
|
|
// we give them the proper content type
|
|
|
|
contentType := "image/" + imageFormat
|
|
|
|
c.Data(200, contentType, fBytes)
|
|
|
|
|
2022-07-09 19:39:29 +00:00
|
|
|
slog.Info().Str("rUid", rUID).Msg("Successful upload")
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
func instantiateWithIDs(p *Post) *Post {
|
|
|
|
slog := log.With().Str("caller", "instantiateWithIDs").Logger()
|
2022-01-21 12:58:22 +00:00
|
|
|
// generate new uid and delete key
|
2022-07-14 07:50:26 +00:00
|
|
|
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
|
|
|
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
2022-01-21 12:58:22 +00:00
|
|
|
// lets make sure that we don't clash even though its highly unlikely
|
2022-07-14 07:50:26 +00:00
|
|
|
for db.With(p.TypeCode(true)).Has([]byte(p.UID())) {
|
2022-01-21 12:58:22 +00:00
|
|
|
slog.Warn().Msg(" uid already exists! generating new...")
|
2022-07-14 07:50:26 +00:00
|
|
|
p.uid = entropy.RandStrWithUpper(config.UIDSize)
|
2022-01-21 12:58:22 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
for db.With("key").Has([]byte(p.DelKey())) {
|
2022-01-21 12:58:22 +00:00
|
|
|
slog.Warn().Msg(" delete key already exists! generating new...")
|
2022-07-14 07:50:26 +00:00
|
|
|
p.key = entropy.RandStrWithUpper(config.DeleteKeySize)
|
2022-01-21 12:58:22 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
// 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()),
|
|
|
|
)
|
2022-01-21 12:58:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func readAndScrubImage(file io.ReadSeeker) (scrubbed []byte, err error) {
|
|
|
|
imageFormat, err := checkImage(file)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// dump this into a byte object and scrub it
|
|
|
|
// TO-DO: Write our own function for scrubbing exif
|
|
|
|
fbytes, err := io.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
scrubbed = fbytes
|
|
|
|
|
|
|
|
if imageFormat == "gif" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
scrubbed, err = exifremove.Remove(fbytes)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
type validatingScrubber func(c *gin.Context) ([]byte, error)
|
|
|
|
|
|
|
|
func imgValidateAndScrub(c *gin.Context) ([]byte, error) {
|
2022-01-21 12:58:22 +00:00
|
|
|
slog := log.With().Str("caller", "imgPost").
|
|
|
|
Str("User-Agent", c.GetHeader("User-Agent")).
|
|
|
|
Str("RemoteAddr", c.ClientIP()).Logger()
|
2021-02-15 20:52:35 +00:00
|
|
|
// check if incoming POST data is invalid
|
|
|
|
f, err := c.FormFile("upload")
|
2022-01-21 12:58:22 +00:00
|
|
|
if err != nil || f == nil {
|
2022-07-14 07:50:26 +00:00
|
|
|
return nil, err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2021-07-28 07:31:34 +00:00
|
|
|
slog.Debug().Str("filename", f.Filename).Msg("[+] New upload")
|
2021-02-15 20:52:35 +00:00
|
|
|
|
|
|
|
// read the incoming file into an io file reader
|
|
|
|
file, err := f.Open()
|
|
|
|
if err != nil {
|
2022-01-21 12:58:22 +00:00
|
|
|
errThrow(c, http.StatusInternalServerError, err, "error processing file\n")
|
2022-07-14 07:50:26 +00:00
|
|
|
return nil, err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2022-01-21 12:58:22 +00:00
|
|
|
scrubbed, err := readAndScrubImage(file)
|
|
|
|
if err != nil {
|
|
|
|
errThrow(c, http.StatusBadRequest, err, "invalid request")
|
2022-07-14 07:50:26 +00:00
|
|
|
return nil, err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
return scrubbed, nil
|
|
|
|
}
|
2021-02-15 20:52:35 +00:00
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
func getOldRef(p *Post) (*Post, error) {
|
|
|
|
var oldRef []byte
|
|
|
|
oldRef, err := db.With("hsh").Get(p.Sum())
|
2021-09-01 07:19:10 +00:00
|
|
|
if err != nil {
|
2022-07-14 07:50:26 +00:00
|
|
|
return nil, err
|
2021-09-01 07:19:10 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
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
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
p.Log().Trace().
|
|
|
|
Str("ogUid", string(oldRef)).
|
|
|
|
Msg("stale hash found, deleting entry...")
|
|
|
|
err = db.With("hsh").Delete(p.Sum())
|
2022-01-21 12:58:22 +00:00
|
|
|
if err != nil {
|
2022-07-14 07:50:26 +00:00
|
|
|
p.Log().Error().Err(err).Msg("failed to delete stale hash")
|
|
|
|
p = nil
|
2022-01-21 12:58:22 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
return p, err
|
|
|
|
}
|
2021-02-15 20:52:35 +00:00
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
func post(c *gin.Context, vas validatingScrubber, t EntryType) error {
|
|
|
|
scrubbed, err := vas(c)
|
2021-02-15 20:52:35 +00:00
|
|
|
if err != nil {
|
2022-07-14 07:50:26 +00:00
|
|
|
if c != nil {
|
|
|
|
return errThrow(c, http.StatusBadRequest, err, "invalid request")
|
|
|
|
}
|
|
|
|
return err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
2022-07-14 07:50:26 +00:00
|
|
|
var p *Post
|
|
|
|
switch t {
|
|
|
|
case Image:
|
|
|
|
p = NewImg(scrubbed, false)
|
|
|
|
case Text:
|
|
|
|
p = NewTxt(scrubbed, false)
|
|
|
|
default:
|
|
|
|
return errors.New("invalid entry type")
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
// 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
|
|
|
|
}
|
2021-02-17 03:43:22 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
p = instantiateWithIDs(p)
|
|
|
|
if p == nil {
|
|
|
|
if c != nil {
|
|
|
|
return errThrow(c, 500, err, "upload failed")
|
|
|
|
}
|
|
|
|
return err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
err = savePost(p)
|
|
|
|
if err != nil {
|
|
|
|
if c != nil {
|
|
|
|
return errThrow(c, http.StatusInternalServerError, err, "internal error")
|
|
|
|
}
|
|
|
|
return err
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
// good to go, send them to the finisher function
|
|
|
|
p.Log().Trace().Msg("saved to database successfully, sending to NewPostResponse")
|
2021-02-15 20:52:35 +00:00
|
|
|
|
2022-07-14 07:50:26 +00:00
|
|
|
p.NewPostResponse(c)
|
|
|
|
return nil
|
2021-02-15 20:52:35 +00:00
|
|
|
}
|