2020-08-23 01:32:46 +03:00
|
|
|
package web
|
|
|
|
|
|
|
|
import (
|
2020-08-23 17:22:20 +03:00
|
|
|
"encoding/json"
|
2021-04-15 20:26:17 +03:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2020-08-23 01:32:46 +03:00
|
|
|
routing "github.com/fasthttp/router"
|
2021-04-15 21:15:42 +03:00
|
|
|
"github.com/lus/pasty/internal/config"
|
2021-04-15 20:26:17 +03:00
|
|
|
"github.com/lus/pasty/internal/static"
|
2021-06-21 21:09:40 +03:00
|
|
|
"github.com/lus/pasty/internal/storage"
|
2021-04-15 20:26:17 +03:00
|
|
|
v1 "github.com/lus/pasty/internal/web/controllers/v1"
|
2021-07-22 23:26:21 +03:00
|
|
|
v2 "github.com/lus/pasty/internal/web/controllers/v2"
|
2020-08-24 00:07:07 +03:00
|
|
|
"github.com/ulule/limiter/v3"
|
|
|
|
limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp"
|
|
|
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
2020-08-23 01:32:46 +03:00
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
)
|
|
|
|
|
2020-08-23 17:44:06 +03:00
|
|
|
// Serve serves the web resources
|
2020-08-23 01:32:46 +03:00
|
|
|
func Serve() error {
|
|
|
|
// Create the router
|
|
|
|
router := routing.New()
|
|
|
|
|
2020-08-23 15:13:17 +03:00
|
|
|
// Define the 404 handler
|
|
|
|
router.NotFound = func(ctx *fasthttp.RequestCtx) {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
|
|
ctx.SetBodyString("not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Route the frontend requests
|
|
|
|
frontend := frontendHandler()
|
2021-06-21 21:09:40 +03:00
|
|
|
raw := rawHandler()
|
2020-08-23 15:13:17 +03:00
|
|
|
router.GET("/{path:*}", func(ctx *fasthttp.RequestCtx) {
|
|
|
|
path := string(ctx.Path())
|
2020-08-24 19:32:14 +03:00
|
|
|
if !strings.HasPrefix(path, "/api") && (strings.Count(path, "/") == 1 || strings.HasPrefix(path, "/assets")) {
|
2021-04-29 15:57:08 +03:00
|
|
|
if strings.HasPrefix(path, "/assets/js/") {
|
|
|
|
ctx.SetContentType("text/javascript")
|
|
|
|
}
|
2020-08-23 15:13:17 +03:00
|
|
|
frontend(ctx)
|
|
|
|
return
|
2021-06-21 21:09:40 +03:00
|
|
|
} else if strings.HasSuffix(strings.TrimSuffix(path, "/"), "/raw") {
|
|
|
|
raw(ctx)
|
|
|
|
return
|
2020-08-23 15:13:17 +03:00
|
|
|
}
|
|
|
|
router.NotFound(ctx)
|
|
|
|
})
|
|
|
|
|
2020-08-24 00:07:07 +03:00
|
|
|
// Set up the rate limiter
|
2021-04-15 21:15:42 +03:00
|
|
|
rate, err := limiter.NewRateFromFormatted(config.Current.RateLimit)
|
2020-08-24 00:07:07 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rateLimiter := limiter.New(memory.NewStore(), rate)
|
|
|
|
rateLimiterMiddleware := limitFasthttp.NewMiddleware(rateLimiter)
|
|
|
|
|
2020-08-23 01:32:46 +03:00
|
|
|
// Route the API endpoints
|
|
|
|
apiRoute := router.Group("/api")
|
|
|
|
{
|
|
|
|
v1Route := apiRoute.Group("/v1")
|
|
|
|
{
|
2020-08-23 17:40:06 +03:00
|
|
|
v1Route.GET("/info", func(ctx *fasthttp.RequestCtx) {
|
2021-04-20 17:38:00 +03:00
|
|
|
jsonData, _ := json.Marshal(map[string]interface{}{
|
|
|
|
"version": static.Version,
|
2021-07-22 23:26:21 +03:00
|
|
|
"deletionTokens": config.Current.ModificationTokens,
|
2020-08-23 17:40:06 +03:00
|
|
|
})
|
|
|
|
ctx.SetBody(jsonData)
|
|
|
|
})
|
2020-08-24 00:07:07 +03:00
|
|
|
v1.InitializePastesController(v1Route.Group("/pastes"), rateLimiterMiddleware)
|
2020-08-23 01:32:46 +03:00
|
|
|
}
|
2021-07-22 23:26:21 +03:00
|
|
|
|
|
|
|
v2Route := apiRoute.Group("/v2")
|
|
|
|
{
|
2022-01-29 18:18:40 +03:00
|
|
|
pasteLifetime := int64(-1)
|
|
|
|
if config.Current.AutoDelete.Enabled {
|
|
|
|
pasteLifetime = config.Current.AutoDelete.Lifetime.Milliseconds()
|
|
|
|
}
|
2021-07-22 23:26:21 +03:00
|
|
|
v2Route.GET("/info", func(ctx *fasthttp.RequestCtx) {
|
|
|
|
jsonData, _ := json.Marshal(map[string]interface{}{
|
|
|
|
"version": static.Version,
|
|
|
|
"modificationTokens": config.Current.ModificationTokens,
|
|
|
|
"reports": config.Current.Reports.Reports,
|
2022-01-29 18:18:40 +03:00
|
|
|
"pasteLifetime": pasteLifetime,
|
2021-07-22 23:26:21 +03:00
|
|
|
})
|
|
|
|
ctx.SetBody(jsonData)
|
|
|
|
})
|
|
|
|
v2.InitializePastesController(v2Route.Group("/pastes"), rateLimiterMiddleware)
|
|
|
|
}
|
2020-08-23 01:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-08-23 17:22:20 +03:00
|
|
|
// Route the hastebin documents route if hastebin support is enabled
|
2021-04-15 21:15:42 +03:00
|
|
|
if config.Current.HastebinSupport {
|
2020-08-24 00:07:07 +03:00
|
|
|
router.POST("/documents", rateLimiterMiddleware.Handle(v1.HastebinSupportHandler))
|
2020-08-23 17:22:20 +03:00
|
|
|
}
|
|
|
|
|
2020-08-23 17:44:06 +03:00
|
|
|
// Serve the web resources
|
2020-08-23 15:13:17 +03:00
|
|
|
return (&fasthttp.Server{
|
2020-08-23 21:29:18 +03:00
|
|
|
Handler: func(ctx *fasthttp.RequestCtx) {
|
|
|
|
// Add the CORS headers
|
|
|
|
ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET,POST,DELETE,OPTIONS")
|
|
|
|
ctx.Response.Header.Set("Access-Control-Allow-Origin", "*")
|
|
|
|
|
|
|
|
// Call the router handler
|
|
|
|
router.Handler(ctx)
|
|
|
|
},
|
|
|
|
Logger: new(nilLogger),
|
2021-04-15 21:15:42 +03:00
|
|
|
}).ListenAndServe(config.Current.WebAddress)
|
2020-08-23 15:13:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// frontendHandler handles the frontend routing
|
|
|
|
func frontendHandler() fasthttp.RequestHandler {
|
|
|
|
// Create the file server
|
|
|
|
fs := &fasthttp.FS{
|
2022-01-29 03:28:54 +03:00
|
|
|
Root: static.TempFrontendPath,
|
2020-08-23 15:13:17 +03:00
|
|
|
IndexNames: []string{"index.html"},
|
|
|
|
CacheDuration: 0,
|
|
|
|
}
|
|
|
|
fs.PathNotFound = func(ctx *fasthttp.RequestCtx) {
|
2020-08-24 19:32:14 +03:00
|
|
|
if strings.HasPrefix(string(ctx.Path()), "/assets") {
|
|
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
|
|
ctx.SetBodyString("not found")
|
|
|
|
return
|
|
|
|
}
|
2020-08-23 15:13:17 +03:00
|
|
|
ctx.SendFile(filepath.Join(fs.Root, "index.html"))
|
|
|
|
}
|
|
|
|
return fs.NewRequestHandler()
|
2020-08-23 01:32:46 +03:00
|
|
|
}
|
2021-06-21 21:09:40 +03:00
|
|
|
|
|
|
|
func rawHandler() fasthttp.RequestHandler {
|
|
|
|
return func(ctx *fasthttp.RequestCtx) {
|
|
|
|
path := string(ctx.Path())
|
|
|
|
pathSanitized := strings.TrimPrefix(strings.TrimSuffix(path, "/"), "/")
|
|
|
|
pasteID := strings.TrimSuffix(pathSanitized, "/raw")
|
|
|
|
|
|
|
|
paste, err := storage.Current.Get(pasteID)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetBodyString(paste.Content)
|
|
|
|
}
|
|
|
|
}
|