github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/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  	"io"
    23  )
    24  
    25  var (
    26  	emptyMeta  = []byte(`:{}`)
    27  	metaPrefix = []byte(`{"metadata"`)
    28  	metaSuffix = []byte(`}`)
    29  )
    30  
    31  var (
    32  	// Unmarshal is an alias for json.Unmarshal
    33  	Unmarshal = json.Unmarshal
    34  
    35  	//Marshal is an alias for json.Marshal
    36  	Marshal = json.Marshal
    37  )
    38  
    39  // Decode will parse the json byte array to the target object. When
    40  // unknown fields are _not_ allowed we still accept unknown
    41  // fields in the Object's metadata
    42  //
    43  // See https://github.com/knative/serving/issues/11448 for details
    44  func Decode(bites []byte, target interface{}, disallowUnknownFields bool) error {
    45  	if !disallowUnknownFields {
    46  		return json.Unmarshal(bites, target)
    47  	}
    48  
    49  	// If we don't allow unknown fields we skip validating fields in the metadata
    50  	// block since that is opaque to us and validated by the API server
    51  	start, end, err := findMetadataOffsets(bites)
    52  	if err != nil {
    53  		return err
    54  	} else if start == -1 || end == -1 {
    55  		// If for some reason the json does not have metadata continue with normal parsing
    56  		dec := json.NewDecoder(bytes.NewReader(bites))
    57  		dec.DisallowUnknownFields()
    58  		return dec.Decode(target)
    59  	}
    60  
    61  	before := bites[:start]
    62  	metadata := bites[start:end]
    63  	after := bites[end:]
    64  
    65  	// Parse everything but skip metadata
    66  	dec := json.NewDecoder(io.MultiReader(
    67  		bytes.NewReader(before),
    68  		bytes.NewReader(emptyMeta),
    69  		bytes.NewReader(after),
    70  	))
    71  
    72  	dec.DisallowUnknownFields()
    73  	if err := dec.Decode(target); err != nil {
    74  		return err
    75  	}
    76  
    77  	// Now we parse just the metadata
    78  	dec = json.NewDecoder(io.MultiReader(
    79  		bytes.NewReader(metaPrefix),
    80  		bytes.NewReader(metadata),
    81  		bytes.NewReader(metaSuffix),
    82  	))
    83  
    84  	if err := dec.Decode(target); err != nil {
    85  		return err
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  func findMetadataOffsets(bites []byte) (start, end int64, err error) {
    92  	start, end = -1, -1
    93  	level := 0
    94  
    95  	var (
    96  		dec = json.NewDecoder(bytes.NewReader(bites))
    97  		t   json.Token
    98  	)
    99  
   100  	for {
   101  		t, err = dec.Token()
   102  		if err == io.EOF { //nolint
   103  			break
   104  		}
   105  		if err != nil {
   106  			return
   107  		}
   108  
   109  		switch v := t.(type) {
   110  		case json.Delim:
   111  			if v == '{' {
   112  				level++
   113  			} else if v == '}' {
   114  				level--
   115  			}
   116  		case string:
   117  			if v == "metadata" && level == 1 {
   118  				start = dec.InputOffset()
   119  				x := struct{}{}
   120  				if err = dec.Decode(&x); err != nil {
   121  					return -1, -1, err
   122  				}
   123  				end = dec.InputOffset()
   124  
   125  				// we exit early to stop processing the rest of the object
   126  				return
   127  			}
   128  		}
   129  	}
   130  	return -1, -1, nil
   131  }