Add extensions to support Discord embeds

This commit is contained in:
Tobias B 2021-11-18 02:35:04 +01:00
parent 1a2dbf266d
commit a81a33c17a
No known key found for this signature in database
GPG Key ID: 5EF4C92355A3B53D
10 changed files with 109 additions and 54 deletions

View File

@ -4,5 +4,7 @@ FILE_NAME_LENGTH=12
FILE_MAX_SIZE=100
FILE_META_DB_PATH=
FILE_EXPIRATION_CYCLE=10
METRICS_ENABLED=true
FILE_SERVING_ENABLED=true
FILE_EXTENSIONS_RESPONSE=true
FILE_EXTENSIONS_EXCLUDED=image/png,image/jpeg
METRICS_ENABLED=true

View File

@ -82,8 +82,10 @@ For examplary usage of the environment variables, have a look at the `.env.dist`
| `FILE_MAX_SIZE` | Maximum size for uploaded files in Megabytes. |
| `FILE_META_DB_PATH` | Path to the directory, where the sqlite database for file metadata should be stored. Recommended to not be the same folder as `FILE_STORAGE_PATH` to prevent overlapping. |
| `FILE_EXPIRATION_CYCLE` | Determines the interval of the expiration cycle. `5` means that every 5 seconds the files will be checked for expiration. |
| `FILE_SERVING_ENABLED` | Defaults to `true`, if `false`, the server won't serve the stored files. |
| `FILE_EXTENSIONS_RESPONSE` | Defaults to `true`. if the file name returned will have its extension added to it. |
| `FILE_EXTENSIONS_EXCLUDED` | Comma-seperated list of MIME types, that should be excluded from the extension response rule above. Defaults to `image/png,image/jpeg` |
| `METRICS_ENABLED` | Is normally set to true, but otherwise disables the Prometheus metrics publishing. |
| `FILE_SERVING_ENABLED` | Defaults to true, when `false`, then the server won't serve the stored files. |
## Tokens
@ -182,7 +184,7 @@ After that you can import it to your custom upload goals in the ShareX UI.
"metadata": "{ \"expiration\": 3600 }"
},
"FileFormName": "file",
"URL": "https://your-domain.com/$json:id$"
"URL": "https://your-domain.com/$json:fileName$"
}
```

View File

@ -1 +1 @@
0.6.1
0.6.2

View File

@ -15,7 +15,7 @@ import (
func main() {
err := godotenv.Load()
if err != nil {
klog.Warningf("Error loading .env file: %v", err)
klog.Warningf("Could not load .env file: %v", err)
}
klog.Infoln("Hello World!")

View File

@ -6,6 +6,13 @@ import (
"os"
)
const (
ExpireNever = -1
EnvDefaultFileStoragePath = "/var/lib/aqua/files/"
EnvDefaultMetaDbPath = "/var/lib/aqua/"
)
type AuthConfig struct {
ValidTokens []*TokenConfig `yaml:"validTokens"`
}

View File

@ -1,12 +1,12 @@
package handler
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/superioz/aqua/internal/config"
"github.com/superioz/aqua/internal/metrics"
"github.com/superioz/aqua/internal/mime"
"github.com/superioz/aqua/internal/request"
"github.com/superioz/aqua/internal/storage"
"github.com/superioz/aqua/pkg/env"
"k8s.io/klog"
@ -20,27 +20,13 @@ const (
SizeMegaByte = 1 << (10 * 2)
)
var (
emptyRequestMetadata = &RequestMetadata{Expiration: storage.ExpireNever}
)
// TODO when accessing: `/N2YwODUx.mp4` => `/N2YwODUx`
// RequestFormFile is the metadata we get from the file
// which is requested to be uploaded.
type RequestFormFile struct {
File multipart.File
ContentType string
ContentLength int64
}
type RequestMetadata struct {
Expiration int64 `json:"expiration"`
}
type UploadHandler struct {
AuthConfig *config.AuthConfig
FileStorage *storage.FileStorage
// excluded as per defined by the environment variable
// FILE_EXTENSIONS_EXCEPT
exclMimeTypes []string
}
func NewUploadHandler() *UploadHandler {
@ -48,6 +34,7 @@ func NewUploadHandler() *UploadHandler {
handler.ReloadAuthConfig()
handler.FileStorage = storage.NewFileStorage()
handler.exclMimeTypes = env.ListOrDefault("FILE_EXTENSIONS_EXCLUDED", []string{"image/png", "image/jpeg"})
return handler
}
@ -124,8 +111,8 @@ func (h *UploadHandler) Upload(c *gin.Context) {
}
defer of.Close()
metadata := getMetadata(form)
rff := &RequestFormFile{
metadata := request.GetMetadata(form)
rff := &request.RequestFormFile{
File: of,
ContentType: ct,
ContentLength: c.Request.ContentLength,
@ -145,22 +132,26 @@ func (h *UploadHandler) Upload(c *gin.Context) {
klog.Infof("Stored file %s (expiresIn: %s)", sf.Id, expiresIn)
metrics.IncFilesUploaded()
c.JSON(http.StatusOK, gin.H{"id": sf.Id})
// add extension to file name if it is enabled and
// the extension is not excluded
storedName := sf.Id
if env.BoolOrDefault("FILE_EXTENSIONS_RESPONSE", true) && !h.isExtensionExcluded(sf.MimeType) {
storedName = fmt.Sprintf("%s.%s", storedName, mime.GetExtension(sf.MimeType))
}
c.JSON(http.StatusOK, gin.H{"fileName": storedName})
}
func getMetadata(form *multipart.Form) *RequestMetadata {
metaRawList := form.Value["metadata"]
if len(metaRawList) == 0 {
return emptyRequestMetadata
// isExtensionExcluded returns if the given mime type
// should be excluded by the extension response rule, which states if
// an extension should be appended to the file name when responding.
func (h *UploadHandler) isExtensionExcluded(mimeType string) bool {
for _, exclMimeType := range h.exclMimeTypes {
if exclMimeType == mimeType {
return true
}
}
metaRaw := metaRawList[0]
var metadata *RequestMetadata
err := json.Unmarshal([]byte(metaRaw), &metadata)
if err != nil {
return emptyRequestMetadata
}
return metadata
return false
}
// workaround for file Content-Type headers
@ -191,10 +182,17 @@ func getToken(c *gin.Context) string {
// HandleStaticFiles takes the files inside the configured file storage
// path and serves them to the client.
func HandleStaticFiles() gin.HandlerFunc {
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", storage.EnvDefaultFileStoragePath)
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", config.EnvDefaultFileStoragePath)
return func(c *gin.Context) {
fileName := c.Param("file")
// the file name could contain the extension
// we split it and ship it.
if strings.Contains(fileName, ".") {
fileName = strings.Split(fileName, ".")[0]
}
fullPath := fileStoragePath + fileName
f, err := os.Open(fullPath)

View File

@ -28,7 +28,7 @@ var (
// IsValid checks if given type is inside Types map
func IsValid(t string) bool {
for _, mt := range Types {
for mt := range Types {
if mt == t {
return true
}

View File

@ -0,0 +1,38 @@
package request
import (
"encoding/json"
"github.com/superioz/aqua/internal/config"
"mime/multipart"
)
var (
emptyRequestMetadata = &RequestMetadata{Expiration: config.ExpireNever}
)
// RequestFormFile is the metadata we get from the file
// which is requested to be uploaded.
type RequestFormFile struct {
File multipart.File
ContentType string
ContentLength int64
}
type RequestMetadata struct {
Expiration int64 `json:"expiration"`
}
func GetMetadata(form *multipart.Form) *RequestMetadata {
metaRawList := form.Value["metadata"]
if len(metaRawList) == 0 {
return emptyRequestMetadata
}
metaRaw := metaRawList[0]
var metadata *RequestMetadata
err := json.Unmarshal([]byte(metaRaw), &metadata)
if err != nil {
return emptyRequestMetadata
}
return metadata
}

View File

@ -5,8 +5,9 @@ import (
"errors"
"fmt"
"github.com/google/uuid"
"github.com/superioz/aqua/internal/handler"
"github.com/superioz/aqua/internal/config"
"github.com/superioz/aqua/internal/metrics"
"github.com/superioz/aqua/internal/request"
"github.com/superioz/aqua/pkg/env"
"k8s.io/klog"
"strings"
@ -15,13 +16,6 @@ import (
_ "modernc.org/sqlite"
)
const (
ExpireNever = -1
EnvDefaultFileStoragePath = "/var/lib/aqua/files/"
EnvDefaultMetaDbPath = "/var/lib/aqua/"
)
type StoredFile struct {
Id string
UploadedAt int64
@ -43,14 +37,14 @@ type FileStorage struct {
}
func NewFileStorage() *FileStorage {
metaDbFilePath := env.StringOrDefault("FILE_META_DB_PATH", EnvDefaultMetaDbPath)
metaDbFilePath := env.StringOrDefault("FILE_META_DB_PATH", config.EnvDefaultMetaDbPath)
fileMetaDb := NewSqliteFileMetaDatabase(metaDbFilePath)
err := fileMetaDb.Connect()
if err != nil {
klog.Errorf("Could not connect to file meta db: %v", err)
}
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", EnvDefaultFileStoragePath)
fileStoragePath := env.StringOrDefault("FILE_STORAGE_PATH", config.EnvDefaultFileStoragePath)
fileSystem := NewLocalFileStorage(fileStoragePath)
return &FileStorage{
@ -102,7 +96,7 @@ func (fs *FileStorage) Cleanup() error {
return nil
}
func (fs *FileStorage) StoreFile(rff *handler.RequestFormFile, expiration int64) (*StoredFile, error) {
func (fs *FileStorage) StoreFile(rff *request.RequestFormFile, expiration int64) (*StoredFile, error) {
name, err := getRandomFileName(env.IntOrDefault("FILE_NAME_LENGTH", 8))
if err != nil {
return nil, errors.New("could not generate random name")
@ -116,8 +110,8 @@ func (fs *FileStorage) StoreFile(rff *handler.RequestFormFile, expiration int64)
currentTime := time.Now().Unix()
expAt := currentTime + expiration
if expiration == ExpireNever {
expAt = ExpireNever
if expiration == config.ExpireNever {
expAt = config.ExpireNever
}
sf := &StoredFile{

14
pkg/env/env.go vendored
View File

@ -3,6 +3,7 @@ package env
import (
"os"
"strconv"
"strings"
)
func String(name string) (string, bool) {
@ -57,3 +58,16 @@ func BoolOrDefault(name string, def bool) bool {
}
return b
}
func ListOrDefault(name string, def []string) []string {
s, ok := String(name)
if !ok {
return def
}
spl := strings.Split(s, ",")
if len(spl) == 0 {
return def
}
return spl
}