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