github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/model/utils.go (about) 1 package model 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/base32" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net" 12 "net/http" 13 "net/mail" 14 "net/url" 15 "regexp" 16 "strconv" 17 "strings" 18 "sync" 19 "time" 20 "unicode" 21 22 "github.com/masterhung0112/hk_server/v5/shared/i18n" 23 "github.com/pborman/uuid" 24 ) 25 26 const ( 27 LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" 28 UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 29 NUMBERS = "0123456789" 30 SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" 31 MB = 1 << 20 32 ) 33 34 type StringInterface map[string]interface{} 35 type StringArray []string 36 37 func (sa StringArray) Remove(input string) StringArray { 38 for index := range sa { 39 if sa[index] == input { 40 ret := make(StringArray, 0, len(sa)-1) 41 ret = append(ret, sa[:index]...) 42 return append(ret, sa[index+1:]...) 43 } 44 } 45 return sa 46 } 47 48 func (sa StringArray) Contains(input string) bool { 49 for index := range sa { 50 if sa[index] == input { 51 return true 52 } 53 } 54 55 return false 56 } 57 func (sa StringArray) Equals(input StringArray) bool { 58 59 if len(sa) != len(input) { 60 return false 61 } 62 63 for index := range sa { 64 65 if sa[index] != input[index] { 66 return false 67 } 68 } 69 70 return true 71 } 72 73 var translateFunc i18n.TranslateFunc 74 var translateFuncOnce sync.Once 75 76 func AppErrorInit(t i18n.TranslateFunc) { 77 translateFuncOnce.Do(func() { 78 translateFunc = t 79 }) 80 } 81 82 type AppError struct { 83 Id string `json:"id"` 84 Message string `json:"message"` // Message to be display to the end user without debugging information 85 DetailedError string `json:"detailed_error"` // Internal error string to help the developer 86 RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header 87 StatusCode int `json:"status_code,omitempty"` // The http status code 88 Where string `json:"-"` // The function where it happened in the form of Struct.Func 89 IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific 90 params map[string]interface{} 91 } 92 93 func (er *AppError) Error() string { 94 return er.Where + ": " + er.Message + ", " + er.DetailedError 95 } 96 97 func (er *AppError) Translate(T i18n.TranslateFunc) { 98 if T == nil { 99 er.Message = er.Id 100 return 101 } 102 103 if er.params == nil { 104 er.Message = T(er.Id) 105 } else { 106 er.Message = T(er.Id, er.params) 107 } 108 } 109 110 func (er *AppError) SystemMessage(T i18n.TranslateFunc) string { 111 if er.params == nil { 112 return T(er.Id) 113 } 114 return T(er.Id, er.params) 115 } 116 117 func (er *AppError) ToJson() string { 118 b, _ := json.Marshal(er) 119 return string(b) 120 } 121 122 // AppErrorFromJson will decode the input and return an AppError 123 func AppErrorFromJson(data io.Reader) *AppError { 124 str := "" 125 bytes, rerr := ioutil.ReadAll(data) 126 if rerr != nil { 127 str = rerr.Error() 128 } else { 129 str = string(bytes) 130 } 131 132 decoder := json.NewDecoder(strings.NewReader(str)) 133 var er AppError 134 err := decoder.Decode(&er) 135 if err != nil { 136 return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError) 137 } 138 return &er 139 } 140 141 func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError { 142 ap := &AppError{} 143 ap.Id = id 144 ap.params = params 145 ap.Message = id 146 ap.Where = where 147 ap.DetailedError = details 148 ap.StatusCode = status 149 ap.IsOAuth = false 150 ap.Translate(translateFunc) 151 return ap 152 } 153 154 var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769") 155 156 // NewId is a globally unique identifier. It is a [A-Z0-9] string 26 157 // characters long. It is a UUID version 4 Guid that is zbased32 encoded 158 // with the padding stripped off. 159 func NewId() string { 160 var b bytes.Buffer 161 encoder := base32.NewEncoder(encoding, &b) 162 encoder.Write(uuid.NewRandom()) 163 encoder.Close() 164 b.Truncate(26) // removes the '==' padding 165 return b.String() 166 } 167 168 // NewRandomTeamName is a NewId that will be a valid team name. 169 func NewRandomTeamName() string { 170 teamName := NewId() 171 for IsReservedTeamName(teamName) { 172 teamName = NewId() 173 } 174 return teamName 175 } 176 177 // NewRandomString returns a random string of the given length. 178 // The resulting entropy will be (5 * length) bits. 179 func NewRandomString(length int) string { 180 data := make([]byte, 1+(length*5/8)) 181 rand.Read(data) 182 return encoding.EncodeToString(data)[:length] 183 } 184 185 // GetMillis is a convenience method to get milliseconds since epoch. 186 func GetMillis() int64 { 187 return time.Now().UnixNano() / int64(time.Millisecond) 188 } 189 190 // GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time. 191 func GetMillisForTime(thisTime time.Time) int64 { 192 return thisTime.UnixNano() / int64(time.Millisecond) 193 } 194 195 // GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch. 196 func GetTimeForMillis(millis int64) time.Time { 197 return time.Unix(0, millis*int64(time.Millisecond)) 198 } 199 200 // PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format 201 func PadDateStringZeros(dateString string) string { 202 parts := strings.Split(dateString, "-") 203 for index, part := range parts { 204 if len(part) == 1 { 205 parts[index] = "0" + part 206 } 207 } 208 dateString = strings.Join(parts[:], "-") 209 return dateString 210 } 211 212 // GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day 213 func GetStartOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 { 214 localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset) 215 resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, localSearchTimeZone) 216 return GetMillisForTime(resultTime) 217 } 218 219 // GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day 220 func GetEndOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 { 221 localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset) 222 resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, localSearchTimeZone) 223 return GetMillisForTime(resultTime) 224 } 225 226 func CopyStringMap(originalMap map[string]string) map[string]string { 227 copyMap := make(map[string]string) 228 for k, v := range originalMap { 229 copyMap[k] = v 230 } 231 return copyMap 232 } 233 234 // MapToJson converts a map to a json string 235 func MapToJson(objmap map[string]string) string { 236 b, _ := json.Marshal(objmap) 237 return string(b) 238 } 239 240 // MapBoolToJson converts a map to a json string 241 func MapBoolToJson(objmap map[string]bool) string { 242 b, _ := json.Marshal(objmap) 243 return string(b) 244 } 245 246 // MapFromJson will decode the key/value pair map 247 func MapFromJson(data io.Reader) map[string]string { 248 decoder := json.NewDecoder(data) 249 250 var objmap map[string]string 251 if err := decoder.Decode(&objmap); err != nil { 252 return make(map[string]string) 253 } 254 return objmap 255 } 256 257 // MapFromJson will decode the key/value pair map 258 func MapBoolFromJson(data io.Reader) map[string]bool { 259 decoder := json.NewDecoder(data) 260 261 var objmap map[string]bool 262 if err := decoder.Decode(&objmap); err != nil { 263 return make(map[string]bool) 264 } 265 return objmap 266 } 267 268 func ArrayToJson(objmap []string) string { 269 b, _ := json.Marshal(objmap) 270 return string(b) 271 } 272 273 func ArrayFromJson(data io.Reader) []string { 274 decoder := json.NewDecoder(data) 275 276 var objmap []string 277 if err := decoder.Decode(&objmap); err != nil { 278 return make([]string, 0) 279 } 280 return objmap 281 } 282 283 func ArrayFromInterface(data interface{}) []string { 284 stringArray := []string{} 285 286 dataArray, ok := data.([]interface{}) 287 if !ok { 288 return stringArray 289 } 290 291 for _, v := range dataArray { 292 if str, ok := v.(string); ok { 293 stringArray = append(stringArray, str) 294 } 295 } 296 297 return stringArray 298 } 299 300 func StringInterfaceToJson(objmap map[string]interface{}) string { 301 b, _ := json.Marshal(objmap) 302 return string(b) 303 } 304 305 func StringInterfaceFromJson(data io.Reader) map[string]interface{} { 306 decoder := json.NewDecoder(data) 307 308 var objmap map[string]interface{} 309 if err := decoder.Decode(&objmap); err != nil { 310 return make(map[string]interface{}) 311 } 312 return objmap 313 } 314 315 func StringToJson(s string) string { 316 b, _ := json.Marshal(s) 317 return string(b) 318 } 319 320 func StringFromJson(data io.Reader) string { 321 decoder := json.NewDecoder(data) 322 323 var s string 324 if err := decoder.Decode(&s); err != nil { 325 return "" 326 } 327 return s 328 } 329 330 // ToJson serializes an arbitrary data type to JSON, discarding the error. 331 func ToJson(v interface{}) []byte { 332 b, _ := json.Marshal(v) 333 return b 334 } 335 336 func GetServerIpAddress(iface string) string { 337 var addrs []net.Addr 338 if iface == "" { 339 var err error 340 addrs, err = net.InterfaceAddrs() 341 if err != nil { 342 return "" 343 } 344 } else { 345 interfaces, err := net.Interfaces() 346 if err != nil { 347 return "" 348 } 349 for _, i := range interfaces { 350 if i.Name == iface { 351 addrs, err = i.Addrs() 352 if err != nil { 353 return "" 354 } 355 break 356 } 357 } 358 } 359 360 for _, addr := range addrs { 361 362 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() { 363 if ip.IP.To4() != nil { 364 return ip.IP.String() 365 } 366 } 367 } 368 369 return "" 370 } 371 372 func IsLower(s string) bool { 373 return strings.ToLower(s) == s 374 } 375 376 func IsValidEmail(email string) bool { 377 if !IsLower(email) { 378 return false 379 } 380 381 if addr, err := mail.ParseAddress(email); err != nil { 382 return false 383 } else if addr.Name != "" { 384 // mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow 385 return false 386 } 387 388 return true 389 } 390 391 var reservedName = []string{ 392 "admin", 393 "api", 394 "channel", 395 "claim", 396 "error", 397 "files", 398 "help", 399 "landing", 400 "login", 401 "mfa", 402 "oauth", 403 "plug", 404 "plugins", 405 "post", 406 "signup", 407 } 408 409 func IsValidChannelIdentifier(s string) bool { 410 411 if !IsValidAlphaNumHyphenUnderscore(s, true) { 412 return false 413 } 414 415 if len(s) < CHANNEL_NAME_MIN_LENGTH { 416 return false 417 } 418 419 return true 420 } 421 422 func IsValidAlphaNum(s string) bool { 423 validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) 424 425 return validAlphaNum.MatchString(s) 426 } 427 428 func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { 429 if withFormat { 430 validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) 431 return validAlphaNumHyphenUnderscore.MatchString(s) 432 } 433 434 validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 435 return validSimpleAlphaNumHyphenUnderscore.MatchString(s) 436 } 437 438 func IsValidAlphaNumHyphenUnderscorePlus(s string) bool { 439 440 validSimpleAlphaNumHyphenUnderscorePlus := regexp.MustCompile(`^[a-zA-Z0-9+_-]+$`) 441 return validSimpleAlphaNumHyphenUnderscorePlus.MatchString(s) 442 } 443 444 func Etag(parts ...interface{}) string { 445 446 etag := CurrentVersion 447 448 for _, part := range parts { 449 etag += fmt.Sprintf(".%v", part) 450 } 451 452 return etag 453 } 454 455 var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) 456 var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) 457 var hashtagStart = regexp.MustCompile(`^#{2,}`) 458 var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) 459 460 func ParseHashtags(text string) (string, string) { 461 words := strings.Fields(text) 462 463 hashtagString := "" 464 plainString := "" 465 for _, word := range words { 466 // trim off surrounding punctuation 467 word = puncStart.ReplaceAllString(word, "") 468 word = puncEnd.ReplaceAllString(word, "") 469 470 // and remove extra pound #s 471 word = hashtagStart.ReplaceAllString(word, "#") 472 473 if validHashtag.MatchString(word) { 474 hashtagString += " " + word 475 } else { 476 plainString += " " + word 477 } 478 } 479 480 if len(hashtagString) > 1000 { 481 hashtagString = hashtagString[:999] 482 lastSpace := strings.LastIndex(hashtagString, " ") 483 if lastSpace > -1 { 484 hashtagString = hashtagString[:lastSpace] 485 } else { 486 hashtagString = "" 487 } 488 } 489 490 return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString) 491 } 492 493 func ClearMentionTags(post string) string { 494 post = strings.Replace(post, "<mention>", "", -1) 495 post = strings.Replace(post, "</mention>", "", -1) 496 return post 497 } 498 499 func IsValidHttpUrl(rawUrl string) bool { 500 if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { 501 return false 502 } 503 504 if u, err := url.ParseRequestURI(rawUrl); err != nil || u.Scheme == "" || u.Host == "" { 505 return false 506 } 507 508 return true 509 } 510 511 func IsValidTurnOrStunServer(rawUri string) bool { 512 if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { 513 return false 514 } 515 516 if _, err := url.ParseRequestURI(rawUri); err != nil { 517 return false 518 } 519 520 return true 521 } 522 523 func IsSafeLink(link *string) bool { 524 if link != nil { 525 if IsValidHttpUrl(*link) { 526 return true 527 } else if strings.HasPrefix(*link, "/") { 528 return true 529 } else { 530 return false 531 } 532 } 533 534 return true 535 } 536 537 func IsValidWebsocketUrl(rawUrl string) bool { 538 if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { 539 return false 540 } 541 542 if _, err := url.ParseRequestURI(rawUrl); err != nil { 543 return false 544 } 545 546 return true 547 } 548 549 func IsValidTrueOrFalseString(value string) bool { 550 return value == "true" || value == "false" 551 } 552 553 func IsValidNumberString(value string) bool { 554 if _, err := strconv.Atoi(value); err != nil { 555 return false 556 } 557 558 return true 559 } 560 561 func IsValidId(value string) bool { 562 if len(value) != 26 { 563 return false 564 } 565 566 for _, r := range value { 567 if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 568 return false 569 } 570 } 571 572 return true 573 } 574 575 // Copied from https://golang.org/src/net/dnsclient.go#L119 576 func IsDomainName(s string) bool { 577 // See RFC 1035, RFC 3696. 578 // Presentation format has dots before every label except the first, and the 579 // terminal empty label is optional here because we assume fully-qualified 580 // (absolute) input. We must therefore reserve space for the first and last 581 // labels' length octets in wire format, where they are necessary and the 582 // maximum total length is 255. 583 // So our _effective_ maximum is 253, but 254 is not rejected if the last 584 // character is a dot. 585 l := len(s) 586 if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { 587 return false 588 } 589 590 last := byte('.') 591 ok := false // Ok once we've seen a letter. 592 partlen := 0 593 for i := 0; i < len(s); i++ { 594 c := s[i] 595 switch { 596 default: 597 return false 598 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': 599 ok = true 600 partlen++ 601 case '0' <= c && c <= '9': 602 // fine 603 partlen++ 604 case c == '-': 605 // Byte before dash cannot be dot. 606 if last == '.' { 607 return false 608 } 609 partlen++ 610 case c == '.': 611 // Byte before dot cannot be dot, dash. 612 if last == '.' || last == '-' { 613 return false 614 } 615 if partlen > 63 || partlen == 0 { 616 return false 617 } 618 partlen = 0 619 } 620 last = c 621 } 622 if last == '-' || partlen > 63 { 623 return false 624 } 625 626 return ok 627 } 628 629 func RemoveDuplicateStrings(in []string) []string { 630 out := []string{} 631 seen := make(map[string]bool, len(in)) 632 633 for _, item := range in { 634 if !seen[item] { 635 out = append(out, item) 636 637 seen[item] = true 638 } 639 } 640 641 return out 642 } 643 644 func GetPreferredTimezone(timezone StringMap) string { 645 if timezone["useAutomaticTimezone"] == "true" { 646 return timezone["automaticTimezone"] 647 } 648 649 return timezone["manualTimezone"] 650 } 651 652 // IsSamlFile checks if filename is a SAML file. 653 func IsSamlFile(saml *SamlSettings, filename string) bool { 654 return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile 655 } 656 657 func AsStringBoolMap(list []string) map[string]bool { 658 listMap := map[string]bool{} 659 for _, p := range list { 660 listMap[p] = true 661 } 662 return listMap 663 } 664 665 // SanitizeUnicode will remove undesirable Unicode characters from a string. 666 func SanitizeUnicode(s string) string { 667 return strings.Map(filterBlocklist, s) 668 } 669 670 // filterBlocklist returns `r` if it is not in the blocklist, otherwise drop (-1). 671 // Blocklist is taken from https://www.w3.org/TR/unicode-xml/#Charlist 672 func filterBlocklist(r rune) rune { 673 const drop = -1 674 switch r { 675 case '\u0340', '\u0341': // clones of grave and acute; deprecated in Unicode 676 return drop 677 case '\u17A3', '\u17D3': // obsolete characters for Khmer; deprecated in Unicode 678 return drop 679 case '\u2028', '\u2029': // line and paragraph separator 680 return drop 681 case '\u202A', '\u202B', '\u202C', '\u202D', '\u202E': // BIDI embedding controls 682 return drop 683 case '\u206A', '\u206B': // activate/inhibit symmetric swapping; deprecated in Unicode 684 return drop 685 case '\u206C', '\u206D': // activate/inhibit Arabic form shaping; deprecated in Unicode 686 return drop 687 case '\u206E', '\u206F': // activate/inhibit national digit shapes; deprecated in Unicode 688 return drop 689 case '\uFFF9', '\uFFFA', '\uFFFB': // interlinear annotation characters 690 return drop 691 case '\uFEFF': // byte order mark 692 return drop 693 case '\uFFFC': // object replacement character 694 return drop 695 } 696 697 // Scoping for musical notation 698 if r >= 0x0001D173 && r <= 0x0001D17A { 699 return drop 700 } 701 702 // Language tag code points 703 if r >= 0x000E0000 && r <= 0x000E007F { 704 return drop 705 } 706 707 return r 708 } 709 710 // UniqueStrings returns a unique subset of the string slice provided. 711 func UniqueStrings(input []string) []string { 712 u := make([]string, 0, len(input)) 713 m := make(map[string]bool) 714 715 for _, val := range input { 716 if _, ok := m[val]; !ok { 717 m[val] = true 718 u = append(u, val) 719 } 720 } 721 722 return u 723 }