2016-07-07 14:51:49 +00:00
|
|
|
package jsonutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2016-08-05 22:04:30 +00:00
|
|
|
"compress/gzip"
|
2016-07-07 14:51:49 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
2016-08-05 17:34:23 +00:00
|
|
|
"github.com/hashicorp/vault/helper/compressutil"
|
2016-08-04 21:20:37 +00:00
|
|
|
)
|
|
|
|
|
2016-07-07 15:29:38 +00:00
|
|
|
// Encodes/Marshals the given object into JSON
|
2016-07-07 14:51:49 +00:00
|
|
|
func EncodeJSON(in interface{}) ([]byte, error) {
|
2016-08-04 21:20:37 +00:00
|
|
|
if in == nil {
|
|
|
|
return nil, fmt.Errorf("input for encoding is nil")
|
|
|
|
}
|
2016-07-07 14:51:49 +00:00
|
|
|
var buf bytes.Buffer
|
|
|
|
enc := json.NewEncoder(&buf)
|
|
|
|
if err := enc.Encode(in); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
2016-07-07 15:29:38 +00:00
|
|
|
// Decodes/Unmarshals the given JSON into a desired object
|
2016-07-07 14:51:49 +00:00
|
|
|
func DecodeJSON(data []byte, out interface{}) error {
|
2016-07-07 15:29:38 +00:00
|
|
|
if data == nil {
|
|
|
|
return fmt.Errorf("'data' being decoded is nil")
|
|
|
|
}
|
|
|
|
if out == nil {
|
|
|
|
return fmt.Errorf("output parameter 'out' is nil")
|
2016-07-07 14:51:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return DecodeJSONFromReader(bytes.NewReader(data), out)
|
|
|
|
}
|
|
|
|
|
2016-07-07 15:29:38 +00:00
|
|
|
// Decodes/Unmarshals the given io.Reader pointing to a JSON, into a desired object
|
2016-07-07 14:51:49 +00:00
|
|
|
func DecodeJSONFromReader(r io.Reader, out interface{}) error {
|
2016-07-07 15:29:38 +00:00
|
|
|
if r == nil {
|
|
|
|
return fmt.Errorf("'io.Reader' being decoded is nil")
|
|
|
|
}
|
|
|
|
if out == nil {
|
|
|
|
return fmt.Errorf("output parameter 'out' is nil")
|
|
|
|
}
|
|
|
|
|
2016-07-07 14:51:49 +00:00
|
|
|
dec := json.NewDecoder(r)
|
|
|
|
|
2016-07-07 15:29:38 +00:00
|
|
|
// While decoding JSON values, intepret the integer values as `json.Number`s instead of `float64`.
|
2016-07-07 14:51:49 +00:00
|
|
|
dec.UseNumber()
|
|
|
|
|
|
|
|
// Since 'out' is an interface representing a pointer, pass it to the decoder without an '&'
|
|
|
|
return dec.Decode(out)
|
|
|
|
}
|
2016-08-04 21:20:37 +00:00
|
|
|
|
2016-08-05 22:04:30 +00:00
|
|
|
// DecompressAndDecodeJSON tries to decompress the given data. The call to
|
|
|
|
// decompress, fails if the content was not compressed in the first place,
|
|
|
|
// which is identified by a canary byte before the compressed data. If the data
|
|
|
|
// is not compressed, it is JSON decoded directly. Otherwise the decompressed
|
|
|
|
// data will be JSON decoded.
|
|
|
|
func DecompressAndDecodeJSON(dataBytes []byte, out interface{}) error {
|
|
|
|
if dataBytes == nil || len(dataBytes) == 0 {
|
|
|
|
return fmt.Errorf("'dataBytes' being decoded is invalid")
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|
|
|
|
if out == nil {
|
|
|
|
return fmt.Errorf("output parameter 'out' is nil")
|
|
|
|
}
|
|
|
|
|
2016-08-05 22:04:30 +00:00
|
|
|
// Decompress the dataBytes using Gzip format. Decompression when using Gzip
|
|
|
|
// is agnostic of the compression levels used during compression.
|
|
|
|
decompressedBytes, unencrypted, err :=
|
|
|
|
compressutil.Decompress(dataBytes, &compressutil.CompressionConfig{
|
|
|
|
Type: compressutil.CompressionTypeGzip,
|
|
|
|
})
|
2016-08-04 21:20:37 +00:00
|
|
|
if err != nil {
|
2016-08-05 17:34:23 +00:00
|
|
|
return fmt.Errorf("failed to decompress JSON: err: %v", err)
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|
|
|
|
|
2016-08-05 22:04:30 +00:00
|
|
|
// If the dataBytes supplied failed to contain the compression canary, it
|
|
|
|
// can be inferred that it was not compressed in the first place. Try
|
|
|
|
// to decode it.
|
2016-08-05 17:34:23 +00:00
|
|
|
if unencrypted {
|
2016-08-05 22:04:30 +00:00
|
|
|
return DecodeJSON(dataBytes, out)
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|
|
|
|
|
2016-08-05 17:34:23 +00:00
|
|
|
if decompressedBytes == nil || len(decompressedBytes) == 0 {
|
|
|
|
return fmt.Errorf("decompressed data being decoded is invalid")
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|
|
|
|
|
2016-08-05 22:04:30 +00:00
|
|
|
// JSON decode the decompressed data
|
2016-08-05 17:34:23 +00:00
|
|
|
return DecodeJSON(decompressedBytes, out)
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// EncodeJSONAndCompress encodes the given input into JSON and compresses the
|
2016-08-05 22:04:30 +00:00
|
|
|
// encoded value using Gzip format (BestCompression level). A canary byte is
|
|
|
|
// placed at the beginning of the returned bytes for the logic in decompression
|
|
|
|
// method to identify compressed input.
|
2016-08-04 21:20:37 +00:00
|
|
|
func EncodeJSONAndCompress(in interface{}) ([]byte, error) {
|
|
|
|
if in == nil {
|
|
|
|
return nil, fmt.Errorf("input for encoding is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
// First JSON encode the given input
|
|
|
|
encodedBytes, err := EncodeJSON(in)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-05 22:04:30 +00:00
|
|
|
// For compression, use Gzip format with 'BestCompression' level.
|
|
|
|
return compressutil.Compress(encodedBytes, &compressutil.CompressionConfig{
|
|
|
|
Type: compressutil.CompressionTypeGzip,
|
|
|
|
GzipCompressionLevel: gzip.BestCompression,
|
|
|
|
})
|
2016-08-04 21:20:37 +00:00
|
|
|
}
|