Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-05-08 02:29:17 +03:00
parent 72fd4415b8
commit 594465269a
4 changed files with 51 additions and 5 deletions

View File

@@ -15,10 +15,10 @@
packages = rec { packages = rec {
slenpaste = pkgs.buildGoModule { slenpaste = pkgs.buildGoModule {
pname = "slenpaste"; pname = "slenpaste";
version = "0.1.2"; version = "0.1.3";
src = ./.; src = ./.;
goPackagePath = "github.com/slendidev/slenpaste"; goPackagePath = "github.com/slendidev/slenpaste";
vendorHash = null; vendorHash = "sha256-MUvodL6K71SCfxu51T/Ka2/w32Kz+IXem1bYqXQLSaU=";
}; };
default = slenpaste; default = slenpaste;
}; };
@@ -55,6 +55,11 @@
default = false; default = false;
description = "Whether to expire on first view"; description = "Whether to expire on first view";
}; };
options.services.slenpaste.https = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to use https:// in generated URLs";
};
config = lib.mkIf config.services.slenpaste.enable { config = lib.mkIf config.services.slenpaste.enable {
systemd.services.slenpaste = { systemd.services.slenpaste = {
@@ -68,6 +73,7 @@
-listen "${config.services.slenpaste.listen}" \ -listen "${config.services.slenpaste.listen}" \
-expire "${config.services.slenpaste.expireDur}" \ -expire "${config.services.slenpaste.expireDur}" \
${lib.optionalString config.services.slenpaste.expireOnView "-expire-on-view=false"} ${lib.optionalString config.services.slenpaste.expireOnView "-expire-on-view=false"}
${lib.optionalString config.services.slenpaste.https "-https"}
''; '';
Restart = "on-failure"; Restart = "on-failure";
}; };

2
go.mod
View File

@@ -1,3 +1,5 @@
module github.com/slendidev/slenpaste module github.com/slendidev/slenpaste
go 1.24.2 go 1.24.2
require golang.org/x/time v0.11.0 // indirect

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=

42
main.go
View File

@@ -12,6 +12,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"sync"
"golang.org/x/time/rate"
) )
var ( var (
@@ -20,6 +22,9 @@ var (
staticDir string staticDir string
expireDur time.Duration expireDur time.Duration
expireOnView bool expireOnView bool
limiters = make(map[string]*rate.Limiter)
limMu sync.Mutex
useHTTPS bool
) )
type meta struct { type meta struct {
@@ -27,6 +32,32 @@ type meta struct {
ExpireOnView bool `json:"expire_on_view"` ExpireOnView bool `json:"expire_on_view"`
} }
func getLimiter(ip string) *rate.Limiter {
limMu.Lock()
defer limMu.Unlock()
lim, ok := limiters[ip]
if !ok {
lim = rate.NewLimiter(1, 5) // 1 req/sec, burst of 5
limiters[ip] = lim
}
return lim
}
func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if i := strings.LastIndex(ip, ":"); i != -1 {
ip = ip[:i]
}
lim := getLimiter(ip)
if !lim.Allow() {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
next(w, r)
}
}
func randomID(n int) string { func randomID(n int) string {
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, n) b := make([]rune, n)
@@ -140,7 +171,11 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
_ = os.WriteFile(path+".json", metaBytes, 0644) _ = os.WriteFile(path+".json", metaBytes, 0644)
} }
fmt.Fprintf(w, "http://%s/%s\n", domain, filename) scheme := "http"
if useHTTPS {
scheme = "https"
}
fmt.Fprintf(w, "%s://%s/%s\n", scheme, domain, filename)
} }
func viewHandler(w http.ResponseWriter, r *http.Request) { func viewHandler(w http.ResponseWriter, r *http.Request) {
@@ -192,15 +227,16 @@ func main() {
flag.StringVar(&staticDir, "static", "static", "directory to save pastes") flag.StringVar(&staticDir, "static", "static", "directory to save pastes")
flag.DurationVar(&expireDur, "expire", 0, "time after which paste expires (e.g. 5m, 1h)") flag.DurationVar(&expireDur, "expire", 0, "time after which paste expires (e.g. 5m, 1h)")
flag.BoolVar(&expireOnView, "expire-on-view", false, "delete paste after it's viewed once") flag.BoolVar(&expireOnView, "expire-on-view", false, "delete paste after it's viewed once")
flag.BoolVar(&useHTTPS, "https", false, "use https:// in generated URLs")
flag.Parse() flag.Parse()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", rateLimitMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
uploadHandler(w, r) uploadHandler(w, r)
} else { } else {
viewHandler(w, r) viewHandler(w, r)
} }
}) }))
fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir) fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir)
http.ListenAndServe(listenAddr, nil) http.ListenAndServe(listenAddr, nil)