add vendoring

This commit is contained in:
Aine
2022-11-16 12:08:51 +02:00
parent 14751cbf3a
commit c1d33fe3cb
1104 changed files with 759066 additions and 0 deletions

9
vendor/maunium.net/go/mautrix/util/base58/README.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
base58
==========
This is a copy of <https://github.com/btcsuite/btcd/tree/master/btcutil/base58>.
## License
Package base58 is licensed under the [copyfree](http://copyfree.org) ISC
License.

49
vendor/maunium.net/go/mautrix/util/base58/alphabet.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
// Copyright (c) 2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// AUTOGENERATED by genalphabet.go; do not edit.
package base58
const (
// alphabet is the modified base58 alphabet used by Bitcoin.
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
alphabetIdx0 = '1'
)
var b58 = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 255, 255, 255, 255, 255, 255,
255, 9, 10, 11, 12, 13, 14, 15,
16, 255, 17, 18, 19, 20, 21, 255,
22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 255, 255, 255, 255, 255,
255, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 255, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
}

138
vendor/maunium.net/go/mautrix/util/base58/base58.go generated vendored Normal file
View File

@@ -0,0 +1,138 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58
import (
"math/big"
)
//go:generate go run genalphabet.go
var bigRadix = [...]*big.Int{
big.NewInt(0),
big.NewInt(58),
big.NewInt(58 * 58),
big.NewInt(58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
bigRadix10,
}
var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
// Decode decodes a modified base58 string to a byte slice.
func Decode(b string) []byte {
answer := big.NewInt(0)
scratch := new(big.Int)
// Calculating with big.Int is slow for each iteration.
// x += b58[b[i]] * j
// j *= 58
//
// Instead we can try to do as much calculations on int64.
// We can represent a 10 digit base58 number using an int64.
//
// Hence we'll try to convert 10, base58 digits at a time.
// The rough idea is to calculate `t`, such that:
//
// t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0
// x *= 58^10
// x += t
//
// Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10.
// In that case we'll use the bigRadix[n] lookup for the appropriate power.
for t := b; len(t) > 0; {
n := len(t)
if n > 10 {
n = 10
}
total := uint64(0)
for _, v := range t[:n] {
tmp := b58[v]
if tmp == 255 {
return []byte("")
}
total = total*58 + uint64(tmp)
}
answer.Mul(answer, bigRadix[n])
scratch.SetUint64(total)
answer.Add(answer, scratch)
t = t[n:]
}
tmpval := answer.Bytes()
var numZeros int
for numZeros = 0; numZeros < len(b); numZeros++ {
if b[numZeros] != alphabetIdx0 {
break
}
}
flen := numZeros + len(tmpval)
val := make([]byte, flen)
copy(val[numZeros:], tmpval)
return val
}
// Encode encodes a byte slice to a modified base58 string.
func Encode(b []byte) string {
x := new(big.Int)
x.SetBytes(b)
// maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
maxlen := int(float64(len(b))*1.365658237309761) + 1
answer := make([]byte, 0, maxlen)
mod := new(big.Int)
for x.Sign() > 0 {
// Calculating with big.Int is slow for each iteration.
// x, mod = x / 58, x % 58
//
// Instead we can try to do as much calculations on int64.
// x, mod = x / 58^10, x % 58^10
//
// Which will give us mod, which is 10 digit base58 number.
// We'll loop that 10 times to convert to the answer.
x.DivMod(x, bigRadix10, mod)
if x.Sign() == 0 {
// When x = 0, we need to ensure we don't add any extra zeros.
m := mod.Int64()
for m > 0 {
answer = append(answer, alphabet[m%58])
m /= 58
}
} else {
m := mod.Int64()
for i := 0; i < 10; i++ {
answer = append(answer, alphabet[m%58])
m /= 58
}
}
}
// leading zero bytes
for _, i := range b {
if i != 0 {
break
}
answer = append(answer, alphabetIdx0)
}
// reverse
alen := len(answer)
for i := 0; i < alen/2; i++ {
answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
}
return string(answer)
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58
import (
"crypto/sha256"
"errors"
)
// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
// the checksum.
var ErrChecksum = errors.New("checksum error")
// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
// checksum: first four bytes of sha256^2
func checksum(input []byte) (cksum [4]byte) {
h := sha256.Sum256(input)
h2 := sha256.Sum256(h[:])
copy(cksum[:], h2[:4])
return
}
// CheckEncode prepends a version byte and appends a four byte checksum.
func CheckEncode(input []byte, version byte) string {
b := make([]byte, 0, 1+len(input)+4)
b = append(b, version)
b = append(b, input...)
cksum := checksum(b)
b = append(b, cksum[:]...)
return Encode(b)
}
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, version byte, err error) {
decoded := Decode(input)
if len(decoded) < 5 {
return nil, 0, ErrInvalidFormat
}
version = decoded[0]
var cksum [4]byte
copy(cksum[:], decoded[len(decoded)-4:])
if checksum(decoded[:len(decoded)-4]) != cksum {
return nil, 0, ErrChecksum
}
payload := decoded[1 : len(decoded)-4]
result = append(result, payload...)
return
}

29
vendor/maunium.net/go/mautrix/util/base58/doc.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package base58 provides an API for working with modified base58 and Base58Check
encodings.
# Modified Base58 Encoding
Standard base58 encoding is similar to standard base64 encoding except, as the
name implies, it uses a 58 character alphabet which results in an alphanumeric
string and allows some characters which are problematic for humans to be
excluded. Due to this, there can be various base58 alphabets.
The modified base58 alphabet used by Bitcoin, and hence this package, omits the
0, O, I, and l characters that look the same in many fonts and are therefore
hard to humans to distinguish.
# Base58Check Encoding Scheme
The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
time of this writing, however it can be used to generically encode arbitrary
byte arrays into human-readable strings along with a version byte that can be
used to differentiate the same payload. For Bitcoin addresses, the extra
version is used to differentiate the network of otherwise identical public keys
which helps prevent using an address intended for one network on another.
*/
package base58

View File

@@ -0,0 +1,288 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package configupgrade
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
type YAMLMap map[string]YAMLNode
type YAMLList []YAMLNode
type YAMLNode struct {
*yaml.Node
Map YAMLMap
List YAMLList
Key *yaml.Node
}
type YAMLType uint32
const (
Null YAMLType = 1 << iota
Bool
Str
Int
Float
Timestamp
List
Map
Binary
)
func (t YAMLType) String() string {
switch t {
case Null:
return NullTag
case Bool:
return BoolTag
case Str:
return StrTag
case Int:
return IntTag
case Float:
return FloatTag
case Timestamp:
return TimestampTag
case List:
return SeqTag
case Map:
return MapTag
case Binary:
return BinaryTag
default:
panic(fmt.Errorf("can't convert type %d to string", t))
}
}
func tagToType(tag string) YAMLType {
switch tag {
case NullTag:
return Null
case BoolTag:
return Bool
case StrTag:
return Str
case IntTag:
return Int
case FloatTag:
return Float
case TimestampTag:
return Timestamp
case SeqTag:
return List
case MapTag:
return Map
case BinaryTag:
return Binary
default:
return 0
}
}
const (
NullTag = "!!null"
BoolTag = "!!bool"
StrTag = "!!str"
IntTag = "!!int"
FloatTag = "!!float"
TimestampTag = "!!timestamp"
SeqTag = "!!seq"
MapTag = "!!map"
BinaryTag = "!!binary"
)
func fromNode(node, key *yaml.Node) YAMLNode {
switch node.Kind {
case yaml.DocumentNode:
return fromNode(node.Content[0], nil)
case yaml.AliasNode:
return fromNode(node.Alias, nil)
case yaml.MappingNode:
return YAMLNode{
Node: node,
Map: parseYAMLMap(node),
Key: key,
}
case yaml.SequenceNode:
return YAMLNode{
Node: node,
List: parseYAMLList(node),
}
default:
return YAMLNode{Node: node, Key: key}
}
}
func (yn *YAMLNode) toNode() *yaml.Node {
yn.UpdateContent()
return yn.Node
}
func (yn *YAMLNode) UpdateContent() {
switch {
case yn.Map != nil && yn.Node.Kind == yaml.MappingNode:
yn.Content = yn.Map.toNodes()
case yn.List != nil && yn.Node.Kind == yaml.SequenceNode:
yn.Content = yn.List.toNodes()
}
}
func parseYAMLList(node *yaml.Node) YAMLList {
data := make(YAMLList, len(node.Content))
for i, item := range node.Content {
data[i] = fromNode(item, nil)
}
return data
}
func (yl YAMLList) toNodes() []*yaml.Node {
nodes := make([]*yaml.Node, len(yl))
for i, item := range yl {
nodes[i] = item.toNode()
}
return nodes
}
func parseYAMLMap(node *yaml.Node) YAMLMap {
if len(node.Content)%2 != 0 {
panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content)))
}
data := make(YAMLMap, len(node.Content)/2)
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]
if key.Kind == yaml.ScalarNode {
data[key.Value] = fromNode(value, key)
}
}
return data
}
func (ym YAMLMap) toNodes() []*yaml.Node {
nodes := make([]*yaml.Node, len(ym)*2)
i := 0
for key, value := range ym {
nodes[i] = makeStringNode(key)
nodes[i+1] = value.toNode()
i += 2
}
return nodes
}
func makeStringNode(val string) *yaml.Node {
var node yaml.Node
node.SetString(val)
return &node
}
func StringNode(val string) YAMLNode {
return YAMLNode{Node: makeStringNode(val)}
}
type Helper struct {
Base YAMLNode
Config YAMLNode
}
func NewHelper(base, cfg *yaml.Node) *Helper {
return &Helper{
Base: fromNode(base, nil),
Config: fromNode(cfg, nil),
}
}
func (helper *Helper) AddSpaceBeforeComment(path ...string) {
node := helper.GetBaseNode(path...)
if node == nil || node.Key == nil {
panic(fmt.Errorf("didn't find key at %+v", path))
}
node.Key.HeadComment = "\n" + node.Key.HeadComment
}
func (helper *Helper) Copy(allowedTypes YAMLType, path ...string) {
base, cfg := helper.Base, helper.Config
var ok bool
for _, item := range path {
cfg, ok = cfg.Map[item]
if !ok {
return
}
base, ok = base.Map[item]
if !ok {
_, _ = fmt.Fprintf(os.Stderr, "Ignoring config field %s which is missing in base config\n", strings.Join(path, "->"))
return
}
}
if allowedTypes&tagToType(cfg.Tag) == 0 {
_, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->"))
return
}
base.Tag = cfg.Tag
base.Style = cfg.Style
switch base.Kind {
case yaml.ScalarNode:
base.Value = cfg.Value
case yaml.SequenceNode, yaml.MappingNode:
base.Content = cfg.Content
}
}
func getNode(cfg YAMLNode, path []string) *YAMLNode {
var ok bool
for _, item := range path {
cfg, ok = cfg.Map[item]
if !ok {
return nil
}
}
return &cfg
}
func (helper *Helper) GetNode(path ...string) *YAMLNode {
return getNode(helper.Config, path)
}
func (helper *Helper) GetBaseNode(path ...string) *YAMLNode {
return getNode(helper.Base, path)
}
func (helper *Helper) Get(tag YAMLType, path ...string) (string, bool) {
node := helper.GetNode(path...)
if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 {
return "", false
}
return node.Value, true
}
func (helper *Helper) GetBase(path ...string) string {
return helper.GetBaseNode(path...).Value
}
func (helper *Helper) Set(tag YAMLType, value string, path ...string) {
base := helper.Base
for _, item := range path {
base = base.Map[item]
}
base.Tag = tag.String()
base.Value = value
}
func (helper *Helper) SetMap(value YAMLMap, path ...string) {
base := helper.Base
for _, item := range path {
base = base.Map[item]
}
if base.Tag != MapTag || base.Kind != yaml.MappingNode {
panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind))
}
base.Content = value.toNodes()
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package configupgrade
import (
"fmt"
"os"
"path"
"gopkg.in/yaml.v3"
)
type Upgrader interface {
DoUpgrade(helper *Helper)
}
type SpacedUpgrader interface {
Upgrader
SpacedBlocks() [][]string
}
type BaseUpgrader interface {
Upgrader
GetBase() string
}
type StructUpgrader struct {
SimpleUpgrader
Blocks [][]string
Base string
}
func (su *StructUpgrader) SpacedBlocks() [][]string {
return su.Blocks
}
func (su *StructUpgrader) GetBase() string {
return su.Base
}
type SimpleUpgrader func(helper *Helper)
func (su SimpleUpgrader) DoUpgrade(helper *Helper) {
su(helper)
}
func (helper *Helper) apply(upgrader Upgrader) {
upgrader.DoUpgrade(helper)
helper.addSpaces(upgrader)
}
func (helper *Helper) addSpaces(upgrader Upgrader) {
spaced, ok := upgrader.(SpacedUpgrader)
if ok {
for _, spacePath := range spaced.SpacedBlocks() {
helper.AddSpaceBeforeComment(spacePath...)
}
}
}
func Do(configPath string, save bool, upgrader BaseUpgrader, additional ...Upgrader) ([]byte, bool, error) {
sourceData, err := os.ReadFile(configPath)
if err != nil {
return nil, false, fmt.Errorf("failed to read config: %w", err)
}
var base, cfg yaml.Node
err = yaml.Unmarshal([]byte(upgrader.GetBase()), &base)
if err != nil {
return sourceData, false, fmt.Errorf("failed to unmarshal example config: %w", err)
}
err = yaml.Unmarshal(sourceData, &cfg)
if err != nil {
return sourceData, false, fmt.Errorf("failed to unmarshal config: %w", err)
}
helper := NewHelper(&base, &cfg)
helper.apply(upgrader)
for _, add := range additional {
helper.apply(add)
}
output, err := yaml.Marshal(&base)
if err != nil {
return sourceData, false, fmt.Errorf("failed to marshal updated config: %w", err)
}
if save {
var tempFile *os.File
tempFile, err = os.CreateTemp(path.Dir(configPath), "mautrix-config-*.yaml")
if err != nil {
return output, true, fmt.Errorf("failed to create temp file for writing config: %w", err)
}
_, err = tempFile.Write(output)
if err != nil {
_ = os.Remove(tempFile.Name())
return output, true, fmt.Errorf("failed to write updated config to temp file: %w", err)
}
err = os.Rename(tempFile.Name(), configPath)
if err != nil {
_ = os.Remove(tempFile.Name())
return output, true, fmt.Errorf("failed to override current config with temp file: %w", err)
}
}
return output, true, nil
}

