github.com/prebid/prebid-server/v2@v2.18.0/util/jsonutil/jsonutil.go (about)

     1  package jsonutil
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"strings"
     8  	"unsafe"
     9  
    10  	jsoniter "github.com/json-iterator/go"
    11  	"github.com/modern-go/reflect2"
    12  	"github.com/prebid/prebid-server/v2/errortypes"
    13  )
    14  
    15  var comma = byte(',')
    16  var colon = byte(':')
    17  var sqBracket = byte(']')
    18  var closingCurlyBracket = byte('}')
    19  
    20  // Finds element in json byte array with any level of nesting
    21  func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) {
    22  	elementName := elementNames[0]
    23  	buf := bytes.NewBuffer(extension)
    24  	dec := json.NewDecoder(buf)
    25  	found := false
    26  	var startIndex, endIndex int64
    27  	var i interface{}
    28  	for {
    29  		token, err := dec.Token()
    30  		if err == io.EOF {
    31  			// io.EOF is a successful end
    32  			break
    33  		}
    34  		if err != nil {
    35  			return false, -1, -1, err
    36  		}
    37  		if token == elementName {
    38  			err := dec.Decode(&i)
    39  			if err != nil {
    40  				return false, -1, -1, err
    41  			}
    42  			endIndex = dec.InputOffset()
    43  
    44  			if dec.More() {
    45  				//if there were other elements before
    46  				if extension[startIndex] == comma {
    47  					startIndex++
    48  				}
    49  				for {
    50  					//structure has more elements, need to find index of comma
    51  					if extension[endIndex] == comma {
    52  						endIndex++
    53  						break
    54  					}
    55  					endIndex++
    56  				}
    57  			}
    58  			found = true
    59  			break
    60  		} else {
    61  			startIndex = dec.InputOffset()
    62  		}
    63  	}
    64  	if found {
    65  		if len(elementNames) == 1 {
    66  			return found, startIndex, endIndex, nil
    67  		} else if len(elementNames) > 1 {
    68  			for {
    69  				//find the beginning of nested element
    70  				if extension[startIndex] == colon {
    71  					startIndex++
    72  					break
    73  				}
    74  				startIndex++
    75  			}
    76  			for {
    77  				if endIndex == int64(len(extension)) {
    78  					endIndex--
    79  				}
    80  
    81  				//if structure had more elements, need to find index of comma at the end
    82  				if extension[endIndex] == sqBracket || extension[endIndex] == closingCurlyBracket {
    83  					break
    84  				}
    85  
    86  				if extension[endIndex] == comma {
    87  					endIndex--
    88  					break
    89  				} else {
    90  					endIndex--
    91  				}
    92  			}
    93  			if found {
    94  				found, startInd, endInd, err := FindElement(extension[startIndex:endIndex], elementNames[1:]...)
    95  				return found, startIndex + startInd, startIndex + endInd, err
    96  			}
    97  			return found, startIndex, startIndex, nil
    98  		}
    99  	}
   100  	return found, startIndex, endIndex, nil
   101  }
   102  
   103  // Drops element from json byte array
   104  // - Doesn't support drop element from json list
   105  // - Keys in the path can skip levels
   106  // - First found element will be removed
   107  func DropElement(extension []byte, elementNames ...string) ([]byte, error) {
   108  	found, startIndex, endIndex, err := FindElement(extension, elementNames...)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	if found {
   113  		extension = append(extension[:startIndex], extension[endIndex:]...)
   114  	}
   115  	return extension, nil
   116  }
   117  
   118  // jsonConfigValidationOn attempts to maintain compatibility with the standard library which
   119  // includes enabling validation
   120  var jsonConfigValidationOn = jsoniter.ConfigCompatibleWithStandardLibrary
   121  
   122  // jsonConfigValidationOff disables validation
   123  var jsonConfigValidationOff = jsoniter.Config{
   124  	EscapeHTML:             true,
   125  	SortMapKeys:            true,
   126  	ValidateJsonRawMessage: false,
   127  }.Froze()
   128  
   129  // Unmarshal unmarshals a byte slice into the specified data structure without performing
   130  // any validation on the data. An unmarshal error is returned if a non-validation error occurs.
   131  func Unmarshal(data []byte, v interface{}) error {
   132  	err := jsonConfigValidationOff.Unmarshal(data, v)
   133  	if err != nil {
   134  		return &errortypes.FailedToUnmarshal{
   135  			Message: tryExtractErrorMessage(err),
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  // UnmarshalValid validates and unmarshals a byte slice into the specified data structure
   142  // returning an error if validation fails
   143  func UnmarshalValid(data []byte, v interface{}) error {
   144  	if err := jsonConfigValidationOn.Unmarshal(data, v); err != nil {
   145  		return &errortypes.FailedToUnmarshal{
   146  			Message: tryExtractErrorMessage(err),
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  // Marshal marshals a data structure into a byte slice without performing any validation
   153  // on the data. A marshal error is returned if a non-validation error occurs.
   154  func Marshal(v interface{}) ([]byte, error) {
   155  	data, err := jsonConfigValidationOn.Marshal(v)
   156  	if err != nil {
   157  		return nil, &errortypes.FailedToMarshal{
   158  			Message: err.Error(),
   159  		}
   160  	}
   161  	return data, nil
   162  }
   163  
   164  // tryExtractErrorMessage attempts to extract a sane error message from the json-iter package. The errors
   165  // returned from that library are not types and include a lot of extra information we don't want to respond with.
   166  // This is hacky, but it's the only downside to the json-iter library.
   167  func tryExtractErrorMessage(err error) string {
   168  	msg := err.Error()
   169  
   170  	msgEndIndex := strings.LastIndex(msg, ", error found in #")
   171  	if msgEndIndex == -1 {
   172  		return msg
   173  	}
   174  
   175  	msgStartIndex := strings.Index(msg, ": ")
   176  	if msgStartIndex == -1 {
   177  		return msg
   178  	}
   179  
   180  	operationStack := []string{msg[0:msgStartIndex]}
   181  	for {
   182  		msgStartIndexNext := strings.Index(msg[msgStartIndex+2:], ": ")
   183  
   184  		// no more matches
   185  		if msgStartIndexNext == -1 {
   186  			break
   187  		}
   188  
   189  		// matches occur after the end message marker (sanity check)
   190  		if (msgStartIndex + msgStartIndexNext) >= msgEndIndex {
   191  			break
   192  		}
   193  
   194  		// match should not contain a space, indicates operation is really an error message
   195  		match := msg[msgStartIndex+2 : msgStartIndex+2+msgStartIndexNext]
   196  		if strings.Contains(match, " ") {
   197  			break
   198  		}
   199  
   200  		operationStack = append(operationStack, match)
   201  		msgStartIndex += msgStartIndexNext + 2
   202  	}
   203  
   204  	if len(operationStack) > 1 && isLikelyDetailedErrorMessage(msg[msgStartIndex+2:]) {
   205  		return "cannot unmarshal " + operationStack[len(operationStack)-2] + ": " + msg[msgStartIndex+2:msgEndIndex]
   206  	}
   207  
   208  	return msg[msgStartIndex+2 : msgEndIndex]
   209  }
   210  
   211  // isLikelyDetailedErrorMessage checks if the json unmarshal error contains enough information such
   212  // that the caller clearly understands the context, where the structure name is not needed.
   213  func isLikelyDetailedErrorMessage(msg string) bool {
   214  	return !strings.HasPrefix(msg, "request.")
   215  }
   216  
   217  // RawMessageExtension will call json.Compact() on every json.RawMessage field when getting marshalled.
   218  type RawMessageExtension struct {
   219  	jsoniter.DummyExtension
   220  }
   221  
   222  // CreateEncoder substitutes the default jsoniter encoder of the json.RawMessage type with ours, that
   223  // calls json.Compact() before writting to the stream
   224  func (e *RawMessageExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
   225  	if typ == jsonRawMessageType {
   226  		return &rawMessageCodec{}
   227  	}
   228  	return nil
   229  }
   230  
   231  var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()
   232  
   233  // rawMessageCodec implements jsoniter.ValEncoder interface so we can override the default json.RawMessage Encode()
   234  // function with our implementation
   235  type rawMessageCodec struct{}
   236  
   237  func (codec *rawMessageCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
   238  	if ptr != nil {
   239  		jsonRawMsg := *(*[]byte)(ptr)
   240  
   241  		dst := bytes.NewBuffer(make([]byte, 0, len(jsonRawMsg)))
   242  		if err := json.Compact(dst, jsonRawMsg); err == nil {
   243  			stream.Write(dst.Bytes())
   244  		}
   245  	}
   246  }
   247  
   248  func (codec *rawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool {
   249  	return ptr == nil || len(*((*json.RawMessage)(ptr))) == 0
   250  }