2020-12-28 10:13:45 +00:00
package main
2021-01-29 13:27:38 +00:00
import (
"bytes"
2020-12-28 10:13:45 +00:00
valid "github.com/asaskevich/govalidator"
2021-01-29 13:27:38 +00:00
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
2020-12-28 10:13:45 +00:00
exifremove "github.com/scottleedavis/go-exif-remove"
"github.com/twharmon/gouid"
2021-01-29 13:27:38 +00:00
"golang.org/x/crypto/blake2b"
"image"
2020-12-28 10:13:45 +00:00
_ "image/gif"
2021-01-29 13:27:38 +00:00
"io"
2020-12-28 10:13:45 +00:00
"io/ioutil"
"net/http"
2020-12-28 16:21:40 +00:00
"strings"
2020-12-28 10:13:45 +00:00
)
2020-12-28 16:21:40 +00:00
var fExt string
2020-12-28 10:13:45 +00:00
type Post struct {
2021-01-29 13:27:38 +00:00
Imgurl string ` json:"Imgurl" `
Delkey string ` json:"Delkey" `
2020-12-28 10:13:45 +00:00
}
func postUpload ( c * gin . Context , id string , key string ) {
2021-01-08 04:37:26 +00:00
imgurl := baseUrl + "i/" + string ( id )
keyurl := "duplicate"
2021-01-29 13:27:38 +00:00
if key != "nil" {
keyurl = baseUrl + "d/i/" + string ( key )
}
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
log . Info ( ) . Str ( "func" , "imgPost" ) . Str ( "id" , id ) . Str ( "status" , "201" ) . Str ( "imgurl" , imgurl ) . Str ( "keyurl" , keyurl )
c . JSON ( 201 , gin . H { "delkey" : keyurl , "imgurl" : imgurl } )
2021-01-08 04:37:26 +00:00
return
2020-12-28 10:13:45 +00:00
}
2020-12-28 16:21:40 +00:00
func imgDel ( c * gin . Context ) {
2021-01-24 20:23:09 +00:00
fn = "imgDel"
2021-01-24 15:28:13 +00:00
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "Request received!" ) // received request
2020-12-28 16:21:40 +00:00
rKey := c . Param ( "key" )
2021-01-29 13:27:38 +00:00
if len ( rKey ) != 16 || ! valid . IsAlphanumeric ( rKey ) {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Msg ( "delete request failed sanity check! email <a href=" mailto : staff @ urls . is ">staff</a> please." )
errThrow ( c , 400 , "400" , "400" ) // look for signs of pen testing..
2020-12-28 16:21:40 +00:00
return
}
targetImg , _ := keyDB . Get ( [ ] byte ( rKey ) )
2021-01-29 13:27:38 +00:00
if targetImg == nil || ! strings . Contains ( string ( targetImg ) , "i." ) {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rkey" , rKey ) . Msg ( "no img delete entry found with provided key. If this keeps happening, email <a href=" mailto : staff @ urls . is ">staff</a> please." )
errThrow ( c , 400 , "400" , "400" ) // Might have two windows open and deleted it.. otherwise look for permission errors, etc.
2020-12-28 16:21:40 +00:00
return
}
finalTarget := strings . Split ( string ( targetImg ) , "." )
if ! imgDB . Has ( [ ] byte ( finalTarget [ 1 ] ) ) {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rkey" , rKey ) . Msg ( "corresponding image not found in database! Do you have multiple tabs/windows open? If not, email <a href=" mailto : staff @ urls . is ">staff</a> please." )
errThrow ( c , 500 , "500" , "500" ) // Might have two windows open and deleted it.. otherwise look for permission errors, etc.
2020-12-28 16:21:40 +00:00
return
}
err := imgDB . Delete ( [ ] byte ( finalTarget [ 1 ] ) )
if err != nil {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rkey" , finalTarget [ 1 ] ) . Msg ( "delete failed! Did you already delete it? email <a href=" mailto : staff @ urls . is ">staff</a> for support issues." )
errThrow ( c , 500 , "500" , "500" ) // Check permissions, hd space, tx/rx errors..
2020-12-28 16:21:40 +00:00
return
}
if imgDB . Has ( [ ] byte ( finalTarget [ 1 ] ) ) {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rkey" , finalTarget [ 1 ] ) . Msg ( "delete failed!? Did you already delete it? email <a href=" mailto : staff @ urls . is ">staff</a> for support issues." )
2021-01-24 15:28:13 +00:00
errThrow ( c , 500 , "500" , "500" )
2020-12-28 16:21:40 +00:00
return
}
2021-01-29 13:27:38 +00:00
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" )
2020-12-28 16:21:40 +00:00
err = keyDB . Delete ( [ ] byte ( rKey ) )
if err != nil {
2021-02-13 15:58:10 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rkey" , finalTarget [ 1 ] ) . Msg ( "Couldn't delete key. email <a href=" mailto : staff @ urls . is ">support staff</a> about this ASAP." )
2021-01-29 13:27:38 +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
2021-02-13 15:58:10 +00:00
// we will delete the hash entry then and re-add then.
// also they are likely fucking with the server.
2020-12-28 16:21:40 +00:00
}
2021-01-24 15:28:13 +00:00
func imgView ( c * gin . Context ) {
2021-01-24 20:23:09 +00:00
fn = "imgView"
2021-01-29 13:27:38 +00:00
// 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)
2020-12-28 16:21:40 +00:00
sUid := strings . Split ( c . Param ( "uid" ) , "." )
rUid := sUid [ 0 ]
if len ( sUid ) > 1 {
fExt = strings . ToLower ( sUid [ 1 ] )
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "ext" , fExt ) . Msg ( "detected file extension" )
if fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif" {
2021-01-24 20:23:09 +00:00
log . Error ( ) . Str ( "func" , fn ) . Msg ( "Bad file extension!" )
2021-01-24 15:28:13 +00:00
errThrow ( c , 400 , "400" , "400" )
2020-12-28 10:13:45 +00:00
return
}
2021-01-29 13:27:38 +00:00
} else {
fExt = "nil"
}
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
if ! valid . IsAlphanumeric ( rUid ) || len ( rUid ) < 3 || len ( rUid ) > 16 {
log . Error ( ) . Str ( "func" , fn ) . Msg ( "request discarded as invalid" ) // these limits should be variables eventually
errThrow ( c , 400 , "400" , "400" )
2020-12-28 16:21:40 +00:00
return
}
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Msg ( "request validated" ) // now that we think its a valid request we will query
2020-12-28 10:13:45 +00:00
2020-12-28 16:21:40 +00:00
fBytes , _ := imgDB . Get ( [ ] byte ( rUid ) )
if fBytes == nil {
2021-01-29 13:27:38 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Msg ( "no corresponding file for this id" )
2020-12-28 16:21:40 +00:00
errThrow ( c , 404 , "404" , "File not found" )
return
2020-12-28 10:13:45 +00:00
}
2020-12-28 16:21:40 +00:00
2021-01-24 20:23:09 +00:00
file := bytes . NewReader ( fBytes )
2020-12-28 16:21:40 +00:00
imageFormat , ok := checkImage ( file )
2021-01-29 13:27:38 +00:00
if ! ok {
2021-01-24 20:23:09 +00:00
errThrow ( c , http . StatusBadRequest , "400" , "400" )
2021-01-29 13:27:38 +00:00
log . Error ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Msg ( "the file we grabbed is not an image!?" ) // not sure how a non image would get uploaded
return // however, better safe than sorry
} else {
log . Debug ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Str ( "imageFormat" , imageFormat ) . Msg ( "Image format detected" )
}
2020-12-28 16:21:40 +00:00
2021-01-29 13:27:38 +00:00
if fExt != "nil" && fExt != imageFormat { // additional extension sanity check
log . Error ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Msg ( "requested file extension does not match filetype" )
errThrow ( c , 400 , "400" , "400" )
2020-12-28 16:21:40 +00:00
return
}
2021-01-29 13:27:38 +00:00
contentType := "image/" + imageFormat // extension or not
// we give them the proper content type
2020-12-28 16:21:40 +00:00
c . Data ( 200 , contentType , fBytes )
2021-01-29 13:27:38 +00:00
log . Info ( ) . Str ( "func" , fn ) . Str ( "rUid" , rUid ) . Msg ( "Successful upload" )
2020-12-28 10:13:45 +00:00
}
func imgPost ( c * gin . Context ) {
2021-01-24 20:23:09 +00:00
fn = "imgPost"
2020-12-28 10:13:45 +00:00
var Scrubbed [ ] byte
f , err := c . FormFile ( "upload" )
if err != nil {
2021-01-24 15:28:13 +00:00
errThrow ( c , http . StatusBadRequest , err . Error ( ) , "400" ) // 400 bad request
2021-01-29 13:27:38 +00:00
} // incoming POST data is invalid
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "filename" , f . Filename ) . Msg ( "[+] New upload" )
2020-12-28 10:13:45 +00:00
2021-02-13 15:58:10 +00:00
file , err := f . Open ( ) // Eventually replace this with hashing routine to make it browser PSK encrypted faux E2E...
2020-12-28 10:13:45 +00:00
if err != nil {
errThrow ( c , http . StatusInternalServerError , err . Error ( ) , "error processing file\n" )
}
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "verifying file is an image" )
2020-12-28 10:13:45 +00:00
imageFormat , ok := checkImage ( file )
if ! ok {
2021-02-13 15:58:10 +00:00
errThrow ( c , http . StatusBadRequest , "400" , "input does not appear to be an image. you will be rate limited if this keeps happening." )
return // add counter function and start rate limiting eventually.
2021-01-29 13:27:38 +00:00
} else {
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "image file type detected" )
}
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "dumping byte form of file" )
2020-12-28 10:13:45 +00:00
fbytes , err := ioutil . ReadAll ( file )
if imageFormat != "gif" {
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Err ( err ) . Msg ( "error scrubbing exif" )
2020-12-28 10:13:45 +00:00
Scrubbed , err = exifremove . Remove ( fbytes )
if err != nil {
errThrow ( c , http . StatusInternalServerError , err . Error ( ) , "error scrubbing exif" )
return
}
} else {
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "skipping exif scrub for gif image" )
2020-12-28 10:13:45 +00:00
Scrubbed = fbytes
}
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "calculating blake2b checksum" )
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
Hashr , _ := blake2b . New ( 64 , nil )
2020-12-28 10:13:45 +00:00
Hashr . Write ( Scrubbed )
hash := Hashr . Sum ( nil )
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Msg ( "Checking for duplicate's in database" )
2020-12-28 10:13:45 +00:00
2020-12-28 16:21:40 +00:00
imgRef , _ := hashDB . Get ( hash )
2021-01-24 20:23:09 +00:00
ogUid := string ( imgRef )
2020-12-28 10:13:45 +00:00
if imgRef != nil {
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "ogUid" , ogUid ) . Msg ( "duplicate checksum in hash database, checking if file still exists..." )
2020-12-28 16:21:40 +00:00
if imgDB . Has ( imgRef ) {
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "ogUid" , ogUid ) . Msg ( "duplicate file found! returning original URL" )
2021-02-13 15:58:10 +00:00
postUpload ( c , ogUid , "nil" ) // they weren't the original uploader so they don't get a delete key for now.
return // ENHANCEMENT: Needs to hash the file with session and/or epoch of time uploaded.
2020-12-28 16:21:40 +00:00
} else {
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "ogUid" , ogUid ) . Msg ( "stale hash found, deleting entry..." )
2020-12-28 16:21:40 +00:00
hashDB . Delete ( hash )
}
2020-12-28 10:13:45 +00:00
}
2021-01-29 13:27:38 +00:00
log . Info ( ) . Str ( "func" , fn ) . Msg ( "no duplicate images found, generating uid and delete key" )
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
uid := gouid . String ( uidSize ) // these should both be config directives eventually
key := gouid . String ( keySize ) // generate delete key
2020-12-28 10:13:45 +00:00
// lets make sure that we don't clash even though its highly unlikely
for uidRef , _ := imgDB . Get ( [ ] byte ( uid ) ) ; uidRef != nil ; {
2021-01-29 13:27:38 +00:00
log . Info ( ) . Str ( "func" , fn ) . Msg ( " uid already exists! generating new..." )
uid = gouid . String ( uidSize )
2020-12-28 10:13:45 +00:00
}
2020-12-28 16:21:40 +00:00
for keyRef , _ := keyDB . Get ( [ ] byte ( key ) ) ; keyRef != nil ; {
2021-01-29 13:27:38 +00:00
log . Info ( ) . Str ( "func" , fn ) . Msg ( " delete key already exists! generating new..." )
key = gouid . String ( keySize )
2020-12-28 10:13:45 +00:00
}
2021-01-29 13:27:38 +00:00
hashDB . Put ( [ ] byte ( hash ) , [ ] byte ( uid ) ) // save checksum to db to prevent dupes in the future
2020-12-28 10:13:45 +00:00
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "uid" , uid ) . Msg ( "saving file to database" )
2020-12-28 10:13:45 +00:00
err = imgDB . Put ( [ ] byte ( uid ) , [ ] byte ( Scrubbed ) )
if err != nil {
2020-12-28 16:21:40 +00:00
errThrow ( c , 500 , err . Error ( ) , "upload failed" )
2020-12-28 10:13:45 +00:00
return
}
2021-01-29 13:27:38 +00:00
err = keyDB . Put ( [ ] byte ( key ) , [ ] byte ( "i." + uid ) ) // add delete key to database with image prefix
2020-12-28 10:13:45 +00:00
if err != nil {
errThrow ( c , http . StatusInternalServerError , err . Error ( ) , "internal error" )
return
}
2021-01-29 13:27:38 +00:00
log . Debug ( ) . Str ( "func" , fn ) . Str ( "uid" , uid ) . Msg ( "saved to database successfully, sending to postUpload" )
2021-02-13 15:58:10 +00:00
// Make tick for metrics without getting all sketchy on privacy.
2020-12-28 10:13:45 +00:00
postUpload ( c , uid , key )
}
func checkImage ( r io . ReadSeeker ) ( string , bool ) {
_ , fmt , err := image . Decode ( r )
_ , err2 := r . Seek ( 0 , 0 )
if err != nil || err2 != nil {
return "" , false
}
return fmt , true
}
func getSize ( s io . Seeker ) ( size int64 , err error ) {
if _ , err = s . Seek ( 0 , 0 ) ; err != nil {
return
}
// 2 == from the end of the file
if size , err = s . Seek ( 0 , 2 ) ; err != nil {
return
}
_ , err = s . Seek ( 0 , 0 )
return
}