<commit_before>/* Copyright 2016-2017 Vector Creations Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package gomatrixserverlib

import (
	"encoding/binary"
	"errors"
	"sort"
	"strings"
	"unicode/utf8"

	"github.com/tidwall/gjson"
)

// CanonicalJSON re-encodes the JSON in a canonical encoding. The encoding is
// the shortest possible encoding using integer values with sorted object keys.
// At present this function performs:
// * shortest encoding, sorted lexicographically by UTF-8 codepoint:
//   https://matrix.org/docs/spec/appendices#canonical-json
// Returns a gomatrixserverlib.BadJSONError if JSON validation fails.
func CanonicalJSON(input []byte) ([]byte, error) {
	if !gjson.Valid(string(input)) {
		return nil, BadJSONError{errors.New("gjson validation failed")}
	}

	return CanonicalJSONAssumeValid(input), nil
}

// Returns a gomatrixserverlib.BadJSONError if the canonical JSON fails enforced
// checks or if JSON validation fails. At present this function performs:
// * integer bounds checking for room version 6 and above:
//   https://matrix.org/docs/spec/rooms/v6#canonical-json
// * shortest encoding, sorted lexicographically by UTF-8 codepoint:
//   https://matrix.org/docs/spec/appendices#canonical-json
// Returns a gomatrixserverlib.BadJSONError if JSON validation fails.
func EnforcedCanonicalJSON(input []byte, roomVersion RoomVersion) ([]byte, error) {
	if enforce, err := roomVersion.EnforceCanonicalJSON(); err == nil && enforce {
		if err = verifyEnforcedCanonicalJSON(input); err != nil {
			return nil, BadJSONError{err}
		}
	}

	return CanonicalJSON(input)
}

func verifyEnforcedCanonicalJSON(input []byte) error {
	valid := true
	res := gjson.ParseBytes(input)
	var iter func(key, value gjson.Result) bool
	iter = func(_, value gjson.Result) bool {
		if value.IsArray() || value.IsObject() {
			value.ForEach(iter)
			return true
		}
		if value.Num < -9007199254740991 || value.Num > 9007199254740991 {
			valid = false
			return false
		}
		if value.Num != 0 && strings.ContainsRune(value.Raw, '.') {
			valid = false
			return false
		}
		return true
	}
	res.ForEach(iter)
	if !valid {
		return errors.New("value is outside of safe range")
	}
	return nil
}

// CanonicalJSONAssumeValid is the same as CanonicalJSON, but assumes the
// input is valid JSON
func CanonicalJSONAssumeValid(input []byte) []byte {
	input = CompactJSON(input, make([]byte, 0, len(input)))
	return SortJSON(input, make([]byte, 0, len(input)))
}

// SortJSON reencodes the JSON with the object keys sorted by lexicographically
// by codepoint. The input must be valid JSON.<commit_msg>Make canonical json error public (#239)<commit_after>/* Copyright 2016-2017 Vector Creations Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package gomatrixserverlib

import (
	"encoding/binary"
	"errors"
	"sort"
	"strings"
	"unicode/utf8"

	"github.com/tidwall/gjson"
)

// CanonicalJSON re-encodes the JSON in a canonical encoding. The encoding is
// the shortest possible encoding using integer values with sorted object keys.
// At present this function performs:
// * shortest encoding, sorted lexicographically by UTF-8 codepoint:
//   https://matrix.org/docs/spec/appendices#canonical-json
// Returns a gomatrixserverlib.BadJSONError if JSON validation fails.
func CanonicalJSON(input []byte) ([]byte, error) {
	if !gjson.Valid(string(input)) {
		return nil, BadJSONError{errors.New("gjson validation failed")}
	}

	return CanonicalJSONAssumeValid(input), nil
}

// Returns a gomatrixserverlib.BadJSONError if the canonical JSON fails enforced
// checks or if JSON validation fails. At present this function performs:
// * integer bounds checking for room version 6 and above:
//   https://matrix.org/docs/spec/rooms/v6#canonical-json
// * shortest encoding, sorted lexicographically by UTF-8 codepoint:
//   https://matrix.org/docs/spec/appendices#canonical-json
// Returns a gomatrixserverlib.BadJSONError if JSON validation fails.
func EnforcedCanonicalJSON(input []byte, roomVersion RoomVersion) ([]byte, error) {
	if enforce, err := roomVersion.EnforceCanonicalJSON(); err == nil && enforce {
		if err = verifyEnforcedCanonicalJSON(input); err != nil {
			return nil, BadJSONError{err}
		}
	}

	return CanonicalJSON(input)
}

var ErrCanonicalJSON = errors.New("value is outside of safe range")

func verifyEnforcedCanonicalJSON(input []byte) error {
	valid := true
	res := gjson.ParseBytes(input)
	var iter func(key, value gjson.Result) bool
	iter = func(_, value gjson.Result) bool {
		if value.IsArray() || value.IsObject() {
			value.ForEach(iter)
			return true
		}
		if value.Num < -9007199254740991 || value.Num > 9007199254740991 {
			valid = false
			return false
		}
		if value.Num != 0 && strings.ContainsRune(value.Raw, '.') {
			valid = false
			return false
		}
		return true
	}
	res.ForEach(iter)
	if !valid {
		return ErrCanonicalJSON
	}
	return nil
}

// CanonicalJSONAssumeValid is the same as CanonicalJSON, but assumes the
// input is valid JSON
func CanonicalJSONAssumeValid(input []byte) []byte {
	input = CompactJSON(input, make([]byte, 0, len(input)))
	return SortJSON(input, make([]byte, 0, len(input)))
}

// SortJSON reencodes the JSON with the object keys sorted by lexicographically
// by codepoint. The input must be valid JSON.
{'fraction_non_alphanumeric': 0.10267786404690224, 'fraction_numerical': 0.01695452384725083, 'mean_word_length': 4.186524239934265, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3117310', 'n_tokens_mistral': 2000, 'n_tokens_neox': 1845, 'n_words': 649}