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 {
slenpaste = pkgs.buildGoModule {
pname = "slenpaste";
version = "0.1.2";
version = "0.1.3";
src = ./.;
goPackagePath = "github.com/slendidev/slenpaste";
vendorHash = null;
vendorHash = "sha256-MUvodL6K71SCfxu51T/Ka2/w32Kz+IXem1bYqXQLSaU=";
};
default = slenpaste;
};
@@ -55,6 +55,11 @@
default = false;
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 {
systemd.services.slenpaste = {
@@ -68,6 +73,7 @@
-listen "${config.services.slenpaste.listen}" \
-expire "${config.services.slenpaste.expireDur}" \
${lib.optionalString config.services.slenpaste.expireOnView "-expire-on-view=false"}
${lib.optionalString config.services.slenpaste.https "-https"}
'';
Restart = "on-failure";
};

2
go.mod
View File

@@ -1,3 +1,5 @@
module github.com/slendidev/slenpaste
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"
"strings"
"time"
"sync"
"golang.org/x/time/rate"
)
var (
@@ -20,6 +22,9 @@ var (
staticDir string
expireDur time.Duration
expireOnView bool
limiters = make(map[string]*rate.Limiter)
limMu sync.Mutex
useHTTPS bool
)
type meta struct {
@@ -27,6 +32,32 @@ type meta struct {
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 {
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, n)
@@ -140,7 +171,11 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
_ = 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) {
@@ -192,15 +227,16 @@ func main() {
flag.StringVar(&staticDir, "static", "static", "directory to save pastes")
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(&useHTTPS, "https", false, "use https:// in generated URLs")
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 {
uploadHandler(w, r)
} else {
viewHandler(w, r)
}
})
}))
fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir)
http.ListenAndServe(listenAddr, nil)