From 3f40c25b04e94f89e81552f66cdee17a99207704 Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 05:31:43 +0100 Subject: [PATCH 01/14] port template engine --- main.go | 144 ++++++++-------------------------------------- render/render.go | 74 ++++++++++++++++++++++++ utils/error.go | 2 +- utils/readFile.go | 15 +++++ 4 files changed, 115 insertions(+), 120 deletions(-) create mode 100644 render/render.go create mode 100644 utils/readFile.go diff --git a/main.go b/main.go index 7877ca3..daa074a 100644 --- a/main.go +++ b/main.go @@ -4,22 +4,27 @@ import ( "flag" "fmt" "net/http" - "os" - "time" "codeberg.org/rimgo/rimgo/pages" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/static" "codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/views" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cache" - "github.com/gofiber/fiber/v2/middleware/filesystem" - "github.com/gofiber/fiber/v2/middleware/recover" - "github.com/gofiber/template/handlebars/v2" "github.com/joho/godotenv" - "github.com/mailgun/raymond/v2" ) +// a handler that returns error if it can't respond +type handler func(w http.ResponseWriter, r *http.Request) error + +func wrapHandler(h handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := h(w, r) + if err != nil { + http.Error(w, "oop", 500) + } + }) +} + func main() { envPath := flag.String("c", ".env", "Path to env file") godotenv.Load(*envPath) @@ -27,120 +32,21 @@ func main() { pages.InitializeApiClient() - views := http.FS(views.GetFiles()) - if os.Getenv("ENV") == "dev" { - views = http.Dir("./views") - } - engine := handlebars.NewFileSystem(views, ".hbs") + views := views.GetFiles() + static := static.GetFiles() + render.Initialize(views) - engine.AddFunc("noteq", func(a interface{}, b interface{}, options *raymond.Options) interface{} { - if raymond.Str(a) != raymond.Str(b) { - return options.Fn() - } - return "" - }) - - app := fiber.New(fiber.Config{ - Views: engine, - Prefork: utils.Config.FiberPrefork, - UnescapePath: true, - StreamRequestBody: true, - ErrorHandler: func(ctx *fiber.Ctx, err error) error { - code := fiber.StatusInternalServerError - - if e, ok := err.(*fiber.Error); ok { - code = e.Code - } - - return utils.RenderError(ctx, code) - }, - }) - - app.Use(recover.New(recover.Config{ - EnableStackTrace: true, - StackTraceHandler: func(c *fiber.Ctx, e interface{}) { - fmt.Println(e) - }, + app := http.NewServeMux() + app.Handle("/static/", http.FileServerFS(static)) + app.Handle("GET /test-render", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + err := render.Render(w, "errors/404", nil) + fmt.Println(err) + return err })) - if os.Getenv("ENV") == "dev" { - app.Use("/static", filesystem.New(filesystem.Config{ - Root: http.Dir("./static"), - })) - app.Get("/errors/429", func(c *fiber.Ctx) error { - return c.Render("errors/429", nil) - }) - app.Get("/errors/429/img", func(c *fiber.Ctx) error { - return c.Redirect("/static/img/error-429.png") - }) - app.Get("/errors/404", func(c *fiber.Ctx) error { - return c.Render("errors/404", nil) - }) - app.Get("/errors/404/img", func(c *fiber.Ctx) error { - return c.Redirect("/static/img/error-404.png") - }) - app.Get("/errors/error", func(c *fiber.Ctx) error { - return c.Render("errors/error", fiber.Map{ - "err": "Test error", - }) - }) - app.Get("/errors/error/img", func(c *fiber.Ctx) error { - return c.Redirect("/static/img/error-generic.png") - }) - } else { - app.Use("/static", filesystem.New(filesystem.Config{ - MaxAge: 2592000, - Root: http.FS(static.GetFiles()), - })) - app.Use(cache.New(cache.Config{ - Expiration: 30 * time.Minute, - MaxBytes: 25000000, - KeyGenerator: func(c *fiber.Ctx) string { - return utils.GetInstanceUrl(c) + c.OriginalURL() - }, - CacheControl: true, - StoreResponseHeaders: true, - })) - } - - app.Get("/robots.txt", func(c *fiber.Ctx) error { - file, _ := static.GetFiles().ReadFile("robots.txt") - _, err := c.Write(file) - return err - }) - app.Get("/favicon.ico", func(c *fiber.Ctx) error { - file, _ := static.GetFiles().ReadFile("favicon/favicon.ico") - _, err := c.Write(file) - return err - }) - - app.Get("/", pages.HandleFrontpage) - app.Get("/about", pages.HandleAbout) - app.Get("/privacy", pages.HandlePrivacy) - app.Get("/search", pages.HandleSearch) - app.Get("/trending.:type", pages.HandleTrendingRSS) - app.Get("/trending", pages.HandleTrending) - app.Get("/a/:postID", pages.HandlePost) - app.Get("/a/:postID/embed", pages.HandleEmbed) - app.Get("/t/:tag.:type", pages.HandleTagRSS) - app.Get("/t/:tag", pages.HandleTag) - app.Get("/t/:tag/:postID", pages.HandlePost) - app.Get("/r/:sub/:postID", pages.HandlePost) - app.Get("/user/:userID.:type", pages.HandleUserRSS) - app.Get("/user/:userID", pages.HandleUser) - app.Get("/user/:userID/favorites", pages.HandleUserFavorites) - app.Get("/user/:userID/comments", pages.HandleUserComments) - app.Get("/user/:userID/cover", pages.HandleUserCover) - app.Get("/user/:userID/avatar", pages.HandleUserAvatar) - app.Get("/gallery/:postID", pages.HandlePost) - app.Get("/gallery/:postID/embed", pages.HandleEmbed) - app.Get("/:postID.gifv", pages.HandleGifv) - app.Get("/:baseName.:extension", pages.HandleMedia) - app.Get("/stack/:baseName.:extension", pages.HandleMedia) - app.Get("/:postID", pages.HandlePost) - app.Get("/:postID/embed", pages.HandleEmbed) - - err := app.Listen(utils.Config.Addr + ":" + utils.Config.Port) + addr := utils.Config.Addr + ":" + utils.Config.Port + fmt.Println("listening on " + addr) + err := http.ListenAndServe(addr, app) if err != nil { fmt.Println(err) } diff --git a/render/render.go b/render/render.go new file mode 100644 index 0000000..d62e32a --- /dev/null +++ b/render/render.go @@ -0,0 +1,74 @@ +// stolen from gofiber/template but simpler +package render + +import ( + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + + "codeberg.org/rimgo/rimgo/utils" + "github.com/mailgun/raymond/v2" +) + +var Renderer *renderer + +func Render(out io.Writer, name string, bind map[string]any) error { + return Renderer.Render(out, name, bind) +} + +type renderer struct { + templates map[string]*raymond.Template + funcmap map[string]any +} + +const ext = ".hbs" + +func Initialize(views fs.FS) { + r := new(renderer) + r.templates = make(map[string]*raymond.Template) + raymond.RegisterHelpers(r.funcmap) + fs.WalkDir(views, "/", func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + path, _ = filepath.Rel("/", path) + name, hasExt := strings.CutSuffix(path, ext) + if !hasExt { + return nil + } + path = filepath.ToSlash(path) + buf, err := utils.ReadFile(path, views) + if err != nil { + return err + } + tmpl, err := raymond.Parse(string(buf)) + if err != nil { + return err + } + r.templates[name] = tmpl + return nil + }) + for j := range r.templates { + for n, template := range r.templates { + r.templates[j].RegisterPartialTemplate(n, template) + } + } + Renderer = r +} + +func (r *renderer) Render(out io.Writer, name string, bind map[string]any) error { + tmpl := r.templates[name] + if tmpl == nil { + return fmt.Errorf("render: template %s does not exist", name) + } + parsed, err := tmpl.Exec(bind) + if err != nil { + return fmt.Errorf("render: %w", err) + } + if _, err = out.Write([]byte(parsed)); err != nil { + return fmt.Errorf("render: %w", err) + } + return err +} diff --git a/utils/error.go b/utils/error.go index e6bc8dd..7d4ac7d 100644 --- a/utils/error.go +++ b/utils/error.go @@ -14,7 +14,7 @@ func RenderError(c *fiber.Ctx, code int) error { if code != 0 { codeStr = strconv.Itoa(code) } - img, _ := static.GetFiles().ReadFile("img/error-" + codeStr + ".png") + img, _ := ReadFile("img/error-"+codeStr+".png", static.GetFiles()) c.Set("Content-Type", "image/png") return c.Status(code).Send(img) } else { diff --git a/utils/readFile.go b/utils/readFile.go new file mode 100644 index 0000000..6b1593c --- /dev/null +++ b/utils/readFile.go @@ -0,0 +1,15 @@ +package utils + +import ( + "io" + "io/fs" +) + +func ReadFile(path string, fs fs.FS) ([]byte, error) { + file, err := fs.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + return io.ReadAll(file) +} From 189ebeefde074ecacbc7efeb61b1764546cff6e6 Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 17:08:15 +0100 Subject: [PATCH 02/14] add noteq --- render/helpers.go | 17 +++++++++++++++++ render/render.go | 3 +-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 render/helpers.go diff --git a/render/helpers.go b/render/helpers.go new file mode 100644 index 0000000..a752ae3 --- /dev/null +++ b/render/helpers.go @@ -0,0 +1,17 @@ +package render + +import "github.com/mailgun/raymond/v2" + +func (r *renderer) registerHelpers() { + funcmap := map[string]any{ + "noteq": noteq, + } + raymond.RegisterHelpers(funcmap) +} + +func noteq(a, b any, options *raymond.Options) any { + if raymond.Str(a) != raymond.Str(b) { + return options.Fn() + } + return "" +} diff --git a/render/render.go b/render/render.go index d62e32a..7befcde 100644 --- a/render/render.go +++ b/render/render.go @@ -20,7 +20,6 @@ func Render(out io.Writer, name string, bind map[string]any) error { type renderer struct { templates map[string]*raymond.Template - funcmap map[string]any } const ext = ".hbs" @@ -28,7 +27,7 @@ const ext = ".hbs" func Initialize(views fs.FS) { r := new(renderer) r.templates = make(map[string]*raymond.Template) - raymond.RegisterHelpers(r.funcmap) + r.registerHelpers() fs.WalkDir(views, "/", func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err From 04fbc7f5f4709450634d7814ae0c96c0e11864aa Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 17:24:40 +0100 Subject: [PATCH 03/14] fix embed handling --- main.go | 2 +- render/render.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index daa074a..1033072 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ func main() { render.Initialize(views) app := http.NewServeMux() - app.Handle("/static/", http.FileServerFS(static)) + app.Handle("/static/", http.StripPrefix("/static/", http.FileServerFS(static))) app.Handle("GET /test-render", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { err := render.Render(w, "errors/404", nil) fmt.Println(err) diff --git a/render/render.go b/render/render.go index 7befcde..3b13da9 100644 --- a/render/render.go +++ b/render/render.go @@ -28,11 +28,10 @@ func Initialize(views fs.FS) { r := new(renderer) r.templates = make(map[string]*raymond.Template) r.registerHelpers() - fs.WalkDir(views, "/", func(path string, d fs.DirEntry, err error) error { + fs.WalkDir(views, ".", func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } - path, _ = filepath.Rel("/", path) name, hasExt := strings.CutSuffix(path, ext) if !hasExt { return nil From cd4a36c9f7ef303a43306ff4b2606b574cf178df Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 18:57:06 +0100 Subject: [PATCH 04/14] port most routes --- main.go | 64 ++++++++++++++++++++++++++++++--- pages/about.go | 20 ++++++----- pages/embed.go | 39 ++++++++++---------- pages/frontpage.go | 16 +++++---- pages/media.go | 76 +++++++++++++++++++++------------------ pages/post.go | 31 ++++++++-------- pages/privacy.go | 13 +++---- pages/rss.go | 2 ++ pages/search.go | 29 ++++++++------- pages/tag.go | 29 +++++++-------- pages/trending.go | 27 +++++++------- pages/user.go | 87 ++++++++++++++++++++++----------------------- render/render.go | 8 +++-- utils/accepts.go | 22 ++++++++++++ utils/error.go | 31 +++++++++++----- utils/readFile.go | 15 -------- utils/setHeaders.go | 14 ++++---- 17 files changed, 310 insertions(+), 213 deletions(-) create mode 100644 utils/accepts.go delete mode 100644 utils/readFile.go diff --git a/main.go b/main.go index 1033072..558f498 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,9 @@ package main import ( "flag" "fmt" + "io" "net/http" + "strings" "codeberg.org/rimgo/rimgo/pages" "codeberg.org/rimgo/rimgo/render" @@ -20,7 +22,8 @@ func wrapHandler(h handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := h(w, r) if err != nil { - http.Error(w, "oop", 500) + fmt.Println(err) + http.Error(w, http.StatusText(500), 500) } }) } @@ -37,10 +40,63 @@ func main() { render.Initialize(views) app := http.NewServeMux() - app.Handle("/static/", http.StripPrefix("/static/", http.FileServerFS(static))) - app.Handle("GET /test-render", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + + app.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(static))) + app.Handle("GET /robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + file, _ := static.Open("robots.txt") + defer file.Close() + io.Copy(w, file) + })) + app.Handle("GET /favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + file, _ := static.Open("favicon/favicon.ico") + defer file.Close() + io.Copy(w, file) + })) + + app.Handle("GET /{$}", wrapHandler(pages.HandleFrontpage)) + // app.Handle("GET /{postID}/embed", wrapHandler(pages.HandleEmbed)) // fix this conflict + app.Handle("GET /a/{postID}", wrapHandler(pages.HandlePost)) + app.Handle("GET /a/{postID}/embed", wrapHandler(pages.HandleEmbed)) + // app.Handle("GET /t/:tag.:type", pages.HandleTagRSS) + app.Handle("GET /t/{tag}", wrapHandler(pages.HandleTag)) + app.Handle("GET /t/{tag}/{postID}", wrapHandler(pages.HandlePost)) + app.Handle("GET /r/{sub}/{postID}", wrapHandler(pages.HandlePost)) + // app.Handle("GET /user/:userID.:type", pages.HandleUserRSS) + app.Handle("GET /user/{userID}", wrapHandler(pages.HandleUser)) + app.Handle("GET /user/{userID}/favorites", wrapHandler(pages.HandleUserFavorites)) + app.Handle("GET /user/{userID}/comments", wrapHandler(pages.HandleUserComments)) + app.Handle("GET /user/{userID}/cover", wrapHandler(pages.HandleUserCover)) + app.Handle("GET /user/{userID}/avatar", wrapHandler(pages.HandleUserAvatar)) + app.Handle("GET /gallery/{postID}", wrapHandler(pages.HandlePost)) + app.Handle("GET /gallery/{postID}/embed", wrapHandler(pages.HandleEmbed)) + app.Handle("GET /{component}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + component := r.PathValue("component") + switch { + case component == "about": + return pages.HandleAbout(w, r) + case component == "privacy": + return pages.HandlePrivacy(w, r) + case component == "search": + return pages.HandleSearch(w, r) + case component == "trending": + return pages.HandleTrending(w, r) + case strings.HasPrefix(component, "trending."): + // return pages.HandleTrendingRSS(w, r) + case strings.HasSuffix(component, ".gifv"): + r.SetPathValue("postID", component) + return pages.HandleGifv(w, r) + case strings.Contains(component, "."): + return pages.HandleMedia(w, r) + default: + r.SetPathValue("postID", component) + return pages.HandlePost(w, r) + } + return nil + })) + app.Handle("GET /stack/:baseName.:extension", wrapHandler(pages.HandleMedia)) + // matches anything with no more specific route + app.Handle("GET /", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { err := render.Render(w, "errors/404", nil) - fmt.Println(err) return err })) diff --git a/pages/about.go b/pages/about.go index 2c7d522..3ed0fcc 100644 --- a/pages/about.go +++ b/pages/about.go @@ -1,19 +1,21 @@ package pages import ( + "net/http" + + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleAbout(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=31557600") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleAbout(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - return c.Render("about", fiber.Map{ - "proto": c.Protocol(), - "domain": c.Hostname(), + return render.Render(w, "about", map[string]any{ + "proto": r.Proto, + "domain": r.Host, "force_webp": utils.Config.ForceWebp, }) } diff --git a/pages/embed.go b/pages/embed.go index 25f9333..f7016c6 100644 --- a/pages/embed.go +++ b/pages/embed.go @@ -1,48 +1,49 @@ package pages import ( + "net/http" "strings" "codeberg.org/rimgo/rimgo/api" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleEmbed(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("Cache-Control", "public,max-age=31557600") - c.Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content") +func HandleEmbed(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content") post, err := api.Album{}, error(nil) switch { - case strings.HasPrefix(c.Path(), "/a"): - post, err = ApiClient.FetchAlbum(c.Params("postID")) - case strings.HasPrefix(c.Path(), "/gallery"): - post, err = ApiClient.FetchPosts(c.Params("postID")) + case strings.HasPrefix(r.URL.Path, "/a"): + post, err = ApiClient.FetchAlbum(r.PathValue("postID")) + case strings.HasPrefix(r.URL.Path, "/gallery"): + post, err = ApiClient.FetchPosts(r.PathValue("postID")) default: - post, err = ApiClient.FetchMedia(c.Params("postID")) + post, err = ApiClient.FetchMedia(r.PathValue("postID")) } if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } if err != nil { return err } - return c.Render("embed", fiber.Map{ + return render.Render(w, "embed", map[string]any{ "post": post, }) } -func HandleGifv(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("Cache-Control", "public,max-age=31557600") - c.Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content") +func HandleGifv(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; form-action 'none'; media-src 'self'; style-src 'self'; img-src 'self'; block-all-mixed-content") - return c.Render("gifv", fiber.Map{ - "id": c.Params("postID"), + return render.Render(w, "gifv", map[string]any{ + "id": r.PathValue("postID"), }) } diff --git a/pages/frontpage.go b/pages/frontpage.go index 1ba96cd..b3e7fef 100644 --- a/pages/frontpage.go +++ b/pages/frontpage.go @@ -1,19 +1,21 @@ package pages import ( + "net/http" + + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) var VersionInfo string -func HandleFrontpage(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=31557600") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleFrontpage(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - return c.Render("frontpage", fiber.Map{ + return render.Render(w, "frontpage", map[string]any{ "config": utils.Config, "version": VersionInfo, }) diff --git a/pages/media.go b/pages/media.go index fbd1f91..e327811 100644 --- a/pages/media.go +++ b/pages/media.go @@ -1,54 +1,58 @@ package pages import ( + "io" "mime" "net/http" "strings" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleMedia(c *fiber.Ctx) error { - c.Set("Cache-Control", "public,max-age=31557600") - c.Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'") - if strings.HasPrefix(c.Path(), "/stack") { - return handleMedia(c, "https://i.stack.imgur.com/"+strings.ReplaceAll(c.Params("baseName"), "stack/", "")+"."+c.Params("extension")) +func HandleMedia(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'") + splitName := strings.SplitN(r.PathValue("component"), ".", 2) + baseName, extension := splitName[0], splitName[1] + if strings.HasPrefix(r.URL.Path, "/stack") { + return handleMedia(w, r, "https://i.stack.imgur.com/"+strings.ReplaceAll(baseName, "stack/", "")+"."+extension) } else { - return handleMedia(c, "https://i.imgur.com/"+c.Params("baseName")+"."+c.Params("extension")) + return handleMedia(w, r, "https://i.imgur.com/"+baseName+"."+extension) } } -func HandleUserCover(c *fiber.Ctx) error { - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'") - return handleMedia(c, "https://imgur.com/user/"+c.Params("userID")+"/cover?maxwidth=2560") +func HandleUserCover(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'") + return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/cover?maxwidth=2560") } -func HandleUserAvatar(c *fiber.Ctx) error { - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'") - return handleMedia(c, "https://imgur.com/user/"+c.Params("userID")+"/avatar") +func HandleUserAvatar(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'") + return handleMedia(w, r, "https://imgur.com/user/"+r.PathValue("userID")+"/avatar") } -func handleMedia(c *fiber.Ctx, url string) error { - utils.SetHeaders(c) +func handleMedia(w http.ResponseWriter, r *http.Request, url string) error { + utils.SetHeaders(w) + path := r.URL.Path if utils.Config.ForceWebp && - !strings.HasSuffix(c.Path(), ".webp") && - c.Get("Sec-Fetch-Dest") == "image" && - c.Query("no_webp") == "" && - c.Accepts("image/webp") == "image/webp" && - !strings.HasPrefix(c.Path(), "/stack") { + !strings.HasSuffix(path, ".webp") && + r.Header.Get("Sec-Fetch-Dest") == "image" && + r.URL.Query().Get("no_webp") == "" && + utils.Accepts(r, "image/webp") && + !strings.HasPrefix(path, "/stack") { url = strings.ReplaceAll(url, ".png", ".webp") url = strings.ReplaceAll(url, ".jpg", ".webp") url = strings.ReplaceAll(url, ".jpeg", ".webp") - filename := strings.TrimPrefix(c.Path(), "/") - c.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename})) + filename := strings.TrimPrefix(path, "/") + w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename*": filename})) } - if strings.HasPrefix(c.Path(), "/stack") && strings.Contains(c.OriginalURL(), "?") { - url = url + "?" + strings.Split(c.OriginalURL(), "?")[1] + queryStr := r.URL.Query().Encode() + if strings.HasPrefix(path, "/stack") && queryStr != "" { + url = url + "?" + queryStr } req, err := http.NewRequest("GET", url, nil) @@ -58,8 +62,9 @@ func handleMedia(c *fiber.Ctx, url string) error { utils.SetReqHeaders(req) - if c.Get("Range") != "" { - req.Header.Set("Range", c.Get("Range")) + rng := r.URL.Query().Get("Range") + if rng != "" { + req.Header.Set("Range", rng) } res, err := http.DefaultClient.Do(req) @@ -68,17 +73,18 @@ func handleMedia(c *fiber.Ctx, url string) error { } if res.StatusCode == 404 || strings.Contains(res.Request.URL.String(), "error/404") { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } else if res.StatusCode == 429 { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } - c.Set("Accept-Ranges", "bytes") - c.Set("Content-Type", res.Header.Get("Content-Type")) - c.Set("Content-Length", res.Header.Get("Content-Length")) + w.Header().Set("Accept-Ranges", "bytes") + w.Header().Set("Content-Type", res.Header.Get("Content-Type")) + w.Header().Set("Content-Length", res.Header.Get("Content-Length")) if res.Header.Get("Content-Range") != "" { - c.Set("Content-Range", res.Header.Get("Content-Range")) + w.Header().Set("Content-Range", res.Header.Get("Content-Range")) } - return c.SendStream(res.Body) + _, err = io.Copy(w, res.Body) + return err } diff --git a/pages/post.go b/pages/post.go index 1fc10fc..171d312 100644 --- a/pages/post.go +++ b/pages/post.go @@ -3,12 +3,13 @@ package pages import ( "crypto/rand" "fmt" + "net/http" "strconv" "strings" "codeberg.org/rimgo/rimgo/api" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) // Cursed function @@ -33,31 +34,31 @@ func nextInTag(client *api.Client, tagname, sort, page, I string) string { return tag.Posts[i+1].Link } -func HandlePost(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") +func HandlePost(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") - postId := c.Params("postID") + postId := r.PathValue("postID") if strings.Contains(postId, "-") { postId = postId[len(postId)-7:] } post, err := api.Album{}, error(nil) switch { - case strings.HasPrefix(c.Path(), "/a"): + case strings.HasPrefix(r.URL.Path, "/a"): post, err = ApiClient.FetchAlbum(postId) - case strings.HasPrefix(c.Path(), "/gallery"): + case strings.HasPrefix(r.URL.Path, "/gallery"): post, err = ApiClient.FetchPosts(postId) - case strings.HasPrefix(c.Path(), "/t"): + case strings.HasPrefix(r.URL.Path, "/t"): post, err = ApiClient.FetchPosts(postId) default: post, err = ApiClient.FetchMedia(postId) } if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil && post.Id == "" && strings.Contains(err.Error(), "404") { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } if err != nil { return err @@ -65,13 +66,13 @@ func HandlePost(c *fiber.Ctx) error { comments := []api.Comment{} if post.SharedWithCommunity { - c.Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Cache-Control", "public,max-age=604800") comments, err = ApiClient.FetchComments(postId) if err != nil { return err } } else { - c.Set("Cache-Control", "public,max-age=31557600") + w.Header().Set("Cache-Control", "public,max-age=31557600") } nonce := "" @@ -82,16 +83,16 @@ func HandlePost(c *fiber.Ctx) error { nonce = fmt.Sprintf("%x", b) csp = csp + " 'nonce-" + nonce + "'" } - c.Set("Content-Security-Policy", csp) + w.Header().Set("Content-Security-Policy", csp) var next string - tagParam := strings.Split(c.Query("tag"), ".") + tagParam := strings.Split(r.URL.Query().Get("tag"), ".") if len(tagParam) == 4 { tag, sort, page, index := tagParam[0], tagParam[1], tagParam[2], tagParam[3] next = nextInTag(ApiClient, tag, sort, page, index) } - return c.Render("post", fiber.Map{ + return render.Render(w, "post", map[string]any{ "post": post, "next": next, "comments": comments, diff --git a/pages/privacy.go b/pages/privacy.go index c2591e9..f9f651c 100644 --- a/pages/privacy.go +++ b/pages/privacy.go @@ -1,17 +1,18 @@ package pages import ( - "github.com/gofiber/fiber/v2" + "net/http" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" ) -func HandlePrivacy(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Content-Security-Policy", "default-src 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandlePrivacy(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "default-src 'none'; form-action 'self'; style-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - return c.Render("privacy", fiber.Map{ + return render.Render(w, "privacy", map[string]any{ "config": utils.Config, "version": VersionInfo, }) diff --git a/pages/rss.go b/pages/rss.go index d84ec1a..d7b7890 100644 --- a/pages/rss.go +++ b/pages/rss.go @@ -1,3 +1,5 @@ +//go:build fiber + package pages import ( diff --git a/pages/search.go b/pages/search.go index baad157..1f664fe 100644 --- a/pages/search.go +++ b/pages/search.go @@ -1,30 +1,33 @@ package pages import ( + "net/http" "strconv" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleSearch(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleSearch(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - query := c.Query("q") + query := r.URL.Query().Get("q") if utils.ImgurRe.MatchString(query) { - return c.Redirect(utils.ImgurRe.ReplaceAllString(query, "")) + w.Header().Set("Location", utils.ImgurRe.ReplaceAllString(query, "")) + w.WriteHeader(302) + return nil } - page := "0" - if c.Query("page") != "" { - page = c.Query("page") + page := r.URL.Query().Get("page") + if page == "" { + page = "0" } - pageNumber, err := strconv.Atoi(c.Query("page")) + pageNumber, err := strconv.Atoi(page) if err != nil { pageNumber = 0 } @@ -34,7 +37,7 @@ func HandleSearch(c *fiber.Ctx) error { return err } - return c.Render("search", fiber.Map{ + return render.Render(w, "search", map[string]any{ "query": query, "results": results, "page": pageNumber, diff --git a/pages/tag.go b/pages/tag.go index 145ef49..4f1da58 100644 --- a/pages/tag.go +++ b/pages/tag.go @@ -1,40 +1,41 @@ package pages import ( + "net/http" "strconv" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleTag(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleTag(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - page := "1" - if c.Query("page") != "" { - page = c.Query("page") + page := r.URL.Query().Get("page") + if page == "" { + page = "1" } - pageNumber, err := strconv.Atoi(c.Query("page")) + pageNumber, err := strconv.Atoi(page) if err != nil { pageNumber = 0 } - tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), page) + tag, err := ApiClient.FetchTag(r.PathValue("tag"), r.URL.Query().Get("sort"), page) if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } if tag.Display == "" { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } - return c.Render("tag", fiber.Map{ + return render.Render(w, "tag", map[string]any{ "tag": tag, "page": page, "nextPage": pageNumber + 1, diff --git a/pages/trending.go b/pages/trending.go index c44a6d0..6b447d3 100644 --- a/pages/trending.go +++ b/pages/trending.go @@ -1,35 +1,36 @@ package pages import ( + "net/http" "strconv" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleTrending(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleTrending(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - page := "1" - if c.Query("page") != "" { - page = c.Query("page") + page := r.URL.Query().Get("page") + if page == "" { + page = "1" } - pageNumber, err := strconv.Atoi(c.Query("page")) + pageNumber, err := strconv.Atoi(page) if err != nil { pageNumber = 1 } - section := c.Query("section") + section := r.URL.Query().Get("section") switch section { case "hot", "new", "top": default: section = "hot" } - sort := c.Query("sort") + sort := r.URL.Query().Get("sort") switch sort { case "newest", "best", "popular": default: @@ -41,7 +42,7 @@ func HandleTrending(c *fiber.Ctx) error { return err } - return c.Render("trending", fiber.Map{ + return render.Render(w, "trending", map[string]any{ "results": results, "section": section, "sort": sort, diff --git a/pages/user.go b/pages/user.go index 3a07e57..482b5d9 100644 --- a/pages/user.go +++ b/pages/user.go @@ -1,49 +1,49 @@ package pages import ( + "net/http" "strconv" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" ) -func HandleUser(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleUser(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - page := "0" - if c.Query("page") != "" { - page = c.Query("page") + page := r.URL.Query().Get("page") + if page == "" { + page = "0" } - pageNumber, err := strconv.Atoi(c.Query("page")) + pageNumber, err := strconv.Atoi(page) if err != nil { pageNumber = 0 } - user, err := ApiClient.FetchUser(c.Params("userID")) + user, err := ApiClient.FetchUser(r.PathValue("userID")) if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } if user.Username == "" { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } - submissions, err := ApiClient.FetchSubmissions(c.Params("userID"), "newest", page) + submissions, err := ApiClient.FetchSubmissions(r.PathValue("userID"), "newest", page) if err != nil && err.Error() == "ratelimited by imgur" { - c.Status(429) - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } - return c.Render("user", fiber.Map{ + return render.Render(w, "user", map[string]any{ "user": user, "submissions": submissions, "page": page, @@ -52,74 +52,73 @@ func HandleUser(c *fiber.Ctx) error { }) } -func HandleUserComments(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleUserComments(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - user, err := ApiClient.FetchUser(c.Params("userID")) + user, err := ApiClient.FetchUser(r.PathValue("userID")) if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } if user.Username == "" { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } - comments, err := ApiClient.FetchUserComments(c.Params("userID")) + comments, err := ApiClient.FetchUserComments(r.PathValue("userID")) if err != nil && err.Error() == "ratelimited by imgur" { - c.Status(429) - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } - return c.Render("userComments", fiber.Map{ + return render.Render(w, "userComments", map[string]any{ "user": user, "comments": comments, }) } -func HandleUserFavorites(c *fiber.Ctx) error { - utils.SetHeaders(c) - c.Set("X-Frame-Options", "DENY") - c.Set("Cache-Control", "public,max-age=604800") - c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") +func HandleUserFavorites(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Cache-Control", "public,max-age=604800") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") - page := "0" - if c.Query("page") != "" { - page = c.Query("page") + page := r.URL.Query().Get("page") + if page == "" { + page = "0" } - pageNumber, err := strconv.Atoi(c.Query("page")) + pageNumber, err := strconv.Atoi(page) if err != nil { pageNumber = 0 } - user, err := ApiClient.FetchUser(c.Params("userID")) + user, err := ApiClient.FetchUser(r.PathValue("userID")) if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } if user.Username == "" { - return utils.RenderError(c, 404) + return utils.RenderError(w, r, 404) } - favorites, err := ApiClient.FetchUserFavorites(c.Params("userID"), "newest", page) + favorites, err := ApiClient.FetchUserFavorites(r.PathValue("userID"), "newest", page) if err != nil && err.Error() == "ratelimited by imgur" { - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } - return c.Render("userFavorites", fiber.Map{ + return render.Render(w, "userFavorites", map[string]any{ "user": user, "favorites": favorites, "page": page, diff --git a/render/render.go b/render/render.go index 3b13da9..4e033e1 100644 --- a/render/render.go +++ b/render/render.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" - "codeberg.org/rimgo/rimgo/utils" "github.com/mailgun/raymond/v2" ) @@ -37,7 +36,12 @@ func Initialize(views fs.FS) { return nil } path = filepath.ToSlash(path) - buf, err := utils.ReadFile(path, views) + file, err := views.Open(path) + if err != nil { + return err + } + defer file.Close() + buf, err := io.ReadAll(file) if err != nil { return err } diff --git a/utils/accepts.go b/utils/accepts.go new file mode 100644 index 0000000..43147bb --- /dev/null +++ b/utils/accepts.go @@ -0,0 +1,22 @@ +package utils + +import ( + "net/http" + "strings" +) + +func Accepts(r *http.Request, format string) bool { + format = strings.ToLower(format) + group := strings.Split(format, "/")[0] + "/*" + header := r.Header.Get("Accept") + if header == "" { + return false + } + for _, mime := range strings.Split(header, ",") { + mime = strings.ToLower(strings.TrimSpace(strings.SplitN(mime, ";", 2)[0])) + if mime == "*/*" || mime == format || mime == group { + return true + } + } + return false +} diff --git a/utils/error.go b/utils/error.go index 7d4ac7d..00680d3 100644 --- a/utils/error.go +++ b/utils/error.go @@ -1,25 +1,38 @@ package utils import ( + "io" + "net/http" "strconv" - "strings" + "codeberg.org/rimgo/rimgo/render" "codeberg.org/rimgo/rimgo/static" - "github.com/gofiber/fiber/v2" ) -func RenderError(c *fiber.Ctx, code int) error { - if !strings.Contains(c.Get("Accept"), "html") && c.Params("extension") != "" { +func RenderError(w http.ResponseWriter, r *http.Request, code int) error { + if !Accepts(r, "text/html") && r.PathValue("extension") != "" { codeStr := "generic" if code != 0 { codeStr = strconv.Itoa(code) } - img, _ := ReadFile("img/error-"+codeStr+".png", static.GetFiles()) - c.Set("Content-Type", "image/png") - return c.Status(code).Send(img) + w.Header().Set("Content-Type", "image/png") + w.WriteHeader(code) + file, _ := static.GetFiles().Open("img/error-" + codeStr + ".png") + defer file.Close() + _, err := io.Copy(w, file) + if err != nil { + // panic on error to avoid a loop + panic(err) + } } else { - return c.Status(code).Render("errors/"+strconv.Itoa(code), fiber.Map{ - "path": c.Path(), + w.WriteHeader(code) + err := render.Render(w, "errors/"+strconv.Itoa(code), map[string]any{ + "path": r.URL.Path, }) + if err != nil { + // panic on error to avoid a loop + panic(err) + } } + return nil } diff --git a/utils/readFile.go b/utils/readFile.go deleted file mode 100644 index 6b1593c..0000000 --- a/utils/readFile.go +++ /dev/null @@ -1,15 +0,0 @@ -package utils - -import ( - "io" - "io/fs" -) - -func ReadFile(path string, fs fs.FS) ([]byte, error) { - file, err := fs.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - return io.ReadAll(file) -} diff --git a/utils/setHeaders.go b/utils/setHeaders.go index a884844..348e2f7 100644 --- a/utils/setHeaders.go +++ b/utils/setHeaders.go @@ -2,16 +2,14 @@ package utils import ( "net/http" - - "github.com/gofiber/fiber/v2" ) -func SetHeaders(c *fiber.Ctx) { - c.Set("Referrer-Policy", "no-referrer") - c.Set("X-Content-Type-Options", "nosniff") - c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow") - c.Set("Strict-Transport-Security", "max-age=31557600") - c.Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()") +func SetHeaders(w http.ResponseWriter) { + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Robots-Tag", "noindex, noimageindex, nofollow") + w.Header().Set("Strict-Transport-Security", "max-age=31557600") + w.Header().Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()") } func SetReqHeaders(req *http.Request) { From bf849e1cbc5ea5bfd8bd1d4e5e43f79e70fa288a Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 19:17:36 +0100 Subject: [PATCH 05/14] remove FIBER_PREFORK --- utils/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/utils/config.go b/utils/config.go index 6526e39..096e68a 100644 --- a/utils/config.go +++ b/utils/config.go @@ -11,7 +11,6 @@ type config struct { ImgurId string ProtocolDetection bool Secure bool - FiberPrefork bool ForceWebp bool ImageCache bool CleanupInterval time.Duration @@ -39,7 +38,6 @@ func LoadConfig() { ImgurId: envString("IMGUR_CLIENT_ID", "546c25a59c58ad7"), ProtocolDetection: envBool("PROTOCOL_DETECTION"), Secure: envBool("SECURE"), - FiberPrefork: envBool("FIBER_PREFORK"), ForceWebp: envBool("FORCE_WEBP"), Privacy: map[string]interface{}{ "set": os.Getenv("PRIVACY_NOT_COLLECTED") != "", From fd704f53e78b0834d883064d739a11a17d1d5596 Mon Sep 17 00:00:00 2001 From: orangix Date: Mon, 19 Jan 2026 19:25:42 +0100 Subject: [PATCH 06/14] update getUrl.go to use net/http --- utils/getUrl.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/utils/getUrl.go b/utils/getUrl.go index 8c3ddec..63d6306 100644 --- a/utils/getUrl.go +++ b/utils/getUrl.go @@ -1,18 +1,21 @@ package utils -import "github.com/gofiber/fiber/v2" +import "net/http" -func GetInstanceProtocol(c *fiber.Ctx) string { +func GetInstanceProtocol(r *http.Request) string { proto := "https" if !Config.Secure { proto = "http" } if Config.ProtocolDetection { - proto = c.Get("X-Forwarded-Proto", proto) + xproto := r.Header.Get("X-Forwarded-Proto") + if xproto != "" { + proto = xproto + } } return proto } -func GetInstanceUrl(c *fiber.Ctx) string { - return GetInstanceProtocol(c) + "://" + c.Hostname() +func GetInstanceUrl(r *http.Request) string { + return GetInstanceProtocol(r) + "://" + r.Host } From 7b1314fae384f8a2a9e99af7aca0980f6e73aec7 Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 23 Jan 2026 19:27:24 +0100 Subject: [PATCH 07/14] rss --- main.go | 28 ++++++++++++++---- pages/rss.go | 67 +++++++++++++++++++++++-------------------- utils/splitNameExt.go | 16 +++++++++++ 3 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 utils/splitNameExt.go diff --git a/main.go b/main.go index 558f498..58000ad 100644 --- a/main.go +++ b/main.go @@ -58,11 +58,26 @@ func main() { app.Handle("GET /a/{postID}", wrapHandler(pages.HandlePost)) app.Handle("GET /a/{postID}/embed", wrapHandler(pages.HandleEmbed)) // app.Handle("GET /t/:tag.:type", pages.HandleTagRSS) - app.Handle("GET /t/{tag}", wrapHandler(pages.HandleTag)) + app.Handle("GET /t/{tag}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + name, ext := utils.SplitNameExt(r.PathValue("tag")) + if ext != "" { + r.SetPathValue("tag", name[0:len(name)-1]) + r.SetPathValue("type", ext) + return pages.HandleTagRSS(w, r) + } + return pages.HandleTag(w, r) + })) app.Handle("GET /t/{tag}/{postID}", wrapHandler(pages.HandlePost)) app.Handle("GET /r/{sub}/{postID}", wrapHandler(pages.HandlePost)) - // app.Handle("GET /user/:userID.:type", pages.HandleUserRSS) - app.Handle("GET /user/{userID}", wrapHandler(pages.HandleUser)) + app.Handle("GET /user/{userID}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + name, ext := utils.SplitNameExt(r.PathValue("userID")) + if ext != "" { + r.SetPathValue("userID", name[0:len(name)-1]) + r.SetPathValue("type", ext) + return pages.HandleUserRSS(w, r) + } + return pages.HandleUser(w, r) + })) app.Handle("GET /user/{userID}/favorites", wrapHandler(pages.HandleUserFavorites)) app.Handle("GET /user/{userID}/comments", wrapHandler(pages.HandleUserComments)) app.Handle("GET /user/{userID}/cover", wrapHandler(pages.HandleUserCover)) @@ -81,7 +96,9 @@ func main() { case component == "trending": return pages.HandleTrending(w, r) case strings.HasPrefix(component, "trending."): - // return pages.HandleTrendingRSS(w, r) + _, ext := utils.SplitNameExt(component) + r.SetPathValue("type", ext) + return pages.HandleTrendingRSS(w, r) case strings.HasSuffix(component, ".gifv"): r.SetPathValue("postID", component) return pages.HandleGifv(w, r) @@ -91,9 +108,8 @@ func main() { r.SetPathValue("postID", component) return pages.HandlePost(w, r) } - return nil })) - app.Handle("GET /stack/:baseName.:extension", wrapHandler(pages.HandleMedia)) + // app.Handle("GET /stack/:baseName.:extension", wrapHandler(pages.HandleMedia)) // matches anything with no more specific route app.Handle("GET /", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { err := render.Render(w, "errors/404", nil) diff --git a/pages/rss.go b/pages/rss.go index d7b7890..e748b40 100644 --- a/pages/rss.go +++ b/pages/rss.go @@ -1,51 +1,54 @@ -//go:build fiber - package pages import ( + "mime" + "net/http" "time" "codeberg.org/rimgo/rimgo/api" "codeberg.org/rimgo/rimgo/utils" - "github.com/gofiber/fiber/v2" "github.com/gorilla/feeds" ) -func HandleTagRSS(c *fiber.Ctx) error { - utils.SetHeaders(c) +func HandleTagRSS(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) - tag, err := ApiClient.FetchTag(c.Params("tag"), c.Query("sort"), "1") + tag, err := ApiClient.FetchTag(r.PathValue("tag"), r.URL.Query().Get("sort"), "1") if err != nil && err.Error() == "ratelimited by imgur" { - return c.Status(429).SendString("rate limited by imgur") + w.WriteHeader(429) + _, err := w.Write([]byte("rate limited by imgur")) + return err } if err != nil { return err } if tag.Display == "" { - return c.Status(404).SendString("tag not found") + w.WriteHeader(404) + _, err := w.Write([]byte("tag not found")) + return err } - instance := utils.GetInstanceUrl(c) + instance := utils.GetInstanceUrl(r) feed := &feeds.Feed{ Title: tag.Display + " on Imgur", - Link: &feeds.Link{Href: instance + "/t/" + c.Params("tag")}, + Link: &feeds.Link{Href: instance + "/t/" + r.PathValue("tag")}, Created: time.Now(), } - return handleFeed(c, instance, feed, tag.Posts) + return handleFeed(w, r, instance, feed, tag.Posts) } -func HandleTrendingRSS(c *fiber.Ctx) error { - utils.SetHeaders(c) +func HandleTrendingRSS(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) - section := c.Query("section") + section := r.URL.Query().Get("section") switch section { case "hot", "new", "top": default: section = "hot" } - sort := c.Query("sort") + sort := r.URL.Query().Get("sort") switch sort { case "newest", "best", "popular": default: @@ -57,7 +60,7 @@ func HandleTrendingRSS(c *fiber.Ctx) error { return err } - instance := utils.GetInstanceUrl(c) + instance := utils.GetInstanceUrl(r) feed := &feeds.Feed{ Title: "Trending on Imgur", @@ -65,24 +68,23 @@ func HandleTrendingRSS(c *fiber.Ctx) error { Created: time.Now(), } - return handleFeed(c, instance, feed, results) + return handleFeed(w, r, instance, feed, results) } -func HandleUserRSS(c *fiber.Ctx) error { - utils.SetHeaders(c) +func HandleUserRSS(w http.ResponseWriter, r *http.Request) error { + utils.SetHeaders(w) - user := c.Params("userID") + user := r.PathValue("userID") submissions, err := ApiClient.FetchSubmissions(user, "newest", "1") if err != nil && err.Error() == "ratelimited by imgur" { - c.Status(429) - return utils.RenderError(c, 429) + return utils.RenderError(w, r, 429) } if err != nil { return err } - instance := utils.GetInstanceUrl(c) + instance := utils.GetInstanceUrl(r) feed := &feeds.Feed{ Title: user + " on Imgur", @@ -90,10 +92,10 @@ func HandleUserRSS(c *fiber.Ctx) error { Created: time.Now(), } - return handleFeed(c, instance, feed, submissions) + return handleFeed(w, r, instance, feed, submissions) } -func handleFeed(c *fiber.Ctx, instance string, feed *feeds.Feed, posts []api.Submission) error { +func handleFeed(w http.ResponseWriter, r *http.Request, instance string, feed *feeds.Feed, posts []api.Submission) error { feed.Items = []*feeds.Item{} for _, post := range posts { @@ -112,27 +114,30 @@ func handleFeed(c *fiber.Ctx, instance string, feed *feeds.Feed, posts []api.Sub feed.Items = append(feed.Items, item) } - c.Type(c.Params("type")) - switch c.Params("type") { + w.Header().Set("Content-Type", mime.TypeByExtension("."+r.PathValue("type"))) + switch r.PathValue("type") { case "atom": body, err := feed.ToAtom() if err != nil { return err } - return c.SendString(body) + w.Write([]byte(body)) case "json": body, err := feed.ToJSON() if err != nil { return err } - return c.JSON(body) + w.Write([]byte(body)) case "rss": body, err := feed.ToRss() if err != nil { return err } - return c.SendString(body) + w.Write([]byte(body)) default: - return c.Status(400).SendString("invalid type") + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + w.Write([]byte("invalid type")) } + return nil } diff --git a/utils/splitNameExt.go b/utils/splitNameExt.go new file mode 100644 index 0000000..fc87bd2 --- /dev/null +++ b/utils/splitNameExt.go @@ -0,0 +1,16 @@ +package utils + +func SplitNameExt(path string) (name, ext string) { + name, ext = path, "" + for range 5 { + if len(name) == 0 || name[len(name)-1] == '.' || name[len(name)-1] == '/' { + break + } + name = name[:len(name)-1] + ext = path[len(name):] + } + if len(name) == 0 || name[len(name)-1] != '.' { + return path, "" + } + return +} From 975ffa0b9cf9ad2aee99e4317cede442c5c6abc0 Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 23 Jan 2026 19:42:55 +0100 Subject: [PATCH 08/14] uncomment stack handler --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 58000ad..c327f31 100644 --- a/main.go +++ b/main.go @@ -109,7 +109,7 @@ func main() { return pages.HandlePost(w, r) } })) - // app.Handle("GET /stack/:baseName.:extension", wrapHandler(pages.HandleMedia)) + app.Handle("GET /stack/{component}", wrapHandler(pages.HandleMedia)) // matches anything with no more specific route app.Handle("GET /", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { err := render.Render(w, "errors/404", nil) From 4441d25d38a56d0396f5892958d501397d9498c1 Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 23 Jan 2026 20:14:56 +0100 Subject: [PATCH 09/14] fix errors --- main.go | 32 +++++++++++++++++++--- utils/error.go | 35 ++++++++++++++----------- views/errors/{error.hbs => generic.hbs} | 0 3 files changed, 49 insertions(+), 18 deletions(-) rename views/errors/{error.hbs => generic.hbs} (100%) diff --git a/main.go b/main.go index c327f31..0b99d4a 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "os" "strings" "codeberg.org/rimgo/rimgo/pages" @@ -20,10 +21,15 @@ type handler func(w http.ResponseWriter, r *http.Request) error func wrapHandler(h handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if v := recover(); v != nil { + utils.RenderError(w, r, 500, fmt.Sprint(v)) + } + }() err := h(w, r) if err != nil { fmt.Println(err) - http.Error(w, http.StatusText(500), 500) + utils.RenderError(w, r, 500, err.Error()) } }) } @@ -53,11 +59,31 @@ func main() { io.Copy(w, file) })) + if os.Getenv("ENV") == "dev" { + app.Handle("GET /errors/429", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + utils.RenderError(w, r, 429) + })) + app.Handle("GET /errors/429/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "/static/img/error-429.png") + w.WriteHeader(302) + })) + app.Handle("GET /errors/404", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + utils.RenderError(w, r, 404) + })) + app.Handle("GET /errors/404/img", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "/static/img/error-404.png") + w.WriteHeader(302) + })) + app.Handle("GET /errors/error", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + return fmt.Errorf("Test error") + })) + app.Handle("GET /errors/panic", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { + panic("Test error") + })) + } app.Handle("GET /{$}", wrapHandler(pages.HandleFrontpage)) - // app.Handle("GET /{postID}/embed", wrapHandler(pages.HandleEmbed)) // fix this conflict app.Handle("GET /a/{postID}", wrapHandler(pages.HandlePost)) app.Handle("GET /a/{postID}/embed", wrapHandler(pages.HandleEmbed)) - // app.Handle("GET /t/:tag.:type", pages.HandleTagRSS) app.Handle("GET /t/{tag}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { name, ext := utils.SplitNameExt(r.PathValue("tag")) if ext != "" { diff --git a/utils/error.go b/utils/error.go index 00680d3..30bd215 100644 --- a/utils/error.go +++ b/utils/error.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io" "net/http" "strconv" @@ -9,30 +10,34 @@ import ( "codeberg.org/rimgo/rimgo/static" ) -func RenderError(w http.ResponseWriter, r *http.Request, code int) error { +func RenderError(w http.ResponseWriter, r *http.Request, code int, str ...string) (err error) { + if len(str) != 1 { + str = []string{""} + } + codeStr := "generic" + if code == 0 { + code = 500 + } + if code != 500 { + codeStr = strconv.Itoa(code) + } if !Accepts(r, "text/html") && r.PathValue("extension") != "" { - codeStr := "generic" - if code != 0 { - codeStr = strconv.Itoa(code) - } w.Header().Set("Content-Type", "image/png") w.WriteHeader(code) file, _ := static.GetFiles().Open("img/error-" + codeStr + ".png") defer file.Close() - _, err := io.Copy(w, file) - if err != nil { - // panic on error to avoid a loop - panic(err) - } + _, err = io.Copy(w, file) + } else { w.WriteHeader(code) - err := render.Render(w, "errors/"+strconv.Itoa(code), map[string]any{ + err = render.Render(w, "errors/"+codeStr, map[string]any{ "path": r.URL.Path, + "err": str[0], }) - if err != nil { - // panic on error to avoid a loop - panic(err) - } + } + if err != nil { + // don't panic or return error, it will loop + fmt.Println("error in RenderError: " + err.Error()) } return nil } diff --git a/views/errors/error.hbs b/views/errors/generic.hbs similarity index 100% rename from views/errors/error.hbs rename to views/errors/generic.hbs From c208a55f40d67abea0ec899a7f1f734dd96c3fd8 Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 23 Jan 2026 20:27:20 +0100 Subject: [PATCH 10/14] go mod tidy --- go.mod | 17 +---------------- go.sum | 35 ----------------------------------- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 32e6f52..1f9462a 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.24.0 require ( github.com/PuerkitoBio/goquery v1.11.0 github.com/dustin/go-humanize v1.0.1 - github.com/gofiber/fiber/v2 v2.52.10 - github.com/gofiber/template/handlebars/v2 v2.1.12 github.com/gorilla/feeds v1.2.0 github.com/joho/godotenv v1.5.1 github.com/mailgun/raymond/v2 v2.0.48 @@ -17,26 +15,13 @@ require ( ) require ( - github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect - github.com/gofiber/template v1.8.3 // indirect - github.com/gofiber/utils v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/klauspost/compress v1.18.2 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/philhofer/fwd v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tinylib/msgp v1.6.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.68.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect diff --git a/go.sum b/go.sum index b122387..44dacbb 100644 --- a/go.sum +++ b/go.sum @@ -1,57 +1,31 @@ github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY= -github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= -github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= -github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= -github.com/gofiber/template/handlebars/v2 v2.1.12 h1:uWBMEnhTxVyarRjyj5uDrNg8rVMGzi6fhsL+PAJBvb0= -github.com/gofiber/template/handlebars/v2 v2.1.12/go.mod h1:K3h933a8wPFjIrLRUcnIVPTUW867ND6gqpw0zZ3yKpk= -github.com/gofiber/utils v1.2.0 h1:NCaqd+Efg3khhN++eeUUTyBz+byIxAsmIjpl8kKOMIc= -github.com/gofiber/utils v1.2.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= -github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -72,14 +46,6 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= -github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok= -github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 h1:1Coh5BsUBlXoEJmIEaNzVAWrtg9k7/eJzailMQr1grw= gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8= @@ -120,7 +86,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 3b95e89fa174cbeca6cde65dff592fc244f4c0d2 Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 30 Jan 2026 04:04:53 +0100 Subject: [PATCH 11/14] filter extensions for HandleMedia --- main.go | 9 ++++++++- pages/media.go | 7 +++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 0b99d4a..eaa130c 100644 --- a/main.go +++ b/main.go @@ -129,7 +129,14 @@ func main() { r.SetPathValue("postID", component) return pages.HandleGifv(w, r) case strings.Contains(component, "."): - return pages.HandleMedia(w, r) + baseName, extension := utils.SplitNameExt(r.PathValue("component")) + r.SetPathValue("baseName", baseName) + r.SetPathValue("extension", extension) + switch extension { + case "png", "gif", "jpg", "jpeg", "webp": + return pages.HandleMedia(w, r) + } + fallthrough default: r.SetPathValue("postID", component) return pages.HandlePost(w, r) diff --git a/pages/media.go b/pages/media.go index e327811..8ea24e5 100644 --- a/pages/media.go +++ b/pages/media.go @@ -12,12 +12,11 @@ import ( func HandleMedia(w http.ResponseWriter, r *http.Request) error { w.Header().Set("Cache-Control", "public,max-age=31557600") w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'") - splitName := strings.SplitN(r.PathValue("component"), ".", 2) - baseName, extension := splitName[0], splitName[1] + baseName, extension := r.PathValue("baseName"), r.PathValue("extension") if strings.HasPrefix(r.URL.Path, "/stack") { - return handleMedia(w, r, "https://i.stack.imgur.com/"+strings.ReplaceAll(baseName, "stack/", "")+"."+extension) + return handleMedia(w, r, "https://i.stack.imgur.com/"+baseName[5:]+extension) } else { - return handleMedia(w, r, "https://i.imgur.com/"+baseName+"."+extension) + return handleMedia(w, r, "https://i.imgur.com/"+baseName+extension) } } From d84ca93e0e6d1b21d7ead7bb7077cdec5f688f4e Mon Sep 17 00:00:00 2001 From: orangix Date: Fri, 30 Jan 2026 06:07:20 +0100 Subject: [PATCH 12/14] add mp4 and webm --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index eaa130c..c0ad7ed 100644 --- a/main.go +++ b/main.go @@ -133,7 +133,7 @@ func main() { r.SetPathValue("baseName", baseName) r.SetPathValue("extension", extension) switch extension { - case "png", "gif", "jpg", "jpeg", "webp": + case "png", "gif", "jpg", "jpeg", "webp", "mp4", "webm": return pages.HandleMedia(w, r) } fallthrough From 4ffe09bb81a5a83b23592729ba7b567714987e9c Mon Sep 17 00:00:00 2001 From: orangix Date: Sat, 31 Jan 2026 05:19:02 +0100 Subject: [PATCH 13/14] simplify SplitNameExt --- main.go | 6 +++--- pages/media.go | 2 +- pages/rss.go | 6 +++--- utils/splitNameExt.go | 16 ++++------------ 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index c0ad7ed..159b71d 100644 --- a/main.go +++ b/main.go @@ -87,7 +87,7 @@ func main() { app.Handle("GET /t/{tag}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { name, ext := utils.SplitNameExt(r.PathValue("tag")) if ext != "" { - r.SetPathValue("tag", name[0:len(name)-1]) + r.SetPathValue("tag", name) r.SetPathValue("type", ext) return pages.HandleTagRSS(w, r) } @@ -98,7 +98,7 @@ func main() { app.Handle("GET /user/{userID}", wrapHandler(func(w http.ResponseWriter, r *http.Request) error { name, ext := utils.SplitNameExt(r.PathValue("userID")) if ext != "" { - r.SetPathValue("userID", name[0:len(name)-1]) + r.SetPathValue("userID", name) r.SetPathValue("type", ext) return pages.HandleUserRSS(w, r) } @@ -133,7 +133,7 @@ func main() { r.SetPathValue("baseName", baseName) r.SetPathValue("extension", extension) switch extension { - case "png", "gif", "jpg", "jpeg", "webp", "mp4", "webm": + case ".png", ".gif", ".jpg", ".jpeg", ".webp", ".mp4", ".webm": return pages.HandleMedia(w, r) } fallthrough diff --git a/pages/media.go b/pages/media.go index 8ea24e5..a010cc9 100644 --- a/pages/media.go +++ b/pages/media.go @@ -14,7 +14,7 @@ func HandleMedia(w http.ResponseWriter, r *http.Request) error { w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'") baseName, extension := r.PathValue("baseName"), r.PathValue("extension") if strings.HasPrefix(r.URL.Path, "/stack") { - return handleMedia(w, r, "https://i.stack.imgur.com/"+baseName[5:]+extension) + return handleMedia(w, r, "https://i.stack.imgur.com/"+baseName+extension) } else { return handleMedia(w, r, "https://i.imgur.com/"+baseName+extension) } diff --git a/pages/rss.go b/pages/rss.go index e748b40..975b568 100644 --- a/pages/rss.go +++ b/pages/rss.go @@ -116,19 +116,19 @@ func handleFeed(w http.ResponseWriter, r *http.Request, instance string, feed *f w.Header().Set("Content-Type", mime.TypeByExtension("."+r.PathValue("type"))) switch r.PathValue("type") { - case "atom": + case ".atom": body, err := feed.ToAtom() if err != nil { return err } w.Write([]byte(body)) - case "json": + case ".json": body, err := feed.ToJSON() if err != nil { return err } w.Write([]byte(body)) - case "rss": + case ".rss": body, err := feed.ToRss() if err != nil { return err diff --git a/utils/splitNameExt.go b/utils/splitNameExt.go index fc87bd2..0487a86 100644 --- a/utils/splitNameExt.go +++ b/utils/splitNameExt.go @@ -1,16 +1,8 @@ package utils +import "path/filepath" + func SplitNameExt(path string) (name, ext string) { - name, ext = path, "" - for range 5 { - if len(name) == 0 || name[len(name)-1] == '.' || name[len(name)-1] == '/' { - break - } - name = name[:len(name)-1] - ext = path[len(name):] - } - if len(name) == 0 || name[len(name)-1] != '.' { - return path, "" - } - return + ext = filepath.Ext(path) + return path[:len(path)-len(ext)], ext } From e21a9f48562225eada13076c9a21e93d5c50e2f7 Mon Sep 17 00:00:00 2001 From: orangix Date: Sun, 1 Feb 2026 17:28:12 +0100 Subject: [PATCH 14/14] don't prepend . to pages/rss --- pages/rss.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/rss.go b/pages/rss.go index 975b568..a19b3ff 100644 --- a/pages/rss.go +++ b/pages/rss.go @@ -114,7 +114,7 @@ func handleFeed(w http.ResponseWriter, r *http.Request, instance string, feed *f feed.Items = append(feed.Items, item) } - w.Header().Set("Content-Type", mime.TypeByExtension("."+r.PathValue("type"))) + w.Header().Set("Content-Type", mime.TypeByExtension(r.PathValue("type"))) switch r.PathValue("type") { case ".atom": body, err := feed.ToAtom()