diff --git a/img.go b/img.go index 3d76fc7..2360990 100644 --- a/img.go +++ b/img.go @@ -3,19 +3,22 @@ package main import( valid "github.com/asaskevich/govalidator" exifremove "github.com/scottleedavis/go-exif-remove" + "golang.org/x/crypto/blake2b" "github.com/twharmon/gouid" "github.com/gin-gonic/gin" "encoding/json" _ "image/gif" - "crypto/md5" "io/ioutil" "net/http" + "strings" "image" "bytes" "fmt" "io" ) +var fExt string + type Post struct { Imgurl string `json:"Imgurl"` Delkey string `json:"Delkey"` @@ -23,13 +26,15 @@ type Post struct { func postUpload(c *gin.Context, id string, key string) { imgurl := baseUrl + "i/" + string(id) - keyurl := baseUrl + "i/d/" + string(key) + keyurl := "image_is_duplicate" + if key != "nil" { keyurl = baseUrl + "d/i/" + string(key) } d := Post{ Imgurl: imgurl, Delkey: keyurl, } + var p []byte p, err := json.Marshal(d) if err != nil { @@ -37,34 +42,106 @@ func postUpload(c *gin.Context, id string, key string) { return } - c.JSON(200, string(p)) // they weren't the original uploader so they don't get a delete key + fmt.Println("[imgPost]["+id+"] Success: " + imgurl + " " + keyurl) + c.JSON(200, string(p)) return } -func imgView(c *gin.Context) { - rUid := c.Param("uid") - fmt.Println("[imgView] Received request") - if (valid.IsAlphanumeric(rUid)) { - fmt.Println("[imgView][" + rUid + "] Request validated") - fBytes, _ := imgDB.Get([]byte(rUid)) - if fBytes == nil { - fmt.Println("[imgView] No data found for: " + rUid) - errThrow(c, 404, "404", "File not found") +func imgDel(c *gin.Context) { + fmt.Println("[imgDel] Received request") + rKey := c.Param("key") + if (len(rKey) != 16 || !valid.IsAlphanumeric(rKey)) { + fmt.Println("[imgDel] delete request failed sanity check") + errThrow(c, 400, "400", "Bad request") + return + } + + targetImg, _ := keyDB.Get([]byte(rKey)) + if (targetImg == nil || !strings.Contains(string(targetImg), "i.")) { + fmt.Println("[imgDel] no img delete entry found with key: " + rKey) + errThrow(c, 400, "400", "Bad request") + return + } + + finalTarget := strings.Split(string(targetImg), ".") + + if !imgDB.Has([]byte(finalTarget[1])) { + fmt.Println("[imgDel]["+finalTarget[1]+"] corresponding image not found in database??") + errThrow(c, 500, "500", "Internal error") // this shouldn't happen...? + return + } + err := imgDB.Delete([]byte(finalTarget[1])) + if err != nil { + fmt.Println("[imgDel]["+finalTarget[1]+"] Delete failed!!") + errThrow(c, 500, err.Error(), "Internal error") + return + } + + if imgDB.Has([]byte(finalTarget[1])) { + fmt.Println("[imgDel]["+finalTarget[1]+"] Delete failed!?") + errThrow(c, 500, err.Error(), "Internal error") + return + } + + fmt.Println("[imgDel]["+finalTarget[1]+"] Image file deleted successfully") + fmt.Println("[imgDel]["+finalTarget[1]+"] Removing delete key entry") + err = keyDB.Delete([]byte(rKey)) + if err != nil { + fmt.Println("[imgDel]["+finalTarget[1]+"] 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, "OK") // 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) { // the user can access their image with or without a file extension in URI + fmt.Println("[imgView] Received request") // 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]) + fmt.Println("[imgView] Detected file extension: " + fExt) + if (fExt != "png" && fExt != "jpg" && fExt != "jpeg" && fExt != "gif") { + fmt.Println("[imgView] Bad file extension!") + errThrow(c, 400, "400", "Bad request") return } + } else { fExt = "nil" } - fmt.Println("[imgView][" + rUid + "] Detecting image type") - file := bytes.NewReader(fBytes) - imageFormat, ok := checkImage(file) - if !ok { - errThrow(c, http.StatusBadRequest, "bad request", "content does not appear to be an image") - return - } else { fmt.Println("[imgView][" + rUid + "] " + imageFormat + " detected") } - contentType := "image/" + imageFormat - - c.Data(200, contentType, fBytes) + if (!valid.IsAlphanumeric(rUid) || len(rUid) < 3 || len(rUid) > 16) { + fmt.Println("[imgView] request discarded as invalid") // these limits should be variables eventually + errThrow(c,400,"400", "Bad request") + return } + + fmt.Println("[imgView][" + rUid + "] Request validated") // now that we think its a valid request we will query + + fBytes, _ := imgDB.Get([]byte(rUid)) + if fBytes == nil { + fmt.Println("[imgView] No data found for: " + rUid) + errThrow(c, 404, "404", "File not found") + return + } + + fmt.Println("[imgView][" + rUid + "] Detecting image type") // not sure how a non image would get uploaded + file := bytes.NewReader(fBytes) // however, better safe than sorry + imageFormat, ok := checkImage(file) + if !ok { + errThrow(c, http.StatusBadRequest, "bad request", "content does not appear to be an image") + return + } else { fmt.Println("[imgView][" + rUid + "] " + imageFormat + " detected") } + + if (fExt != "nil" && fExt != imageFormat) { // additional extension sanity check + fmt.Println("[imgView][" + rUid + "] given file extension does not match filetype " + imageFormat) + errThrow(c,400,"400", "Bad request") + return + } + + contentType := "image/" + imageFormat // extension or not + // we give them the proper content type + c.Data(200, contentType, fBytes) + } @@ -104,25 +181,31 @@ func imgPost(c *gin.Context) { Scrubbed = fbytes } - fmt.Println("[imgPost] calculating MD5 hash") + fmt.Println("[imgPost] calculating blake2b checksum") - Hashr := md5.New() + Hashr, _ := blake2b.New(64,nil) Hashr.Write(Scrubbed) hash := Hashr.Sum(nil) fmt.Println("[imgPost] Checking for duplicate's in database") - imgRef, _ := md5DB.Get(hash) + imgRef, _ := hashDB.Get(hash) if imgRef != nil { - fmt.Println("[imgPost][" + string(imgRef) + "] duplicate file found in md5 database, returning URL for uid: " + string(imgRef)) - postUpload(c,string(imgRef),"nil") // they weren't the original uploader so they don't get a delete key - return + fmt.Println("[imgPost][" + string(imgRef) + "] duplicate checksum in hash database, checking if file still exists...") + if imgDB.Has(imgRef) { + fmt.Println("[imgPost][" + string(imgRef) + "] duplicate file found! returning URL for uid: " + string(imgRef)) + postUpload(c,string(imgRef),"nil") // they weren't the original uploader so they don't get a delete key + return + } else { + fmt.Println("[imgPost][" + string(imgRef) + "] stale hash found, deleting entry...") + hashDB.Delete(hash) + } } - fmt.Println("[imgPost] no duplicate md5s found, generating uid and delete key") + fmt.Println("[imgPost] no duplicate images found, generating uid and delete key") - uid := gouid.String(5) + uid := gouid.String(5) // these should both be config directives eventually key := gouid.String(16) // generate delete key @@ -131,23 +214,23 @@ func imgPost(c *gin.Context) { fmt.Println("[imgPost] uid already exists! generating new...") uid = gouid.String(5) } - for keyRef, _ := keyDB.Get([]byte(uid)); keyRef != nil; { + for keyRef, _ := keyDB.Get([]byte(key)); keyRef != nil; { fmt.Println("[imgPost] delete key already exists! generating new...") key = gouid.String(16) } - md5DB.Put([]byte(hash),[]byte(uid)) // save md5 to db to prevent dupes in the future + hashDB.Put([]byte(hash),[]byte(uid)) // save checksum to db to prevent dupes in the future fmt.Println("[imgPost][" + uid + "] saving file to database") err = imgDB.Put([]byte(uid), []byte(Scrubbed)) if err != nil { - errThrow(c, http.StatusInternalServerError, err.Error(), "upload failed") + errThrow(c, 500, err.Error(), "upload failed") return } - err = keyDB.Put([]byte("i." + uid), []byte(key)) // add delete key to database with image prefix + err = keyDB.Put([]byte(key), []byte("i." + uid)) // add delete key to database with image prefix if err != nil { errThrow(c, http.StatusInternalServerError, err.Error(), "internal error") return diff --git a/main.go b/router.go similarity index 91% rename from main.go rename to router.go index 7ea9c80..4b46973 100644 --- a/main.go +++ b/router.go @@ -9,7 +9,7 @@ import ( ) var imgDB *bitcask.Bitcask -var md5DB *bitcask.Bitcask +var hashDB *bitcask.Bitcask var keyDB *bitcask.Bitcask var urlDB *bitcask.Bitcask var txtDB *bitcask.Bitcask @@ -65,7 +65,7 @@ func init() { imgDB, _ = bitcask.Open("./data/img", opts...) fmt.Println("Initializing img database") - md5DB, _ = bitcask.Open("./data/md5", opts...) // this will probably only be for images + hashDB, _ = bitcask.Open("./data/hsh", opts...) // this will probably only be for images fmt.Println("Initializing md5 database") txtDB, _ = bitcask.Open("./data/txt") @@ -89,6 +89,11 @@ func main() { imgR.GET("/:uid", imgView) } + delR := router.Group("/d") + { + delR.GET("/i/:key", imgDel) + } + txtR := router.Group("/t") { txtR.POST("/put", txtPost)