knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/json/decode.go (about)

     1  /*
     2  Copyright 2021 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package json
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"io"
    24  )
    25  
    26  var (
    27  	emptyMeta  = []byte(`:{}`)
    28  	metaPrefix = []byte(`{"metadata"`)
    29  	metaSuffix = []byte(`}`)
    30  )
    31  
    32  var (
    33  	// Unmarshal is an alias for json.Unmarshal
    34  	Unmarshal = json.Unmarshal
    35  
    36  	// Marshal is an alias for json.Marshal
    37  	Marshal = json.Marshal
    38  )
    39  
    40  // Decode will parse the json byte array to the target object. When
    41  // unknown fields are _not_ allowed we still accept unknown
    42  // fields in the Object's metadata
    43  //
    44  // See https://github.com/knative/serving/issues/11448 for details
    45  func Decode(bites []byte, target interface{}, disallowUnknownFields bool) error {
    46  	if !disallowUnknownFields {
    47  		return json.Unmarshal(bites, target)
    48  	}
    49  
    50  	// If we don't allow unknown fields we skip validating fields in the metadata
    51  	// block since that is opaque to us and validated by the API server
    52  	start, end, err := findMetadataOffsets(bites)
    53  	if err != nil {
    54  		return err
    55  	} else if start == -1 || end == -1 {
    56  		// If for some reason the json does not have metadata continue with normal parsing
    57  		dec := json.NewDecoder(bytes.NewReader(bites))
    58  		dec.DisallowUnknownFields()
    59  		return dec.Decode(target)
    60  	}
    61  
    62  	before := bites[:start]
    63  	metadata := bites[start:end]
    64  	after := bites[end:]
    65  
    66  	// Parse everything but skip metadata
    67  	dec := json.NewDecoder(io.MultiReader(
    68  		bytes.NewReader(before),
    69  		bytes.NewReader(emptyMeta),
    70  		bytes.NewReader(after),
    71  	))
    72  
    73  	dec.DisallowUnknownFields()
    74  	if err := dec.Decode(target); err != nil {
    75  		return err
    76  	}
    77  
    78  	// Now we parse just the metadata
    79  	dec = json.NewDecoder(io.MultiReader(
    80  		bytes.NewReader(metaPrefix),
    81  		bytes.NewReader(metadata),
    82  		bytes.NewReader(metaSuffix),
    83  	))
    84  
    85  	return dec.Decode(target)
    86  }
    87  
    88  func findMetadataOffsets(bites []byte) (start, end int64, err error) {
    89  	start, end = -1, -1
    90  	level := 0
    91  
    92  	var (
    93  		dec = json.NewDecoder(bytes.NewReader(bites))
    94  		t   json.Token
    95  	)
    96  
    97  	for {
    98  		t, err = dec.Token()
    99  		if errors.Is(err, io.EOF) {
   100  			break
   101  		}
   102  		if err != nil {
   103  			return start, end, err
   104  		}
   105  
   106  		switch v := t.(type) {
   107  		case json.Delim:
   108  			switch v {
   109  			case '{':
   110  				level++
   111  			case '}':
   112  				level--
   113  			}
   114  		case string:
   115  			if v == "metadata" && level == 1 {
   116  				start = dec.InputOffset()
   117  				x := struct{}{}
   118  				if err = dec.Decode(&x); err != nil {
   119  					return -1, -1, err
   120  				}
   121  				end = dec.InputOffset()
   122  
   123  				// we exit early to stop processing the rest of the object
   124  				return start, end, err
   125  			}
   126  		}
   127  	}
   128  	return -1, -1, nil
   129  }