...

Source file src/github.com/secure-systems-lab/go-securesystemslib/cjson/canonicaljson.go

Documentation: github.com/secure-systems-lab/go-securesystemslib/cjson

     1  package cjson
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"reflect"
     9  	"sort"
    10  	"strings"
    11  )
    12  
    13  /*
    14  encodeCanonicalString is a helper function to canonicalize the passed string
    15  according to the OLPC canonical JSON specification for strings (see
    16  http://wiki.laptop.org/go/Canonical_JSON).  String canonicalization consists of
    17  escaping backslashes ("\") and double quotes (") and wrapping the resulting
    18  string in double quotes (").
    19  */
    20  func encodeCanonicalString(s string) string {
    21  	// Escape backslashes
    22  	s = strings.ReplaceAll(s, "\\", "\\\\")
    23  	// Escape double quotes
    24  	s = strings.ReplaceAll(s, "\"", "\\\"")
    25  	// Wrap with double quotes
    26  	return fmt.Sprintf("\"%s\"", s)
    27  }
    28  
    29  /*
    30  encodeCanonical is a helper function to recursively canonicalize the passed
    31  object according to the OLPC canonical JSON specification (see
    32  http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed
    33  *bytes.Buffer.  If canonicalization fails it returns an error.
    34  */
    35  func encodeCanonical(obj interface{}, result *strings.Builder) (err error) {
    36  	switch objAsserted := obj.(type) {
    37  	case string:
    38  		result.WriteString(encodeCanonicalString(objAsserted))
    39  
    40  	case bool:
    41  		if objAsserted {
    42  			result.WriteString("true")
    43  		} else {
    44  			result.WriteString("false")
    45  		}
    46  
    47  	// The wrapping `EncodeCanonical` function decodes the passed json data with
    48  	// `decoder.UseNumber` so that any numeric value is stored as `json.Number`
    49  	// (instead of the default `float64`). This allows us to assert that it is a
    50  	// non-floating point number, which are the only numbers allowed by the used
    51  	// canonicalization specification.
    52  	case json.Number:
    53  		if _, err := objAsserted.Int64(); err != nil {
    54  			panic(fmt.Sprintf("Can't canonicalize floating point number '%s'",
    55  				objAsserted))
    56  		}
    57  		result.WriteString(objAsserted.String())
    58  
    59  	case nil:
    60  		result.WriteString("null")
    61  
    62  	// Canonicalize slice
    63  	case []interface{}:
    64  		result.WriteString("[")
    65  		for i, val := range objAsserted {
    66  			if err := encodeCanonical(val, result); err != nil {
    67  				return err
    68  			}
    69  			if i < (len(objAsserted) - 1) {
    70  				result.WriteString(",")
    71  			}
    72  		}
    73  		result.WriteString("]")
    74  
    75  	case map[string]interface{}:
    76  		result.WriteString("{")
    77  
    78  		// Make a list of keys
    79  		var mapKeys []string
    80  		for key := range objAsserted {
    81  			mapKeys = append(mapKeys, key)
    82  		}
    83  		// Sort keys
    84  		sort.Strings(mapKeys)
    85  
    86  		// Canonicalize map
    87  		for i, key := range mapKeys {
    88  			if err := encodeCanonical(key, result); err != nil {
    89  				return err
    90  			}
    91  
    92  			result.WriteString(":")
    93  			if err := encodeCanonical(objAsserted[key], result); err != nil {
    94  				return err
    95  			}
    96  			if i < (len(mapKeys) - 1) {
    97  				result.WriteString(",")
    98  			}
    99  			i++
   100  		}
   101  		result.WriteString("}")
   102  
   103  	default:
   104  		// We recover in a deferred function defined above
   105  		panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'",
   106  			objAsserted, reflect.TypeOf(objAsserted)))
   107  	}
   108  	return nil
   109  }
   110  
   111  /*
   112  EncodeCanonical JSON canonicalizes the passed object and returns it as a byte
   113  slice.  It uses the OLPC canonical JSON specification (see
   114  http://wiki.laptop.org/go/Canonical_JSON).  If canonicalization fails the byte
   115  slice is nil and the second return value contains the error.
   116  */
   117  func EncodeCanonical(obj interface{}) (out []byte, err error) {
   118  	// We use panic if an error occurs and recover in a deferred function,
   119  	// which is always called before returning.
   120  	// There we set the error that is returned eventually.
   121  	defer func() {
   122  		if r := recover(); r != nil {
   123  			err = errors.New(r.(string))
   124  		}
   125  	}()
   126  
   127  	// FIXME: Terrible hack to turn the passed struct into a map, converting
   128  	// the struct's variable names to the json key names defined in the struct
   129  	data, err := json.Marshal(obj)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	var jsonMap interface{}
   134  
   135  	dec := json.NewDecoder(bytes.NewReader(data))
   136  	dec.UseNumber()
   137  	if err := dec.Decode(&jsonMap); err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Create a buffer and write the canonicalized JSON bytes to it
   142  	var result strings.Builder
   143  	// Allocate output result buffer with the input size.
   144  	result.Grow(len(data))
   145  	// Recursively encode the jsonmap
   146  	if err := encodeCanonical(jsonMap, &result); err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	return []byte(result.String()), nil
   151  }
   152  

View as plain text