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