// Copyright 2024 Patial Tech (Ankit Patial). // // This file is part of code.patial.tech/go/appcore, which is MIT licensed. // See http://opensource.org/licenses/MIT package request import ( "encoding/json" "errors" "fmt" "io" "net/http" "code.patial.tech/go/appcore/validate" ) // MaxRequestBodySize is the maximum allowed size for request bodies (1MB default). // This prevents resource exhaustion attacks from large payloads. // Override this value if you need to accept larger requests. var MaxRequestBodySize int64 = 1 << 20 // 1MB func FormField(r *http.Request, key string) (string, error) { f, err := Payload[map[string]any](r) if err != nil { return "", err } return fmt.Sprintf("%v", f[key]), nil } func PayloadMap(r *http.Request) (map[string]any, error) { return Payload[map[string]any](r) } func PayloadWithValidate[T any](r *http.Request) (T, error) { p, err := Payload[T](r) if err != nil { return p, err } if err := validate.Struct(p); err != nil { return p, err } return p, nil } func Payload[T any](r *http.Request) (T, error) { var p T // Limit request body size to prevent resource exhaustion limited := io.LimitReader(r.Body, MaxRequestBodySize) decoder := json.NewDecoder(limited) if err := decoder.Decode(&p); err != nil { // Check if we hit the size limit if err == io.EOF || err == io.ErrUnexpectedEOF { // Try to read one more byte to see if there's more data var buf [1]byte if n, _ := limited.Read(buf[:]); n == 0 { // We hit the limit return p, errors.New("request body too large") } } return p, err } return p, nil } // DecodeJSON decodes JSON from the request body into the provided pointer. // This is useful when you want to decode into an existing variable. // The request body size is limited by MaxRequestBodySize to prevent DoS attacks. func DecodeJSON(r *http.Request, v any) error { // Limit request body size to prevent resource exhaustion limited := io.LimitReader(r.Body, MaxRequestBodySize) decoder := json.NewDecoder(limited) if err := decoder.Decode(v); err != nil { // Check if we hit the size limit if err == io.EOF || err == io.ErrUnexpectedEOF { // Try to read one more byte to see if there's more data var buf [1]byte if n, _ := limited.Read(buf[:]); n == 0 { // We hit the limit return errors.New("request body too large") } } return err } return nil }