Reintroduce zero width feature, JSON response fix
This commit is contained in:
parent
ce6a5d101c
commit
5b4b5d4b87
|
@ -1,13 +1,16 @@
|
|||
{
|
||||
"Version": "13.7.0",
|
||||
"Name": "Tytanium Example Configuration",
|
||||
"Version": "13.5.0",
|
||||
"DestinationType": "ImageUploader, TextUploader, FileUploader",
|
||||
"RequestMethod": "POST",
|
||||
"RequestURL": "https://yourdomainhere.com/upload",
|
||||
"Parameters": {
|
||||
"zerowidth": "$prompt:zero width (1=y, 0=n, 0 default)|0$"
|
||||
},
|
||||
"Headers": {
|
||||
"Authorization": "your key here"
|
||||
},
|
||||
"Body": "MultipartFormData",
|
||||
"FileFormName": "file",
|
||||
"URL": "$response$"
|
||||
"URL": "$json:data.uri$",
|
||||
"ErrorMessage": "$json:message$"
|
||||
}
|
24
init.go
24
init.go
|
@ -14,8 +14,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
mebibyte = 1 << 20
|
||||
minute = 60000
|
||||
mebibyte = 1 << 20
|
||||
minute = 60000
|
||||
characterTagLengthEncoded = 12
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -85,14 +86,17 @@ func initConfiguration() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: what the fuck is this clean it up
|
||||
// - ID length * 4 bytes,
|
||||
// - extension length limit * 4 bytes,
|
||||
// - 1 byte for the / character,
|
||||
// - 4 bytes for the . character
|
||||
// - X bytes for encryption key
|
||||
constants.PathLengthLimitBytes = (global.Configuration.Storage.IDLength * 4) + (constants.ExtensionLengthLimit * 4) + 5 + global.Configuration.Encryption.EncryptionKeyLength
|
||||
|
||||
// Domain length + 1 byte for "/"
|
||||
// ID length * 12 (%00%00%00%00)
|
||||
// Extension length * 12
|
||||
// 9 (?enc_key=) * 12
|
||||
// Encryption key length * 12
|
||||
constants.PathLengthLimitBytes =
|
||||
(len(global.Configuration.Domain) + 1) +
|
||||
(global.Configuration.Storage.IDLength * characterTagLengthEncoded) +
|
||||
(constants.ExtensionLengthLimit * characterTagLengthEncoded) +
|
||||
(9 * characterTagLengthEncoded) +
|
||||
(global.Configuration.Encryption.EncryptionKeyLength * characterTagLengthEncoded)
|
||||
log.Println("[init] Loaded configuration")
|
||||
}
|
||||
|
||||
|
|
|
@ -29,30 +29,37 @@ type JSONResponse struct {
|
|||
}
|
||||
|
||||
// SendTextResponse sends a plaintext response to the client along with an HTTP status code.
|
||||
func SendTextResponse(ctx *fasthttp.RequestCtx, msg string, code int) {
|
||||
ctx.Response.Header.SetContentType(plainTextContentType)
|
||||
if code == fasthttp.StatusInternalServerError {
|
||||
log.Printf(fmt.Sprintf("Unhandled error!, %s", msg))
|
||||
if global.Configuration.Logging.Enabled {
|
||||
logger.ErrorLogger.Printf("500 response sent; error message: %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetStatusCode(code)
|
||||
_, e := fmt.Fprint(ctx.Response.BodyWriter(), msg)
|
||||
if e != nil {
|
||||
log.Printf(fmt.Sprintf("Request failed to send! %v, status code %d", e, code))
|
||||
if global.Configuration.Logging.Enabled {
|
||||
logger.ErrorLogger.Printf("Failed to send response; error message: %s, status code: %d", e, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
//func SendTextResponse(ctx *fasthttp.RequestCtx, msg string, code int) {
|
||||
// ctx.Response.Header.SetContentType(plainTextContentType)
|
||||
// if code == fasthttp.StatusInternalServerError {
|
||||
// log.Printf(fmt.Sprintf("Unhandled error!, %s", msg))
|
||||
// if global.Configuration.Logging.Enabled {
|
||||
// logger.ErrorLogger.Printf("500 response sent; error message: %s", msg)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// ctx.SetStatusCode(code)
|
||||
// _, e := fmt.Fprint(ctx.Response.BodyWriter(), msg)
|
||||
// if e != nil {
|
||||
// log.Printf(fmt.Sprintf("Request failed to send! %v, status code %d", e, code))
|
||||
// if global.Configuration.Logging.Enabled {
|
||||
// logger.ErrorLogger.Printf("Failed to send response; error message: %s, status code: %d", e, code)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// SendJSONResponse sends a JSON encoded response to the client along with an HTTP status code of 200 OK.
|
||||
func SendJSONResponse(ctx *fasthttp.RequestCtx, json interface{}, statusCode int) {
|
||||
func SendJSONResponse(ctx *fasthttp.RequestCtx, j JSONResponse, statusCode int) {
|
||||
if statusCode == RequestStatusInternalError {
|
||||
log.Printf(fmt.Sprintf("Unhandled error!, %s", j.Message))
|
||||
if global.Configuration.Logging.Enabled {
|
||||
logger.ErrorLogger.Printf("500 response sent; error message: %v", j.Message)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetContentType(jsonContentType)
|
||||
ctx.SetStatusCode(statusCode)
|
||||
e := json2.NewEncoder(ctx.Response.BodyWriter()).Encode(json)
|
||||
e := json2.NewEncoder(ctx.Response.BodyWriter()).Encode(j)
|
||||
if e != nil {
|
||||
if global.Configuration.Logging.Enabled {
|
||||
logger.ErrorLogger.Printf("Failed to send JSON response; error message: %v", e)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/minio/sio"
|
||||
"github.com/valyala/fasthttp"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
@ -19,8 +20,11 @@ import (
|
|||
"tytanium/utils"
|
||||
)
|
||||
|
||||
const encryptionKeyParam = "enc_key"
|
||||
const rawParam = "raw"
|
||||
const (
|
||||
paramEncryptionKey = "enc_key"
|
||||
paramRaw = "raw"
|
||||
ZeroWidthFirstByte = 37
|
||||
)
|
||||
|
||||
// discordHTML represents what is sent back to any client which User-Agent contains the regex contained in
|
||||
// discordBotRegex.
|
||||
|
@ -43,9 +47,7 @@ var (
|
|||
// ServeFile will serve the / endpoint. It gets the "id" variable from mux and tries to find the file's information in the database.
|
||||
// If an ID is either not provided or not found, the function hands the request off to ServeNotFound.
|
||||
func ServeFile(ctx *fasthttp.RequestCtx) {
|
||||
pBytes := ctx.Request.URI().Path()
|
||||
|
||||
if len(pBytes) > constants.PathLengthLimitBytes {
|
||||
if len(ctx.Request.RequestURI()) > constants.PathLengthLimitBytes {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
|
@ -54,7 +56,7 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(pBytes) <= 1 {
|
||||
if len(ctx.Request.RequestURI()) <= 1 {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
|
@ -63,7 +65,31 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(ctx.QueryArgs().Peek(encryptionKeyParam)) == 0 {
|
||||
// Convert entire request URI to a normal string if the first byte represents a URL-encoded string.
|
||||
if ctx.Request.RequestURI()[1] == ZeroWidthFirstByte {
|
||||
uriDecoded, err := url.QueryUnescape(string(ctx.Request.RequestURI()))
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Failed to decode string.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if len(uriDecoded) <= 1 {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Zero-width string is not long enough.",
|
||||
}, fasthttp.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Request.SetRequestURI("/" + utils.ZeroWidthToString(uriDecoded[1:]))
|
||||
}
|
||||
|
||||
if len(ctx.QueryArgs().Peek(paramEncryptionKey)) == 0 {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
|
@ -72,9 +98,8 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
p := string(pBytes[1:])
|
||||
|
||||
filePath := path.Join(global.Configuration.Storage.Directory, p)
|
||||
pathNoLeadingSlash := string(ctx.Request.URI().Path()[1:])
|
||||
filePath := path.Join(global.Configuration.Storage.Directory, pathNoLeadingSlash)
|
||||
|
||||
// we only need to know if it exists or not
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
|
@ -130,7 +155,7 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
_ = fileReader.Close()
|
||||
}()
|
||||
|
||||
key, err := encryption.DeriveKey(ctx.QueryArgs().Peek(encryptionKeyParam), []byte(global.Configuration.Encryption.Nonce))
|
||||
key, err := encryption.DeriveKey(ctx.QueryArgs().Peek(paramEncryptionKey), []byte(global.Configuration.Encryption.Nonce))
|
||||
if err != nil {
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusInternalError,
|
||||
|
@ -166,17 +191,17 @@ func ServeFile(ctx *fasthttp.RequestCtx) {
|
|||
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-Disposition", fmt.Sprintf("inline; filename=\"%s\"", pathNoLeadingSlash))
|
||||
ctx.Response.Header.Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
|
||||
|
||||
if discordBotRegex.Match(ctx.Request.Header.UserAgent()) && !ctx.QueryArgs().Has(rawParam) {
|
||||
if discordBotRegex.Match(ctx.Request.Header.UserAgent()) && !ctx.QueryArgs().Has(paramRaw) {
|
||||
if mimetype.EqualsAny(mimeType.String(), "image/png", "image/jpeg", "image/gif") {
|
||||
ctx.Response.Header.SetContentType("text/html; charset=utf8")
|
||||
ctx.Response.Header.Add("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
ctx.Response.Header.Add("Pragma", "no-cache")
|
||||
ctx.Response.Header.Add("Expires", "0")
|
||||
|
||||
u := fmt.Sprintf("%s/%s?%s=true&enc_key=%s", global.Configuration.Domain, p, rawParam, string(ctx.QueryArgs().Peek(encryptionKeyParam)))
|
||||
u := fmt.Sprintf("%s/%s?%s=true&enc_key=%s", global.Configuration.Domain, pathNoLeadingSlash, paramRaw, string(ctx.QueryArgs().Peek(paramEncryptionKey)))
|
||||
_, _ = fmt.Fprint(ctx.Response.BodyWriter(), strings.Replace(discordHTML, "{{.}}", u, 1))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,5 +7,9 @@ import (
|
|||
|
||||
// ServeNotFound will always return an HTTP status code of 404 + error message text.
|
||||
func ServeNotFound(ctx *fasthttp.RequestCtx) {
|
||||
response.SendTextResponse(ctx, "Not found", fasthttp.StatusNotFound)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Not found.",
|
||||
}, fasthttp.StatusNotFound)
|
||||
}
|
||||
|
|
|
@ -19,11 +19,6 @@ import (
|
|||
|
||||
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) {
|
||||
|
@ -209,17 +204,23 @@ func ServeUpload(ctx *fasthttp.RequestCtx) {
|
|||
logger.InfoLogger.Printf("File %s was created, size: %d", fileName, f.Size)
|
||||
}
|
||||
|
||||
var u string
|
||||
if string(ctx.QueryArgs().Peek("omitdomain")) == "1" {
|
||||
u = fileName
|
||||
} else {
|
||||
u = fmt.Sprintf("%s/%s?enc_key=%s", global.Configuration.Domain, fileName, masterKey)
|
||||
targetPath := fmt.Sprintf("%s?enc_key=%s", fileName, masterKey)
|
||||
|
||||
if global.Configuration.ForceZeroWidth || string(ctx.QueryArgs().Peek("zerowidth")) == "1" {
|
||||
targetPath = utils.StringToZeroWidth(targetPath)
|
||||
}
|
||||
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusOK,
|
||||
Data: uploadResponse{
|
||||
URI: u,
|
||||
Data: struct {
|
||||
URI string `json:"uri"`
|
||||
Path string `json:"path"`
|
||||
FileName string `json:"file_name"`
|
||||
EncryptionKey string `json:"encryption_key"`
|
||||
}{
|
||||
URI: global.Configuration.Domain + "/" + targetPath,
|
||||
Path: targetPath,
|
||||
FileName: fileName,
|
||||
EncryptionKey: masterKey,
|
||||
},
|
||||
Message: "",
|
||||
|
|
|
@ -10,7 +10,11 @@ import (
|
|||
// HTTP status code 401 is returned.
|
||||
func IsAuthorized(ctx *fasthttp.RequestCtx) bool {
|
||||
if string(ctx.Request.Header.Peek("authorization")) != global.Configuration.Security.MasterKey {
|
||||
response.SendTextResponse(ctx, "Not authorized.", fasthttp.StatusUnauthorized)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "Not authorized to access that.",
|
||||
}, fasthttp.StatusUnauthorized)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -21,11 +21,19 @@ const (
|
|||
// FilterSanitize means the file's Content-Type header returned to the client should be changed to text/plain.
|
||||
func FilterCheck(ctx *fasthttp.RequestCtx, mimeType string) FilterStatus {
|
||||
if len(global.Configuration.Filter.Blacklist) > 0 && mimetype.EqualsAny(mimeType, global.Configuration.Filter.Blacklist...) {
|
||||
response.SendTextResponse(ctx, "File type is blacklisted.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "This file type is blacklisted.",
|
||||
}, fasthttp.StatusOK)
|
||||
return FilterFail
|
||||
}
|
||||
if len(global.Configuration.Filter.Whitelist) > 0 && !mimetype.EqualsAny(mimeType, global.Configuration.Filter.Whitelist...) {
|
||||
response.SendTextResponse(ctx, "File type is not whitelisted.", fasthttp.StatusBadRequest)
|
||||
response.SendJSONResponse(ctx, response.JSONResponse{
|
||||
Status: response.RequestStatusError,
|
||||
Data: nil,
|
||||
Message: "This file type is not whitelisted.",
|
||||
}, fasthttp.StatusOK)
|
||||
return FilterFail
|
||||
}
|
||||
if len(global.Configuration.Filter.Sanitize) > 0 && mimetype.EqualsAny(mimeType, global.Configuration.Filter.Sanitize...) {
|
||||
|
|
|
@ -1 +1,61 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
characterIndex = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.?=_"
|
||||
characterReference = []string{
|
||||
"\U000E0050", "\U000E0043", "\U000E0034", "\U000E0035",
|
||||
"\U000E002D", "\U000E002A", "\U000E005D", "\U000E002E",
|
||||
"\U000E0026", "\U000E0024", "\U000E0058", "\U000E004E",
|
||||
"\U000E0037", "\U000E0049", "\U000E0051", "\U000E0041",
|
||||
"\U000E0028", "\U000E0027", "\U000E004B", "\U000E005E",
|
||||
"\U000E0044", "\U000E0040", "\U000E004D", "\U000E0056",
|
||||
"\U000E0060", "\U000E0055", "\U000E0030", "\U000E0023",
|
||||
"\U000E0039", "\U000E004F", "\U000E0052", "\U000E002B",
|
||||
"\U000E0057", "\U000E003C", "\U000E0053", "\U000E005B",
|
||||
"\U000E003F", "\U000E0021", "\U000E003B", "\U000E0046",
|
||||
"\U000E0031", "\U000E0059", "\U000E003E", "\U000E0047",
|
||||
"\U000E005C", "\U000E003D", "\U000E0054", "\U000E0048",
|
||||
"\U000E005F", "\U000E0038", "\U000E003A", "\U000E002F",
|
||||
"\U000E005A", "\U000E0020", "\U000E0042", "\U000E0033",
|
||||
"\U000E0036", "\U000E004A", "\U000E0022", "\U000E0045",
|
||||
"\U000E0032", "\U000E002C", "\U000E0029", "\U000E007B",
|
||||
"\U000E007C", "\U000E007D", "\U000E007E",
|
||||
}
|
||||
)
|
||||
|
||||
func GetCharacterIndex(s string) int {
|
||||
return strings.Index(characterIndex, s)
|
||||
}
|
||||
|
||||
func ZeroWidthToString(encodedStr string) string {
|
||||
|
||||
var finalStr string
|
||||
strAsRunes := []rune(encodedStr)
|
||||
|
||||
for _, v := range strAsRunes {
|
||||
for pos, i := range characterReference {
|
||||
if []rune(i)[0] == v {
|
||||
finalStr += string(characterIndex[pos])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalStr
|
||||
}
|
||||
|
||||
func StringToZeroWidth(baseStr string) string {
|
||||
|
||||
var finalStr string
|
||||
splitStr := strings.Split(baseStr, "")
|
||||
|
||||
for _, r := range splitStr {
|
||||
index := GetCharacterIndex(r)
|
||||
finalStr += characterReference[index]
|
||||
}
|
||||
|
||||
return finalStr
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue