decodeJSONWithLimit is the size-configurable variant. Use this for endpoints that accept large payloads (e.g. bulk-import) where the default cap is too small — but always pass an explicit cap, never remove the wrapper.
(r *http.Request, v interface{}, maxBytes int64)
| 2192 | // default cap is too small — but always pass an explicit cap, never |
| 2193 | // remove the wrapper. |
| 2194 | func decodeJSONWithLimit(r *http.Request, v interface{}, maxBytes int64) error { |
| 2195 | raw, err := readBodyForDecode(r, maxBytes) |
| 2196 | if err != nil { |
| 2197 | return fmt.Errorf("invalid JSON: %w", err) |
| 2198 | } |
| 2199 | // An EMPTY (or whitespace-only) body must keep returning a wrapped |
| 2200 | // io.EOF. json.Decoder.Decode answered io.EOF there and at least one |
| 2201 | // caller depends on it — handlers_playbooks.go treats |
| 2202 | // errors.Is(err, io.EOF) as "no arguments supplied" and runs anyway — |
| 2203 | // while json.Unmarshal answers a SyntaxError instead, which that check |
| 2204 | // cannot see. Found by TestPlaybookRunAcceptsEmptyBody, which is exactly |
| 2205 | // the wiring a helper-level change is blind to. |
| 2206 | // Trim only the four bytes JSON itself calls whitespace. bytes.TrimSpace |
| 2207 | // uses unicode.IsSpace, which also strips \v, \f, U+00A0 and friends — |
| 2208 | // none of which encoding/json accepts. With TrimSpace a body of just |
| 2209 | // "\v" looked EMPTY here and returned io.EOF, so an EOF-tolerant caller |
| 2210 | // (playbook run, share links) treated a syntactically invalid body as an |
| 2211 | // ABSENT one and proceeded. Same Go-versus-spec whitespace divergence |
| 2212 | // that bites when a Go trim stands in for another grammar's definition |
| 2213 | // (codex round 22). |
| 2214 | if len(bytes.Trim(raw, " \t\r\n")) == 0 { |
| 2215 | return fmt.Errorf("invalid JSON: %w", io.EOF) |
| 2216 | } |
| 2217 | // Refuse a decoded NUL BEFORE unmarshalling, so the value never exists |
| 2218 | // in a Go string that a handler could hand to the store. See |
| 2219 | // bodyDecodesNUL for why the body needs its own rule and why the check |
| 2220 | // cannot be a substring search. BUG-2803. |
| 2221 | if bodyDecodesNUL(raw) { |
| 2222 | return errJSONBodyNUL |
| 2223 | } |
| 2224 | // json.Unmarshal rather than a Decoder over the buffer: it is the |
| 2225 | // cheaper of the two by ~2x in total allocation (see readBodyForDecode's |
| 2226 | // measurement), and it REFUSES trailing non-whitespace after the JSON |
| 2227 | // value where Decode silently ignores it. That second difference is a |
| 2228 | // deliberate behaviour change in the same direction as this fix — |
| 2229 | // malformed input is refused at the door rather than partly consumed — |
| 2230 | // and it is the only compatibility change in BUG-2803. Trailing |
| 2231 | // whitespace, which real clients do send, is still accepted. |
| 2232 | if err := json.Unmarshal(raw, v); err != nil { |
| 2233 | return fmt.Errorf("invalid JSON: %w", err) |
| 2234 | } |
| 2235 | return nil |
| 2236 | } |
| 2237 | |
| 2238 | // errJSONBodyNUL is returned by decodeJSON when a string in the request body |
| 2239 | // decodes to a value containing a NUL. Every decodeJSON caller already turns |