11package request
22
33import (
4+ "bytes"
45 "encoding/json"
56 "encoding/xml"
7+ "errors"
68 "io"
79 "net/http"
810)
911
12+ // ErrBodyTooLarge is returned when the request body exceeds
13+ // the maximum allowed size. Callers can check for this error
14+ // using [errors.Is].
15+ var ErrBodyTooLarge = errors .New ("request body too large" )
16+
1017// DefaultMaxBodySize is the default maximum request body size
1118// (10 MB) used by the size-limited body reading functions.
1219// This prevents denial-of-service attacks via excessively
@@ -24,16 +31,26 @@ func Bytes(r *http.Request) ([]byte, error) {
2431}
2532
2633// LimitedBytes reads the request body up to maxSize bytes and
27- // returns it as a byte slice. If the body exceeds maxSize, an
28- // error is returned. This prevents denial-of-service attacks via
29- // excessively large request bodies. Pass -1 to use
34+ // returns it as a byte slice. If the body exceeds maxSize,
35+ // [ErrBodyTooLarge] is returned. This prevents denial-of-service
36+ // attacks via excessively large request bodies. Pass -1 to use
3037// [DefaultMaxBodySize].
3138func LimitedBytes (r * http.Request , maxSize int64 ) ([]byte , error ) {
3239 if maxSize < 0 {
3340 maxSize = DefaultMaxBodySize
3441 }
3542
36- return io .ReadAll (io .LimitReader (r .Body , maxSize + 1 ))
43+ data , err := io .ReadAll (io .LimitReader (r .Body , maxSize + 1 ))
44+
45+ if err != nil {
46+ return nil , err
47+ }
48+
49+ if int64 (len (data )) > maxSize {
50+ return nil , ErrBodyTooLarge
51+ }
52+
53+ return data , nil
3754}
3855
3956// String reads the request body and returns it as a string.
@@ -54,8 +71,8 @@ func String(r *http.Request) (string, error) {
5471}
5572
5673// LimitedString reads the request body up to maxSize bytes and
57- // returns it as a string. If the body exceeds maxSize, the result
58- // is truncated . Pass -1 to use [DefaultMaxBodySize].
74+ // returns it as a string. If the body exceeds maxSize,
75+ // [ErrBodyTooLarge] is returned . Pass -1 to use [DefaultMaxBodySize].
5976func LimitedString (r * http.Request , maxSize int64 ) (string , error ) {
6077 body , err := LimitedBytes (r , maxSize )
6178
@@ -106,17 +123,26 @@ func StrictJSON[T any](r *http.Request) (value T, err error) {
106123}
107124
108125// LimitedJSON decodes JSON data from the request body into a value
109- // of type T, reading at most maxSize bytes. This prevents
126+ // of type T, reading at most maxSize bytes. If the body exceeds
127+ // maxSize, [ErrBodyTooLarge] is returned. This prevents
110128// denial-of-service attacks via oversized JSON payloads. Pass -1
111129// to use [DefaultMaxBodySize].
112130func LimitedJSON [T any ](r * http.Request , maxSize int64 ) (value T , err error ) {
113131 if maxSize < 0 {
114132 maxSize = DefaultMaxBodySize
115133 }
116134
117- limited := io .LimitReader (r .Body , maxSize + 1 )
135+ data , err := io .ReadAll ( io . LimitReader (r .Body , maxSize + 1 ) )
118136
119- if err := json .NewDecoder (limited ).Decode (& value ); err != nil {
137+ if err != nil {
138+ return value , err
139+ }
140+
141+ if int64 (len (data )) > maxSize {
142+ return value , ErrBodyTooLarge
143+ }
144+
145+ if err := json .NewDecoder (bytes .NewReader (data )).Decode (& value ); err != nil {
120146 return value , err
121147 }
122148
@@ -125,14 +151,24 @@ func LimitedJSON[T any](r *http.Request, maxSize int64) (value T, err error) {
125151
126152// StrictLimitedJSON decodes JSON data from the request body into
127153// a value of type T, reading at most maxSize bytes and rejecting
128- // unknown fields. Pass -1 to use [DefaultMaxBodySize].
154+ // unknown fields. If the body exceeds maxSize, [ErrBodyTooLarge]
155+ // is returned. Pass -1 to use [DefaultMaxBodySize].
129156func StrictLimitedJSON [T any ](r * http.Request , maxSize int64 ) (value T , err error ) {
130157 if maxSize < 0 {
131158 maxSize = DefaultMaxBodySize
132159 }
133160
134- limited := io .LimitReader (r .Body , maxSize + 1 )
135- decoder := json .NewDecoder (limited )
161+ data , err := io .ReadAll (io .LimitReader (r .Body , maxSize + 1 ))
162+
163+ if err != nil {
164+ return value , err
165+ }
166+
167+ if int64 (len (data )) > maxSize {
168+ return value , ErrBodyTooLarge
169+ }
170+
171+ decoder := json .NewDecoder (bytes .NewReader (data ))
136172 decoder .DisallowUnknownFields ()
137173
138174 if err := decoder .Decode (& value ); err != nil {
@@ -149,6 +185,11 @@ func StrictLimitedJSON[T any](r *http.Request, maxSize int64) (value T, err erro
149185// WARNING: This function decodes without any body size limit.
150186// Prefer [LimitedXML] or apply [http.MaxBytesReader] in a
151187// middleware to prevent memory exhaustion from oversized requests.
188+ //
189+ // WARNING: Go's [encoding/xml] does not protect against entity
190+ // expansion attacks (e.g. "Billion Laughs"). Callers should
191+ // validate or sanitize XML input before processing, or use
192+ // [LimitedXML] with a small size limit to bound expansion.
152193func XML [T any ](r * http.Request ) (value T , err error ) {
153194 if err := xml .NewDecoder (r .Body ).Decode (& value ); err != nil {
154195 return value , err
@@ -158,17 +199,32 @@ func XML[T any](r *http.Request) (value T, err error) {
158199}
159200
160201// LimitedXML decodes XML data from the request body into a value
161- // of type T, reading at most maxSize bytes. This prevents
202+ // of type T, reading at most maxSize bytes. If the body exceeds
203+ // maxSize, [ErrBodyTooLarge] is returned. This prevents
162204// denial-of-service attacks via oversized XML payloads. Pass -1
163205// to use [DefaultMaxBodySize].
206+ //
207+ // WARNING: Go's [encoding/xml] does not protect against entity
208+ // expansion attacks (e.g. "Billion Laughs"). Even with a small
209+ // maxSize, a crafted XML document may expand to significantly
210+ // more memory than its wire size. Callers should validate or
211+ // sanitize XML input before processing.
164212func LimitedXML [T any ](r * http.Request , maxSize int64 ) (value T , err error ) {
165213 if maxSize < 0 {
166214 maxSize = DefaultMaxBodySize
167215 }
168216
169- limited := io .LimitReader (r .Body , maxSize + 1 )
217+ data , err := io .ReadAll (io .LimitReader (r .Body , maxSize + 1 ))
218+
219+ if err != nil {
220+ return value , err
221+ }
222+
223+ if int64 (len (data )) > maxSize {
224+ return value , ErrBodyTooLarge
225+ }
170226
171- if err := xml .NewDecoder (limited ).Decode (& value ); err != nil {
227+ if err := xml .NewDecoder (bytes . NewReader ( data ) ).Decode (& value ); err != nil {
172228 return value , err
173229 }
174230
0 commit comments