github.com/vnforks/kid@v5.11.1+incompatible/model/utils.go (about) 1 // Copyright (c) 2015-present Mattermost, 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/nicksnyder/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() string { 306 if addrs, err := net.InterfaceAddrs(); err != nil { 307 return "" 308 } else { 309 for _, addr := range addrs { 310 311 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() { 312 if ip.IP.To4() != nil { 313 return ip.IP.String() 314 } 315 } 316 } 317 } 318 319 return "" 320 } 321 322 func IsLower(s string) bool { 323 return strings.ToLower(s) == s 324 } 325 326 func IsValidEmail(email string) bool { 327 if !IsLower(email) { 328 return false 329 } 330 331 if addr, err := mail.ParseAddress(email); err != nil { 332 return false 333 } else if addr.Name != "" { 334 // mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow 335 return false 336 } 337 338 return true 339 } 340 341 var reservedName = []string{ 342 "signup", 343 "login", 344 "admin", 345 "channel", 346 "post", 347 "api", 348 "oauth", 349 } 350 351 func IsValidChannelIdentifier(s string) bool { 352 353 if !IsValidAlphaNumHyphenUnderscore(s, true) { 354 return false 355 } 356 357 if len(s) < CHANNEL_NAME_MIN_LENGTH { 358 return false 359 } 360 361 return true 362 } 363 364 func IsValidAlphaNum(s string) bool { 365 validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) 366 367 return validAlphaNum.MatchString(s) 368 } 369 370 func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { 371 if withFormat { 372 validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) 373 return validAlphaNumHyphenUnderscore.MatchString(s) 374 } 375 376 validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 377 return validSimpleAlphaNumHyphenUnderscore.MatchString(s) 378 } 379 380 func Etag(parts ...interface{}) string { 381 382 etag := CurrentVersion 383 384 for _, part := range parts { 385 etag += fmt.Sprintf(".%v", part) 386 } 387 388 return etag 389 } 390 391 var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) 392 var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) 393 var hashtagStart = regexp.MustCompile(`^#{2,}`) 394 var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) 395 396 func ParseHashtags(text string) (string, string) { 397 words := strings.Fields(text) 398 399 hashtagString := "" 400 plainString := "" 401 for _, word := range words { 402 // trim off surrounding punctuation 403 word = puncStart.ReplaceAllString(word, "") 404 word = puncEnd.ReplaceAllString(word, "") 405 406 // and remove extra pound #s 407 word = hashtagStart.ReplaceAllString(word, "#") 408 409 if validHashtag.MatchString(word) { 410 hashtagString += " " + word 411 } else { 412 plainString += " " + word 413 } 414 } 415 416 if len(hashtagString) > 1000 { 417 hashtagString = hashtagString[:999] 418 lastSpace := strings.LastIndex(hashtagString, " ") 419 if lastSpace > -1 { 420 hashtagString = hashtagString[:lastSpace] 421 } else { 422 hashtagString = "" 423 } 424 } 425 426 return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString) 427 } 428 429 func IsFileExtImage(ext string) bool { 430 ext = strings.ToLower(ext) 431 for _, imgExt := range IMAGE_EXTENSIONS { 432 if ext == imgExt { 433 return true 434 } 435 } 436 return false 437 } 438 439 func GetImageMimeType(ext string) string { 440 ext = strings.ToLower(ext) 441 if len(IMAGE_MIME_TYPES[ext]) == 0 { 442 return "image" 443 } else { 444 return IMAGE_MIME_TYPES[ext] 445 } 446 } 447 448 func ClearMentionTags(post string) string { 449 post = strings.Replace(post, "<mention>", "", -1) 450 post = strings.Replace(post, "</mention>", "", -1) 451 return post 452 } 453 454 func IsValidHttpUrl(rawUrl string) bool { 455 if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { 456 return false 457 } 458 459 if _, err := url.ParseRequestURI(rawUrl); err != nil { 460 return false 461 } 462 463 return true 464 } 465 466 func IsValidTurnOrStunServer(rawUri string) bool { 467 if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { 468 return false 469 } 470 471 if _, err := url.ParseRequestURI(rawUri); err != nil { 472 return false 473 } 474 475 return true 476 } 477 478 func IsSafeLink(link *string) bool { 479 if link != nil { 480 if IsValidHttpUrl(*link) { 481 return true 482 } else if strings.HasPrefix(*link, "/") { 483 return true 484 } else { 485 return false 486 } 487 } 488 489 return true 490 } 491 492 func IsValidWebsocketUrl(rawUrl string) bool { 493 if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { 494 return false 495 } 496 497 if _, err := url.ParseRequestURI(rawUrl); err != nil { 498 return false 499 } 500 501 return true 502 } 503 504 func IsValidTrueOrFalseString(value string) bool { 505 return value == "true" || value == "false" 506 } 507 508 func IsValidNumberString(value string) bool { 509 if _, err := strconv.Atoi(value); err != nil { 510 return false 511 } 512 513 return true 514 } 515 516 func IsValidId(value string) bool { 517 if len(value) != 26 { 518 return false 519 } 520 521 for _, r := range value { 522 if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 523 return false 524 } 525 } 526 527 return true 528 } 529 530 // Copied from https://golang.org/src/net/dnsclient.go#L119 531 func IsDomainName(s string) bool { 532 // See RFC 1035, RFC 3696. 533 // Presentation format has dots before every label except the first, and the 534 // terminal empty label is optional here because we assume fully-qualified 535 // (absolute) input. We must therefore reserve space for the first and last 536 // labels' length octets in wire format, where they are necessary and the 537 // maximum total length is 255. 538 // So our _effective_ maximum is 253, but 254 is not rejected if the last 539 // character is a dot. 540 l := len(s) 541 if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { 542 return false 543 } 544 545 last := byte('.') 546 ok := false // Ok once we've seen a letter. 547 partlen := 0 548 for i := 0; i < len(s); i++ { 549 c := s[i] 550 switch { 551 default: 552 return false 553 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': 554 ok = true 555 partlen++ 556 case '0' <= c && c <= '9': 557 // fine 558 partlen++ 559 case c == '-': 560 // Byte before dash cannot be dot. 561 if last == '.' { 562 return false 563 } 564 partlen++ 565 case c == '.': 566 // Byte before dot cannot be dot, dash. 567 if last == '.' || last == '-' { 568 return false 569 } 570 if partlen > 63 || partlen == 0 { 571 return false 572 } 573 partlen = 0 574 } 575 last = c 576 } 577 if last == '-' || partlen > 63 { 578 return false 579 } 580 581 return ok 582 } 583 584 func RemoveDuplicateStrings(in []string) []string { 585 out := []string{} 586 seen := make(map[string]bool, len(in)) 587 588 for _, item := range in { 589 if !seen[item] { 590 out = append(out, item) 591 592 seen[item] = true 593 } 594 } 595 596 return out 597 } 598 599 func GetPreferredTimezone(timezone StringMap) string { 600 if timezone["useAutomaticTimezone"] == "true" { 601 return timezone["automaticTimezone"] 602 } 603 604 return timezone["manualTimezone"] 605 }