102
vendor/maunium.net/go/mautrix/util/dbutil/connlog.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package dbutil
import (
"context"
"database/sql"
"time"
)
// LoggingExecable is a wrapper for anything with database Exec methods (i.e. sql.Conn, sql.DB and sql.Tx)
// that can preprocess queries (e.g. replacing $ with ? on SQLite) and log query durations.
type LoggingExecable struct {
UnderlyingExecable Execable
db *Database
}
func (le *LoggingExecable) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
start := time.Now()
query = le.db.mutateQuery(query)
res, err := le.UnderlyingExecable.ExecContext(ctx, query, args...)
le.db.Log.QueryTiming(ctx, "Exec", query, args, time.Since(start))
return res, err
}
func (le *LoggingExecable) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
start := time.Now()
query = le.db.mutateQuery(query)
rows, err := le.UnderlyingExecable.QueryContext(ctx, query, args...)
le.db.Log.QueryTiming(ctx, "Query", query, args, time.Since(start))
return rows, err
}
func (le *LoggingExecable) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
start := time.Now()
query = le.db.mutateQuery(query)
row := le.UnderlyingExecable.QueryRowContext(ctx, query, args...)
le.db.Log.QueryTiming(ctx, "QueryRow", query, args, time.Since(start))
return row
}
func (le *LoggingExecable) Exec(query string, args ...interface{}) (sql.Result, error) {
return le.ExecContext(context.Background(), query, args...)
}
func (le *LoggingExecable) Query(query string, args ...interface{}) (*sql.Rows, error) {
return le.QueryContext(context.Background(), query, args...)
}
func (le *LoggingExecable) QueryRow(query string, args ...interface{}) *sql.Row {
return le.QueryRowContext(context.Background(), query, args...)
}
// loggingDB is a wrapper for LoggingExecable that allows access to BeginTx.
//
// While LoggingExecable has a pointer to the database and could use BeginTx, it's not technically safe since
// the LoggingExecable could be for a transaction (where BeginTx wouldn't make sense).
type loggingDB struct {
LoggingExecable
}
func (ld *loggingDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*LoggingTxn, error) {
start := time.Now()
tx, err := ld.db.RawDB.BeginTx(ctx, opts)
ld.db.Log.QueryTiming(ctx, "Begin", "", nil, time.Since(start))
if err != nil {
return nil, err
}
return &LoggingTxn{
LoggingExecable: LoggingExecable{UnderlyingExecable: tx, db: ld.db},
UnderlyingTx: tx,
ctx: ctx,
}, nil
}
func (ld *loggingDB) Begin() (*LoggingTxn, error) {
return ld.BeginTx(context.Background(), nil)
}
type LoggingTxn struct {
LoggingExecable
UnderlyingTx *sql.Tx
ctx context.Context
}
func (lt *LoggingTxn) Commit() error {
start := time.Now()
err := lt.UnderlyingTx.Commit()
lt.db.Log.QueryTiming(lt.ctx, "Commit", "", nil, time.Since(start))
return err
}
func (lt *LoggingTxn) Rollback() error {
start := time.Now()
err := lt.UnderlyingTx.Rollback()
lt.db.Log.QueryTiming(lt.ctx, "Rollback", "", nil, time.Since(start))
return err
}

