Update routes to return structured JSON
This commit is contained in:
parent
e539043714
commit
418c21139a
|
@ -2,7 +2,7 @@ package routes
|
|||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/security"
|
||||
"tytanium/security"
|
||||
)
|
||||
|
||||
// ServeAuthCheck validates the master key by calling IsAuthorized.
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"bytes"
|
||||
_ "embed"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/global"
|
||||
"github.com/vysiondev/tytanium/logger"
|
||||
"io"
|
||||
"tytanium/global"
|
||||
"tytanium/logger"
|
||||
)
|
||||
|
||||
//go:embed favicon.ico
|
||||
|
|
|
@ -3,24 +3,24 @@ package routes
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/minio/sio"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/constants"
|
||||
"github.com/vysiondev/tytanium/global"
|
||||
"github.com/vysiondev/tytanium/response"
|
||||
"github.com/vysiondev/tytanium/security"
|
||||
"github.com/vysiondev/tytanium/utils"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"tytanium/constants"
|
||||
"tytanium/encryption"
|
||||
"tytanium/global"
|
||||
"tytanium/response"
|
||||
"tytanium/security"
|
||||
"tytanium/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
rawParam = "raw"
|
||||
ZeroWidthCharacterFirstByte = 243
|
||||
)
|
||||
const encryptionKeyParam = "enc_key"
|
||||
const rawParam = "raw"
|
||||
|
||||
// discordHTML represents what is sent back to any client which User-Agent contains the regex contained in
|
||||
// discordBotRegex.
|
||||
|
@ -46,24 +46,33 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
pBytes := ctx.Request.URI().Path()
|
||||
|
||||
if len(pBytes) > constants.PathLengthLimitBytes {
|
||||
response.SendTextResponse(ctx, "Path is too long.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Path is too long.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pBytes) <= 1 {
|
||||
response.SendTextResponse(ctx, "Path is too short.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Path is too short.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.QueryArgs().Peek(encryptionKeyParam)) == 0 {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "No encryption key was provided. (enc_key)",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
p := string(pBytes[1:])
|
||||
// Convert entire path to normal string if a zero-width character is detected at the beginning.
|
||||
if pBytes[1] == ZeroWidthCharacterFirstByte {
|
||||
p = utils.StringToZeroWidthCharacters(p)
|
||||
if len(p) == 0 {
|
||||
response.SendTextResponse(ctx, "Malformed zero-width URL path.", fasthttp.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filePath := path.Join(global.Configuration.Storage.Directory, p)
|
||||
|
||||
|
@ -74,43 +83,92 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
ServeNotFound(ctx)
|
||||
return
|
||||
}
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("os.Stat() could not be called on the file. %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("os.Stat() could not be called on the file. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
response.SendTextResponse(ctx, "This is a directory, not a file", fasthttp.StatusBadRequest)
|
||||
ServeNotFound(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if global.Configuration.RateLimit.Bandwidth.Download > 0 && global.Configuration.RateLimit.Bandwidth.ResetAfter > 0 {
|
||||
isBandwidthLimitNotReached, err := security.Try(ctx, global.RedisClient, fmt.Sprintf("%s_%s", constants.RateLimitBandwidthDownload, utils.GetIP(ctx)), int64(global.Configuration.RateLimit.Bandwidth.Download), int64(global.Configuration.RateLimit.Bandwidth.ResetAfter), fileInfo.Size())
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Bandwidth limit couldn't be checked. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
if !isBandwidthLimitNotReached {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Download bandwidth limit reached; try again later.",
|
||||
}, fasthttp.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We don't need a limited reader because mimetype.DetectReader automatically caps it
|
||||
fileReader, e := os.OpenFile(filePath, os.O_RDONLY, 0644)
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("The file could not be opened. %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("The file could not be opened. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = fileReader.Close()
|
||||
}()
|
||||
|
||||
if global.Configuration.RateLimit.Bandwidth.Download > 0 && global.Configuration.RateLimit.Bandwidth.ResetAfter > 0 {
|
||||
isBandwidthLimitNotReached, err := security.Try(ctx, global.RedisClient, fmt.Sprintf("%s_%s", constants.RateLimitBandwidthDownload, utils.GetIP(ctx)), int64(global.Configuration.RateLimit.Bandwidth.Download), int64(global.Configuration.RateLimit.Bandwidth.ResetAfter), fileInfo.Size())
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Bandwidth limit couldn't be checked. %v", err), fasthttp.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !isBandwidthLimitNotReached {
|
||||
response.SendTextResponse(ctx, "Download bandwidth limit reached; try again later.", fasthttp.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mimeType, e := mimetype.DetectReader(fileReader)
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Cannot detect the mime type of this file retrieved from server. It might be corrupted. %v", e), fasthttp.StatusBadRequest)
|
||||
key, err := encryption.DeriveKey(ctx.QueryArgs().Peek(encryptionKeyParam), []byte(global.Configuration.Encryption.Nonce))
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to generate encryption key. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
decryptedReader, err := sio.DecryptReader(fileReader, sio.Config{Key: key[:]})
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to create a decrypted reader for mime type inspection. %v", e),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
mimeType, e := mimetype.DetectReader(decryptedReader)
|
||||
if e != nil {
|
||||
response.SendInvalidEncryptionKeyResponse(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
filterStatus := security.FilterCheck(ctx, mimeType.String())
|
||||
if filterStatus == security.FilterFail {
|
||||
// already sent a response if filter check failed
|
||||
return
|
||||
} else if filterStatus == security.FilterSanitize {
|
||||
ctx.Response.Header.Set("Content-Type", "text/plain; charset=utf8")
|
||||
} else {
|
||||
ctx.Response.Header.Set("Content-Type", mimeType.String())
|
||||
}
|
||||
|
||||
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", p))
|
||||
ctx.Response.Header.Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
|
||||
|
||||
if discordBotRegex.Match(ctx.Request.Header.UserAgent()) && !ctx.QueryArgs().Has(rawParam) {
|
||||
if mimetype.EqualsAny(mimeType.String(), "image/png", "image/jpeg", "image/gif") {
|
||||
ctx.Response.Header.SetContentType("text/html; charset=utf8")
|
||||
|
@ -118,33 +176,31 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
ctx.Response.Header.Add("Pragma", "no-cache")
|
||||
ctx.Response.Header.Add("Expires", "0")
|
||||
|
||||
u := fmt.Sprintf("%s/%s?%s=true", utils.GetServerRoot(ctx), p, rawParam)
|
||||
u := fmt.Sprintf("%s/%s?%s=true&enc_key=%s", global.Configuration.Domain, p, rawParam, string(ctx.QueryArgs().Peek(encryptionKeyParam)))
|
||||
_, _ = fmt.Fprint(ctx.Response.BodyWriter(), strings.Replace(discordHTML, "{{.}}", u, 1))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filterStatus := security.FilterCheck(ctx, mimeType.String())
|
||||
if filterStatus == security.FilterFail {
|
||||
// already sent a response if filter check failed
|
||||
return
|
||||
} else if filterStatus == security.FilterSanitize {
|
||||
ctx.Response.Header.Set("Content-Type", "text/plain")
|
||||
} else {
|
||||
ctx.Response.Header.Set("Content-Type", mimeType.String())
|
||||
}
|
||||
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", p))
|
||||
ctx.Response.Header.Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
|
||||
|
||||
_, e = fileReader.Seek(0, io.SeekStart)
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Reader could not be reset to its initial position. %v", e), fasthttp.StatusInternalServerError)
|
||||
_, err = fileReader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to reset file reader to 0. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
_, copyErr := io.Copy(ctx.Response.BodyWriter(), fileReader)
|
||||
if copyErr != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("File wasn't written to the client successfully. %v", copyErr), fasthttp.StatusInternalServerError)
|
||||
return
|
||||
if _, err = sio.Decrypt(ctx.Response.BodyWriter(), fileReader, sio.Config{Key: key[:]}); err != nil {
|
||||
if _, ok := err.(sio.Error); ok {
|
||||
response.SendInvalidEncryptionKeyResponse(ctx)
|
||||
return
|
||||
}
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to write decrypted file to the response body. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package routes
|
|||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/response"
|
||||
"tytanium/response"
|
||||
)
|
||||
|
||||
// ServeNotFound will always return an HTTP status code of 404 + error message text.
|
||||
|
|
|
@ -4,18 +4,17 @@ import (
|
|||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/constants"
|
||||
"github.com/vysiondev/tytanium/global"
|
||||
"github.com/vysiondev/tytanium/response"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"tytanium/constants"
|
||||
"tytanium/global"
|
||||
"tytanium/response"
|
||||
)
|
||||
|
||||
// GeneralStats represent all stats returned when making a GET request to /stats.
|
||||
type GeneralStats struct {
|
||||
ServerVersion string `json:"server_version"`
|
||||
RuntimeVersion string `json:"runtime_version,omitempty"`
|
||||
MemoryUsage int64 `json:"memory_usage,omitempty"`
|
||||
SizeStats StatsFromSizeChecker `json:"size_stats"`
|
||||
}
|
||||
|
||||
|
@ -34,43 +33,57 @@ func ServeStats(ctx *fasthttp.RequestCtx) {
|
|||
|
||||
totalSize, err := getStatValueFromRedis(ctx, global.RedisClient, "sc_total_size")
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("An error occurred while trying to get sc_total_size from Redis: %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("An error occurred while trying to get sc_total_size from Redis: %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
stats.SizeStats.TotalSize = totalSize
|
||||
|
||||
fileCount, err := getStatValueFromRedis(ctx, global.RedisClient, "sc_file_count")
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("An error occurred while trying to get sc_file_count from Redis: %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("An error occurred while trying to get sc_file_count from Redis: %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
stats.SizeStats.FileCount = fileCount
|
||||
|
||||
timeToComplete, err := getStatValueFromRedis(ctx, global.RedisClient, "sc_time_to_complete")
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("An error occurred while trying to get sc_time_to_complete from Redis: %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("An error occurred while trying to get sc_time_to_complete from Redis: %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
stats.SizeStats.TimeToComplete = timeToComplete
|
||||
|
||||
lastUpdated, err := getStatValueFromRedis(ctx, global.RedisClient, "sc_last_updated")
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("An error occurred while trying to get sc_last_updated from Redis: %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("An error occurred while trying to get sc_last_updated from Redis: %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
stats.SizeStats.LastUpdated = lastUpdated
|
||||
|
||||
if global.Configuration.MoreStats {
|
||||
memUsage, err := getStatValueFromRedis(ctx, global.RedisClient, "ty_mem_usage")
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("An error occurred while trying to get ty_mem_usage from Redis: %v", err), fasthttp.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
stats.MemoryUsage = memUsage
|
||||
stats.RuntimeVersion = runtime.Version()
|
||||
}
|
||||
|
||||
response.SendJSONResponse(ctx, &stats)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusOK,
|
||||
Data: &stats,
|
||||
Message: "",
|
||||
}, fasthttp.StatusOK)
|
||||
}
|
||||
|
||||
func getStatValueFromRedis(ctx *fasthttp.RequestCtx, c *redis.Client, key string) (int64, error) {
|
||||
|
|
|
@ -3,21 +3,27 @@ package routes
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/minio/sio"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/vysiondev/tytanium/constants"
|
||||
"github.com/vysiondev/tytanium/global"
|
||||
"github.com/vysiondev/tytanium/logger"
|
||||
"github.com/vysiondev/tytanium/response"
|
||||
"github.com/vysiondev/tytanium/security"
|
||||
"github.com/vysiondev/tytanium/utils"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"tytanium/constants"
|
||||
"tytanium/encryption"
|
||||
"tytanium/global"
|
||||
"tytanium/logger"
|
||||
"tytanium/response"
|
||||
"tytanium/security"
|
||||
"tytanium/utils"
|
||||
)
|
||||
|
||||
const fileHandler = "file"
|
||||
|
||||
type uploadResponse struct {
|
||||
URI string `json:"uri"`
|
||||
EncryptionKey string `json:"encryption_key"`
|
||||
}
|
||||
|
||||
// ServeUpload handles all incoming POST requests to /upload. It will take a multipart form, parse the file,
|
||||
// then write it to disk.
|
||||
func ServeUpload(ctx *fasthttp.RequestCtx) {
|
||||
|
@ -28,15 +34,27 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
mp, e := ctx.Request.MultipartForm()
|
||||
if e != nil {
|
||||
if e == fasthttp.ErrNoMultipartForm {
|
||||
response.SendTextResponse(ctx, "No multipart form was in the request.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "No multipart form was present in the request.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("The multipart form couldn't be parsed. %v", e), fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("The multipart form couldn't be parsed. %v", e),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
defer ctx.Request.RemoveMultipartFormFiles()
|
||||
if mp.File == nil || len(mp.File[fileHandler]) == 0 {
|
||||
response.SendTextResponse(ctx, "No files were sent.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "No files were sent.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
f := mp.File[fileHandler][0]
|
||||
|
@ -44,11 +62,19 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
if global.Configuration.RateLimit.Bandwidth.Upload > 0 && global.Configuration.RateLimit.Bandwidth.ResetAfter > 0 {
|
||||
isUploadBandwidthLimitNotReached, err := security.Try(ctx, global.RedisClient, fmt.Sprintf("%s_%s", constants.RateLimitBandwidthUpload, utils.GetIP(ctx)), int64(global.Configuration.RateLimit.Bandwidth.Upload), int64(global.Configuration.RateLimit.Bandwidth.ResetAfter), f.Size)
|
||||
if err != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Bandwidth limit couldn't be checked. %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Bandwidth limit could not be checked. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
if !isUploadBandwidthLimitNotReached {
|
||||
response.SendTextResponse(ctx, "Upload bandwidth limit reached; try again later.", fasthttp.StatusTooManyRequests)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Upload bandwidth limit reached; try again later.",
|
||||
}, fasthttp.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -56,13 +82,21 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
ext := path.Ext(f.Filename)
|
||||
|
||||
if len(ext) > constants.ExtensionLengthLimit {
|
||||
response.SendTextResponse(ctx, "The file extension is too long.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "File extension is too long.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
openedFile, e := f.Open()
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("File failed to open. %v", e), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("File could not be opened. %v", e),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
@ -71,7 +105,11 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
|
||||
mimeType, e := mimetype.DetectReader(openedFile)
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, "Cannot detect the mime type of this file.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: "Cannot detect the mime type of this file.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -83,7 +121,11 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
|
||||
_, e = openedFile.Seek(0, io.SeekStart)
|
||||
if e != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Reader could not be reset to its initial position. %v", e), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Reader could not be reset to its initial position. %v", e),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -92,15 +134,8 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
|
||||
// loop until an unoccupied id is found
|
||||
for {
|
||||
var wg sync.WaitGroup
|
||||
randomStringChan := make(chan string, 1)
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
utils.RandBytes(global.Configuration.Storage.IDLength, randomStringChan, func() { wg.Done() })
|
||||
}()
|
||||
wg.Wait()
|
||||
fileId := <-randomStringChan
|
||||
|
||||
fileId := utils.RandString(global.Configuration.Storage.IDLength)
|
||||
fileName = fileId + ext
|
||||
|
||||
i, e := os.Stat(path.Join(global.Configuration.Storage.Directory, fileName))
|
||||
|
@ -114,28 +149,59 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
}
|
||||
attempts++
|
||||
if attempts >= global.Configuration.Storage.CollisionCheckAttempts {
|
||||
response.SendTextResponse(ctx, "Tried too many times to find a valid file ID to use. Consider increasing the ID length.", fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Tried too many times to find a valid file ID to use. Consider increasing the ID length.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fsFile, err := os.Create(path.Join(global.Configuration.Storage.Directory, fileName))
|
||||
destFile, err := os.Create(path.Join(global.Configuration.Storage.Directory, fileName))
|
||||
defer func() {
|
||||
_ = fsFile.Close()
|
||||
_ = destFile.Close()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
if err == os.ErrPermission {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Permission to create a file was denied. %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Permission to create the file was denied. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("Could not create the file. %v", err), fasthttp.StatusInternalServerError)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to create the file. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
_, writeErr := io.Copy(fsFile, openedFile)
|
||||
if writeErr != nil {
|
||||
response.SendTextResponse(ctx, fmt.Sprintf("The file failed to write to disk. %v", e), fasthttp.StatusInternalServerError)
|
||||
masterKey := utils.RandString(global.Configuration.Encryption.EncryptionKeyLength)
|
||||
|
||||
key, err := encryption.DeriveKey([]byte(masterKey), []byte(global.Configuration.Encryption.Nonce))
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to generate encryption key. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = sio.Encrypt(destFile, openedFile, sio.Config{Key: key[:]}); err != nil {
|
||||
if _, ok := err.(sio.Error); ok {
|
||||
response.SendInvalidEncryptionKeyResponse(ctx)
|
||||
return
|
||||
}
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
Data: nil,
|
||||
Message: fmt.Sprintf("Failed to write encrypted file to disk. %v", err),
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -143,16 +209,19 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
logger.InfoLogger.Printf("File %s was created, size: %d", fileName, f.Size)
|
||||
}
|
||||
|
||||
if global.Configuration.ForceZeroWidth || string(ctx.QueryArgs().Peek("zerowidth")) == "1" {
|
||||
fileName = utils.ZeroWidthCharactersToString(fileName)
|
||||
}
|
||||
|
||||
var u string
|
||||
if string(ctx.QueryArgs().Peek("omitdomain")) == "1" {
|
||||
u = fileName
|
||||
} else {
|
||||
u = fmt.Sprintf("%s/%s", utils.GetServerRoot(ctx), fileName)
|
||||
u = fmt.Sprintf("%s/%s?enc_key=%s", global.Configuration.Domain, fileName, masterKey)
|
||||
}
|
||||
|
||||
response.SendTextResponse(ctx, u, fasthttp.StatusOK)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusOK,
|
||||
Data: uploadResponse{
|
||||
URI: u,
|
||||
EncryptionKey: masterKey,
|
||||
},
|
||||
Message: "",
|
||||
}, fasthttp.StatusOK)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue