Skip to content

Commit

Permalink
Add custom character limit to pastes (#196)
Browse files Browse the repository at this point in the history
# Background

Recently I've been looking at alternatives to the service 0x0.st for
pasting log files to. Logpaste seems like the perfect replacement, but
it has limits on file size. Adding the ability for administrators to add
custom file sizes would allow admins to make the file size the
equivalent of 0x0's (512 MiB) or even larger. It would also allow them
to set smaller limits as well if resources are limited or if the content
being pasted doesn't warrant large sizes.

# Changes

- The default limit is now `2 * 1024 * 1024`, or 2 MiB instead of `2 *
1000 * 1000`, 2 MB
- Add cli flag `-maxsize int` to allow admins to set an arbitrary limit
on paste file sizes if they would like to
- The argument is passed in by # of MiB (so for a 512MiB limit,
`-maxsize 512`, or for the default 2MiB, `-maxsize 2`)
- Add field `maxCharLimit` to the `defaultServer` struct for storing the
given size
- Add fields to `handlers/paste_test.go` to use the new struct field in
order for tests to pass
- Replace instances of `MaxPasteCharacters` with `s.maxCharLimit`

---------

Co-authored-by: Michael Lynch <[email protected]>
  • Loading branch information
csfore and mtlynch authored Dec 4, 2023
1 parent effbfce commit a883a44
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ docker run \
| `-footer` | Footer to display on homepage (may include HTML) | |
| `-showdocs` | Whether to display usage documentation on homepage | `true` |
| `-perminutelimit` | Number of pastes to allow per IP per minute | `0` (no limit) |
| `-maxsize` | Max file size users can upload | `2` (2 MiB) |

### Docker environment variables

Expand Down
6 changes: 5 additions & 1 deletion cmd/logpaste/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@ func main() {
true, "whether to display usage information on homepage")
perMinuteLimit := flag.Int("perminutelimit",
0, "number of pastes to allow per IP per minute (set to 0 to disable rate limiting)")
maxPasteMiB := flag.Int64("maxsize", 2, "max file size as MiB")

flag.Parse()

const charactersPerMiB = 1024 * 1024
maxCharLimit := *maxPasteMiB * charactersPerMiB

h := gorilla.LoggingHandler(os.Stdout, handlers.New(handlers.SiteProperties{
Title: *title,
Subtitle: *subtitle,
FooterHTML: *footer,
ShowDocs: *showDocs,
}, *perMinuteLimit).Router())
}, *perMinuteLimit, maxCharLimit).Router())
if os.Getenv("PS_BEHIND_PROXY") != "" {
h = gorilla.ProxyIPHeadersHandler(h)
}
Expand Down
17 changes: 7 additions & 10 deletions handlers/paste.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import (
"github.com/mtlynch/logpaste/store"
)

const MaxPasteCharacters = 2 * 1000 * 1000

type PastePutResponse struct {
Id string `json:"id"`
}
Expand Down Expand Up @@ -54,15 +52,15 @@ func (s defaultServer) pastePut() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")

bodyRaw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, MaxPasteCharacters))
bodyRaw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, s.maxCharLimit))
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read request body", http.StatusBadRequest)
return
}

body := string(bodyRaw)
if !validatePaste(body, w) {
if !validatePaste(body, w, s.maxCharLimit) {
return
}

Expand All @@ -87,13 +85,12 @@ func (s defaultServer) pastePut() http.HandlerFunc {

func (s defaultServer) pastePost() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, MaxPasteCharacters+1024)
if err := r.ParseMultipartForm(MaxPasteCharacters); err != nil {
r.Body = http.MaxBytesReader(w, r.Body, s.maxCharLimit+1024)
if err := r.ParseMultipartForm(s.maxCharLimit); err != nil {
log.Printf("failed to parse form: %v", err)
http.Error(w, "no valid multipart/form-data found", http.StatusBadRequest)
return
}

w.Header().Set("Access-Control-Allow-Origin", "*")

body, ok := parsePasteFromMultipartForm(r.MultipartForm, w)
Expand All @@ -103,7 +100,7 @@ func (s defaultServer) pastePost() http.HandlerFunc {
return
}

if !validatePaste(body, w) {
if !validatePaste(body, w, s.maxCharLimit) {
return
}

Expand All @@ -127,12 +124,12 @@ func generateEntryId() string {
return random.String(8)
}

func validatePaste(p string, w http.ResponseWriter) bool {
func validatePaste(p string, w http.ResponseWriter, maxCharLimit int64) bool {
if len(strings.TrimSpace(p)) == 0 {
log.Print("Paste body was empty")
http.Error(w, "empty body", http.StatusBadRequest)
return false
} else if len(p) > MaxPasteCharacters {
} else if int64(len(p)) > maxCharLimit {
log.Printf("Paste body was too long: %d characters", len(p))
http.Error(w, "body too long", http.StatusBadRequest)
return false
Expand Down
17 changes: 11 additions & 6 deletions handlers/paste_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/mtlynch/logpaste/store"
)

const MaxPasteCharacters = 2 * 1024 * 1024

type mockStore struct {
entries map[string]string
}
Expand Down Expand Up @@ -41,8 +43,9 @@ func TestPasteGet(t *testing.T) {
}
router := mux.NewRouter()
s := defaultServer{
store: &ds,
router: router,
store: &ds,
router: router,
maxCharLimit: MaxPasteCharacters,
}
s.routes()

Expand Down Expand Up @@ -129,8 +132,9 @@ func TestPastePut(t *testing.T) {
}
router := mux.NewRouter()
s := defaultServer{
store: &ds,
router: router,
store: &ds,
router: router,
maxCharLimit: MaxPasteCharacters,
}
s.routes()

Expand Down Expand Up @@ -172,8 +176,9 @@ func TestPastePost(t *testing.T) {
}
router := mux.NewRouter()
s := defaultServer{
store: &ds,
router: router,
store: &ds,
router: router,
maxCharLimit: MaxPasteCharacters,
}
s.routes()
for _, tt := range []struct {
Expand Down
4 changes: 3 additions & 1 deletion handlers/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ type Server interface {
Router() *mux.Router
}

func New(sp SiteProperties, perMinuteLimit int) Server {
func New(sp SiteProperties, perMinuteLimit int, maxCharLimit int64) Server {
s := defaultServer{
router: mux.NewRouter(),
store: sqlite.New(),
siteProps: sp,
ipRateLimiter: limit.New(perMinuteLimit),
maxCharLimit: maxCharLimit,
}
s.routes()
return s
Expand All @@ -35,6 +36,7 @@ type defaultServer struct {
store store.Store
siteProps SiteProperties
ipRateLimiter limit.IPRateLimiter
maxCharLimit int64
}

// Router returns the underlying router interface for the server.
Expand Down

0 comments on commit a883a44

Please sign in to comment.