206
vendor/maunium.net/go/mautrix/util/dbutil/database.go generated vendored Normal file
View File

@@ -0,0 +1,206 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package dbutil
import (
"context"
"database/sql"
"fmt"
"regexp"
"strings"
"time"
"maunium.net/go/mautrix/bridge/bridgeconfig"
)
type Dialect int
const (
DialectUnknown Dialect = iota
Postgres
SQLite
)
func (dialect Dialect) String() string {
switch dialect {
case Postgres:
return "postgres"
case SQLite:
return "sqlite3"
default:
return ""
}
}
func ParseDialect(engine string) (Dialect, error) {
switch strings.ToLower(engine) {
case "postgres", "postgresql":
return Postgres, nil
case "sqlite3", "sqlite", "litestream":
return SQLite, nil
default:
return DialectUnknown, fmt.Errorf("unknown dialect '%s'", engine)
}
}
type Scannable interface {
Scan(...interface{}) error
}
// Expected implementations of Scannable
var (
_ Scannable = (*sql.Row)(nil)
_ Scannable = (*sql.Rows)(nil)
)
type ContextExecable interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
type Execable interface {
ContextExecable
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
type Transaction interface {
Execable
Commit() error
Rollback() error
}
// Expected implementations of Execable
var (
_ Execable = (*sql.Tx)(nil)
_ Execable = (*sql.DB)(nil)
_ Execable = (*LoggingExecable)(nil)
_ Transaction = (*LoggingTxn)(nil)
_ ContextExecable = (*sql.Conn)(nil)
)
type Database struct {
loggingDB
RawDB *sql.DB
Owner string
VersionTable string
Log DatabaseLogger
Dialect Dialect
UpgradeTable UpgradeTable
IgnoreForeignTables bool
IgnoreUnsupportedDatabase bool
}
var positionalParamPattern = regexp.MustCompile(`\$(\d+)`)
func (db *Database) mutateQuery(query string) string {
switch db.Dialect {
case SQLite:
return positionalParamPattern.ReplaceAllString(query, "?$1")
default:
return query
}
}
func (db *Database) Child(versionTable string, upgradeTable UpgradeTable, log DatabaseLogger) *Database {
if log == nil {
log = db.Log
}
return &Database{
RawDB: db.RawDB,
loggingDB: db.loggingDB,
Owner: "",
VersionTable: versionTable,
UpgradeTable: upgradeTable,
Log: log,
Dialect: db.Dialect,
IgnoreForeignTables: true,
IgnoreUnsupportedDatabase: db.IgnoreUnsupportedDatabase,
}
}
func NewWithDB(db *sql.DB, rawDialect string) (*Database, error) {
dialect, err := ParseDialect(rawDialect)
if err != nil {
return nil, err
}
wrappedDB := &Database{
RawDB: db,
Dialect: dialect,
Log: NoopLogger,
IgnoreForeignTables: true,
VersionTable: "version",
}
wrappedDB.loggingDB.UnderlyingExecable = db
wrappedDB.loggingDB.db = wrappedDB
return wrappedDB, nil
}
func NewWithDialect(uri, rawDialect string) (*Database, error) {
db, err := sql.Open(rawDialect, uri)
if err != nil {
return nil, err
}
return NewWithDB(db, rawDialect)
}
func (db *Database) Configure(cfg bridgeconfig.DatabaseConfig) error {
db.RawDB.SetMaxOpenConns(cfg.MaxOpenConns)
db.RawDB.SetMaxIdleConns(cfg.MaxIdleConns)
if len(cfg.ConnMaxIdleTime) > 0 {
maxIdleTimeDuration, err := time.ParseDuration(cfg.ConnMaxIdleTime)
if err != nil {
return fmt.Errorf("failed to parse max_conn_idle_time: %w", err)
}
db.RawDB.SetConnMaxIdleTime(maxIdleTimeDuration)
}
if len(cfg.ConnMaxLifetime) > 0 {
maxLifetimeDuration, err := time.ParseDuration(cfg.ConnMaxLifetime)
if err != nil {
return fmt.Errorf("failed to parse max_conn_idle_time: %w", err)
}
db.RawDB.SetConnMaxLifetime(maxLifetimeDuration)
}
return nil
}
func NewFromConfig(owner string, cfg bridgeconfig.DatabaseConfig, logger DatabaseLogger) (*Database, error) {
dialect, err := ParseDialect(cfg.Type)
if err != nil {
return nil, err
}
conn, err := sql.Open(cfg.Type, cfg.URI)
if err != nil {
return nil, err
}
if logger == nil {
logger = NoopLogger
}
wrappedDB := &Database{
RawDB: conn,
Owner: owner,
Dialect: dialect,
Log: logger,
IgnoreForeignTables: true,
VersionTable: "version",
}
err = wrappedDB.Configure(cfg)
if err != nil {
return nil, err
}
wrappedDB.loggingDB.UnderlyingExecable = conn
wrappedDB.loggingDB.db = wrappedDB
return wrappedDB, nil
}

129
vendor/maunium.net/go/mautrix/util/dbutil/log.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
package dbutil
import (
"context"
"regexp"
"strings"
"time"
"github.com/rs/zerolog"
"maunium.net/go/maulogger/v2"
)
type DatabaseLogger interface {
QueryTiming(ctx context.Context, method, query string, args []interface{}, duration time.Duration)
WarnUnsupportedVersion(current, latest int)
PrepareUpgrade(current, latest int)
DoUpgrade(from, to int, message string)
// Deprecated: legacy warning method, return errors instead
Warn(msg string, args ...interface{})
}
type noopLogger struct{}
var NoopLogger DatabaseLogger = &noopLogger{}
func (n noopLogger) WarnUnsupportedVersion(_, _ int) {}
func (n noopLogger) PrepareUpgrade(_, _ int) {}
func (n noopLogger) DoUpgrade(_, _ int, _ string) {}
func (n noopLogger) Warn(msg string, args ...interface{}) {}
func (n noopLogger) QueryTiming(_ context.Context, _, _ string, _ []interface{}, _ time.Duration) {}
type mauLogger struct {
l maulogger.Logger
}
func MauLogger(log maulogger.Logger) DatabaseLogger {
return &mauLogger{l: log}
}
func (m mauLogger) WarnUnsupportedVersion(current, latest int) {
m.l.Warnfln("Unsupported database schema version: currently on v%d, latest known: v%d - continuing anyway", current, latest)
}
func (m mauLogger) PrepareUpgrade(current, latest int) {
m.l.Infofln("Database currently on v%d, latest: v%d", current, latest)
}
func (m mauLogger) DoUpgrade(from, to int, message string) {
m.l.Infofln("Upgrading database from v%d to v%d: %s", from, to, message)
}
func (m mauLogger) QueryTiming(_ context.Context, method, query string, _ []interface{}, duration time.Duration) {
if duration > 1*time.Second {
m.l.Warnfln("%s(%s) took %.3f seconds", method, query, duration.Seconds())
}
}
func (m mauLogger) Warn(msg string, args ...interface{}) {
m.l.Warnfln(msg, args...)
}
type zeroLogger struct {
l *zerolog.Logger
}
func ZeroLogger(log zerolog.Logger) DatabaseLogger {
return ZeroLoggerPtr(&log)
}
func ZeroLoggerPtr(log *zerolog.Logger) DatabaseLogger {
return &zeroLogger{l: log}
}
func (z zeroLogger) WarnUnsupportedVersion(current, latest int) {
z.l.Warn().
Int("current_db_version", current).
Int("latest_known_version", latest).
Msg("Unsupported database schema version, continuing anyway")
}
func (z zeroLogger) PrepareUpgrade(current, latest int) {
evt := z.l.Info().
Int("current_db_version", current).
Int("latest_known_version", latest)
if current == latest {
evt.Msg("Database is up to date")
} else {
evt.Msg("Preparing to update database schema")
}
}
func (z zeroLogger) DoUpgrade(from, to int, message string) {
z.l.Info().
Int("from", from).
Int("to", to).
Str("description", message).
Msg("Upgrading database")
}
var whitespaceRegex = regexp.MustCompile(`\s+`)
func (z zeroLogger) QueryTiming(ctx context.Context, method, query string, args []interface{}, duration time.Duration) {
log := zerolog.Ctx(ctx)
if log.GetLevel() == zerolog.Disabled {
log = z.l
}
if log.GetLevel() != zerolog.TraceLevel && duration < 1*time.Second {
return
}
query = strings.TrimSpace(whitespaceRegex.ReplaceAllLiteralString(query, " "))
log.Trace().
Int64("duration_µs", duration.Microseconds()).
Str("method", method).
Str("query", query).
Interface("query_args", args).
Msg("Query")
if duration >= 1*time.Second {
log.Warn().
Float64("duration_seconds", duration.Seconds()).
Str("method", method).
Str("query", query).
Msg("Query took long")
}
}
func (z zeroLogger) Warn(msg string, args ...interface{}) {
z.l.Warn().Msgf(msg, args...)
}

View File

@@ -0,0 +1,21 @@
-- v0 -> v3: Sample revision jump
CREATE TABLE foo (
-- only: postgres
key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
-- only: sqlite
key INTEGER PRIMARY KEY,
data JSONB NOT NULL
);
-- only: sqlite until "end only"
CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index';
END;
-- end only sqlite
-- only: postgres until "end only"
CREATE FUNCTION delete_data() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index';
RETURN NEW;
END $$;
-- end only postgres

View File

@@ -0,0 +1,11 @@
CREATE TABLE foo (
key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data JSONB NOT NULL
);
CREATE FUNCTION delete_data() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index';
RETURN NEW;
END $$;
-- end only postgres

View File

@@ -0,0 +1,10 @@
CREATE TABLE foo (
key INTEGER PRIMARY KEY,
data JSONB NOT NULL
);
CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index';
END;
-- end only sqlite

154
vendor/maunium.net/go/mautrix/util/dbutil/upgrades.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package dbutil
import (
"database/sql"
"errors"
"fmt"
)
type upgradeFunc func(Transaction, *Database) error
type upgrade struct {
message string
fn upgradeFunc
upgradesTo int
}
var ErrUnsupportedDatabaseVersion = fmt.Errorf("unsupported database schema version")
var ErrForeignTables = fmt.Errorf("the database contains foreign tables")
var ErrNotOwned = fmt.Errorf("the database is owned by")
func (db *Database) getVersion() (int, error) {
_, err := db.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (version INTEGER)", db.VersionTable))
if err != nil {
return -1, err
}
version := 0
err = db.QueryRow(fmt.Sprintf("SELECT version FROM %s LIMIT 1", db.VersionTable)).Scan(&version)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return -1, err
}
return version, nil
}
const tableExistsPostgres = "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=$1)"
const tableExistsSQLite = "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND tbl_name=$1)"
func (db *Database) tableExists(table string) (exists bool, err error) {
if db.Dialect == SQLite {
err = db.QueryRow(tableExistsSQLite, table).Scan(&exists)
} else if db.Dialect == Postgres {
err = db.QueryRow(tableExistsPostgres, table).Scan(&exists)
}
return
}
func (db *Database) tableExistsNoError(table string) bool {
exists, err := db.tableExists(table)
if err != nil {
panic(fmt.Errorf("failed to check if table exists: %w", err))
}
return exists
}
const createOwnerTable = `
CREATE TABLE IF NOT EXISTS database_owner (
key INTEGER PRIMARY KEY DEFAULT 0,
owner TEXT NOT NULL
)
`
func (db *Database) checkDatabaseOwner() error {
var owner string
if !db.IgnoreForeignTables {
if db.tableExistsNoError("state_groups_state") {
return fmt.Errorf("%w (found state_groups_state, likely belonging to Synapse)", ErrForeignTables)
} else if db.tableExistsNoError("roomserver_rooms") {
return fmt.Errorf("%w (found roomserver_rooms, likely belonging to Dendrite)", ErrForeignTables)
}
}
if db.Owner == "" {
return nil
}
if _, err := db.Exec(createOwnerTable); err != nil {
return fmt.Errorf("failed to ensure database owner table exists: %w", err)
} else if err = db.QueryRow("SELECT owner FROM database_owner WHERE key=0").Scan(&owner); errors.Is(err, sql.ErrNoRows) {
_, err = db.Exec("INSERT INTO database_owner (key, owner) VALUES (0, $1)", db.Owner)
if err != nil {
return fmt.Errorf("failed to insert database owner: %w", err)
}
} else if err != nil {
return fmt.Errorf("failed to check database owner: %w", err)
} else if owner != db.Owner {
return fmt.Errorf("%w %s", ErrNotOwned, owner)
}
return nil
}
func (db *Database) setVersion(tx Transaction, version int) error {
_, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", db.VersionTable))
if err != nil {
return err
}
_, err = tx.Exec(fmt.Sprintf("INSERT INTO %s (version) VALUES ($1)", db.VersionTable), version)
return err
}
func (db *Database) Upgrade() error {
err := db.checkDatabaseOwner()
if err != nil {
return err
}
version, err := db.getVersion()
if err != nil {
return err
}
if version > len(db.UpgradeTable) {
if db.IgnoreUnsupportedDatabase {
db.Log.WarnUnsupportedVersion(version, len(db.UpgradeTable))
return nil
}
return fmt.Errorf("%w: currently on v%d, latest known: v%d", ErrUnsupportedDatabaseVersion, version, len(db.UpgradeTable))
}
db.Log.PrepareUpgrade(version, len(db.UpgradeTable))
logVersion := version
for version < len(db.UpgradeTable) {
upgradeItem := db.UpgradeTable[version]
if upgradeItem.fn == nil {
version++
continue
}
db.Log.DoUpgrade(logVersion, upgradeItem.upgradesTo, upgradeItem.message)
var tx Transaction
tx, err = db.Begin()
if err != nil {
return err
}
err = upgradeItem.fn(tx, db)
if err != nil {
return err
}
version = upgradeItem.upgradesTo
logVersion = version
err = db.setVersion(tx, version)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,255 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package dbutil
import (
"bytes"
"errors"
"fmt"
"io/fs"
"path/filepath"
"regexp"
"strconv"
"strings"
)
type UpgradeTable []upgrade
func (ut *UpgradeTable) extend(toSize int) {
if cap(*ut) >= toSize {
*ut = (*ut)[:toSize]
} else {
resized := make([]upgrade, toSize)
copy(resized, *ut)
*ut = resized
}
}
func (ut *UpgradeTable) Register(from, to int, message string, fn upgradeFunc) {
if from < 0 {
from += to
}
if from < 0 {
panic("invalid from value in UpgradeTable.Register() call")
}
upg := upgrade{message: message, fn: fn, upgradesTo: to}
if len(*ut) == from {
*ut = append(*ut, upg)
return
} else if len(*ut) < from {
ut.extend(from + 1)
} else if (*ut)[from].fn != nil {
panic(fmt.Errorf("tried to override upgrade at %d ('%s') with '%s'", from, (*ut)[from].message, upg.message))
}
(*ut)[from] = upg
}
// Syntax is either
//
// -- v0 -> v1: Message
//
// or
//
// -- v1: Message
var upgradeHeaderRegex = regexp.MustCompile(`^-- (?:v(\d+) -> )?v(\d+): (.+)$`)
func parseFileHeader(file []byte) (from, to int, message string, lines [][]byte, err error) {
lines = bytes.Split(file, []byte("\n"))
if len(lines) < 2 {
err = errors.New("upgrade file too short")
return
}
var maybeFrom int
match := upgradeHeaderRegex.FindSubmatch(lines[0])
lines = lines[1:]
if match == nil {
err = errors.New("header not found")
} else if len(match) != 4 {
err = errors.New("unexpected number of items in regex match")
} else if maybeFrom, err = strconv.Atoi(string(match[1])); len(match[1]) > 0 && err != nil {
err = fmt.Errorf("invalid source version: %w", err)
} else if to, err = strconv.Atoi(string(match[2])); err != nil {
err = fmt.Errorf("invalid target version: %w", err)
} else {
if len(match[1]) > 0 {
from = maybeFrom
} else {
from = -1
}
message = string(match[3])
}
return
}
// To limit the next line to one dialect:
//
// -- only: postgres
//
// To limit the next N lines:
//
// -- only: sqlite for next 123 lines
//
// If the single-line limit is on the second line of the file, the whole file is limited to that dialect.
var dialectLineFilter = regexp.MustCompile(`^\s*-- only: (postgres|sqlite)(?: for next (\d+) lines| until "(end) only")?`)
// Constants used to make parseDialectFilter clearer
const (
skipUntilEndTag = -1
skipNothing = 0
skipCurrentLine = 1
skipNextLine = 2
)
func (db *Database) parseDialectFilter(line []byte) (int, error) {
match := dialectLineFilter.FindSubmatch(line)
if match == nil {
return skipNothing, nil
}
dialect, err := ParseDialect(string(match[1]))
if err != nil {
return skipNothing, err
} else if dialect == db.Dialect {
// Skip the dialect filter line
return skipCurrentLine, nil
} else if bytes.Equal(match[3], []byte("end")) {
return skipUntilEndTag, nil
} else if len(match[2]) == 0 {
// Skip the dialect filter and the next line
return skipNextLine, nil
} else {
// Parse number of lines to skip, add 1 for current line
lineCount, err := strconv.Atoi(string(match[2]))
if err != nil {
return skipNothing, fmt.Errorf("invalid line count '%s': %w", match[2], err)
}
return skipCurrentLine + lineCount, nil
}
}
var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`)
func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) {
output := make([][]byte, 0, len(lines))
for i := 0; i < len(lines); i++ {
skipLines, err := db.parseDialectFilter(lines[i])
if err != nil {
return "", err
} else if skipLines > 0 {
// Current line is implicitly skipped, so reduce one here
i += skipLines - 1
} else if skipLines == skipUntilEndTag {
startedAt := i
startedAtMatch := dialectLineFilter.FindSubmatch(lines[startedAt])
for ; i < len(lines); i++ {
if match := endLineFilter.FindSubmatch(lines[i]); match != nil {
if !bytes.Equal(match[1], startedAtMatch[1]) {
return "", fmt.Errorf(`unexpected end tag %q for %q start at line %d`, string(match[0]), string(startedAtMatch[1]), startedAt)
}
break
}
}
if i == len(lines) {
return "", fmt.Errorf(`didn't get end tag matching start %q at line %d`, string(startedAtMatch[1]), startedAt)
}
} else {
output = append(output, lines[i])
}
}
return string(bytes.Join(output, []byte("\n"))), nil
}
func sqlUpgradeFunc(fileName string, lines [][]byte) upgradeFunc {
return func(tx Transaction, db *Database) error {
if skip, err := db.parseDialectFilter(lines[0]); err == nil && skip == skipNextLine {
return nil
} else if upgradeSQL, err := db.filterSQLUpgrade(lines); err != nil {
panic(fmt.Errorf("failed to parse upgrade %s: %w", fileName, err))
} else {
_, err = tx.Exec(upgradeSQL)
return err
}
}
}
func splitSQLUpgradeFunc(sqliteData, postgresData string) upgradeFunc {
return func(tx Transaction, database *Database) (err error) {
switch database.Dialect {
case SQLite:
_, err = tx.Exec(sqliteData)
case Postgres:
_, err = tx.Exec(postgresData)
default:
err = fmt.Errorf("unknown dialect %s", database.Dialect)
}
return
}
}
func parseSplitSQLUpgrade(name string, fs fullFS, skipNames map[string]struct{}) (from, to int, message string, fn upgradeFunc) {
postgresName := fmt.Sprintf("%s.postgres.sql", name)
sqliteName := fmt.Sprintf("%s.sqlite.sql", name)
skipNames[postgresName] = struct{}{}
skipNames[sqliteName] = struct{}{}
postgresData, err := fs.ReadFile(postgresName)
if err != nil {
panic(err)
}
sqliteData, err := fs.ReadFile(sqliteName)
if err != nil {
panic(err)
}
from, to, message, _, err = parseFileHeader(postgresData)
if err != nil {
panic(fmt.Errorf("failed to parse header in %s: %w", postgresName, err))
}
sqliteFrom, sqliteTo, sqliteMessage, _, err := parseFileHeader(sqliteData)
if err != nil {
panic(fmt.Errorf("failed to parse header in %s: %w", sqliteName, err))
}
if from != sqliteFrom || to != sqliteTo {
panic(fmt.Errorf("mismatching versions in postgres and sqlite versions of %s: %d/%d -> %d/%d", name, from, sqliteFrom, to, sqliteTo))
} else if message != sqliteMessage {
panic(fmt.Errorf("mismatching message in postgres and sqlite versions of %s: %q != %q", name, message, sqliteMessage))
}
fn = splitSQLUpgradeFunc(string(sqliteData), string(postgresData))
return
}
type fullFS interface {
fs.ReadFileFS
fs.ReadDirFS
}
var splitFileNameRegex = regexp.MustCompile(`^(.+)\.(postgres|sqlite)\.sql$`)
func (ut *UpgradeTable) RegisterFS(fs fullFS) {
ut.RegisterFSPath(fs, ".")
}
func (ut *UpgradeTable) RegisterFSPath(fs fullFS, dir string) {
files, err := fs.ReadDir(dir)
if err != nil {
panic(err)
}
skipNames := map[string]struct{}{}
for _, file := range files {
if file.IsDir() || !strings.HasSuffix(file.Name(), ".sql") {
// do nothing
} else if _, skip := skipNames[file.Name()]; skip {
// also do nothing
} else if splitName := splitFileNameRegex.FindStringSubmatch(file.Name()); splitName != nil {
from, to, message, fn := parseSplitSQLUpgrade(splitName[1], fs, skipNames)
ut.Register(from, to, message, fn)
} else if data, err := fs.ReadFile(filepath.Join(dir, file.Name())); err != nil {
panic(err)
} else if from, to, message, lines, err := parseFileHeader(data); err != nil {
panic(fmt.Errorf("failed to parse header in %s: %w", file.Name(), err))
} else {
ut.Register(from, to, message, sqlUpgradeFunc(file.Name(), lines))
}
}
}

