2020-08-23 01:32:46 +03:00
|
|
|
package v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2021-04-15 21:15:42 +03:00
|
|
|
"time"
|
2021-04-15 20:26:17 +03:00
|
|
|
|
2020-08-23 01:32:46 +03:00
|
|
|
"github.com/fasthttp/router"
|
2021-04-15 21:15:42 +03:00
|
|
|
"github.com/lus/pasty/internal/config"
|
|
|
|
"github.com/lus/pasty/internal/shared"
|
2021-04-15 20:26:17 +03:00
|
|
|
"github.com/lus/pasty/internal/storage"
|
2021-04-15 21:15:42 +03:00
|
|
|
"github.com/lus/pasty/internal/utils"
|
2020-08-24 00:07:07 +03:00
|
|
|
limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp"
|
2020-08-23 01:32:46 +03:00
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
)
|
|
|
|
|
|
|
|
// InitializePastesController initializes the '/v1/pastes/*' controller
|
2020-08-24 00:07:07 +03:00
|
|
|
func InitializePastesController(group *router.Group, rateLimiterMiddleware *limitFasthttp.Middleware) {
|
|
|
|
group.GET("/{id}", rateLimiterMiddleware.Handle(v1GetPaste))
|
|
|
|
group.POST("", rateLimiterMiddleware.Handle(v1PostPaste))
|
|
|
|
group.DELETE("/{id}", rateLimiterMiddleware.Handle(v1DeletePaste))
|
2020-08-23 01:32:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// v1GetPaste handles the 'GET /v1/pastes/{id}' endpoint
|
|
|
|
func v1GetPaste(ctx *fasthttp.RequestCtx) {
|
2020-08-24 21:22:53 +03:00
|
|
|
// Read the ID
|
|
|
|
id := ctx.UserValue("id").(string)
|
2020-08-23 01:32:46 +03:00
|
|
|
|
|
|
|
// Retrieve the paste
|
|
|
|
paste, err := storage.Current.Get(id)
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if paste == nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
|
|
ctx.SetBodyString("paste not found")
|
|
|
|
return
|
|
|
|
}
|
2021-07-22 23:26:21 +03:00
|
|
|
legacyPaste := legacyFromModern(paste)
|
|
|
|
legacyPaste.DeletionToken = ""
|
2020-08-23 01:32:46 +03:00
|
|
|
|
|
|
|
// Respond with the paste
|
2021-07-22 23:26:21 +03:00
|
|
|
jsonData, err := json.Marshal(legacyPaste)
|
2020-08-23 01:32:46 +03:00
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.SetBody(jsonData)
|
|
|
|
}
|
2020-08-23 01:40:20 +03:00
|
|
|
|
|
|
|
// v1PostPaste handles the 'POST /v1/pastes' endpoint
|
|
|
|
func v1PostPaste(ctx *fasthttp.RequestCtx) {
|
2021-05-23 21:55:16 +03:00
|
|
|
// Check content length before reading body into memory
|
|
|
|
if config.Current.LengthCap > 0 &&
|
|
|
|
ctx.Request.Header.ContentLength() > config.Current.LengthCap {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
|
|
ctx.SetBodyString("request body length overflow")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-23 01:40:20 +03:00
|
|
|
// Unmarshal the body
|
|
|
|
values := make(map[string]string)
|
|
|
|
err := json.Unmarshal(ctx.PostBody(), &values)
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
|
|
ctx.SetBodyString("invalid request body")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the content of the paste
|
|
|
|
if values["content"] == "" {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
|
|
ctx.SetBodyString("missing 'content' field")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-24 21:22:53 +03:00
|
|
|
// Acquire the paste ID
|
|
|
|
id, err := storage.AcquireID()
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-23 01:40:20 +03:00
|
|
|
// Create the paste object
|
2021-04-15 21:15:42 +03:00
|
|
|
paste := &shared.Paste{
|
2021-07-30 22:51:33 +03:00
|
|
|
ID: id,
|
|
|
|
Content: values["content"],
|
|
|
|
Created: time.Now().Unix(),
|
2020-08-23 01:40:20 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 23:26:21 +03:00
|
|
|
// Set a modification token
|
|
|
|
modificationToken := ""
|
|
|
|
if config.Current.ModificationTokens {
|
2021-07-24 18:00:29 +03:00
|
|
|
modificationToken = utils.RandomString(config.Current.ModificationTokenCharacters, config.Current.ModificationTokenLength)
|
2021-07-22 23:26:21 +03:00
|
|
|
paste.ModificationToken = modificationToken
|
2021-04-20 17:38:00 +03:00
|
|
|
|
2021-07-22 23:26:21 +03:00
|
|
|
err = paste.HashModificationToken()
|
2021-04-20 17:38:00 +03:00
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
2020-08-23 17:06:40 +03:00
|
|
|
}
|
|
|
|
|
2020-08-23 01:45:30 +03:00
|
|
|
// Save the paste
|
|
|
|
err = storage.Current.Save(paste)
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-23 01:40:20 +03:00
|
|
|
// Respond with the paste
|
2021-07-22 23:26:21 +03:00
|
|
|
pasteCopy := legacyFromModern(paste)
|
|
|
|
pasteCopy.DeletionToken = modificationToken
|
2020-08-23 17:06:40 +03:00
|
|
|
jsonData, err := json.Marshal(pasteCopy)
|
2020-08-23 01:40:20 +03:00
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.SetBody(jsonData)
|
|
|
|
}
|
2020-08-23 01:45:30 +03:00
|
|
|
|
|
|
|
// v1DeletePaste handles the 'DELETE /v1/pastes/{id}'
|
|
|
|
func v1DeletePaste(ctx *fasthttp.RequestCtx) {
|
2020-08-24 21:22:53 +03:00
|
|
|
// Read the ID
|
|
|
|
id := ctx.UserValue("id").(string)
|
2020-08-23 01:45:30 +03:00
|
|
|
|
|
|
|
// Unmarshal the body
|
|
|
|
values := make(map[string]string)
|
2020-08-24 21:22:53 +03:00
|
|
|
err := json.Unmarshal(ctx.PostBody(), &values)
|
2020-08-23 01:45:30 +03:00
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
|
|
ctx.SetBodyString("invalid request body")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-22 23:26:21 +03:00
|
|
|
// Validate the modification token of the paste
|
|
|
|
modificationToken := values["deletionToken"]
|
|
|
|
if modificationToken == "" {
|
2020-08-23 01:45:30 +03:00
|
|
|
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
|
|
ctx.SetBodyString("missing 'deletionToken' field")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the paste
|
|
|
|
paste, err := storage.Current.Get(id)
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if paste == nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
|
|
ctx.SetBodyString("paste not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-22 23:26:21 +03:00
|
|
|
// Check if the modification token is correct
|
|
|
|
if (config.Current.ModificationTokenMaster == "" || modificationToken != config.Current.ModificationTokenMaster) && !paste.CheckModificationToken(modificationToken) {
|
2020-08-23 01:45:30 +03:00
|
|
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
|
|
|
ctx.SetBodyString("invalid deletion token")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the paste
|
|
|
|
err = storage.Current.Delete(paste.ID)
|
|
|
|
if err != nil {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
ctx.SetBodyString(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Respond with 'ok'
|
|
|
|
ctx.SetBodyString("ok")
|
|
|
|
}
|