-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
string.go
87 lines (78 loc) · 2.52 KB
/
string.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Copyright (c) 2023 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 random
import (
"encoding/binary"
"hash/crc32"
"strings"
"unsafe"
)
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// StringBytes generates a random string of the given length and returns it as a byte array.
func StringBytes(n int) []byte {
if n <= 0 {
return []byte{}
}
input := Bytes(n * 2)
for i := 0; i < n; i++ {
// Risk of modulo bias is only 2 in 65535, values between 0 and 65533 are uniformly distributed
input[i] = letters[binary.BigEndian.Uint16(input[i*2:])%uint16(len(letters))]
}
input = input[:n]
return input
}
// String generates a random string of the given length.
func String(n int) string {
if n <= 0 {
return ""
}
str := StringBytes(n)
return *(*string)(unsafe.Pointer(&str))
}
func base62Encode(val uint32, minWidth int) []byte {
out := make([]byte, 0, minWidth)
for val > 0 {
out = append(out, letters[val%uint32(len(letters))])
val /= 62
}
if len(out) < minWidth {
paddedOut := make([]byte, minWidth)
copy(paddedOut[minWidth-len(out):], out)
for i := 0; i < minWidth-len(out); i++ {
paddedOut[i] = '0'
}
out = paddedOut
}
return out
}
// Token generates a GitHub-style token with the given prefix, a random part, and a checksum at the end.
// The format is `prefix_random_checksum`. The checksum is always 6 characters.
func Token(namespace string, randomLength int) string {
token := make([]byte, len(namespace)+1+randomLength+1+6)
copy(token, namespace)
token[len(namespace)] = '_'
copy(token[len(namespace)+1:], StringBytes(randomLength))
token[len(namespace)+randomLength+1] = '_'
checksum := base62Encode(crc32.ChecksumIEEE(token[:len(token)-7]), 6)
copy(token[len(token)-6:], checksum)
return *(*string)(unsafe.Pointer(&token))
}
// GetTokenPrefix parses the given token generated with Token, validates the checksum and returns the prefix namespace.
func GetTokenPrefix(token string) string {
parts := strings.Split(token, "_")
if len(parts) != 3 {
return ""
}
checksum := base62Encode(crc32.ChecksumIEEE([]byte(parts[0]+"_"+parts[1])), 6)
if string(checksum) != parts[2] {
return ""
}
return parts[0]
}
// IsToken checks if the given token is a valid token generated with Token with the given namespace..
func IsToken(namespace, token string) bool {
return GetTokenPrefix(token) == namespace
}