2021-03-07 19:51:18 +00:00
|
|
|
package web
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
|
|
|
"git.1750studios.com/ToddShepard/ShortDragon/internal/database"
|
|
|
|
)
|
|
|
|
|
|
|
|
func redirect(c *gin.Context) {
|
|
|
|
var URL database.URL
|
|
|
|
short := c.Param("short")
|
|
|
|
err := database.Db.First(&URL, "short = ?", short).Error
|
|
|
|
if err != nil {
|
|
|
|
c.Redirect(http.StatusFound, viper.GetString("DefaultURL"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !URL.Long.Valid && !URL.FileName.Valid {
|
|
|
|
c.Redirect(http.StatusFound, viper.GetString("DefaultURL"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if URL.Hits.Valid && !(c.Request.Header.Get("DNT") == "1") {
|
|
|
|
URL.Hits.Int64 = URL.Hits.Int64 + 1
|
|
|
|
database.Db.Save(&URL)
|
|
|
|
}
|
|
|
|
if URL.Long.Valid {
|
|
|
|
c.Redirect(http.StatusFound, URL.Long.String)
|
|
|
|
} else {
|
|
|
|
c.Redirect(http.StatusFound, "/f/"+URL.Short.String)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func encode(c *gin.Context) {
|
|
|
|
var URL database.URL
|
|
|
|
var count uint
|
|
|
|
URL.Long.String = c.PostForm("LongURL")
|
|
|
|
if URL.Long.String == "" {
|
|
|
|
// No URL, maybe a file
|
|
|
|
URL.Long.Valid = false
|
|
|
|
file, err := c.FormFile("File")
|
|
|
|
if err != nil {
|
|
|
|
c.Data(http.StatusFailedDependency, "text/plain", []byte(fmt.Sprintf("Not a URL, nor File; Error: %+v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fparts := strings.Split(file.Filename, "/")
|
|
|
|
destName := path.Join(viper.GetString("FileFolder"), fparts[len(fparts)-1])
|
|
|
|
src, err := file.Open()
|
|
|
|
if err != nil {
|
|
|
|
c.Data(http.StatusFailedDependency, "text/plain", []byte(fmt.Sprintf("Error: %+v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer src.Close()
|
|
|
|
|
|
|
|
var newHash string
|
|
|
|
hasher := sha256.New()
|
2021-03-07 22:42:11 +00:00
|
|
|
hasher.Write([]byte(fparts[len(fparts)-1]))
|
2021-03-07 19:51:18 +00:00
|
|
|
io.Copy(hasher, src)
|
|
|
|
newHash = hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
database.Db.Where("file_hash = ?", newHash).FirstOrInit(&URL)
|
|
|
|
|
|
|
|
if !URL.FileName.Valid {
|
|
|
|
// No file with given hash in DB
|
|
|
|
if _, err := os.Stat(destName); err == nil {
|
|
|
|
// File does exist, save it with hash
|
|
|
|
destName += newHash
|
|
|
|
}
|
|
|
|
out, err := os.Create(destName)
|
|
|
|
if err != nil {
|
|
|
|
c.Data(http.StatusFailedDependency, "text/plain", []byte(fmt.Sprintf("Error: %+v", err)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer out.Close()
|
|
|
|
src.Seek(0, io.SeekStart)
|
|
|
|
io.Copy(out, src)
|
|
|
|
destParts := strings.Split(destName, "/")
|
|
|
|
|
|
|
|
URL.FileName.String = destParts[len(destParts)-1]
|
|
|
|
URL.FileName.Valid = true
|
|
|
|
URL.FileSize.Int64 = file.Size
|
|
|
|
URL.FileSize.Valid = true
|
|
|
|
URL.FileHash.String = newHash
|
|
|
|
URL.FileHash.Valid = true
|
|
|
|
URL.Long.Valid = false
|
|
|
|
|
|
|
|
retention := time.Duration(float64(viper.GetInt("MinAge")) + float64(-viper.GetInt("MaxAge")+viper.GetInt("MinAge"))*math.Pow(float64(URL.FileSize.Int64/viper.GetInt64("MaxSize")-1), 3.0)*24)
|
2021-03-07 21:33:23 +00:00
|
|
|
URL.FileDelete.Time = time.Now().Add(retention * time.Hour)
|
|
|
|
URL.FileDelete.Valid = true
|
2021-03-07 19:51:18 +00:00
|
|
|
} else {
|
|
|
|
retention := time.Duration(float64(viper.GetInt("MinAge")) + float64(-viper.GetInt("MaxAge")+viper.GetInt("MinAge"))*math.Pow(float64(URL.FileSize.Int64/viper.GetInt64("MaxSize")-1), 3.0)*24)
|
2021-03-07 21:33:23 +00:00
|
|
|
URL.FileDelete.Time = time.Now().Add(retention * time.Hour)
|
|
|
|
URL.FileDelete.Valid = true
|
2021-03-07 19:51:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
URL.Long.Valid = true
|
|
|
|
database.Db.Where("long = ?", URL.Long.String).FirstOrInit(&URL)
|
|
|
|
}
|
|
|
|
|
|
|
|
if URL.Short.Valid {
|
2021-03-07 23:59:57 +00:00
|
|
|
c.Data(http.StatusOK, "text/plain", []byte(viper.GetString("ShortURL")+"/r/"+URL.Short.String))
|
2021-03-07 19:51:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
URL.Short.String = c.DefaultPostForm("ShortURL", "")
|
|
|
|
if URL.Short.String == "" {
|
|
|
|
var hash string
|
|
|
|
if !URL.FileHash.Valid {
|
|
|
|
hasher := sha512.New()
|
|
|
|
hasher.Write([]byte(URL.Long.String))
|
|
|
|
hash = hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
} else {
|
|
|
|
hash = URL.FileHash.String
|
|
|
|
}
|
|
|
|
|
|
|
|
i := 2
|
|
|
|
for {
|
|
|
|
database.Db.Model(&database.URL{}).Where("short = ?", hash[0:i]).Count(&count)
|
|
|
|
if count > 0 && i < len(hash) {
|
|
|
|
i = i + 1
|
|
|
|
} else if count > 0 {
|
|
|
|
c.AbortWithStatus(http.StatusConflict)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
URL.Short.String = hash[0:i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
URL.Short.Valid = true
|
|
|
|
URL.Hits.Int64 = 0
|
|
|
|
if c.DefaultPostForm("Track", "true") == "true" {
|
|
|
|
URL.Hits.Valid = true
|
|
|
|
} else {
|
|
|
|
URL.Hits.Valid = false
|
|
|
|
}
|
|
|
|
err := database.Db.Create(&URL).Error
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
2021-03-07 21:27:32 +00:00
|
|
|
c.Data(http.StatusCreated, "text/plain", []byte(viper.GetString("ShortURL")+"/r/"+URL.Short.String))
|
2021-03-07 19:51:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func decode(c *gin.Context) {
|
|
|
|
var URL database.URL
|
|
|
|
short := c.Param("short")
|
|
|
|
err := database.Db.Where("short = ?", short).Find(&URL).Error
|
|
|
|
if err != nil && err == gorm.ErrRecordNotFound {
|
|
|
|
c.Data(http.StatusNotFound, "text/plain", []byte("Record not found"))
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if URL.Long.Valid {
|
|
|
|
c.Data(http.StatusOK, "text/plain", []byte(URL.Long.String))
|
|
|
|
} else {
|
|
|
|
c.Data(http.StatusOK, "text/plain", []byte(URL.FileName.String))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func info(c *gin.Context) {
|
|
|
|
var URL database.URL
|
|
|
|
short := c.Param("short")
|
|
|
|
err := database.Db.Where("short = ?", short).Find(&URL).Error
|
|
|
|
if err != nil && err == gorm.ErrRecordNotFound {
|
|
|
|
c.Data(http.StatusNotFound, "text/plain", []byte("Record not found"))
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.JSON(http.StatusOK, URL)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFile(c *gin.Context) {
|
|
|
|
var URL database.URL
|
|
|
|
short := c.Param("short")
|
|
|
|
err := database.Db.First(&URL, "short = ?", short).Error
|
|
|
|
if err != nil {
|
|
|
|
c.Redirect(http.StatusFound, viper.GetString("DefaultURL"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
file := path.Join(viper.GetString("FileFolder"), URL.FileName.String)
|
|
|
|
name := strings.TrimSuffix(file, URL.FileHash.String)
|
|
|
|
parts := strings.Split(name, "/")
|
|
|
|
|
|
|
|
if viper.GetBool("XAccel") {
|
|
|
|
c.Header("Content-Disposition", "attatchment; filename="+parts[len(parts)-1])
|
2021-03-07 22:23:59 +00:00
|
|
|
c.Header("X-Accel-Redirect", viper.GetString("XAccelPrefix")+"/"+URL.FileName.String)
|
2021-03-07 19:51:18 +00:00
|
|
|
} else {
|
|
|
|
c.FileAttachment(file, parts[len(parts)-1])
|
|
|
|
}
|
|
|
|
}
|