-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(limits): add size-bounded JSON/YAML/XML decoding helpers #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // Package limits provides size-bounded parsing helpers for common formats. | ||
| package limits |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package limits | ||
|
|
||
| import "github.com/hyp3rd/ewrap" | ||
|
|
||
| var ( | ||
| // ErrInvalidLimitConfig indicates an invalid limits configuration. | ||
| ErrInvalidLimitConfig = ewrap.New("invalid limits config") | ||
| // ErrInvalidLimitInput indicates the input or target is invalid. | ||
| ErrInvalidLimitInput = ewrap.New("invalid limits input") | ||
| // ErrLimitExceeded indicates the input exceeded the configured limit. | ||
| ErrLimitExceeded = ewrap.New("input exceeds limit") | ||
| // ErrReadFailed indicates the input could not be read. | ||
| ErrReadFailed = ewrap.New("input read failed") | ||
| // ErrDecodeFailed indicates the input could not be decoded. | ||
| ErrDecodeFailed = ewrap.New("input decode failed") | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,188 @@ | ||||||
| package limits | ||||||
|
|
||||||
| import ( | ||||||
| "bytes" | ||||||
| "encoding/xml" | ||||||
| "errors" | ||||||
| "fmt" | ||||||
| "io" | ||||||
|
|
||||||
| "gopkg.in/yaml.v3" | ||||||
|
|
||||||
| "github.com/hyp3rd/sectools/pkg/converters" | ||||||
| sectencoding "github.com/hyp3rd/sectools/pkg/encoding" | ||||||
| ) | ||||||
|
|
||||||
| const ( | ||||||
| limitsDefaultMaxBytes = 1 << 20 | ||||||
| ) | ||||||
|
|
||||||
| // Option configures size limits and decoding behavior. | ||||||
| type Option func(*config) error | ||||||
|
|
||||||
| type config struct { | ||||||
| maxBytes int | ||||||
| yamlAllowUnknownFields bool | ||||||
| } | ||||||
|
|
||||||
| // ReadAll reads the entire input, enforcing the configured size limit. | ||||||
| func ReadAll(reader io.Reader, opts ...Option) ([]byte, error) { | ||||||
| cfg, err := resolveConfig(opts) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
||||||
| return readAll(reader, cfg.maxBytes) | ||||||
| } | ||||||
|
|
||||||
| // DecodeJSON decodes JSON with size bounds and strict defaults. | ||||||
| func DecodeJSON(reader io.Reader, value any, opts ...Option) error { | ||||||
| cfg, err := resolveConfig(opts) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| if reader == nil || value == nil { | ||||||
| return ErrInvalidLimitInput | ||||||
| } | ||||||
|
|
||||||
| data, err := readAll(reader, cfg.maxBytes) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| return sectencoding.DecodeJSON(data, value, sectencoding.WithJSONMaxBytes(cfg.maxBytes)) | ||||||
| } | ||||||
|
|
||||||
| // DecodeYAML decodes YAML with size bounds. | ||||||
| // Unknown fields are rejected by default unless WithYAMLAllowUnknownFields(true) is set. | ||||||
| func DecodeYAML(reader io.Reader, value any, opts ...Option) error { | ||||||
| cfg, err := resolveConfig(opts) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| if reader == nil || value == nil { | ||||||
| return ErrInvalidLimitInput | ||||||
| } | ||||||
|
|
||||||
| data, err := readAll(reader, cfg.maxBytes) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| decoder := yaml.NewDecoder(bytes.NewReader(data)) | ||||||
| decoder.KnownFields(!cfg.yamlAllowUnknownFields) | ||||||
|
|
||||||
| err = decoder.Decode(value) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("%w: %w", ErrDecodeFailed, err) | ||||||
| } | ||||||
|
|
||||||
| var extra any | ||||||
|
|
||||||
| err = decoder.Decode(&extra) | ||||||
| if err != nil { | ||||||
| if errors.Is(err, io.EOF) { | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| return fmt.Errorf("%w: %w", ErrDecodeFailed, err) | ||||||
| } | ||||||
|
|
||||||
| return ErrDecodeFailed | ||||||
|
||||||
| return ErrDecodeFailed | |
| return fmt.Errorf("%w: multiple documents detected", ErrDecodeFailed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The call to
sectencoding.DecodeJSONwithWithJSONMaxBytes(cfg.maxBytes)is redundant. The data has already been read with size limits enforced byreadAll(reader, cfg.maxBytes), and the size check will occur again in the encoding package's DecodeJSON. Sincesectencoding.DecodeJSONaccepts a byte slice (not a reader), passing the maxBytes option serves no additional purpose and may cause confusion. Consider removing theWithJSONMaxBytesoption from this call.