mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-24 20:56:39 +00:00
493 lines
12 KiB
Go
493 lines
12 KiB
Go
|
// Copyright (C) MongoDB, Inc. 2017-present.
|
||
|
//
|
||
|
// 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
|
||
|
|
||
|
package bsonrw
|
||
|
|
||
|
import (
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
|
||
|
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
|
)
|
||
|
|
||
|
func wrapperKeyBSONType(key string) bsontype.Type {
|
||
|
switch key {
|
||
|
case "$numberInt":
|
||
|
return bsontype.Int32
|
||
|
case "$numberLong":
|
||
|
return bsontype.Int64
|
||
|
case "$oid":
|
||
|
return bsontype.ObjectID
|
||
|
case "$symbol":
|
||
|
return bsontype.Symbol
|
||
|
case "$numberDouble":
|
||
|
return bsontype.Double
|
||
|
case "$numberDecimal":
|
||
|
return bsontype.Decimal128
|
||
|
case "$binary":
|
||
|
return bsontype.Binary
|
||
|
case "$code":
|
||
|
return bsontype.JavaScript
|
||
|
case "$scope":
|
||
|
return bsontype.CodeWithScope
|
||
|
case "$timestamp":
|
||
|
return bsontype.Timestamp
|
||
|
case "$regularExpression":
|
||
|
return bsontype.Regex
|
||
|
case "$dbPointer":
|
||
|
return bsontype.DBPointer
|
||
|
case "$date":
|
||
|
return bsontype.DateTime
|
||
|
case "$minKey":
|
||
|
return bsontype.MinKey
|
||
|
case "$maxKey":
|
||
|
return bsontype.MaxKey
|
||
|
case "$undefined":
|
||
|
return bsontype.Undefined
|
||
|
}
|
||
|
|
||
|
return bsontype.EmbeddedDocument
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseBinary() (b []byte, subType byte, err error) {
|
||
|
if ejv.t != bsontype.EmbeddedDocument {
|
||
|
return nil, 0, fmt.Errorf("$binary value should be object, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
binObj := ejv.v.(*extJSONObject)
|
||
|
bFound := false
|
||
|
stFound := false
|
||
|
|
||
|
for i, key := range binObj.keys {
|
||
|
val := binObj.values[i]
|
||
|
|
||
|
switch key {
|
||
|
case "base64":
|
||
|
if bFound {
|
||
|
return nil, 0, errors.New("duplicate base64 key in $binary")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return nil, 0, fmt.Errorf("$binary base64 value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
base64Bytes, err := base64.StdEncoding.DecodeString(val.v.(string))
|
||
|
if err != nil {
|
||
|
return nil, 0, fmt.Errorf("invalid $binary base64 string: %s", val.v.(string))
|
||
|
}
|
||
|
|
||
|
b = base64Bytes
|
||
|
bFound = true
|
||
|
case "subType":
|
||
|
if stFound {
|
||
|
return nil, 0, errors.New("duplicate subType key in $binary")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return nil, 0, fmt.Errorf("$binary subType value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
i, err := strconv.ParseInt(val.v.(string), 16, 64)
|
||
|
if err != nil {
|
||
|
return nil, 0, fmt.Errorf("invalid $binary subType string: %s", val.v.(string))
|
||
|
}
|
||
|
|
||
|
subType = byte(i)
|
||
|
stFound = true
|
||
|
default:
|
||
|
return nil, 0, fmt.Errorf("invalid key in $binary object: %s", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !bFound {
|
||
|
return nil, 0, errors.New("missing base64 field in $binary object")
|
||
|
}
|
||
|
|
||
|
if !stFound {
|
||
|
return nil, 0, errors.New("missing subType field in $binary object")
|
||
|
|
||
|
}
|
||
|
|
||
|
return b, subType, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseDBPointer() (ns string, oid primitive.ObjectID, err error) {
|
||
|
if ejv.t != bsontype.EmbeddedDocument {
|
||
|
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer value should be object, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
dbpObj := ejv.v.(*extJSONObject)
|
||
|
oidFound := false
|
||
|
nsFound := false
|
||
|
|
||
|
for i, key := range dbpObj.keys {
|
||
|
val := dbpObj.values[i]
|
||
|
|
||
|
switch key {
|
||
|
case "$ref":
|
||
|
if nsFound {
|
||
|
return "", primitive.NilObjectID, errors.New("duplicate $ref key in $dbPointer")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $ref value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
ns = val.v.(string)
|
||
|
nsFound = true
|
||
|
case "$id":
|
||
|
if oidFound {
|
||
|
return "", primitive.NilObjectID, errors.New("duplicate $id key in $dbPointer")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $id value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
oid, err = primitive.ObjectIDFromHex(val.v.(string))
|
||
|
if err != nil {
|
||
|
return "", primitive.NilObjectID, err
|
||
|
}
|
||
|
|
||
|
oidFound = true
|
||
|
default:
|
||
|
return "", primitive.NilObjectID, fmt.Errorf("invalid key in $dbPointer object: %s", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !nsFound {
|
||
|
return "", oid, errors.New("missing $ref field in $dbPointer object")
|
||
|
}
|
||
|
|
||
|
if !oidFound {
|
||
|
return "", oid, errors.New("missing $id field in $dbPointer object")
|
||
|
}
|
||
|
|
||
|
return ns, oid, nil
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
rfc3339Milli = "2006-01-02T15:04:05.999Z07:00"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
timeFormats = []string{rfc3339Milli, "2006-01-02T15:04:05.999Z0700"}
|
||
|
)
|
||
|
|
||
|
func (ejv *extJSONValue) parseDateTime() (int64, error) {
|
||
|
switch ejv.t {
|
||
|
case bsontype.Int32:
|
||
|
return int64(ejv.v.(int32)), nil
|
||
|
case bsontype.Int64:
|
||
|
return ejv.v.(int64), nil
|
||
|
case bsontype.String:
|
||
|
return parseDatetimeString(ejv.v.(string))
|
||
|
case bsontype.EmbeddedDocument:
|
||
|
return parseDatetimeObject(ejv.v.(*extJSONObject))
|
||
|
default:
|
||
|
return 0, fmt.Errorf("$date value should be string or object, but instead is %s", ejv.t)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func parseDatetimeString(data string) (int64, error) {
|
||
|
var t time.Time
|
||
|
var err error
|
||
|
// try acceptable time formats until one matches
|
||
|
for _, format := range timeFormats {
|
||
|
t, err = time.Parse(format, data)
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("invalid $date value string: %s", data)
|
||
|
}
|
||
|
|
||
|
return int64(primitive.NewDateTimeFromTime(t)), nil
|
||
|
}
|
||
|
|
||
|
func parseDatetimeObject(data *extJSONObject) (d int64, err error) {
|
||
|
dFound := false
|
||
|
|
||
|
for i, key := range data.keys {
|
||
|
val := data.values[i]
|
||
|
|
||
|
switch key {
|
||
|
case "$numberLong":
|
||
|
if dFound {
|
||
|
return 0, errors.New("duplicate $numberLong key in $date")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return 0, fmt.Errorf("$date $numberLong field should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
d, err = val.parseInt64()
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
dFound = true
|
||
|
default:
|
||
|
return 0, fmt.Errorf("invalid key in $date object: %s", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !dFound {
|
||
|
return 0, errors.New("missing $numberLong field in $date object")
|
||
|
}
|
||
|
|
||
|
return d, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseDecimal128() (primitive.Decimal128, error) {
|
||
|
if ejv.t != bsontype.String {
|
||
|
return primitive.Decimal128{}, fmt.Errorf("$numberDecimal value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
d, err := primitive.ParseDecimal128(ejv.v.(string))
|
||
|
if err != nil {
|
||
|
return primitive.Decimal128{}, fmt.Errorf("$invalid $numberDecimal string: %s", ejv.v.(string))
|
||
|
}
|
||
|
|
||
|
return d, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseDouble() (float64, error) {
|
||
|
if ejv.t == bsontype.Double {
|
||
|
return ejv.v.(float64), nil
|
||
|
}
|
||
|
|
||
|
if ejv.t != bsontype.String {
|
||
|
return 0, fmt.Errorf("$numberDouble value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
switch ejv.v.(string) {
|
||
|
case "Infinity":
|
||
|
return math.Inf(1), nil
|
||
|
case "-Infinity":
|
||
|
return math.Inf(-1), nil
|
||
|
case "NaN":
|
||
|
return math.NaN(), nil
|
||
|
}
|
||
|
|
||
|
f, err := strconv.ParseFloat(ejv.v.(string), 64)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
return f, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseInt32() (int32, error) {
|
||
|
if ejv.t == bsontype.Int32 {
|
||
|
return ejv.v.(int32), nil
|
||
|
}
|
||
|
|
||
|
if ejv.t != bsontype.String {
|
||
|
return 0, fmt.Errorf("$numberInt value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
if i < math.MinInt32 || i > math.MaxInt32 {
|
||
|
return 0, fmt.Errorf("$numberInt value should be int32 but instead is int64: %d", i)
|
||
|
}
|
||
|
|
||
|
return int32(i), nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseInt64() (int64, error) {
|
||
|
if ejv.t == bsontype.Int64 {
|
||
|
return ejv.v.(int64), nil
|
||
|
}
|
||
|
|
||
|
if ejv.t != bsontype.String {
|
||
|
return 0, fmt.Errorf("$numberLong value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
return i, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseJavascript() (code string, err error) {
|
||
|
if ejv.t != bsontype.String {
|
||
|
return "", fmt.Errorf("$code value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
return ejv.v.(string), nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseMinMaxKey(minmax string) error {
|
||
|
if ejv.t != bsontype.Int32 {
|
||
|
return fmt.Errorf("$%sKey value should be int32, but instead is %s", minmax, ejv.t)
|
||
|
}
|
||
|
|
||
|
if ejv.v.(int32) != 1 {
|
||
|
return fmt.Errorf("$%sKey value must be 1, but instead is %d", minmax, ejv.v.(int32))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseObjectID() (primitive.ObjectID, error) {
|
||
|
if ejv.t != bsontype.String {
|
||
|
return primitive.NilObjectID, fmt.Errorf("$oid value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
return primitive.ObjectIDFromHex(ejv.v.(string))
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseRegex() (pattern, options string, err error) {
|
||
|
if ejv.t != bsontype.EmbeddedDocument {
|
||
|
return "", "", fmt.Errorf("$regularExpression value should be object, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
regexObj := ejv.v.(*extJSONObject)
|
||
|
patFound := false
|
||
|
optFound := false
|
||
|
|
||
|
for i, key := range regexObj.keys {
|
||
|
val := regexObj.values[i]
|
||
|
|
||
|
switch key {
|
||
|
case "pattern":
|
||
|
if patFound {
|
||
|
return "", "", errors.New("duplicate pattern key in $regularExpression")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return "", "", fmt.Errorf("$regularExpression pattern value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
pattern = val.v.(string)
|
||
|
patFound = true
|
||
|
case "options":
|
||
|
if optFound {
|
||
|
return "", "", errors.New("duplicate options key in $regularExpression")
|
||
|
}
|
||
|
|
||
|
if val.t != bsontype.String {
|
||
|
return "", "", fmt.Errorf("$regularExpression options value should be string, but instead is %s", val.t)
|
||
|
}
|
||
|
|
||
|
options = val.v.(string)
|
||
|
optFound = true
|
||
|
default:
|
||
|
return "", "", fmt.Errorf("invalid key in $regularExpression object: %s", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !patFound {
|
||
|
return "", "", errors.New("missing pattern field in $regularExpression object")
|
||
|
}
|
||
|
|
||
|
if !optFound {
|
||
|
return "", "", errors.New("missing options field in $regularExpression object")
|
||
|
|
||
|
}
|
||
|
|
||
|
return pattern, options, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseSymbol() (string, error) {
|
||
|
if ejv.t != bsontype.String {
|
||
|
return "", fmt.Errorf("$symbol value should be string, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
return ejv.v.(string), nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseTimestamp() (t, i uint32, err error) {
|
||
|
if ejv.t != bsontype.EmbeddedDocument {
|
||
|
return 0, 0, fmt.Errorf("$timestamp value should be object, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
handleKey := func(key string, val *extJSONValue, flag bool) (uint32, error) {
|
||
|
if flag {
|
||
|
return 0, fmt.Errorf("duplicate %s key in $timestamp", key)
|
||
|
}
|
||
|
|
||
|
switch val.t {
|
||
|
case bsontype.Int32:
|
||
|
value := val.v.(int32)
|
||
|
|
||
|
if value < 0 {
|
||
|
return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
|
||
|
}
|
||
|
|
||
|
return uint32(value), nil
|
||
|
case bsontype.Int64:
|
||
|
value := val.v.(int64)
|
||
|
if value < 0 || value > int64(math.MaxUint32) {
|
||
|
return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
|
||
|
}
|
||
|
|
||
|
return uint32(value), nil
|
||
|
default:
|
||
|
return 0, fmt.Errorf("$timestamp %s value should be uint32, but instead is %s", key, val.t)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tsObj := ejv.v.(*extJSONObject)
|
||
|
tFound := false
|
||
|
iFound := false
|
||
|
|
||
|
for j, key := range tsObj.keys {
|
||
|
val := tsObj.values[j]
|
||
|
|
||
|
switch key {
|
||
|
case "t":
|
||
|
if t, err = handleKey(key, val, tFound); err != nil {
|
||
|
return 0, 0, err
|
||
|
}
|
||
|
|
||
|
tFound = true
|
||
|
case "i":
|
||
|
if i, err = handleKey(key, val, iFound); err != nil {
|
||
|
return 0, 0, err
|
||
|
}
|
||
|
|
||
|
iFound = true
|
||
|
default:
|
||
|
return 0, 0, fmt.Errorf("invalid key in $timestamp object: %s", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !tFound {
|
||
|
return 0, 0, errors.New("missing t field in $timestamp object")
|
||
|
}
|
||
|
|
||
|
if !iFound {
|
||
|
return 0, 0, errors.New("missing i field in $timestamp object")
|
||
|
}
|
||
|
|
||
|
return t, i, nil
|
||
|
}
|
||
|
|
||
|
func (ejv *extJSONValue) parseUndefined() error {
|
||
|
if ejv.t != bsontype.Boolean {
|
||
|
return fmt.Errorf("undefined value should be boolean, but instead is %s", ejv.t)
|
||
|
}
|
||
|
|
||
|
if !ejv.v.(bool) {
|
||
|
return fmt.Errorf("$undefined balue boolean should be true, but instead is %v", ejv.v.(bool))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|