149 lines
3.2 KiB
Go
149 lines
3.2 KiB
Go
package tools
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"html"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
snowflakeMutex sync.Mutex
|
|
snowflakeMachineID int64
|
|
snowflakeSequence int64
|
|
snowflakeTimestamp int64
|
|
|
|
// Updates to normalizers here should be mirrored in `GET_Limits.go`!
|
|
|
|
RegexSpaces = regexp.MustCompile(`\s{2,}`)
|
|
RegexUnderscores = regexp.MustCompile(`_+`)
|
|
RegexNewlines = regexp.MustCompile(`\n{2,}`)
|
|
RegexMatcherTitle = regexp.MustCompile(`^[\S\s]{1,80}$`)
|
|
RegexMatcherTag = regexp.MustCompile(`^[\p{L}\p{N}_]{1,32}$`)
|
|
RegexMatcherComment = regexp.MustCompile(`^[\S\s]{10,240}$`)
|
|
)
|
|
|
|
func NormalizeTitle(str string) (string, bool) {
|
|
if str == "" {
|
|
return str, false
|
|
}
|
|
if !RegexMatcherTitle.MatchString(str) {
|
|
return str, false
|
|
}
|
|
str = RegexSpaces.ReplaceAllString(str, " ")
|
|
str = strings.TrimSpace(str)
|
|
str = html.EscapeString(str)
|
|
return str, true
|
|
}
|
|
|
|
func NormalizeTag(str string) (string, bool) {
|
|
if str == "" {
|
|
return str, false
|
|
}
|
|
if !RegexMatcherTag.MatchString(str) {
|
|
return str, false
|
|
}
|
|
str = RegexUnderscores.ReplaceAllString(str, "_")
|
|
str = strings.Trim(str, "_")
|
|
str = strings.ToUpper(str)
|
|
return str, true
|
|
}
|
|
|
|
func NormalizeComment(str string) (string, bool) {
|
|
if str == "" {
|
|
return str, false
|
|
}
|
|
if !RegexMatcherComment.MatchString(str) {
|
|
return str, false
|
|
}
|
|
str = RegexNewlines.ReplaceAllString(str, " ")
|
|
str = strings.TrimSpace(str)
|
|
str = html.EscapeString(str)
|
|
return str, true
|
|
}
|
|
|
|
func ParseLimit(str string) int {
|
|
v, _ := strconv.Atoi(str)
|
|
return min(100, max(1, v))
|
|
}
|
|
|
|
func ParseSnowflake(str string) int64 {
|
|
v, err := strconv.ParseInt(str, 10, 64)
|
|
if err != nil || v < 1 {
|
|
return 0
|
|
}
|
|
return v
|
|
}
|
|
|
|
func ParseJSON(r io.Reader, v any) error {
|
|
l := io.LimitReader(r, int64(LIMIT_JSON))
|
|
d := json.NewDecoder(l)
|
|
d.DisallowUnknownFields()
|
|
return d.Decode(v)
|
|
}
|
|
|
|
// Generate a Unique Snowflake
|
|
func RequestSnowflake() int64 {
|
|
snowflakeMutex.Lock()
|
|
defer snowflakeMutex.Unlock()
|
|
|
|
now := time.Now().UnixMilli()
|
|
|
|
if now != snowflakeTimestamp {
|
|
snowflakeSequence = 0
|
|
} else {
|
|
snowflakeSequence++
|
|
if snowflakeSequence > SNOWFLAKE_MAX_SEQUENCE {
|
|
for now <= snowflakeTimestamp {
|
|
time.Sleep(time.Millisecond)
|
|
now = time.Now().UnixMilli()
|
|
}
|
|
snowflakeSequence = 0
|
|
}
|
|
}
|
|
|
|
snowflakeTimestamp = now
|
|
return ((now - SNOWFLAKE_EPOCH_MILLI) << 22) | (snowflakeMachineID << 12) | snowflakeSequence
|
|
}
|
|
|
|
// Generate a Hex String out of random bytes
|
|
func RequestToken() string {
|
|
b := make([]byte, 32)
|
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
panic("failed to generate enough random bytes")
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// Generate a SHA256 Hex String from a given string
|
|
func RequestHash(str string) string {
|
|
h := sha256.Sum256([]byte(str))
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// Get the Client IP Address as a SHA256 Hex String
|
|
func RequestAddressHash(r *http.Request) string {
|
|
return RequestHash(RequestAddress(r))
|
|
}
|
|
|
|
// Get the Client IP Address
|
|
func RequestAddress(r *http.Request) string {
|
|
if HTTP_PROXY != "" {
|
|
return r.Header.Get(HTTP_PROXY)
|
|
}
|
|
addr, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return addr
|
|
}
|