Add check for json duplicate fields

This commit is contained in:
Moss 2020-02-12 11:56:18 +01:00
parent 0d212b09d0
commit a6d90babbf
1 changed files with 77 additions and 9 deletions

View File

@ -327,17 +327,12 @@ func (r *rawTemplate) parsePostProcessor(
// Parse takes the given io.Reader and parses a Template object out of it. // Parse takes the given io.Reader and parses a Template object out of it.
func Parse(r io.Reader) (*Template, error) { func Parse(r io.Reader) (*Template, error) {
// Create a buffer to copy what we read // First, decode the object into an interface{} and search for duplicate fields.
var buf bytes.Buffer // We do this instead of the rawTemplate directly because we'd rather use mapstructure to
if _, err := buf.ReadFrom(r); err != nil {
return nil, err
}
// First, decode the object into an interface{}. We do this instead of
// the rawTemplate directly because we'd rather use mapstructure to
// decode since it has richer errors. // decode since it has richer errors.
var raw interface{} var raw interface{}
if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { buf, err := jsonUnmarshal(r, &raw)
if err != nil {
return nil, err return nil, err
} }
@ -394,6 +389,79 @@ func Parse(r io.Reader) (*Template, error) {
return rawTpl.Template() return rawTpl.Template()
} }
func jsonUnmarshal(r io.Reader, raw *interface{}) (bytes.Buffer, error) {
// Create a buffer to copy what we read
var buf bytes.Buffer
if _, err := buf.ReadFrom(r); err != nil {
return buf, err
}
// Decode the object into an interface{}
if err := json.Unmarshal(buf.Bytes(), raw); err != nil {
return buf, err
}
// If Json is valid, check for duplicate fields to avoid silent unwanted override
jsonDecoder := json.NewDecoder(strings.NewReader(buf.String()))
if err := checkForDuplicateFields(jsonDecoder); err != nil {
return buf, err
}
return buf, nil
}
func checkForDuplicateFields(d *json.Decoder) error {
// Get next token from JSON
t, err := d.Token()
if err != nil {
return err
}
delim, ok := t.(json.Delim)
// Do nothing if it's not a delimiter
if !ok {
return nil
}
// Check for duplicates inside of a delimiter {} or []
switch delim {
case '{':
keys := make(map[string]bool)
for d.More() {
// Get attribute key
t, err := d.Token()
if err != nil {
return err
}
key := t.(string)
// Check for duplicates
if keys[key] {
return fmt.Errorf("template has duplicate field: %s", key)
}
keys[key] = true
// Check value to find duplicates in nested blocks
if err := checkForDuplicateFields(d); err != nil {
return err
}
}
case '[':
for d.More() {
if err := checkForDuplicateFields(d); err != nil {
return err
}
}
}
// consume closing delimiter } or ]
if _, err := d.Token(); err != nil {
return err
}
return nil
}
// ParseFile is the same as Parse but is a helper to automatically open // ParseFile is the same as Parse but is a helper to automatically open
// a file for parsing. // a file for parsing.
func ParseFile(path string) (*Template, error) { func ParseFile(path string) (*Template, error) {