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 }