snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"foo"
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"some": "key"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"some_key": 1234
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"nested": {
|
||||
"object": {
|
||||
"source": "base64://aGVsbG8gd29ybGQ="
|
||||
},
|
||||
"array": [
|
||||
{
|
||||
"nested": {
|
||||
"source": "base64://aGVsbG8gd29ybGQ="
|
||||
}
|
||||
},
|
||||
"base64://aGVsbG8gd29ybGQ="
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"nested": {
|
||||
"object": {
|
||||
"ignore_this_key": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
},
|
||||
"array": [
|
||||
{
|
||||
"nested": {
|
||||
"source": "base64://aGVsbG8gd29ybGQ="
|
||||
}
|
||||
},
|
||||
"base64://aGVsbG8gd29ybGQ="
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"key": "https://foobar.com",
|
||||
"bar": "base64://YXNkZg=="
|
||||
}
|
||||
76
oryx/jsonx/debug.go
Normal file
76
oryx/jsonx/debug.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Anonymize takes a JSON byte array and anonymizes its content by
|
||||
// recursively replacing all values with a string indicating their type.
|
||||
//
|
||||
// It recurses into nested objects and arrays, but ignores the "schemas" and "id".
|
||||
func Anonymize(data []byte, except ...string) []byte {
|
||||
obj := make(map[string]any)
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
return []byte(fmt.Sprintf(`{"error": "invalid JSON", "message": %q}`, err.Error()))
|
||||
}
|
||||
|
||||
anonymize(obj, except...)
|
||||
|
||||
out, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return []byte(fmt.Sprintf(`{"error": "could not marshal JSON shape", "message": %q}`, err.Error()))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func anonymize(obj map[string]any, except ...string) {
|
||||
for k, v := range obj {
|
||||
if slices.Contains(except, k) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case []any:
|
||||
for elIdx, el := range v {
|
||||
switch el := el.(type) {
|
||||
case map[string]any:
|
||||
anonymize(el)
|
||||
v[elIdx] = el
|
||||
default:
|
||||
v[elIdx] = jsonType(el)
|
||||
}
|
||||
}
|
||||
|
||||
case map[string]any:
|
||||
anonymize(v)
|
||||
obj[k] = v
|
||||
default:
|
||||
obj[k] = jsonType(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jsonType(v any) string {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return "string"
|
||||
case float64:
|
||||
return "number"
|
||||
case bool:
|
||||
return "boolean"
|
||||
case nil:
|
||||
return "null"
|
||||
case []any:
|
||||
return "array"
|
||||
case map[string]any:
|
||||
return "object"
|
||||
default:
|
||||
return fmt.Sprintf("%T", v)
|
||||
}
|
||||
}
|
||||
16
oryx/jsonx/decoder.go
Normal file
16
oryx/jsonx/decoder.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NewStrictDecoder is a shorthand for json.Decoder.DisallowUnknownFields
|
||||
func NewStrictDecoder(b io.Reader) *json.Decoder {
|
||||
d := json.NewDecoder(b)
|
||||
d.DisallowUnknownFields()
|
||||
return d
|
||||
}
|
||||
113
oryx/jsonx/embed.go
Normal file
113
oryx/jsonx/embed.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/ory/x/osx"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
ignoreKeys []string
|
||||
onlySchemes []string
|
||||
}
|
||||
|
||||
type OptionsModifier func(*options)
|
||||
|
||||
func newOptions(o []OptionsModifier) *options {
|
||||
opt := &options{}
|
||||
for _, f := range o {
|
||||
f(opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func WithIgnoreKeys(keys ...string) OptionsModifier {
|
||||
return func(o *options) {
|
||||
o.ignoreKeys = keys
|
||||
}
|
||||
}
|
||||
|
||||
func WithOnlySchemes(scheme ...string) OptionsModifier {
|
||||
return func(o *options) {
|
||||
o.onlySchemes = scheme
|
||||
}
|
||||
}
|
||||
|
||||
func EmbedSources(in json.RawMessage, opts ...OptionsModifier) (out json.RawMessage, err error) {
|
||||
out = make([]byte, len(in))
|
||||
copy(out, in)
|
||||
if err := embed(gjson.ParseBytes(in), nil, &out, newOptions(opts)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func embed(parsed gjson.Result, parents []string, result *json.RawMessage, o *options) (err error) {
|
||||
if parsed.IsObject() {
|
||||
parsed.ForEach(func(k, v gjson.Result) bool {
|
||||
err = embed(v, append(parents, strings.ReplaceAll(k.String(), ".", "\\.")), result, o)
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if parsed.IsArray() {
|
||||
for kk, vv := range parsed.Array() {
|
||||
if err = embed(vv, append(parents, strconv.Itoa(kk)), result, o); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if parsed.Type != gjson.String {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(parents) > 0 && slices.Contains(o.ignoreKeys, parents[len(parents)-1]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
loc, err := url.ParseRequestURI(parsed.String())
|
||||
if err != nil {
|
||||
// Not a URL, return
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(o.onlySchemes) == 0 {
|
||||
if loc.Scheme != "file" && loc.Scheme != "http" && loc.Scheme != "https" && loc.Scheme != "base64" {
|
||||
// Not a known pattern, ignore
|
||||
return nil
|
||||
}
|
||||
} else if !slices.Contains(o.onlySchemes, loc.Scheme) {
|
||||
// Not a known pattern, ignore
|
||||
return nil
|
||||
}
|
||||
|
||||
contents, err := osx.ReadFileFromAllSources(loc.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(contents)
|
||||
key := strings.Join(parents, ".")
|
||||
if key == "" {
|
||||
key = "@"
|
||||
}
|
||||
|
||||
interim, err := sjson.SetBytes(*result, key, "base64://"+encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*result = interim
|
||||
return
|
||||
}
|
||||
1
oryx/jsonx/fixture/embed/1.json
Normal file
1
oryx/jsonx/fixture/embed/1.json
Normal file
@@ -0,0 +1 @@
|
||||
"foo"
|
||||
3
oryx/jsonx/fixture/embed/2.json
Normal file
3
oryx/jsonx/fixture/embed/2.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"some": "key"
|
||||
}
|
||||
3
oryx/jsonx/fixture/embed/3.json
Normal file
3
oryx/jsonx/fixture/embed/3.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"some_key": 1234
|
||||
}
|
||||
15
oryx/jsonx/fixture/embed/4.json
Normal file
15
oryx/jsonx/fixture/embed/4.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"nested": {
|
||||
"object": {
|
||||
"source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
},
|
||||
"array": [
|
||||
{
|
||||
"nested": {
|
||||
"source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
}
|
||||
},
|
||||
"https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
oryx/jsonx/fixture/embed/5.json
Normal file
1
oryx/jsonx/fixture/embed/5.json
Normal file
@@ -0,0 +1 @@
|
||||
"https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
15
oryx/jsonx/fixture/embed/6.json
Normal file
15
oryx/jsonx/fixture/embed/6.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"nested": {
|
||||
"object": {
|
||||
"ignore_this_key": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
},
|
||||
"array": [
|
||||
{
|
||||
"nested": {
|
||||
"source": "https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
}
|
||||
},
|
||||
"https://gist.githubusercontent.com/aeneasr/eb4612d295f613ee44bada6e30e2a856/raw/29edbda41bcb27492a1ac56926e03dee9480708f/hello-world.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
39
oryx/jsonx/flatten.go
Normal file
39
oryx/jsonx/flatten.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Flatten flattens a JSON object using dot notation.
|
||||
func Flatten(raw json.RawMessage) map[string]interface{} {
|
||||
parsed := gjson.ParseBytes(raw)
|
||||
if !parsed.IsObject() {
|
||||
return nil
|
||||
}
|
||||
|
||||
flattened := make(map[string]interface{})
|
||||
flatten(parsed, nil, flattened)
|
||||
return flattened
|
||||
}
|
||||
|
||||
func flatten(parsed gjson.Result, parents []string, flattened map[string]interface{}) {
|
||||
if parsed.IsObject() {
|
||||
parsed.ForEach(func(k, v gjson.Result) bool {
|
||||
flatten(v, append(parents, strings.ReplaceAll(k.String(), ".", "\\.")), flattened)
|
||||
return true
|
||||
})
|
||||
} else if parsed.IsArray() {
|
||||
for kk, vv := range parsed.Array() {
|
||||
flatten(vv, append(parents, strconv.Itoa(kk)), flattened)
|
||||
}
|
||||
} else {
|
||||
flattened[strings.Join(parents, ".")] = parsed.Value()
|
||||
}
|
||||
}
|
||||
77
oryx/jsonx/get.go
Normal file
77
oryx/jsonx/get.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func jsonKey(f reflect.StructField) *string {
|
||||
if jsonTag := f.Tag.Get("json"); jsonTag != "" {
|
||||
if jsonTag == "-" {
|
||||
return nil
|
||||
}
|
||||
return &strings.Split(jsonTag, ",")[0]
|
||||
} else if f.Anonymous {
|
||||
return nil
|
||||
} else if f.IsExported() {
|
||||
return &f.Name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllValidJSONKeys returns all JSON keys from the struct or *struct type.
|
||||
// It does not return keys from nested slices, but embedded/nested structs.
|
||||
func AllValidJSONKeys(s interface{}) (keys []string) {
|
||||
t := reflect.TypeOf(s)
|
||||
v := reflect.ValueOf(s)
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
}
|
||||
for i := range t.NumField() {
|
||||
f := t.Field(i)
|
||||
jKey := jsonKey(f)
|
||||
if k := f.Type.Kind(); k == reflect.Struct || k == reflect.Pointer {
|
||||
subKeys := AllValidJSONKeys(v.Field(i).Interface())
|
||||
for _, subKey := range subKeys {
|
||||
if jKey != nil {
|
||||
keys = append(keys, *jKey+"."+subKey)
|
||||
} else {
|
||||
keys = append(keys, subKey)
|
||||
}
|
||||
}
|
||||
} else if jKey != nil {
|
||||
keys = append(keys, *jKey)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ParseEnsureKeys returns a result that has the GetRequireValidKey function.
|
||||
func ParseEnsureKeys(original interface{}, raw []byte) *Result {
|
||||
return &Result{
|
||||
keys: AllValidJSONKeys(original),
|
||||
result: gjson.ParseBytes(raw),
|
||||
}
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
result gjson.Result
|
||||
keys []string
|
||||
}
|
||||
|
||||
// GetRequireValidKey ensures that the key is valid before returning the result.
|
||||
func (r *Result) GetRequireValidKey(t require.TestingT, key string) gjson.Result {
|
||||
require.Contains(t, r.keys, key)
|
||||
return r.result.Get(key)
|
||||
}
|
||||
|
||||
func GetRequireValidKey(t require.TestingT, original interface{}, raw []byte, key string) gjson.Result {
|
||||
return ParseEnsureKeys(original, raw).GetRequireValidKey(t, key)
|
||||
}
|
||||
17
oryx/jsonx/helpers.go
Normal file
17
oryx/jsonx/helpers.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMarshalJSONString(t *testing.T, i interface{}) string {
|
||||
out, err := json.Marshal(i)
|
||||
require.NoError(t, err)
|
||||
return string(out)
|
||||
}
|
||||
97
oryx/jsonx/patch.go
Normal file
97
oryx/jsonx/patch.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var opAllowList = map[string]struct{}{
|
||||
"add": {},
|
||||
"remove": {},
|
||||
"replace": {},
|
||||
}
|
||||
|
||||
func isUnsupported(op jsonpatch.Operation) bool {
|
||||
_, ok := opAllowList[op.Kind()]
|
||||
|
||||
return !ok
|
||||
}
|
||||
|
||||
func isElementAccess(path string) bool {
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
elements := strings.Split(path, "/")
|
||||
lastElement := elements[len(elements)-1:][0]
|
||||
if lastElement == "-" {
|
||||
return true
|
||||
}
|
||||
if _, err := strconv.Atoi(lastElement); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ApplyJSONPatch applies a JSON patch to an object and returns the modified
|
||||
// object. The original object is not modified. It returns an error if the patch
|
||||
// is invalid or if the patch includes paths that are denied. denyPaths is a
|
||||
// list of path globs (interpreted with [glob.Compile] that are not allowed to
|
||||
// be patched.
|
||||
func ApplyJSONPatch[T any](p json.RawMessage, object T, denyPaths ...string) (result T, err error) {
|
||||
patch, err := jsonpatch.DecodePatch(p)
|
||||
if err != nil {
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
|
||||
denyPattern := fmt.Sprintf("{%s}", strings.ToLower(strings.Join(denyPaths, ",")))
|
||||
matcher, err := glob.Compile(denyPattern, '/')
|
||||
if err != nil {
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, op := range patch {
|
||||
// Some operations are buggy, see https://github.com/evanphx/json-patch/pull/158
|
||||
if isUnsupported(op) {
|
||||
return result, errors.Errorf("unsupported operation: %s", op.Kind())
|
||||
}
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return result, errors.Errorf("error parsing patch operations: %v", err)
|
||||
}
|
||||
if matcher.Match(strings.ToLower(path)) {
|
||||
return result, errors.Errorf("patch includes denied path: %s", path)
|
||||
}
|
||||
|
||||
// JSON patch officially rejects replacing paths that don't exist, but we want to be more tolerant.
|
||||
// Therefore, we will ensure that all paths that we want to replace exist in the original document.
|
||||
if op.Kind() == "replace" && !isElementAccess(path) {
|
||||
op["op"] = new(json.RawMessage(`"add"`))
|
||||
}
|
||||
}
|
||||
|
||||
original, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := jsonpatch.NewApplyOptions()
|
||||
options.EnsurePathExistsOnAdd = true
|
||||
|
||||
modified, err := patch.ApplyWithOptions(original, options)
|
||||
if err != nil {
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(modified, &result)
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
64
oryx/jsonx/stub/random.json
Normal file
64
oryx/jsonx/stub/random.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"floating": [
|
||||
-1273085434,
|
||||
953442581,
|
||||
{
|
||||
"ready": "silent",
|
||||
"worker": "situation",
|
||||
"joy": "difference",
|
||||
"probably": -413625494,
|
||||
"gray": {
|
||||
"parent": "pull",
|
||||
"shore": -738396277,
|
||||
"usually": 1050049449,
|
||||
"hold": [
|
||||
[
|
||||
181518765,
|
||||
[
|
||||
{
|
||||
"steam": {
|
||||
"box": false,
|
||||
"cry": 1463961818,
|
||||
"appropriate": 1249911539,
|
||||
"through": 695239749,
|
||||
"ago": true,
|
||||
"entirely": -851427469
|
||||
},
|
||||
"leather": "across",
|
||||
"flies": -1571371799,
|
||||
"over": 512666854,
|
||||
"thank": true,
|
||||
"shaking": true
|
||||
},
|
||||
"hit",
|
||||
-648178744.4899056,
|
||||
1225027271,
|
||||
-1481507228,
|
||||
true
|
||||
],
|
||||
-2114582277,
|
||||
1390060204.9360588,
|
||||
1615602630.9049141,
|
||||
"darkness"
|
||||
],
|
||||
63427197.713988304,
|
||||
-580344963.961421,
|
||||
"stems",
|
||||
1016960217.612642,
|
||||
1240918909
|
||||
],
|
||||
"buy": true,
|
||||
"wonder": false
|
||||
},
|
||||
"little": "cloud"
|
||||
},
|
||||
"grade",
|
||||
false,
|
||||
"thou"
|
||||
],
|
||||
"wagon": -1722583702,
|
||||
"shop": 1294397217,
|
||||
"spend": "greatest",
|
||||
"product": "whale",
|
||||
"fall": "to"
|
||||
}
|
||||
Reference in New Issue
Block a user