33
vendor/maunium.net/go/mautrix/util/dualerror.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package util
import (
"errors"
"fmt"
)
type DualError struct {
High error
Low error
}
func NewDualError(high, low error) DualError {
return DualError{high, low}
}
func (err DualError) Is(other error) bool {
return errors.Is(other, err.High) || errors.Is(other, err.Low)
}
func (err DualError) Unwrap() error {
return err.Low
}
func (err DualError) Error() string {
return fmt.Sprintf("%v: %v", err.High, err.Low)
}

31
vendor/maunium.net/go/mautrix/util/gjson.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package util
import (
"strings"
)
var GJSONEscaper = strings.NewReplacer(
`\`, `\\`,
".", `\.`,
"|", `\|`,
"#", `\#`,
"@", `\@`,
"*", `\*`,
"?", `\?`)
func GJSONPath(path ...string) string {
var result strings.Builder
for i, part := range path {
_, _ = GJSONEscaper.WriteString(&result, part)
if i < len(path)-1 {
result.WriteRune('.')
}
}
return result.String()
}

30
vendor/maunium.net/go/mautrix/util/marshal.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package util
import (
"encoding/json"
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// MarshalAndDeleteEmpty marshals a JSON object, then uses gjson to delete empty objects at the given gjson paths.
//
// This can be used as a convenient way to create a marshaler that omits empty non-pointer structs.
// See mautrix.RespSync for example.
func MarshalAndDeleteEmpty(marshalable interface{}, paths []string) ([]byte, error) {
data, err := json.Marshal(marshalable)
if err != nil {
return nil, err
}
for _, path := range paths {
res := gjson.GetBytes(data, path)
if res.IsObject() && len(res.Raw) == 2 {
data, err = sjson.DeleteBytes(data, path)
if err != nil {
return nil, fmt.Errorf("failed to delete empty %s: %w", path, err)
}
}
}
return data, nil
}

50
vendor/maunium.net/go/mautrix/util/mimetypes.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
// Copyright (c) 2022 Sumner Evans
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package util
import (
"mime"
"strings"
)
// MimeExtensionSanityOverrides includes extensions for various common mimetypes.
//
// This is necessary because sometimes the OS mimetype database and Go interact in weird ways,
// which causes very obscure extensions to be first in the array for common mimetypes
// (e.g. image/jpeg -> .jpe, text/plain -> ,v).
var MimeExtensionSanityOverrides = map[string]string{
"image/png": ".png",
"image/webp": ".webp",
"image/jpeg": ".jpg",
"image/tiff": ".tiff",
"image/heif": ".heic",
"image/heic": ".heic",
"audio/mpeg": ".mp3",
"audio/ogg": ".ogg",
"audio/webm": ".webm",
"audio/x-caf": ".caf",
"video/mp4": ".mp4",
"video/mpeg": ".mpeg",
"video/webm": ".webm",
"text/plain": ".txt",
"text/html": ".html",
"application/xml": ".xml",
}
func ExtensionFromMimetype(mimetype string) string {
ext, ok := MimeExtensionSanityOverrides[strings.Split(mimetype, ";")[0]]
if !ok {
exts, _ := mime.ExtensionsByType(mimetype)
if len(exts) > 0 {
ext = exts[0]
}
}
return ext
}

65
vendor/maunium.net/go/mautrix/util/random.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package util
import (
"crypto/rand"
"encoding/base64"
"hash/crc32"
"strings"
"unsafe"
)
func RandomBytes(n int) []byte {
data := make([]byte, n)
_, err := rand.Read(data)
if err != nil {
panic(err)
}
return data
}
var letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// RandomString generates a random string of the given length.
func RandomString(n int) string {
if n <= 0 {
return ""
}
base64Len := n
if n%4 != 0 {
base64Len += 4 - (n % 4)
}
decodedLength := base64.RawStdEncoding.DecodedLen(base64Len)
output := make([]byte, base64Len)
base64.RawStdEncoding.Encode(output, RandomBytes(decodedLength))
for i, char := range output {
if char == '+' || char == '/' {
_, err := rand.Read(output[i : i+1])
if err != nil {
panic(err)
}
output[i] = letters[int(output[i])%len(letters)]
}
}
return (*(*string)(unsafe.Pointer(&output)))[:n]
}
func base62Encode(val uint32, minWidth int) string {
var buf strings.Builder
for val > 0 {
buf.WriteByte(letters[val%62])
val /= 62
}
return strings.Repeat("0", minWidth-buf.Len()) + buf.String()
}
func RandomToken(namespace string, randomLength int) string {
token := namespace + "_" + RandomString(randomLength)
checksum := base62Encode(crc32.ChecksumIEEE([]byte(token)), 6)
return token + "_" + checksum
}