mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-19 20:14:17 -04:00
* refactor APIs in JMAP and Groupware in order to implement pagination across multiple accountIds and multiple suppliers (currently implemented using a mock supplier for contacts) * requires go 1.26 due to use of self-reflecting generics type constraints * still missing: query criteria and sorting parameters * still missing: multi-accountId support for emails * errors are now all just 'error' in the APIs, instead of the specialized implementations, and are interpreted dynamically where necessary in order to transform them into HTTP responses * remove position, anchor, anchorOffset as individual query parameters as we now only support a 'next=...' token for subsequent pages (except in emails for now), and use jmap.QueryParams instead; those tokens have a header character for the format, followed by a JSON encoded QueryParams map, all wrapped into base62 to make it clearer that it is meant to be an opaque token, and not a parameter clients should tinker with or construct themselves * introduce QueryParamsSupplier as an interface to provide QueryParams for various scenarios (single supplier, multiple supplier, ...) per accountId * implement multi-supplier template methods slist and squery
98 lines
2.1 KiB
Go
98 lines
2.1 KiB
Go
package groupware
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
)
|
|
|
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
|
|
var base = big.NewInt(int64(len(alphabet)))
|
|
|
|
// EncodeBytes converts any byte slice into a Base62 string
|
|
func EncodeBytesToBase62(src []byte) string {
|
|
if len(src) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// 1. Convert the bytes into a single large big.Int
|
|
n := new(big.Int).SetBytes(src)
|
|
if n.Cmp(big.NewInt(0)) == 0 {
|
|
return string(alphabet[0])
|
|
}
|
|
|
|
// 2. Convert the big.Int to Base62
|
|
var result []byte
|
|
mod := new(big.Int)
|
|
for n.Cmp(big.NewInt(0)) > 0 {
|
|
n.DivMod(n, base, mod)
|
|
result = append(result, alphabet[mod.Int64()])
|
|
}
|
|
|
|
// 3. Handle leading zeros in the original byte array.
|
|
// Because math/big drops leading zeros, we must explicitly preserve them.
|
|
for _, b := range src {
|
|
if b != 0x00 {
|
|
break
|
|
}
|
|
result = append(result, alphabet[0])
|
|
}
|
|
|
|
// Reverse the result slice to get the correct order
|
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
|
result[i], result[j] = result[j], result[i]
|
|
}
|
|
|
|
return string(result)
|
|
}
|
|
|
|
// DecodeBytes converts a Base62 string back into its original byte slice
|
|
func DecodeBytesFromBase62(src string) ([]byte, error) {
|
|
if len(src) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
n := big.NewInt(0)
|
|
idx := big.NewInt(0)
|
|
|
|
// 1. Reconstruct the large integer from the Base62 characters
|
|
for i := 0; i < len(src); i++ {
|
|
pos := findAlphabetIndex(src[i])
|
|
if pos == -1 {
|
|
return nil, errors.New("invalid character in base62 string")
|
|
}
|
|
idx.SetInt64(int64(pos))
|
|
n.Mul(n, base)
|
|
n.Add(n, idx)
|
|
}
|
|
|
|
// 2. Extract the raw bytes from the big.Int
|
|
decoded := n.Bytes()
|
|
|
|
// 3. Re-prepend the leading zeros that were stripped during encoding
|
|
var leadingZeros int
|
|
for i := 0; i < len(src); i++ {
|
|
if src[i] != alphabet[0] {
|
|
break
|
|
}
|
|
leadingZeros++
|
|
}
|
|
|
|
if leadingZeros > 0 {
|
|
zeroBytes := make([]byte, leadingZeros)
|
|
decoded = append(zeroBytes, decoded...)
|
|
}
|
|
|
|
return decoded, nil
|
|
}
|
|
|
|
// Helper to find the index of a character in our alphabet
|
|
func findAlphabetIndex(char byte) int {
|
|
for i := 0; i < len(alphabet); i++ {
|
|
if alphabet[i] == char {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|